Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
21 changes: 18 additions & 3 deletions lib/web_ui/lib/src/engine/profiler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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].
///
Expand Down Expand Up @@ -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);
}
}
}

Expand Down
3 changes: 0 additions & 3 deletions lib/web_ui/lib/src/engine/safe_browser_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,6 @@ Future<T> promiseToFuture<T>(Object jsPromise) {
return js_util.promiseToFuture<T>(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]
Expand Down
1 change: 1 addition & 0 deletions lib/web_ui/lib/ui_web/src/ui_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
16 changes: 16 additions & 0 deletions lib/web_ui/lib/ui_web/src/ui_web/benchmarks.dart
Original file line number Diff line number Diff line change
@@ -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;
}
172 changes: 115 additions & 57 deletions lib/web_ui/test/engine/profiler_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -28,13 +29,29 @@ void testMain() {
}

void _profilerTests() {
final List<String> warnings = <String>[];
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;
});

Expand All @@ -44,49 +61,116 @@ void _profilerTests() {

test('can listen to benchmarks', () {
final List<BenchmarkDatapoint> data = <BenchmarkDatapoint>[];
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>[BenchmarkDatapoint('foo', 123)]);
expect(data, <BenchmarkDatapoint>[('foo', 123)]);
data.clear();

Profiler.instance.benchmark('bar', 0.0125);
expect(data, <BenchmarkDatapoint>[BenchmarkDatapoint('bar', 0.0125)]);
expect(data, <BenchmarkDatapoint>[('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<BenchmarkDatapoint> data = <BenchmarkDatapoint>[];
// 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<BenchmarkDatapoint> data = <BenchmarkDatapoint>[];
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, <BenchmarkDatapoint>[('foo', 123)]);
data.clear();

Profiler.instance.benchmark('bar', 0.0125);
expect(data, <BenchmarkDatapoint>[('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<Error>()),
);
expect(data, isEmpty);
test('throws on wrong listener type', () {
final List<BenchmarkDatapoint> data = <BenchmarkDatapoint>[];

// 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<Error>()),
);
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<BenchmarkDatapoint> uiWebData = <BenchmarkDatapoint>[];
final List<BenchmarkDatapoint> jsData = <BenchmarkDatapoint>[];

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, <BenchmarkDatapoint>[('foo', 123)]);
expect(jsData, <BenchmarkDatapoint>[('foo', 123)]);
uiWebData.clear();
jsData.clear();

Profiler.instance.benchmark('bar', 0.0125);
expect(uiWebData, <BenchmarkDatapoint>[('bar', 0.0125)]);
expect(jsData, <BenchmarkDatapoint>[('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);
});
});
}

Expand Down Expand Up @@ -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);