diff --git a/packages/web_benchmarks/CHANGELOG.md b/packages/web_benchmarks/CHANGELOG.md index 7d4a422d2f48..9acd4517ab2c 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 2b8e170c1f8a..6f64d0489950 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 @@ 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( @@ -58,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 { @@ -116,7 +119,7 @@ Future _runBenchmark(String? benchmarkName) async { } void _fallbackToManual(String error) { - html.document.body!.appendHtml(''' + document.body!.appendHtml('''

$error

@@ -127,33 +130,33 @@ 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); - }); + 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 = '

    ${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()); + // 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 @@ 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' @@ -186,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. @@ -209,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; @@ -220,19 +223,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 +248,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 +286,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 XMLHttpRequest request = await _requestXhr( '/next-benchmark', method: 'POST', mimeType: 'application/json', @@ -298,7 +301,7 @@ class LocalBenchmarkServerClient { } isInManualMode = false; - return request.responseText ?? kManualFallback; + return request.responseText; } void _checkNotManualMode() { @@ -314,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', @@ -324,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', @@ -335,7 +338,7 @@ class LocalBenchmarkServerClient { /// server. Future 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 @@ 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', @@ -366,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', @@ -374,23 +377,27 @@ 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 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 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 7154386f9894..c4b5ca8e3b55 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 de97f0b2162d..e9e1791bd75a 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 58e8429952c5..fda2b308980d 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 9d532b18a01f..9f6b8e534c28 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