Merge branch 'main' into srawlins-patch-1
diff --git a/DEPS b/DEPS
index 325b82b..4fdea30 100644
--- a/DEPS
+++ b/DEPS
@@ -14,7 +14,7 @@
   'flutter_git': 'https://flutter.googlesource.com',
   'skia_git': 'https://skia.googlesource.com',
   'llvm_git': 'https://llvm.googlesource.com',
-  'skia_revision': '539ab9d398121cb2dc9a6d20a0643a59a4c15e02',
+  'skia_revision': '3a081993e2a740118839621e0b0fd206e045987e',
 
   # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY
   # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version.
@@ -56,28 +56,28 @@
   # Dart is: https://github.com/dart-lang/sdk/blob/main/DEPS
   # You can use //tools/dart/create_updated_flutter_deps.py to produce
   # updated revision list of existing dependencies.
-  'dart_revision': 'd916a5f69a486de98316900f19ef0ff46834b03d',
+  'dart_revision': '993d3069f42e98b2b29e441bc98424065cc255ca',
 
   # WARNING: DO NOT EDIT MANUALLY
   # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py
-  'dart_binaryen_rev': '459bc0797f67cb2a8fd4598bb7143b34036608d9',
+  'dart_binaryen_rev': '93883fde36ac158fd415dcd6dbd387dcfd928d3c',
   'dart_boringssl_gen_rev': 'fef055e8d2749b82c79c8f043be1cbe5e8e4b40c',
   'dart_boringssl_rev': '2db0eb3f96a5756298dcd7f9319e56a98585bd10',
   'dart_browser_launcher_rev': 'e5fc5d488eb5038bfec2a6690c72ab8dd353e101',
   'dart_clock_rev': '7956d60042f4ea979c4554d43eeb57d087627869',
-  'dart_collection_rev': '24b75d85df6a26aac7be13b56ff1ce4360c04a64',
+  'dart_collection_rev': '887b826b50f48d6a9cd2c0684aa353e8e3a0fad0',
   'dart_devtools_rev': 'dcef4f6efe986f110f55dbbe7f3731802e86690a',
   'dart_libprotobuf_rev': '24487dd1045c7f3d64a21f38a3f0c06cc4cf2edb',
   'dart_perfetto_rev': '13ce0c9e13b0940d2476cd0cff2301708a9a2e2b',
   'dart_protobuf_gn_rev': 'ca669f79945418f6229e4fef89b666b2a88cbb10',
   'dart_protobuf_rev': 'ccf104dbc36929c0f8708285d5f3a8fae206343e',
   'dart_pub_rev': '1efd3f5e274e153637d99698b0ee454f6f2550ab',
-  'dart_tools_rev': 'd4995d47b99d5e9564abfed2218f4a23df75983b',
+  'dart_tools_rev': 'f882de9ba86712003728d4663e1b73a620d352b1',
   'dart_watcher_rev': '3b850778ad0b62db3aa2cfe48832870c2461db30',
   'dart_web_rev': '8478cd27d574249eca3d41f9135458dfda2762c8',
   'dart_webdev_rev': '5f30c560dc4e3df341356c43ec1a766ee6b74a7c',
   'dart_webkit_inspection_protocol_rev': 'b459c427b74bf5e0919a083a97a167fb74d8bff1',
-  'dart_yaml_edit_rev': '5c54d455f272bbb83c948ac420c677371e69ae77',
+  'dart_yaml_edit_rev': '35f4248c7bbba289b3899fa55486e2f31ef1a8c5',
 
   'ocmock_rev': 'c4ec0e3a7a9f56cfdbd0aa01f4f97bb4b75c5ef8', # v3.7.1
 
@@ -343,16 +343,16 @@
   # WARNING: Unused Dart dependencies in the list below till "WARNING:" marker are removed automatically - see create_updated_flutter_deps.py.
 
   'src/flutter/third_party/dart/third_party/binaryen/src':
-   Var('chromium_git') + '/external/github.com/WebAssembly/binaryen.git@459bc0797f67cb2a8fd4598bb7143b34036608d9',
+   Var('chromium_git') + '/external/github.com/WebAssembly/binaryen.git@93883fde36ac158fd415dcd6dbd387dcfd928d3c',
 
   'src/flutter/third_party/dart/third_party/devtools':
-   {'dep_type': 'cipd', 'packages': [{'package': 'dart/third_party/flutter/devtools', 'version': 'git_revision:f5e84f91b32b219d646cfb87a891cd143dc84056'}]},
+   {'dep_type': 'cipd', 'packages': [{'package': 'dart/third_party/flutter/devtools', 'version': 'git_revision:dcef4f6efe986f110f55dbbe7f3731802e86690a'}]},
 
   'src/flutter/third_party/dart/third_party/pkg/args':
-   Var('dart_git') + '/args.git@e623652744c82533829f2e62b1aba1a6cf06e291',
+   Var('dart_git') + '/args.git@09c0fca1785c9df39288a48f767994eed80bed40',
 
   'src/flutter/third_party/dart/third_party/pkg/async':
-   Var('dart_git') + '/async.git@c0d81f8699682d01d657a9bf827107d11904a247',
+   Var('dart_git') + '/async.git@5f70a996f673d625e3502597084653686c3e754c',
 
   'src/flutter/third_party/dart/third_party/pkg/bazel_worker':
    Var('dart_git') + '/bazel_worker.git@aa3cc9e826350b960e0c5a67e6065bcedba8b0ac',
@@ -373,10 +373,10 @@
    Var('dart_git') + '/collection.git' + '@' + Var('dart_collection_rev'),
 
   'src/flutter/third_party/dart/third_party/pkg/convert':
-   Var('dart_git') + '/convert.git@9035cafefc1da4315f26058734d0c2a19d5ab56a',
+   Var('dart_git') + '/convert.git@d361833e117cb2438d2a2a6d0b0acb28ff0910fb',
 
   'src/flutter/third_party/dart/third_party/pkg/crypto':
-   Var('dart_git') + '/crypto.git@eede7d6918c51159c1422b7449f40dbac660ee57',
+   Var('dart_git') + '/crypto.git@3d26ef4cf22d4b218ba30e616544ad3cf52f64a1',
 
   'src/flutter/third_party/dart/third_party/pkg/csslib':
    Var('dart_git') + '/csslib.git@a3700b05bbcc42782e8a7024790dbf019d89c249',
@@ -385,7 +385,7 @@
    Var('dart_git') + '/dart_style.git@5d35f4d829ffb8532d345d95d3e9504ae6cd839e',
 
   'src/flutter/third_party/dart/third_party/pkg/dartdoc':
-   Var('dart_git') + '/dartdoc.git@5df03dd913a0a2e20421cac61112aa84111160e0',
+   Var('dart_git') + '/dartdoc.git@80c6f18f34b387d4b9ce89ddd2e3049093335f9d',
 
   'src/flutter/third_party/dart/third_party/pkg/file':
    Var('dart_git') + '/external/github.com/google/file.dart@6842feaef1c4e06239bd30f8d3ef722838b1c97e',
@@ -406,13 +406,13 @@
    Var('dart_git') + '/http_multi_server.git@e7515b5896b83d522189802a1e14e103e19426c0',
 
   'src/flutter/third_party/dart/third_party/pkg/http_parser':
-   Var('dart_git') + '/http_parser.git@ce528cf82f3d26ac761e29b2494a9e0c270d4939',
+   Var('dart_git') + '/http_parser.git@23d775898ee90be9daf3297e298a8869bc755d84',
 
   'src/flutter/third_party/dart/third_party/pkg/intl':
    Var('dart_git') + '/intl.git@5d65e3808ce40e6282e40881492607df4e35669f',
 
   'src/flutter/third_party/dart/third_party/pkg/json_rpc_2':
-   Var('dart_git') + '/json_rpc_2.git@b4810dc7bee5828f240586c81f3f34853cacdbce',
+   Var('dart_git') + '/json_rpc_2.git@c9b616bded8cdb5bfdc836ba7648afa6aba40062',
 
   'src/flutter/third_party/dart/third_party/pkg/leak_tracker':
    Var('dart_git') + '/leak_tracker.git@f5620600a5ce1c44f65ddaa02001e200b096e14c',
@@ -427,7 +427,7 @@
    Var('dart_git') + '/matcher.git@31f13583630e093731c8cf2b843c14196d748c5c',
 
   'src/flutter/third_party/dart/third_party/pkg/mockito':
-   Var('dart_git') + '/mockito.git@3de67548e833a8eef66a2a49070b197c2c08b3ab',
+   Var('dart_git') + '/mockito.git@57d484f9b8e7f6a504966a901174358a42fa932a',
 
   'src/flutter/third_party/dart/third_party/pkg/native':
    Var('dart_git') + '/native.git@659511886501bcce638c3966590df04984909ef0',
@@ -439,7 +439,7 @@
    Var('dart_git') + '/path.git@e969f42ed112dd702a9453beb9df6c12ae2d3805',
 
   'src/flutter/third_party/dart/third_party/pkg/pool':
-   Var('dart_git') + '/pool.git@924fb04353cec915d927f9f1aed88e2eda92b98a',
+   Var('dart_git') + '/pool.git@7bfc71b39742753a88688e56e55a828a2f5dc0bf',
 
   'src/flutter/third_party/dart/third_party/pkg/protobuf':
    Var('dart_git') + '/protobuf.git' + '@' + Var('dart_protobuf_rev'),
@@ -469,10 +469,10 @@
    Var('dart_git') + '/stream_channel.git@f4407168b275fcde9187baefd7dbce76d0992825',
 
   'src/flutter/third_party/dart/third_party/pkg/string_scanner':
-   Var('dart_git') + '/string_scanner.git@2139417ffcd0392bde3ba9bc83ee13eaa5fbed01',
+   Var('dart_git') + '/string_scanner.git@084b201c54b168aced178fff41fce71e3869ae42',
 
   'src/flutter/third_party/dart/third_party/pkg/tar':
-   Var('dart_git') + '/external/github.com/simolus3/tar.git@32ceb55e673141abff4e84b99483fe5eb881c291',
+   Var('dart_git') + '/external/github.com/simolus3/tar.git@5a1ea943e70cdf3fa5e1102cdbb9418bd9b4b81a',
 
   'src/flutter/third_party/dart/third_party/pkg/term_glyph':
    Var('dart_git') + '/term_glyph.git@19d8c08a4e81122639129c62049896021910c932',
@@ -487,7 +487,7 @@
    Var('dart_git') + '/tools.git' + '@' + Var('dart_tools_rev'),
 
   'src/flutter/third_party/dart/third_party/pkg/typed_data':
-   Var('dart_git') + '/typed_data.git@2bb9e6ead6394e2d4ec6068c5ece8b2ec0e2b945',
+   Var('dart_git') + '/typed_data.git@6abfafdcf661cd8a814619d7e2a3e99edb3a3862',
 
   'src/flutter/third_party/dart/third_party/pkg/watcher':
    Var('dart_git') + '/watcher.git' + '@' + Var('dart_watcher_rev'),
@@ -496,7 +496,7 @@
    Var('dart_git') + '/web.git' + '@' + Var('dart_web_rev'),
 
   'src/flutter/third_party/dart/third_party/pkg/web_socket_channel':
-   Var('dart_git') + '/web_socket_channel.git@0e1d6e2eb5a0bfd62e45b772ac7107d796176cf6',
+   Var('dart_git') + '/web_socket_channel.git@40aa29f1d2167467f5934d755891a8beb62a1239',
 
   'src/flutter/third_party/dart/third_party/pkg/webdev':
    Var('dart_git') + '/webdev.git' + '@' + Var('dart_webdev_rev'),
@@ -956,7 +956,7 @@
      'packages': [
        {
         'package': 'fuchsia/sdk/core/linux-amd64',
-        'version': 'T2Cq00sVabK2fCW9rsjkaWqRxX6RLvKGTNya2LeZ2MAC'
+        'version': '9F_NaKPd2twhbPwP7A4a6UlWT-_f9PVyLrS8ZFo-3UkC'
        }
      ],
      'condition': 'download_fuchsia_deps and not download_fuchsia_sdk',
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 771f879..248a9f2 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -212,7 +212,9 @@
     - unnecessary_null_in_if_null_operators
     - unnecessary_nullable_for_final_variable_declarations
     - unnecessary_overrides
-    - unnecessary_parenthesis
+    # TODO(zanderso): Temporarily disabling to unblock a Dart -> Engine roll.
+    # https://github.com/flutter/engine/pull/55927.
+    # - unnecessary_parenthesis
     # - unnecessary_raw_strings # what's "necessary" is a matter of opinion; consistency across strings can help readability more than this lint
     - unnecessary_statements
     - unnecessary_string_escapes
diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files
index b70659a..993fc88 100644
--- a/ci/licenses_golden/excluded_files
+++ b/ci/licenses_golden/excluded_files
@@ -1444,6 +1444,11 @@
 ../../../flutter/third_party/dart/runtime/tools/heapsnapshot/README.md
 ../../../flutter/third_party/dart/runtime/tools/heapsnapshot/pubspec.yaml
 ../../../flutter/third_party/dart/runtime/tools/heapsnapshot/test
+../../../flutter/third_party/dart/runtime/tools/profiling/.gitignore
+../../../flutter/third_party/dart/runtime/tools/profiling/CHANGELOG.md
+../../../flutter/third_party/dart/runtime/tools/profiling/README.md
+../../../flutter/third_party/dart/runtime/tools/profiling/analysis_options.yaml
+../../../flutter/third_party/dart/runtime/tools/profiling/pubspec.yaml
 ../../../flutter/third_party/dart/runtime/tools/utils.py
 ../../../flutter/third_party/dart/runtime/tools/valgrind.py
 ../../../flutter/third_party/dart/runtime/tools/wiki/README.md
@@ -1490,6 +1495,7 @@
 ../../../flutter/third_party/dart/runtime/vm/compiler/backend/locations_helpers_test.cc
 ../../../flutter/third_party/dart/runtime/vm/compiler/backend/loops_test.cc
 ../../../flutter/third_party/dart/runtime/vm/compiler/backend/memory_copy_test.cc
+../../../flutter/third_party/dart/runtime/vm/compiler/backend/pragma_unsafe_no_bounds_check_test.cc
 ../../../flutter/third_party/dart/runtime/vm/compiler/backend/range_analysis_test.cc
 ../../../flutter/third_party/dart/runtime/vm/compiler/backend/reachability_fence_test.cc
 ../../../flutter/third_party/dart/runtime/vm/compiler/backend/redundancy_elimination_test.cc
@@ -1565,7 +1571,7 @@
 ../../../flutter/third_party/dart/runtime/vm/port_test.cc
 ../../../flutter/third_party/dart/runtime/vm/profiler_test.cc
 ../../../flutter/third_party/dart/runtime/vm/protos/.gitignore
-../../../flutter/third_party/dart/runtime/vm/regexp_test.cc
+../../../flutter/third_party/dart/runtime/vm/regexp/regexp_test.cc
 ../../../flutter/third_party/dart/runtime/vm/ring_buffer_test.cc
 ../../../flutter/third_party/dart/runtime/vm/scopes_test.cc
 ../../../flutter/third_party/dart/runtime/vm/service
diff --git a/ci/licenses_golden/licenses_dart b/ci/licenses_golden/licenses_dart
index 6ec669e..7d85232 100644
--- a/ci/licenses_golden/licenses_dart
+++ b/ci/licenses_golden/licenses_dart
@@ -1,7 +1,8 @@
-Signature: 125335ea80d045b9f6f1a902f51abbc7
+Signature: 29033265e95e2a79cca60a10ae90b328
 
 ====================================================================================================
 LIBRARY: dart
+ORIGIN: http://www.apache.org/licenses/LICENSE-2.0 referenced by ../../../flutter/third_party/dart/runtime/tools/profiling/lib/src/pprof/profile.proto
 ORIGIN: http://www.apache.org/licenses/LICENSE-2.0 referenced by ../../../flutter/third_party/dart/runtime/vm/protos/perfetto/common/builtin_clock.proto
 ORIGIN: http://www.apache.org/licenses/LICENSE-2.0 referenced by ../../../flutter/third_party/dart/runtime/vm/protos/perfetto/trace/clock_snapshot.proto
 ORIGIN: http://www.apache.org/licenses/LICENSE-2.0 referenced by ../../../flutter/third_party/dart/runtime/vm/protos/perfetto/trace/interned_data/interned_data.proto
@@ -15,6 +16,7 @@
 ORIGIN: http://www.apache.org/licenses/LICENSE-2.0 referenced by ../../../flutter/third_party/dart/runtime/vm/protos/perfetto/trace/track_event/track_descriptor.proto
 ORIGIN: http://www.apache.org/licenses/LICENSE-2.0 referenced by ../../../flutter/third_party/dart/runtime/vm/protos/perfetto/trace/track_event/track_event.proto
 TYPE: LicenseType.apache
+FILE: ../../../flutter/third_party/dart/runtime/tools/profiling/lib/src/pprof/profile.proto
 FILE: ../../../flutter/third_party/dart/runtime/vm/protos/perfetto/common/builtin_clock.proto
 FILE: ../../../flutter/third_party/dart/runtime/vm/protos/perfetto/trace/clock_snapshot.proto
 FILE: ../../../flutter/third_party/dart/runtime/vm/protos/perfetto/trace/interned_data/interned_data.proto
@@ -1924,16 +1926,19 @@
 ORIGIN: ../../../flutter/third_party/dart/runtime/vm/metrics.h + ../../../flutter/third_party/dart/LICENSE
 ORIGIN: ../../../flutter/third_party/dart/runtime/vm/object_graph.cc + ../../../flutter/third_party/dart/LICENSE
 ORIGIN: ../../../flutter/third_party/dart/runtime/vm/object_graph.h + ../../../flutter/third_party/dart/LICENSE
-ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp.cc + ../../../flutter/third_party/dart/LICENSE
-ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp.h + ../../../flutter/third_party/dart/LICENSE
-ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp_assembler.cc + ../../../flutter/third_party/dart/LICENSE
-ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp_assembler.h + ../../../flutter/third_party/dart/LICENSE
-ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp_assembler_ir.cc + ../../../flutter/third_party/dart/LICENSE
-ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp_assembler_ir.h + ../../../flutter/third_party/dart/LICENSE
-ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp_ast.cc + ../../../flutter/third_party/dart/LICENSE
-ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp_ast.h + ../../../flutter/third_party/dart/LICENSE
-ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp_parser.cc + ../../../flutter/third_party/dart/LICENSE
-ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp_parser.h + ../../../flutter/third_party/dart/LICENSE
+ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp.cc + ../../../flutter/third_party/dart/LICENSE
+ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp.h + ../../../flutter/third_party/dart/LICENSE
+ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_assembler.cc + ../../../flutter/third_party/dart/LICENSE
+ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_assembler.h + ../../../flutter/third_party/dart/LICENSE
+ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_assembler_ir.cc + ../../../flutter/third_party/dart/LICENSE
+ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_assembler_ir.h + ../../../flutter/third_party/dart/LICENSE
+ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_ast.cc + ../../../flutter/third_party/dart/LICENSE
+ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_ast.h + ../../../flutter/third_party/dart/LICENSE
+ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_parser.cc + ../../../flutter/third_party/dart/LICENSE
+ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_parser.h + ../../../flutter/third_party/dart/LICENSE
+ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp/unibrow-inl.h + ../../../flutter/third_party/dart/LICENSE
+ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp/unibrow.cc + ../../../flutter/third_party/dart/LICENSE
+ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp/unibrow.h + ../../../flutter/third_party/dart/LICENSE
 ORIGIN: ../../../flutter/third_party/dart/runtime/vm/report.cc + ../../../flutter/third_party/dart/LICENSE
 ORIGIN: ../../../flutter/third_party/dart/runtime/vm/report.h + ../../../flutter/third_party/dart/LICENSE
 ORIGIN: ../../../flutter/third_party/dart/runtime/vm/ring_buffer.h + ../../../flutter/third_party/dart/LICENSE
@@ -1942,9 +1947,6 @@
 ORIGIN: ../../../flutter/third_party/dart/runtime/vm/simulator_arm64.h + ../../../flutter/third_party/dart/LICENSE
 ORIGIN: ../../../flutter/third_party/dart/runtime/vm/stack_frame_arm64.h + ../../../flutter/third_party/dart/LICENSE
 ORIGIN: ../../../flutter/third_party/dart/runtime/vm/tags.cc + ../../../flutter/third_party/dart/LICENSE
-ORIGIN: ../../../flutter/third_party/dart/runtime/vm/unibrow-inl.h + ../../../flutter/third_party/dart/LICENSE
-ORIGIN: ../../../flutter/third_party/dart/runtime/vm/unibrow.cc + ../../../flutter/third_party/dart/LICENSE
-ORIGIN: ../../../flutter/third_party/dart/runtime/vm/unibrow.h + ../../../flutter/third_party/dart/LICENSE
 ORIGIN: ../../../flutter/third_party/dart/sdk/lib/_internal/js_dev_runtime/private/preambles/d8.js + ../../../flutter/third_party/dart/LICENSE
 ORIGIN: ../../../flutter/third_party/dart/sdk/lib/_internal/js_dev_runtime/private/preambles/jsshell.js + ../../../flutter/third_party/dart/LICENSE
 ORIGIN: ../../../flutter/third_party/dart/sdk/lib/_internal/js_runtime/lib/linked_hash_map.dart + ../../../flutter/third_party/dart/LICENSE
@@ -2016,16 +2018,19 @@
 FILE: ../../../flutter/third_party/dart/runtime/vm/metrics.h
 FILE: ../../../flutter/third_party/dart/runtime/vm/object_graph.cc
 FILE: ../../../flutter/third_party/dart/runtime/vm/object_graph.h
-FILE: ../../../flutter/third_party/dart/runtime/vm/regexp.cc
-FILE: ../../../flutter/third_party/dart/runtime/vm/regexp.h
-FILE: ../../../flutter/third_party/dart/runtime/vm/regexp_assembler.cc
-FILE: ../../../flutter/third_party/dart/runtime/vm/regexp_assembler.h
-FILE: ../../../flutter/third_party/dart/runtime/vm/regexp_assembler_ir.cc
-FILE: ../../../flutter/third_party/dart/runtime/vm/regexp_assembler_ir.h
-FILE: ../../../flutter/third_party/dart/runtime/vm/regexp_ast.cc
-FILE: ../../../flutter/third_party/dart/runtime/vm/regexp_ast.h
-FILE: ../../../flutter/third_party/dart/runtime/vm/regexp_parser.cc
-FILE: ../../../flutter/third_party/dart/runtime/vm/regexp_parser.h
+FILE: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp.cc
+FILE: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp.h
+FILE: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_assembler.cc
+FILE: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_assembler.h
+FILE: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_assembler_ir.cc
+FILE: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_assembler_ir.h
+FILE: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_ast.cc
+FILE: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_ast.h
+FILE: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_parser.cc
+FILE: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_parser.h
+FILE: ../../../flutter/third_party/dart/runtime/vm/regexp/unibrow-inl.h
+FILE: ../../../flutter/third_party/dart/runtime/vm/regexp/unibrow.cc
+FILE: ../../../flutter/third_party/dart/runtime/vm/regexp/unibrow.h
 FILE: ../../../flutter/third_party/dart/runtime/vm/report.cc
 FILE: ../../../flutter/third_party/dart/runtime/vm/report.h
 FILE: ../../../flutter/third_party/dart/runtime/vm/ring_buffer.h
@@ -2034,9 +2039,6 @@
 FILE: ../../../flutter/third_party/dart/runtime/vm/simulator_arm64.h
 FILE: ../../../flutter/third_party/dart/runtime/vm/stack_frame_arm64.h
 FILE: ../../../flutter/third_party/dart/runtime/vm/tags.cc
-FILE: ../../../flutter/third_party/dart/runtime/vm/unibrow-inl.h
-FILE: ../../../flutter/third_party/dart/runtime/vm/unibrow.cc
-FILE: ../../../flutter/third_party/dart/runtime/vm/unibrow.h
 FILE: ../../../flutter/third_party/dart/sdk/lib/_internal/js_dev_runtime/private/preambles/d8.js
 FILE: ../../../flutter/third_party/dart/sdk/lib/_internal/js_dev_runtime/private/preambles/jsshell.js
 FILE: ../../../flutter/third_party/dart/sdk/lib/_internal/js_runtime/lib/linked_hash_map.dart
@@ -2116,12 +2118,12 @@
 ORIGIN: ../../../flutter/third_party/dart/runtime/vm/profiler_service.h + ../../../flutter/third_party/dart/LICENSE
 ORIGIN: ../../../flutter/third_party/dart/runtime/vm/program_visitor.cc + ../../../flutter/third_party/dart/LICENSE
 ORIGIN: ../../../flutter/third_party/dart/runtime/vm/program_visitor.h + ../../../flutter/third_party/dart/LICENSE
-ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp_assembler_bytecode.cc + ../../../flutter/third_party/dart/LICENSE
-ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp_assembler_bytecode.h + ../../../flutter/third_party/dart/LICENSE
-ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp_assembler_bytecode_inl.h + ../../../flutter/third_party/dart/LICENSE
-ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp_bytecodes.h + ../../../flutter/third_party/dart/LICENSE
-ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp_interpreter.cc + ../../../flutter/third_party/dart/LICENSE
-ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp_interpreter.h + ../../../flutter/third_party/dart/LICENSE
+ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_assembler_bytecode.cc + ../../../flutter/third_party/dart/LICENSE
+ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_assembler_bytecode.h + ../../../flutter/third_party/dart/LICENSE
+ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_assembler_bytecode_inl.h + ../../../flutter/third_party/dart/LICENSE
+ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_bytecodes.h + ../../../flutter/third_party/dart/LICENSE
+ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_interpreter.cc + ../../../flutter/third_party/dart/LICENSE
+ORIGIN: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_interpreter.h + ../../../flutter/third_party/dart/LICENSE
 ORIGIN: ../../../flutter/third_party/dart/runtime/vm/scope_timer.h + ../../../flutter/third_party/dart/LICENSE
 ORIGIN: ../../../flutter/third_party/dart/runtime/vm/service_event.cc + ../../../flutter/third_party/dart/LICENSE
 ORIGIN: ../../../flutter/third_party/dart/runtime/vm/service_event.h + ../../../flutter/third_party/dart/LICENSE
@@ -2195,12 +2197,12 @@
 FILE: ../../../flutter/third_party/dart/runtime/vm/profiler_service.h
 FILE: ../../../flutter/third_party/dart/runtime/vm/program_visitor.cc
 FILE: ../../../flutter/third_party/dart/runtime/vm/program_visitor.h
-FILE: ../../../flutter/third_party/dart/runtime/vm/regexp_assembler_bytecode.cc
-FILE: ../../../flutter/third_party/dart/runtime/vm/regexp_assembler_bytecode.h
-FILE: ../../../flutter/third_party/dart/runtime/vm/regexp_assembler_bytecode_inl.h
-FILE: ../../../flutter/third_party/dart/runtime/vm/regexp_bytecodes.h
-FILE: ../../../flutter/third_party/dart/runtime/vm/regexp_interpreter.cc
-FILE: ../../../flutter/third_party/dart/runtime/vm/regexp_interpreter.h
+FILE: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_assembler_bytecode.cc
+FILE: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_assembler_bytecode.h
+FILE: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_assembler_bytecode_inl.h
+FILE: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_bytecodes.h
+FILE: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_interpreter.cc
+FILE: ../../../flutter/third_party/dart/runtime/vm/regexp/regexp_interpreter.h
 FILE: ../../../flutter/third_party/dart/runtime/vm/scope_timer.h
 FILE: ../../../flutter/third_party/dart/runtime/vm/service_event.cc
 FILE: ../../../flutter/third_party/dart/runtime/vm/service_event.h
@@ -4074,6 +4076,11 @@
 ORIGIN: ../../../flutter/third_party/dart/runtime/platform/synchronization_win.cc + ../../../flutter/third_party/dart/LICENSE
 ORIGIN: ../../../flutter/third_party/dart/runtime/platform/threads.h + ../../../flutter/third_party/dart/LICENSE
 ORIGIN: ../../../flutter/third_party/dart/runtime/tools/dartfuzz/flag_fuzzer.dart + ../../../flutter/third_party/dart/LICENSE
+ORIGIN: ../../../flutter/third_party/dart/runtime/tools/profiling/bin/convert_allocation_profile.dart + ../../../flutter/third_party/dart/LICENSE
+ORIGIN: ../../../flutter/third_party/dart/runtime/tools/profiling/bin/set_uprobe.dart + ../../../flutter/third_party/dart/LICENSE
+ORIGIN: ../../../flutter/third_party/dart/runtime/tools/profiling/lib/src/elf_utils.dart + ../../../flutter/third_party/dart/LICENSE
+ORIGIN: ../../../flutter/third_party/dart/runtime/tools/profiling/lib/src/perf/perf_data.dart + ../../../flutter/third_party/dart/LICENSE
+ORIGIN: ../../../flutter/third_party/dart/runtime/tools/profiling/lib/src/symbols.dart + ../../../flutter/third_party/dart/LICENSE
 ORIGIN: ../../../flutter/third_party/dart/runtime/vm/bytecode_reader.cc + ../../../flutter/third_party/dart/LICENSE
 ORIGIN: ../../../flutter/third_party/dart/runtime/vm/bytecode_reader.h + ../../../flutter/third_party/dart/LICENSE
 ORIGIN: ../../../flutter/third_party/dart/runtime/vm/compiler/assembler/disassembler_kbc.cc + ../../../flutter/third_party/dart/LICENSE
@@ -4120,6 +4127,11 @@
 FILE: ../../../flutter/third_party/dart/runtime/platform/synchronization_win.cc
 FILE: ../../../flutter/third_party/dart/runtime/platform/threads.h
 FILE: ../../../flutter/third_party/dart/runtime/tools/dartfuzz/flag_fuzzer.dart
+FILE: ../../../flutter/third_party/dart/runtime/tools/profiling/bin/convert_allocation_profile.dart
+FILE: ../../../flutter/third_party/dart/runtime/tools/profiling/bin/set_uprobe.dart
+FILE: ../../../flutter/third_party/dart/runtime/tools/profiling/lib/src/elf_utils.dart
+FILE: ../../../flutter/third_party/dart/runtime/tools/profiling/lib/src/perf/perf_data.dart
+FILE: ../../../flutter/third_party/dart/runtime/tools/profiling/lib/src/symbols.dart
 FILE: ../../../flutter/third_party/dart/runtime/vm/bytecode_reader.cc
 FILE: ../../../flutter/third_party/dart/runtime/vm/bytecode_reader.h
 FILE: ../../../flutter/third_party/dart/runtime/vm/compiler/assembler/disassembler_kbc.cc
@@ -4371,6 +4383,10 @@
 FILE: ../../../flutter/third_party/dart/runtime/tools/entitlements/gen_snapshot.plist
 FILE: ../../../flutter/third_party/dart/runtime/tools/entitlements/gen_snapshot_product.plist
 FILE: ../../../flutter/third_party/dart/runtime/tools/entitlements/run_vm_tests.plist
+FILE: ../../../flutter/third_party/dart/runtime/tools/profiling/lib/src/pprof/generated/profile.pb.dart
+FILE: ../../../flutter/third_party/dart/runtime/tools/profiling/lib/src/pprof/generated/profile.pbenum.dart
+FILE: ../../../flutter/third_party/dart/runtime/tools/profiling/lib/src/pprof/generated/profile.pbjson.dart
+FILE: ../../../flutter/third_party/dart/runtime/tools/profiling/lib/src/pprof/generated/profile.pbserver.dart
 FILE: ../../../flutter/third_party/dart/runtime/tools/wiki/styles/style.scss
 FILE: ../../../flutter/third_party/dart/runtime/tools/wiki/templates/includes/auto-refresh.html
 FILE: ../../../flutter/third_party/dart/runtime/tools/wiki/templates/page.html
@@ -4787,7 +4803,7 @@
   This Source Code Form is "Incompatible With Secondary Licenses", as
   defined by the Mozilla Public License, v. 2.0.
 
-You may obtain a copy of this library's Source Code Form from: https://dart.googlesource.com/sdk/+/115a3c609bf22d78d532ea9a9ecbcfb11ca9764f
+You may obtain a copy of this library's Source Code Form from: https://dart.googlesource.com/sdk/+/993d3069f42e98b2b29e441bc98424065cc255ca
 /third_party/fallback_root_certificates/
 
 ====================================================================================================
diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index fd9a96d..283a93a 100644
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -44799,6 +44799,9 @@
 ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_layout.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_layout.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_layout_test.cc + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_manager.cc + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_manager.h + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_manager_test.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_pending_event.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_pending_event.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_view_delegate.cc + ../../../flutter/LICENSE
@@ -47699,6 +47702,9 @@
 FILE: ../../../flutter/shell/platform/linux/fl_keyboard_layout.cc
 FILE: ../../../flutter/shell/platform/linux/fl_keyboard_layout.h
 FILE: ../../../flutter/shell/platform/linux/fl_keyboard_layout_test.cc
+FILE: ../../../flutter/shell/platform/linux/fl_keyboard_manager.cc
+FILE: ../../../flutter/shell/platform/linux/fl_keyboard_manager.h
+FILE: ../../../flutter/shell/platform/linux/fl_keyboard_manager_test.cc
 FILE: ../../../flutter/shell/platform/linux/fl_keyboard_pending_event.cc
 FILE: ../../../flutter/shell/platform/linux/fl_keyboard_pending_event.h
 FILE: ../../../flutter/shell/platform/linux/fl_keyboard_view_delegate.cc
diff --git a/ci/licenses_golden/licenses_fuchsia b/ci/licenses_golden/licenses_fuchsia
index d4b5ecb..4d22975 100644
--- a/ci/licenses_golden/licenses_fuchsia
+++ b/ci/licenses_golden/licenses_fuchsia
@@ -1,4 +1,4 @@
-Signature: 147bb5b1f6ce1d858d13c192ce702004
+Signature: e6795c8a2a5ff5824f7e5b3c503cf201
 
 ====================================================================================================
 LIBRARY: fuchsia_sdk
@@ -14859,7 +14859,6 @@
 ORIGIN: ../../../fuchsia/sdk/linux/pkg/stdcompat/include/lib/stdcompat/internal/exception.h + ../../../fuchsia/sdk/linux/LICENSE
 ORIGIN: ../../../fuchsia/sdk/linux/pkg/stdcompat/include/lib/stdcompat/internal/functional.h + ../../../fuchsia/sdk/linux/LICENSE
 ORIGIN: ../../../fuchsia/sdk/linux/pkg/stdcompat/include/lib/stdcompat/internal/span.h + ../../../fuchsia/sdk/linux/LICENSE
-ORIGIN: ../../../fuchsia/sdk/linux/pkg/stdcompat/include/lib/stdcompat/internal/tuple.h + ../../../fuchsia/sdk/linux/LICENSE
 ORIGIN: ../../../fuchsia/sdk/linux/pkg/stdcompat/include/lib/stdcompat/internal/type_traits.h + ../../../fuchsia/sdk/linux/LICENSE
 ORIGIN: ../../../fuchsia/sdk/linux/pkg/stdcompat/include/lib/stdcompat/iterator.h + ../../../fuchsia/sdk/linux/LICENSE
 ORIGIN: ../../../fuchsia/sdk/linux/pkg/stdcompat/include/lib/stdcompat/span.h + ../../../fuchsia/sdk/linux/LICENSE
@@ -15042,7 +15041,6 @@
 FILE: ../../../fuchsia/sdk/linux/pkg/stdcompat/include/lib/stdcompat/internal/exception.h
 FILE: ../../../fuchsia/sdk/linux/pkg/stdcompat/include/lib/stdcompat/internal/functional.h
 FILE: ../../../fuchsia/sdk/linux/pkg/stdcompat/include/lib/stdcompat/internal/span.h
-FILE: ../../../fuchsia/sdk/linux/pkg/stdcompat/include/lib/stdcompat/internal/tuple.h
 FILE: ../../../fuchsia/sdk/linux/pkg/stdcompat/include/lib/stdcompat/internal/type_traits.h
 FILE: ../../../fuchsia/sdk/linux/pkg/stdcompat/include/lib/stdcompat/iterator.h
 FILE: ../../../fuchsia/sdk/linux/pkg/stdcompat/include/lib/stdcompat/span.h
@@ -15415,7 +15413,6 @@
 ORIGIN: ../../../fuchsia/sdk/linux/pkg/scenic_cpp_testing/fake_flatland.cc + ../../../fuchsia/sdk/linux/LICENSE
 ORIGIN: ../../../fuchsia/sdk/linux/pkg/scenic_cpp_testing/fake_flatland_types.cc + ../../../fuchsia/sdk/linux/LICENSE
 ORIGIN: ../../../fuchsia/sdk/linux/pkg/stdcompat/include/lib/stdcompat/internal/linkage.h + ../../../fuchsia/sdk/linux/LICENSE
-ORIGIN: ../../../fuchsia/sdk/linux/pkg/stdcompat/include/lib/stdcompat/internal/variant.h + ../../../fuchsia/sdk/linux/LICENSE
 ORIGIN: ../../../fuchsia/sdk/linux/pkg/sys/component/realm_builder_absolute.shard.cml + ../../../fuchsia/sdk/linux/LICENSE
 ORIGIN: ../../../fuchsia/sdk/linux/pkg/sys/component/realm_builder_base.shard.cml + ../../../fuchsia/sdk/linux/LICENSE
 ORIGIN: ../../../fuchsia/sdk/linux/pkg/sys_component_cpp_testing/internal/convert.cc + ../../../fuchsia/sdk/linux/LICENSE
@@ -15769,7 +15766,6 @@
 FILE: ../../../fuchsia/sdk/linux/pkg/scenic_cpp_testing/fake_flatland.cc
 FILE: ../../../fuchsia/sdk/linux/pkg/scenic_cpp_testing/fake_flatland_types.cc
 FILE: ../../../fuchsia/sdk/linux/pkg/stdcompat/include/lib/stdcompat/internal/linkage.h
-FILE: ../../../fuchsia/sdk/linux/pkg/stdcompat/include/lib/stdcompat/internal/variant.h
 FILE: ../../../fuchsia/sdk/linux/pkg/sys/component/realm_builder_absolute.shard.cml
 FILE: ../../../fuchsia/sdk/linux/pkg/sys/component/realm_builder_base.shard.cml
 FILE: ../../../fuchsia/sdk/linux/pkg/sys_component_cpp_testing/internal/convert.cc
@@ -16126,6 +16122,8 @@
 ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-23/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE
 ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE
 ORIGIN: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/availability_levels.inc + ../../../fuchsia/sdk/linux/LICENSE
+ORIGIN: ../../../fuchsia/sdk/linux/pkg/driver_incoming_cpp/include/lib/driver/incoming/cpp/service_validator.h + ../../../fuchsia/sdk/linux/LICENSE
+ORIGIN: ../../../fuchsia/sdk/linux/pkg/driver_incoming_cpp/service_validator.cc + ../../../fuchsia/sdk/linux/LICENSE
 ORIGIN: ../../../fuchsia/sdk/linux/pkg/driver_metadata_cpp/include/lib/driver/metadata/cpp/metadata.h + ../../../fuchsia/sdk/linux/LICENSE
 ORIGIN: ../../../fuchsia/sdk/linux/pkg/driver_metadata_cpp/include/lib/driver/metadata/cpp/metadata_server.h + ../../../fuchsia/sdk/linux/LICENSE
 ORIGIN: ../../../fuchsia/sdk/linux/pkg/driver_node_cpp/add_child.cc + ../../../fuchsia/sdk/linux/LICENSE
@@ -16148,6 +16146,7 @@
 ORIGIN: ../../../fuchsia/sdk/linux/pkg/syslog_cpp/include/lib/syslog/cpp/log_message_impl.h + ../../../fuchsia/sdk/linux/LICENSE
 ORIGIN: ../../../fuchsia/sdk/linux/pkg/syslog_cpp/log_message_impl.cc + ../../../fuchsia/sdk/linux/LICENSE
 ORIGIN: ../../../fuchsia/sdk/linux/pkg/vulkan/offer.shard.cml + ../../../fuchsia/sdk/linux/LICENSE
+ORIGIN: ../../../fuchsia/sdk/linux/pkg/zx/time.cc + ../../../fuchsia/sdk/linux/LICENSE
 TYPE: LicenseType.bsd
 FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/availability_levels.inc
 FILE: ../../../fuchsia/sdk/linux/arch/riscv64/sysroot/include/zircon/availability_levels.inc
@@ -16208,6 +16207,8 @@
 FILE: ../../../fuchsia/sdk/linux/obj/x64-api-23/sysroot/include/zircon/availability_levels.inc
 FILE: ../../../fuchsia/sdk/linux/obj/x64-api-24/sysroot/include/zircon/availability_levels.inc
 FILE: ../../../fuchsia/sdk/linux/obj/x64-api-NEXT/sysroot/include/zircon/availability_levels.inc
+FILE: ../../../fuchsia/sdk/linux/pkg/driver_incoming_cpp/include/lib/driver/incoming/cpp/service_validator.h
+FILE: ../../../fuchsia/sdk/linux/pkg/driver_incoming_cpp/service_validator.cc
 FILE: ../../../fuchsia/sdk/linux/pkg/driver_metadata_cpp/include/lib/driver/metadata/cpp/metadata.h
 FILE: ../../../fuchsia/sdk/linux/pkg/driver_metadata_cpp/include/lib/driver/metadata/cpp/metadata_server.h
 FILE: ../../../fuchsia/sdk/linux/pkg/driver_node_cpp/add_child.cc
@@ -16230,6 +16231,7 @@
 FILE: ../../../fuchsia/sdk/linux/pkg/syslog_cpp/include/lib/syslog/cpp/log_message_impl.h
 FILE: ../../../fuchsia/sdk/linux/pkg/syslog_cpp/log_message_impl.cc
 FILE: ../../../fuchsia/sdk/linux/pkg/vulkan/offer.shard.cml
+FILE: ../../../fuchsia/sdk/linux/pkg/zx/time.cc
 ----------------------------------------------------------------------------------------------------
 Copyright 2024 The Fuchsia Authors. All rights reserved.
 
diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia
index 844c362..a96a0bb 100644
--- a/ci/licenses_golden/licenses_skia
+++ b/ci/licenses_golden/licenses_skia
@@ -1,4 +1,4 @@
-Signature: 015672489e829391bd5d2de2efd67231
+Signature: 83c4ecafc6e635226445e328864ab843
 
 ====================================================================================================
 LIBRARY: etc1
diff --git a/impeller/core/device_buffer.h b/impeller/core/device_buffer.h
index 07dba77..7fa2f3e 100644
--- a/impeller/core/device_buffer.h
+++ b/impeller/core/device_buffer.h
@@ -23,9 +23,9 @@
                                     Range source_range,
                                     size_t offset = 0u);
 
-  virtual bool SetLabel(const std::string& label) = 0;
+  virtual bool SetLabel(std::string_view label) = 0;
 
-  virtual bool SetLabel(const std::string& label, Range range) = 0;
+  virtual bool SetLabel(std::string_view label, Range range) = 0;
 
   /// @brief Create a buffer view of this entire buffer.
   static BufferView AsBufferView(std::shared_ptr<DeviceBuffer> buffer);
diff --git a/impeller/core/host_buffer.cc b/impeller/core/host_buffer.cc
index 69b8c1c..1bee375 100644
--- a/impeller/core/host_buffer.cc
+++ b/impeller/core/host_buffer.cc
@@ -37,10 +37,6 @@
 
 HostBuffer::~HostBuffer() = default;
 
-void HostBuffer::SetLabel(std::string label) {
-  label_ = std::move(label);
-}
-
 BufferView HostBuffer::Emplace(const void* buffer,
                                size_t length,
                                size_t align) {
diff --git a/impeller/core/host_buffer.h b/impeller/core/host_buffer.h
index 90ab30d..43c03c3 100644
--- a/impeller/core/host_buffer.h
+++ b/impeller/core/host_buffer.h
@@ -30,10 +30,7 @@
   static std::shared_ptr<HostBuffer> Create(
       const std::shared_ptr<Allocator>& allocator);
 
-  // |Buffer|
-  virtual ~HostBuffer();
-
-  void SetLabel(std::string label);
+  ~HostBuffer();
 
   //----------------------------------------------------------------------------
   /// @brief      Emplace uniform data onto the host buffer. Ensure that backend
@@ -169,7 +166,6 @@
   size_t current_buffer_ = 0u;
   size_t offset_ = 0u;
   size_t frame_index_ = 0u;
-  std::string label_;
 };
 
 }  // namespace impeller
diff --git a/impeller/display_list/aiks_dl_basic_unittests.cc b/impeller/display_list/aiks_dl_basic_unittests.cc
index 4dcfc75..39e5d05 100644
--- a/impeller/display_list/aiks_dl_basic_unittests.cc
+++ b/impeller/display_list/aiks_dl_basic_unittests.cc
@@ -94,7 +94,7 @@
 namespace {
 bool GenerateMipmap(const std::shared_ptr<Context>& context,
                     std::shared_ptr<Texture> texture,
-                    std::string label) {
+                    std::string_view label) {
   auto buffer = context->CreateCommandBuffer();
   if (!buffer) {
     return false;
@@ -103,7 +103,7 @@
   if (!pass) {
     return false;
   }
-  pass->GenerateMipmap(std::move(texture), std::move(label));
+  pass->GenerateMipmap(std::move(texture), label);
 
   pass->EncodeCommands(context->GetResourceAllocator());
   return context->GetCommandQueue()->Submit({buffer}).ok();
diff --git a/impeller/display_list/aiks_dl_blend_unittests.cc b/impeller/display_list/aiks_dl_blend_unittests.cc
index 41c57e8..f4691f6 100644
--- a/impeller/display_list/aiks_dl_blend_unittests.cc
+++ b/impeller/display_list/aiks_dl_blend_unittests.cc
@@ -888,5 +888,22 @@
   ASSERT_TRUE(OpenPlaygroundHere(callback));
 }
 
+TEST_P(AiksTest, DestructiveBlendColorFilterFloodsClip) {
+  DisplayListBuilder builder;
+
+  DlPaint paint;
+  paint.setColor(DlColor::kBlue());
+  builder.DrawPaint(paint);
+
+  DlPaint save_paint;
+  save_paint.setColorFilter(
+      DlBlendColorFilter::Make(DlColor::kRed(), DlBlendMode::kSrc));
+  builder.SaveLayer(nullptr, &save_paint);
+  builder.Restore();
+
+  // Should be solid red as the destructive color filter floods the clip.
+  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
+}
+
 }  // namespace testing
 }  // namespace impeller
diff --git a/impeller/display_list/aiks_dl_unittests.cc b/impeller/display_list/aiks_dl_unittests.cc
index 236345f..279b7bc 100644
--- a/impeller/display_list/aiks_dl_unittests.cc
+++ b/impeller/display_list/aiks_dl_unittests.cc
@@ -160,12 +160,15 @@
   paint.setColor(DlColor::kBlack().withAlpha(128));
   paint.setColorFilter(
       DlBlendColorFilter::Make(DlColor::kRed(), DlBlendMode::kDstOver));
+  builder.Save();
+  builder.ClipRect(SkRect::MakeXYWH(100, 500, 300, 300));
   builder.SaveLayer(nullptr, &paint);
 
   DlPaint draw_paint;
   draw_paint.setColor(DlColor::kBlue());
   builder.DrawRect(SkRect::MakeXYWH(100, 500, 300, 300), draw_paint);
   builder.Restore();
+  builder.Restore();
 
   ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
 }
@@ -203,12 +206,15 @@
   save_paint.setColor(DlColor::kBlack().withAlpha(128));
   save_paint.setColorFilter(
       DlBlendColorFilter::Make(DlColor::kRed(), DlBlendMode::kDstOver));
+  builder.Save();
+  builder.ClipRect(SkRect::MakeXYWH(100, 500, 300, 300));
   builder.SaveLayer(nullptr, &save_paint);
 
   DlPaint draw_paint;
   draw_paint.setColor(DlColor::kBlue());
   builder.DrawRect(SkRect::MakeXYWH(100, 500, 300, 300), draw_paint);
   builder.Restore();
+  builder.Restore();
 
   ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
 }
diff --git a/impeller/display_list/canvas.cc b/impeller/display_list/canvas.cc
index d14db69..dedb9c3 100644
--- a/impeller/display_list/canvas.cc
+++ b/impeller/display_list/canvas.cc
@@ -1015,7 +1015,9 @@
       filter_contents,                    //
       /*flood_output_coverage=*/
       Entity::IsBlendModeDestructive(paint.blend_mode),  //
-      /*flood_input_coverage=*/!!backdrop_filter         //
+      /*flood_input_coverage=*/!!backdrop_filter ||
+          (paint.color_filter &&
+           paint.color_filter->modifies_transparent_black())  //
   );
 
   if (!maybe_subpass_coverage.has_value()) {
diff --git a/impeller/entity/contents/atlas_contents.cc b/impeller/entity/contents/atlas_contents.cc
index 19053b0..3addc0b 100644
--- a/impeller/entity/contents/atlas_contents.cc
+++ b/impeller/entity/contents/atlas_contents.cc
@@ -94,8 +94,7 @@
     using FS = PorterDuffBlendPipeline::FragmentShader;
 
 #ifdef IMPELLER_DEBUG
-    pass.SetCommandLabel(
-        SPrintF("DrawAtlas Blend (%s)", BlendModeToString(blend_mode)));
+    pass.SetCommandLabel("DrawAtlas Blend");
 #endif  // IMPELLER_DEBUG
     pass.SetVertexBuffer(geometry_->CreateBlendVertexBuffer(host_buffer));
     pass.SetPipeline(
@@ -138,8 +137,7 @@
   using FS = VerticesUberShader::FragmentShader;
 
 #ifdef IMPELLER_DEBUG
-  pass.SetCommandLabel(
-      SPrintF("DrawAtlas Advanced Blend (%s)", BlendModeToString(blend_mode)));
+  pass.SetCommandLabel("DrawAtlas Advanced Blend");
 #endif  // IMPELLER_DEBUG
   pass.SetVertexBuffer(geometry_->CreateBlendVertexBuffer(host_buffer));
 
diff --git a/impeller/entity/contents/content_context.cc b/impeller/entity/contents/content_context.cc
index a563c2c..70d1df7 100644
--- a/impeller/entity/contents/content_context.cc
+++ b/impeller/entity/contents/content_context.cc
@@ -493,12 +493,12 @@
   if (context->GetCapabilities()->SupportsOffscreenMSAA() && msaa_enabled) {
     subpass_target = GetRenderTargetCache()->CreateOffscreenMSAA(
         *context, texture_size,
-        /*mip_count=*/mip_count, SPrintF("%s Offscreen", label.data()),
+        /*mip_count=*/mip_count, label,
         RenderTarget::kDefaultColorAttachmentConfigMSAA, depth_stencil_config);
   } else {
     subpass_target = GetRenderTargetCache()->CreateOffscreen(
         *context, texture_size,
-        /*mip_count=*/mip_count, SPrintF("%s Offscreen", label.data()),
+        /*mip_count=*/mip_count, label,
         RenderTarget::kDefaultColorAttachmentConfig, depth_stencil_config);
   }
   return MakeSubpass(label, subpass_target, command_buffer, subpass_callback);
@@ -520,7 +520,7 @@
   if (!sub_renderpass) {
     return fml::Status(fml::StatusCode::kUnknown, "");
   }
-  sub_renderpass->SetLabel(SPrintF("%s RenderPass", label.data()));
+  sub_renderpass->SetLabel(label);
 
   if (!subpass_callback(*this, *sub_renderpass)) {
     return fml::Status(fml::StatusCode::kUnknown, "");
diff --git a/impeller/entity/contents/content_context.h b/impeller/entity/contents/content_context.h
index c2fe1fc..9abdc51 100644
--- a/impeller/entity/contents/content_context.h
+++ b/impeller/entity/contents/content_context.h
@@ -992,7 +992,7 @@
                              PipelineDescriptor& desc) {
           opts.ApplyToPipelineDescriptor(desc);
           desc.SetLabel(
-              SPrintF("%s V#%zu", desc.GetLabel().c_str(), variants_count));
+              SPrintF("%s V#%zu", desc.GetLabel().data(), variants_count));
         });
     std::unique_ptr<RenderPipelineHandleT> variant =
         std::make_unique<RenderPipelineHandleT>(std::move(variant_future));
diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc
index d2e39ea..b70c717 100644
--- a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc
+++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc
@@ -239,7 +239,7 @@
   //     .Contains(coverage_hint.value()))
 
   std::optional<Rect> snapshot_coverage = input_snapshot.GetCoverage();
-  if (input_snapshot.transform.IsIdentity() &&
+  if (input_snapshot.transform.Equals(snapshot_entity.GetTransform()) &&
       source_expanded_coverage_hint.has_value() &&
       snapshot_coverage.has_value() &&
       snapshot_coverage->Contains(source_expanded_coverage_hint.value())) {
diff --git a/impeller/entity/contents/test/recording_render_pass.cc b/impeller/entity/contents/test/recording_render_pass.cc
index dcc35cc..da36a5f 100644
--- a/impeller/entity/contents/test/recording_render_pass.cc
+++ b/impeller/entity/contents/test/recording_render_pass.cc
@@ -92,7 +92,7 @@
 }
 
 // |RenderPass|
-void RecordingRenderPass::OnSetLabel(std::string label) {
+void RecordingRenderPass::OnSetLabel(std::string_view label) {
   return;
 }
 
diff --git a/impeller/entity/contents/test/recording_render_pass.h b/impeller/entity/contents/test/recording_render_pass.h
index 12fd10c..ffbef6f 100644
--- a/impeller/entity/contents/test/recording_render_pass.h
+++ b/impeller/entity/contents/test/recording_render_pass.h
@@ -69,7 +69,7 @@
                     const std::unique_ptr<const Sampler>& sampler) override;
 
   // |RenderPass|
-  void OnSetLabel(std::string label) override;
+  void OnSetLabel(std::string_view label) override;
 
   // |RenderPass|
   bool OnEncodeCommands(const Context& context) const override;
diff --git a/impeller/entity/contents/texture_contents.cc b/impeller/entity/contents/texture_contents.cc
index f23d39f..f443558 100644
--- a/impeller/entity/contents/texture_contents.cc
+++ b/impeller/entity/contents/texture_contents.cc
@@ -31,8 +31,8 @@
   return contents;
 }
 
-void TextureContents::SetLabel(std::string label) {
-  label_ = std::move(label);
+void TextureContents::SetLabel(std::string_view label) {
+  label_ = label;
 }
 
 void TextureContents::SetDestinationRect(Rect rect) {
diff --git a/impeller/entity/contents/texture_contents.h b/impeller/entity/contents/texture_contents.h
index 2526eab..34657ff 100644
--- a/impeller/entity/contents/texture_contents.h
+++ b/impeller/entity/contents/texture_contents.h
@@ -25,7 +25,7 @@
   ///         when image filters are applied.
   static std::shared_ptr<TextureContents> MakeRect(Rect destination);
 
-  void SetLabel(std::string label);
+  void SetLabel(std::string_view label);
 
   void SetDestinationRect(Rect rect);
 
diff --git a/impeller/entity/render_target_cache.cc b/impeller/entity/render_target_cache.cc
index c96dc6a..dbfe0a9 100644
--- a/impeller/entity/render_target_cache.cc
+++ b/impeller/entity/render_target_cache.cc
@@ -31,7 +31,7 @@
     const Context& context,
     ISize size,
     int mip_count,
-    const std::string& label,
+    std::string_view label,
     RenderTarget::AttachmentConfig color_attachment_config,
     std::optional<RenderTarget::AttachmentConfig> stencil_attachment_config,
     const std::shared_ptr<Texture>& existing_color_texture,
@@ -79,7 +79,7 @@
     const Context& context,
     ISize size,
     int mip_count,
-    const std::string& label,
+    std::string_view label,
     RenderTarget::AttachmentConfigMSAA color_attachment_config,
     std::optional<RenderTarget::AttachmentConfig> stencil_attachment_config,
     const std::shared_ptr<Texture>& existing_color_msaa_texture,
diff --git a/impeller/entity/render_target_cache.h b/impeller/entity/render_target_cache.h
index 838814e..7c7f914 100644
--- a/impeller/entity/render_target_cache.h
+++ b/impeller/entity/render_target_cache.h
@@ -5,6 +5,7 @@
 #ifndef FLUTTER_IMPELLER_ENTITY_RENDER_TARGET_CACHE_H_
 #define FLUTTER_IMPELLER_ENTITY_RENDER_TARGET_CACHE_H_
 
+#include <string_view>
 #include "impeller/renderer/render_target.h"
 
 namespace impeller {
@@ -29,7 +30,7 @@
       const Context& context,
       ISize size,
       int mip_count,
-      const std::string& label = "Offscreen",
+      std::string_view label = "Offscreen",
       RenderTarget::AttachmentConfig color_attachment_config =
           RenderTarget::kDefaultColorAttachmentConfig,
       std::optional<RenderTarget::AttachmentConfig> stencil_attachment_config =
@@ -42,7 +43,7 @@
       const Context& context,
       ISize size,
       int mip_count,
-      const std::string& label = "Offscreen MSAA",
+      std::string_view label = "Offscreen MSAA",
       RenderTarget::AttachmentConfigMSAA color_attachment_config =
           RenderTarget::kDefaultColorAttachmentConfigMSAA,
       std::optional<RenderTarget::AttachmentConfig> stencil_attachment_config =
diff --git a/impeller/entity/save_layer_utils.cc b/impeller/entity/save_layer_utils.cc
index 6c9e528..3049902 100644
--- a/impeller/entity/save_layer_utils.cc
+++ b/impeller/entity/save_layer_utils.cc
@@ -25,11 +25,8 @@
   // first is the presence of a backdrop filter on the saveLayer. The second is
   // the presence of a color filter that effects transparent black on the
   // saveLayer. The last is the presence of unbounded content within the
-  // saveLayer (such as a drawPaint, bdf, et cetera). Note that currently
-  // only the presence of a backdrop filter impacts this flag, while color
-  // filters are not yet handled
-  // (https://github.com/flutter/flutter/issues/154035) and unbounded coverage
-  // is handled in the display list dispatcher.
+  // saveLayer (such as a drawPaint, bdf, et cetera). Note that unbounded
+  // coverage is handled in the display list dispatcher.
   //
   // Backdrop filters apply before the saveLayer is restored. The presence of
   // a backdrop filter causes the content coverage of the saveLayer to be
diff --git a/impeller/geometry/matrix.h b/impeller/geometry/matrix.h
index 1726c30..166fc11 100644
--- a/impeller/geometry/matrix.h
+++ b/impeller/geometry/matrix.h
@@ -407,6 +407,27 @@
 
   std::optional<MatrixDecomposition> Decompose() const;
 
+  bool Equals(const Matrix& matrix, Scalar epsilon = 1e-5f) const {
+    const Scalar* a = m;
+    const Scalar* b = matrix.m;
+    return ScalarNearlyEqual(a[0], b[0], epsilon) &&
+           ScalarNearlyEqual(a[1], b[1], epsilon) &&
+           ScalarNearlyEqual(a[2], b[2], epsilon) &&
+           ScalarNearlyEqual(a[3], b[3], epsilon) &&
+           ScalarNearlyEqual(a[4], b[4], epsilon) &&
+           ScalarNearlyEqual(a[5], b[5], epsilon) &&
+           ScalarNearlyEqual(a[6], b[6], epsilon) &&
+           ScalarNearlyEqual(a[7], b[7], epsilon) &&
+           ScalarNearlyEqual(a[8], b[8], epsilon) &&
+           ScalarNearlyEqual(a[9], b[9], epsilon) &&
+           ScalarNearlyEqual(a[10], b[10], epsilon) &&
+           ScalarNearlyEqual(a[11], b[11], epsilon) &&
+           ScalarNearlyEqual(a[12], b[12], epsilon) &&
+           ScalarNearlyEqual(a[13], b[13], epsilon) &&
+           ScalarNearlyEqual(a[14], b[14], epsilon) &&
+           ScalarNearlyEqual(a[15], b[15], epsilon);
+  }
+
   constexpr bool operator==(const Matrix& m) const {
     // clang-format off
     return vec[0] == m.vec[0]
diff --git a/impeller/geometry/matrix_unittests.cc b/impeller/geometry/matrix_unittests.cc
index ac16008..91a2a50 100644
--- a/impeller/geometry/matrix_unittests.cc
+++ b/impeller/geometry/matrix_unittests.cc
@@ -25,6 +25,18 @@
                                         11.0, 21.0, 0.0, 1.0)));
 }
 
+TEST(MatrixTest, Equals) {
+  Matrix x;
+  Matrix y = x;
+  EXPECT_TRUE(x.Equals(y));
+}
+
+TEST(MatrixTest, NotEquals) {
+  Matrix x;
+  Matrix y = x.Translate({1, 0, 0});
+  EXPECT_FALSE(x.Equals(y));
+}
+
 TEST(MatrixTest, HasPerspective2D) {
   EXPECT_FALSE(Matrix().HasPerspective2D());
 
diff --git a/impeller/geometry/scalar.h b/impeller/geometry/scalar.h
index 2600a49..dadc528 100644
--- a/impeller/geometry/scalar.h
+++ b/impeller/geometry/scalar.h
@@ -22,6 +22,11 @@
   return val >= T{} ? val : -val;
 }
 
+template <>
+constexpr Scalar Absolute<Scalar>(const float& val) {
+  return fabsf(val);
+}
+
 constexpr inline bool ScalarNearlyZero(Scalar x,
                                        Scalar tolerance = kEhCloseEnough) {
   return Absolute(x) <= tolerance;
diff --git a/impeller/renderer/backend/gles/blit_command_gles.cc b/impeller/renderer/backend/gles/blit_command_gles.cc
index 9d20a01..04f6c4a 100644
--- a/impeller/renderer/backend/gles/blit_command_gles.cc
+++ b/impeller/renderer/backend/gles/blit_command_gles.cc
@@ -73,7 +73,7 @@
   // glBlitFramebuffer is a GLES3 proc. Since we target GLES2, we need to
   // emulate the blit when it's not available in the driver.
   if (!gl.BlitFramebuffer.IsAvailable()) {
-    // TODO(135818): Emulate the blit using a raster draw call here.
+    // TODO(157064): Emulate the blit using a raster draw call here.
     VALIDATION_LOG << "Texture blit fallback not implemented yet for GLES2.";
     return false;
   }
@@ -367,7 +367,7 @@
   // glBlitFramebuffer is a GLES3 proc. Since we target GLES2, we need to
   // emulate the blit when it's not available in the driver.
   if (!gl.BlitFramebuffer.IsAvailable()) {
-    // TODO(135818): Emulate the blit using a raster draw call here.
+    // TODO(157064): Emulate the blit using a raster draw call here.
     VALIDATION_LOG << "Texture blit fallback not implemented yet for GLES2.";
     return false;
   }
diff --git a/impeller/renderer/backend/gles/blit_pass_gles.cc b/impeller/renderer/backend/gles/blit_pass_gles.cc
index 39d963d..94d8300 100644
--- a/impeller/renderer/backend/gles/blit_pass_gles.cc
+++ b/impeller/renderer/backend/gles/blit_pass_gles.cc
@@ -27,8 +27,8 @@
 }
 
 // |BlitPass|
-void BlitPassGLES::OnSetLabel(std::string label) {
-  label_ = std::move(label);
+void BlitPassGLES::OnSetLabel(std::string_view label) {
+  label_ = std::string(label);
 }
 
 [[nodiscard]] bool EncodeCommandsInReactor(
@@ -96,7 +96,7 @@
     std::shared_ptr<Texture> destination,
     IRect source_region,
     IPoint destination_origin,
-    std::string label) {
+    std::string_view label) {
   auto command = std::make_unique<BlitCopyTextureToTextureCommandGLES>();
   command->label = label;
   command->source = std::move(source);
@@ -114,7 +114,7 @@
     std::shared_ptr<DeviceBuffer> destination,
     IRect source_region,
     size_t destination_offset,
-    std::string label) {
+    std::string_view label) {
   auto command = std::make_unique<BlitCopyTextureToBufferCommandGLES>();
   command->label = label;
   command->source = std::move(source);
@@ -131,7 +131,7 @@
     BufferView source,
     std::shared_ptr<Texture> destination,
     IRect destination_region,
-    std::string label,
+    std::string_view label,
     uint32_t mip_level,
     uint32_t slice,
     bool convert_to_read) {
@@ -150,7 +150,7 @@
 
 // |BlitPass|
 bool BlitPassGLES::OnGenerateMipmapCommand(std::shared_ptr<Texture> texture,
-                                           std::string label) {
+                                           std::string_view label) {
   auto command = std::make_unique<BlitGenerateMipmapCommandGLES>();
   command->label = label;
   command->texture = std::move(texture);
diff --git a/impeller/renderer/backend/gles/blit_pass_gles.h b/impeller/renderer/backend/gles/blit_pass_gles.h
index f6ad24e..321bd42 100644
--- a/impeller/renderer/backend/gles/blit_pass_gles.h
+++ b/impeller/renderer/backend/gles/blit_pass_gles.h
@@ -35,7 +35,7 @@
   bool IsValid() const override;
 
   // |BlitPass|
-  void OnSetLabel(std::string label) override;
+  void OnSetLabel(std::string_view label) override;
 
   // |BlitPass|
   bool EncodeCommands(
@@ -50,27 +50,27 @@
                                      std::shared_ptr<Texture> destination,
                                      IRect source_region,
                                      IPoint destination_origin,
-                                     std::string label) override;
+                                     std::string_view label) override;
 
   // |BlitPass|
   bool OnCopyTextureToBufferCommand(std::shared_ptr<Texture> source,
                                     std::shared_ptr<DeviceBuffer> destination,
                                     IRect source_region,
                                     size_t destination_offset,
-                                    std::string label) override;
+                                    std::string_view label) override;
 
   // |BlitPass|
   bool OnCopyBufferToTextureCommand(BufferView source,
                                     std::shared_ptr<Texture> destination,
                                     IRect destination_region,
-                                    std::string label,
+                                    std::string_view label,
                                     uint32_t mip_level,
                                     uint32_t slice,
                                     bool convert_to_read) override;
 
   // |BlitPass|
   bool OnGenerateMipmapCommand(std::shared_ptr<Texture> texture,
-                               std::string label) override;
+                               std::string_view label) override;
 
   BlitPassGLES(const BlitPassGLES&) = delete;
 
diff --git a/impeller/renderer/backend/gles/command_buffer_gles.cc b/impeller/renderer/backend/gles/command_buffer_gles.cc
index ea6a651..d570cbc 100644
--- a/impeller/renderer/backend/gles/command_buffer_gles.cc
+++ b/impeller/renderer/backend/gles/command_buffer_gles.cc
@@ -19,7 +19,7 @@
 CommandBufferGLES::~CommandBufferGLES() = default;
 
 // |CommandBuffer|
-void CommandBufferGLES::SetLabel(const std::string& label) const {
+void CommandBufferGLES::SetLabel(std::string_view label) const {
   // Cannot support.
 }
 
diff --git a/impeller/renderer/backend/gles/command_buffer_gles.h b/impeller/renderer/backend/gles/command_buffer_gles.h
index fe45b92..2188ccd 100644
--- a/impeller/renderer/backend/gles/command_buffer_gles.h
+++ b/impeller/renderer/backend/gles/command_buffer_gles.h
@@ -26,7 +26,7 @@
                     ReactorGLES::Ref reactor);
 
   // |CommandBuffer|
-  void SetLabel(const std::string& label) const override;
+  void SetLabel(std::string_view label) const override;
 
   // |CommandBuffer|
   bool IsValid() const override;
diff --git a/impeller/renderer/backend/gles/device_buffer_gles.cc b/impeller/renderer/backend/gles/device_buffer_gles.cc
index 38c1e59..f4caaf5 100644
--- a/impeller/renderer/backend/gles/device_buffer_gles.cc
+++ b/impeller/renderer/backend/gles/device_buffer_gles.cc
@@ -110,13 +110,15 @@
 }
 
 // |DeviceBuffer|
-bool DeviceBufferGLES::SetLabel(const std::string& label) {
+bool DeviceBufferGLES::SetLabel(std::string_view label) {
+#ifdef IMPELLER_DEBUG
   reactor_->SetDebugLabel(handle_, label);
+#endif  // IMPELLER_DEBUG
   return true;
 }
 
 // |DeviceBuffer|
-bool DeviceBufferGLES::SetLabel(const std::string& label, Range range) {
+bool DeviceBufferGLES::SetLabel(std::string_view label, Range range) {
   // Cannot support debug label on the range. Set the label for the entire
   // range.
   return SetLabel(label);
diff --git a/impeller/renderer/backend/gles/device_buffer_gles.h b/impeller/renderer/backend/gles/device_buffer_gles.h
index db65d0c..a18e010 100644
--- a/impeller/renderer/backend/gles/device_buffer_gles.h
+++ b/impeller/renderer/backend/gles/device_buffer_gles.h
@@ -56,10 +56,10 @@
                         size_t offset) override;
 
   // |DeviceBuffer|
-  bool SetLabel(const std::string& label) override;
+  bool SetLabel(std::string_view label) override;
 
   // |DeviceBuffer|
-  bool SetLabel(const std::string& label, Range range) override;
+  bool SetLabel(std::string_view label, Range range) override;
 
   DeviceBufferGLES(const DeviceBufferGLES&) = delete;
 
diff --git a/impeller/renderer/backend/gles/pipeline_library_gles.cc b/impeller/renderer/backend/gles/pipeline_library_gles.cc
index 6583142..493aabd 100644
--- a/impeller/renderer/backend/gles/pipeline_library_gles.cc
+++ b/impeller/renderer/backend/gles/pipeline_library_gles.cc
@@ -48,7 +48,7 @@
 
 static void LogShaderCompilationFailure(const ProcTableGLES& gl,
                                         GLuint shader,
-                                        const std::string& name,
+                                        std::string_view name,
                                         const fml::Mapping& source_mapping,
                                         ShaderStage stage) {
   std::stringstream stream;
@@ -99,10 +99,9 @@
   }
 
   gl.SetDebugLabel(DebugResourceType::kShader, vert_shader,
-                   SPrintF("%s Vertex Shader", descriptor.GetLabel().c_str()));
-  gl.SetDebugLabel(
-      DebugResourceType::kShader, frag_shader,
-      SPrintF("%s Fragment Shader", descriptor.GetLabel().c_str()));
+                   SPrintF("%s Vertex Shader", descriptor.GetLabel().data()));
+  gl.SetDebugLabel(DebugResourceType::kShader, frag_shader,
+                   SPrintF("%s Fragment Shader", descriptor.GetLabel().data()));
 
   fml::ScopedCleanupClosure delete_vert_shader(
       [&gl, vert_shader]() { gl.DeleteShader(vert_shader); });
diff --git a/impeller/renderer/backend/gles/reactor_gles.cc b/impeller/renderer/backend/gles/reactor_gles.cc
index 54e58ff..d8b7cb6 100644
--- a/impeller/renderer/backend/gles/reactor_gles.cc
+++ b/impeller/renderer/backend/gles/reactor_gles.cc
@@ -275,7 +275,8 @@
   }
 }
 
-void ReactorGLES::SetDebugLabel(const HandleGLES& handle, std::string label) {
+void ReactorGLES::SetDebugLabel(const HandleGLES& handle,
+                                std::string_view label) {
   if (!can_set_debug_labels_) {
     return;
   }
@@ -284,7 +285,7 @@
   }
   WriterLock handles_lock(handles_mutex_);
   if (auto found = handles_.find(handle); found != handles_.end()) {
-    found->second.pending_debug_label = std::move(label);
+    found->second.pending_debug_label = label;
   }
 }
 
diff --git a/impeller/renderer/backend/gles/reactor_gles.h b/impeller/renderer/backend/gles/reactor_gles.h
index b427c3f..a2e3ce6 100644
--- a/impeller/renderer/backend/gles/reactor_gles.h
+++ b/impeller/renderer/backend/gles/reactor_gles.h
@@ -190,7 +190,7 @@
   /// @param[in]  handle  The handle
   /// @param[in]  label   The label
   ///
-  void SetDebugLabel(const HandleGLES& handle, std::string label);
+  void SetDebugLabel(const HandleGLES& handle, std::string_view label);
 
   using Operation = std::function<void(const ReactorGLES& reactor)>;
 
diff --git a/impeller/renderer/backend/gles/render_pass_gles.cc b/impeller/renderer/backend/gles/render_pass_gles.cc
index 54d8441..3906329 100644
--- a/impeller/renderer/backend/gles/render_pass_gles.cc
+++ b/impeller/renderer/backend/gles/render_pass_gles.cc
@@ -36,8 +36,8 @@
 }
 
 // |RenderPass|
-void RenderPassGLES::OnSetLabel(std::string label) {
-  label_ = std::move(label);
+void RenderPassGLES::OnSetLabel(std::string_view label) {
+  label_ = label;
 }
 
 void ConfigureBlending(const ProcTableGLES& gl,
diff --git a/impeller/renderer/backend/gles/render_pass_gles.h b/impeller/renderer/backend/gles/render_pass_gles.h
index 9d4f986..e753a75 100644
--- a/impeller/renderer/backend/gles/render_pass_gles.h
+++ b/impeller/renderer/backend/gles/render_pass_gles.h
@@ -34,7 +34,7 @@
   bool IsValid() const override;
 
   // |RenderPass|
-  void OnSetLabel(std::string label) override;
+  void OnSetLabel(std::string_view label) override;
 
   // |RenderPass|
   bool OnEncodeCommands(const Context& context) const override;
diff --git a/impeller/renderer/backend/metal/blit_pass_mtl.h b/impeller/renderer/backend/metal/blit_pass_mtl.h
index fed45cb..7883819 100644
--- a/impeller/renderer/backend/metal/blit_pass_mtl.h
+++ b/impeller/renderer/backend/metal/blit_pass_mtl.h
@@ -35,7 +35,7 @@
   bool IsValid() const override;
 
   // |BlitPass|
-  void OnSetLabel(std::string label) override;
+  void OnSetLabel(std::string_view label) override;
 
   // |BlitPass|
   bool EncodeCommands(
@@ -50,26 +50,26 @@
                                      std::shared_ptr<Texture> destination,
                                      IRect source_region,
                                      IPoint destination_origin,
-                                     std::string label) override;
+                                     std::string_view label) override;
 
   // |BlitPass|
   bool OnCopyTextureToBufferCommand(std::shared_ptr<Texture> source,
                                     std::shared_ptr<DeviceBuffer> destination,
                                     IRect source_region,
                                     size_t destination_offset,
-                                    std::string label) override;
+                                    std::string_view label) override;
   // |BlitPass|
   bool OnCopyBufferToTextureCommand(BufferView source,
                                     std::shared_ptr<Texture> destination,
                                     IRect destination_region,
-                                    std::string label,
+                                    std::string_view label,
                                     uint32_t mip_level,
                                     uint32_t slice,
                                     bool convert_to_read) override;
 
   // |BlitPass|
   bool OnGenerateMipmapCommand(std::shared_ptr<Texture> texture,
-                               std::string label) override;
+                               std::string_view label) override;
 
   BlitPassMTL(const BlitPassMTL&) = delete;
 
diff --git a/impeller/renderer/backend/metal/blit_pass_mtl.mm b/impeller/renderer/backend/metal/blit_pass_mtl.mm
index d1b583b..09f381f 100644
--- a/impeller/renderer/backend/metal/blit_pass_mtl.mm
+++ b/impeller/renderer/backend/metal/blit_pass_mtl.mm
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "impeller/renderer/backend/metal/blit_pass_mtl.h"
+
 #include <Metal/Metal.h>
 #import <MetalPerformanceShaders/MetalPerformanceShaders.h>
 #include <cstdint>
@@ -50,11 +51,11 @@
   return is_valid_;
 }
 
-void BlitPassMTL::OnSetLabel(std::string label) {
+void BlitPassMTL::OnSetLabel(std::string_view label) {
   if (label.empty()) {
     return;
   }
-  [encoder_ setLabel:@(label.c_str())];
+  [encoder_ setLabel:@(label.data())];
 }
 
 bool BlitPassMTL::EncodeCommands(
@@ -70,7 +71,7 @@
     std::shared_ptr<Texture> destination,
     IRect source_region,
     IPoint destination_origin,
-    std::string label) {
+    std::string_view label) {
   auto source_mtl = TextureMTL::Cast(*source).GetMTLTexture();
   if (!source_mtl) {
     return false;
@@ -90,7 +91,7 @@
 
 #ifdef IMPELLER_DEBUG
   if (is_metal_trace_active_) {
-    [encoder_ pushDebugGroup:@(label.c_str())];
+    [encoder_ pushDebugGroup:@(label.data())];
   }
 #endif  // IMPELLER_DEBUG
   [encoder_ copyFromTexture:source_mtl
@@ -139,7 +140,7 @@
     std::shared_ptr<DeviceBuffer> destination,
     IRect source_region,
     size_t destination_offset,
-    std::string label) {
+    std::string_view label) {
   auto source_mtl = TextureMTL::Cast(*source).GetMTLTexture();
   if (!source_mtl) {
     return false;
@@ -164,7 +165,7 @@
 
 #ifdef IMPELLER_DEBUG
   if (is_metal_trace_active_) {
-    [encoder_ pushDebugGroup:@(label.c_str())];
+    [encoder_ pushDebugGroup:@(label.data())];
   }
 #endif  // IMPELLER_DEBUG
   [encoder_ copyFromTexture:source_mtl
@@ -189,7 +190,7 @@
     BufferView source,
     std::shared_ptr<Texture> destination,
     IRect destination_region,
-    std::string label,
+    std::string_view label,
     uint32_t mip_level,
     uint32_t slice,
     bool convert_to_read) {
@@ -215,7 +216,7 @@
 
 #ifdef IMPELLER_DEBUG
   if (is_metal_trace_active_) {
-    [encoder_ pushDebugGroup:@(label.c_str())];
+    [encoder_ pushDebugGroup:@(label.data())];
   }
 #endif  // IMPELLER_DEBUG
   [encoder_
@@ -241,10 +242,10 @@
 
 // |BlitPass|
 bool BlitPassMTL::OnGenerateMipmapCommand(std::shared_ptr<Texture> texture,
-                                          std::string label) {
+                                          std::string_view label) {
 #ifdef IMPELLER_DEBUG
   if (is_metal_trace_active_) {
-    [encoder_ pushDebugGroup:@(label.c_str())];
+    [encoder_ pushDebugGroup:@(label.data())];
   }
 #endif  // IMPELLER_DEBUG
   auto result = TextureMTL::Cast(*texture).GenerateMipmap(encoder_);
diff --git a/impeller/renderer/backend/metal/command_buffer_mtl.h b/impeller/renderer/backend/metal/command_buffer_mtl.h
index dad6379..6c5c948 100644
--- a/impeller/renderer/backend/metal/command_buffer_mtl.h
+++ b/impeller/renderer/backend/metal/command_buffer_mtl.h
@@ -28,7 +28,7 @@
                    id<MTLCommandQueue> queue);
 
   // |CommandBuffer|
-  void SetLabel(const std::string& label) const override;
+  void SetLabel(std::string_view label) const override;
 
   // |CommandBuffer|
   bool IsValid() const override;
diff --git a/impeller/renderer/backend/metal/command_buffer_mtl.mm b/impeller/renderer/backend/metal/command_buffer_mtl.mm
index 21e9873..93bee9c 100644
--- a/impeller/renderer/backend/metal/command_buffer_mtl.mm
+++ b/impeller/renderer/backend/metal/command_buffer_mtl.mm
@@ -140,12 +140,14 @@
   return buffer_ != nil;
 }
 
-void CommandBufferMTL::SetLabel(const std::string& label) const {
+void CommandBufferMTL::SetLabel(std::string_view label) const {
+#ifdef IMPELLER_DEBUG
   if (label.empty()) {
     return;
   }
 
   [buffer_ setLabel:@(label.data())];
+#endif  // IMPELLER_DEBUG
 }
 
 static CommandBuffer::Status ToCommitResult(MTLCommandBufferStatus status) {
diff --git a/impeller/renderer/backend/metal/device_buffer_mtl.h b/impeller/renderer/backend/metal/device_buffer_mtl.h
index 6980669..dbc61e2 100644
--- a/impeller/renderer/backend/metal/device_buffer_mtl.h
+++ b/impeller/renderer/backend/metal/device_buffer_mtl.h
@@ -42,10 +42,10 @@
                         size_t offset) override;
 
   // |DeviceBuffer|
-  bool SetLabel(const std::string& label) override;
+  bool SetLabel(std::string_view label) override;
 
   // |DeviceBuffer|
-  bool SetLabel(const std::string& label, Range range) override;
+  bool SetLabel(std::string_view label, Range range) override;
 
   // |DeviceBuffer|
   void Flush(std::optional<Range> range) const override;
diff --git a/impeller/renderer/backend/metal/device_buffer_mtl.mm b/impeller/renderer/backend/metal/device_buffer_mtl.mm
index ff763f2..4d0e053 100644
--- a/impeller/renderer/backend/metal/device_buffer_mtl.mm
+++ b/impeller/renderer/backend/metal/device_buffer_mtl.mm
@@ -64,20 +64,24 @@
 #endif
 }
 
-bool DeviceBufferMTL::SetLabel(const std::string& label) {
+bool DeviceBufferMTL::SetLabel(std::string_view label) {
+#ifdef IMPELLER_DEBUG
   if (label.empty()) {
     return false;
   }
-  [buffer_ setLabel:@(label.c_str())];
+  [buffer_ setLabel:@(label.data())];
+#endif  // IMPELLER_DEBUG
   return true;
 }
 
-bool DeviceBufferMTL::SetLabel(const std::string& label, Range range) {
+bool DeviceBufferMTL::SetLabel(std::string_view label, Range range) {
+#ifdef IMPELLER_DEBUG
   if (label.empty()) {
     return false;
   }
-  [buffer_ addDebugMarker:@(label.c_str())
+  [buffer_ addDebugMarker:@(label.data())
                     range:NSMakeRange(range.offset, range.length)];
+#endif  // IMPELLER_DEBUG
   return true;
 }
 
diff --git a/impeller/renderer/backend/metal/pipeline_library_mtl.mm b/impeller/renderer/backend/metal/pipeline_library_mtl.mm
index 5f0dbd4..6c157d1 100644
--- a/impeller/renderer/backend/metal/pipeline_library_mtl.mm
+++ b/impeller/renderer/backend/metal/pipeline_library_mtl.mm
@@ -28,7 +28,7 @@
 static void GetMTLRenderPipelineDescriptor(const PipelineDescriptor& desc,
                                            const Callback& callback) {
   auto descriptor = [[MTLRenderPipelineDescriptor alloc] init];
-  descriptor.label = @(desc.GetLabel().c_str());
+  descriptor.label = @(desc.GetLabel().data());
   descriptor.rasterSampleCount = static_cast<NSUInteger>(desc.GetSampleCount());
   bool created_specialized_function = false;
 
diff --git a/impeller/renderer/backend/metal/render_pass_mtl.h b/impeller/renderer/backend/metal/render_pass_mtl.h
index 60cfe52..86f5a3c 100644
--- a/impeller/renderer/backend/metal/render_pass_mtl.h
+++ b/impeller/renderer/backend/metal/render_pass_mtl.h
@@ -55,7 +55,7 @@
   bool IsValid() const override;
 
   // |RenderPass|
-  void OnSetLabel(std::string label) override;
+  void OnSetLabel(std::string_view label) override;
 
   // |RenderPass|
   bool OnEncodeCommands(const Context& context) const override;
diff --git a/impeller/renderer/backend/metal/render_pass_mtl.mm b/impeller/renderer/backend/metal/render_pass_mtl.mm
index 3c9fda4..b03ae56 100644
--- a/impeller/renderer/backend/metal/render_pass_mtl.mm
+++ b/impeller/renderer/backend/metal/render_pass_mtl.mm
@@ -170,7 +170,7 @@
   return is_valid_;
 }
 
-void RenderPassMTL::OnSetLabel(std::string label) {
+void RenderPassMTL::OnSetLabel(std::string_view label) {
 #ifdef IMPELLER_DEBUG
   if (label.empty()) {
     return;
diff --git a/impeller/renderer/backend/vulkan/blit_pass_vk.cc b/impeller/renderer/backend/vulkan/blit_pass_vk.cc
index 5c4aaba..5a3b2ef 100644
--- a/impeller/renderer/backend/vulkan/blit_pass_vk.cc
+++ b/impeller/renderer/backend/vulkan/blit_pass_vk.cc
@@ -49,12 +49,7 @@
 
 BlitPassVK::~BlitPassVK() = default;
 
-void BlitPassVK::OnSetLabel(std::string label) {
-  if (label.empty()) {
-    return;
-  }
-  label_ = std::move(label);
-}
+void BlitPassVK::OnSetLabel(std::string_view label) {}
 
 // |BlitPass|
 bool BlitPassVK::IsValid() const {
@@ -73,7 +68,7 @@
     std::shared_ptr<Texture> destination,
     IRect source_region,
     IPoint destination_origin,
-    std::string label) {
+    std::string_view label) {
   const auto& cmd_buffer = command_buffer_->GetCommandBuffer();
 
   const auto& src = TextureVK::Cast(*source);
@@ -156,7 +151,7 @@
     std::shared_ptr<DeviceBuffer> destination,
     IRect source_region,
     size_t destination_offset,
-    std::string label) {
+    std::string_view label) {
   const auto& cmd_buffer = command_buffer_->GetCommandBuffer();
 
   // cast source and destination to TextureVK
@@ -245,7 +240,7 @@
     BufferView source,
     std::shared_ptr<Texture> destination,
     IRect destination_region,
-    std::string label,
+    std::string_view label,
     uint32_t mip_level,
     uint32_t slice,
     bool convert_to_read) {
@@ -401,7 +396,7 @@
 
 // |BlitPass|
 bool BlitPassVK::OnGenerateMipmapCommand(std::shared_ptr<Texture> texture,
-                                         std::string label) {
+                                         std::string_view label) {
   auto& src = TextureVK::Cast(*texture);
 
   const auto size = src.GetTextureDescriptor().size;
diff --git a/impeller/renderer/backend/vulkan/blit_pass_vk.h b/impeller/renderer/backend/vulkan/blit_pass_vk.h
index 625693b..073d422 100644
--- a/impeller/renderer/backend/vulkan/blit_pass_vk.h
+++ b/impeller/renderer/backend/vulkan/blit_pass_vk.h
@@ -23,7 +23,6 @@
   friend class CommandBufferVK;
 
   std::shared_ptr<CommandBufferVK> command_buffer_;
-  std::string label_;
 
   explicit BlitPassVK(std::shared_ptr<CommandBufferVK> command_buffer);
 
@@ -31,7 +30,7 @@
   bool IsValid() const override;
 
   // |BlitPass|
-  void OnSetLabel(std::string label) override;
+  void OnSetLabel(std::string_view label) override;
 
   // |BlitPass|
   bool EncodeCommands(
@@ -50,26 +49,26 @@
                                      std::shared_ptr<Texture> destination,
                                      IRect source_region,
                                      IPoint destination_origin,
-                                     std::string label) override;
+                                     std::string_view label) override;
 
   // |BlitPass|
   bool OnCopyTextureToBufferCommand(std::shared_ptr<Texture> source,
                                     std::shared_ptr<DeviceBuffer> destination,
                                     IRect source_region,
                                     size_t destination_offset,
-                                    std::string label) override;
+                                    std::string_view label) override;
 
   // |BlitPass|
   bool OnCopyBufferToTextureCommand(BufferView source,
                                     std::shared_ptr<Texture> destination,
                                     IRect destination_region,
-                                    std::string label,
+                                    std::string_view label,
                                     uint32_t mip_level,
                                     uint32_t slice,
                                     bool convert_to_read) override;
   // |BlitPass|
   bool OnGenerateMipmapCommand(std::shared_ptr<Texture> texture,
-                               std::string label) override;
+                               std::string_view label) override;
 
   BlitPassVK(const BlitPassVK&) = delete;
 
diff --git a/impeller/renderer/backend/vulkan/command_buffer_vk.cc b/impeller/renderer/backend/vulkan/command_buffer_vk.cc
index 1e58aff..abe4971 100644
--- a/impeller/renderer/backend/vulkan/command_buffer_vk.cc
+++ b/impeller/renderer/backend/vulkan/command_buffer_vk.cc
@@ -31,12 +31,14 @@
 
 CommandBufferVK::~CommandBufferVK() = default;
 
-void CommandBufferVK::SetLabel(const std::string& label) const {
+void CommandBufferVK::SetLabel(std::string_view label) const {
+#ifdef IMPELLER_DEBUG
   auto context = context_.lock();
   if (!context) {
     return;
   }
   ContextVK::Cast(*context).SetDebugName(GetCommandBuffer(), label);
+#endif  // IMPELLER_DEBUG
 }
 
 bool CommandBufferVK::IsValid() const {
diff --git a/impeller/renderer/backend/vulkan/command_buffer_vk.h b/impeller/renderer/backend/vulkan/command_buffer_vk.h
index b03635a..6a78787 100644
--- a/impeller/renderer/backend/vulkan/command_buffer_vk.h
+++ b/impeller/renderer/backend/vulkan/command_buffer_vk.h
@@ -95,7 +95,7 @@
                   std::shared_ptr<FenceWaiterVK> fence_waiter);
 
   // |CommandBuffer|
-  void SetLabel(const std::string& label) const override;
+  void SetLabel(std::string_view label) const override;
 
   // |CommandBuffer|
   bool IsValid() const override;
diff --git a/impeller/renderer/backend/vulkan/device_buffer_vk.cc b/impeller/renderer/backend/vulkan/device_buffer_vk.cc
index a43a582..5e135a3 100644
--- a/impeller/renderer/backend/vulkan/device_buffer_vk.cc
+++ b/impeller/renderer/backend/vulkan/device_buffer_vk.cc
@@ -48,7 +48,8 @@
   return true;
 }
 
-bool DeviceBufferVK::SetLabel(const std::string& label) {
+bool DeviceBufferVK::SetLabel(std::string_view label) {
+#ifdef IMPELLER_DEBUG
   auto context = context_.lock();
   if (!context || !resource_->buffer.is_valid()) {
     // The context could have died at this point.
@@ -57,11 +58,14 @@
 
   ::vmaSetAllocationName(resource_->buffer.get().allocator,   //
                          resource_->buffer.get().allocation,  //
-                         label.c_str()                        //
+                         label.data()                         //
   );
 
   return ContextVK::Cast(*context).SetDebugName(resource_->buffer.get().buffer,
                                                 label);
+#else
+  return true;
+#endif  // IMPELLER_DEBUG
 }
 
 void DeviceBufferVK::Flush(std::optional<Range> range) const {
@@ -78,7 +82,7 @@
                             flush_range.offset, flush_range.length);
 }
 
-bool DeviceBufferVK::SetLabel(const std::string& label, Range range) {
+bool DeviceBufferVK::SetLabel(std::string_view label, Range range) {
   // We do not have the ability to name ranges. Just name the whole thing.
   return SetLabel(label);
 }
diff --git a/impeller/renderer/backend/vulkan/device_buffer_vk.h b/impeller/renderer/backend/vulkan/device_buffer_vk.h
index 744453d..540c165 100644
--- a/impeller/renderer/backend/vulkan/device_buffer_vk.h
+++ b/impeller/renderer/backend/vulkan/device_buffer_vk.h
@@ -61,10 +61,10 @@
                         size_t offset) override;
 
   // |DeviceBuffer|
-  bool SetLabel(const std::string& label) override;
+  bool SetLabel(std::string_view label) override;
 
   // |DeviceBuffer|
-  bool SetLabel(const std::string& label, Range range) override;
+  bool SetLabel(std::string_view label, Range range) override;
 
   // |DeviceBuffer|
   void Flush(std::optional<Range> range) const override;
diff --git a/impeller/renderer/backend/vulkan/pipeline_vk.cc b/impeller/renderer/backend/vulkan/pipeline_vk.cc
index 727a60b..fba6034 100644
--- a/impeller/renderer/backend/vulkan/pipeline_vk.cc
+++ b/impeller/renderer/backend/vulkan/pipeline_vk.cc
@@ -7,6 +7,7 @@
 #include "flutter/fml/make_copyable.h"
 #include "flutter/fml/status_or.h"
 #include "flutter/fml/trace_event.h"
+#include "impeller/base/strings.h"
 #include "impeller/base/timing.h"
 #include "impeller/renderer/backend/vulkan/capabilities_vk.h"
 #include "impeller/renderer/backend/vulkan/context_vk.h"
@@ -159,8 +160,11 @@
     return {};
   }
 
-  ContextVK::SetDebugName(device, pass.get(),
-                          "Compat Render Pass: " + desc.GetLabel());
+#ifdef IMPELLER_DEBUG
+  ContextVK::SetDebugName(
+      device, pass.get(),
+      SPrintF("Compat Render Pass: %s", desc.GetLabel().data()));
+#endif  // IMPELLER_DEBUG
 
   return pass;
 }
@@ -207,8 +211,11 @@
                         "unable to create uniform descriptors")};
   }
 
-  ContextVK::SetDebugName(device_holder->GetDevice(), descs_layout.get(),
-                          "Descriptor Set Layout " + desc.GetLabel());
+#ifdef IMPELLER_DEBUG
+  ContextVK::SetDebugName(
+      device_holder->GetDevice(), descs_layout.get(),
+      SPrintF("Descriptor Set Layout: %s", desc.GetLabel().data()));
+#endif  // IMPELLER_DEBUG
 
   return fml::StatusOr<vk::UniqueDescriptorSetLayout>(std::move(descs_layout));
 }
@@ -229,8 +236,11 @@
                         "Could not create pipeline layout for pipeline.")};
   }
 
-  ContextVK::SetDebugName(device_holder->GetDevice(), *pipeline_layout.value,
-                          "Pipeline Layout " + desc.GetLabel());
+#ifdef IMPELLER_DEBUG
+  ContextVK::SetDebugName(
+      device_holder->GetDevice(), *pipeline_layout.value,
+      SPrintF("Pipeline Layout %s", desc.GetLabel().data()));
+#endif  // IMPELLER_DEBUG
 
   return std::move(pipeline_layout.value);
 }
@@ -439,8 +449,10 @@
     ReportPipelineCreationFeedback(desc, feedback);
   }
 
+#ifdef IMPELLER_DEBUG
   ContextVK::SetDebugName(device_holder->GetDevice(), *pipeline,
-                          "Pipeline " + desc.GetLabel());
+                          SPrintF("Pipeline %s", desc.GetLabel().data()));
+#endif  // IMPELLER_DEBUG
 
   return std::move(pipeline);
 }
@@ -451,8 +463,7 @@
     const std::shared_ptr<DeviceHolderVK>& device_holder,
     const std::weak_ptr<PipelineLibrary>& weak_library,
     std::shared_ptr<SamplerVK> immutable_sampler) {
-  TRACE_EVENT1("flutter", "PipelineVK::Create", "Name",
-               desc.GetLabel().c_str());
+  TRACE_EVENT1("flutter", "PipelineVK::Create", "Name", desc.GetLabel().data());
 
   auto library = weak_library.lock();
 
diff --git a/impeller/renderer/backend/vulkan/render_pass_vk.cc b/impeller/renderer/backend/vulkan/render_pass_vk.cc
index df6d27d..ddf15f5 100644
--- a/impeller/renderer/backend/vulkan/render_pass_vk.cc
+++ b/impeller/renderer/backend/vulkan/render_pass_vk.cc
@@ -238,10 +238,9 @@
   return is_valid_;
 }
 
-void RenderPassVK::OnSetLabel(std::string label) {
+void RenderPassVK::OnSetLabel(std::string_view label) {
 #ifdef IMPELLER_DEBUG
-  ContextVK::Cast(*context_).SetDebugName(render_pass_->Get(),
-                                          std::string(label).c_str());
+  ContextVK::Cast(*context_).SetDebugName(render_pass_->Get(), label.data());
 #endif  // IMPELLER_DEBUG
 }
 
diff --git a/impeller/renderer/backend/vulkan/render_pass_vk.h b/impeller/renderer/backend/vulkan/render_pass_vk.h
index fa59713..034f888 100644
--- a/impeller/renderer/backend/vulkan/render_pass_vk.h
+++ b/impeller/renderer/backend/vulkan/render_pass_vk.h
@@ -123,7 +123,7 @@
   bool IsValid() const override;
 
   // |RenderPass|
-  void OnSetLabel(std::string label) override;
+  void OnSetLabel(std::string_view label) override;
 
   // |RenderPass|
   bool OnEncodeCommands(const Context& context) const override;
diff --git a/impeller/renderer/blit_pass.cc b/impeller/renderer/blit_pass.cc
index 2c8d3c4..7d199d3 100644
--- a/impeller/renderer/blit_pass.cc
+++ b/impeller/renderer/blit_pass.cc
@@ -16,18 +16,18 @@
 
 BlitPass::~BlitPass() = default;
 
-void BlitPass::SetLabel(std::string label) {
+void BlitPass::SetLabel(std::string_view label) {
   if (label.empty()) {
     return;
   }
-  OnSetLabel(std::move(label));
+  OnSetLabel(label);
 }
 
 bool BlitPass::AddCopy(std::shared_ptr<Texture> source,
                        std::shared_ptr<Texture> destination,
                        std::optional<IRect> source_region,
                        IPoint destination_origin,
-                       std::string label) {
+                       std::string_view label) {
   if (!source) {
     VALIDATION_LOG << "Attempted to add a texture blit with no source.";
     return false;
@@ -77,14 +77,14 @@
 
   return OnCopyTextureToTextureCommand(
       std::move(source), std::move(destination), source_region.value(),
-      destination_origin, std::move(label));
+      destination_origin, label);
 }
 
 bool BlitPass::AddCopy(std::shared_ptr<Texture> source,
                        std::shared_ptr<DeviceBuffer> destination,
                        std::optional<IRect> source_region,
                        size_t destination_offset,
-                       std::string label) {
+                       std::string_view label) {
   if (!source) {
     VALIDATION_LOG << "Attempted to add a texture blit with no source.";
     return false;
@@ -117,13 +117,13 @@
 
   return OnCopyTextureToBufferCommand(std::move(source), std::move(destination),
                                       source_region.value(), destination_offset,
-                                      std::move(label));
+                                      label);
 }
 
 bool BlitPass::AddCopy(BufferView source,
                        std::shared_ptr<Texture> destination,
                        std::optional<IRect> destination_region,
-                       std::string label,
+                       std::string_view label,
                        uint32_t mip_level,
                        uint32_t slice,
                        bool convert_to_read) {
@@ -162,9 +162,9 @@
     return false;
   }
 
-  return OnCopyBufferToTextureCommand(
-      std::move(source), std::move(destination), destination_region_value,
-      std::move(label), mip_level, slice, convert_to_read);
+  return OnCopyBufferToTextureCommand(std::move(source), std::move(destination),
+                                      destination_region_value, label,
+                                      mip_level, slice, convert_to_read);
 }
 
 bool BlitPass::ConvertTextureToShaderRead(
@@ -173,14 +173,14 @@
 }
 
 bool BlitPass::GenerateMipmap(std::shared_ptr<Texture> texture,
-                              std::string label) {
+                              std::string_view label) {
   if (!texture) {
     VALIDATION_LOG << "Attempted to add an invalid mipmap generation command "
                       "with no texture.";
     return false;
   }
 
-  return OnGenerateMipmapCommand(std::move(texture), std::move(label));
+  return OnGenerateMipmapCommand(std::move(texture), label);
 }
 
 }  // namespace impeller
diff --git a/impeller/renderer/blit_pass.h b/impeller/renderer/blit_pass.h
index 5c3b8af..761a402 100644
--- a/impeller/renderer/blit_pass.h
+++ b/impeller/renderer/blit_pass.h
@@ -30,7 +30,7 @@
 
   virtual bool IsValid() const = 0;
 
-  void SetLabel(std::string label);
+  void SetLabel(std::string_view label);
 
   //----------------------------------------------------------------------------
   /// @brief      If the texture is not already in a shader read internal
@@ -71,7 +71,7 @@
                std::shared_ptr<Texture> destination,
                std::optional<IRect> source_region = std::nullopt,
                IPoint destination_origin = {},
-               std::string label = "");
+               std::string_view label = "");
 
   //----------------------------------------------------------------------------
   /// @brief      Record a command to copy the contents of a texture to a
@@ -94,7 +94,7 @@
                std::shared_ptr<DeviceBuffer> destination,
                std::optional<IRect> source_region = std::nullopt,
                size_t destination_offset = 0,
-               std::string label = "");
+               std::string_view label = "");
 
   //----------------------------------------------------------------------------
   /// @brief      Record a command to copy the contents of a buffer to a
@@ -127,7 +127,7 @@
   bool AddCopy(BufferView source,
                std::shared_ptr<Texture> destination,
                std::optional<IRect> destination_region = std::nullopt,
-               std::string label = "",
+               std::string_view label = "",
                uint32_t mip_level = 0,
                uint32_t slice = 0,
                bool convert_to_read = true);
@@ -140,7 +140,8 @@
   ///
   /// @return     If the command was valid for subsequent commitment.
   ///
-  bool GenerateMipmap(std::shared_ptr<Texture> texture, std::string label = "");
+  bool GenerateMipmap(std::shared_ptr<Texture> texture,
+                      std::string_view label = "");
 
   //----------------------------------------------------------------------------
   /// @brief      Encode the recorded commands to the underlying command buffer.
@@ -156,33 +157,33 @@
  protected:
   explicit BlitPass();
 
-  virtual void OnSetLabel(std::string label) = 0;
+  virtual void OnSetLabel(std::string_view label) = 0;
 
   virtual bool OnCopyTextureToTextureCommand(
       std::shared_ptr<Texture> source,
       std::shared_ptr<Texture> destination,
       IRect source_region,
       IPoint destination_origin,
-      std::string label) = 0;
+      std::string_view label) = 0;
 
   virtual bool OnCopyTextureToBufferCommand(
       std::shared_ptr<Texture> source,
       std::shared_ptr<DeviceBuffer> destination,
       IRect source_region,
       size_t destination_offset,
-      std::string label) = 0;
+      std::string_view label) = 0;
 
   virtual bool OnCopyBufferToTextureCommand(
       BufferView source,
       std::shared_ptr<Texture> destination,
       IRect destination_region,
-      std::string label,
+      std::string_view label,
       uint32_t mip_level,
       uint32_t slice,
       bool convert_to_read) = 0;
 
   virtual bool OnGenerateMipmapCommand(std::shared_ptr<Texture> texture,
-                                       std::string label) = 0;
+                                       std::string_view label) = 0;
 
  private:
   BlitPass(const BlitPass&) = delete;
diff --git a/impeller/renderer/command_buffer.h b/impeller/renderer/command_buffer.h
index 4794941..958b4d0 100644
--- a/impeller/renderer/command_buffer.h
+++ b/impeller/renderer/command_buffer.h
@@ -58,7 +58,7 @@
 
   virtual bool IsValid() const = 0;
 
-  virtual void SetLabel(const std::string& label) const = 0;
+  virtual void SetLabel(std::string_view label) const = 0;
 
   //----------------------------------------------------------------------------
   /// @brief      Force execution of pending GPU commands.
diff --git a/impeller/renderer/compute_pipeline_descriptor.cc b/impeller/renderer/compute_pipeline_descriptor.cc
index efb9cda..801eaee 100644
--- a/impeller/renderer/compute_pipeline_descriptor.cc
+++ b/impeller/renderer/compute_pipeline_descriptor.cc
@@ -33,8 +33,8 @@
 }
 
 ComputePipelineDescriptor& ComputePipelineDescriptor::SetLabel(
-    std::string label) {
-  label_ = std::move(label);
+    std::string_view label) {
+  label_ = label;
   return *this;
 }
 
diff --git a/impeller/renderer/compute_pipeline_descriptor.h b/impeller/renderer/compute_pipeline_descriptor.h
index 85311fe..112894b 100644
--- a/impeller/renderer/compute_pipeline_descriptor.h
+++ b/impeller/renderer/compute_pipeline_descriptor.h
@@ -24,7 +24,7 @@
 
   ~ComputePipelineDescriptor();
 
-  ComputePipelineDescriptor& SetLabel(std::string label);
+  ComputePipelineDescriptor& SetLabel(std::string_view label);
 
   const std::string& GetLabel() const;
 
diff --git a/impeller/renderer/pipeline_descriptor.cc b/impeller/renderer/pipeline_descriptor.cc
index 9ae6710..0e6243e 100644
--- a/impeller/renderer/pipeline_descriptor.cc
+++ b/impeller/renderer/pipeline_descriptor.cc
@@ -68,8 +68,8 @@
          specialization_constants_ == other.specialization_constants_;
 }
 
-PipelineDescriptor& PipelineDescriptor::SetLabel(std::string label) {
-  label_ = std::move(label);
+PipelineDescriptor& PipelineDescriptor::SetLabel(std::string_view label) {
+  label_ = std::string(label);
   return *this;
 }
 
@@ -231,7 +231,7 @@
   return nullptr;
 }
 
-const std::string& PipelineDescriptor::GetLabel() const {
+std::string_view PipelineDescriptor::GetLabel() const {
   return label_;
 }
 
diff --git a/impeller/renderer/pipeline_descriptor.h b/impeller/renderer/pipeline_descriptor.h
index 3ea2ae6..83eb56f 100644
--- a/impeller/renderer/pipeline_descriptor.h
+++ b/impeller/renderer/pipeline_descriptor.h
@@ -27,9 +27,9 @@
 
   ~PipelineDescriptor();
 
-  PipelineDescriptor& SetLabel(std::string label);
+  PipelineDescriptor& SetLabel(std::string_view label);
 
-  const std::string& GetLabel() const;
+  std::string_view GetLabel() const;
 
   PipelineDescriptor& SetSampleCount(SampleCount samples);
 
diff --git a/impeller/renderer/render_pass.cc b/impeller/renderer/render_pass.cc
index df68c49..a0c6e56 100644
--- a/impeller/renderer/render_pass.cc
+++ b/impeller/renderer/render_pass.cc
@@ -50,11 +50,11 @@
   return orthographic_;
 }
 
-void RenderPass::SetLabel(std::string label) {
+void RenderPass::SetLabel(std::string_view label) {
   if (label.empty()) {
     return;
   }
-  OnSetLabel(std::move(label));
+  OnSetLabel(label);
 }
 
 bool RenderPass::AddCommand(Command&& command) {
diff --git a/impeller/renderer/render_pass.h b/impeller/renderer/render_pass.h
index c0e9b73..9d83ed1 100644
--- a/impeller/renderer/render_pass.h
+++ b/impeller/renderer/render_pass.h
@@ -6,7 +6,6 @@
 #define FLUTTER_IMPELLER_RENDERER_RENDER_PASS_H_
 
 #include <cstddef>
-#include <string>
 
 #include "fml/status.h"
 #include "impeller/core/formats.h"
@@ -45,7 +44,7 @@
 
   virtual bool IsValid() const = 0;
 
-  void SetLabel(std::string label);
+  void SetLabel(std::string_view label);
 
   /// @brief Reserve [command_count] commands in the HAL command buffer.
   ///
@@ -269,7 +268,7 @@
   static bool ValidateIndexBuffer(const BufferView& index_buffer,
                                   IndexType index_type);
 
-  virtual void OnSetLabel(std::string label) = 0;
+  virtual void OnSetLabel(std::string_view label) = 0;
 
   virtual bool OnEncodeCommands(const Context& context) const = 0;
 
diff --git a/impeller/renderer/render_target.cc b/impeller/renderer/render_target.cc
index 6afa486..389ecd2 100644
--- a/impeller/renderer/render_target.cc
+++ b/impeller/renderer/render_target.cc
@@ -260,7 +260,7 @@
     const Context& context,
     ISize size,
     int mip_count,
-    const std::string& label,
+    std::string_view label,
     RenderTarget::AttachmentConfig color_attachment_config,
     std::optional<RenderTarget::AttachmentConfig> stencil_attachment_config,
     const std::shared_ptr<Texture>& existing_color_texture,
@@ -289,7 +289,9 @@
       return {};
     }
   }
-  color0_tex->SetLabel(SPrintF("%s Color Texture", label.c_str()));
+#ifdef IMPELLER_DEBUG
+  color0_tex->SetLabel(SPrintF("%s Color Texture", label.data()));
+#endif  // IMPELLER_DEBUG
 
   ColorAttachment color0;
   color0.clear_color = color_attachment_config.clear_color;
@@ -314,7 +316,7 @@
     const Context& context,
     ISize size,
     int mip_count,
-    const std::string& label,
+    std::string_view label,
     RenderTarget::AttachmentConfigMSAA color_attachment_config,
     std::optional<RenderTarget::AttachmentConfig> stencil_attachment_config,
     const std::shared_ptr<Texture>& existing_color_msaa_texture,
@@ -349,8 +351,10 @@
       return {};
     }
   }
+#ifdef IMPELLER_DEBUG
   color0_msaa_tex->SetLabel(
-      SPrintF("%s Color Texture (Multisample)", label.c_str()));
+      SPrintF("%s Color Texture (Multisample)", label.data()));
+#endif  // IMPELLER_DEBUG
 
   // Create color resolve texture.
   std::shared_ptr<Texture> color0_resolve_tex;
@@ -372,7 +376,9 @@
       return {};
     }
   }
-  color0_resolve_tex->SetLabel(SPrintF("%s Color Texture", label.c_str()));
+#ifdef IMPELLER_DEBUG
+  color0_resolve_tex->SetLabel(SPrintF("%s Color Texture", label.data()));
+#endif  // IMPELLER_DEBUG
 
   // Color attachment.
 
@@ -415,7 +421,7 @@
     Allocator& allocator,
     ISize size,
     bool msaa,
-    const std::string& label,
+    std::string_view label,
     RenderTarget::AttachmentConfig stencil_attachment_config,
     const std::shared_ptr<Texture>& existing_depth_stencil_texture) {
   std::shared_ptr<Texture> depth_stencil_texture;
@@ -451,8 +457,9 @@
   stencil0.clear_stencil = 0u;
   stencil0.texture = std::move(depth_stencil_texture);
 
-  stencil0.texture->SetLabel(
-      SPrintF("%s Depth+Stencil Texture", label.c_str()));
+#ifdef IMPELLER_DEBUG
+  stencil0.texture->SetLabel(SPrintF("%s Depth+Stencil Texture", label.data()));
+#endif  // IMPELLER_DEBUG
   SetDepthAttachment(std::move(depth0));
   SetStencilAttachment(std::move(stencil0));
 }
diff --git a/impeller/renderer/render_target.h b/impeller/renderer/render_target.h
index 6d63527..77f5bf4 100644
--- a/impeller/renderer/render_target.h
+++ b/impeller/renderer/render_target.h
@@ -82,7 +82,7 @@
       Allocator& allocator,
       ISize size,
       bool msaa,
-      const std::string& label = "Offscreen",
+      std::string_view label = "Offscreen",
       RenderTarget::AttachmentConfig stencil_attachment_config =
           RenderTarget::kDefaultStencilAttachmentConfig,
       const std::shared_ptr<Texture>& depth_stencil_texture = nullptr);
@@ -149,7 +149,7 @@
       const Context& context,
       ISize size,
       int mip_count,
-      const std::string& label = "Offscreen",
+      std::string_view label = "Offscreen",
       RenderTarget::AttachmentConfig color_attachment_config =
           RenderTarget::kDefaultColorAttachmentConfig,
       std::optional<RenderTarget::AttachmentConfig> stencil_attachment_config =
@@ -161,7 +161,7 @@
       const Context& context,
       ISize size,
       int mip_count,
-      const std::string& label = "Offscreen MSAA",
+      std::string_view label = "Offscreen MSAA",
       RenderTarget::AttachmentConfigMSAA color_attachment_config =
           RenderTarget::kDefaultColorAttachmentConfigMSAA,
       std::optional<RenderTarget::AttachmentConfig> stencil_attachment_config =
diff --git a/impeller/renderer/testing/mocks.h b/impeller/renderer/testing/mocks.h
index cc781b8..1e02636 100644
--- a/impeller/renderer/testing/mocks.h
+++ b/impeller/renderer/testing/mocks.h
@@ -28,11 +28,11 @@
   explicit MockDeviceBuffer(const DeviceBufferDescriptor& desc)
       : DeviceBuffer(desc) {}
 
-  MOCK_METHOD(bool, SetLabel, (const std::string& label), (override));
+  MOCK_METHOD(bool, SetLabel, (std::string_view label), (override));
 
   MOCK_METHOD(bool,
               SetLabel,
-              (const std::string& label, Range range),
+              (std::string_view label, Range range),
               (override));
 
   MOCK_METHOD(uint8_t*, OnGetContents, (), (const, override));
@@ -63,7 +63,7 @@
               EncodeCommands,
               (const std::shared_ptr<Allocator>& transients_allocator),
               (const, override));
-  MOCK_METHOD(void, OnSetLabel, (std::string label), (override));
+  MOCK_METHOD(void, OnSetLabel, (std::string_view label), (override));
 
   MOCK_METHOD(bool,
               ResizeTexture,
@@ -77,7 +77,7 @@
                std::shared_ptr<Texture> destination,
                IRect source_region,
                IPoint destination_origin,
-               std::string label),
+               std::string_view label),
               (override));
 
   MOCK_METHOD(bool,
@@ -86,21 +86,21 @@
                std::shared_ptr<DeviceBuffer> destination,
                IRect source_region,
                size_t destination_offset,
-               std::string label),
+               std::string_view label),
               (override));
   MOCK_METHOD(bool,
               OnCopyBufferToTextureCommand,
               (BufferView source,
                std::shared_ptr<Texture> destination,
                IRect destination_rect,
-               std::string label,
+               std::string_view label,
                uint32_t mip_level,
                uint32_t slice,
                bool convert_to_read),
               (override));
   MOCK_METHOD(bool,
               OnGenerateMipmapCommand,
-              (std::shared_ptr<Texture> texture, std::string label),
+              (std::shared_ptr<Texture> texture, std::string_view label),
               (override));
 };
 
@@ -114,7 +114,7 @@
               OnEncodeCommands,
               (const Context& context),
               (const, override));
-  MOCK_METHOD(void, OnSetLabel, (std::string label), (override));
+  MOCK_METHOD(void, OnSetLabel, (std::string_view label), (override));
 };
 
 class MockCommandBuffer : public CommandBuffer {
@@ -122,7 +122,7 @@
   explicit MockCommandBuffer(std::weak_ptr<const Context> context)
       : CommandBuffer(std::move(context)) {}
   MOCK_METHOD(bool, IsValid, (), (const, override));
-  MOCK_METHOD(void, SetLabel, (const std::string& label), (const, override));
+  MOCK_METHOD(void, SetLabel, (std::string_view label), (const, override));
   MOCK_METHOD(std::shared_ptr<BlitPass>, OnCreateBlitPass, (), (override));
   MOCK_METHOD(bool,
               OnSubmitCommands,
diff --git a/impeller/toolkit/interop/impeller.cc b/impeller/toolkit/interop/impeller.cc
index 1484eda..05d8c03 100644
--- a/impeller/toolkit/interop/impeller.cc
+++ b/impeller/toolkit/interop/impeller.cc
@@ -1009,7 +1009,8 @@
 IMPELLER_EXTERN_C
 ImpellerParagraphBuilder ImpellerParagraphBuilderNew(
     ImpellerTypographyContext context) {
-  auto builder = Create<ParagraphBuilder>(*GetPeer(context));
+  auto builder =
+      Create<ParagraphBuilder>(Ref<TypographyContext>(GetPeer(context)));
   if (!builder->IsValid()) {
     VALIDATION_LOG << "Could not create valid paragraph builder.";
     return nullptr;
diff --git a/impeller/toolkit/interop/impeller_unittests.cc b/impeller/toolkit/interop/impeller_unittests.cc
index c582944..2a4690e 100644
--- a/impeller/toolkit/interop/impeller_unittests.cc
+++ b/impeller/toolkit/interop/impeller_unittests.cc
@@ -245,4 +245,97 @@
       }));
 }
 
+static void DrawTextFrame(ImpellerTypographyContext tc,
+                          ImpellerDisplayListBuilder builder,
+                          ImpellerParagraphStyle p_style,
+                          ImpellerPaint bg,
+                          ImpellerColor color,
+                          ImpellerTextAlignment align,
+                          float x_offset) {
+  const char text[] =
+      "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
+
+  ImpellerPaint fg = ImpellerPaintNew();
+
+  // Draw a box.
+  ImpellerPaintSetColor(fg, &color);
+  ImpellerPaintSetDrawStyle(fg, kImpellerDrawStyleStroke);
+  ImpellerRect box_rect = {10 + x_offset, 10, 200, 200};
+  ImpellerDisplayListBuilderDrawRect(builder, &box_rect, fg);
+
+  // Draw text.
+  ImpellerPaintSetDrawStyle(fg, kImpellerDrawStyleFill);
+  ImpellerParagraphStyleSetForeground(p_style, fg);
+  ImpellerParagraphStyleSetBackground(p_style, bg);
+  ImpellerParagraphStyleSetTextAlignment(p_style, align);
+  ImpellerParagraphBuilder p_builder = ImpellerParagraphBuilderNew(tc);
+  ImpellerParagraphBuilderPushStyle(p_builder, p_style);
+  ImpellerParagraphBuilderAddText(p_builder, (const uint8_t*)text,
+                                  sizeof(text));
+  ImpellerParagraph left_p = ImpellerParagraphBuilderBuildParagraphNew(
+      p_builder, box_rect.width - 20.0);
+  ImpellerPoint pt = {20.0f + x_offset, 20.0f};
+  float w = ImpellerParagraphGetMaxWidth(left_p);
+  float h = ImpellerParagraphGetHeight(left_p);
+  ImpellerDisplayListBuilderDrawParagraph(builder, left_p, &pt);
+  ImpellerPaintSetDrawStyle(fg, kImpellerDrawStyleStroke);
+
+  // Draw an inner box around the paragraph layout.
+  ImpellerRect inner_box_rect = {pt.x, pt.y, w, h};
+  ImpellerDisplayListBuilderDrawRect(builder, &inner_box_rect, fg);
+
+  ImpellerParagraphRelease(left_p);
+  ImpellerParagraphBuilderRelease(p_builder);
+  ImpellerPaintRelease(fg);
+}
+
+TEST_P(InteropPlaygroundTest, CanRenderTextAlignments) {
+  ImpellerTypographyContext tc = ImpellerTypographyContextNew();
+
+  ImpellerDisplayList dl = NULL;
+
+  {
+    ImpellerDisplayListBuilder builder = ImpellerDisplayListBuilderNew(NULL);
+    ImpellerPaint bg = ImpellerPaintNew();
+    ImpellerParagraphStyle p_style = ImpellerParagraphStyleNew();
+    ImpellerParagraphStyleSetFontFamily(p_style, "Roboto");
+    ImpellerParagraphStyleSetFontSize(p_style, 24.0);
+    ImpellerParagraphStyleSetFontWeight(p_style, kImpellerFontWeight400);
+
+    // Clear the background to a white color.
+    ImpellerColor clear_color = {1.0, 1.0, 1.0, 1.0};
+    ImpellerPaintSetColor(bg, &clear_color);
+    ImpellerDisplayListBuilderDrawPaint(builder, bg);
+
+    // Draw red, left-aligned text.
+    ImpellerColor red = {1.0, 0.0, 0.0, 1.0};
+    DrawTextFrame(tc, builder, p_style, bg, red, kImpellerTextAlignmentLeft,
+                  0.0);
+
+    // Draw green, centered text.
+    ImpellerColor green = {0.0, 1.0, 0.0, 1.0};
+    DrawTextFrame(tc, builder, p_style, bg, green, kImpellerTextAlignmentCenter,
+                  220.0);
+
+    // Draw blue, right-aligned text.
+    ImpellerColor blue = {0.0, 0.0, 1.0, 1.0};
+    DrawTextFrame(tc, builder, p_style, bg, blue, kImpellerTextAlignmentRight,
+                  440.0);
+
+    dl = ImpellerDisplayListBuilderCreateDisplayListNew(builder);
+
+    ImpellerPaintRelease(bg);
+    ImpellerDisplayListBuilderRelease(builder);
+  }
+
+  ASSERT_TRUE(
+      OpenPlaygroundHere([&](const auto& context, const auto& surface) -> bool {
+        ImpellerSurfaceDrawDisplayList(surface.GetC(), dl);
+        return true;
+      }));
+
+  ImpellerDisplayListRelease(dl);
+  ImpellerTypographyContextRelease(tc);
+}
+
 }  // namespace impeller::interop::testing
diff --git a/impeller/toolkit/interop/paragraph_builder.cc b/impeller/toolkit/interop/paragraph_builder.cc
index 4997d59..d983c38 100644
--- a/impeller/toolkit/interop/paragraph_builder.cc
+++ b/impeller/toolkit/interop/paragraph_builder.cc
@@ -10,41 +10,29 @@
 
 namespace impeller::interop {
 
-ParagraphBuilder::ParagraphBuilder(const TypographyContext& context) {
-  if (!context.IsValid()) {
-    VALIDATION_LOG << "Invalid typography context.";
-    return;
-  }
-
-  static txt::ParagraphStyle kBaseStyle;
-
-  builder_ = std::make_unique<txt::ParagraphBuilderSkia>(
-      kBaseStyle,                   //
-      context.GetFontCollection(),  //
-      true                          // is impeller enabled
-  );
-}
+ParagraphBuilder::ParagraphBuilder(ScopedObject<TypographyContext> context)
+    : context_(std::move(context)) {}
 
 ParagraphBuilder::~ParagraphBuilder() = default;
 
 bool ParagraphBuilder::IsValid() const {
-  return !!builder_;
+  return !!context_;
 }
 
 void ParagraphBuilder::PushStyle(const ParagraphStyle& style) {
-  builder_->PushStyle(style.CreateTextStyle());
+  GetBuilder(style.GetParagraphStyle())->PushStyle(style.CreateTextStyle());
 }
 
 void ParagraphBuilder::PopStyle() {
-  builder_->Pop();
+  GetBuilder()->Pop();
 }
 
 void ParagraphBuilder::AddText(const uint8_t* data, size_t byte_length) {
-  builder_->AddText(data, byte_length);
+  GetBuilder()->AddText(data, byte_length);
 }
 
 ScopedObject<Paragraph> ParagraphBuilder::Build(Scalar width) const {
-  auto txt_paragraph = builder_->Build();
+  auto txt_paragraph = GetBuilder()->Build();
   if (!txt_paragraph) {
     return nullptr;
   }
@@ -52,4 +40,23 @@
   return Create<Paragraph>(std::move(txt_paragraph));
 }
 
+const std::unique_ptr<txt::ParagraphBuilder>& ParagraphBuilder::GetBuilder(
+    const txt::ParagraphStyle& style) const {
+  if (lazy_builder_) {
+    return lazy_builder_;
+  }
+  lazy_builder_ = std::make_unique<txt::ParagraphBuilderSkia>(
+      style,                          //
+      context_->GetFontCollection(),  //
+      true                            // is impeller enabled
+  );
+  return lazy_builder_;
+}
+
+const std::unique_ptr<txt::ParagraphBuilder>& ParagraphBuilder::GetBuilder()
+    const {
+  static txt::ParagraphStyle kDefaultStyle;
+  return GetBuilder(kDefaultStyle);
+}
+
 }  // namespace impeller::interop
diff --git a/impeller/toolkit/interop/paragraph_builder.h b/impeller/toolkit/interop/paragraph_builder.h
index 1560446..8ea141e 100644
--- a/impeller/toolkit/interop/paragraph_builder.h
+++ b/impeller/toolkit/interop/paragraph_builder.h
@@ -20,7 +20,7 @@
     : public Object<ParagraphBuilder,
                     IMPELLER_INTERNAL_HANDLE_NAME(ImpellerParagraphBuilder)> {
  public:
-  explicit ParagraphBuilder(const TypographyContext& context);
+  explicit ParagraphBuilder(ScopedObject<TypographyContext> context);
 
   ~ParagraphBuilder() override;
 
@@ -39,7 +39,13 @@
   ScopedObject<Paragraph> Build(Scalar width) const;
 
  private:
-  std::unique_ptr<txt::ParagraphBuilder> builder_;
+  ScopedObject<TypographyContext> context_;
+  mutable std::unique_ptr<txt::ParagraphBuilder> lazy_builder_;
+
+  const std::unique_ptr<txt::ParagraphBuilder>& GetBuilder(
+      const txt::ParagraphStyle& style) const;
+
+  const std::unique_ptr<txt::ParagraphBuilder>& GetBuilder() const;
 };
 
 }  // namespace impeller::interop
diff --git a/impeller/toolkit/interop/paragraph_style.cc b/impeller/toolkit/interop/paragraph_style.cc
index 14733dc..55a7fcd 100644
--- a/impeller/toolkit/interop/paragraph_style.cc
+++ b/impeller/toolkit/interop/paragraph_style.cc
@@ -51,18 +51,23 @@
 }
 
 void ParagraphStyle::SetBackground(ScopedObject<Paint> paint) {
-  backgrond_ = std::move(paint);
+  background_ = std::move(paint);
 }
 
 txt::TextStyle ParagraphStyle::CreateTextStyle() const {
   auto style = style_.GetTextStyle();
+
   if (foreground_) {
     style.foreground = foreground_->GetPaint();
   }
-  if (backgrond_) {
-    style.background = backgrond_->GetPaint();
+  if (background_) {
+    style.background = background_->GetPaint();
   }
   return style;
 }
 
+const txt::ParagraphStyle& ParagraphStyle::GetParagraphStyle() const {
+  return style_;
+}
+
 }  // namespace impeller::interop
diff --git a/impeller/toolkit/interop/paragraph_style.h b/impeller/toolkit/interop/paragraph_style.h
index 37165e3..0a3e898 100644
--- a/impeller/toolkit/interop/paragraph_style.h
+++ b/impeller/toolkit/interop/paragraph_style.h
@@ -48,10 +48,12 @@
 
   txt::TextStyle CreateTextStyle() const;
 
+  const txt::ParagraphStyle& GetParagraphStyle() const;
+
  private:
   txt::ParagraphStyle style_;
   ScopedObject<Paint> foreground_;
-  ScopedObject<Paint> backgrond_;
+  ScopedObject<Paint> background_;
 };
 
 }  // namespace impeller::interop
diff --git a/lib/ui/painting/image_decoder_impeller.cc b/lib/ui/painting/image_decoder_impeller.cc
index adb06b4..6a11189 100644
--- a/lib/ui/painting/image_decoder_impeller.cc
+++ b/lib/ui/painting/image_decoder_impeller.cc
@@ -483,6 +483,9 @@
   }
 
   texture->SetLabel(impeller::SPrintF("ui.Image(%p)", texture.get()).c_str());
+
+  context->DisposeThreadLocalCachedResources();
+
   return std::make_pair(impeller::DlImageImpeller::Make(std::move(texture)),
                         std::string());
 }
diff --git a/lib/ui/painting/image_decoder_no_gl_unittests.h b/lib/ui/painting/image_decoder_no_gl_unittests.h
index b1839f0..4f4cdf2 100644
--- a/lib/ui/painting/image_decoder_no_gl_unittests.h
+++ b/lib/ui/painting/image_decoder_no_gl_unittests.h
@@ -44,9 +44,9 @@
   ~TestImpellerDeviceBuffer() { free(bytes_); }
 
  private:
-  bool SetLabel(const std::string& label) override { return true; }
+  bool SetLabel(std::string_view label) override { return true; }
 
-  bool SetLabel(const std::string& label, Range range) override { return true; }
+  bool SetLabel(std::string_view label, Range range) override { return true; }
 
   uint8_t* OnGetContents() const override { return bytes_; }
 
diff --git a/lib/ui/painting/image_decoder_unittests.cc b/lib/ui/painting/image_decoder_unittests.cc
index d8f7667..69096ee 100644
--- a/lib/ui/painting/image_decoder_unittests.cc
+++ b/lib/ui/painting/image_decoder_unittests.cc
@@ -92,8 +92,12 @@
     tasks_.clear();
   }
 
+  void DisposeThreadLocalCachedResources() override { did_dispose_ = true; }
+
   void Shutdown() override {}
 
+  bool DidDisposeResources() const { return did_dispose_; }
+
   mutable size_t command_buffer_count_ = 0;
 
  private:
@@ -103,6 +107,7 @@
   };
   std::vector<PendingTask> tasks_;
   std::shared_ptr<const Capabilities> capabilities_;
+  bool did_dispose_ = false;
 };
 
 }  // namespace impeller
@@ -367,12 +372,14 @@
 
   EXPECT_EQ(no_gpu_access_context->command_buffer_count_, 0ul);
   EXPECT_FALSE(invoked);
+  EXPECT_EQ(no_gpu_access_context->DidDisposeResources(), false);
 
   auto result = ImageDecoderImpeller::UploadTextureToStorage(
       no_gpu_access_context, bitmap);
 
   ASSERT_EQ(no_gpu_access_context->command_buffer_count_, 0ul);
   ASSERT_EQ(result.second, "");
+  EXPECT_EQ(no_gpu_access_context->DidDisposeResources(), true);
 
   no_gpu_access_context->FlushTasks(/*fail=*/true);
 }
diff --git a/lib/ui/window/pointer_data_packet_converter.cc b/lib/ui/window/pointer_data_packet_converter.cc
index 45b05a0..b764028 100644
--- a/lib/ui/window/pointer_data_packet_converter.cc
+++ b/lib/ui/window/pointer_data_packet_converter.cc
@@ -74,7 +74,45 @@
         break;
       }
       case PointerData::Change::kAdd: {
-        FML_DCHECK(states_.find(pointer_data.device) == states_.end());
+        auto iter = states_.find(pointer_data.device);
+        if (iter != states_.end()) {
+          // Synthesizes a remove event if the pointer was not previously
+          // removed.
+          PointerState state = iter->second;
+          PointerData synthesized_data = pointer_data;
+          synthesized_data.physical_x = state.physical_x;
+          synthesized_data.physical_y = state.physical_y;
+          synthesized_data.pan_x = state.pan_x;
+          synthesized_data.pan_y = state.pan_y;
+          synthesized_data.scale = state.scale;
+          synthesized_data.rotation = state.rotation;
+          synthesized_data.buttons = state.buttons;
+          synthesized_data.synthesized = 1;
+
+          // The framework expects an add event to always follow a remove event,
+          // and remove events with invalid views are ignored.
+          // To meet the framework's expectations, the view ID of the add event
+          // is used for the remove event if the old view has been destroyed.
+          if (delegate_.ViewExists(state.view_id)) {
+            synthesized_data.view_id = state.view_id;
+
+            if (state.is_down) {
+              // Synthesizes cancel event if the pointer is down.
+              PointerData synthesized_cancel_event = synthesized_data;
+              synthesized_cancel_event.change = PointerData::Change::kCancel;
+              UpdatePointerIdentifier(synthesized_cancel_event, state, false);
+
+              state.is_down = false;
+              converted_pointers.push_back(synthesized_cancel_event);
+            }
+          }
+
+          PointerData synthesized_remove_event = synthesized_data;
+          synthesized_remove_event.change = PointerData::Change::kRemove;
+
+          converted_pointers.push_back(synthesized_remove_event);
+        }
+
         EnsurePointerState(pointer_data);
         converted_pointers.push_back(pointer_data);
         break;
@@ -84,6 +122,11 @@
         auto iter = states_.find(pointer_data.device);
         FML_DCHECK(iter != states_.end());
         PointerState state = iter->second;
+        if (state.view_id != pointer_data.view_id) {
+          // Ignores remove event if the pointer was previously added to a
+          // different view.
+          break;
+        }
 
         if (state.is_down) {
           // Synthesizes cancel event if the pointer is down.
@@ -362,6 +405,7 @@
   state.physical_y = pointer_data.physical_y;
   state.pan_x = 0;
   state.pan_y = 0;
+  state.view_id = pointer_data.view_id;
   states_[pointer_data.device] = state;
   return state;
 }
diff --git a/lib/ui/window/pointer_data_packet_converter.h b/lib/ui/window/pointer_data_packet_converter.h
index 146ade2..7b1b823 100644
--- a/lib/ui/window/pointer_data_packet_converter.h
+++ b/lib/ui/window/pointer_data_packet_converter.h
@@ -38,6 +38,7 @@
   double scale;
   double rotation;
   int64_t buttons;
+  int64_t view_id;
 };
 
 //------------------------------------------------------------------------------
diff --git a/lib/ui/window/pointer_data_packet_converter_unittests.cc b/lib/ui/window/pointer_data_packet_converter_unittests.cc
index 4f6a37f..0eb9667 100644
--- a/lib/ui/window/pointer_data_packet_converter_unittests.cc
+++ b/lib/ui/window/pointer_data_packet_converter_unittests.cc
@@ -510,6 +510,108 @@
   ASSERT_EQ(result[3].buttons, 0);
 }
 
+TEST(PointerDataPacketConverterTest, CanSynthesizeRemove) {
+  TestDelegate delegate;
+  delegate.AddView(100);
+  delegate.AddView(200);
+  PointerDataPacketConverter converter(delegate);
+  auto packet = std::make_unique<PointerDataPacket>(3);
+
+  PointerData data;
+  CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0, 0.0, 0.0, 0);
+  data.view_id = 100;
+  packet->SetPointerData(0, data);
+  CreateSimulatedPointerData(data, PointerData::Change::kDown, 0, 3.0, 4.0, 1);
+  data.view_id = 100;
+  packet->SetPointerData(1, data);
+  CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0, 0.0, 0.0, 0);
+  data.view_id = 200;
+  packet->SetPointerData(2, data);
+  auto converted_packet = converter.Convert(*packet);
+
+  std::vector<PointerData> result;
+  UnpackPointerPacket(result, std::move(converted_packet));
+
+  ASSERT_EQ(result.size(), (size_t)6);
+  ASSERT_EQ(result[0].synthesized, 0);
+  ASSERT_EQ(result[0].view_id, 100);
+
+  // A hover should be synthesized.
+  ASSERT_EQ(result[1].change, PointerData::Change::kHover);
+  ASSERT_EQ(result[1].synthesized, 1);
+  ASSERT_EQ(result[1].physical_delta_x, 3.0);
+  ASSERT_EQ(result[1].physical_delta_y, 4.0);
+  ASSERT_EQ(result[1].buttons, 0);
+
+  ASSERT_EQ(result[2].change, PointerData::Change::kDown);
+  ASSERT_EQ(result[2].pointer_identifier, 1);
+  ASSERT_EQ(result[2].synthesized, 0);
+  ASSERT_EQ(result[2].buttons, 1);
+
+  // A cancel should be synthesized.
+  ASSERT_EQ(result[3].change, PointerData::Change::kCancel);
+  ASSERT_EQ(result[3].pointer_identifier, 1);
+  ASSERT_EQ(result[3].synthesized, 1);
+  ASSERT_EQ(result[3].physical_x, 3.0);
+  ASSERT_EQ(result[3].physical_y, 4.0);
+  ASSERT_EQ(result[3].buttons, 1);
+
+  // A remove should be synthesized.
+  ASSERT_EQ(result[4].physical_x, 3.0);
+  ASSERT_EQ(result[4].physical_y, 4.0);
+  ASSERT_EQ(result[4].synthesized, 1);
+  ASSERT_EQ(result[4].view_id, 100);
+
+  ASSERT_EQ(result[5].synthesized, 0);
+  ASSERT_EQ(result[5].view_id, 200);
+}
+
+TEST(PointerDataPacketConverterTest,
+     CanAvoidDoubleRemoveAfterSynthesizedRemove) {
+  TestDelegate delegate;
+  delegate.AddView(100);
+  delegate.AddView(200);
+  PointerDataPacketConverter converter(delegate);
+  auto packet = std::make_unique<PointerDataPacket>(2);
+
+  PointerData data;
+  CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0, 0.0, 0.0, 0);
+  data.view_id = 100;
+  packet->SetPointerData(0, data);
+  CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0, 0.0, 0.0, 0);
+  data.view_id = 200;
+  packet->SetPointerData(1, data);
+  auto converted_packet = converter.Convert(*packet);
+
+  std::vector<PointerData> result;
+  UnpackPointerPacket(result, std::move(converted_packet));
+
+  ASSERT_EQ(result.size(), (size_t)3);
+  ASSERT_EQ(result[0].synthesized, 0);
+  ASSERT_EQ(result[0].view_id, 100);
+
+  // A remove should be synthesized.
+  ASSERT_EQ(result[1].synthesized, 1);
+  ASSERT_EQ(result[1].view_id, 100);
+
+  ASSERT_EQ(result[2].synthesized, 0);
+  ASSERT_EQ(result[2].view_id, 200);
+
+  // Simulate a double remove.
+  packet = std::make_unique<PointerDataPacket>(1);
+  CreateSimulatedPointerData(data, PointerData::Change::kRemove, 0, 0.0, 0.0,
+                             0);
+  data.view_id = 100;
+  packet->SetPointerData(0, data);
+  converted_packet = converter.Convert(*packet);
+
+  result.clear();
+  UnpackPointerPacket(result, std::move(converted_packet));
+
+  // The double remove should be ignored.
+  ASSERT_EQ(result.size(), (size_t)0);
+}
+
 TEST(PointerDataPacketConverterTest, CanHandleThreeFingerGesture) {
   // Regression test https://github.com/flutter/flutter/issues/20517.
   TestDelegate delegate;
diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm
index 0013657..904a3ea 100644
--- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm
+++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm
@@ -2782,79 +2782,6 @@
   flutterPlatformViewsController->Reset();
 }
 
-- (void)testFlutterPlatformViewForwardingAndDelayingRecognizerFailureCondition {
-  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
-
-  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
-                               /*platform=*/GetDefaultTaskRunner(),
-                               /*raster=*/GetDefaultTaskRunner(),
-                               /*ui=*/GetDefaultTaskRunner(),
-                               /*io=*/GetDefaultTaskRunner());
-  auto flutterPlatformViewsController = std::make_shared<flutter::PlatformViewsController>();
-  flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner());
-  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
-      /*delegate=*/mock_delegate,
-      /*rendering_api=*/mock_delegate.settings_.enable_impeller
-          ? flutter::IOSRenderingAPI::kMetal
-          : flutter::IOSRenderingAPI::kSoftware,
-      /*platform_views_controller=*/flutterPlatformViewsController,
-      /*task_runners=*/runners,
-      /*worker_task_runner=*/nil,
-      /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
-
-  FlutterPlatformViewsTestMockFlutterPlatformFactory* factory =
-      [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init];
-  flutterPlatformViewsController->RegisterViewFactory(
-      factory, @"MockFlutterPlatformView",
-      FlutterPlatformViewGestureRecognizersBlockingPolicyEager);
-  FlutterResult result = ^(id result) {
-  };
-  flutterPlatformViewsController->OnMethodCall(
-      [FlutterMethodCall
-          methodCallWithMethodName:@"create"
-                         arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
-      result);
-
-  XCTAssertNotNil(gMockPlatformView);
-
-  // Find touch inteceptor view
-  UIView* touchInteceptorView = gMockPlatformView;
-  while (touchInteceptorView != nil &&
-         ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
-    touchInteceptorView = touchInteceptorView.superview;
-  }
-  XCTAssertNotNil(touchInteceptorView);
-
-  // Find ForwardGestureRecognizer
-  UIGestureRecognizer* forwardingGestureRecognizer = nil;
-  UIGestureRecognizer* delayingGestureRecognizer = nil;
-  for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) {
-    if ([gestureRecognizer isKindOfClass:NSClassFromString(@"ForwardingGestureRecognizer")]) {
-      forwardingGestureRecognizer = gestureRecognizer;
-    }
-    if ([gestureRecognizer isKindOfClass:NSClassFromString(@"FlutterDelayingGestureRecognizer")]) {
-      delayingGestureRecognizer = gestureRecognizer;
-    }
-  }
-  UIGestureRecognizer* otherGestureRecognizer = OCMClassMock([UIGestureRecognizer class]);
-
-  id flutterViewContoller = OCMClassMock([FlutterViewController class]);
-  flutterPlatformViewsController->SetFlutterViewController(flutterViewContoller);
-
-  XCTAssertFalse([delayingGestureRecognizer.delegate
-                              gestureRecognizer:delayingGestureRecognizer
-      shouldBeRequiredToFailByGestureRecognizer:forwardingGestureRecognizer]);
-  XCTAssertTrue([delayingGestureRecognizer.delegate gestureRecognizer:delayingGestureRecognizer
-                            shouldBeRequiredToFailByGestureRecognizer:otherGestureRecognizer]);
-
-  XCTAssertTrue([delayingGestureRecognizer.delegate gestureRecognizer:delayingGestureRecognizer
-                              shouldRequireFailureOfGestureRecognizer:forwardingGestureRecognizer]);
-  XCTAssertFalse([delayingGestureRecognizer.delegate gestureRecognizer:delayingGestureRecognizer
-                               shouldRequireFailureOfGestureRecognizer:otherGestureRecognizer]);
-
-  flutterPlatformViewsController->Reset();
-}
-
 - (void)testFlutterPlatformViewControllerSubmitFrameWithoutFlutterViewNotCrashing {
   flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
 
diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm
index 90c4d42..5e76654 100644
--- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm
+++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm
@@ -654,14 +654,14 @@
 
 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
     shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer {
-  // The forwarding gesture recognizer should always get all touch events, so it should not
-  // require other gesture recognizer to fail.
-  return otherGestureRecognizer != _forwardingRecognizer;
+  // The forwarding gesture recognizer should always get all touch events, so it should not be
+  // required to fail by any other gesture recognizer.
+  return otherGestureRecognizer != _forwardingRecognizer && otherGestureRecognizer != self;
 }
 
 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
     shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer {
-  return otherGestureRecognizer == _forwardingRecognizer;
+  return otherGestureRecognizer == self;
 }
 
 - (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
diff --git a/shell/platform/fuchsia/dart/BUILD.gn b/shell/platform/fuchsia/dart/BUILD.gn
index b2140f8..b35defc 100644
--- a/shell/platform/fuchsia/dart/BUILD.gn
+++ b/shell/platform/fuchsia/dart/BUILD.gn
@@ -76,9 +76,13 @@
   deps = []
 
   sources = [
+    "async_helper.dart",
     "config.dart",
     "expect.dart",
+    "legacy/async_minitest.dart",
+    "legacy/minitest.dart",
     "minitest.dart",
+    "static_type_helper.dart",
     "variations.dart",
   ]
 }
diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn
index 22c192e..a28e375 100644
--- a/shell/platform/linux/BUILD.gn
+++ b/shell/platform/linux/BUILD.gn
@@ -81,6 +81,7 @@
              "fl_dart_project_private.h",
              "fl_engine_private.h",
              "fl_keyboard_handler.h",
+             "fl_keyboard_manager.h",
              "fl_keyboard_pending_event.h",
              "fl_keyboard_view_delegate.h",
              "fl_key_event.h",
@@ -118,6 +119,7 @@
     "fl_key_responder.cc",
     "fl_keyboard_handler.cc",
     "fl_keyboard_layout.cc",
+    "fl_keyboard_manager.cc",
     "fl_keyboard_pending_event.cc",
     "fl_keyboard_view_delegate.cc",
     "fl_message_codec.cc",
@@ -217,6 +219,7 @@
     "fl_key_embedder_responder_test.cc",
     "fl_keyboard_handler_test.cc",
     "fl_keyboard_layout_test.cc",
+    "fl_keyboard_manager_test.cc",
     "fl_message_codec_test.cc",
     "fl_method_channel_test.cc",
     "fl_method_codec_test.cc",
diff --git a/shell/platform/linux/fl_keyboard_handler.cc b/shell/platform/linux/fl_keyboard_handler.cc
index e914925..266b9b1 100644
--- a/shell/platform/linux/fl_keyboard_handler.cc
+++ b/shell/platform/linux/fl_keyboard_handler.cc
@@ -4,376 +4,23 @@
 
 #include "flutter/shell/platform/linux/fl_keyboard_handler.h"
 
-#include <array>
-#include <cinttypes>
-#include <memory>
-#include <string>
-
-#include "flutter/shell/platform/linux/fl_key_channel_responder.h"
-#include "flutter/shell/platform/linux/fl_key_embedder_responder.h"
-#include "flutter/shell/platform/linux/fl_keyboard_layout.h"
-#include "flutter/shell/platform/linux/fl_keyboard_pending_event.h"
-#include "flutter/shell/platform/linux/key_mapping.h"
 #include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h"
 #include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h"
 
-// Turn on this flag to print complete layout data when switching IMEs. The data
-// is used in unit tests.
-#define DEBUG_PRINT_LAYOUT
-
 static constexpr char kChannelName[] = "flutter/keyboard";
 static constexpr char kGetKeyboardStateMethod[] = "getKeyboardState";
 
-/* Declarations of private classes */
-
-G_DECLARE_FINAL_TYPE(FlKeyboardHandlerUserData,
-                     fl_keyboard_handler_user_data,
-                     FL,
-                     KEYBOARD_HANDLER_USER_DATA,
-                     GObject);
-
-/* End declarations */
-
-namespace {
-
-static bool is_eascii(uint16_t character) {
-  return character < 256;
-}
-
-#ifdef DEBUG_PRINT_LAYOUT
-// Prints layout entries that will be parsed by `MockLayoutData`.
-void debug_format_layout_data(std::string& debug_layout_data,
-                              uint16_t keycode,
-                              uint16_t clue1,
-                              uint16_t clue2) {
-  if (keycode % 4 == 0) {
-    debug_layout_data.append("    ");
-  }
-
-  constexpr int kBufferSize = 30;
-  char buffer[kBufferSize];
-  buffer[0] = 0;
-  buffer[kBufferSize - 1] = 0;
-
-  snprintf(buffer, kBufferSize, "0x%04x, 0x%04x, ", clue1, clue2);
-  debug_layout_data.append(buffer);
-
-  if (keycode % 4 == 3) {
-    snprintf(buffer, kBufferSize, " // 0x%02x", keycode);
-    debug_layout_data.append(buffer);
-  }
-}
-#endif
-
-}  // namespace
-
-/* Define FlKeyboardHandlerUserData */
-
-/**
- * FlKeyboardHandlerUserData:
- * The user_data used when #FlKeyboardHandler sends event to
- * responders.
- */
-
-struct _FlKeyboardHandlerUserData {
-  GObject parent_instance;
-
-  // The owner handler.
-  GWeakRef handler;
-  uint64_t sequence_id;
-};
-
-G_DEFINE_TYPE(FlKeyboardHandlerUserData,
-              fl_keyboard_handler_user_data,
-              G_TYPE_OBJECT)
-
-static void fl_keyboard_handler_user_data_dispose(GObject* object) {
-  g_return_if_fail(FL_IS_KEYBOARD_HANDLER_USER_DATA(object));
-  FlKeyboardHandlerUserData* self = FL_KEYBOARD_HANDLER_USER_DATA(object);
-
-  g_weak_ref_clear(&self->handler);
-
-  G_OBJECT_CLASS(fl_keyboard_handler_user_data_parent_class)->dispose(object);
-}
-
-static void fl_keyboard_handler_user_data_class_init(
-    FlKeyboardHandlerUserDataClass* klass) {
-  G_OBJECT_CLASS(klass)->dispose = fl_keyboard_handler_user_data_dispose;
-}
-
-static void fl_keyboard_handler_user_data_init(
-    FlKeyboardHandlerUserData* self) {}
-
-// Creates a new FlKeyboardHandlerUserData private class with all information.
-static FlKeyboardHandlerUserData* fl_keyboard_handler_user_data_new(
-    FlKeyboardHandler* handler,
-    uint64_t sequence_id) {
-  FlKeyboardHandlerUserData* self = FL_KEYBOARD_HANDLER_USER_DATA(
-      g_object_new(fl_keyboard_handler_user_data_get_type(), nullptr));
-
-  g_weak_ref_init(&self->handler, handler);
-  self->sequence_id = sequence_id;
-  return self;
-}
-
-/* Define FlKeyboardHandler */
-
 struct _FlKeyboardHandler {
   GObject parent_instance;
 
   GWeakRef view_delegate;
 
-  // An array of #FlKeyResponder. Elements are added with
-  // #fl_keyboard_handler_add_responder immediately after initialization and are
-  // automatically released on dispose.
-  GPtrArray* responder_list;
-
-  // An array of #FlKeyboardPendingEvent.
-  //
-  // Its elements are *not* unreferenced when removed. When FlKeyboardHandler is
-  // disposed, this array will be set with a free_func so that the elements are
-  // unreferenced when removed.
-  GPtrArray* pending_responds;
-
-  // An array of #FlKeyboardPendingEvent.
-  //
-  // Its elements are unreferenced when removed.
-  GPtrArray* pending_redispatches;
-
-  // The last sequence ID used. Increased by 1 by every use.
-  uint64_t last_sequence_id;
-
-  // Record the derived layout.
-  //
-  // It is cleared when the platform reports a layout switch. Each entry,
-  // which corresponds to a group, is only initialized on the arrival of the
-  // first event for that group that has a goal keycode.
-  FlKeyboardLayout* derived_layout;
-
-  // A static map from keycodes to all layout goals.
-  //
-  // It is set up when the handler is initialized and is not changed ever after.
-  std::unique_ptr<std::map<uint16_t, const LayoutGoal*>> keycode_to_goals;
-
-  // A static map from logical keys to all mandatory layout goals.
-  //
-  // It is set up when the handler is initialized and is not changed ever after.
-  std::unique_ptr<std::map<uint64_t, const LayoutGoal*>>
-      logical_to_mandatory_goals;
-
   // The channel used by the framework to query the keyboard pressed state.
   FlMethodChannel* channel;
 };
 
 G_DEFINE_TYPE(FlKeyboardHandler, fl_keyboard_handler, G_TYPE_OBJECT);
 
-// This is an exact copy of g_ptr_array_find_with_equal_func.  Somehow CI
-// reports that can not find symbol g_ptr_array_find_with_equal_func, despite
-// the fact that it runs well locally.
-static gboolean g_ptr_array_find_with_equal_func1(GPtrArray* haystack,
-                                                  gconstpointer needle,
-                                                  GEqualFunc equal_func,
-                                                  guint* index_) {
-  guint i;
-  g_return_val_if_fail(haystack != NULL, FALSE);
-  if (equal_func == NULL) {
-    equal_func = g_direct_equal;
-  }
-  for (i = 0; i < haystack->len; i++) {
-    if (equal_func(g_ptr_array_index(haystack, i), needle)) {
-      if (index_ != NULL) {
-        *index_ = i;
-      }
-      return TRUE;
-    }
-  }
-
-  return FALSE;
-}
-
-// Compare a #FlKeyboardPendingEvent with the given sequence_id.
-static gboolean compare_pending_by_sequence_id(gconstpointer a,
-                                               gconstpointer b) {
-  FlKeyboardPendingEvent* pending =
-      FL_KEYBOARD_PENDING_EVENT(const_cast<gpointer>(a));
-  uint64_t sequence_id = *reinterpret_cast<const uint64_t*>(b);
-  return fl_keyboard_pending_event_get_sequence_id(pending) == sequence_id;
-}
-
-// Compare a #FlKeyboardPendingEvent with the given hash.
-static gboolean compare_pending_by_hash(gconstpointer a, gconstpointer b) {
-  FlKeyboardPendingEvent* pending =
-      FL_KEYBOARD_PENDING_EVENT(const_cast<gpointer>(a));
-  uint64_t hash = *reinterpret_cast<const uint64_t*>(b);
-  return fl_keyboard_pending_event_get_hash(pending) == hash;
-}
-
-// Try to remove a pending event from `pending_redispatches` with the target
-// hash.
-//
-// Returns true if the event is found and removed.
-static bool fl_keyboard_handler_remove_redispatched(FlKeyboardHandler* self,
-                                                    uint64_t hash) {
-  guint result_index;
-  gboolean found = g_ptr_array_find_with_equal_func1(
-      self->pending_redispatches, static_cast<const uint64_t*>(&hash),
-      compare_pending_by_hash, &result_index);
-  if (found) {
-    // The removed object is freed due to `pending_redispatches`'s free_func.
-    g_ptr_array_remove_index_fast(self->pending_redispatches, result_index);
-    return TRUE;
-  } else {
-    return FALSE;
-  }
-}
-
-// The callback used by a responder after the event was dispatched.
-static void responder_handle_event_callback(bool handled,
-                                            gpointer user_data_ptr) {
-  g_return_if_fail(FL_IS_KEYBOARD_HANDLER_USER_DATA(user_data_ptr));
-  FlKeyboardHandlerUserData* user_data =
-      FL_KEYBOARD_HANDLER_USER_DATA(user_data_ptr);
-
-  g_autoptr(FlKeyboardHandler) self =
-      FL_KEYBOARD_HANDLER(g_weak_ref_get(&user_data->handler));
-  if (self == nullptr) {
-    return;
-  }
-
-  g_autoptr(FlKeyboardViewDelegate) view_delegate =
-      FL_KEYBOARD_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate));
-  if (view_delegate == nullptr) {
-    return;
-  }
-
-  guint result_index = -1;
-  gboolean found = g_ptr_array_find_with_equal_func1(
-      self->pending_responds, &user_data->sequence_id,
-      compare_pending_by_sequence_id, &result_index);
-  g_return_if_fail(found);
-  FlKeyboardPendingEvent* pending = FL_KEYBOARD_PENDING_EVENT(
-      g_ptr_array_index(self->pending_responds, result_index));
-  g_return_if_fail(pending != nullptr);
-  fl_keyboard_pending_event_mark_replied(pending, handled);
-  // All responders have replied.
-  if (fl_keyboard_pending_event_is_complete(pending)) {
-    g_object_unref(user_data_ptr);
-    gpointer removed =
-        g_ptr_array_remove_index_fast(self->pending_responds, result_index);
-    g_return_if_fail(removed == pending);
-    bool should_redispatch =
-        !fl_keyboard_pending_event_get_any_handled(pending) &&
-        !fl_keyboard_view_delegate_text_filter_key_press(
-            view_delegate, fl_keyboard_pending_event_get_event(pending));
-    if (should_redispatch) {
-      g_ptr_array_add(self->pending_redispatches, pending);
-      fl_keyboard_view_delegate_redispatch_event(
-          view_delegate,
-          FL_KEY_EVENT(fl_keyboard_pending_event_get_event(pending)));
-    } else {
-      g_object_unref(pending);
-    }
-  }
-}
-
-static uint16_t convert_key_to_char(FlKeyboardViewDelegate* view_delegate,
-                                    guint keycode,
-                                    gint group,
-                                    gint level) {
-  GdkKeymapKey key = {keycode, group, level};
-  constexpr int kBmpMax = 0xD7FF;
-  guint origin = fl_keyboard_view_delegate_lookup_key(view_delegate, &key);
-  return origin < kBmpMax ? origin : 0xFFFF;
-}
-
-// Make sure that Flutter has derived the layout for the group of the event,
-// if the event contains a goal keycode.
-static void guarantee_layout(FlKeyboardHandler* self, FlKeyEvent* event) {
-  g_autoptr(FlKeyboardViewDelegate) view_delegate =
-      FL_KEYBOARD_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate));
-  if (view_delegate == nullptr) {
-    return;
-  }
-
-  guint8 group = fl_key_event_get_group(event);
-  if (fl_keyboard_layout_has_group(self->derived_layout, group)) {
-    return;
-  }
-  if (self->keycode_to_goals->find(fl_key_event_get_keycode(event)) ==
-      self->keycode_to_goals->end()) {
-    return;
-  }
-
-  // Clone all mandatory goals. Each goal is removed from this cloned map when
-  // fulfilled, and the remaining ones will be assigned to a default position.
-  std::map<uint64_t, const LayoutGoal*> remaining_mandatory_goals =
-      *self->logical_to_mandatory_goals;
-
-#ifdef DEBUG_PRINT_LAYOUT
-  std::string debug_layout_data;
-  for (uint16_t keycode = 0; keycode < 128; keycode += 1) {
-    std::vector<uint16_t> this_key_clues = {
-        convert_key_to_char(view_delegate, keycode, group, 0),
-        convert_key_to_char(view_delegate, keycode, group, 1),  // Shift
-    };
-    debug_format_layout_data(debug_layout_data, keycode, this_key_clues[0],
-                             this_key_clues[1]);
-  }
-#endif
-
-  // It's important to only traverse layout goals instead of all keycodes.
-  // Some key codes outside of the standard keyboard also gives alpha-numeric
-  // letters, and will therefore take over mandatory goals from standard
-  // keyboard keys if they come first. Example: French keyboard digit 1.
-  for (const LayoutGoal& keycode_goal : layout_goals) {
-    uint16_t keycode = keycode_goal.keycode;
-    std::vector<uint16_t> this_key_clues = {
-        convert_key_to_char(view_delegate, keycode, group, 0),
-        convert_key_to_char(view_delegate, keycode, group, 1),  // Shift
-    };
-
-    // The logical key should be the first available clue from below:
-    //
-    //  - Mandatory goal, if it matches any clue. This ensures that all alnum
-    //    keys can be found somewhere.
-    //  - US layout, if neither clue of the key is EASCII. This ensures that
-    //    there are no non-latin logical keys.
-    //  - A value derived on the fly from keycode & keyval.
-    for (uint16_t clue : this_key_clues) {
-      auto matching_goal = remaining_mandatory_goals.find(clue);
-      if (matching_goal != remaining_mandatory_goals.end()) {
-        // Found a key that produces a mandatory char. Use it.
-        g_return_if_fail(fl_keyboard_layout_get_logical_key(
-                             self->derived_layout, group, keycode) == 0);
-        fl_keyboard_layout_set_logical_key(self->derived_layout, group, keycode,
-                                           clue);
-        remaining_mandatory_goals.erase(matching_goal);
-        break;
-      }
-    }
-    bool has_any_eascii =
-        is_eascii(this_key_clues[0]) || is_eascii(this_key_clues[1]);
-    // See if any produced char meets the requirement as a logical key.
-    if (fl_keyboard_layout_get_logical_key(self->derived_layout, group,
-                                           keycode) == 0 &&
-        !has_any_eascii) {
-      auto found_us_layout = self->keycode_to_goals->find(keycode);
-      if (found_us_layout != self->keycode_to_goals->end()) {
-        fl_keyboard_layout_set_logical_key(
-            self->derived_layout, group, keycode,
-            found_us_layout->second->logical_key);
-      }
-    }
-  }
-
-  // Ensure all mandatory goals are assigned.
-  for (const auto mandatory_goal_iter : remaining_mandatory_goals) {
-    const LayoutGoal* goal = mandatory_goal_iter.second;
-    fl_keyboard_layout_set_logical_key(self->derived_layout, group,
-                                       goal->keycode, goal->logical_key);
-  }
-}
-
 // Returns the keyboard pressed state.
 static FlMethodResponse* get_keyboard_state(FlKeyboardHandler* self) {
   g_autoptr(FlValue) result = fl_value_new_map();
@@ -423,15 +70,7 @@
   FlKeyboardHandler* self = FL_KEYBOARD_HANDLER(object);
 
   g_weak_ref_clear(&self->view_delegate);
-
-  self->keycode_to_goals.reset();
-  self->logical_to_mandatory_goals.reset();
-
-  g_ptr_array_free(self->responder_list, TRUE);
-  g_ptr_array_set_free_func(self->pending_responds, g_object_unref);
-  g_ptr_array_free(self->pending_responds, TRUE);
-  g_ptr_array_free(self->pending_redispatches, TRUE);
-  g_clear_object(&self->derived_layout);
+  g_clear_object(&self->channel);
 
   G_OBJECT_CLASS(fl_keyboard_handler_parent_class)->dispose(object);
 }
@@ -440,27 +79,7 @@
   G_OBJECT_CLASS(klass)->dispose = fl_keyboard_handler_dispose;
 }
 
-static void fl_keyboard_handler_init(FlKeyboardHandler* self) {
-  self->derived_layout = fl_keyboard_layout_new();
-
-  self->keycode_to_goals =
-      std::make_unique<std::map<uint16_t, const LayoutGoal*>>();
-  self->logical_to_mandatory_goals =
-      std::make_unique<std::map<uint64_t, const LayoutGoal*>>();
-  for (const LayoutGoal& goal : layout_goals) {
-    (*self->keycode_to_goals)[goal.keycode] = &goal;
-    if (goal.mandatory) {
-      (*self->logical_to_mandatory_goals)[goal.logical_key] = &goal;
-    }
-  }
-
-  self->responder_list = g_ptr_array_new_with_free_func(g_object_unref);
-
-  self->pending_responds = g_ptr_array_new();
-  self->pending_redispatches = g_ptr_array_new_with_free_func(g_object_unref);
-
-  self->last_sequence_id = 1;
-}
+static void fl_keyboard_handler_init(FlKeyboardHandler* self) {}
 
 FlKeyboardHandler* fl_keyboard_handler_new(
     FlBinaryMessenger* messenger,
@@ -472,27 +91,6 @@
 
   g_weak_ref_init(&self->view_delegate, view_delegate);
 
-  // The embedder responder must be added before the channel responder.
-  g_ptr_array_add(
-      self->responder_list,
-      FL_KEY_RESPONDER(fl_key_embedder_responder_new(
-          [](const FlutterKeyEvent* event, FlutterKeyEventCallback callback,
-             void* callback_user_data, void* send_key_event_user_data) {
-            FlKeyboardHandler* self =
-                FL_KEYBOARD_HANDLER(send_key_event_user_data);
-            g_autoptr(FlKeyboardViewDelegate) view_delegate =
-                FL_KEYBOARD_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate));
-            if (view_delegate == nullptr) {
-              return;
-            }
-            fl_keyboard_view_delegate_send_key_event(
-                view_delegate, event, callback, callback_user_data);
-          },
-          self)));
-  g_ptr_array_add(self->responder_list,
-                  FL_KEY_RESPONDER(fl_key_channel_responder_new(
-                      fl_keyboard_view_delegate_get_messenger(view_delegate))));
-
   // Setup the flutter/keyboard channel.
   g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
   self->channel =
@@ -501,70 +99,3 @@
                                             self, nullptr);
   return self;
 }
-
-gboolean fl_keyboard_handler_handle_event(FlKeyboardHandler* self,
-                                          FlKeyEvent* event) {
-  g_return_val_if_fail(FL_IS_KEYBOARD_HANDLER(self), FALSE);
-  g_return_val_if_fail(event != nullptr, FALSE);
-
-  guarantee_layout(self, event);
-
-  uint64_t incoming_hash = fl_key_event_hash(event);
-  if (fl_keyboard_handler_remove_redispatched(self, incoming_hash)) {
-    return FALSE;
-  }
-
-  FlKeyboardPendingEvent* pending = fl_keyboard_pending_event_new(
-      event, ++self->last_sequence_id, self->responder_list->len);
-
-  g_ptr_array_add(self->pending_responds, pending);
-  FlKeyboardHandlerUserData* user_data = fl_keyboard_handler_user_data_new(
-      self, fl_keyboard_pending_event_get_sequence_id(pending));
-  uint64_t specified_logical_key = fl_keyboard_layout_get_logical_key(
-      self->derived_layout, fl_key_event_get_group(event),
-      fl_key_event_get_keycode(event));
-  for (guint i = 0; i < self->responder_list->len; i++) {
-    FlKeyResponder* responder =
-        FL_KEY_RESPONDER(g_ptr_array_index(self->responder_list, i));
-    fl_key_responder_handle_event(responder, event,
-                                  responder_handle_event_callback, user_data,
-                                  specified_logical_key);
-  }
-
-  return TRUE;
-}
-
-gboolean fl_keyboard_handler_is_state_clear(FlKeyboardHandler* self) {
-  g_return_val_if_fail(FL_IS_KEYBOARD_HANDLER(self), FALSE);
-  return self->pending_responds->len == 0 &&
-         self->pending_redispatches->len == 0;
-}
-
-void fl_keyboard_handler_sync_modifier_if_needed(FlKeyboardHandler* self,
-                                                 guint state,
-                                                 double event_time) {
-  g_return_if_fail(FL_IS_KEYBOARD_HANDLER(self));
-
-  // The embedder responder is the first element in
-  // FlKeyboardHandler.responder_list.
-  FlKeyEmbedderResponder* responder =
-      FL_KEY_EMBEDDER_RESPONDER(g_ptr_array_index(self->responder_list, 0));
-  fl_key_embedder_responder_sync_modifiers_if_needed(responder, state,
-                                                     event_time);
-}
-
-GHashTable* fl_keyboard_handler_get_pressed_state(FlKeyboardHandler* self) {
-  g_return_val_if_fail(FL_IS_KEYBOARD_HANDLER(self), nullptr);
-
-  // The embedder responder is the first element in
-  // FlKeyboardHandler.responder_list.
-  FlKeyEmbedderResponder* responder =
-      FL_KEY_EMBEDDER_RESPONDER(g_ptr_array_index(self->responder_list, 0));
-  return fl_key_embedder_responder_get_pressed_state(responder);
-}
-
-void fl_keyboard_handler_notify_layout_changed(FlKeyboardHandler* self) {
-  g_return_if_fail(FL_IS_KEYBOARD_HANDLER(self));
-  g_clear_object(&self->derived_layout);
-  self->derived_layout = fl_keyboard_layout_new();
-}
diff --git a/shell/platform/linux/fl_keyboard_handler.h b/shell/platform/linux/fl_keyboard_handler.h
index 0d551d6..57da5e9 100644
--- a/shell/platform/linux/fl_keyboard_handler.h
+++ b/shell/platform/linux/fl_keyboard_handler.h
@@ -8,6 +8,7 @@
 #include <gdk/gdk.h>
 
 #include "flutter/shell/platform/linux/fl_keyboard_view_delegate.h"
+#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h"
 
 G_BEGIN_DECLS
 
@@ -21,17 +22,7 @@
 /**
  * FlKeyboardHandler:
  *
- * Processes keyboard events and cooperate with `TextInputHandler`.
- *
- * A keyboard event goes through a few sections, each can choose to handle the
- * event, and only unhandled events can move to the next section:
- *
- * - Keyboard: Dispatch to the embedder responder and the channel responder
- *   simultaneously. After both responders have responded (asynchronously), the
- *   event is considered handled if either responder handles it.
- * - Text input: Events are sent to IM filter (usually owned by
- *   `TextInputHandler`) and are handled synchronously.
- * - Redispatching: Events are inserted back to the system for redispatching.
+ * Provides the channel to receive keyboard requests from the Dart code.
  */
 
 /**
@@ -47,60 +38,6 @@
     FlBinaryMessenger* messenger,
     FlKeyboardViewDelegate* view_delegate);
 
-/**
- * fl_keyboard_handler_handle_event:
- * @handler: the #FlKeyboardHandler self.
- * @event: the event to be dispatched. It is usually a wrap of a GdkEventKey.
- * This event will be managed and released by #FlKeyboardHandler.
- *
- * Make the handler process a system key event. This might eventually send
- * messages to the framework, trigger text input effects, or redispatch the
- * event back to the system.
- */
-gboolean fl_keyboard_handler_handle_event(FlKeyboardHandler* handler,
-                                          FlKeyEvent* event);
-
-/**
- * fl_keyboard_handler_is_state_clear:
- * @handler: the #FlKeyboardHandler self.
- *
- * A debug-only method that queries whether the handler's various states are
- * cleared, i.e. no pending events for redispatching or for responding.
- *
- * Returns: true if the handler's various states are cleared.
- */
-gboolean fl_keyboard_handler_is_state_clear(FlKeyboardHandler* handler);
-
-/**
- * fl_keyboard_handler_sync_modifier_if_needed:
- * @handler: the #FlKeyboardHandler self.
- * @state: the state of the modifiers mask.
- * @event_time: the time attribute of the incoming GDK event.
- *
- * If needed, synthesize modifier keys up and down event by comparing their
- * current pressing states with the given modifiers mask.
- */
-void fl_keyboard_handler_sync_modifier_if_needed(FlKeyboardHandler* handler,
-                                                 guint state,
-                                                 double event_time);
-
-/**
- * fl_keyboard_handler_get_pressed_state:
- * @handler: the #FlKeyboardHandler self.
- *
- * Returns the keyboard pressed state. The hash table contains one entry per
- * pressed keys, mapping from the logical key to the physical key.*
- */
-GHashTable* fl_keyboard_handler_get_pressed_state(FlKeyboardHandler* handler);
-
-/**
- * fl_keyboard_handler_notify_layout_changed:
- * @handler: the #FlKeyboardHandler self.
- *
- * Notify the handler the keyboard layout has changed.
- */
-void fl_keyboard_handler_notify_layout_changed(FlKeyboardHandler* handler);
-
 G_END_DECLS
 
 #endif  // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_HANDLER_H_
diff --git a/shell/platform/linux/fl_keyboard_handler_test.cc b/shell/platform/linux/fl_keyboard_handler_test.cc
index f0e9815..a4d9278 100644
--- a/shell/platform/linux/fl_keyboard_handler_test.cc
+++ b/shell/platform/linux/fl_keyboard_handler_test.cc
@@ -4,143 +4,26 @@
 
 #include "flutter/shell/platform/linux/fl_keyboard_handler.h"
 
-#include <cstring>
-#include <vector>
-
-#include "flutter/shell/platform/embedder/test_utils/key_codes.g.h"
-#include "flutter/shell/platform/linux/fl_binary_messenger_private.h"
 #include "flutter/shell/platform/linux/fl_method_codec_private.h"
-#include "flutter/shell/platform/linux/key_mapping.h"
-#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h"
-#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_codec.h"
 #include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h"
-#include "flutter/shell/platform/linux/testing/fl_test.h"
 #include "flutter/shell/platform/linux/testing/mock_binary_messenger.h"
-#include "flutter/shell/platform/linux/testing/mock_text_input_handler.h"
-#include "flutter/testing/testing.h"
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 
-// Define compound `expect` in macros. If they were defined in functions, the
-// stacktrace wouldn't print where the function is called in the unit tests.
-
-#define EXPECT_KEY_EVENT(RECORD, TYPE, PHYSICAL, LOGICAL, CHAR, SYNTHESIZED) \
-  EXPECT_EQ((RECORD).type, CallRecord::kKeyCallEmbedder);                    \
-  EXPECT_EQ((RECORD).event->type, (TYPE));                                   \
-  EXPECT_EQ((RECORD).event->physical, (PHYSICAL));                           \
-  EXPECT_EQ((RECORD).event->logical, (LOGICAL));                             \
-  EXPECT_STREQ((RECORD).event->character, (CHAR));                           \
-  EXPECT_EQ((RECORD).event->synthesized, (SYNTHESIZED));
-
-#define VERIFY_DOWN(OUT_LOGICAL, OUT_CHAR)                          \
-  EXPECT_EQ(call_records[0].type, CallRecord::kKeyCallEmbedder);    \
-  EXPECT_EQ(call_records[0].event->type, kFlutterKeyEventTypeDown); \
-  EXPECT_EQ(call_records[0].event->logical, (OUT_LOGICAL));         \
-  EXPECT_STREQ(call_records[0].event->character, (OUT_CHAR));       \
-  EXPECT_EQ(call_records[0].event->synthesized, false);             \
-  call_records.clear()
-
-namespace {
-using ::flutter::testing::keycodes::kLogicalAltLeft;
-using ::flutter::testing::keycodes::kLogicalBracketLeft;
-using ::flutter::testing::keycodes::kLogicalComma;
-using ::flutter::testing::keycodes::kLogicalControlLeft;
-using ::flutter::testing::keycodes::kLogicalDigit1;
-using ::flutter::testing::keycodes::kLogicalKeyA;
-using ::flutter::testing::keycodes::kLogicalKeyB;
-using ::flutter::testing::keycodes::kLogicalKeyM;
-using ::flutter::testing::keycodes::kLogicalKeyQ;
-using ::flutter::testing::keycodes::kLogicalMetaLeft;
-using ::flutter::testing::keycodes::kLogicalMinus;
-using ::flutter::testing::keycodes::kLogicalParenthesisRight;
-using ::flutter::testing::keycodes::kLogicalSemicolon;
-using ::flutter::testing::keycodes::kLogicalShiftLeft;
-using ::flutter::testing::keycodes::kLogicalUnderscore;
-
-using ::flutter::testing::keycodes::kPhysicalAltLeft;
-using ::flutter::testing::keycodes::kPhysicalControlLeft;
-using ::flutter::testing::keycodes::kPhysicalKeyA;
-using ::flutter::testing::keycodes::kPhysicalKeyB;
-using ::flutter::testing::keycodes::kPhysicalMetaLeft;
-using ::flutter::testing::keycodes::kPhysicalShiftLeft;
-
-// Hardware key codes.
-typedef std::function<void(bool handled)> AsyncKeyCallback;
-typedef std::function<void(AsyncKeyCallback callback)> ChannelCallHandler;
-typedef std::function<void(const FlutterKeyEvent* event,
-                           AsyncKeyCallback callback)>
-    EmbedderCallHandler;
-typedef std::function<void(FlKeyEvent*)> RedispatchHandler;
-
-// A type that can record all kinds of effects that the keyboard handler
-// triggers.
-//
-// An instance of `CallRecord` might not have all the fields filled.
-typedef struct {
-  enum {
-    kKeyCallEmbedder,
-    kKeyCallChannel,
-  } type;
-
-  AsyncKeyCallback callback;
-  std::unique_ptr<FlutterKeyEvent> event;
-  std::unique_ptr<char[]> event_character;
-} CallRecord;
-
-// Clone a C-string.
-//
-// Must be deleted by delete[].
-char* cloneString(const char* source) {
-  if (source == nullptr) {
-    return nullptr;
-  }
-  size_t charLen = strlen(source);
-  char* target = new char[charLen + 1];
-  strncpy(target, source, charLen + 1);
-  return target;
-}
-
-constexpr guint16 kKeyCodeKeyA = 0x26u;
-constexpr guint16 kKeyCodeKeyB = 0x38u;
-constexpr guint16 kKeyCodeKeyM = 0x3au;
-constexpr guint16 kKeyCodeDigit1 = 0x0au;
-constexpr guint16 kKeyCodeMinus = 0x14u;
-constexpr guint16 kKeyCodeSemicolon = 0x2fu;
-constexpr guint16 kKeyCodeKeyLeftBracket = 0x22u;
-
-static constexpr char kKeyEventChannelName[] = "flutter/keyevent";
 static constexpr char kKeyboardChannelName[] = "flutter/keyboard";
 static constexpr char kGetKeyboardStateMethod[] = "getKeyboardState";
 static constexpr uint64_t kMockPhysicalKey = 42;
 static constexpr uint64_t kMockLogicalKey = 42;
 
-// All key clues for a keyboard layout.
-//
-// The index is (keyCode * 2 + hasShift), where each value is the character for
-// this key (GTK only supports UTF-16.) Since the maximum keycode of interest
-// is 128, it has a total of 256 entries..
-typedef std::array<uint32_t, 256> MockGroupLayoutData;
-typedef std::vector<const MockGroupLayoutData*> MockLayoutData;
-
-extern const MockLayoutData kLayoutUs;
-extern const MockLayoutData kLayoutRussian;
-extern const MockLayoutData kLayoutFrench;
-
 G_BEGIN_DECLS
 
-G_DECLARE_FINAL_TYPE(FlMockViewDelegate,
-                     fl_mock_view_delegate,
+G_DECLARE_FINAL_TYPE(FlMockKeyboardHandlerDelegate,
+                     fl_mock_keyboard_handler_delegate,
                      FL,
-                     MOCK_VIEW_DELEGATE,
+                     MOCK_KEYBOARD_HANDLER_DELEGATE,
                      GObject);
 
-G_DECLARE_FINAL_TYPE(FlMockKeyBinaryMessenger,
-                     fl_mock_key_binary_messenger,
-                     FL,
-                     MOCK_KEY_BINARY_MESSENGER,
-                     GObject)
-
 G_END_DECLS
 
 MATCHER_P(MethodSuccessResponse, result, "") {
@@ -156,243 +39,26 @@
   return false;
 }
 
-/***** FlMockKeyBinaryMessenger *****/
-/* Mock a binary messenger that only processes messages from the embedding on
- * the key event channel, and does so according to the callback set by
- * fl_mock_key_binary_messenger_set_callback_handler */
-
-struct _FlMockKeyBinaryMessenger {
+struct _FlMockKeyboardHandlerDelegate {
   GObject parent_instance;
 };
 
-struct FlMockKeyBinaryMessengerPrivate {
-  ChannelCallHandler callback_handler;
-};
-
-static void fl_mock_key_binary_messenger_iface_init(
-    FlBinaryMessengerInterface* iface);
-
-G_DEFINE_TYPE_WITH_CODE(
-    FlMockKeyBinaryMessenger,
-    fl_mock_key_binary_messenger,
-    G_TYPE_OBJECT,
-    G_IMPLEMENT_INTERFACE(fl_binary_messenger_get_type(),
-                          fl_mock_key_binary_messenger_iface_init);
-    G_ADD_PRIVATE(FlMockKeyBinaryMessenger))
-
-#define FL_MOCK_KEY_BINARY_MESSENGER_GET_PRIVATE(obj)    \
-  static_cast<FlMockKeyBinaryMessengerPrivate*>(         \
-      fl_mock_key_binary_messenger_get_instance_private( \
-          FL_MOCK_KEY_BINARY_MESSENGER(obj)))
-
-static void fl_mock_key_binary_messenger_init(FlMockKeyBinaryMessenger* self) {
-  FlMockKeyBinaryMessengerPrivate* priv =
-      FL_MOCK_KEY_BINARY_MESSENGER_GET_PRIVATE(self);
-  new (priv) FlMockKeyBinaryMessengerPrivate();
-}
-
-static void fl_mock_key_binary_messenger_dispose(GObject* object) {
-  FL_MOCK_KEY_BINARY_MESSENGER_GET_PRIVATE(object)
-      ->~FlMockKeyBinaryMessengerPrivate();
-
-  G_OBJECT_CLASS(fl_mock_key_binary_messenger_parent_class)->dispose(object);
-}
-
-static void fl_mock_key_binary_messenger_class_init(
-    FlMockKeyBinaryMessengerClass* klass) {
-  G_OBJECT_CLASS(klass)->dispose = fl_mock_key_binary_messenger_dispose;
-}
-
-static void fl_mock_key_binary_messenger_send_on_channel(
-    FlBinaryMessenger* messenger,
-    const gchar* channel,
-    GBytes* message,
-    GCancellable* cancellable,
-    GAsyncReadyCallback callback,
-    gpointer user_data) {
-  FlMockKeyBinaryMessenger* self = FL_MOCK_KEY_BINARY_MESSENGER(messenger);
-
-  if (callback != nullptr) {
-    EXPECT_STREQ(channel, kKeyEventChannelName);
-    FL_MOCK_KEY_BINARY_MESSENGER_GET_PRIVATE(self)->callback_handler(
-        [self, cancellable, callback, user_data](bool handled) {
-          g_autoptr(GTask) task =
-              g_task_new(self, cancellable, callback, user_data);
-          g_autoptr(FlValue) result = fl_value_new_map();
-          fl_value_set_string_take(result, "handled",
-                                   fl_value_new_bool(handled));
-          g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new();
-          g_autoptr(GError) error = nullptr;
-          GBytes* data = fl_message_codec_encode_message(
-              FL_MESSAGE_CODEC(codec), result, &error);
-
-          g_task_return_pointer(
-              task, data, reinterpret_cast<GDestroyNotify>(g_bytes_unref));
-        });
-  }
-}
-
-static GBytes* fl_mock_key_binary_messenger_send_on_channel_finish(
-    FlBinaryMessenger* messenger,
-    GAsyncResult* result,
-    GError** error) {
-  return static_cast<GBytes*>(g_task_propagate_pointer(G_TASK(result), error));
-}
-
-static void fl_mock_binary_messenger_resize_channel(
-    FlBinaryMessenger* messenger,
-    const gchar* channel,
-    int64_t new_size) {
-  // Mock implementation. Do nothing.
-}
-
-static void fl_mock_binary_messenger_set_warns_on_channel_overflow(
-    FlBinaryMessenger* messenger,
-    const gchar* channel,
-    bool warns) {
-  // Mock implementation. Do nothing.
-}
-
-static void fl_mock_key_binary_messenger_iface_init(
-    FlBinaryMessengerInterface* iface) {
-  iface->set_message_handler_on_channel =
-      [](FlBinaryMessenger* messenger, const gchar* channel,
-         FlBinaryMessengerMessageHandler handler, gpointer user_data,
-         GDestroyNotify destroy_notify) {
-        EXPECT_STREQ(channel, kKeyEventChannelName);
-        // No need to mock. The key event channel expects no incoming messages
-        // from the framework.
-      };
-  iface->send_response = [](FlBinaryMessenger* messenger,
-                            FlBinaryMessengerResponseHandle* response_handle,
-                            GBytes* response, GError** error) -> gboolean {
-    // The key event channel expects no incoming messages from the framework,
-    // hence no responses either.
-    g_return_val_if_reached(TRUE);
-    return TRUE;
-  };
-  iface->send_on_channel = fl_mock_key_binary_messenger_send_on_channel;
-  iface->send_on_channel_finish =
-      fl_mock_key_binary_messenger_send_on_channel_finish;
-  iface->resize_channel = fl_mock_binary_messenger_resize_channel;
-  iface->set_warns_on_channel_overflow =
-      fl_mock_binary_messenger_set_warns_on_channel_overflow;
-}
-
-static FlMockKeyBinaryMessenger* fl_mock_key_binary_messenger_new() {
-  FlMockKeyBinaryMessenger* self = FL_MOCK_KEY_BINARY_MESSENGER(
-      g_object_new(fl_mock_key_binary_messenger_get_type(), NULL));
-
-  // Added to stop compiler complaining about an unused function.
-  FL_IS_MOCK_KEY_BINARY_MESSENGER(self);
-
-  return self;
-}
-
-static void fl_mock_key_binary_messenger_set_callback_handler(
-    FlMockKeyBinaryMessenger* self,
-    ChannelCallHandler handler) {
-  FL_MOCK_KEY_BINARY_MESSENGER_GET_PRIVATE(self)->callback_handler =
-      std::move(handler);
-}
-
-/***** FlMockViewDelegate *****/
-
-struct _FlMockViewDelegate {
-  GObject parent_instance;
-};
-
-struct FlMockViewDelegatePrivate {
-  FlMockKeyBinaryMessenger* messenger;
-  EmbedderCallHandler embedder_handler;
-  bool text_filter_result;
-  RedispatchHandler redispatch_handler;
-  const MockLayoutData* layout_data;
-};
-
-static void fl_mock_view_keyboard_delegate_iface_init(
+static void fl_mock_keyboard_handler_delegate_keyboard_view_delegate_iface_init(
     FlKeyboardViewDelegateInterface* iface);
 
 G_DEFINE_TYPE_WITH_CODE(
-    FlMockViewDelegate,
-    fl_mock_view_delegate,
+    FlMockKeyboardHandlerDelegate,
+    fl_mock_keyboard_handler_delegate,
     G_TYPE_OBJECT,
-    G_IMPLEMENT_INTERFACE(fl_keyboard_view_delegate_get_type(),
-                          fl_mock_view_keyboard_delegate_iface_init);
-    G_ADD_PRIVATE(FlMockViewDelegate))
+    G_IMPLEMENT_INTERFACE(
+        fl_keyboard_view_delegate_get_type(),
+        fl_mock_keyboard_handler_delegate_keyboard_view_delegate_iface_init))
 
-#define FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(obj) \
-  static_cast<FlMockViewDelegatePrivate*>(     \
-      fl_mock_view_delegate_get_instance_private(FL_MOCK_VIEW_DELEGATE(obj)))
+static void fl_mock_keyboard_handler_delegate_init(
+    FlMockKeyboardHandlerDelegate* self) {}
 
-static void fl_mock_view_delegate_init(FlMockViewDelegate* self) {
-  FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(self);
-  new (priv) FlMockViewDelegatePrivate();
-}
-
-static void fl_mock_view_delegate_dispose(GObject* object) {
-  FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(object);
-
-  FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(object)->~FlMockViewDelegatePrivate();
-  g_clear_object(&priv->messenger);
-
-  G_OBJECT_CLASS(fl_mock_view_delegate_parent_class)->dispose(object);
-}
-
-static void fl_mock_view_delegate_class_init(FlMockViewDelegateClass* klass) {
-  G_OBJECT_CLASS(klass)->dispose = fl_mock_view_delegate_dispose;
-}
-
-static void fl_mock_view_keyboard_send_key_event(
-    FlKeyboardViewDelegate* view_delegate,
-    const FlutterKeyEvent* event,
-    FlutterKeyEventCallback callback,
-    void* user_data) {
-  FlMockViewDelegatePrivate* priv =
-      FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(view_delegate);
-  priv->embedder_handler(event, [callback, user_data](bool handled) {
-    if (callback != nullptr) {
-      callback(handled, user_data);
-    }
-  });
-}
-
-static gboolean fl_mock_view_keyboard_text_filter_key_press(
-    FlKeyboardViewDelegate* view_delegate,
-    FlKeyEvent* event) {
-  FlMockViewDelegatePrivate* priv =
-      FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(view_delegate);
-  return priv->text_filter_result;
-}
-
-static FlBinaryMessenger* fl_mock_view_keyboard_get_messenger(
-    FlKeyboardViewDelegate* view_delegate) {
-  FlMockViewDelegatePrivate* priv =
-      FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(view_delegate);
-  return FL_BINARY_MESSENGER(priv->messenger);
-}
-
-static void fl_mock_view_keyboard_redispatch_event(
-    FlKeyboardViewDelegate* view_delegate,
-    FlKeyEvent* event) {
-  FlMockViewDelegatePrivate* priv =
-      FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(view_delegate);
-  if (priv->redispatch_handler) {
-    priv->redispatch_handler(event);
-  }
-}
-
-static guint fl_mock_view_keyboard_lookup_key(FlKeyboardViewDelegate* delegate,
-                                              const GdkKeymapKey* key) {
-  FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(delegate);
-  guint8 group = static_cast<guint8>(key->group);
-  EXPECT_LT(group, priv->layout_data->size());
-  const MockGroupLayoutData* group_layout = (*priv->layout_data)[group];
-  EXPECT_TRUE(group_layout != nullptr);
-  EXPECT_TRUE(key->level == 0 || key->level == 1);
-  bool shift = key->level == 1;
-  return (*group_layout)[key->keycode * 2 + shift];
-}
+static void fl_mock_keyboard_handler_delegate_class_init(
+    FlMockKeyboardHandlerDelegateClass* klass) {}
 
 static GHashTable* fl_mock_view_keyboard_get_keyboard_state(
     FlKeyboardViewDelegate* view_delegate) {
@@ -403,620 +69,27 @@
   return result;
 }
 
-static void fl_mock_view_keyboard_delegate_iface_init(
+static void fl_mock_keyboard_handler_delegate_keyboard_view_delegate_iface_init(
     FlKeyboardViewDelegateInterface* iface) {
-  iface->send_key_event = fl_mock_view_keyboard_send_key_event;
-  iface->text_filter_key_press = fl_mock_view_keyboard_text_filter_key_press;
-  iface->get_messenger = fl_mock_view_keyboard_get_messenger;
-  iface->redispatch_event = fl_mock_view_keyboard_redispatch_event;
-  iface->lookup_key = fl_mock_view_keyboard_lookup_key;
   iface->get_keyboard_state = fl_mock_view_keyboard_get_keyboard_state;
 }
 
-static FlMockViewDelegate* fl_mock_view_delegate_new() {
-  FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(
-      g_object_new(fl_mock_view_delegate_get_type(), nullptr));
+static FlMockKeyboardHandlerDelegate* fl_mock_keyboard_handler_delegate_new() {
+  FlMockKeyboardHandlerDelegate* self = FL_MOCK_KEYBOARD_HANDLER_DELEGATE(
+      g_object_new(fl_mock_keyboard_handler_delegate_get_type(), nullptr));
 
   // Added to stop compiler complaining about an unused function.
-  FL_IS_MOCK_VIEW_DELEGATE(self);
-
-  FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(self);
-  priv->messenger = fl_mock_key_binary_messenger_new();
+  FL_IS_MOCK_KEYBOARD_HANDLER_DELEGATE(self);
 
   return self;
 }
 
-static void fl_mock_view_set_embedder_handler(FlMockViewDelegate* self,
-                                              EmbedderCallHandler handler) {
-  FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(self);
-  priv->embedder_handler = std::move(handler);
-}
-
-static void fl_mock_view_set_text_filter_result(FlMockViewDelegate* self,
-                                                bool result) {
-  FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(self);
-  priv->text_filter_result = result;
-}
-
-static void fl_mock_view_set_redispatch_handler(FlMockViewDelegate* self,
-                                                RedispatchHandler handler) {
-  FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(self);
-  priv->redispatch_handler = std::move(handler);
-}
-
-static void fl_mock_view_set_layout(FlMockViewDelegate* self,
-                                    const MockLayoutData* layout) {
-  FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(self);
-  priv->layout_data = layout;
-}
-
-/***** End FlMockViewDelegate *****/
-
-class KeyboardTester {
- public:
-  KeyboardTester() {
-    ::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;
-
-    view_ = fl_mock_view_delegate_new();
-    respondToEmbedderCallsWith(false);
-    respondToChannelCallsWith(false);
-    respondToTextInputWith(false);
-    setLayout(kLayoutUs);
-
-    handler_ =
-        fl_keyboard_handler_new(messenger, FL_KEYBOARD_VIEW_DELEGATE(view_));
-  }
-
-  ~KeyboardTester() {
-    g_clear_object(&view_);
-    g_clear_object(&handler_);
-    g_clear_pointer(&redispatched_events_, g_ptr_array_unref);
-  }
-
-  FlKeyboardHandler* handler() { return handler_; }
-
-  // Block until all GdkMainLoop messages are processed, which is basically
-  // used only for channel messages.
-  void flushChannelMessages() {
-    GMainLoop* loop = g_main_loop_new(nullptr, 0);
-    g_idle_add(_flushChannelMessagesCb, loop);
-    g_main_loop_run(loop);
-  }
-
-  // Dispatch each of the given events, expect their results to be false
-  // (unhandled), and clear the event array.
-  //
-  // Returns the number of events redispatched. If any result is unexpected
-  // (handled), return a minus number `-x` instead, where `x` is the index of
-  // the first unexpected redispatch.
-  int redispatchEventsAndClear(GPtrArray* events) {
-    guint event_count = events->len;
-    int first_error = -1;
-    during_redispatch_ = true;
-    for (guint event_id = 0; event_id < event_count; event_id += 1) {
-      FlKeyEvent* event = FL_KEY_EVENT(g_ptr_array_index(events, event_id));
-      bool handled = fl_keyboard_handler_handle_event(handler_, event);
-      EXPECT_FALSE(handled);
-      if (handled) {
-        first_error = first_error == -1 ? event_id : first_error;
-      }
-    }
-    during_redispatch_ = false;
-    g_ptr_array_set_size(events, 0);
-    return first_error < 0 ? event_count : -first_error;
-  }
-
-  void respondToEmbedderCallsWith(bool response) {
-    fl_mock_view_set_embedder_handler(
-        view_, [response, this](const FlutterKeyEvent* event,
-                                const AsyncKeyCallback& callback) {
-          EXPECT_FALSE(during_redispatch_);
-          callback(response);
-        });
-  }
-
-  void recordEmbedderCallsTo(std::vector<CallRecord>& storage) {
-    fl_mock_view_set_embedder_handler(
-        view_, [&storage, this](const FlutterKeyEvent* event,
-                                AsyncKeyCallback callback) {
-          EXPECT_FALSE(during_redispatch_);
-          auto new_event = std::make_unique<FlutterKeyEvent>(*event);
-          char* new_event_character = cloneString(event->character);
-          new_event->character = new_event_character;
-          storage.push_back(CallRecord{
-              .type = CallRecord::kKeyCallEmbedder,
-              .callback = std::move(callback),
-              .event = std::move(new_event),
-              .event_character = std::unique_ptr<char[]>(new_event_character),
-          });
-        });
-  }
-
-  void respondToEmbedderCallsWithAndRecordsTo(
-      bool response,
-      std::vector<CallRecord>& storage) {
-    fl_mock_view_set_embedder_handler(
-        view_, [&storage, response, this](const FlutterKeyEvent* event,
-                                          const AsyncKeyCallback& callback) {
-          EXPECT_FALSE(during_redispatch_);
-          auto new_event = std::make_unique<FlutterKeyEvent>(*event);
-          char* new_event_character = cloneString(event->character);
-          new_event->character = new_event_character;
-          storage.push_back(CallRecord{
-              .type = CallRecord::kKeyCallEmbedder,
-              .event = std::move(new_event),
-              .event_character = std::unique_ptr<char[]>(new_event_character),
-          });
-          callback(response);
-        });
-  }
-
-  void respondToChannelCallsWith(bool response) {
-    FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(view_);
-
-    fl_mock_key_binary_messenger_set_callback_handler(
-        priv->messenger, [response, this](const AsyncKeyCallback& callback) {
-          EXPECT_FALSE(during_redispatch_);
-          callback(response);
-        });
-  }
-
-  void recordChannelCallsTo(std::vector<CallRecord>& storage) {
-    FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(view_);
-
-    fl_mock_key_binary_messenger_set_callback_handler(
-        priv->messenger, [&storage, this](AsyncKeyCallback callback) {
-          EXPECT_FALSE(during_redispatch_);
-          storage.push_back(CallRecord{
-              .type = CallRecord::kKeyCallChannel,
-              .callback = std::move(callback),
-          });
-        });
-  }
-
-  void respondToTextInputWith(bool response) {
-    fl_mock_view_set_text_filter_result(view_, response);
-  }
-
-  void recordRedispatchedEventsTo(GPtrArray* storage) {
-    redispatched_events_ = g_ptr_array_ref(storage);
-    fl_mock_view_set_redispatch_handler(view_, [this](FlKeyEvent* key) {
-      g_ptr_array_add(redispatched_events_, g_object_ref(key));
-    });
-  }
-
-  void setLayout(const MockLayoutData& layout) {
-    fl_mock_view_set_layout(view_, &layout);
-    if (handler_ != nullptr) {
-      fl_keyboard_handler_notify_layout_changed(handler_);
-    }
-  }
-
- private:
-  FlMockViewDelegate* view_;
-  FlKeyboardHandler* handler_ = nullptr;
-  GPtrArray* redispatched_events_ = nullptr;
-  bool during_redispatch_ = false;
-
-  static gboolean _flushChannelMessagesCb(gpointer data) {
-    g_autoptr(GMainLoop) loop = reinterpret_cast<GMainLoop*>(data);
-    g_main_loop_quit(loop);
-    return FALSE;
-  }
-};
-
-// Make sure that the keyboard can be disposed without crashes when there are
-// unresolved pending events.
-TEST(FlKeyboardHandlerTest, DisposeWithUnresolvedPends) {
-  KeyboardTester tester;
-  std::vector<CallRecord> call_records;
-
-  // Record calls so that they aren't responded.
-  tester.recordEmbedderCallsTo(call_records);
-  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
-      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
-  fl_keyboard_handler_handle_event(tester.handler(), event1);
-
-  tester.respondToEmbedderCallsWith(true);
-  g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
-      0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
-  fl_keyboard_handler_handle_event(tester.handler(), event2);
-
-  tester.flushChannelMessages();
-
-  // Passes if the cleanup does not crash.
-}
-
-TEST(FlKeyboardHandlerTest, SingleDelegateWithAsyncResponds) {
-  KeyboardTester tester;
-  std::vector<CallRecord> call_records;
-  g_autoptr(GPtrArray) redispatched =
-      g_ptr_array_new_with_free_func(g_object_unref);
-
-  gboolean handler_handled = false;
-
-  /// Test 1: One event that is handled by the framework
-  tester.recordEmbedderCallsTo(call_records);
-  tester.recordRedispatchedEventsTo(redispatched);
-
-  // Dispatch a key event
-  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
-      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
-  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event1);
-  tester.flushChannelMessages();
-  EXPECT_EQ(handler_handled, true);
-  EXPECT_EQ(redispatched->len, 0u);
-  EXPECT_EQ(call_records.size(), 1u);
-  EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, kPhysicalKeyA,
-                   kLogicalKeyA, "a", false);
-
-  call_records[0].callback(true);
-  tester.flushChannelMessages();
-  EXPECT_EQ(redispatched->len, 0u);
-  EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler()));
-  call_records.clear();
-
-  /// Test 2: Two events that are unhandled by the framework
-  g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
-      0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
-  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event2);
-  tester.flushChannelMessages();
-  EXPECT_EQ(handler_handled, true);
-  EXPECT_EQ(redispatched->len, 0u);
-  EXPECT_EQ(call_records.size(), 1u);
-  EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeUp, kPhysicalKeyA,
-                   kLogicalKeyA, nullptr, false);
-
-  // Dispatch another key event
-  g_autoptr(FlKeyEvent) event3 = fl_key_event_new(
-      0, TRUE, kKeyCodeKeyB, GDK_KEY_b, static_cast<GdkModifierType>(0), 0);
-  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event3);
-  tester.flushChannelMessages();
-  EXPECT_EQ(handler_handled, true);
-  EXPECT_EQ(redispatched->len, 0u);
-  EXPECT_EQ(call_records.size(), 2u);
-  EXPECT_KEY_EVENT(call_records[1], kFlutterKeyEventTypeDown, kPhysicalKeyB,
-                   kLogicalKeyB, "b", false);
-
-  // Resolve the second event first to test out-of-order response
-  call_records[1].callback(false);
-  EXPECT_EQ(redispatched->len, 1u);
-  EXPECT_EQ(
-      fl_key_event_get_keyval(FL_KEY_EVENT(g_ptr_array_index(redispatched, 0))),
-      0x62u);
-  call_records[0].callback(false);
-  tester.flushChannelMessages();
-  EXPECT_EQ(redispatched->len, 2u);
-  EXPECT_EQ(
-      fl_key_event_get_keyval(FL_KEY_EVENT(g_ptr_array_index(redispatched, 1))),
-      0x61u);
-
-  EXPECT_FALSE(fl_keyboard_handler_is_state_clear(tester.handler()));
-  call_records.clear();
-
-  // Resolve redispatches
-  EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 2);
-  tester.flushChannelMessages();
-  EXPECT_EQ(call_records.size(), 0u);
-  EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler()));
-
-  /// Test 3: Dispatch the same event again to ensure that prevention from
-  /// redispatching only works once.
-  g_autoptr(FlKeyEvent) event4 = fl_key_event_new(
-      0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
-  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event4);
-  tester.flushChannelMessages();
-  EXPECT_EQ(handler_handled, true);
-  EXPECT_EQ(redispatched->len, 0u);
-  EXPECT_EQ(call_records.size(), 1u);
-
-  call_records[0].callback(true);
-  EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler()));
-}
-
-TEST(FlKeyboardHandlerTest, SingleDelegateWithSyncResponds) {
-  KeyboardTester tester;
-  gboolean handler_handled = false;
-  std::vector<CallRecord> call_records;
-  g_autoptr(GPtrArray) redispatched =
-      g_ptr_array_new_with_free_func(g_object_unref);
-
-  /// Test 1: One event that is handled by the framework
-  tester.respondToEmbedderCallsWithAndRecordsTo(true, call_records);
-  tester.recordRedispatchedEventsTo(redispatched);
-
-  // Dispatch a key event
-  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
-      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
-  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event1);
-  tester.flushChannelMessages();
-  EXPECT_EQ(handler_handled, true);
-  EXPECT_EQ(call_records.size(), 1u);
-  EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, kPhysicalKeyA,
-                   kLogicalKeyA, "a", false);
-  EXPECT_EQ(redispatched->len, 0u);
-  call_records.clear();
-
-  EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler()));
-  g_ptr_array_set_size(redispatched, 0);
-
-  /// Test 2: An event unhandled by the framework
-  tester.respondToEmbedderCallsWithAndRecordsTo(false, call_records);
-  g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
-      0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
-  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event2);
-  tester.flushChannelMessages();
-  EXPECT_EQ(handler_handled, true);
-  EXPECT_EQ(call_records.size(), 1u);
-  EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeUp, kPhysicalKeyA,
-                   kLogicalKeyA, nullptr, false);
-  EXPECT_EQ(redispatched->len, 1u);
-  call_records.clear();
-
-  EXPECT_FALSE(fl_keyboard_handler_is_state_clear(tester.handler()));
-
-  EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 1);
-  EXPECT_EQ(call_records.size(), 0u);
-
-  EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler()));
-}
-
-TEST(FlKeyboardHandlerTest, WithTwoAsyncDelegates) {
-  KeyboardTester tester;
-  std::vector<CallRecord> call_records;
-  g_autoptr(GPtrArray) redispatched =
-      g_ptr_array_new_with_free_func(g_object_unref);
-
-  gboolean handler_handled = false;
-
-  tester.recordEmbedderCallsTo(call_records);
-  tester.recordChannelCallsTo(call_records);
-  tester.recordRedispatchedEventsTo(redispatched);
-
-  /// Test 1: One delegate responds true, the other false
-
-  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
-      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
-  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event1);
-
-  EXPECT_EQ(handler_handled, true);
-  EXPECT_EQ(redispatched->len, 0u);
-  EXPECT_EQ(call_records.size(), 2u);
-
-  EXPECT_EQ(call_records[0].type, CallRecord::kKeyCallEmbedder);
-  EXPECT_EQ(call_records[1].type, CallRecord::kKeyCallChannel);
-
-  call_records[0].callback(true);
-  call_records[1].callback(false);
-  tester.flushChannelMessages();
-  EXPECT_EQ(redispatched->len, 0u);
-
-  EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler()));
-  call_records.clear();
-
-  /// Test 2: All delegates respond false
-  g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
-      0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
-  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event2);
-
-  EXPECT_EQ(handler_handled, true);
-  EXPECT_EQ(redispatched->len, 0u);
-  EXPECT_EQ(call_records.size(), 2u);
-
-  EXPECT_EQ(call_records[0].type, CallRecord::kKeyCallEmbedder);
-  EXPECT_EQ(call_records[1].type, CallRecord::kKeyCallChannel);
-
-  call_records[0].callback(false);
-  call_records[1].callback(false);
-
-  call_records.clear();
-
-  // Resolve redispatch
-  tester.flushChannelMessages();
-  EXPECT_EQ(redispatched->len, 1u);
-  EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 1);
-  EXPECT_EQ(call_records.size(), 0u);
-
-  EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler()));
-}
-
-TEST(FlKeyboardHandlerTest, TextInputHandlerReturnsFalse) {
-  KeyboardTester tester;
-  g_autoptr(GPtrArray) redispatched =
-      g_ptr_array_new_with_free_func(g_object_unref);
-  gboolean handler_handled = false;
-  tester.recordRedispatchedEventsTo(redispatched);
-  tester.respondToTextInputWith(false);
-
-  // Dispatch a key event.
-  g_autoptr(FlKeyEvent) event = fl_key_event_new(
-      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
-  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event);
-  tester.flushChannelMessages();
-  EXPECT_EQ(handler_handled, true);
-  // The event was redispatched because no one handles it.
-  EXPECT_EQ(redispatched->len, 1u);
-
-  // Resolve redispatched event.
-  EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 1);
-
-  EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler()));
-}
-
-TEST(FlKeyboardHandlerTest, TextInputHandlerReturnsTrue) {
-  KeyboardTester tester;
-  g_autoptr(GPtrArray) redispatched =
-      g_ptr_array_new_with_free_func(g_object_unref);
-  gboolean handler_handled = false;
-  tester.recordRedispatchedEventsTo(redispatched);
-  tester.respondToTextInputWith(true);
-
-  // Dispatch a key event.
-  g_autoptr(FlKeyEvent) event = fl_key_event_new(
-      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
-  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event);
-  tester.flushChannelMessages();
-  EXPECT_EQ(handler_handled, true);
-  // The event was not redispatched because handler handles it.
-  EXPECT_EQ(redispatched->len, 0u);
-
-  EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler()));
-}
-
-TEST(FlKeyboardHandlerTest, CorrectLogicalKeyForLayouts) {
-  KeyboardTester tester;
-
-  std::vector<CallRecord> call_records;
-  tester.recordEmbedderCallsTo(call_records);
-
-  auto sendTap = [&](guint8 keycode, guint keyval, guint8 group) {
-    g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
-        0, TRUE, keycode, keyval, static_cast<GdkModifierType>(0), group);
-    fl_keyboard_handler_handle_event(tester.handler(), event1);
-    g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
-        0, FALSE, keycode, keyval, static_cast<GdkModifierType>(0), group);
-    fl_keyboard_handler_handle_event(tester.handler(), event2);
-  };
-
-  /* US keyboard layout */
-
-  sendTap(kKeyCodeKeyA, GDK_KEY_a, 0);  // KeyA
-  VERIFY_DOWN(kLogicalKeyA, "a");
-
-  sendTap(kKeyCodeKeyA, GDK_KEY_A, 0);  // Shift-KeyA
-  VERIFY_DOWN(kLogicalKeyA, "A");
-
-  sendTap(kKeyCodeDigit1, GDK_KEY_1, 0);  // Digit1
-  VERIFY_DOWN(kLogicalDigit1, "1");
-
-  sendTap(kKeyCodeDigit1, GDK_KEY_exclam, 0);  // Shift-Digit1
-  VERIFY_DOWN(kLogicalDigit1, "!");
-
-  sendTap(kKeyCodeMinus, GDK_KEY_minus, 0);  // Minus
-  VERIFY_DOWN(kLogicalMinus, "-");
-
-  sendTap(kKeyCodeMinus, GDK_KEY_underscore, 0);  // Shift-Minus
-  VERIFY_DOWN(kLogicalUnderscore, "_");
-
-  /* French keyboard layout, group 3, which is when the input method is showing
-   * "Fr" */
-
-  tester.setLayout(kLayoutFrench);
-
-  sendTap(kKeyCodeKeyA, GDK_KEY_q, 3);  // KeyA
-  VERIFY_DOWN(kLogicalKeyQ, "q");
-
-  sendTap(kKeyCodeKeyA, GDK_KEY_Q, 3);  // Shift-KeyA
-  VERIFY_DOWN(kLogicalKeyQ, "Q");
-
-  sendTap(kKeyCodeSemicolon, GDK_KEY_m, 3);  // ; but prints M
-  VERIFY_DOWN(kLogicalKeyM, "m");
-
-  sendTap(kKeyCodeKeyM, GDK_KEY_comma, 3);  // M but prints ,
-  VERIFY_DOWN(kLogicalComma, ",");
-
-  sendTap(kKeyCodeDigit1, GDK_KEY_ampersand, 3);  // Digit1
-  VERIFY_DOWN(kLogicalDigit1, "&");
-
-  sendTap(kKeyCodeDigit1, GDK_KEY_1, 3);  // Shift-Digit1
-  VERIFY_DOWN(kLogicalDigit1, "1");
-
-  sendTap(kKeyCodeMinus, GDK_KEY_parenright, 3);  // Minus
-  VERIFY_DOWN(kLogicalParenthesisRight, ")");
-
-  sendTap(kKeyCodeMinus, GDK_KEY_degree, 3);  // Shift-Minus
-  VERIFY_DOWN(static_cast<uint32_t>(L'°'), "°");
-
-  /* French keyboard layout, group 0, which is pressing the "extra key for
-   * triggering input method" key once after switching to French IME. */
-
-  sendTap(kKeyCodeKeyA, GDK_KEY_a, 0);  // KeyA
-  VERIFY_DOWN(kLogicalKeyA, "a");
-
-  sendTap(kKeyCodeDigit1, GDK_KEY_1, 0);  // Digit1
-  VERIFY_DOWN(kLogicalDigit1, "1");
-
-  /* Russian keyboard layout, group 2 */
-  tester.setLayout(kLayoutRussian);
-
-  sendTap(kKeyCodeKeyA, GDK_KEY_Cyrillic_ef, 2);  // KeyA
-  VERIFY_DOWN(kLogicalKeyA, "ф");
-
-  sendTap(kKeyCodeDigit1, GDK_KEY_1, 2);  // Shift-Digit1
-  VERIFY_DOWN(kLogicalDigit1, "1");
-
-  sendTap(kKeyCodeKeyLeftBracket, GDK_KEY_Cyrillic_ha, 2);
-  VERIFY_DOWN(kLogicalBracketLeft, "х");
-
-  /* Russian keyboard layout, group 0 */
-  sendTap(kKeyCodeKeyA, GDK_KEY_a, 0);  // KeyA
-  VERIFY_DOWN(kLogicalKeyA, "a");
-
-  sendTap(kKeyCodeKeyLeftBracket, GDK_KEY_bracketleft, 0);
-  VERIFY_DOWN(kLogicalBracketLeft, "[");
-}
-
-TEST(FlKeyboardHandlerTest, SynthesizeModifiersIfNeeded) {
-  KeyboardTester tester;
-  std::vector<CallRecord> call_records;
-  tester.recordEmbedderCallsTo(call_records);
-
-  auto verifyModifierIsSynthesized = [&](GdkModifierType mask,
-                                         uint64_t physical, uint64_t logical) {
-    // Modifier is pressed.
-    guint state = mask;
-    fl_keyboard_handler_sync_modifier_if_needed(tester.handler(), state, 1000);
-    EXPECT_EQ(call_records.size(), 1u);
-    EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, physical,
-                     logical, NULL, true);
-    // Modifier is released.
-    state = state ^ mask;
-    fl_keyboard_handler_sync_modifier_if_needed(tester.handler(), state, 1001);
-    EXPECT_EQ(call_records.size(), 2u);
-    EXPECT_KEY_EVENT(call_records[1], kFlutterKeyEventTypeUp, physical, logical,
-                     NULL, true);
-    call_records.clear();
-  };
-
-  // No modifiers pressed.
-  guint state = 0;
-  fl_keyboard_handler_sync_modifier_if_needed(tester.handler(), state, 1000);
-  EXPECT_EQ(call_records.size(), 0u);
-  call_records.clear();
-
-  // Press and release each modifier once.
-  verifyModifierIsSynthesized(GDK_CONTROL_MASK, kPhysicalControlLeft,
-                              kLogicalControlLeft);
-  verifyModifierIsSynthesized(GDK_META_MASK, kPhysicalMetaLeft,
-                              kLogicalMetaLeft);
-  verifyModifierIsSynthesized(GDK_MOD1_MASK, kPhysicalAltLeft, kLogicalAltLeft);
-  verifyModifierIsSynthesized(GDK_SHIFT_MASK, kPhysicalShiftLeft,
-                              kLogicalShiftLeft);
-}
-
-TEST(FlKeyboardHandlerTest, GetPressedState) {
-  KeyboardTester tester;
-  tester.respondToTextInputWith(true);
-
-  // Dispatch a key event.
-  g_autoptr(FlKeyEvent) event = fl_key_event_new(
-      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
-  fl_keyboard_handler_handle_event(tester.handler(), event);
-
-  GHashTable* pressedState =
-      fl_keyboard_handler_get_pressed_state(tester.handler());
-  EXPECT_EQ(g_hash_table_size(pressedState), 1u);
-
-  gpointer physical_key =
-      g_hash_table_lookup(pressedState, uint64_to_gpointer(kPhysicalKeyA));
-  EXPECT_EQ(gpointer_to_uint64(physical_key), kLogicalKeyA);
-}
-
 TEST(FlKeyboardHandlerTest, KeyboardChannelGetPressedState) {
   ::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;
 
   g_autoptr(FlKeyboardHandler) handler = fl_keyboard_handler_new(
-      messenger, FL_KEYBOARD_VIEW_DELEGATE(fl_mock_view_delegate_new()));
+      messenger,
+      FL_KEYBOARD_VIEW_DELEGATE(fl_mock_keyboard_handler_delegate_new()));
   EXPECT_NE(handler, nullptr);
 
   g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
@@ -1034,193 +107,3 @@
 
   messenger.ReceiveMessage(kKeyboardChannelName, message);
 }
-
-// The following layout data is generated using DEBUG_PRINT_LAYOUT.
-
-const MockGroupLayoutData kLayoutUs0{{
-    // +0x0  Shift   +0x1    Shift   +0x2    Shift   +0x3    Shift
-    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x00
-    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x04
-    0xffff, 0x0031, 0xffff, 0x0031, 0x0031, 0x0021, 0x0032, 0x0040,  // 0x08
-    0x0033, 0x0023, 0x0034, 0x0024, 0x0035, 0x0025, 0x0036, 0x005e,  // 0x0c
-    0x0037, 0x0026, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029,  // 0x10
-    0x002d, 0x005f, 0x003d, 0x002b, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x14
-    0x0071, 0x0051, 0x0077, 0x0057, 0x0065, 0x0045, 0x0072, 0x0052,  // 0x18
-    0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049,  // 0x1c
-    0x006f, 0x004f, 0x0070, 0x0050, 0x005b, 0x007b, 0x005d, 0x007d,  // 0x20
-    0xffff, 0xffff, 0xffff, 0x0061, 0x0061, 0x0041, 0x0073, 0x0053,  // 0x24
-    0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048,  // 0x28
-    0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x003b, 0x003a,  // 0x2c
-    0x0027, 0x0022, 0x0060, 0x007e, 0xffff, 0x005c, 0x005c, 0x007c,  // 0x30
-    0x007a, 0x005a, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056,  // 0x34
-    0x0062, 0x0042, 0x006e, 0x004e, 0x006d, 0x004d, 0x002c, 0x003c,  // 0x38
-    0x002e, 0x003e, 0x002f, 0x003f, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x3c
-    0xffff, 0xffff, 0x0020, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x40
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x44
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x48
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x4c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x50
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x54
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x58
-    0xffff, 0xffff, 0x003c, 0x003e, 0x003c, 0x003e, 0xffff, 0xffff,  // 0x5c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x60
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x64
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x68
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x6c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x70
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x74
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x78
-    0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff, 0xffff, 0xffff,  // 0x7c
-}};
-
-const MockGroupLayoutData kLayoutRussian0{
-    // +0x0  Shift   +0x1    Shift   +0x2    Shift   +0x3    Shift
-    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x00
-    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x04
-    0x0000, 0xffff, 0xffff, 0x0031, 0x0031, 0x0021, 0x0032, 0x0040,  // 0x08
-    0x0033, 0x0023, 0x0034, 0x0024, 0x0035, 0x0025, 0x0036, 0x005e,  // 0x0c
-    0x0037, 0x0026, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029,  // 0x10
-    0x002d, 0x005f, 0x003d, 0x002b, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x14
-    0x0071, 0x0051, 0x0077, 0x0057, 0x0065, 0x0045, 0x0072, 0x0052,  // 0x18
-    0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049,  // 0x1c
-    0x006f, 0x004f, 0x0070, 0x0050, 0x005b, 0x007b, 0x005d, 0x007d,  // 0x20
-    0xffff, 0xffff, 0xffff, 0x0061, 0x0061, 0x0041, 0x0073, 0x0053,  // 0x24
-    0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048,  // 0x28
-    0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x003b, 0x003a,  // 0x2c
-    0x0027, 0x0022, 0x0060, 0x007e, 0xffff, 0x005c, 0x005c, 0x007c,  // 0x30
-    0x007a, 0x005a, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056,  // 0x34
-    0x0062, 0x0042, 0x006e, 0x004e, 0x006d, 0x004d, 0x002c, 0x003c,  // 0x38
-    0x002e, 0x003e, 0x002f, 0x003f, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x3c
-    0xffff, 0xffff, 0x0020, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x40
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x44
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x48
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x4c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x50
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x54
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x58
-    0xffff, 0xffff, 0x0000, 0xffff, 0x003c, 0x003e, 0xffff, 0xffff,  // 0x5c
-    0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x60
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff,  // 0x64
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x68
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x6c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x70
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x74
-    0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x78
-    0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff, 0xffff, 0xffff,  // 0x7c
-};
-
-const MockGroupLayoutData kLayoutRussian2{{
-    // +0x0  Shift   +0x1    Shift   +0x2    Shift   +0x3    Shift
-    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x00
-    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x04
-    0xffff, 0x0031, 0x0021, 0x0000, 0x0031, 0x0021, 0x0032, 0x0022,  // 0x08
-    0x0033, 0x06b0, 0x0034, 0x003b, 0x0035, 0x0025, 0x0036, 0x003a,  // 0x0c
-    0x0037, 0x003f, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029,  // 0x10
-    0x002d, 0x005f, 0x003d, 0x002b, 0x0071, 0x0051, 0x0000, 0x0000,  // 0x14
-    0x06ca, 0x06ea, 0x06c3, 0x06e3, 0x06d5, 0x06f5, 0x06cb, 0x06eb,  // 0x18
-    0x06c5, 0x06e5, 0x06ce, 0x06ee, 0x06c7, 0x06e7, 0x06db, 0x06fb,  // 0x1c
-    0x06dd, 0x06fd, 0x06da, 0x06fa, 0x06c8, 0x06e8, 0x06df, 0x06ff,  // 0x20
-    0x0061, 0x0041, 0x0041, 0x0000, 0x06c6, 0x06e6, 0x06d9, 0x06f9,  // 0x24
-    0x06d7, 0x06f7, 0x06c1, 0x06e1, 0x06d0, 0x06f0, 0x06d2, 0x06f2,  // 0x28
-    0x06cf, 0x06ef, 0x06cc, 0x06ec, 0x06c4, 0x06e4, 0x06d6, 0x06f6,  // 0x2c
-    0x06dc, 0x06fc, 0x06a3, 0x06b3, 0x007c, 0x0000, 0x005c, 0x002f,  // 0x30
-    0x06d1, 0x06f1, 0x06de, 0x06fe, 0x06d3, 0x06f3, 0x06cd, 0x06ed,  // 0x34
-    0x06c9, 0x06e9, 0x06d4, 0x06f4, 0x06d8, 0x06f8, 0x06c2, 0x06e2,  // 0x38
-    0x06c0, 0x06e0, 0x002e, 0x002c, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x3c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x40
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x44
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x48
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x4c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x50
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x54
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x58
-    0xffff, 0xffff, 0x003c, 0x003e, 0x002f, 0x007c, 0xffff, 0xffff,  // 0x5c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x60
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x64
-    0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0x0000,  // 0x68
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x6c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x70
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x74
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x00b1,  // 0x78
-    0x00b1, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x7c
-}};
-
-const MockGroupLayoutData kLayoutFrench0 = {
-    // +0x0  Shift   +0x1    Shift   +0x2    Shift   +0x3    Shift
-    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x00
-    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x04
-    0x0000, 0xffff, 0xffff, 0x0031, 0x0031, 0x0021, 0x0032, 0x0040,  // 0x08
-    0x0033, 0x0023, 0x0034, 0x0024, 0x0035, 0x0025, 0x0036, 0x005e,  // 0x0c
-    0x0037, 0x0026, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029,  // 0x10
-    0x002d, 0x005f, 0x003d, 0x002b, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x14
-    0x0071, 0x0051, 0x0077, 0x0057, 0x0065, 0x0045, 0x0072, 0x0052,  // 0x18
-    0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049,  // 0x1c
-    0x006f, 0x004f, 0x0070, 0x0050, 0x005b, 0x007b, 0x005d, 0x007d,  // 0x20
-    0xffff, 0xffff, 0xffff, 0x0061, 0x0061, 0x0041, 0x0073, 0x0053,  // 0x24
-    0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048,  // 0x28
-    0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x003b, 0x003a,  // 0x2c
-    0x0027, 0x0022, 0x0060, 0x007e, 0xffff, 0x005c, 0x005c, 0x007c,  // 0x30
-    0x007a, 0x005a, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056,  // 0x34
-    0x0062, 0x0042, 0x006e, 0x004e, 0x006d, 0x004d, 0x002c, 0x003c,  // 0x38
-    0x002e, 0x003e, 0x002f, 0x003f, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x3c
-    0xffff, 0xffff, 0x0020, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x40
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x44
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x48
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x4c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x50
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x54
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x58
-    0xffff, 0xffff, 0x0000, 0xffff, 0x003c, 0x003e, 0xffff, 0xffff,  // 0x5c
-    0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x60
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff,  // 0x64
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x68
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x6c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x70
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x74
-    0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x78
-    0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff, 0xffff, 0xffff,  // 0x7c
-};
-
-const MockGroupLayoutData kLayoutFrench3 = {
-    // +0x0  Shift   +0x1    Shift   +0x2    Shift   +0x3    Shift
-    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x00
-    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x04
-    0x0000, 0xffff, 0x0000, 0x0000, 0x0026, 0x0031, 0x00e9, 0x0032,  // 0x08
-    0x0022, 0x0033, 0x0027, 0x0034, 0x0028, 0x0035, 0x002d, 0x0036,  // 0x0c
-    0x00e8, 0x0037, 0x005f, 0x0038, 0x00e7, 0x0039, 0x00e0, 0x0030,  // 0x10
-    0x0029, 0x00b0, 0x003d, 0x002b, 0x0000, 0x0000, 0x0061, 0x0041,  // 0x14
-    0x0061, 0x0041, 0x007a, 0x005a, 0x0065, 0x0045, 0x0072, 0x0052,  // 0x18
-    0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049,  // 0x1c
-    0x006f, 0x004f, 0x0070, 0x0050, 0xffff, 0xffff, 0x0024, 0x00a3,  // 0x20
-    0x0041, 0x0000, 0x0000, 0x0000, 0x0071, 0x0051, 0x0073, 0x0053,  // 0x24
-    0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048,  // 0x28
-    0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x006d, 0x004d,  // 0x2c
-    0x00f9, 0x0025, 0x00b2, 0x007e, 0x0000, 0x0000, 0x002a, 0x00b5,  // 0x30
-    0x0077, 0x0057, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056,  // 0x34
-    0x0062, 0x0042, 0x006e, 0x004e, 0x002c, 0x003f, 0x003b, 0x002e,  // 0x38
-    0x003a, 0x002f, 0x0021, 0x00a7, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x3c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x40
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x44
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x48
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x4c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x50
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x54
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x58
-    0xffff, 0x003c, 0x0000, 0xffff, 0x003c, 0x003e, 0xffff, 0xffff,  // 0x5c
-    0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x60
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff,  // 0x64
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x68
-    0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x6c
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x70
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x74
-    0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff,  // 0x78
-    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x7c
-};
-
-const MockLayoutData kLayoutUs{&kLayoutUs0};
-const MockLayoutData kLayoutRussian{&kLayoutRussian0, nullptr,
-                                    &kLayoutRussian2};
-const MockLayoutData kLayoutFrench{&kLayoutFrench0, nullptr, nullptr,
-                                   &kLayoutFrench3};
-
-}  // namespace
diff --git a/shell/platform/linux/fl_keyboard_manager.cc b/shell/platform/linux/fl_keyboard_manager.cc
new file mode 100644
index 0000000..3c0b8f1
--- /dev/null
+++ b/shell/platform/linux/fl_keyboard_manager.cc
@@ -0,0 +1,510 @@
+// 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.
+
+#include "flutter/shell/platform/linux/fl_keyboard_manager.h"
+
+#include <array>
+#include <cinttypes>
+#include <memory>
+#include <string>
+
+#include "flutter/shell/platform/linux/fl_key_channel_responder.h"
+#include "flutter/shell/platform/linux/fl_key_embedder_responder.h"
+#include "flutter/shell/platform/linux/fl_keyboard_layout.h"
+#include "flutter/shell/platform/linux/fl_keyboard_pending_event.h"
+#include "flutter/shell/platform/linux/key_mapping.h"
+
+// Turn on this flag to print complete layout data when switching IMEs. The data
+// is used in unit tests.
+#define DEBUG_PRINT_LAYOUT
+
+/* Declarations of private classes */
+
+G_DECLARE_FINAL_TYPE(FlKeyboardManagerUserData,
+                     fl_keyboard_manager_user_data,
+                     FL,
+                     KEYBOARD_MANAGER_USER_DATA,
+                     GObject);
+
+/* End declarations */
+
+namespace {
+
+static bool is_eascii(uint16_t character) {
+  return character < 256;
+}
+
+#ifdef DEBUG_PRINT_LAYOUT
+// Prints layout entries that will be parsed by `MockLayoutData`.
+void debug_format_layout_data(std::string& debug_layout_data,
+                              uint16_t keycode,
+                              uint16_t clue1,
+                              uint16_t clue2) {
+  if (keycode % 4 == 0) {
+    debug_layout_data.append("    ");
+  }
+
+  constexpr int kBufferSize = 30;
+  char buffer[kBufferSize];
+  buffer[0] = 0;
+  buffer[kBufferSize - 1] = 0;
+
+  snprintf(buffer, kBufferSize, "0x%04x, 0x%04x, ", clue1, clue2);
+  debug_layout_data.append(buffer);
+
+  if (keycode % 4 == 3) {
+    snprintf(buffer, kBufferSize, " // 0x%02x", keycode);
+    debug_layout_data.append(buffer);
+  }
+}
+#endif
+
+}  // namespace
+
+/* Define FlKeyboardManagerUserData */
+
+/**
+ * FlKeyboardManagerUserData:
+ * The user_data used when #FlKeyboardManager sends event to
+ * responders.
+ */
+
+struct _FlKeyboardManagerUserData {
+  GObject parent_instance;
+
+  // The owner manager.
+  GWeakRef manager;
+  uint64_t sequence_id;
+};
+
+G_DEFINE_TYPE(FlKeyboardManagerUserData,
+              fl_keyboard_manager_user_data,
+              G_TYPE_OBJECT)
+
+static void fl_keyboard_manager_user_data_dispose(GObject* object) {
+  g_return_if_fail(FL_IS_KEYBOARD_MANAGER_USER_DATA(object));
+  FlKeyboardManagerUserData* self = FL_KEYBOARD_MANAGER_USER_DATA(object);
+
+  g_weak_ref_clear(&self->manager);
+
+  G_OBJECT_CLASS(fl_keyboard_manager_user_data_parent_class)->dispose(object);
+}
+
+static void fl_keyboard_manager_user_data_class_init(
+    FlKeyboardManagerUserDataClass* klass) {
+  G_OBJECT_CLASS(klass)->dispose = fl_keyboard_manager_user_data_dispose;
+}
+
+static void fl_keyboard_manager_user_data_init(
+    FlKeyboardManagerUserData* self) {}
+
+// Creates a new FlKeyboardManagerUserData private class with all information.
+static FlKeyboardManagerUserData* fl_keyboard_manager_user_data_new(
+    FlKeyboardManager* manager,
+    uint64_t sequence_id) {
+  FlKeyboardManagerUserData* self = FL_KEYBOARD_MANAGER_USER_DATA(
+      g_object_new(fl_keyboard_manager_user_data_get_type(), nullptr));
+
+  g_weak_ref_init(&self->manager, manager);
+  self->sequence_id = sequence_id;
+  return self;
+}
+
+/* Define FlKeyboardManager */
+
+struct _FlKeyboardManager {
+  GObject parent_instance;
+
+  GWeakRef view_delegate;
+
+  // An array of #FlKeyResponder. Elements are added with
+  // #fl_keyboard_manager_add_responder immediately after initialization and are
+  // automatically released on dispose.
+  GPtrArray* responder_list;
+
+  // An array of #FlKeyboardPendingEvent.
+  //
+  // Its elements are *not* unreferenced when removed. When FlKeyboardManager is
+  // disposed, this array will be set with a free_func so that the elements are
+  // unreferenced when removed.
+  GPtrArray* pending_responds;
+
+  // An array of #FlKeyboardPendingEvent.
+  //
+  // Its elements are unreferenced when removed.
+  GPtrArray* pending_redispatches;
+
+  // The last sequence ID used. Increased by 1 by every use.
+  uint64_t last_sequence_id;
+
+  // Record the derived layout.
+  //
+  // It is cleared when the platform reports a layout switch. Each entry,
+  // which corresponds to a group, is only initialized on the arrival of the
+  // first event for that group that has a goal keycode.
+  FlKeyboardLayout* derived_layout;
+
+  // A static map from keycodes to all layout goals.
+  //
+  // It is set up when the manager is initialized and is not changed ever after.
+  std::unique_ptr<std::map<uint16_t, const LayoutGoal*>> keycode_to_goals;
+
+  // A static map from logical keys to all mandatory layout goals.
+  //
+  // It is set up when the manager is initialized and is not changed ever after.
+  std::unique_ptr<std::map<uint64_t, const LayoutGoal*>>
+      logical_to_mandatory_goals;
+};
+
+G_DEFINE_TYPE(FlKeyboardManager, fl_keyboard_manager, G_TYPE_OBJECT);
+
+// This is an exact copy of g_ptr_array_find_with_equal_func.  Somehow CI
+// reports that can not find symbol g_ptr_array_find_with_equal_func, despite
+// the fact that it runs well locally.
+static gboolean g_ptr_array_find_with_equal_func1(GPtrArray* haystack,
+                                                  gconstpointer needle,
+                                                  GEqualFunc equal_func,
+                                                  guint* index_) {
+  guint i;
+  g_return_val_if_fail(haystack != NULL, FALSE);
+  if (equal_func == NULL) {
+    equal_func = g_direct_equal;
+  }
+  for (i = 0; i < haystack->len; i++) {
+    if (equal_func(g_ptr_array_index(haystack, i), needle)) {
+      if (index_ != NULL) {
+        *index_ = i;
+      }
+      return TRUE;
+    }
+  }
+
+  return FALSE;
+}
+
+// Compare a #FlKeyboardPendingEvent with the given sequence_id.
+static gboolean compare_pending_by_sequence_id(gconstpointer a,
+                                               gconstpointer b) {
+  FlKeyboardPendingEvent* pending =
+      FL_KEYBOARD_PENDING_EVENT(const_cast<gpointer>(a));
+  uint64_t sequence_id = *reinterpret_cast<const uint64_t*>(b);
+  return fl_keyboard_pending_event_get_sequence_id(pending) == sequence_id;
+}
+
+// Compare a #FlKeyboardPendingEvent with the given hash.
+static gboolean compare_pending_by_hash(gconstpointer a, gconstpointer b) {
+  FlKeyboardPendingEvent* pending =
+      FL_KEYBOARD_PENDING_EVENT(const_cast<gpointer>(a));
+  uint64_t hash = *reinterpret_cast<const uint64_t*>(b);
+  return fl_keyboard_pending_event_get_hash(pending) == hash;
+}
+
+// Try to remove a pending event from `pending_redispatches` with the target
+// hash.
+//
+// Returns true if the event is found and removed.
+static bool fl_keyboard_manager_remove_redispatched(FlKeyboardManager* self,
+                                                    uint64_t hash) {
+  guint result_index;
+  gboolean found = g_ptr_array_find_with_equal_func1(
+      self->pending_redispatches, static_cast<const uint64_t*>(&hash),
+      compare_pending_by_hash, &result_index);
+  if (found) {
+    // The removed object is freed due to `pending_redispatches`'s free_func.
+    g_ptr_array_remove_index_fast(self->pending_redispatches, result_index);
+    return TRUE;
+  } else {
+    return FALSE;
+  }
+}
+
+// The callback used by a responder after the event was dispatched.
+static void responder_handle_event_callback(bool handled,
+                                            gpointer user_data_ptr) {
+  g_return_if_fail(FL_IS_KEYBOARD_MANAGER_USER_DATA(user_data_ptr));
+  FlKeyboardManagerUserData* user_data =
+      FL_KEYBOARD_MANAGER_USER_DATA(user_data_ptr);
+
+  g_autoptr(FlKeyboardManager) self =
+      FL_KEYBOARD_MANAGER(g_weak_ref_get(&user_data->manager));
+  if (self == nullptr) {
+    return;
+  }
+
+  g_autoptr(FlKeyboardViewDelegate) view_delegate =
+      FL_KEYBOARD_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate));
+  if (view_delegate == nullptr) {
+    return;
+  }
+
+  guint result_index = -1;
+  gboolean found = g_ptr_array_find_with_equal_func1(
+      self->pending_responds, &user_data->sequence_id,
+      compare_pending_by_sequence_id, &result_index);
+  g_return_if_fail(found);
+  FlKeyboardPendingEvent* pending = FL_KEYBOARD_PENDING_EVENT(
+      g_ptr_array_index(self->pending_responds, result_index));
+  g_return_if_fail(pending != nullptr);
+  fl_keyboard_pending_event_mark_replied(pending, handled);
+  // All responders have replied.
+  if (fl_keyboard_pending_event_is_complete(pending)) {
+    g_object_unref(user_data_ptr);
+    gpointer removed =
+        g_ptr_array_remove_index_fast(self->pending_responds, result_index);
+    g_return_if_fail(removed == pending);
+    bool should_redispatch =
+        !fl_keyboard_pending_event_get_any_handled(pending) &&
+        !fl_keyboard_view_delegate_text_filter_key_press(
+            view_delegate, fl_keyboard_pending_event_get_event(pending));
+    if (should_redispatch) {
+      g_ptr_array_add(self->pending_redispatches, pending);
+      fl_keyboard_view_delegate_redispatch_event(
+          view_delegate,
+          FL_KEY_EVENT(fl_keyboard_pending_event_get_event(pending)));
+    } else {
+      g_object_unref(pending);
+    }
+  }
+}
+
+static uint16_t convert_key_to_char(FlKeyboardViewDelegate* view_delegate,
+                                    guint keycode,
+                                    gint group,
+                                    gint level) {
+  GdkKeymapKey key = {keycode, group, level};
+  constexpr int kBmpMax = 0xD7FF;
+  guint origin = fl_keyboard_view_delegate_lookup_key(view_delegate, &key);
+  return origin < kBmpMax ? origin : 0xFFFF;
+}
+
+// Make sure that Flutter has derived the layout for the group of the event,
+// if the event contains a goal keycode.
+static void guarantee_layout(FlKeyboardManager* self, FlKeyEvent* event) {
+  g_autoptr(FlKeyboardViewDelegate) view_delegate =
+      FL_KEYBOARD_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate));
+  if (view_delegate == nullptr) {
+    return;
+  }
+
+  guint8 group = fl_key_event_get_group(event);
+  if (fl_keyboard_layout_has_group(self->derived_layout, group)) {
+    return;
+  }
+  if (self->keycode_to_goals->find(fl_key_event_get_keycode(event)) ==
+      self->keycode_to_goals->end()) {
+    return;
+  }
+
+  // Clone all mandatory goals. Each goal is removed from this cloned map when
+  // fulfilled, and the remaining ones will be assigned to a default position.
+  std::map<uint64_t, const LayoutGoal*> remaining_mandatory_goals =
+      *self->logical_to_mandatory_goals;
+
+#ifdef DEBUG_PRINT_LAYOUT
+  std::string debug_layout_data;
+  for (uint16_t keycode = 0; keycode < 128; keycode += 1) {
+    std::vector<uint16_t> this_key_clues = {
+        convert_key_to_char(view_delegate, keycode, group, 0),
+        convert_key_to_char(view_delegate, keycode, group, 1),  // Shift
+    };
+    debug_format_layout_data(debug_layout_data, keycode, this_key_clues[0],
+                             this_key_clues[1]);
+  }
+#endif
+
+  // It's important to only traverse layout goals instead of all keycodes.
+  // Some key codes outside of the standard keyboard also gives alpha-numeric
+  // letters, and will therefore take over mandatory goals from standard
+  // keyboard keys if they come first. Example: French keyboard digit 1.
+  for (const LayoutGoal& keycode_goal : layout_goals) {
+    uint16_t keycode = keycode_goal.keycode;
+    std::vector<uint16_t> this_key_clues = {
+        convert_key_to_char(view_delegate, keycode, group, 0),
+        convert_key_to_char(view_delegate, keycode, group, 1),  // Shift
+    };
+
+    // The logical key should be the first available clue from below:
+    //
+    //  - Mandatory goal, if it matches any clue. This ensures that all alnum
+    //    keys can be found somewhere.
+    //  - US layout, if neither clue of the key is EASCII. This ensures that
+    //    there are no non-latin logical keys.
+    //  - A value derived on the fly from keycode & keyval.
+    for (uint16_t clue : this_key_clues) {
+      auto matching_goal = remaining_mandatory_goals.find(clue);
+      if (matching_goal != remaining_mandatory_goals.end()) {
+        // Found a key that produces a mandatory char. Use it.
+        g_return_if_fail(fl_keyboard_layout_get_logical_key(
+                             self->derived_layout, group, keycode) == 0);
+        fl_keyboard_layout_set_logical_key(self->derived_layout, group, keycode,
+                                           clue);
+        remaining_mandatory_goals.erase(matching_goal);
+        break;
+      }
+    }
+    bool has_any_eascii =
+        is_eascii(this_key_clues[0]) || is_eascii(this_key_clues[1]);
+    // See if any produced char meets the requirement as a logical key.
+    if (fl_keyboard_layout_get_logical_key(self->derived_layout, group,
+                                           keycode) == 0 &&
+        !has_any_eascii) {
+      auto found_us_layout = self->keycode_to_goals->find(keycode);
+      if (found_us_layout != self->keycode_to_goals->end()) {
+        fl_keyboard_layout_set_logical_key(
+            self->derived_layout, group, keycode,
+            found_us_layout->second->logical_key);
+      }
+    }
+  }
+
+  // Ensure all mandatory goals are assigned.
+  for (const auto mandatory_goal_iter : remaining_mandatory_goals) {
+    const LayoutGoal* goal = mandatory_goal_iter.second;
+    fl_keyboard_layout_set_logical_key(self->derived_layout, group,
+                                       goal->keycode, goal->logical_key);
+  }
+}
+
+static void fl_keyboard_manager_dispose(GObject* object) {
+  FlKeyboardManager* self = FL_KEYBOARD_MANAGER(object);
+
+  g_weak_ref_clear(&self->view_delegate);
+
+  self->keycode_to_goals.reset();
+  self->logical_to_mandatory_goals.reset();
+
+  g_ptr_array_free(self->responder_list, TRUE);
+  g_ptr_array_set_free_func(self->pending_responds, g_object_unref);
+  g_ptr_array_free(self->pending_responds, TRUE);
+  g_ptr_array_free(self->pending_redispatches, TRUE);
+  g_clear_object(&self->derived_layout);
+
+  G_OBJECT_CLASS(fl_keyboard_manager_parent_class)->dispose(object);
+}
+
+static void fl_keyboard_manager_class_init(FlKeyboardManagerClass* klass) {
+  G_OBJECT_CLASS(klass)->dispose = fl_keyboard_manager_dispose;
+}
+
+static void fl_keyboard_manager_init(FlKeyboardManager* self) {
+  self->derived_layout = fl_keyboard_layout_new();
+
+  self->keycode_to_goals =
+      std::make_unique<std::map<uint16_t, const LayoutGoal*>>();
+  self->logical_to_mandatory_goals =
+      std::make_unique<std::map<uint64_t, const LayoutGoal*>>();
+  for (const LayoutGoal& goal : layout_goals) {
+    (*self->keycode_to_goals)[goal.keycode] = &goal;
+    if (goal.mandatory) {
+      (*self->logical_to_mandatory_goals)[goal.logical_key] = &goal;
+    }
+  }
+
+  self->responder_list = g_ptr_array_new_with_free_func(g_object_unref);
+
+  self->pending_responds = g_ptr_array_new();
+  self->pending_redispatches = g_ptr_array_new_with_free_func(g_object_unref);
+
+  self->last_sequence_id = 1;
+}
+
+FlKeyboardManager* fl_keyboard_manager_new(
+    FlBinaryMessenger* messenger,
+    FlKeyboardViewDelegate* view_delegate) {
+  g_return_val_if_fail(FL_IS_KEYBOARD_VIEW_DELEGATE(view_delegate), nullptr);
+
+  FlKeyboardManager* self = FL_KEYBOARD_MANAGER(
+      g_object_new(fl_keyboard_manager_get_type(), nullptr));
+
+  g_weak_ref_init(&self->view_delegate, view_delegate);
+
+  // The embedder responder must be added before the channel responder.
+  g_ptr_array_add(
+      self->responder_list,
+      FL_KEY_RESPONDER(fl_key_embedder_responder_new(
+          [](const FlutterKeyEvent* event, FlutterKeyEventCallback callback,
+             void* callback_user_data, void* send_key_event_user_data) {
+            FlKeyboardManager* self =
+                FL_KEYBOARD_MANAGER(send_key_event_user_data);
+            g_autoptr(FlKeyboardViewDelegate) view_delegate =
+                FL_KEYBOARD_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate));
+            if (view_delegate == nullptr) {
+              return;
+            }
+            fl_keyboard_view_delegate_send_key_event(
+                view_delegate, event, callback, callback_user_data);
+          },
+          self)));
+  g_ptr_array_add(self->responder_list,
+                  FL_KEY_RESPONDER(fl_key_channel_responder_new(messenger)));
+
+  return self;
+}
+
+gboolean fl_keyboard_manager_handle_event(FlKeyboardManager* self,
+                                          FlKeyEvent* event) {
+  g_return_val_if_fail(FL_IS_KEYBOARD_MANAGER(self), FALSE);
+  g_return_val_if_fail(event != nullptr, FALSE);
+
+  guarantee_layout(self, event);
+
+  uint64_t incoming_hash = fl_key_event_hash(event);
+  if (fl_keyboard_manager_remove_redispatched(self, incoming_hash)) {
+    return FALSE;
+  }
+
+  FlKeyboardPendingEvent* pending = fl_keyboard_pending_event_new(
+      event, ++self->last_sequence_id, self->responder_list->len);
+
+  g_ptr_array_add(self->pending_responds, pending);
+  FlKeyboardManagerUserData* user_data = fl_keyboard_manager_user_data_new(
+      self, fl_keyboard_pending_event_get_sequence_id(pending));
+  uint64_t specified_logical_key = fl_keyboard_layout_get_logical_key(
+      self->derived_layout, fl_key_event_get_group(event),
+      fl_key_event_get_keycode(event));
+  for (guint i = 0; i < self->responder_list->len; i++) {
+    FlKeyResponder* responder =
+        FL_KEY_RESPONDER(g_ptr_array_index(self->responder_list, i));
+    fl_key_responder_handle_event(responder, event,
+                                  responder_handle_event_callback, user_data,
+                                  specified_logical_key);
+  }
+
+  return TRUE;
+}
+
+gboolean fl_keyboard_manager_is_state_clear(FlKeyboardManager* self) {
+  g_return_val_if_fail(FL_IS_KEYBOARD_MANAGER(self), FALSE);
+  return self->pending_responds->len == 0 &&
+         self->pending_redispatches->len == 0;
+}
+
+void fl_keyboard_manager_sync_modifier_if_needed(FlKeyboardManager* self,
+                                                 guint state,
+                                                 double event_time) {
+  g_return_if_fail(FL_IS_KEYBOARD_MANAGER(self));
+
+  // The embedder responder is the first element in
+  // FlKeyboardManager.responder_list.
+  FlKeyEmbedderResponder* responder =
+      FL_KEY_EMBEDDER_RESPONDER(g_ptr_array_index(self->responder_list, 0));
+  fl_key_embedder_responder_sync_modifiers_if_needed(responder, state,
+                                                     event_time);
+}
+
+GHashTable* fl_keyboard_manager_get_pressed_state(FlKeyboardManager* self) {
+  g_return_val_if_fail(FL_IS_KEYBOARD_MANAGER(self), nullptr);
+
+  // The embedder responder is the first element in
+  // FlKeyboardManager.responder_list.
+  FlKeyEmbedderResponder* responder =
+      FL_KEY_EMBEDDER_RESPONDER(g_ptr_array_index(self->responder_list, 0));
+  return fl_key_embedder_responder_get_pressed_state(responder);
+}
+
+void fl_keyboard_manager_notify_layout_changed(FlKeyboardManager* self) {
+  g_return_if_fail(FL_IS_KEYBOARD_MANAGER(self));
+  g_clear_object(&self->derived_layout);
+  self->derived_layout = fl_keyboard_layout_new();
+}
diff --git a/shell/platform/linux/fl_keyboard_manager.h b/shell/platform/linux/fl_keyboard_manager.h
new file mode 100644
index 0000000..383c7f9
--- /dev/null
+++ b/shell/platform/linux/fl_keyboard_manager.h
@@ -0,0 +1,108 @@
+// 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.
+
+#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_MANAGER_H_
+#define FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_MANAGER_H_
+
+#include <gdk/gdk.h>
+
+#include "flutter/shell/platform/linux/fl_keyboard_view_delegate.h"
+#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h"
+
+G_BEGIN_DECLS
+
+#define FL_TYPE_KEYBOARD_MANAGER fl_keyboard_manager_get_type()
+G_DECLARE_FINAL_TYPE(FlKeyboardManager,
+                     fl_keyboard_manager,
+                     FL,
+                     KEYBOARD_MANAGER,
+                     GObject);
+
+/**
+ * FlKeyboardManager:
+ *
+ * Processes keyboard events and cooperate with `TextInputManager`.
+ *
+ * A keyboard event goes through a few sections, each can choose to handle the
+ * event, and only unhandled events can move to the next section:
+ *
+ * - Keyboard: Dispatch to the embedder responder and the channel responder
+ *   simultaneously. After both responders have responded (asynchronously), the
+ *   event is considered handled if either responder handles it.
+ * - Text input: Events are sent to IM filter (usually owned by
+ *   `TextInputManager`) and are handled synchronously.
+ * - Redispatching: Events are inserted back to the system for redispatching.
+ */
+
+/**
+ * fl_keyboard_manager_new:
+ * @messenger: an #FlBinaryMessenger.
+ * @view_delegate: An interface that the manager requires to communicate with
+ * the platform. Usually implemented by FlView.
+ *
+ * Create a new #FlKeyboardManager.
+ *
+ * Returns: a new #FlKeyboardManager.
+ */
+FlKeyboardManager* fl_keyboard_manager_new(
+    FlBinaryMessenger* messenger,
+    FlKeyboardViewDelegate* view_delegate);
+
+/**
+ * fl_keyboard_manager_handle_event:
+ * @manager: the #FlKeyboardManager self.
+ * @event: the event to be dispatched. It is usually a wrap of a GdkEventKey.
+ * This event will be managed and released by #FlKeyboardManager.
+ *
+ * Make the manager process a system key event. This might eventually send
+ * messages to the framework, trigger text input effects, or redispatch the
+ * event back to the system.
+ */
+gboolean fl_keyboard_manager_handle_event(FlKeyboardManager* manager,
+                                          FlKeyEvent* event);
+
+/**
+ * fl_keyboard_manager_is_state_clear:
+ * @manager: the #FlKeyboardManager self.
+ *
+ * A debug-only method that queries whether the manager's various states are
+ * cleared, i.e. no pending events for redispatching or for responding.
+ *
+ * Returns: true if the manager's various states are cleared.
+ */
+gboolean fl_keyboard_manager_is_state_clear(FlKeyboardManager* manager);
+
+/**
+ * fl_keyboard_manager_sync_modifier_if_needed:
+ * @manager: the #FlKeyboardManager self.
+ * @state: the state of the modifiers mask.
+ * @event_time: the time attribute of the incoming GDK event.
+ *
+ * If needed, synthesize modifier keys up and down event by comparing their
+ * current pressing states with the given modifiers mask.
+ */
+void fl_keyboard_manager_sync_modifier_if_needed(FlKeyboardManager* manager,
+                                                 guint state,
+                                                 double event_time);
+
+/**
+ * fl_keyboard_manager_get_pressed_state:
+ * @manager: the #FlKeyboardManager self.
+ *
+ * Returns the keyboard pressed state. The hash table contains one entry per
+ * pressed keys, mapping from the logical key to the physical key.*
+ */
+GHashTable* fl_keyboard_manager_get_pressed_state(FlKeyboardManager* manager);
+
+/**
+ * fl_keyboard_manager_notify_layout_changed:
+ * @manager: the #FlKeyboardManager self.
+ *
+ * Notify the manager the keyboard layout has changed.
+ */
+void fl_keyboard_manager_notify_layout_changed(FlKeyboardManager* manager);
+
+G_END_DECLS
+
+#endif  // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_MANAGER_H_
diff --git a/shell/platform/linux/fl_keyboard_manager_test.cc b/shell/platform/linux/fl_keyboard_manager_test.cc
new file mode 100644
index 0000000..d655df3
--- /dev/null
+++ b/shell/platform/linux/fl_keyboard_manager_test.cc
@@ -0,0 +1,1139 @@
+// 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.
+
+#include "flutter/shell/platform/linux/fl_keyboard_manager.h"
+
+#include <cstring>
+#include <vector>
+
+#include "flutter/shell/platform/embedder/test_utils/key_codes.g.h"
+#include "flutter/shell/platform/linux/fl_binary_messenger_private.h"
+#include "flutter/shell/platform/linux/fl_method_codec_private.h"
+#include "flutter/shell/platform/linux/key_mapping.h"
+#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h"
+#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_codec.h"
+#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h"
+#include "flutter/shell/platform/linux/testing/fl_test.h"
+#include "flutter/shell/platform/linux/testing/mock_text_input_handler.h"
+#include "flutter/testing/testing.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+// Define compound `expect` in macros. If they were defined in functions, the
+// stacktrace wouldn't print where the function is called in the unit tests.
+
+#define EXPECT_KEY_EVENT(RECORD, TYPE, PHYSICAL, LOGICAL, CHAR, SYNTHESIZED) \
+  EXPECT_EQ((RECORD).type, CallRecord::kKeyCallEmbedder);                    \
+  EXPECT_EQ((RECORD).event->type, (TYPE));                                   \
+  EXPECT_EQ((RECORD).event->physical, (PHYSICAL));                           \
+  EXPECT_EQ((RECORD).event->logical, (LOGICAL));                             \
+  EXPECT_STREQ((RECORD).event->character, (CHAR));                           \
+  EXPECT_EQ((RECORD).event->synthesized, (SYNTHESIZED));
+
+#define VERIFY_DOWN(OUT_LOGICAL, OUT_CHAR)                          \
+  EXPECT_EQ(call_records[0].type, CallRecord::kKeyCallEmbedder);    \
+  EXPECT_EQ(call_records[0].event->type, kFlutterKeyEventTypeDown); \
+  EXPECT_EQ(call_records[0].event->logical, (OUT_LOGICAL));         \
+  EXPECT_STREQ(call_records[0].event->character, (OUT_CHAR));       \
+  EXPECT_EQ(call_records[0].event->synthesized, false);             \
+  call_records.clear()
+
+namespace {
+using ::flutter::testing::keycodes::kLogicalAltLeft;
+using ::flutter::testing::keycodes::kLogicalBracketLeft;
+using ::flutter::testing::keycodes::kLogicalComma;
+using ::flutter::testing::keycodes::kLogicalControlLeft;
+using ::flutter::testing::keycodes::kLogicalDigit1;
+using ::flutter::testing::keycodes::kLogicalKeyA;
+using ::flutter::testing::keycodes::kLogicalKeyB;
+using ::flutter::testing::keycodes::kLogicalKeyM;
+using ::flutter::testing::keycodes::kLogicalKeyQ;
+using ::flutter::testing::keycodes::kLogicalMetaLeft;
+using ::flutter::testing::keycodes::kLogicalMinus;
+using ::flutter::testing::keycodes::kLogicalParenthesisRight;
+using ::flutter::testing::keycodes::kLogicalSemicolon;
+using ::flutter::testing::keycodes::kLogicalShiftLeft;
+using ::flutter::testing::keycodes::kLogicalUnderscore;
+
+using ::flutter::testing::keycodes::kPhysicalAltLeft;
+using ::flutter::testing::keycodes::kPhysicalControlLeft;
+using ::flutter::testing::keycodes::kPhysicalKeyA;
+using ::flutter::testing::keycodes::kPhysicalKeyB;
+using ::flutter::testing::keycodes::kPhysicalMetaLeft;
+using ::flutter::testing::keycodes::kPhysicalShiftLeft;
+
+// Hardware key codes.
+typedef std::function<void(bool handled)> AsyncKeyCallback;
+typedef std::function<void(AsyncKeyCallback callback)> ChannelCallHandler;
+typedef std::function<void(const FlutterKeyEvent* event,
+                           AsyncKeyCallback callback)>
+    EmbedderCallHandler;
+typedef std::function<void(FlKeyEvent*)> RedispatchHandler;
+
+// A type that can record all kinds of effects that the keyboard handler
+// triggers.
+//
+// An instance of `CallRecord` might not have all the fields filled.
+typedef struct {
+  enum {
+    kKeyCallEmbedder,
+    kKeyCallChannel,
+  } type;
+
+  AsyncKeyCallback callback;
+  std::unique_ptr<FlutterKeyEvent> event;
+  std::unique_ptr<char[]> event_character;
+} CallRecord;
+
+// Clone a C-string.
+//
+// Must be deleted by delete[].
+char* cloneString(const char* source) {
+  if (source == nullptr) {
+    return nullptr;
+  }
+  size_t charLen = strlen(source);
+  char* target = new char[charLen + 1];
+  strncpy(target, source, charLen + 1);
+  return target;
+}
+
+constexpr guint16 kKeyCodeKeyA = 0x26u;
+constexpr guint16 kKeyCodeKeyB = 0x38u;
+constexpr guint16 kKeyCodeKeyM = 0x3au;
+constexpr guint16 kKeyCodeDigit1 = 0x0au;
+constexpr guint16 kKeyCodeMinus = 0x14u;
+constexpr guint16 kKeyCodeSemicolon = 0x2fu;
+constexpr guint16 kKeyCodeKeyLeftBracket = 0x22u;
+
+static constexpr char kKeyEventChannelName[] = "flutter/keyevent";
+
+// All key clues for a keyboard layout.
+//
+// The index is (keyCode * 2 + hasShift), where each value is the character for
+// this key (GTK only supports UTF-16.) Since the maximum keycode of interest
+// is 128, it has a total of 256 entries..
+typedef std::array<uint32_t, 256> MockGroupLayoutData;
+typedef std::vector<const MockGroupLayoutData*> MockLayoutData;
+
+extern const MockLayoutData kLayoutUs;
+extern const MockLayoutData kLayoutRussian;
+extern const MockLayoutData kLayoutFrench;
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE(FlMockViewDelegate,
+                     fl_mock_view_delegate,
+                     FL,
+                     MOCK_VIEW_DELEGATE,
+                     GObject);
+
+G_DECLARE_FINAL_TYPE(FlMockKeyBinaryMessenger,
+                     fl_mock_key_binary_messenger,
+                     FL,
+                     MOCK_KEY_BINARY_MESSENGER,
+                     GObject)
+
+G_END_DECLS
+
+MATCHER_P(MethodSuccessResponse, result, "") {
+  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
+  g_autoptr(FlMethodResponse) response =
+      fl_method_codec_decode_response(FL_METHOD_CODEC(codec), arg, nullptr);
+  fl_method_response_get_result(response, nullptr);
+  if (fl_value_equal(fl_method_response_get_result(response, nullptr),
+                     result)) {
+    return true;
+  }
+  *result_listener << ::testing::PrintToString(response);
+  return false;
+}
+
+/***** FlMockKeyBinaryMessenger *****/
+/* Mock a binary messenger that only processes messages from the embedding on
+ * the key event channel, and does so according to the callback set by
+ * fl_mock_key_binary_messenger_set_callback_handler */
+
+struct _FlMockKeyBinaryMessenger {
+  GObject parent_instance;
+
+  ChannelCallHandler callback_handler;
+};
+
+static void fl_mock_key_binary_messenger_iface_init(
+    FlBinaryMessengerInterface* iface);
+
+G_DEFINE_TYPE_WITH_CODE(
+    FlMockKeyBinaryMessenger,
+    fl_mock_key_binary_messenger,
+    G_TYPE_OBJECT,
+    G_IMPLEMENT_INTERFACE(fl_binary_messenger_get_type(),
+                          fl_mock_key_binary_messenger_iface_init))
+
+static void fl_mock_key_binary_messenger_init(FlMockKeyBinaryMessenger* self) {}
+
+static void fl_mock_key_binary_messenger_dispose(GObject* object) {
+  G_OBJECT_CLASS(fl_mock_key_binary_messenger_parent_class)->dispose(object);
+}
+
+static void fl_mock_key_binary_messenger_class_init(
+    FlMockKeyBinaryMessengerClass* klass) {
+  G_OBJECT_CLASS(klass)->dispose = fl_mock_key_binary_messenger_dispose;
+}
+
+static void fl_mock_key_binary_messenger_send_on_channel(
+    FlBinaryMessenger* messenger,
+    const gchar* channel,
+    GBytes* message,
+    GCancellable* cancellable,
+    GAsyncReadyCallback callback,
+    gpointer user_data) {
+  FlMockKeyBinaryMessenger* self = FL_MOCK_KEY_BINARY_MESSENGER(messenger);
+
+  if (callback != nullptr) {
+    EXPECT_STREQ(channel, kKeyEventChannelName);
+    self->callback_handler([self, cancellable, callback,
+                            user_data](bool handled) {
+      g_autoptr(GTask) task =
+          g_task_new(self, cancellable, callback, user_data);
+      g_autoptr(FlValue) result = fl_value_new_map();
+      fl_value_set_string_take(result, "handled", fl_value_new_bool(handled));
+      g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new();
+      g_autoptr(GError) error = nullptr;
+      GBytes* data = fl_message_codec_encode_message(FL_MESSAGE_CODEC(codec),
+                                                     result, &error);
+
+      g_task_return_pointer(task, data,
+                            reinterpret_cast<GDestroyNotify>(g_bytes_unref));
+    });
+  }
+}
+
+static GBytes* fl_mock_key_binary_messenger_send_on_channel_finish(
+    FlBinaryMessenger* messenger,
+    GAsyncResult* result,
+    GError** error) {
+  return static_cast<GBytes*>(g_task_propagate_pointer(G_TASK(result), error));
+}
+
+static void fl_mock_binary_messenger_resize_channel(
+    FlBinaryMessenger* messenger,
+    const gchar* channel,
+    int64_t new_size) {
+  // Mock implementation. Do nothing.
+}
+
+static void fl_mock_binary_messenger_set_warns_on_channel_overflow(
+    FlBinaryMessenger* messenger,
+    const gchar* channel,
+    bool warns) {
+  // Mock implementation. Do nothing.
+}
+
+static void fl_mock_key_binary_messenger_iface_init(
+    FlBinaryMessengerInterface* iface) {
+  iface->set_message_handler_on_channel =
+      [](FlBinaryMessenger* messenger, const gchar* channel,
+         FlBinaryMessengerMessageHandler handler, gpointer user_data,
+         GDestroyNotify destroy_notify) {
+        EXPECT_STREQ(channel, kKeyEventChannelName);
+        // No need to mock. The key event channel expects no incoming messages
+        // from the framework.
+      };
+  iface->send_response = [](FlBinaryMessenger* messenger,
+                            FlBinaryMessengerResponseHandle* response_handle,
+                            GBytes* response, GError** error) -> gboolean {
+    // The key event channel expects no incoming messages from the framework,
+    // hence no responses either.
+    g_return_val_if_reached(TRUE);
+    return TRUE;
+  };
+  iface->send_on_channel = fl_mock_key_binary_messenger_send_on_channel;
+  iface->send_on_channel_finish =
+      fl_mock_key_binary_messenger_send_on_channel_finish;
+  iface->resize_channel = fl_mock_binary_messenger_resize_channel;
+  iface->set_warns_on_channel_overflow =
+      fl_mock_binary_messenger_set_warns_on_channel_overflow;
+}
+
+static FlMockKeyBinaryMessenger* fl_mock_key_binary_messenger_new() {
+  FlMockKeyBinaryMessenger* self = FL_MOCK_KEY_BINARY_MESSENGER(
+      g_object_new(fl_mock_key_binary_messenger_get_type(), NULL));
+
+  // Added to stop compiler complaining about an unused function.
+  FL_IS_MOCK_KEY_BINARY_MESSENGER(self);
+
+  return self;
+}
+
+static void fl_mock_key_binary_messenger_set_callback_handler(
+    FlMockKeyBinaryMessenger* self,
+    ChannelCallHandler handler) {
+  self->callback_handler = std::move(handler);
+}
+
+/***** FlMockViewDelegate *****/
+
+struct _FlMockViewDelegate {
+  GObject parent_instance;
+
+  FlMockKeyBinaryMessenger* messenger;
+  EmbedderCallHandler embedder_handler;
+  bool text_filter_result;
+  RedispatchHandler redispatch_handler;
+  const MockLayoutData* layout_data;
+};
+
+static void fl_mock_view_keyboard_delegate_iface_init(
+    FlKeyboardViewDelegateInterface* iface);
+
+G_DEFINE_TYPE_WITH_CODE(
+    FlMockViewDelegate,
+    fl_mock_view_delegate,
+    G_TYPE_OBJECT,
+    G_IMPLEMENT_INTERFACE(fl_keyboard_view_delegate_get_type(),
+                          fl_mock_view_keyboard_delegate_iface_init))
+
+static void fl_mock_view_delegate_init(FlMockViewDelegate* self) {}
+
+static void fl_mock_view_delegate_dispose(GObject* object) {
+  FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(object);
+
+  g_clear_object(&self->messenger);
+
+  G_OBJECT_CLASS(fl_mock_view_delegate_parent_class)->dispose(object);
+}
+
+static void fl_mock_view_delegate_class_init(FlMockViewDelegateClass* klass) {
+  G_OBJECT_CLASS(klass)->dispose = fl_mock_view_delegate_dispose;
+}
+
+static void fl_mock_view_keyboard_send_key_event(
+    FlKeyboardViewDelegate* view_delegate,
+    const FlutterKeyEvent* event,
+    FlutterKeyEventCallback callback,
+    void* user_data) {
+  FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(view_delegate);
+  self->embedder_handler(event, [callback, user_data](bool handled) {
+    if (callback != nullptr) {
+      callback(handled, user_data);
+    }
+  });
+}
+
+static gboolean fl_mock_view_keyboard_text_filter_key_press(
+    FlKeyboardViewDelegate* view_delegate,
+    FlKeyEvent* event) {
+  FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(view_delegate);
+  return self->text_filter_result;
+}
+
+static void fl_mock_view_keyboard_redispatch_event(
+    FlKeyboardViewDelegate* view_delegate,
+    FlKeyEvent* event) {
+  FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(view_delegate);
+  if (self->redispatch_handler) {
+    self->redispatch_handler(event);
+  }
+}
+
+static guint fl_mock_view_keyboard_lookup_key(
+    FlKeyboardViewDelegate* view_delegate,
+    const GdkKeymapKey* key) {
+  FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(view_delegate);
+  guint8 group = static_cast<guint8>(key->group);
+  EXPECT_LT(group, self->layout_data->size());
+  const MockGroupLayoutData* group_layout = (*self->layout_data)[group];
+  EXPECT_TRUE(group_layout != nullptr);
+  EXPECT_TRUE(key->level == 0 || key->level == 1);
+  bool shift = key->level == 1;
+  return (*group_layout)[key->keycode * 2 + shift];
+}
+
+static void fl_mock_view_keyboard_delegate_iface_init(
+    FlKeyboardViewDelegateInterface* iface) {
+  iface->send_key_event = fl_mock_view_keyboard_send_key_event;
+  iface->text_filter_key_press = fl_mock_view_keyboard_text_filter_key_press;
+  iface->redispatch_event = fl_mock_view_keyboard_redispatch_event;
+  iface->lookup_key = fl_mock_view_keyboard_lookup_key;
+}
+
+static FlMockViewDelegate* fl_mock_view_delegate_new() {
+  FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(
+      g_object_new(fl_mock_view_delegate_get_type(), nullptr));
+
+  // Added to stop compiler complaining about an unused function.
+  FL_IS_MOCK_VIEW_DELEGATE(self);
+
+  self->messenger = fl_mock_key_binary_messenger_new();
+
+  return self;
+}
+
+static void fl_mock_view_set_embedder_handler(FlMockViewDelegate* self,
+                                              EmbedderCallHandler handler) {
+  self->embedder_handler = std::move(handler);
+}
+
+static void fl_mock_view_set_text_filter_result(FlMockViewDelegate* self,
+                                                bool result) {
+  self->text_filter_result = result;
+}
+
+static void fl_mock_view_set_redispatch_handler(FlMockViewDelegate* self,
+                                                RedispatchHandler handler) {
+  self->redispatch_handler = std::move(handler);
+}
+
+static void fl_mock_view_set_layout(FlMockViewDelegate* self,
+                                    const MockLayoutData* layout) {
+  self->layout_data = layout;
+}
+
+/***** End FlMockViewDelegate *****/
+
+class KeyboardTester {
+ public:
+  KeyboardTester() {
+    view_ = fl_mock_view_delegate_new();
+    respondToEmbedderCallsWith(false);
+    respondToChannelCallsWith(false);
+    respondToTextInputWith(false);
+    setLayout(kLayoutUs);
+
+    handler_ = fl_keyboard_manager_new(FL_BINARY_MESSENGER(view_->messenger),
+                                       FL_KEYBOARD_VIEW_DELEGATE(view_));
+  }
+
+  ~KeyboardTester() {
+    g_clear_object(&view_);
+    g_clear_object(&handler_);
+    g_clear_pointer(&redispatched_events_, g_ptr_array_unref);
+  }
+
+  FlKeyboardManager* handler() { return handler_; }
+
+  // Block until all GdkMainLoop messages are processed, which is basically
+  // used only for channel messages.
+  void flushChannelMessages() {
+    GMainLoop* loop = g_main_loop_new(nullptr, 0);
+    g_idle_add(_flushChannelMessagesCb, loop);
+    g_main_loop_run(loop);
+  }
+
+  // Dispatch each of the given events, expect their results to be false
+  // (unhandled), and clear the event array.
+  //
+  // Returns the number of events redispatched. If any result is unexpected
+  // (handled), return a minus number `-x` instead, where `x` is the index of
+  // the first unexpected redispatch.
+  int redispatchEventsAndClear(GPtrArray* events) {
+    guint event_count = events->len;
+    int first_error = -1;
+    during_redispatch_ = true;
+    for (guint event_id = 0; event_id < event_count; event_id += 1) {
+      FlKeyEvent* event = FL_KEY_EVENT(g_ptr_array_index(events, event_id));
+      bool handled = fl_keyboard_manager_handle_event(handler_, event);
+      EXPECT_FALSE(handled);
+      if (handled) {
+        first_error = first_error == -1 ? event_id : first_error;
+      }
+    }
+    during_redispatch_ = false;
+    g_ptr_array_set_size(events, 0);
+    return first_error < 0 ? event_count : -first_error;
+  }
+
+  void respondToEmbedderCallsWith(bool response) {
+    fl_mock_view_set_embedder_handler(
+        view_, [response, this](const FlutterKeyEvent* event,
+                                const AsyncKeyCallback& callback) {
+          EXPECT_FALSE(during_redispatch_);
+          callback(response);
+        });
+  }
+
+  void recordEmbedderCallsTo(std::vector<CallRecord>& storage) {
+    fl_mock_view_set_embedder_handler(
+        view_, [&storage, this](const FlutterKeyEvent* event,
+                                AsyncKeyCallback callback) {
+          EXPECT_FALSE(during_redispatch_);
+          auto new_event = std::make_unique<FlutterKeyEvent>(*event);
+          char* new_event_character = cloneString(event->character);
+          new_event->character = new_event_character;
+          storage.push_back(CallRecord{
+              .type = CallRecord::kKeyCallEmbedder,
+              .callback = std::move(callback),
+              .event = std::move(new_event),
+              .event_character = std::unique_ptr<char[]>(new_event_character),
+          });
+        });
+  }
+
+  void respondToEmbedderCallsWithAndRecordsTo(
+      bool response,
+      std::vector<CallRecord>& storage) {
+    fl_mock_view_set_embedder_handler(
+        view_, [&storage, response, this](const FlutterKeyEvent* event,
+                                          const AsyncKeyCallback& callback) {
+          EXPECT_FALSE(during_redispatch_);
+          auto new_event = std::make_unique<FlutterKeyEvent>(*event);
+          char* new_event_character = cloneString(event->character);
+          new_event->character = new_event_character;
+          storage.push_back(CallRecord{
+              .type = CallRecord::kKeyCallEmbedder,
+              .event = std::move(new_event),
+              .event_character = std::unique_ptr<char[]>(new_event_character),
+          });
+          callback(response);
+        });
+  }
+
+  void respondToChannelCallsWith(bool response) {
+    fl_mock_key_binary_messenger_set_callback_handler(
+        view_->messenger, [response, this](const AsyncKeyCallback& callback) {
+          EXPECT_FALSE(during_redispatch_);
+          callback(response);
+        });
+  }
+
+  void recordChannelCallsTo(std::vector<CallRecord>& storage) {
+    fl_mock_key_binary_messenger_set_callback_handler(
+        view_->messenger, [&storage, this](AsyncKeyCallback callback) {
+          EXPECT_FALSE(during_redispatch_);
+          storage.push_back(CallRecord{
+              .type = CallRecord::kKeyCallChannel,
+              .callback = std::move(callback),
+          });
+        });
+  }
+
+  void respondToTextInputWith(bool response) {
+    fl_mock_view_set_text_filter_result(view_, response);
+  }
+
+  void recordRedispatchedEventsTo(GPtrArray* storage) {
+    redispatched_events_ = g_ptr_array_ref(storage);
+    fl_mock_view_set_redispatch_handler(view_, [this](FlKeyEvent* key) {
+      g_ptr_array_add(redispatched_events_, g_object_ref(key));
+    });
+  }
+
+  void setLayout(const MockLayoutData& layout) {
+    fl_mock_view_set_layout(view_, &layout);
+    if (handler_ != nullptr) {
+      fl_keyboard_manager_notify_layout_changed(handler_);
+    }
+  }
+
+ private:
+  FlMockViewDelegate* view_;
+  FlKeyboardManager* handler_ = nullptr;
+  GPtrArray* redispatched_events_ = nullptr;
+  bool during_redispatch_ = false;
+
+  static gboolean _flushChannelMessagesCb(gpointer data) {
+    g_autoptr(GMainLoop) loop = reinterpret_cast<GMainLoop*>(data);
+    g_main_loop_quit(loop);
+    return FALSE;
+  }
+};
+
+// Make sure that the keyboard can be disposed without crashes when there are
+// unresolved pending events.
+TEST(FlKeyboardManagerTest, DisposeWithUnresolvedPends) {
+  KeyboardTester tester;
+  std::vector<CallRecord> call_records;
+
+  // Record calls so that they aren't responded.
+  tester.recordEmbedderCallsTo(call_records);
+  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
+      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  fl_keyboard_manager_handle_event(tester.handler(), event1);
+
+  tester.respondToEmbedderCallsWith(true);
+  g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
+      0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  fl_keyboard_manager_handle_event(tester.handler(), event2);
+
+  tester.flushChannelMessages();
+
+  // Passes if the cleanup does not crash.
+}
+
+TEST(FlKeyboardManagerTest, SingleDelegateWithAsyncResponds) {
+  KeyboardTester tester;
+  std::vector<CallRecord> call_records;
+  g_autoptr(GPtrArray) redispatched =
+      g_ptr_array_new_with_free_func(g_object_unref);
+
+  gboolean handler_handled = false;
+
+  /// Test 1: One event that is handled by the framework
+  tester.recordEmbedderCallsTo(call_records);
+  tester.recordRedispatchedEventsTo(redispatched);
+
+  // Dispatch a key event
+  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
+      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event1);
+  tester.flushChannelMessages();
+  EXPECT_EQ(handler_handled, true);
+  EXPECT_EQ(redispatched->len, 0u);
+  EXPECT_EQ(call_records.size(), 1u);
+  EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, kPhysicalKeyA,
+                   kLogicalKeyA, "a", false);
+
+  call_records[0].callback(true);
+  tester.flushChannelMessages();
+  EXPECT_EQ(redispatched->len, 0u);
+  EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.handler()));
+  call_records.clear();
+
+  /// Test 2: Two events that are unhandled by the framework
+  g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
+      0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event2);
+  tester.flushChannelMessages();
+  EXPECT_EQ(handler_handled, true);
+  EXPECT_EQ(redispatched->len, 0u);
+  EXPECT_EQ(call_records.size(), 1u);
+  EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeUp, kPhysicalKeyA,
+                   kLogicalKeyA, nullptr, false);
+
+  // Dispatch another key event
+  g_autoptr(FlKeyEvent) event3 = fl_key_event_new(
+      0, TRUE, kKeyCodeKeyB, GDK_KEY_b, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event3);
+  tester.flushChannelMessages();
+  EXPECT_EQ(handler_handled, true);
+  EXPECT_EQ(redispatched->len, 0u);
+  EXPECT_EQ(call_records.size(), 2u);
+  EXPECT_KEY_EVENT(call_records[1], kFlutterKeyEventTypeDown, kPhysicalKeyB,
+                   kLogicalKeyB, "b", false);
+
+  // Resolve the second event first to test out-of-order response
+  call_records[1].callback(false);
+  EXPECT_EQ(redispatched->len, 1u);
+  EXPECT_EQ(
+      fl_key_event_get_keyval(FL_KEY_EVENT(g_ptr_array_index(redispatched, 0))),
+      0x62u);
+  call_records[0].callback(false);
+  tester.flushChannelMessages();
+  EXPECT_EQ(redispatched->len, 2u);
+  EXPECT_EQ(
+      fl_key_event_get_keyval(FL_KEY_EVENT(g_ptr_array_index(redispatched, 1))),
+      0x61u);
+
+  EXPECT_FALSE(fl_keyboard_manager_is_state_clear(tester.handler()));
+  call_records.clear();
+
+  // Resolve redispatches
+  EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 2);
+  tester.flushChannelMessages();
+  EXPECT_EQ(call_records.size(), 0u);
+  EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.handler()));
+
+  /// Test 3: Dispatch the same event again to ensure that prevention from
+  /// redispatching only works once.
+  g_autoptr(FlKeyEvent) event4 = fl_key_event_new(
+      0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event4);
+  tester.flushChannelMessages();
+  EXPECT_EQ(handler_handled, true);
+  EXPECT_EQ(redispatched->len, 0u);
+  EXPECT_EQ(call_records.size(), 1u);
+
+  call_records[0].callback(true);
+  EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.handler()));
+}
+
+TEST(FlKeyboardManagerTest, SingleDelegateWithSyncResponds) {
+  KeyboardTester tester;
+  gboolean handler_handled = false;
+  std::vector<CallRecord> call_records;
+  g_autoptr(GPtrArray) redispatched =
+      g_ptr_array_new_with_free_func(g_object_unref);
+
+  /// Test 1: One event that is handled by the framework
+  tester.respondToEmbedderCallsWithAndRecordsTo(true, call_records);
+  tester.recordRedispatchedEventsTo(redispatched);
+
+  // Dispatch a key event
+  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
+      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event1);
+  tester.flushChannelMessages();
+  EXPECT_EQ(handler_handled, true);
+  EXPECT_EQ(call_records.size(), 1u);
+  EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, kPhysicalKeyA,
+                   kLogicalKeyA, "a", false);
+  EXPECT_EQ(redispatched->len, 0u);
+  call_records.clear();
+
+  EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.handler()));
+  g_ptr_array_set_size(redispatched, 0);
+
+  /// Test 2: An event unhandled by the framework
+  tester.respondToEmbedderCallsWithAndRecordsTo(false, call_records);
+  g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
+      0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event2);
+  tester.flushChannelMessages();
+  EXPECT_EQ(handler_handled, true);
+  EXPECT_EQ(call_records.size(), 1u);
+  EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeUp, kPhysicalKeyA,
+                   kLogicalKeyA, nullptr, false);
+  EXPECT_EQ(redispatched->len, 1u);
+  call_records.clear();
+
+  EXPECT_FALSE(fl_keyboard_manager_is_state_clear(tester.handler()));
+
+  EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 1);
+  EXPECT_EQ(call_records.size(), 0u);
+
+  EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.handler()));
+}
+
+TEST(FlKeyboardManagerTest, WithTwoAsyncDelegates) {
+  KeyboardTester tester;
+  std::vector<CallRecord> call_records;
+  g_autoptr(GPtrArray) redispatched =
+      g_ptr_array_new_with_free_func(g_object_unref);
+
+  gboolean handler_handled = false;
+
+  tester.recordEmbedderCallsTo(call_records);
+  tester.recordChannelCallsTo(call_records);
+  tester.recordRedispatchedEventsTo(redispatched);
+
+  /// Test 1: One delegate responds true, the other false
+
+  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
+      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event1);
+
+  EXPECT_EQ(handler_handled, true);
+  EXPECT_EQ(redispatched->len, 0u);
+  EXPECT_EQ(call_records.size(), 2u);
+
+  EXPECT_EQ(call_records[0].type, CallRecord::kKeyCallEmbedder);
+  EXPECT_EQ(call_records[1].type, CallRecord::kKeyCallChannel);
+
+  call_records[0].callback(true);
+  call_records[1].callback(false);
+  tester.flushChannelMessages();
+  EXPECT_EQ(redispatched->len, 0u);
+
+  EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.handler()));
+  call_records.clear();
+
+  /// Test 2: All delegates respond false
+  g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
+      0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event2);
+
+  EXPECT_EQ(handler_handled, true);
+  EXPECT_EQ(redispatched->len, 0u);
+  EXPECT_EQ(call_records.size(), 2u);
+
+  EXPECT_EQ(call_records[0].type, CallRecord::kKeyCallEmbedder);
+  EXPECT_EQ(call_records[1].type, CallRecord::kKeyCallChannel);
+
+  call_records[0].callback(false);
+  call_records[1].callback(false);
+
+  call_records.clear();
+
+  // Resolve redispatch
+  tester.flushChannelMessages();
+  EXPECT_EQ(redispatched->len, 1u);
+  EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 1);
+  EXPECT_EQ(call_records.size(), 0u);
+
+  EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.handler()));
+}
+
+TEST(FlKeyboardManagerTest, TextInputHandlerReturnsFalse) {
+  KeyboardTester tester;
+  g_autoptr(GPtrArray) redispatched =
+      g_ptr_array_new_with_free_func(g_object_unref);
+  gboolean handler_handled = false;
+  tester.recordRedispatchedEventsTo(redispatched);
+  tester.respondToTextInputWith(false);
+
+  // Dispatch a key event.
+  g_autoptr(FlKeyEvent) event = fl_key_event_new(
+      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event);
+  tester.flushChannelMessages();
+  EXPECT_EQ(handler_handled, true);
+  // The event was redispatched because no one handles it.
+  EXPECT_EQ(redispatched->len, 1u);
+
+  // Resolve redispatched event.
+  EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 1);
+
+  EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.handler()));
+}
+
+TEST(FlKeyboardManagerTest, TextInputHandlerReturnsTrue) {
+  KeyboardTester tester;
+  g_autoptr(GPtrArray) redispatched =
+      g_ptr_array_new_with_free_func(g_object_unref);
+  gboolean handler_handled = false;
+  tester.recordRedispatchedEventsTo(redispatched);
+  tester.respondToTextInputWith(true);
+
+  // Dispatch a key event.
+  g_autoptr(FlKeyEvent) event = fl_key_event_new(
+      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event);
+  tester.flushChannelMessages();
+  EXPECT_EQ(handler_handled, true);
+  // The event was not redispatched because handler handles it.
+  EXPECT_EQ(redispatched->len, 0u);
+
+  EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.handler()));
+}
+
+TEST(FlKeyboardManagerTest, CorrectLogicalKeyForLayouts) {
+  KeyboardTester tester;
+
+  std::vector<CallRecord> call_records;
+  tester.recordEmbedderCallsTo(call_records);
+
+  auto sendTap = [&](guint8 keycode, guint keyval, guint8 group) {
+    g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
+        0, TRUE, keycode, keyval, static_cast<GdkModifierType>(0), group);
+    fl_keyboard_manager_handle_event(tester.handler(), event1);
+    g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
+        0, FALSE, keycode, keyval, static_cast<GdkModifierType>(0), group);
+    fl_keyboard_manager_handle_event(tester.handler(), event2);
+  };
+
+  /* US keyboard layout */
+
+  sendTap(kKeyCodeKeyA, GDK_KEY_a, 0);  // KeyA
+  VERIFY_DOWN(kLogicalKeyA, "a");
+
+  sendTap(kKeyCodeKeyA, GDK_KEY_A, 0);  // Shift-KeyA
+  VERIFY_DOWN(kLogicalKeyA, "A");
+
+  sendTap(kKeyCodeDigit1, GDK_KEY_1, 0);  // Digit1
+  VERIFY_DOWN(kLogicalDigit1, "1");
+
+  sendTap(kKeyCodeDigit1, GDK_KEY_exclam, 0);  // Shift-Digit1
+  VERIFY_DOWN(kLogicalDigit1, "!");
+
+  sendTap(kKeyCodeMinus, GDK_KEY_minus, 0);  // Minus
+  VERIFY_DOWN(kLogicalMinus, "-");
+
+  sendTap(kKeyCodeMinus, GDK_KEY_underscore, 0);  // Shift-Minus
+  VERIFY_DOWN(kLogicalUnderscore, "_");
+
+  /* French keyboard layout, group 3, which is when the input method is showing
+   * "Fr" */
+
+  tester.setLayout(kLayoutFrench);
+
+  sendTap(kKeyCodeKeyA, GDK_KEY_q, 3);  // KeyA
+  VERIFY_DOWN(kLogicalKeyQ, "q");
+
+  sendTap(kKeyCodeKeyA, GDK_KEY_Q, 3);  // Shift-KeyA
+  VERIFY_DOWN(kLogicalKeyQ, "Q");
+
+  sendTap(kKeyCodeSemicolon, GDK_KEY_m, 3);  // ; but prints M
+  VERIFY_DOWN(kLogicalKeyM, "m");
+
+  sendTap(kKeyCodeKeyM, GDK_KEY_comma, 3);  // M but prints ,
+  VERIFY_DOWN(kLogicalComma, ",");
+
+  sendTap(kKeyCodeDigit1, GDK_KEY_ampersand, 3);  // Digit1
+  VERIFY_DOWN(kLogicalDigit1, "&");
+
+  sendTap(kKeyCodeDigit1, GDK_KEY_1, 3);  // Shift-Digit1
+  VERIFY_DOWN(kLogicalDigit1, "1");
+
+  sendTap(kKeyCodeMinus, GDK_KEY_parenright, 3);  // Minus
+  VERIFY_DOWN(kLogicalParenthesisRight, ")");
+
+  sendTap(kKeyCodeMinus, GDK_KEY_degree, 3);  // Shift-Minus
+  VERIFY_DOWN(static_cast<uint32_t>(L'°'), "°");
+
+  /* French keyboard layout, group 0, which is pressing the "extra key for
+   * triggering input method" key once after switching to French IME. */
+
+  sendTap(kKeyCodeKeyA, GDK_KEY_a, 0);  // KeyA
+  VERIFY_DOWN(kLogicalKeyA, "a");
+
+  sendTap(kKeyCodeDigit1, GDK_KEY_1, 0);  // Digit1
+  VERIFY_DOWN(kLogicalDigit1, "1");
+
+  /* Russian keyboard layout, group 2 */
+  tester.setLayout(kLayoutRussian);
+
+  sendTap(kKeyCodeKeyA, GDK_KEY_Cyrillic_ef, 2);  // KeyA
+  VERIFY_DOWN(kLogicalKeyA, "ф");
+
+  sendTap(kKeyCodeDigit1, GDK_KEY_1, 2);  // Shift-Digit1
+  VERIFY_DOWN(kLogicalDigit1, "1");
+
+  sendTap(kKeyCodeKeyLeftBracket, GDK_KEY_Cyrillic_ha, 2);
+  VERIFY_DOWN(kLogicalBracketLeft, "х");
+
+  /* Russian keyboard layout, group 0 */
+  sendTap(kKeyCodeKeyA, GDK_KEY_a, 0);  // KeyA
+  VERIFY_DOWN(kLogicalKeyA, "a");
+
+  sendTap(kKeyCodeKeyLeftBracket, GDK_KEY_bracketleft, 0);
+  VERIFY_DOWN(kLogicalBracketLeft, "[");
+}
+
+TEST(FlKeyboardManagerTest, SynthesizeModifiersIfNeeded) {
+  KeyboardTester tester;
+  std::vector<CallRecord> call_records;
+  tester.recordEmbedderCallsTo(call_records);
+
+  auto verifyModifierIsSynthesized = [&](GdkModifierType mask,
+                                         uint64_t physical, uint64_t logical) {
+    // Modifier is pressed.
+    guint state = mask;
+    fl_keyboard_manager_sync_modifier_if_needed(tester.handler(), state, 1000);
+    EXPECT_EQ(call_records.size(), 1u);
+    EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, physical,
+                     logical, NULL, true);
+    // Modifier is released.
+    state = state ^ mask;
+    fl_keyboard_manager_sync_modifier_if_needed(tester.handler(), state, 1001);
+    EXPECT_EQ(call_records.size(), 2u);
+    EXPECT_KEY_EVENT(call_records[1], kFlutterKeyEventTypeUp, physical, logical,
+                     NULL, true);
+    call_records.clear();
+  };
+
+  // No modifiers pressed.
+  guint state = 0;
+  fl_keyboard_manager_sync_modifier_if_needed(tester.handler(), state, 1000);
+  EXPECT_EQ(call_records.size(), 0u);
+  call_records.clear();
+
+  // Press and release each modifier once.
+  verifyModifierIsSynthesized(GDK_CONTROL_MASK, kPhysicalControlLeft,
+                              kLogicalControlLeft);
+  verifyModifierIsSynthesized(GDK_META_MASK, kPhysicalMetaLeft,
+                              kLogicalMetaLeft);
+  verifyModifierIsSynthesized(GDK_MOD1_MASK, kPhysicalAltLeft, kLogicalAltLeft);
+  verifyModifierIsSynthesized(GDK_SHIFT_MASK, kPhysicalShiftLeft,
+                              kLogicalShiftLeft);
+}
+
+TEST(FlKeyboardManagerTest, GetPressedState) {
+  KeyboardTester tester;
+  tester.respondToTextInputWith(true);
+
+  // Dispatch a key event.
+  g_autoptr(FlKeyEvent) event = fl_key_event_new(
+      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  fl_keyboard_manager_handle_event(tester.handler(), event);
+
+  GHashTable* pressedState =
+      fl_keyboard_manager_get_pressed_state(tester.handler());
+  EXPECT_EQ(g_hash_table_size(pressedState), 1u);
+
+  gpointer physical_key =
+      g_hash_table_lookup(pressedState, uint64_to_gpointer(kPhysicalKeyA));
+  EXPECT_EQ(gpointer_to_uint64(physical_key), kLogicalKeyA);
+}
+
+// The following layout data is generated using DEBUG_PRINT_LAYOUT.
+
+const MockGroupLayoutData kLayoutUs0{{
+    // +0x0  Shift   +0x1    Shift   +0x2    Shift   +0x3    Shift
+    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x00
+    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x04
+    0xffff, 0x0031, 0xffff, 0x0031, 0x0031, 0x0021, 0x0032, 0x0040,  // 0x08
+    0x0033, 0x0023, 0x0034, 0x0024, 0x0035, 0x0025, 0x0036, 0x005e,  // 0x0c
+    0x0037, 0x0026, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029,  // 0x10
+    0x002d, 0x005f, 0x003d, 0x002b, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x14
+    0x0071, 0x0051, 0x0077, 0x0057, 0x0065, 0x0045, 0x0072, 0x0052,  // 0x18
+    0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049,  // 0x1c
+    0x006f, 0x004f, 0x0070, 0x0050, 0x005b, 0x007b, 0x005d, 0x007d,  // 0x20
+    0xffff, 0xffff, 0xffff, 0x0061, 0x0061, 0x0041, 0x0073, 0x0053,  // 0x24
+    0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048,  // 0x28
+    0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x003b, 0x003a,  // 0x2c
+    0x0027, 0x0022, 0x0060, 0x007e, 0xffff, 0x005c, 0x005c, 0x007c,  // 0x30
+    0x007a, 0x005a, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056,  // 0x34
+    0x0062, 0x0042, 0x006e, 0x004e, 0x006d, 0x004d, 0x002c, 0x003c,  // 0x38
+    0x002e, 0x003e, 0x002f, 0x003f, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x3c
+    0xffff, 0xffff, 0x0020, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x40
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x44
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x48
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x4c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x50
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x54
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x58
+    0xffff, 0xffff, 0x003c, 0x003e, 0x003c, 0x003e, 0xffff, 0xffff,  // 0x5c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x60
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x64
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x68
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x6c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x70
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x74
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x78
+    0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff, 0xffff, 0xffff,  // 0x7c
+}};
+
+const MockGroupLayoutData kLayoutRussian0{
+    // +0x0  Shift   +0x1    Shift   +0x2    Shift   +0x3    Shift
+    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x00
+    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x04
+    0x0000, 0xffff, 0xffff, 0x0031, 0x0031, 0x0021, 0x0032, 0x0040,  // 0x08
+    0x0033, 0x0023, 0x0034, 0x0024, 0x0035, 0x0025, 0x0036, 0x005e,  // 0x0c
+    0x0037, 0x0026, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029,  // 0x10
+    0x002d, 0x005f, 0x003d, 0x002b, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x14
+    0x0071, 0x0051, 0x0077, 0x0057, 0x0065, 0x0045, 0x0072, 0x0052,  // 0x18
+    0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049,  // 0x1c
+    0x006f, 0x004f, 0x0070, 0x0050, 0x005b, 0x007b, 0x005d, 0x007d,  // 0x20
+    0xffff, 0xffff, 0xffff, 0x0061, 0x0061, 0x0041, 0x0073, 0x0053,  // 0x24
+    0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048,  // 0x28
+    0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x003b, 0x003a,  // 0x2c
+    0x0027, 0x0022, 0x0060, 0x007e, 0xffff, 0x005c, 0x005c, 0x007c,  // 0x30
+    0x007a, 0x005a, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056,  // 0x34
+    0x0062, 0x0042, 0x006e, 0x004e, 0x006d, 0x004d, 0x002c, 0x003c,  // 0x38
+    0x002e, 0x003e, 0x002f, 0x003f, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x3c
+    0xffff, 0xffff, 0x0020, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x40
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x44
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x48
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x4c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x50
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x54
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x58
+    0xffff, 0xffff, 0x0000, 0xffff, 0x003c, 0x003e, 0xffff, 0xffff,  // 0x5c
+    0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x60
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff,  // 0x64
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x68
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x6c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x70
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x74
+    0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x78
+    0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff, 0xffff, 0xffff,  // 0x7c
+};
+
+const MockGroupLayoutData kLayoutRussian2{{
+    // +0x0  Shift   +0x1    Shift   +0x2    Shift   +0x3    Shift
+    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x00
+    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x04
+    0xffff, 0x0031, 0x0021, 0x0000, 0x0031, 0x0021, 0x0032, 0x0022,  // 0x08
+    0x0033, 0x06b0, 0x0034, 0x003b, 0x0035, 0x0025, 0x0036, 0x003a,  // 0x0c
+    0x0037, 0x003f, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029,  // 0x10
+    0x002d, 0x005f, 0x003d, 0x002b, 0x0071, 0x0051, 0x0000, 0x0000,  // 0x14
+    0x06ca, 0x06ea, 0x06c3, 0x06e3, 0x06d5, 0x06f5, 0x06cb, 0x06eb,  // 0x18
+    0x06c5, 0x06e5, 0x06ce, 0x06ee, 0x06c7, 0x06e7, 0x06db, 0x06fb,  // 0x1c
+    0x06dd, 0x06fd, 0x06da, 0x06fa, 0x06c8, 0x06e8, 0x06df, 0x06ff,  // 0x20
+    0x0061, 0x0041, 0x0041, 0x0000, 0x06c6, 0x06e6, 0x06d9, 0x06f9,  // 0x24
+    0x06d7, 0x06f7, 0x06c1, 0x06e1, 0x06d0, 0x06f0, 0x06d2, 0x06f2,  // 0x28
+    0x06cf, 0x06ef, 0x06cc, 0x06ec, 0x06c4, 0x06e4, 0x06d6, 0x06f6,  // 0x2c
+    0x06dc, 0x06fc, 0x06a3, 0x06b3, 0x007c, 0x0000, 0x005c, 0x002f,  // 0x30
+    0x06d1, 0x06f1, 0x06de, 0x06fe, 0x06d3, 0x06f3, 0x06cd, 0x06ed,  // 0x34
+    0x06c9, 0x06e9, 0x06d4, 0x06f4, 0x06d8, 0x06f8, 0x06c2, 0x06e2,  // 0x38
+    0x06c0, 0x06e0, 0x002e, 0x002c, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x3c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x40
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x44
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x48
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x4c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x50
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x54
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x58
+    0xffff, 0xffff, 0x003c, 0x003e, 0x002f, 0x007c, 0xffff, 0xffff,  // 0x5c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x60
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x64
+    0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0x0000,  // 0x68
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x6c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x70
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x74
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x00b1,  // 0x78
+    0x00b1, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x7c
+}};
+
+const MockGroupLayoutData kLayoutFrench0 = {
+    // +0x0  Shift   +0x1    Shift   +0x2    Shift   +0x3    Shift
+    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x00
+    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x04
+    0x0000, 0xffff, 0xffff, 0x0031, 0x0031, 0x0021, 0x0032, 0x0040,  // 0x08
+    0x0033, 0x0023, 0x0034, 0x0024, 0x0035, 0x0025, 0x0036, 0x005e,  // 0x0c
+    0x0037, 0x0026, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029,  // 0x10
+    0x002d, 0x005f, 0x003d, 0x002b, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x14
+    0x0071, 0x0051, 0x0077, 0x0057, 0x0065, 0x0045, 0x0072, 0x0052,  // 0x18
+    0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049,  // 0x1c
+    0x006f, 0x004f, 0x0070, 0x0050, 0x005b, 0x007b, 0x005d, 0x007d,  // 0x20
+    0xffff, 0xffff, 0xffff, 0x0061, 0x0061, 0x0041, 0x0073, 0x0053,  // 0x24
+    0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048,  // 0x28
+    0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x003b, 0x003a,  // 0x2c
+    0x0027, 0x0022, 0x0060, 0x007e, 0xffff, 0x005c, 0x005c, 0x007c,  // 0x30
+    0x007a, 0x005a, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056,  // 0x34
+    0x0062, 0x0042, 0x006e, 0x004e, 0x006d, 0x004d, 0x002c, 0x003c,  // 0x38
+    0x002e, 0x003e, 0x002f, 0x003f, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x3c
+    0xffff, 0xffff, 0x0020, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x40
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x44
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x48
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x4c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x50
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x54
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x58
+    0xffff, 0xffff, 0x0000, 0xffff, 0x003c, 0x003e, 0xffff, 0xffff,  // 0x5c
+    0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x60
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff,  // 0x64
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x68
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x6c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x70
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x74
+    0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x78
+    0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff, 0xffff, 0xffff,  // 0x7c
+};
+
+const MockGroupLayoutData kLayoutFrench3 = {
+    // +0x0  Shift   +0x1    Shift   +0x2    Shift   +0x3    Shift
+    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x00
+    0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff,  // 0x04
+    0x0000, 0xffff, 0x0000, 0x0000, 0x0026, 0x0031, 0x00e9, 0x0032,  // 0x08
+    0x0022, 0x0033, 0x0027, 0x0034, 0x0028, 0x0035, 0x002d, 0x0036,  // 0x0c
+    0x00e8, 0x0037, 0x005f, 0x0038, 0x00e7, 0x0039, 0x00e0, 0x0030,  // 0x10
+    0x0029, 0x00b0, 0x003d, 0x002b, 0x0000, 0x0000, 0x0061, 0x0041,  // 0x14
+    0x0061, 0x0041, 0x007a, 0x005a, 0x0065, 0x0045, 0x0072, 0x0052,  // 0x18
+    0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049,  // 0x1c
+    0x006f, 0x004f, 0x0070, 0x0050, 0xffff, 0xffff, 0x0024, 0x00a3,  // 0x20
+    0x0041, 0x0000, 0x0000, 0x0000, 0x0071, 0x0051, 0x0073, 0x0053,  // 0x24
+    0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048,  // 0x28
+    0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x006d, 0x004d,  // 0x2c
+    0x00f9, 0x0025, 0x00b2, 0x007e, 0x0000, 0x0000, 0x002a, 0x00b5,  // 0x30
+    0x0077, 0x0057, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056,  // 0x34
+    0x0062, 0x0042, 0x006e, 0x004e, 0x002c, 0x003f, 0x003b, 0x002e,  // 0x38
+    0x003a, 0x002f, 0x0021, 0x00a7, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x3c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x40
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x44
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x48
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x4c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x50
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x54
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x58
+    0xffff, 0x003c, 0x0000, 0xffff, 0x003c, 0x003e, 0xffff, 0xffff,  // 0x5c
+    0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x60
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff,  // 0x64
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x68
+    0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x6c
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x70
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x74
+    0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff,  // 0x78
+    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,  // 0x7c
+};
+
+const MockLayoutData kLayoutUs{&kLayoutUs0};
+const MockLayoutData kLayoutRussian{&kLayoutRussian0, nullptr,
+                                    &kLayoutRussian2};
+const MockLayoutData kLayoutFrench{&kLayoutFrench0, nullptr, nullptr,
+                                   &kLayoutFrench3};
+
+}  // namespace
diff --git a/shell/platform/linux/fl_keyboard_view_delegate.cc b/shell/platform/linux/fl_keyboard_view_delegate.cc
index e1301c1..771e428 100644
--- a/shell/platform/linux/fl_keyboard_view_delegate.cc
+++ b/shell/platform/linux/fl_keyboard_view_delegate.cc
@@ -32,13 +32,6 @@
       self, event);
 }
 
-FlBinaryMessenger* fl_keyboard_view_delegate_get_messenger(
-    FlKeyboardViewDelegate* self) {
-  g_return_val_if_fail(FL_IS_KEYBOARD_VIEW_DELEGATE(self), nullptr);
-
-  return FL_KEYBOARD_VIEW_DELEGATE_GET_IFACE(self)->get_messenger(self);
-}
-
 void fl_keyboard_view_delegate_redispatch_event(FlKeyboardViewDelegate* self,
                                                 FlKeyEvent* event) {
   g_return_if_fail(FL_IS_KEYBOARD_VIEW_DELEGATE(self));
diff --git a/shell/platform/linux/fl_keyboard_view_delegate.h b/shell/platform/linux/fl_keyboard_view_delegate.h
index eb1104f..1342390 100644
--- a/shell/platform/linux/fl_keyboard_view_delegate.h
+++ b/shell/platform/linux/fl_keyboard_view_delegate.h
@@ -42,8 +42,6 @@
   gboolean (*text_filter_key_press)(FlKeyboardViewDelegate* delegate,
                                     FlKeyEvent* event);
 
-  FlBinaryMessenger* (*get_messenger)(FlKeyboardViewDelegate* delegate);
-
   void (*redispatch_event)(FlKeyboardViewDelegate* delegate, FlKeyEvent* event);
 
   guint (*lookup_key)(FlKeyboardViewDelegate* view_delegate,
@@ -82,17 +80,6 @@
     FlKeyEvent* event);
 
 /**
- * fl_keyboard_view_delegate_get_messenger:
- *
- * Returns a binary messenger that can be used to send messages to the
- * framework.
- *
- * The ownership of messenger is kept by the view delegate.
- */
-FlBinaryMessenger* fl_keyboard_view_delegate_get_messenger(
-    FlKeyboardViewDelegate* delegate);
-
-/**
  * fl_keyboard_view_delegate_redispatch_event:
  *
  * Handles `FlKeyboardHandler`'s request to insert a GDK event to the system for
diff --git a/shell/platform/linux/fl_view.cc b/shell/platform/linux/fl_view.cc
index 6e44ecb..3828f8e 100644
--- a/shell/platform/linux/fl_view.cc
+++ b/shell/platform/linux/fl_view.cc
@@ -17,6 +17,7 @@
 #include "flutter/shell/platform/linux/fl_framebuffer.h"
 #include "flutter/shell/platform/linux/fl_key_event.h"
 #include "flutter/shell/platform/linux/fl_keyboard_handler.h"
+#include "flutter/shell/platform/linux/fl_keyboard_manager.h"
 #include "flutter/shell/platform/linux/fl_keyboard_view_delegate.h"
 #include "flutter/shell/platform/linux/fl_mouse_cursor_handler.h"
 #include "flutter/shell/platform/linux/fl_platform_handler.h"
@@ -63,6 +64,8 @@
 
   FlScrollingManager* scrolling_manager;
 
+  FlKeyboardManager* keyboard_manager;
+
   // Flutter system channel handlers.
   FlKeyboardHandler* keyboard_handler;
   FlTextInputHandler* text_input_handler;
@@ -151,6 +154,9 @@
   g_clear_object(&self->keyboard_handler);
   self->keyboard_handler =
       fl_keyboard_handler_new(messenger, FL_KEYBOARD_VIEW_DELEGATE(self));
+  g_clear_object(&self->keyboard_manager);
+  self->keyboard_manager =
+      fl_keyboard_manager_new(messenger, FL_KEYBOARD_VIEW_DELEGATE(self));
 }
 
 static void init_scrolling(FlView* self) {
@@ -230,7 +236,7 @@
   gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self));
   fl_scrolling_manager_set_last_mouse_position(
       self->scrolling_manager, event_x * scale_factor, event_y * scale_factor);
-  fl_keyboard_handler_sync_modifier_if_needed(self->keyboard_handler,
+  fl_keyboard_manager_sync_modifier_if_needed(self->keyboard_manager,
                                               event_state, event_time);
   fl_engine_send_mouse_pointer_event(
       self->engine, self->view_id, phase,
@@ -379,11 +385,6 @@
                                                  event);
   };
 
-  iface->get_messenger = [](FlKeyboardViewDelegate* view_delegate) {
-    FlView* self = FL_VIEW(view_delegate);
-    return fl_engine_get_binary_messenger(self->engine);
-  };
-
   iface->redispatch_event = [](FlKeyboardViewDelegate* view_delegate,
                                FlKeyEvent* event) {
     GdkEventType event_type =
@@ -500,7 +501,7 @@
 
   gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self));
 
-  fl_keyboard_handler_sync_modifier_if_needed(self->keyboard_handler,
+  fl_keyboard_manager_sync_modifier_if_needed(self->keyboard_manager,
                                               event_state, event_time);
   fl_engine_send_mouse_pointer_event(
       self->engine, self->view_id, self->button_state != 0 ? kMove : kHover,
@@ -559,7 +560,7 @@
 }
 
 static void keymap_keys_changed_cb(FlView* self) {
-  fl_keyboard_handler_notify_layout_changed(self->keyboard_handler);
+  fl_keyboard_manager_notify_layout_changed(self->keyboard_manager);
 }
 
 static void gesture_rotation_begin_cb(FlView* self) {
@@ -728,11 +729,12 @@
   g_clear_pointer(&self->background_color, gdk_rgba_free);
   g_clear_object(&self->window_state_monitor);
   g_clear_object(&self->scrolling_manager);
-  g_clear_object(&self->keyboard_handler);
+  g_clear_object(&self->keyboard_manager);
   if (self->keymap_keys_changed_cb_id != 0) {
     g_signal_handler_disconnect(self->keymap, self->keymap_keys_changed_cb_id);
     self->keymap_keys_changed_cb_id = 0;
   }
+  g_clear_object(&self->keyboard_handler);
   g_clear_object(&self->mouse_cursor_handler);
   g_clear_object(&self->platform_handler);
   g_clear_object(&self->view_accessible);
@@ -755,8 +757,8 @@
 static gboolean fl_view_key_press_event(GtkWidget* widget, GdkEventKey* event) {
   FlView* self = FL_VIEW(widget);
 
-  return fl_keyboard_handler_handle_event(
-      self->keyboard_handler, fl_key_event_new_from_gdk_event(gdk_event_copy(
+  return fl_keyboard_manager_handle_event(
+      self->keyboard_manager, fl_key_event_new_from_gdk_event(gdk_event_copy(
                                   reinterpret_cast<GdkEvent*>(event))));
 }
 
@@ -764,8 +766,8 @@
 static gboolean fl_view_key_release_event(GtkWidget* widget,
                                           GdkEventKey* event) {
   FlView* self = FL_VIEW(widget);
-  return fl_keyboard_handler_handle_event(
-      self->keyboard_handler, fl_key_event_new_from_gdk_event(gdk_event_copy(
+  return fl_keyboard_manager_handle_event(
+      self->keyboard_manager, fl_key_event_new_from_gdk_event(gdk_event_copy(
                                   reinterpret_cast<GdkEvent*>(event))));
 }
 
@@ -911,5 +913,5 @@
 
 GHashTable* fl_view_get_keyboard_state(FlView* self) {
   g_return_val_if_fail(FL_IS_VIEW(self), nullptr);
-  return fl_keyboard_handler_get_pressed_state(self->keyboard_handler);
+  return fl_keyboard_manager_get_pressed_state(self->keyboard_manager);
 }
diff --git a/sky/packages/sky_engine/LICENSE b/sky/packages/sky_engine/LICENSE
index 1d758e4..82c2017 100644
--- a/sky/packages/sky_engine/LICENSE
+++ b/sky/packages/sky_engine/LICENSE
@@ -32054,7 +32054,7 @@
   This Source Code Form is "Incompatible With Secondary Licenses", as
   defined by the Mozilla Public License, v. 2.0.
 
-You may obtain a copy of this library's Source Code Form from: https://dart.googlesource.com/sdk/+/d916a5f69a486de98316900f19ef0ff46834b03d
+You may obtain a copy of this library's Source Code Form from: https://dart.googlesource.com/sdk/+/993d3069f42e98b2b29e441bc98424065cc255ca
 /third_party/fallback_root_certificates/
 
 --------------------------------------------------------------------------------
diff --git a/testing/impeller_golden_tests_output.txt b/testing/impeller_golden_tests_output.txt
index 8952a74..ef3b466 100644
--- a/testing/impeller_golden_tests_output.txt
+++ b/testing/impeller_golden_tests_output.txt
@@ -545,6 +545,9 @@
 impeller_Play_AiksTest_CoverageOriginShouldBeAccountedForInSubpasses_Metal.png
 impeller_Play_AiksTest_CoverageOriginShouldBeAccountedForInSubpasses_OpenGLES.png
 impeller_Play_AiksTest_CoverageOriginShouldBeAccountedForInSubpasses_Vulkan.png
+impeller_Play_AiksTest_DestructiveBlendColorFilterFloodsClip_Metal.png
+impeller_Play_AiksTest_DestructiveBlendColorFilterFloodsClip_OpenGLES.png
+impeller_Play_AiksTest_DestructiveBlendColorFilterFloodsClip_Vulkan.png
 impeller_Play_AiksTest_DispatcherDoesNotCullPerspectiveTransformedChildDisplayLists_Metal.png
 impeller_Play_AiksTest_DispatcherDoesNotCullPerspectiveTransformedChildDisplayLists_OpenGLES.png
 impeller_Play_AiksTest_DispatcherDoesNotCullPerspectiveTransformedChildDisplayLists_Vulkan.png
diff --git a/tools/yapf.sh b/tools/yapf.sh
index 48d0974..bab0071 100755
--- a/tools/yapf.sh
+++ b/tools/yapf.sh
@@ -36,5 +36,19 @@
 SCRIPT_DIR=$(follow_links "$(dirname -- "${BASH_SOURCE[0]}")")
 SRC_DIR="$(cd "$SCRIPT_DIR/../.."; pwd -P)"
 YAPF_DIR="$(cd "$SRC_DIR/flutter/third_party/yapf"; pwd -P)"
+if command -v python3.10 &> /dev/null; then
+  PYTHON_EXEC="python3.10"
+else
+  python3 -c "
+import sys
+version = sys.version_info
+if (version.major, version.minor) > (3, 10):
+    print(f'Error: python3 version {version.major}.{version.minor} is greater than 3.10.', file=sys.stderr)
+    sys.exit(1)
+else:
+    print(f'Using python3 version {version.major}.{version.minor}.')
+" || exit 1
+  PYTHON_EXEC="python3"
+fi
 
-PYTHONPATH="$YAPF_DIR" python3 "$YAPF_DIR/yapf" "$@"
+PYTHONPATH="$YAPF_DIR" $PYTHON_EXEC "$YAPF_DIR/yapf" "$@"