part of dart._engine; // 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. /// The mode the app is running in. /// Keep these in sync with the same constants on the framework-side under foundation/constants.dart. const bool kReleaseMode = bool.fromEnvironment('dart.vm.product'); /// A constant that is true if the application was compiled in profile mode. const bool kProfileMode = bool.fromEnvironment('dart.vm.profile'); /// A constant that is true if the application was compiled in debug mode. const bool kDebugMode = !kReleaseMode && !kProfileMode; /// Returns mode of the app is running in as a string. String get buildMode => kReleaseMode ? 'release' : kProfileMode ? 'profile' : 'debug'; /// A benchmark metric that includes frame-related computations prior to /// submitting layer and picture operations to the underlying renderer, such as /// HTML and CanvasKit. During this phase we compute transforms, clips, and /// other information needed for rendering. const String kProfilePrerollFrame = 'preroll_frame'; /// A benchmark metric that includes submitting layer and picture information /// to the renderer. const String kProfileApplyFrame = 'apply_frame'; final List _hotRestartListeners = []; /// Requests that [listener] is called just before hot restarting the app. void registerHotRestartListener(ui.VoidCallback listener) { _hotRestartListeners.add(listener); } /// Pretends that hot restart is about to happen. /// /// Useful in tests to check that the engine performs appropriate clean-ups, /// such as removing static DOM listeners, prior to allowing the Dart runtime /// to re-initialize the program. void debugEmulateHotRestart() { // While hot restart listeners are executing, more listeners may be added. To // avoid concurrent modification, the listeners are copies and emptied. If new // listeners are added in the process, the loop will pick them up. while (_hotRestartListeners.isNotEmpty) { final List copyOfListeners = _hotRestartListeners.toList(); _hotRestartListeners.clear(); for (final ui.VoidCallback listener in copyOfListeners) { listener(); } } } /// How far along the initialization process the engine is currently is. /// /// The initialization process starts with [none] and proceeds in increasing /// `index` number until [initialized]. enum DebugEngineInitializationState { /// Initialization hasn't started yet. uninitialized, /// The engine is initializing its non-UI services. initializingServices, /// The engine has initialized its non-UI services, but hasn't started /// initializing the UI. initializedServices, /// The engine started attaching UI surfaces to the web page. initializingUi, /// The engine has fully completed initialization. /// /// At this point the framework can start using the engine for I/O, rendering, /// etc. /// /// This is the final state of the engine. initialized, } /// The current initialization state of the engine. /// /// See [DebugEngineInitializationState] for possible states. DebugEngineInitializationState get initializationState => _initializationState; DebugEngineInitializationState _initializationState = DebugEngineInitializationState.uninitialized; /// Resets the state back to [DebugEngineInitializationState.uninitialized]. /// /// This is for testing only. void debugResetEngineInitializationState() { _initializationState = DebugEngineInitializationState.uninitialized; } /// Initializes non-UI engine services. /// /// Does not put any UI onto the page. It is therefore safe to call this /// function while the page is showing non-Flutter UI, such as a loading /// indicator, a splash screen, or in an add-to-app scenario where the host page /// is written using a different web framework. /// /// See also: /// /// * [initializeEngineUi], which is typically called after this function, and /// puts UI elements on the page. Future initializeEngineServices({ ui_web.AssetManager? assetManager, JsFlutterConfiguration? jsConfiguration, }) async { if (_initializationState != DebugEngineInitializationState.uninitialized) { assert(() { throw StateError( 'Invalid engine initialization state. `initializeEngineServices` was ' 'called, but the engine has already started initialization and is ' 'currently in state "$_initializationState".', ); }()); return; } _initializationState = DebugEngineInitializationState.initializingServices; // Store `jsConfiguration` so user settings are available to the engine. configuration.setUserConfiguration(jsConfiguration); // Called by the Web runtime just before hot restarting the app. // // This extension cleans up resources that are registered with browser's // global singletons that Dart compiler is unable to clean-up automatically. // // This extension does not need to clean-up Dart statics. Those are cleaned // up by the compiler. developer.registerExtension('ext.flutter.disassemble', (_, _) { for (final ui.VoidCallback listener in _hotRestartListeners) { listener(); } return Future.value( developer.ServiceExtensionResponse.result('OK'), ); }); if (Profiler.isBenchmarkMode) { Profiler.ensureInitialized(); } bool waitingForAnimation = false; scheduleFrameCallback = () { // We're asked to schedule a frame and call `frameHandler` when the frame // fires. if (!waitingForAnimation) { waitingForAnimation = true; domWindow.requestAnimationFrame((JSNumber highResTime) { FrameTimingRecorder.recordCurrentFrameVsync(); // In Flutter terminology "building a frame" consists of "beginning // frame" and "drawing frame". // // We do not call `recordBuildFinish` from here because // part of the rasterization process, particularly in the HTML // renderer, takes place in the `SceneBuilder.build()`. FrameTimingRecorder.recordCurrentFrameBuildStart(); // Reset immediately, because `frameHandler` can schedule more frames. waitingForAnimation = false; // We have to convert high-resolution time to `int` so we can construct // a `Duration` out of it. However, high-res time is supplied in // milliseconds as a double value, with sub-millisecond information // hidden in the fraction. So we first multiply it by 1000 to uncover // microsecond precision, and only then convert to `int`. final int highResTimeMicroseconds = (1000 * highResTime.toDartDouble).toInt(); if (EnginePlatformDispatcher.instance.onBeginFrame != null) { EnginePlatformDispatcher.instance.invokeOnBeginFrame( Duration(microseconds: highResTimeMicroseconds), ); } if (EnginePlatformDispatcher.instance.onDrawFrame != null) { // TODO(yjbanov): technically Flutter flushes microtasks between // onBeginFrame and onDrawFrame. We don't, which hasn't // been an issue yet, but eventually we'll have to // implement it properly. (Also see the to-do in // `EnginePlatformDispatcher.scheduleWarmUpFrame`). EnginePlatformDispatcher.instance.invokeOnDrawFrame(); } }); } }; assetManager ??= ui_web.AssetManager(assetBase: configuration.assetBase); _setAssetManager(assetManager); Future initializeRendererCallback() async => renderer.initialize(); await Future.wait(>[initializeRendererCallback(), _downloadAssetFonts()]); _initializationState = DebugEngineInitializationState.initializedServices; } /// Initializes the UI surfaces for the Flutter framework to render to. /// /// Must be called after [initializeEngineServices]. /// /// This function will start altering the HTML structure of the page. If used /// in an add-to-app scenario, the host page is expected to prepare for Flutter /// UI appearing on screen prior to calling this function. Future initializeEngineUi() async { if (_initializationState != DebugEngineInitializationState.initializedServices) { assert(() { throw StateError( 'Invalid engine initialization state. `initializeEngineUi` was ' 'called while the engine initialization state was ' '"$_initializationState". `initializeEngineUi` can only be called ' 'when the engine is in state ' '"${DebugEngineInitializationState.initializedServices}".', ); }()); return; } _initializationState = DebugEngineInitializationState.initializingUi; RawKeyboard.initialize(onMacOs: ui_web.browser.operatingSystem == ui_web.OperatingSystem.macOs); KeyboardBinding.initInstance(); // Ensures Flutter renders a global "generator" meta-tag. ensureMetaTag('generator', 'Flutter'); if (!configuration.multiViewEnabled) { final EngineFlutterWindow implicitView = ensureImplicitViewInitialized( hostElement: configuration.hostElement, ); if (renderer is HtmlRenderer) { ensureResourceManagerInitialized(implicitView); } } _initializationState = DebugEngineInitializationState.initialized; } ui_web.AssetManager get engineAssetManager => _debugAssetManager ?? _assetManager!; ui_web.AssetManager? _assetManager; ui_web.AssetManager? _debugAssetManager; set debugOnlyAssetManager(ui_web.AssetManager? manager) => _debugAssetManager = manager; void _setAssetManager(ui_web.AssetManager assetManager) { if (assetManager == _assetManager) { return; } _assetManager = assetManager; } Future _downloadAssetFonts() async { renderer.fontCollection.clear(); if (ui_web.debugEmulateFlutterTesterEnvironment) { // Load the embedded test font before loading fonts from the assets so that // the embedded test font is the default (first) font. await renderer.fontCollection.loadFontFromList( EmbeddedTestFont.flutterTest.data, fontFamily: EmbeddedTestFont.flutterTest.fontFamily, ); } if (_debugAssetManager != null || _assetManager != null) { await renderer.fontCollection.loadAssetFonts(await fetchFontManifest(ui_web.assetManager)); } } /// Whether to disable the font fallback system. /// /// We need to disable font fallbacks for some framework tests because /// Flutter error messages may contain an arrow symbol which is not /// covered by ASCII fonts. This causes us to try to download the /// Noto Sans Symbols font, which kicks off a `Timer` which doesn't /// complete before the Widget tree is disposed (this is by design). bool get debugDisableFontFallbacks => _debugDisableFontFallbacks; set debugDisableFontFallbacks(bool value) { _debugDisableFontFallbacks = value; } bool _debugDisableFontFallbacks = false;