[web_benchmarks] migrate to pkg:web (#5592)
Drop dart:html
Enables usage via WebAssembly
Fixes https://github.com/flutter/flutter/issues/139577
diff --git a/packages/web_benchmarks/CHANGELOG.md b/packages/web_benchmarks/CHANGELOG.md
index 7d4a422..9acd451 100644
--- a/packages/web_benchmarks/CHANGELOG.md
+++ b/packages/web_benchmarks/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.1.0+11
+
+* Migrates benchmark recorder from `dart:html` to `package:web` to support WebAssembly.
+
## 0.1.0+10
* Ensure the benchmark client reloads with the proper `initialPage`.
diff --git a/packages/web_benchmarks/lib/client.dart b/packages/web_benchmarks/lib/client.dart
index 2b8e170..6f64d04 100644
--- a/packages/web_benchmarks/lib/client.dart
+++ b/packages/web_benchmarks/lib/client.dart
@@ -4,11 +4,14 @@
import 'dart:async';
import 'dart:convert' show json;
-import 'dart:html' as html;
+import 'dart:js_interop';
import 'dart:math' as math;
+import 'package:web/helpers.dart';
+
import 'src/common.dart';
import 'src/recorder.dart';
+
export 'src/recorder.dart';
/// Signature for a function that creates a [Recorder].
@@ -47,7 +50,7 @@
await _runBenchmark(nextBenchmark);
- final Uri currentUri = Uri.parse(html.window.location.href);
+ final Uri currentUri = Uri.parse(window.location.href);
// Create a new URI with the current 'page' value set to [initialPage] to
// ensure the benchmark app is reloaded at the proper location.
final Uri newUri = Uri(
@@ -58,7 +61,7 @@
);
// Reloading the window will trigger the next benchmark to run.
- html.window.location.replace(newUri.toString());
+ window.location.replace(newUri.toString());
}
Future<void> _runBenchmark(String? benchmarkName) async {
@@ -116,7 +119,7 @@
}
void _fallbackToManual(String error) {
- html.document.body!.appendHtml('''
+ document.body!.appendHtml('''
<div id="manual-panel">
<h3>$error</h3>
@@ -127,33 +130,33 @@
${_benchmarks.keys.map((String name) => '<li><button id="$name">$name</button></li>').join('\n')}
</ul>
</div>
- ''',
- validator: html.NodeValidatorBuilder()
- ..allowHtml5()
- ..allowInlineStyles());
+ ''');
for (final String benchmarkName in _benchmarks.keys) {
// Find the button elements added above.
- final html.Element button = html.document.querySelector('#$benchmarkName')!;
- button.addEventListener('click', (_) {
- final html.Element? manualPanel =
- html.document.querySelector('#manual-panel');
- manualPanel?.remove();
- _runBenchmark(benchmarkName);
- });
+ final Element button = document.querySelector('#$benchmarkName')!;
+ button.addEventListener(
+ 'click',
+ (JSAny? arg) {
+ final Element? manualPanel = document.querySelector('#manual-panel');
+ manualPanel?.remove();
+ _runBenchmark(benchmarkName);
+ }.toJS);
}
}
/// Visualizes results on the Web page for manual inspection.
void _printResultsToScreen(Profile profile) {
- final html.BodyElement body = html.document.body!;
+ final HTMLBodyElement body = document.body! as HTMLBodyElement;
- body.innerHtml = '<h2>${profile.name}</h2>';
+ body.innerHTML = '<h2>${profile.name}</h2>';
profile.scoreData.forEach((String scoreKey, Timeseries timeseries) {
body.appendHtml('<h2>$scoreKey</h2>');
body.appendHtml('<pre>${timeseries.computeStats()}</pre>');
- body.append(TimeseriesVisualization(timeseries).render());
+ // TODO(kevmoo): remove `NodeGlue` cast when we no longer need to support
+ // pkg:web 0.3.0
+ NodeGlue(body).append(TimeseriesVisualization(timeseries).render());
});
}
@@ -162,10 +165,10 @@
/// Creates a visualization for a [Timeseries].
TimeseriesVisualization(this._timeseries) {
_stats = _timeseries.computeStats();
- _canvas = html.CanvasElement();
- _screenWidth = html.window.screen!.width!;
+ _canvas = CanvasElement();
+ _screenWidth = window.screen.width;
_canvas.width = _screenWidth;
- _canvas.height = (_kCanvasHeight * html.window.devicePixelRatio).round();
+ _canvas.height = (_kCanvasHeight * window.devicePixelRatio).round();
_canvas.style
..width = '100%'
..height = '${_kCanvasHeight}px'
@@ -186,8 +189,8 @@
final Timeseries _timeseries;
late TimeseriesStats _stats;
- late html.CanvasElement _canvas;
- late html.CanvasRenderingContext2D _ctx;
+ late CanvasElement _canvas;
+ late CanvasRenderingContext2D _ctx;
late int _screenWidth;
// Used to normalize benchmark values to chart height.
@@ -209,9 +212,9 @@
}
/// Renders the timeseries into a `<canvas>` and returns the canvas element.
- html.CanvasElement render() {
- _ctx.translate(0, _kCanvasHeight * html.window.devicePixelRatio);
- _ctx.scale(1, -html.window.devicePixelRatio);
+ CanvasElement render() {
+ _ctx.translate(0, _kCanvasHeight * window.devicePixelRatio);
+ _ctx.scale(1, -window.devicePixelRatio);
final double barWidth = _screenWidth / _stats.samples.length;
double xOffset = 0;
@@ -220,19 +223,19 @@
if (sample.isWarmUpValue) {
// Put gray background behind warm-up samples.
- _ctx.fillStyle = 'rgba(200,200,200,1)';
+ _ctx.fillStyle = 'rgba(200,200,200,1)'.toJS;
_ctx.fillRect(xOffset, 0, barWidth, _normalized(_maxValueChartRange));
}
if (sample.magnitude > _maxValueChartRange) {
// The sample value is so big it doesn't fit on the chart. Paint it purple.
- _ctx.fillStyle = 'rgba(100,50,100,0.8)';
+ _ctx.fillStyle = 'rgba(100,50,100,0.8)'.toJS;
} else if (sample.isOutlier) {
// The sample is an outlier, color it light red.
- _ctx.fillStyle = 'rgba(255,50,50,0.6)';
+ _ctx.fillStyle = 'rgba(255,50,50,0.6)'.toJS;
} else {
// A non-outlier sample, color it light blue.
- _ctx.fillStyle = 'rgba(50,50,255,0.6)';
+ _ctx.fillStyle = 'rgba(50,50,255,0.6)'.toJS;
}
_ctx.fillRect(xOffset, 0, barWidth - 1, _normalized(sample.magnitude));
@@ -245,12 +248,12 @@
_normalized(_stats.average));
// Draw a horizontal dashed line corresponding to the outlier cut off.
- _ctx.setLineDash(<num>[5, 5]);
+ _ctx.setLineDash(<JSNumber>[5.toJS, 5.toJS].toJS);
drawLine(0, _normalized(_stats.outlierCutOff), _screenWidth,
_normalized(_stats.outlierCutOff));
// Draw a light red band that shows the noise (1 stddev in each direction).
- _ctx.fillStyle = 'rgba(255,50,50,0.3)';
+ _ctx.fillStyle = 'rgba(255,50,50,0.3)'.toJS;
_ctx.fillRect(
0,
_normalized(_stats.average * (1 - _stats.noise)),
@@ -283,7 +286,7 @@
/// Returns [kManualFallback] if local server is not available (uses 404 as a
/// signal).
Future<String> requestNextBenchmark() async {
- final html.HttpRequest request = await _requestXhr(
+ final XMLHttpRequest request = await _requestXhr(
'/next-benchmark',
method: 'POST',
mimeType: 'application/json',
@@ -298,7 +301,7 @@
}
isInManualMode = false;
- return request.responseText ?? kManualFallback;
+ return request.responseText;
}
void _checkNotManualMode() {
@@ -314,7 +317,7 @@
/// DevTools Protocol.
Future<void> startPerformanceTracing(String? benchmarkName) async {
_checkNotManualMode();
- await html.HttpRequest.request(
+ await HttpRequest.request(
'/start-performance-tracing?label=$benchmarkName',
method: 'POST',
mimeType: 'application/json',
@@ -324,7 +327,7 @@
/// Stops the performance tracing session started by [startPerformanceTracing].
Future<void> stopPerformanceTracing() async {
_checkNotManualMode();
- await html.HttpRequest.request(
+ await HttpRequest.request(
'/stop-performance-tracing',
method: 'POST',
mimeType: 'application/json',
@@ -335,7 +338,7 @@
/// server.
Future<void> sendProfileData(Profile profile) async {
_checkNotManualMode();
- final html.HttpRequest request = await html.HttpRequest.request(
+ final XMLHttpRequest request = await _requestXhr(
'/profile-data',
method: 'POST',
mimeType: 'application/json',
@@ -352,7 +355,7 @@
/// The server will halt the devicelab task and log the error.
Future<void> reportError(dynamic error, StackTrace stackTrace) async {
_checkNotManualMode();
- await html.HttpRequest.request(
+ await HttpRequest.request(
'/on-error',
method: 'POST',
mimeType: 'application/json',
@@ -366,7 +369,7 @@
/// Reports a message about the demo to the benchmark server.
Future<void> printToConsole(String report) async {
_checkNotManualMode();
- await html.HttpRequest.request(
+ await HttpRequest.request(
'/print-to-console',
method: 'POST',
mimeType: 'text/plain',
@@ -374,23 +377,27 @@
);
}
- /// This is the same as calling [html.HttpRequest.request] but it doesn't
+ /// This is the same as calling [XMLHttpRequest.request] but it doesn't
/// crash on 404, which we use to detect `flutter run`.
- Future<html.HttpRequest> _requestXhr(
+ Future<XMLHttpRequest> _requestXhr(
String url, {
required String method,
required String mimeType,
- required dynamic sendData,
+ required String sendData,
}) {
- final Completer<html.HttpRequest> completer = Completer<html.HttpRequest>();
- final html.HttpRequest xhr = html.HttpRequest();
- xhr.open(method, url, async: true);
+ final Completer<XMLHttpRequest> completer = Completer<XMLHttpRequest>();
+ final XMLHttpRequest xhr = XMLHttpRequest();
+ xhr.open(method, url, true);
xhr.overrideMimeType(mimeType);
- xhr.onLoad.listen((html.ProgressEvent e) {
+ xhr.onLoad.listen((ProgressEvent e) {
completer.complete(xhr);
});
xhr.onError.listen(completer.completeError);
- xhr.send(sendData);
+ xhr.send(sendData.toJS);
return completer.future;
}
}
+
+extension on HTMLElement {
+ void appendHtml(String input) => insertAdjacentHTML('beforeend', input);
+}
diff --git a/packages/web_benchmarks/lib/src/recorder.dart b/packages/web_benchmarks/lib/src/recorder.dart
index 7154386..c4b5ca8 100644
--- a/packages/web_benchmarks/lib/src/recorder.dart
+++ b/packages/web_benchmarks/lib/src/recorder.dart
@@ -3,7 +3,7 @@
// found in the LICENSE file.
import 'dart:async';
-import 'dart:html' as html;
+import 'dart:js_interop';
import 'dart:math' as math;
import 'dart:ui';
import 'dart:ui_web' as ui_web;
@@ -15,6 +15,7 @@
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:meta/meta.dart';
+import 'package:web/helpers.dart' as html;
import 'common.dart';
@@ -1188,7 +1189,7 @@
html.window.performance.mark('measured_frame_end#$_currentFrameNumber');
html.window.performance.measure(
'measured_frame',
- 'measured_frame_start#$_currentFrameNumber',
+ 'measured_frame_start#$_currentFrameNumber'.toJS,
'measured_frame_end#$_currentFrameNumber',
);
diff --git a/packages/web_benchmarks/pubspec.yaml b/packages/web_benchmarks/pubspec.yaml
index de97f0b..e9e1791 100644
--- a/packages/web_benchmarks/pubspec.yaml
+++ b/packages/web_benchmarks/pubspec.yaml
@@ -2,7 +2,7 @@
description: A benchmark harness for performance-testing Flutter apps in Chrome.
repository: https://github.com/flutter/packages/tree/main/packages/web_benchmarks
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+web_benchmarks%22
-version: 0.1.0+10
+version: 0.1.0+11
environment:
sdk: ">=3.2.0 <4.0.0"
@@ -20,6 +20,7 @@
shelf: ^1.2.0
shelf_static: ^1.1.0
test: ^1.19.5
+ web: '>=0.3.0 <0.5.0'
webkit_inspection_protocol: ^1.0.0
topics:
diff --git a/packages/web_benchmarks/testing/README.md b/packages/web_benchmarks/testing/README.md
index 58e8429..fda2b30 100644
--- a/packages/web_benchmarks/testing/README.md
+++ b/packages/web_benchmarks/testing/README.md
@@ -11,7 +11,7 @@
* Fetch dependencies for the `test_app` directory inside `testing`:
```bash
- flutter pub get testing/test_app
+ flutter pub get --directory testing/test_app
```
* Fetch dependencies for the `web_benchmarks` directory:
diff --git a/packages/web_benchmarks/testing/test_app/.gitignore b/packages/web_benchmarks/testing/test_app/.gitignore
index 9d532b1..9f6b8e5 100644
--- a/packages/web_benchmarks/testing/test_app/.gitignore
+++ b/packages/web_benchmarks/testing/test_app/.gitignore
@@ -31,9 +31,6 @@
.pub/
/build/
-# Web related
-lib/generated_plugin_registrant.dart
-
# Symbolication related
app.*.symbols