Skip to content

Commit 1ed2a68

Browse files
ditmanschwa423
authored andcommitted
[web] Unify JS configuration. Make it available from initEngine. (flutter#37187)
* Add the 'windows' parameter to the initializeEngine function. * Wire the initEngine configuration into the engine. * Extend configuration.dart * Reuse JsFlutterConfiguration JS-interop as the configuration source both for initEngine, and the window.flutterConfig object. * Add 'renderer' and 'targetElement' fields to the JsFlutterConfiguration object. * Modify the `FlutterConfiguration` object so that it supports more than one configuration source. Return the first non-null value that was most recently set, or fall back to a default. * Silence bootstrap initialization log to debug level. * targetElement to hostElement * jsParams -> runtimeConfiguration * Add configuration_test.dart * Add test to check init stores config. * Renamed test so it actually runs. * Update configuration object. Make it throwy at init/override. * Use new config object, tweak some docs. * Tweak warn/assert messages. * Some renaming: * runtimeConfig -> configuration (as unnamed function parameter) * runtimeConfiguration -> jsConfiguration (as named function parameter, to prevent clashes with the configuration singleton in the engine code) * initEngine -> initializeEngine (because no need to abbreviate that) * Ensure init test does not use global config. * Sort JsFlutterConfiguration getters alphabetically. * Addresses PR comments.
1 parent 06e52fb commit 1ed2a68

12 files changed

+251
-85
lines changed

lib/web_ui/lib/initialization.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ Future<void> webOnlyWarmupEngine({
6666
}) async {
6767
// Create the object that knows how to bootstrap an app from JS and Dart.
6868
final engine.AppBootstrap bootstrap = engine.AppBootstrap(
69-
initEngine: () async {
70-
await engine.initializeEngineServices();
69+
initializeEngine: ([engine.JsFlutterConfiguration? configuration]) async {
70+
await engine.initializeEngineServices(jsConfiguration: configuration);
7171
}, runApp: () async {
7272
if (registerPlugins != null) {
7373
registerPlugins();
@@ -86,11 +86,11 @@ Future<void> webOnlyWarmupEngine({
8686
}
8787
if (autoStart) {
8888
// The user does not want control of the app, bootstrap immediately.
89-
print('Flutter Web Bootstrap: Auto');
89+
engine.domWindow.console.debug('Flutter Web Bootstrap: Auto.');
9090
await bootstrap.autoStart();
9191
} else {
9292
// Yield control of the bootstrap procedure to the user.
93-
print('Flutter Web Bootstrap: Programmatic');
93+
engine.domWindow.console.debug('Flutter Web Bootstrap: Programmatic.');
9494
engine.didCreateEngineInitializer!(bootstrap.prepareEngineInitializer());
9595
}
9696
}

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

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,33 @@
44

55
import 'package:js/js.dart';
66

7+
import 'configuration.dart';
78
import 'js_interop/js_loader.dart';
89
import 'js_interop/js_promise.dart';
910

11+
/// The type of a function that initializes an engine (in Dart).
12+
typedef InitEngineFn = Future<void> Function([JsFlutterConfiguration? params]);
13+
1014
/// A class that controls the coarse lifecycle of a Flutter app.
1115
class AppBootstrap {
12-
/// Construct a FlutterLoader
13-
AppBootstrap({required Function initEngine, required Function runApp}) :
14-
_initEngine = initEngine, _runApp = runApp;
15-
16-
// TODO(dit): Be more strict with the below typedefs, so we can add incoming params for each function.
16+
/// Construct an AppBootstrap.
17+
AppBootstrap({required InitEngineFn initializeEngine, required Function runApp}) :
18+
_initializeEngine = initializeEngine, _runApp = runApp;
1719

18-
// A function to initialize the engine
19-
final Function _initEngine;
20+
// A function to initialize the engine.
21+
final InitEngineFn _initializeEngine;
2022

21-
// A function to run the app
23+
// A function to run the app.
24+
//
25+
// TODO(dit): Be more strict with the typedef of this function, so we can add
26+
// typed params to the function. (See InitEngineFn).
2227
final Function _runApp;
2328

2429
/// Immediately bootstraps the app.
2530
///
2631
/// This calls `initEngine` and `runApp` in succession.
2732
Future<void> autoStart() async {
28-
await _initEngine();
33+
await _initializeEngine();
2934
await _runApp();
3035
}
3136

@@ -47,15 +52,14 @@ class AppBootstrap {
4752
}),
4853
// Calls [_initEngine], and returns a JS Promise that resolves to an
4954
// app runner object.
50-
initializeEngine: allowInterop(([InitializeEngineFnParameters? params]) {
55+
initializeEngine: allowInterop(([JsFlutterConfiguration? configuration]) {
5156
// `params` coming from Javascript may be used to configure the engine intialization.
52-
// The internal `initEngine` function must accept those params, and then this
53-
// code needs to be slightly modified to pass them to the initEngine call below.
57+
// The internal `initEngine` function must accept those params.
5458
return Promise<FlutterAppRunner>(allowInterop((
5559
PromiseResolver<FlutterAppRunner> resolve,
5660
PromiseRejecter _,
5761
) async {
58-
await _initEngine();
62+
await _initializeEngine(configuration);
5963
// Return an app runner object
6064
resolve(_prepareAppRunner());
6165
}));

lib/web_ui/lib/src/engine/configuration.dart

Lines changed: 110 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,60 +5,129 @@
55
/// JavaScript API a Flutter Web application can use to configure the Web
66
/// Engine.
77
///
8-
/// The configuration is a plain JavaScript object set as the
9-
/// `flutterConfiguration` property of the top-level `window` object.
8+
/// The configuration is passed from JavaScript to the engine as part of the
9+
/// bootstrap process, through the `FlutterEngineInitializer.initializeEngine`
10+
/// JS method, with an (optional) object of type [JsFlutterConfiguration].
11+
///
12+
/// This library also supports the legacy method of setting a plain JavaScript
13+
/// object set as the `flutterConfiguration` property of the top-level `window`
14+
/// object, but that approach is now deprecated and will warn users.
15+
///
16+
/// Both methods are **disallowed** to be used at the same time.
1017
///
1118
/// Example:
1219
///
13-
/// <head>
14-
/// <script>
15-
/// window.flutterConfiguration = {
16-
/// canvasKitBaseUrl: "https://example.com/my-custom-canvaskit/"
17-
/// };
18-
/// </script>
19-
/// </head>
20+
/// _flutter.loader.loadEntrypoint({
21+
/// // ...
22+
/// onEntrypointLoaded: async function(engineInitializer) {
23+
/// let appRunner = await engineInitializer.initializeEngine({
24+
/// // JsFlutterConfiguration goes here...
25+
/// canvasKitBaseUrl: "https://example.com/my-custom-canvaskit/",
26+
/// });
27+
/// appRunner.runApp();
28+
/// }
29+
/// });
30+
///
31+
/// Example of the **deprecated** style (this will issue a JS console warning!):
32+
///
33+
/// <script>
34+
/// window.flutterConfiguration = {
35+
/// canvasKitBaseUrl: "https://example.com/my-custom-canvaskit/"
36+
/// };
37+
/// </script>
2038
///
21-
/// Configuration properties supplied via `window.flutterConfiguration`
22-
/// override those supplied using the corresponding environment variables. For
23-
/// example, if both `window.flutterConfiguration.canvasKitBaseUrl` and the
24-
/// `FLUTTER_WEB_CANVASKIT_URL` environment variables are provided,
25-
/// `window.flutterConfiguration.canvasKitBaseUrl` is used.
39+
/// Configuration properties supplied via this object override those supplied
40+
/// using the corresponding environment variables. For example, if both the
41+
/// `canvasKitBaseUrl` config entry and the `FLUTTER_WEB_CANVASKIT_URL`
42+
/// environment variables are provided, the `canvasKitBaseUrl` entry is used.
2643
2744
@JS()
2845
library configuration;
2946

3047
import 'package:js/js.dart';
48+
import 'package:meta/meta.dart';
49+
import 'dom.dart';
3150

3251
/// The version of CanvasKit used by the web engine by default.
3352
// DO NOT EDIT THE NEXT LINE OF CODE MANUALLY
3453
// See `lib/web_ui/README.md` for how to roll CanvasKit to a new version.
3554
const String _canvaskitVersion = '0.37.0';
3655

3756
/// The Web Engine configuration for the current application.
38-
FlutterConfiguration get configuration => _configuration ??= FlutterConfiguration(_jsConfiguration);
57+
FlutterConfiguration get configuration =>
58+
_configuration ??= FlutterConfiguration.legacy(_jsConfiguration);
3959
FlutterConfiguration? _configuration;
4060

4161
/// Sets the given configuration as the current one.
4262
///
4363
/// This must be called before the engine is initialized. Calling it after the
4464
/// engine is initialized will result in some of the properties not taking
4565
/// effect because they are consumed during initialization.
66+
@visibleForTesting
4667
void debugSetConfiguration(FlutterConfiguration configuration) {
4768
_configuration = configuration;
4869
}
4970

5071
/// Supplies Web Engine configuration properties.
5172
class FlutterConfiguration {
52-
/// Constructs a configuration from a JavaScript object containing
53-
/// runtime-supplied properties.
54-
FlutterConfiguration(this._js);
73+
/// Constructs an unitialized configuration object.
74+
@visibleForTesting
75+
FlutterConfiguration();
5576

56-
final JsFlutterConfiguration? _js;
77+
/// Constucts a "tainted by JS globals" configuration object.
78+
///
79+
/// This configuration style is deprecated. It will warn the user about the
80+
/// new API (if used)
81+
FlutterConfiguration.legacy(JsFlutterConfiguration? config) {
82+
if (config != null) {
83+
_usedLegacyConfigStyle = true;
84+
_configuration = config;
85+
}
86+
// Warn the user of the deprecated behavior.
87+
assert(() {
88+
if (config != null) {
89+
domWindow.console.warn('window.flutterConfiguration is now deprecated.\n'
90+
'Use engineInitializer.initializeEngine(config) instead.\n'
91+
'See: https://docs.flutter.dev/development/platform-integration/web/initialization');
92+
}
93+
if (_requestedRendererType != null) {
94+
domWindow.console.warn('window.flutterWebRenderer is now deprecated.\n'
95+
'Use engineInitializer.initializeEngine(config) instead.\n'
96+
'See: https://docs.flutter.dev/development/platform-integration/web/initialization');
97+
}
98+
return true;
99+
}());
100+
}
101+
102+
bool _usedLegacyConfigStyle = false;
103+
JsFlutterConfiguration? _configuration;
104+
105+
/// Sets a value for [_configuration].
106+
///
107+
/// This method is called by the engine initialization process, through the
108+
/// [initEngineServices] method.
109+
///
110+
/// This method throws an AssertionError, if the _configuration object has
111+
/// been set to anything non-null through the [FlutterConfiguration.legacy]
112+
/// constructor.
113+
void setUserConfiguration(JsFlutterConfiguration? configuration) {
114+
if (configuration != null) {
115+
assert(!_usedLegacyConfigStyle,
116+
'Use engineInitializer.initializeEngine(config) only. '
117+
'Using the (deprecated) window.flutterConfiguration and initializeEngine '
118+
'configuration simultaneously is not supported.');
119+
assert(_requestedRendererType == null || configuration.renderer == null,
120+
'Use engineInitializer.initializeEngine(config) only. '
121+
'Using the (deprecated) window.flutterWebRenderer and initializeEngine '
122+
'configuration simultaneously is not supported.');
123+
_configuration = configuration;
124+
}
125+
}
57126

58127
// Static constant parameters.
59128
//
60129
// These properties affect tree shaking and therefore cannot be supplied at
61-
// runtime. They must be static constants for the compiler to remove dead
130+
// runtime. They must be static constants for the compiler to remove dead code
62131
// effectively.
63132

64133
/// Auto detect which rendering backend to use.
@@ -110,7 +179,7 @@ class FlutterConfiguration {
110179
/// --web-renderer=canvaskit \
111180
/// --dart-define=FLUTTER_WEB_CANVASKIT_URL=https://example.com/custom-canvaskit-build/
112181
/// ```
113-
String get canvasKitBaseUrl => _js?.canvasKitBaseUrl ?? _defaultCanvasKitBaseUrl;
182+
String get canvasKitBaseUrl => _configuration?.canvasKitBaseUrl ?? _defaultCanvasKitBaseUrl;
114183
static const String _defaultCanvasKitBaseUrl = String.fromEnvironment(
115184
'FLUTTER_WEB_CANVASKIT_URL',
116185
defaultValue: 'https://unpkg.com/canvaskit-wasm@$_canvaskitVersion/bin/',
@@ -121,7 +190,7 @@ class FlutterConfiguration {
121190
///
122191
/// This is mainly used for testing or for apps that want to ensure they
123192
/// run on devices which don't support WebGL.
124-
bool get canvasKitForceCpuOnly => _js?.canvasKitForceCpuOnly ?? _defaultCanvasKitForceCpuOnly;
193+
bool get canvasKitForceCpuOnly => _configuration?.canvasKitForceCpuOnly ?? _defaultCanvasKitForceCpuOnly;
125194
static const bool _defaultCanvasKitForceCpuOnly = bool.fromEnvironment(
126195
'FLUTTER_WEB_CANVASKIT_FORCE_CPU_ONLY',
127196
);
@@ -135,7 +204,7 @@ class FlutterConfiguration {
135204
///
136205
/// This value can be specified using either the `FLUTTER_WEB_MAXIMUM_SURFACES`
137206
/// environment variable, or using the runtime configuration.
138-
int get canvasKitMaximumSurfaces => _js?.canvasKitMaximumSurfaces ?? _defaultCanvasKitMaximumSurfaces;
207+
int get canvasKitMaximumSurfaces => _configuration?.canvasKitMaximumSurfaces ?? _defaultCanvasKitMaximumSurfaces;
139208
static const int _defaultCanvasKitMaximumSurfaces = int.fromEnvironment(
140209
'FLUTTER_WEB_MAXIMUM_SURFACES',
141210
defaultValue: 8,
@@ -152,10 +221,23 @@ class FlutterConfiguration {
152221
/// ```
153222
/// flutter run -d chrome --profile --dart-define=FLUTTER_WEB_DEBUG_SHOW_SEMANTICS=true
154223
/// ```
155-
bool get debugShowSemanticsNodes => _js?.debugShowSemanticsNodes ?? _defaultDebugShowSemanticsNodes;
224+
bool get debugShowSemanticsNodes => _configuration?.debugShowSemanticsNodes ?? _defaultDebugShowSemanticsNodes;
156225
static const bool _defaultDebugShowSemanticsNodes = bool.fromEnvironment(
157226
'FLUTTER_WEB_DEBUG_SHOW_SEMANTICS',
158227
);
228+
229+
/// Returns the [hostElement] in which the Flutter Application is supposed
230+
/// to render, or `null` if the user hasn't specified anything.
231+
DomElement? get hostElement => _configuration?.hostElement;
232+
233+
/// Returns the [requestedRendererType] to be used with the current Flutter
234+
/// application, normally 'canvaskit' or 'auto'.
235+
///
236+
/// This value may come from the JS configuration, but also a specific JS value:
237+
/// `window.flutterWebRenderer`.
238+
///
239+
/// This is used by the Renderer class to decide how to initialize the engine.
240+
String? get requestedRendererType => _configuration?.renderer ?? _requestedRendererType;
159241
}
160242

161243
@JS('window.flutterConfiguration')
@@ -169,13 +251,13 @@ class JsFlutterConfiguration {}
169251
extension JsFlutterConfigurationExtension on JsFlutterConfiguration {
170252
external String? get canvasKitBaseUrl;
171253
external bool? get canvasKitForceCpuOnly;
172-
external bool? get debugShowSemanticsNodes;
173-
174254
external int? get canvasKitMaximumSurfaces;
175-
external set canvasKitMaximumSurfaces(int? maxSurfaces);
255+
external bool? get debugShowSemanticsNodes;
256+
external DomElement? get hostElement;
257+
external String? get renderer;
176258
}
177259

178260
/// A JavaScript entrypoint that allows developer to set rendering backend
179261
/// at runtime before launching the application.
180262
@JS('window.flutterWebRenderer')
181-
external String? get requestedRendererType;
263+
external String? get _requestedRendererType;

lib/web_ui/lib/src/engine/dom.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class DomConsole {}
7777
extension DomConsoleExtension on DomConsole {
7878
external void warn(Object? arg);
7979
external void error(Object? arg);
80+
external void debug(Object? arg);
8081
}
8182

8283
@JS('window')

lib/web_ui/lib/src/engine/initialization.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'dart:developer' as developer;
77

88
import 'package:ui/src/engine/assets.dart';
99
import 'package:ui/src/engine/browser_detection.dart';
10+
import 'package:ui/src/engine/configuration.dart';
1011
import 'package:ui/src/engine/embedder.dart';
1112
import 'package:ui/src/engine/mouse_cursor.dart';
1213
import 'package:ui/src/engine/navigation.dart';
@@ -126,6 +127,7 @@ void debugResetEngineInitializationState() {
126127
/// puts UI elements on the page.
127128
Future<void> initializeEngineServices({
128129
AssetManager? assetManager,
130+
JsFlutterConfiguration? jsConfiguration
129131
}) async {
130132
if (_initializationState != DebugEngineInitializationState.uninitialized) {
131133
assert(() {
@@ -139,6 +141,9 @@ Future<void> initializeEngineServices({
139141
}
140142
_initializationState = DebugEngineInitializationState.initializingServices;
141143

144+
// Store `jsConfiguration` so user settings are available to the engine.
145+
configuration.setUserConfiguration(jsConfiguration);
146+
142147
// Setup the hook that allows users to customize URL strategy before running
143148
// the app.
144149
_addUrlStrategyListener();

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

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ library js_loader;
77

88
import 'package:js/js.dart';
99

10+
import '../configuration.dart';
1011
import 'js_promise.dart';
1112

1213
/// Typedef for the function that notifies JS that the main entrypoint is up and running.
@@ -25,26 +26,6 @@ external Object? get loader;
2526
@JS('_flutter.loader.didCreateEngineInitializer')
2627
external DidCreateEngineInitializerFn? get didCreateEngineInitializer;
2728

28-
// /// window._flutter
29-
// @JS('_flutter')
30-
// external FlutterJsNamespace? get flutterjs;
31-
32-
// /// window._flutter.loader
33-
// @JS()
34-
// @anonymous
35-
// class FlutterJsNamespace {
36-
// external FlutterJsLoaderNamespace? get loader;
37-
// }
38-
39-
// /// The bits of window._flutter.loader that the Flutter Engine cares about.
40-
// @JS()
41-
// @anonymous
42-
// class FlutterJsLoaderNamespace {
43-
// /// A hook to notify JavaScript that Flutter is up and running!
44-
// /// This is setup by flutter.js when the main entrypoint bundle is injected.
45-
// external DidCreateEngineInitializerFn? get didCreateEngineInitializer;
46-
// }
47-
4829
// FlutterEngineInitializer
4930

5031
/// An object that allows the user to initialize the Engine of a Flutter App.
@@ -61,17 +42,12 @@ abstract class FlutterEngineInitializer{
6142
});
6243
}
6344

64-
/// The shape of the object that can be passed as parameter to the
65-
/// initializeEngine function of the FlutterEngineInitializer object
66-
/// (when called from JS).
67-
@JS()
68-
@anonymous
69-
@staticInterop
70-
abstract class InitializeEngineFnParameters {
71-
}
72-
7345
/// Typedef for the function that initializes the flutter engine.
74-
typedef InitializeEngineFn = Promise<FlutterAppRunner?> Function([InitializeEngineFnParameters?]);
46+
///
47+
/// [JsFlutterConfiguration] comes from `../configuration.dart`. It is the same
48+
/// object that can be used to configure flutter "inline", through the
49+
/// (to be deprecated) `window.flutterConfiguration` object.
50+
typedef InitializeEngineFn = Promise<FlutterAppRunner?> Function([JsFlutterConfiguration?]);
7551

7652
/// Typedef for the `autoStart` function that can be called straight from an engine initializer instance.
7753
/// (Similar to [RunAppFn], but taking no specific "runApp" parameters).

0 commit comments

Comments
 (0)