Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 14a858c

Browse files
Fix JS interop signatures to use only JS types. (#45668)
This prepares for some upcoming changes to dart2js which will be more strict about what types can be used in a JS interop declaration.
1 parent 2c53043 commit 14a858c

10 files changed

Lines changed: 107 additions & 126 deletions

File tree

lib/web_ui/lib/src/engine/app_bootstrap.dart

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,8 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
import 'package:js/js_util.dart' show allowInterop;
6-
75
import 'configuration.dart';
86
import 'js_interop/js_loader.dart';
9-
import 'js_interop/js_promise.dart';
107

118
/// The type of a function that initializes an engine (in Dart).
129
typedef InitEngineFn = Future<void> Function([JsFlutterConfiguration? params]);
@@ -40,33 +37,26 @@ class AppBootstrap {
4037
// This is a convenience method that lets the programmer call "autoStart"
4138
// from JavaScript immediately after the main.dart.js has loaded.
4239
// Returns a promise that resolves to the Flutter app that was started.
43-
autoStart: allowInterop(() => futureToPromise(() async {
40+
autoStart: () async {
4441
await autoStart();
4542
// Return the App that was just started
4643
return _prepareFlutterApp();
47-
}())),
44+
},
4845
// Calls [_initEngine], and returns a JS Promise that resolves to an
4946
// app runner object.
50-
initializeEngine: allowInterop(([JsFlutterConfiguration? configuration]) => futureToPromise(() async {
47+
initializeEngine: ([JsFlutterConfiguration? configuration]) async {
5148
await _initializeEngine(configuration);
5249
return _prepareAppRunner();
53-
}()))
50+
}
5451
);
5552
}
5653

5754
/// Creates an appRunner that runs our encapsulated runApp function.
5855
FlutterAppRunner _prepareAppRunner() {
59-
return FlutterAppRunner(runApp: allowInterop(([RunAppFnParameters? params]) {
60-
// `params` coming from JS may be used to configure the run app method.
61-
return Promise<FlutterApp>(allowInterop((
62-
PromiseResolver<FlutterApp> resolve,
63-
PromiseRejecter _,
64-
) async {
65-
await _runApp();
66-
// Return the App that was just started
67-
resolve.resolve(_prepareFlutterApp());
68-
}));
69-
}));
56+
return FlutterAppRunner(runApp: ([RunAppFnParameters? params]) async {
57+
await _runApp();
58+
return _prepareFlutterApp();
59+
});
7060
}
7161

7262
/// Represents the App that was just started, and its JS API.

lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ Future<ByteBuffer> readVideoFramePixelsUnmodified(VideoFrame videoFrame) async {
142142
// In dart2wasm, Uint8List is not the same as a JS Uint8Array. So we
143143
// explicitly construct the JS object here.
144144
final JSUint8Array destination = createUint8ArrayFromLength(size);
145-
final JsPromise copyPromise = videoFrame.copyTo(destination);
145+
final JSPromise copyPromise = videoFrame.copyTo(destination);
146146
await promiseToFuture<void>(copyPromise);
147147

148148
// In dart2wasm, `toDart` incurs a copy here. On JS backends, this is a

lib/web_ui/lib/src/engine/js_interop/js_loader.dart

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ library js_loader;
88
import 'dart:js_interop';
99

1010
import 'package:js/js_util.dart' as js_util;
11-
12-
import '../configuration.dart';
13-
import 'js_promise.dart';
11+
import 'package:ui/src/engine.dart';
1412

1513
@JS()
1614
@staticInterop
@@ -34,6 +32,17 @@ extension FlutterLoaderExtension on FlutterLoader {
3432
bool get isAutoStart => !js_util.hasProperty(this, 'didCreateEngineInitializer');
3533
}
3634

35+
/// Typedef for the function that initializes the flutter engine.
36+
/// ///
37+
/// [JsFlutterConfiguration] comes from `../configuration.dart`. It is the same
38+
/// object that can be used to configure flutter "inline", through the
39+
/// (to be deprecated) `window.flutterConfiguration` object.
40+
typedef InitializeEngineFn = Future<FlutterAppRunner> Function([JsFlutterConfiguration?]);
41+
42+
/// Typedef for the `autoStart` function that can be called straight from an engine initializer instance.
43+
/// (Similar to [RunAppFn], but taking no specific "runApp" parameters).
44+
typedef ImmediateRunAppFn = Future<FlutterApp> Function();
45+
3746
// FlutterEngineInitializer
3847

3948
/// An object that allows the user to initialize the Engine of a Flutter App.
@@ -44,34 +53,34 @@ extension FlutterLoaderExtension on FlutterLoader {
4453
@anonymous
4554
@staticInterop
4655
abstract class FlutterEngineInitializer{
47-
external factory FlutterEngineInitializer({
56+
factory FlutterEngineInitializer({
4857
required InitializeEngineFn initializeEngine,
4958
required ImmediateRunAppFn autoStart,
59+
}) => FlutterEngineInitializer._(
60+
initializeEngine: (([JsFlutterConfiguration? config]) => futureToPromise(initializeEngine(config))).toJS,
61+
autoStart: (() => futureToPromise(autoStart())).toJS,
62+
);
63+
external factory FlutterEngineInitializer._({
64+
required JSFunction initializeEngine,
65+
required JSFunction autoStart,
5066
});
5167
}
5268

53-
/// Typedef for the function that initializes the flutter engine.
54-
///
55-
/// [JsFlutterConfiguration] comes from `../configuration.dart`. It is the same
56-
/// object that can be used to configure flutter "inline", through the
57-
/// (to be deprecated) `window.flutterConfiguration` object.
58-
typedef InitializeEngineFn = Promise<FlutterAppRunner> Function([JsFlutterConfiguration?]);
59-
60-
/// Typedef for the `autoStart` function that can be called straight from an engine initializer instance.
61-
/// (Similar to [RunAppFn], but taking no specific "runApp" parameters).
62-
typedef ImmediateRunAppFn = Promise<FlutterApp> Function();
63-
6469
// FlutterAppRunner
6570

6671
/// A class that exposes a function that runs the Flutter app,
6772
/// and returns a promise of a FlutterAppCleaner.
6873
@JS()
6974
@anonymous
7075
@staticInterop
71-
abstract class FlutterAppRunner {
76+
abstract class FlutterAppRunner extends JSObject {
77+
factory FlutterAppRunner({required RunAppFn runApp,}) => FlutterAppRunner._(
78+
runApp: (([RunAppFnParameters? args]) => futureToPromise(runApp(args))).toJS
79+
);
80+
7281
/// Runs a flutter app
73-
external factory FlutterAppRunner({
74-
required RunAppFn runApp, // Returns an App
82+
external factory FlutterAppRunner._({
83+
required JSFunction runApp, // Returns an App
7584
});
7685
}
7786

@@ -84,15 +93,15 @@ abstract class RunAppFnParameters {
8493
}
8594

8695
/// Typedef for the function that runs the flutter app main entrypoint.
87-
typedef RunAppFn = Promise<FlutterApp> Function([RunAppFnParameters?]);
96+
typedef RunAppFn = Future<FlutterApp> Function([RunAppFnParameters?]);
8897

8998
// FlutterApp
9099

91100
/// A class that exposes the public API of a running Flutter Web App running.
92101
@JS()
93102
@anonymous
94103
@staticInterop
95-
abstract class FlutterApp {
104+
abstract class FlutterApp extends JSObject {
96105
/// Cleans a Flutter app
97106
external factory FlutterApp();
98107
}

lib/web_ui/lib/src/engine/js_interop/js_promise.dart

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,40 +11,27 @@ import 'package:js/js_util.dart' as js_util;
1111

1212
import '../util.dart';
1313

14-
@JS()
15-
@staticInterop
16-
class PromiseResolver<T extends Object?> {}
17-
18-
extension PromiseResolverExtension<T extends Object?> on PromiseResolver<T> {
19-
void resolve(T result) => js_util.callMethod(this, 'call', <Object>[this, if (result != null) result]);
20-
}
21-
22-
@JS()
23-
@staticInterop
24-
class PromiseRejecter {}
25-
26-
extension PromiseRejecterExtension on PromiseRejecter {
27-
void reject(Object? error) => js_util.callMethod(this, 'call', <Object>[this, if (error != null) error]);
14+
extension CallExtension on JSFunction {
15+
external void call(JSAny? this_, JSAny? object);
2816
}
2917

30-
/// Type-safe JS Promises
3118
@JS('Promise')
32-
@staticInterop
33-
abstract class Promise<T extends Object?> {
34-
/// A constructor for a JS promise
35-
external factory Promise(PromiseExecutor<T> executor);
36-
}
19+
external JSAny get _promiseConstructor;
20+
21+
JSPromise createPromise(JSFunction executor) =>
22+
js_util.callConstructor(
23+
_promiseConstructor,
24+
<Object>[executor],
25+
);
3726

38-
/// The type of function that is used to create a Promise<T>
39-
typedef PromiseExecutor<T extends Object?> = void Function(PromiseResolver<T> resolve, PromiseRejecter reject);
4027

41-
Promise<T> futureToPromise<T extends Object>(Future<T> future) {
42-
return Promise<T>(js_util.allowInterop((PromiseResolver<T> resolver, PromiseRejecter rejecter) {
28+
JSPromise futureToPromise<T extends JSAny>(Future<T> future) {
29+
return createPromise((JSFunction resolver, JSFunction rejecter) {
4330
future.then(
44-
(T value) => resolver.resolve(value),
31+
(T value) => resolver.call(null, value),
4532
onError: (Object? error) {
4633
printWarning('Rejecting promise with error: $error');
47-
rejecter.reject(error);
34+
rejecter.call(null, null);
4835
});
49-
}));
36+
}.toJS);
5037
}

lib/web_ui/lib/src/engine/safe_browser_api.dart

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -196,20 +196,6 @@ bool get _defaultBrowserSupportsImageDecoder =>
196196
// enable it explicitly.
197197
bool get _isBrowserImageDecoderStable => browserEngine == BrowserEngine.blink;
198198

199-
/// The signature of the function passed to the constructor of JavaScript `Promise`.
200-
typedef JsPromiseCallback = void Function(void Function(Object? value) resolve, void Function(Object? error) reject);
201-
202-
/// Corresponds to JavaScript's `Promise`.
203-
///
204-
/// This type doesn't need any members. Instead, it should be first converted
205-
/// to Dart's [Future] using [promiseToFuture] then interacted with through the
206-
/// [Future] API.
207-
@JS('window.Promise')
208-
@staticInterop
209-
class JsPromise {
210-
external factory JsPromise(JsPromiseCallback callback);
211-
}
212-
213199
/// Corresponds to the browser's `ImageDecoder` type.
214200
///
215201
/// See also:
@@ -228,7 +214,7 @@ extension ImageDecoderExtension on ImageDecoder {
228214
external JSBoolean get _complete;
229215
bool get complete => _complete.toDart;
230216

231-
external JsPromise decode(DecodeOptions options);
217+
external JSPromise decode(DecodeOptions options);
232218
external JSVoid close();
233219
}
234220

@@ -302,8 +288,8 @@ extension VideoFrameExtension on VideoFrame {
302288
double allocationSize() => _allocationSize().toDartDouble;
303289

304290
@JS('copyTo')
305-
external JsPromise _copyTo(JSAny destination);
306-
JsPromise copyTo(Object destination) => _copyTo(destination.toJSAnyShallow);
291+
external JSPromise _copyTo(JSAny destination);
292+
JSPromise copyTo(Object destination) => _copyTo(destination.toJSAnyShallow);
307293

308294
@JS('format')
309295
external JSString? get _format;
@@ -344,7 +330,7 @@ extension VideoFrameExtension on VideoFrame {
344330
class ImageTrackList {}
345331

346332
extension ImageTrackListExtension on ImageTrackList {
347-
external JsPromise get ready;
333+
external JSPromise get ready;
348334
external ImageTrack? get selectedTrack;
349335
}
350336

lib/web_ui/test/canvaskit/canvaskit_api_test.dart

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1847,8 +1847,8 @@ void _paragraphTests() {
18471847
}, skip: isFirefox); // Intended: Headless firefox has no webgl support https://github.com/flutter/flutter/issues/109265
18481848

18491849
group('getCanvasKitJsFileNames', () {
1850-
dynamic oldV8BreakIterator = v8BreakIterator;
1851-
dynamic oldIntlSegmenter = intlSegmenter;
1850+
JSAny? oldV8BreakIterator = v8BreakIterator;
1851+
JSAny? oldIntlSegmenter = intlSegmenter;
18521852

18531853
setUp(() {
18541854
oldV8BreakIterator = v8BreakIterator;
@@ -1861,8 +1861,8 @@ void _paragraphTests() {
18611861
});
18621862

18631863
test('in Chromium-based browsers', () {
1864-
v8BreakIterator = Object(); // Any non-null value.
1865-
intlSegmenter = Object(); // Any non-null value.
1864+
v8BreakIterator = Object().toJSBox; // Any non-null value.
1865+
intlSegmenter = Object().toJSBox; // Any non-null value.
18661866
browserSupportsImageDecoder = true;
18671867

18681868
expect(getCanvasKitJsFileNames(CanvasKitVariant.full), <String>['canvaskit.js']);
@@ -1874,7 +1874,7 @@ void _paragraphTests() {
18741874
});
18751875

18761876
test('in older versions of Chromium-based browsers', () {
1877-
v8BreakIterator = Object(); // Any non-null value.
1877+
v8BreakIterator = Object().toJSBox; // Any non-null value.
18781878
intlSegmenter = null; // Older versions of Chromium didn't have the Intl.Segmenter API.
18791879
browserSupportsImageDecoder = true;
18801880

@@ -1884,15 +1884,15 @@ void _paragraphTests() {
18841884
});
18851885

18861886
test('in other browsers', () {
1887-
intlSegmenter = Object(); // Any non-null value.
1887+
intlSegmenter = Object().toJSBox; // Any non-null value.
18881888

18891889
v8BreakIterator = null;
18901890
browserSupportsImageDecoder = true;
18911891
expect(getCanvasKitJsFileNames(CanvasKitVariant.full), <String>['canvaskit.js']);
18921892
expect(getCanvasKitJsFileNames(CanvasKitVariant.chromium), <String>['chromium/canvaskit.js']);
18931893
expect(getCanvasKitJsFileNames(CanvasKitVariant.auto), <String>['canvaskit.js']);
18941894

1895-
v8BreakIterator = Object();
1895+
v8BreakIterator = Object().toJSBox;
18961896
browserSupportsImageDecoder = false;
18971897
// TODO(mdebbar): we don't check image codecs for now.
18981898
// https://github.com/flutter/flutter/issues/122331
@@ -1935,13 +1935,13 @@ void _paragraphTests() {
19351935

19361936

19371937
@JS('window.Intl.v8BreakIterator')
1938-
external dynamic get v8BreakIterator;
1938+
external JSAny? get v8BreakIterator;
19391939

19401940
@JS('window.Intl.v8BreakIterator')
1941-
external set v8BreakIterator(dynamic x);
1941+
external set v8BreakIterator(JSAny? x);
19421942

19431943
@JS('window.Intl.Segmenter')
1944-
external dynamic get intlSegmenter;
1944+
external JSAny? get intlSegmenter;
19451945

19461946
@JS('window.Intl.Segmenter')
1947-
external set intlSegmenter(dynamic x);
1947+
external set intlSegmenter(JSAny? x);

lib/web_ui/test/engine/app_bootstrap_test.dart

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,17 @@ void testMain() {
8888

8989
final FlutterEngineInitializer engineInitializer = bootstrap.prepareEngineInitializer();
9090

91-
final Object appInitializer = await promiseToFuture<Object>(callMethod<Object>(engineInitializer, 'initializeEngine', <Object?>[]));
92-
final Object maybeApp = await promiseToFuture<Object>(callMethod<Object>(appInitializer, 'runApp', <Object?>[]));
93-
91+
final Object appInitializer = await promiseToFuture<Object>(callMethod<Object>(
92+
engineInitializer,
93+
'initializeEngine',
94+
<Object?>[]
95+
));
96+
expect(appInitializer, isA<FlutterAppRunner>());
97+
final Object maybeApp = await promiseToFuture<Object>(callMethod<Object>(
98+
appInitializer,
99+
'runApp',
100+
<Object?>[]
101+
));
94102
expect(maybeApp, isA<FlutterApp>());
95103
expect(initCalled, 1, reason: 'initEngine should have been called.');
96104
expect(runCalled, 2, reason: 'runApp should have been called.');

0 commit comments

Comments
 (0)