diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 7cf45a5a796c1..f5ce84eeef4b1 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -2141,6 +2141,7 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/tile_mode.dart + ../../../flutter/LICENS ORIGIN: ../../../flutter/lib/web_ui/lib/ui.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web/asset_manager.dart + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web/benchmarks.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web/initialization.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web/navigation/platform_location.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web/navigation/url_strategy.dart + ../../../flutter/LICENSE @@ -4866,6 +4867,7 @@ FILE: ../../../flutter/lib/web_ui/lib/tile_mode.dart FILE: ../../../flutter/lib/web_ui/lib/ui.dart FILE: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web.dart FILE: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web/asset_manager.dart +FILE: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web/benchmarks.dart FILE: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web/initialization.dart FILE: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web/navigation/platform_location.dart FILE: ../../../flutter/lib/web_ui/lib/ui_web/src/ui_web/navigation/url_strategy.dart diff --git a/lib/web_ui/lib/src/engine/profiler.dart b/lib/web_ui/lib/src/engine/profiler.dart index fa5c3c47debe0..ffabd12d2156b 100644 --- a/lib/web_ui/lib/src/engine/profiler.dart +++ b/lib/web_ui/lib/src/engine/profiler.dart @@ -6,13 +6,18 @@ import 'dart:async'; import 'dart:js_interop'; import 'package:ui/ui.dart' as ui; +import 'package:ui/ui_web/src/ui_web.dart' as ui_web; import 'dom.dart'; import 'platform_dispatcher.dart'; -import 'safe_browser_api.dart'; +import 'util.dart'; +// TODO(mdebbar): Deprecate this and remove it. +// https://github.com/flutter/flutter/issues/127395 @JS('window._flutter_internal_on_benchmark') -external JSExportedDartFunction? get onBenchmark; +external JSExportedDartFunction? get jsBenchmarkValueCallback; + +ui_web.BenchmarkValueCallback? engineBenchmarkValueCallback; /// A function that computes a value of type [R]. /// @@ -105,10 +110,20 @@ class Profiler { void benchmark(String name, double value) { _checkBenchmarkMode(); - final OnBenchmark? callback = onBenchmark?.toDart as OnBenchmark?; + final ui_web.BenchmarkValueCallback? callback = + jsBenchmarkValueCallback?.toDart as ui_web.BenchmarkValueCallback?; if (callback != null) { + printWarning( + 'The JavaScript benchmarking API (i.e. `window._flutter_internal_on_benchmark`) ' + 'is deprecated and will be removed in a future release. Please use ' + '`benchmarkValueCallback` from `dart:ui_web` instead.', + ); callback(name, value); } + + if (engineBenchmarkValueCallback != null) { + engineBenchmarkValueCallback!(name, value); + } } } diff --git a/lib/web_ui/lib/src/engine/safe_browser_api.dart b/lib/web_ui/lib/src/engine/safe_browser_api.dart index 59fc2bf5a00e2..1335ad866ef48 100644 --- a/lib/web_ui/lib/src/engine/safe_browser_api.dart +++ b/lib/web_ui/lib/src/engine/safe_browser_api.dart @@ -66,9 +66,6 @@ Future promiseToFuture(Object jsPromise) { return js_util.promiseToFuture(jsPromise); } -/// A function that receives a benchmark [value] labeleb by [name]. -typedef OnBenchmark = void Function(String name, double value); - /// Parses a string [source] into a double. /// /// Uses the JavaScript `parseFloat` function instead of Dart's [double.parse] diff --git a/lib/web_ui/lib/ui_web/src/ui_web.dart b/lib/web_ui/lib/ui_web/src/ui_web.dart index f8d21ca37768d..02ec39b539595 100644 --- a/lib/web_ui/lib/ui_web/src/ui_web.dart +++ b/lib/web_ui/lib/ui_web/src/ui_web.dart @@ -9,6 +9,7 @@ library ui_web; export 'ui_web/asset_manager.dart'; +export 'ui_web/benchmarks.dart'; export 'ui_web/initialization.dart'; export 'ui_web/navigation/platform_location.dart'; export 'ui_web/navigation/url_strategy.dart'; diff --git a/lib/web_ui/lib/ui_web/src/ui_web/benchmarks.dart b/lib/web_ui/lib/ui_web/src/ui_web/benchmarks.dart new file mode 100644 index 0000000000000..bd22abfe0d461 --- /dev/null +++ b/lib/web_ui/lib/ui_web/src/ui_web/benchmarks.dart @@ -0,0 +1,16 @@ +// 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. + +import 'package:ui/src/engine.dart'; + +/// Signature of the callback that receives a benchmark [value] labeled by +/// [name]. +typedef BenchmarkValueCallback = void Function(String name, double value); + +/// A callback for receiving benchmark values. +/// +/// Each benchmark value is labeled by a `name` and has a double `value`. +set benchmarkValueCallback(BenchmarkValueCallback? callback) { + engineBenchmarkValueCallback = callback; +} diff --git a/lib/web_ui/test/engine/profiler_test.dart b/lib/web_ui/test/engine/profiler_test.dart index 8aa36c8e2e101..5f113891eaa9f 100644 --- a/lib/web_ui/test/engine/profiler_test.dart +++ b/lib/web_ui/test/engine/profiler_test.dart @@ -7,11 +7,12 @@ import 'dart:js_interop'; import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; +import 'package:ui/ui_web/src/ui_web.dart' as ui_web; import '../common/spy.dart'; @JS('window._flutter_internal_on_benchmark') -external set onBenchmark(JSAny? object); +external set jsBenchmarkValueCallback(JSAny? object); void main() { internalBootstrapBrowserTest(() => testMain); @@ -28,13 +29,29 @@ void testMain() { } void _profilerTests() { + final List warnings = []; + late void Function(String) oldPrintWarning; + + setUpAll(() { + oldPrintWarning = printWarning; + printWarning = (String warning) { + warnings.add(warning); + }; + }); + setUp(() { + warnings.clear(); Profiler.isBenchmarkMode = true; Profiler.ensureInitialized(); }); + tearDownAll(() { + printWarning = oldPrintWarning; + }); + tearDown(() { - onBenchmark = null; + jsBenchmarkValueCallback = null; + ui_web.benchmarkValueCallback = null; Profiler.isBenchmarkMode = false; }); @@ -44,49 +61,116 @@ void _profilerTests() { test('can listen to benchmarks', () { final List data = []; - onBenchmark = (String name, num value) { - data.add(BenchmarkDatapoint(name, value)); - }.toJS; + ui_web.benchmarkValueCallback = (String name, double value) { + data.add((name, value)); + }; Profiler.instance.benchmark('foo', 123); - expect(data, [BenchmarkDatapoint('foo', 123)]); + expect(data, [('foo', 123)]); data.clear(); Profiler.instance.benchmark('bar', 0.0125); - expect(data, [BenchmarkDatapoint('bar', 0.0125)]); + expect(data, [('bar', 0.0125)]); data.clear(); // Remove listener and make sure nothing breaks and the data isn't being // sent to the old callback anymore. - onBenchmark = null; + ui_web.benchmarkValueCallback = null; expect(() => Profiler.instance.benchmark('baz', 99.999), returnsNormally); expect(data, isEmpty); }); - test('throws on wrong listener type', () { - final List data = []; + // TODO(mdebbar): Remove this group once the JS API is removed. + // https://github.com/flutter/flutter/issues/127395 + group('[JS API]', () { + test('can listen to benchmarks', () { + final List data = []; + jsBenchmarkValueCallback = (String name, double value) { + data.add((name, value)); + }.toJS; + + Profiler.instance.benchmark('foo', 123); + expect(warnings, hasLength(1)); + expect(warnings.single, contains('deprecated')); + expect(warnings.single, contains('benchmarkValueCallback')); + expect(warnings.single, contains('dart:ui_web')); + warnings.clear(); + + expect(data, [('foo', 123)]); + data.clear(); + + Profiler.instance.benchmark('bar', 0.0125); + expect(data, [('bar', 0.0125)]); + data.clear(); + + // Remove listener and make sure nothing breaks and the data isn't being + // sent to the old callback anymore. + jsBenchmarkValueCallback = null; + expect(() => Profiler.instance.benchmark('baz', 99.999), returnsNormally); + expect(data, isEmpty); + }); - // Wrong callback signature. - onBenchmark = (num value) { - data.add(BenchmarkDatapoint('bad', value)); - }.toJS; - expect( - () => Profiler.instance.benchmark('foo', 123), - - // dart2js throws a NoSuchMethodError, dart2wasm throws a TypeError here. - // Just make sure it throws an error in this case. - throwsA(isA()), - ); - expect(data, isEmpty); + test('throws on wrong listener type', () { + final List data = []; + + // Wrong callback signature. + jsBenchmarkValueCallback = (double value) { + data.add(('bad', value)); + }.toJS; + expect( + () => Profiler.instance.benchmark('foo', 123), - // Not even a callback. - onBenchmark = 'string'.toJS; - expect( - () => Profiler.instance.benchmark('foo', 123), - // dart2js throws a TypeError, while dart2wasm throws an explicit - // exception. - throwsA(anything), - ); + // dart2js throws a NoSuchMethodError, dart2wasm throws a TypeError here. + // Just make sure it throws an error in this case. + throwsA(isA()), + ); + expect(data, isEmpty); + + // Not even a callback. + jsBenchmarkValueCallback = 'string'.toJS; + expect( + () => Profiler.instance.benchmark('foo', 123), + // dart2js throws a TypeError, while dart2wasm throws an explicit + // exception. + throwsA(anything), + ); + }); + + test('can be combined with ui_web API', () { + final List uiWebData = []; + final List jsData = []; + + ui_web.benchmarkValueCallback = (String name, double value) { + uiWebData.add((name, value)); + }; + jsBenchmarkValueCallback = (String name, double value) { + jsData.add((name, value)); + }.toJS; + + Profiler.instance.benchmark('foo', 123); + expect(warnings, hasLength(1)); + expect(warnings.single, contains('deprecated')); + expect(warnings.single, contains('benchmarkValueCallback')); + expect(warnings.single, contains('dart:ui_web')); + warnings.clear(); + + expect(uiWebData, [('foo', 123)]); + expect(jsData, [('foo', 123)]); + uiWebData.clear(); + jsData.clear(); + + Profiler.instance.benchmark('bar', 0.0125); + expect(uiWebData, [('bar', 0.0125)]); + expect(jsData, [('bar', 0.0125)]); + uiWebData.clear(); + jsData.clear(); + + ui_web.benchmarkValueCallback = null; + jsBenchmarkValueCallback = null; + expect(() => Profiler.instance.benchmark('baz', 99.999), returnsNormally); + expect(uiWebData, isEmpty); + expect(jsData, isEmpty); + }); }); } @@ -136,30 +220,4 @@ void _instrumentationTests() { }); } -class BenchmarkDatapoint { - BenchmarkDatapoint(this.name, this.value); - - final String name; - final num value; - - @override - int get hashCode => Object.hash(name, value); - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - return other is BenchmarkDatapoint - && other.name == name - && other.value == value; - } - - @override - String toString() { - return '$runtimeType("$name", $value)'; - } -} +typedef BenchmarkDatapoint = (String, double);