Skwasm benchmarks. (#129681)

This enables benchmarks for the Skwasm renderer, compiled with
dart2wasm.

Platform views aren't supported in Skwasm yet, so we are skipping those
benchmarks for now.
diff --git a/.ci.yaml b/.ci.yaml
index e5ebac4..47f99de 100644
--- a/.ci.yaml
+++ b/.ci.yaml
@@ -1093,6 +1093,7 @@
 
   - name: Linux web_benchmarks_html
     recipe: devicelab/devicelab_drone
+    presubmit: false
     timeout: 60
     properties:
       dependencies: >-
@@ -1108,6 +1109,25 @@
       - bin/**
       - .ci.yaml
 
+  - name: Linux web_benchmarks_skwasm
+    bringup: true
+    recipe: devicelab/devicelab_drone
+    presubmit: false
+    timeout: 60
+    properties:
+      dependencies: >-
+        [
+          {"dependency": "android_sdk", "version": "version:33v6"},
+          {"dependency": "chrome_and_driver", "version": "version:114.0"}
+        ]
+      tags: >
+        ["devicelab"]
+      task_name: web_benchmarks_skwasm
+    runIf:
+      - dev/**
+      - bin/**
+      - .ci.yaml
+
   - name: Linux web_long_running_tests_1_5
     recipe: flutter/flutter_drone
     timeout: 60
diff --git a/TESTOWNERS b/TESTOWNERS
index 65b0ead..5be1295 100644
--- a/TESTOWNERS
+++ b/TESTOWNERS
@@ -264,6 +264,7 @@
 /dev/devicelab/bin/tasks/technical_debt__cost.dart @HansMuller @flutter/framework
 /dev/devicelab/bin/tasks/web_benchmarks_canvaskit.dart @yjbanov @flutter/web
 /dev/devicelab/bin/tasks/web_benchmarks_html.dart @yjbanov @flutter/web
+/dev/devicelab/bin/tasks/web_benchmarks_skwasm.dart @jacksongardner @flutter/web
 /dev/devicelab/bin/tasks/windows_home_scroll_perf__timeline_summary.dart @jonahwilliams @flutter/engine
 /dev/devicelab/bin/tasks/windows_startup_test.dart @loic-sharma @flutter/desktop
 
diff --git a/dev/benchmarks/macrobenchmarks/lib/src/web/bench_mouse_region_grid_scroll.dart b/dev/benchmarks/macrobenchmarks/lib/src/web/bench_mouse_region_grid_scroll.dart
index 6eabda4..7fbb80e 100644
--- a/dev/benchmarks/macrobenchmarks/lib/src/web/bench_mouse_region_grid_scroll.dart
+++ b/dev/benchmarks/macrobenchmarks/lib/src/web/bench_mouse_region_grid_scroll.dart
@@ -130,10 +130,10 @@
     final int frameDurationMs = fullFrameDuration.inMilliseconds;
 
     final int fullFrames = duration.inMilliseconds ~/ frameDurationMs;
-    final Offset fullFrameOffset = offset * ((frameDurationMs as double) / durationMs);
+    final Offset fullFrameOffset = offset * (frameDurationMs.toDouble() / durationMs);
 
     final Duration finalFrameDuration = duration - fullFrameDuration * fullFrames;
-    final Offset finalFrameOffset = offset - fullFrameOffset * (fullFrames as double);
+    final Offset finalFrameOffset = offset - fullFrameOffset * fullFrames.toDouble();
 
     await gesture.down(start, timeStamp: currentTime);
 
diff --git a/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart b/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart
index 544f5bb..e4e18d5 100644
--- a/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart
+++ b/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart
@@ -36,6 +36,7 @@
 typedef RecorderFactory = Recorder Function();
 
 const bool isCanvasKit = bool.fromEnvironment('FLUTTER_WEB_USE_SKIA');
+const bool isSkwasm = bool.fromEnvironment('FLUTTER_WEB_USE_SKWASM');
 
 /// List of all benchmarks that run in the devicelab.
 ///
@@ -62,14 +63,18 @@
   BenchMouseRegionGridHover.benchmarkName: () => BenchMouseRegionGridHover(),
   BenchMouseRegionMixedGridHover.benchmarkName: () => BenchMouseRegionMixedGridHover(),
   BenchWrapBoxScroll.benchmarkName: () => BenchWrapBoxScroll(),
-  BenchPlatformViewInfiniteScroll.benchmarkName: () => BenchPlatformViewInfiniteScroll.forward(),
-  BenchPlatformViewInfiniteScroll.benchmarkNameBackward: () => BenchPlatformViewInfiniteScroll.backward(),
+  if (!isSkwasm) ...<String, RecorderFactory>{
+    // Platform views are not yet supported with Skwasm.
+    // https://github.com/flutter/flutter/issues/126346
+    BenchPlatformViewInfiniteScroll.benchmarkName: () => BenchPlatformViewInfiniteScroll.forward(),
+    BenchPlatformViewInfiniteScroll.benchmarkNameBackward: () => BenchPlatformViewInfiniteScroll.backward(),
+  },
   BenchMaterial3Components.benchmarkName: () => BenchMaterial3Components(),
   BenchMaterial3Semantics.benchmarkName: () => BenchMaterial3Semantics(),
   BenchMaterial3ScrollSemantics.benchmarkName: () => BenchMaterial3ScrollSemantics(),
 
-  // CanvasKit-only benchmarks
-  if (isCanvasKit) ...<String, RecorderFactory>{
+  // Skia-only benchmarks
+  if (isCanvasKit || isSkwasm) ...<String, RecorderFactory>{
     BenchTextLayout.canvasKitBenchmarkName: () => BenchTextLayout.canvasKit(),
     BenchBuildColorsGrid.canvasKitBenchmarkName: () => BenchBuildColorsGrid.canvasKit(),
     BenchTextCachedLayout.canvasKitBenchmarkName: () => BenchTextCachedLayout.canvasKit(),
@@ -81,7 +86,7 @@
   },
 
   // HTML-only benchmarks
-  if (!isCanvasKit) ...<String, RecorderFactory>{
+  if (!isCanvasKit && !isSkwasm) ...<String, RecorderFactory>{
     BenchTextLayout.canvasBenchmarkName: () => BenchTextLayout.canvas(),
     BenchTextCachedLayout.canvasBenchmarkName: () => BenchTextCachedLayout.canvas(),
     BenchBuildColorsGrid.canvasBenchmarkName: () => BenchBuildColorsGrid.canvas(),
diff --git a/dev/devicelab/bin/tasks/web_benchmarks_canvaskit.dart b/dev/devicelab/bin/tasks/web_benchmarks_canvaskit.dart
index 313f7e2..1aabf51 100644
--- a/dev/devicelab/bin/tasks/web_benchmarks_canvaskit.dart
+++ b/dev/devicelab/bin/tasks/web_benchmarks_canvaskit.dart
@@ -8,6 +8,9 @@
 /// Runs all Web benchmarks using the CanvasKit rendering backend.
 Future<void> main() async {
   await task(() async {
-    return runWebBenchmark(useCanvasKit: true);
+    return runWebBenchmark((
+      webRenderer: 'canvaskit',
+      useWasm: false
+    ));
   });
 }
diff --git a/dev/devicelab/bin/tasks/web_benchmarks_html.dart b/dev/devicelab/bin/tasks/web_benchmarks_html.dart
index 8cca0d6..b7719cc 100644
--- a/dev/devicelab/bin/tasks/web_benchmarks_html.dart
+++ b/dev/devicelab/bin/tasks/web_benchmarks_html.dart
@@ -8,6 +8,9 @@
 /// Runs all Web benchmarks using the HTML rendering backend.
 Future<void> main() async {
   await task(() async {
-    return runWebBenchmark(useCanvasKit: false);
+    return runWebBenchmark((
+      webRenderer: 'html',
+      useWasm: false
+    ));
   });
 }
diff --git a/dev/devicelab/bin/tasks/web_benchmarks_skwasm.dart b/dev/devicelab/bin/tasks/web_benchmarks_skwasm.dart
new file mode 100644
index 0000000..256634a
--- /dev/null
+++ b/dev/devicelab/bin/tasks/web_benchmarks_skwasm.dart
@@ -0,0 +1,16 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter_devicelab/framework/framework.dart';
+import 'package:flutter_devicelab/tasks/web_benchmarks.dart';
+
+/// Runs all Web benchmarks using the Skwasm rendering backend.
+Future<void> main() async {
+  await task(() async {
+    return runWebBenchmark((
+      webRenderer: 'skwasm',
+      useWasm: true
+    ));
+  });
+}
diff --git a/dev/devicelab/lib/framework/browser.dart b/dev/devicelab/lib/framework/browser.dart
index 3180bb7..9d4581f 100644
--- a/dev/devicelab/lib/framework/browser.dart
+++ b/dev/devicelab/lib/framework/browser.dart
@@ -25,6 +25,7 @@
     this.windowHeight = 1024,
     this.headless,
     this.debugPort,
+    this.enableWasmGC = false,
   });
 
   /// If not null passed as `--user-data-dir`.
@@ -53,6 +54,9 @@
   /// mode without a debug port, Chrome quits immediately. For most tests it is
   /// typical to set [headless] to true and set a non-null debug port.
   final int? debugPort;
+
+  /// Whether to enable experimental WasmGC flags
+  final bool enableWasmGC;
 }
 
 /// A function called when the Chrome process encounters an error.
@@ -104,6 +108,8 @@
       '--no-default-browser-check',
       '--disable-default-apps',
       '--disable-translate',
+      if (options.enableWasmGC)
+        '--js-flags=--experimental-wasm-gc',
     ];
 
     final io.Process chromeProcess = await _spawnChromiumProcess(
diff --git a/dev/devicelab/lib/tasks/web_benchmarks.dart b/dev/devicelab/lib/tasks/web_benchmarks.dart
index de98f4f..051133c 100644
--- a/dev/devicelab/lib/tasks/web_benchmarks.dart
+++ b/dev/devicelab/lib/tasks/web_benchmarks.dart
@@ -20,7 +20,12 @@
 const int benchmarkServerPort = 9999;
 const int chromeDebugPort = 10000;
 
-Future<TaskResult> runWebBenchmark({ required bool useCanvasKit }) async {
+typedef WebBenchmarkOptions = ({
+  String webRenderer,
+  bool useWasm,
+});
+
+Future<TaskResult> runWebBenchmark(WebBenchmarkOptions benchmarkOptions) async {
   // Reduce logging level. Otherwise, package:webkit_inspection_protocol is way too spammy.
   Logger.root.level = Level.INFO;
   final String macrobenchmarksDirectory = path.join(flutterDirectory.path, 'dev', 'benchmarks', 'macrobenchmarks');
@@ -28,8 +33,12 @@
     await flutter('clean');
     await evalFlutter('build', options: <String>[
       'web',
+      if (benchmarkOptions.useWasm) ...<String>[
+        '--wasm',
+        '--wasm-opt=debug',
+      ],
       '--dart-define=FLUTTER_WEB_ENABLE_PROFILING=true',
-      '--web-renderer=${useCanvasKit ? 'canvaskit' : 'html'}',
+      '--web-renderer=${benchmarkOptions.webRenderer}',
       '--profile',
       '--no-web-resources-cdn',
       '-t',
@@ -115,7 +124,7 @@
         return Response.internalServerError(body: '$error');
       }
     }).add(createBuildDirectoryHandler(
-      path.join(macrobenchmarksDirectory, 'build', 'web'),
+      path.join(macrobenchmarksDirectory, 'build', benchmarkOptions.useWasm ? 'web_wasm' : 'web'),
     ));
 
     server = await io.HttpServer.bind('localhost', benchmarkServerPort);
@@ -137,6 +146,7 @@
         userDataDirectory: userDataDir,
         headless: isUncalibratedSmokeTest,
         debugPort: chromeDebugPort,
+        enableWasmGC: benchmarkOptions.useWasm,
       );
 
       print('Launching Chrome.');
@@ -149,7 +159,6 @@
       );
 
       print('Waiting for the benchmark to report benchmark profile.');
-      final String backend = useCanvasKit ? 'canvaskit' : 'html';
       final Map<String, dynamic> taskResult = <String, dynamic>{};
       final List<String> benchmarkScoreKeys = <String>[];
       final List<Map<String, dynamic>> profiles = await profileData.future;
@@ -161,7 +170,7 @@
           throw 'Benchmark name is empty';
         }
 
-        final String namespace = '$benchmarkName.$backend';
+        final String namespace = '$benchmarkName.${benchmarkOptions.webRenderer}';
         final List<String> scoreKeys = List<String>.from(profile['scoreKeys'] as List<dynamic>);
         if (scoreKeys.isEmpty) {
           throw 'No score keys in benchmark "$benchmarkName"';