From 770326773f680f4b786b8ce1fe6444171c117289 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Wed, 6 Dec 2023 12:21:04 -0800 Subject: [PATCH 1/4] [web_benchmarks] migrate to pkg:web Drop dart:html Enables usage via WebAssembly Fixes https://github.com/flutter/flutter/issues/139577 --- packages/web_benchmarks/CHANGELOG.md | 4 ++ packages/web_benchmarks/lib/client.dart | 67 ++++++++++--------- packages/web_benchmarks/lib/src/recorder.dart | 5 +- packages/web_benchmarks/pubspec.yaml | 3 +- packages/web_benchmarks/testing/README.md | 2 +- .../testing/test_app/.gitignore | 3 - 6 files changed, 47 insertions(+), 37 deletions(-) diff --git a/packages/web_benchmarks/CHANGELOG.md b/packages/web_benchmarks/CHANGELOG.md index 7d4a422d2f4..9acd4517ab2 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 2b8e170c1f8..cb467002679 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' as html; + import 'src/common.dart'; import 'src/recorder.dart'; + export 'src/recorder.dart'; /// Signature for a function that creates a [Recorder]. @@ -127,33 +130,32 @@ void _fallbackToManual(String error) { ${_benchmarks.keys.map((String name) => '
  • ').join('\n')} - ''', - 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); - }); + button.addEventListener( + 'click', + (JSAny? arg) { + final html.Element? manualPanel = + html.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 html.HTMLBodyElement body = html.document.body! as html.HTMLBodyElement; - body.innerHtml = '

    ${profile.name}

    '; + body.innerHTML = '

    ${profile.name}

    '; profile.scoreData.forEach((String scoreKey, Timeseries timeseries) { body.appendHtml('

    $scoreKey

    '); body.appendHtml('
    ${timeseries.computeStats()}
    '); - body.append(TimeseriesVisualization(timeseries).render()); + body.append(TimeseriesVisualization(timeseries).render() as JSObject); }); } @@ -163,7 +165,7 @@ class TimeseriesVisualization { TimeseriesVisualization(this._timeseries) { _stats = _timeseries.computeStats(); _canvas = html.CanvasElement(); - _screenWidth = html.window.screen!.width!; + _screenWidth = html.window.screen.width; _canvas.width = _screenWidth; _canvas.height = (_kCanvasHeight * html.window.devicePixelRatio).round(); _canvas.style @@ -220,19 +222,19 @@ class TimeseriesVisualization { 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 +247,12 @@ class TimeseriesVisualization { _normalized(_stats.average)); // Draw a horizontal dashed line corresponding to the outlier cut off. - _ctx.setLineDash([5, 5]); + _ctx.setLineDash([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 +285,7 @@ class LocalBenchmarkServerClient { /// Returns [kManualFallback] if local server is not available (uses 404 as a /// signal). Future requestNextBenchmark() async { - final html.HttpRequest request = await _requestXhr( + final html.XMLHttpRequest request = await _requestXhr( '/next-benchmark', method: 'POST', mimeType: 'application/json', @@ -298,7 +300,7 @@ class LocalBenchmarkServerClient { } isInManualMode = false; - return request.responseText ?? kManualFallback; + return request.responseText; } void _checkNotManualMode() { @@ -335,7 +337,7 @@ class LocalBenchmarkServerClient { /// server. Future sendProfileData(Profile profile) async { _checkNotManualMode(); - final html.HttpRequest request = await html.HttpRequest.request( + final html.XMLHttpRequest request = await _requestXhr( '/profile-data', method: 'POST', mimeType: 'application/json', @@ -376,21 +378,26 @@ class LocalBenchmarkServerClient { /// This is the same as calling [html.HttpRequest.request] but it doesn't /// crash on 404, which we use to detect `flutter run`. - Future _requestXhr( + Future _requestXhr( String url, { required String method, required String mimeType, - required dynamic sendData, + required String sendData, }) { - final Completer completer = Completer(); - final html.HttpRequest xhr = html.HttpRequest(); - xhr.open(method, url, async: true); + final Completer completer = + Completer(); + final html.XMLHttpRequest xhr = html.XMLHttpRequest(); + xhr.open(method, url, true); xhr.overrideMimeType(mimeType); xhr.onLoad.listen((html.ProgressEvent e) { completer.complete(xhr); }); xhr.onError.listen(completer.completeError); - xhr.send(sendData); + xhr.send(sendData.toJS); return completer.future; } } + +extension on html.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 7154386f989..c4b5ca8e3b5 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/scheduler.dart'; 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 @@ void endMeasureFrame() { 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 de97f0b2162..e9e1791bd75 100644 --- a/packages/web_benchmarks/pubspec.yaml +++ b/packages/web_benchmarks/pubspec.yaml @@ -2,7 +2,7 @@ name: web_benchmarks 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 @@ dependencies: 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 58e8429952c..fda2b308980 100644 --- a/packages/web_benchmarks/testing/README.md +++ b/packages/web_benchmarks/testing/README.md @@ -11,7 +11,7 @@ do the following: * 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 9d532b18a01..9f6b8e534c2 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 From e92b7f1fe8ff202c5a06549a0cf5d7ec6e4736c9 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Wed, 6 Dec 2023 12:42:05 -0800 Subject: [PATCH 2/4] fixes for pkg:web 0.3.0 --- packages/web_benchmarks/lib/client.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/web_benchmarks/lib/client.dart b/packages/web_benchmarks/lib/client.dart index cb467002679..bbe502c8dea 100644 --- a/packages/web_benchmarks/lib/client.dart +++ b/packages/web_benchmarks/lib/client.dart @@ -155,7 +155,9 @@ void _printResultsToScreen(Profile profile) { profile.scoreData.forEach((String scoreKey, Timeseries timeseries) { body.appendHtml('

    $scoreKey

    '); body.appendHtml('
    ${timeseries.computeStats()}
    '); - body.append(TimeseriesVisualization(timeseries).render() as JSObject); + // TODO(kevmoo): remove `NodeClue` cast when we no longer need to support + // pkg:web 0.3.0 + html.NodeGlue(body).append(TimeseriesVisualization(timeseries).render()); }); } From 3684f60070d6cc7fc811bbb687e58c3c95e67731 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Wed, 6 Dec 2023 12:43:53 -0800 Subject: [PATCH 3/4] review nit --- packages/web_benchmarks/lib/client.dart | 58 ++++++++++++------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/packages/web_benchmarks/lib/client.dart b/packages/web_benchmarks/lib/client.dart index bbe502c8dea..4b7a8a9b777 100644 --- a/packages/web_benchmarks/lib/client.dart +++ b/packages/web_benchmarks/lib/client.dart @@ -7,7 +7,7 @@ import 'dart:convert' show json; import 'dart:js_interop'; import 'dart:math' as math; -import 'package:web/helpers.dart' as html; +import 'package:web/helpers.dart'; import 'src/common.dart'; import 'src/recorder.dart'; @@ -50,7 +50,7 @@ Future runBenchmarks( 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( @@ -61,7 +61,7 @@ Future runBenchmarks( ); // Reloading the window will trigger the next benchmark to run. - html.window.location.replace(newUri.toString()); + window.location.replace(newUri.toString()); } Future _runBenchmark(String? benchmarkName) async { @@ -119,7 +119,7 @@ Future _runBenchmark(String? benchmarkName) async { } void _fallbackToManual(String error) { - html.document.body!.appendHtml(''' + document.body!.appendHtml('''

    $error

    @@ -134,12 +134,11 @@ void _fallbackToManual(String error) { for (final String benchmarkName in _benchmarks.keys) { // Find the button elements added above. - final html.Element button = html.document.querySelector('#$benchmarkName')!; + final Element button = document.querySelector('#$benchmarkName')!; button.addEventListener( 'click', (JSAny? arg) { - final html.Element? manualPanel = - html.document.querySelector('#manual-panel'); + final Element? manualPanel = document.querySelector('#manual-panel'); manualPanel?.remove(); _runBenchmark(benchmarkName); }.toJS); @@ -148,7 +147,7 @@ void _fallbackToManual(String error) { /// Visualizes results on the Web page for manual inspection. void _printResultsToScreen(Profile profile) { - final html.HTMLBodyElement body = html.document.body! as html.HTMLBodyElement; + final HTMLBodyElement body = document.body! as HTMLBodyElement; body.innerHTML = '

    ${profile.name}

    '; @@ -157,7 +156,7 @@ void _printResultsToScreen(Profile profile) { body.appendHtml('
    ${timeseries.computeStats()}
    '); // TODO(kevmoo): remove `NodeClue` cast when we no longer need to support // pkg:web 0.3.0 - html.NodeGlue(body).append(TimeseriesVisualization(timeseries).render()); + NodeGlue(body).append(TimeseriesVisualization(timeseries).render()); }); } @@ -166,10 +165,10 @@ class TimeseriesVisualization { /// 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' @@ -190,8 +189,8 @@ class TimeseriesVisualization { 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. @@ -213,9 +212,9 @@ class TimeseriesVisualization { } /// Renders the timeseries into a `` 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; @@ -287,7 +286,7 @@ class LocalBenchmarkServerClient { /// Returns [kManualFallback] if local server is not available (uses 404 as a /// signal). Future requestNextBenchmark() async { - final html.XMLHttpRequest request = await _requestXhr( + final XMLHttpRequest request = await _requestXhr( '/next-benchmark', method: 'POST', mimeType: 'application/json', @@ -318,7 +317,7 @@ class LocalBenchmarkServerClient { /// DevTools Protocol. Future startPerformanceTracing(String? benchmarkName) async { _checkNotManualMode(); - await html.HttpRequest.request( + await HttpRequest.request( '/start-performance-tracing?label=$benchmarkName', method: 'POST', mimeType: 'application/json', @@ -328,7 +327,7 @@ class LocalBenchmarkServerClient { /// Stops the performance tracing session started by [startPerformanceTracing]. Future stopPerformanceTracing() async { _checkNotManualMode(); - await html.HttpRequest.request( + await HttpRequest.request( '/stop-performance-tracing', method: 'POST', mimeType: 'application/json', @@ -339,7 +338,7 @@ class LocalBenchmarkServerClient { /// server. Future sendProfileData(Profile profile) async { _checkNotManualMode(); - final html.XMLHttpRequest request = await _requestXhr( + final XMLHttpRequest request = await _requestXhr( '/profile-data', method: 'POST', mimeType: 'application/json', @@ -356,7 +355,7 @@ class LocalBenchmarkServerClient { /// The server will halt the devicelab task and log the error. Future reportError(dynamic error, StackTrace stackTrace) async { _checkNotManualMode(); - await html.HttpRequest.request( + await HttpRequest.request( '/on-error', method: 'POST', mimeType: 'application/json', @@ -370,7 +369,7 @@ class LocalBenchmarkServerClient { /// Reports a message about the demo to the benchmark server. Future printToConsole(String report) async { _checkNotManualMode(); - await html.HttpRequest.request( + await HttpRequest.request( '/print-to-console', method: 'POST', mimeType: 'text/plain', @@ -378,20 +377,19 @@ class LocalBenchmarkServerClient { ); } - /// 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 _requestXhr( + Future _requestXhr( String url, { required String method, required String mimeType, required String sendData, }) { - final Completer completer = - Completer(); - final html.XMLHttpRequest xhr = html.XMLHttpRequest(); + final Completer completer = Completer(); + 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); @@ -400,6 +398,6 @@ class LocalBenchmarkServerClient { } } -extension on html.HTMLElement { +extension on HTMLElement { void appendHtml(String input) => insertAdjacentHTML('beforeend', input); } From 8e25e0882667136399be635d249e20881ac8d439 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Wed, 6 Dec 2023 13:46:57 -0800 Subject: [PATCH 4/4] Update packages/web_benchmarks/lib/client.dart --- packages/web_benchmarks/lib/client.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web_benchmarks/lib/client.dart b/packages/web_benchmarks/lib/client.dart index 4b7a8a9b777..6f64d048995 100644 --- a/packages/web_benchmarks/lib/client.dart +++ b/packages/web_benchmarks/lib/client.dart @@ -154,7 +154,7 @@ void _printResultsToScreen(Profile profile) { profile.scoreData.forEach((String scoreKey, Timeseries timeseries) { body.appendHtml('

    $scoreKey

    '); body.appendHtml('
    ${timeseries.computeStats()}
    '); - // TODO(kevmoo): remove `NodeClue` cast when we no longer need to support + // TODO(kevmoo): remove `NodeGlue` cast when we no longer need to support // pkg:web 0.3.0 NodeGlue(body).append(TimeseriesVisualization(timeseries).render()); });