// 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. #ifndef FLUTTER_RUNTIME_RUNTIME_CONTROLLER_H_ #define FLUTTER_RUNTIME_RUNTIME_CONTROLLER_H_ #include #include #include "flutter/assets/asset_manager.h" #include "flutter/common/task_runners.h" #include "flutter/flow/layers/layer_tree.h" #include "flutter/fml/macros.h" #include "flutter/fml/mapping.h" #include "flutter/lib/ui/io_manager.h" #include "flutter/lib/ui/painting/image_generator_registry.h" #include "flutter/lib/ui/text/font_collection.h" #include "flutter/lib/ui/ui_dart_state.h" #include "flutter/lib/ui/window/platform_configuration.h" #include "flutter/lib/ui/window/pointer_data_packet.h" #include "flutter/lib/ui/window/pointer_data_packet_converter.h" #include "flutter/runtime/dart_vm.h" #include "flutter/runtime/platform_data.h" #include "flutter/runtime/platform_isolate_manager.h" #include "rapidjson/document.h" #include "rapidjson/stringbuffer.h" namespace flutter { class Scene; class RuntimeDelegate; class View; class Window; //------------------------------------------------------------------------------ /// Represents an instance of a running root isolate with window bindings. In /// normal operation, a single instance of this object is owned by the engine /// per shell. This object may only be created, used, and collected on the UI /// task runner. Window state queried by the root isolate is stored by this /// object. In cold-restart scenarios, the engine may collect this before /// installing a new runtime controller in its place. The Clone method may be /// used by the engine to copy the currently accumulated window state so it can /// be referenced by the new runtime controller. /// /// When `RuntimeController` is created, it takes some time before the root /// isolate becomes ready. Operation during this gap is stored by /// `RuntimeController` and flushed to the Dart VM when the isolate becomes /// ready before the entrypoint function. See `PlatformData`. /// class RuntimeController : public PlatformConfigurationClient, PointerDataPacketConverter::Delegate { public: /// A callback that's invoked after this `RuntimeController` attempts to /// add a view to the Dart isolate. /// /// If the Dart isolate is not launched yet, this callback will be stored /// and invoked after the isolate is launched. /// /// The `added` parameter is false if the add operation fails or was /// cancelled while pending using `RemoveView`. using AddViewCallback = std::function; //---------------------------------------------------------------------------- /// @brief Creates a new instance of a runtime controller. This is /// usually only done by the engine instance associated with the /// shell. /// /// @param client The runtime delegate. This is /// usually the `Engine` instance. /// @param vm A reference to a running Dart VM. /// The runtime controller must be /// collected before the VM is /// destroyed (this order is /// guaranteed by the shell). /// @param[in] idle_notification_callback The idle notification callback. /// This allows callers to run native /// code in isolate scope when the VM /// is about to be notified that the /// engine is going to be idle. /// @param[in] platform_data The window data (if exists). /// @param[in] isolate_create_callback The isolate create callback. This /// allows callers to run native code /// in isolate scope on the UI task /// runner as soon as the root isolate /// has been created. /// @param[in] isolate_shutdown_callback The isolate shutdown callback. /// This allows callers to run native /// code in isolate scoped on the UI /// task runner just as the root /// isolate is about to be torn down. /// @param[in] persistent_isolate_data Unstructured persistent read-only /// data that the root isolate can /// access in a synchronous manner. /// @param[in] context Engine-owned state which is /// accessed by the root dart isolate. /// RuntimeController( RuntimeDelegate& p_client, DartVM* vm, fml::RefPtr p_isolate_snapshot, const std::function& idle_notification_callback, const PlatformData& platform_data, const fml::closure& isolate_create_callback, const fml::closure& isolate_shutdown_callback, std::shared_ptr p_persistent_isolate_data, const UIDartState::Context& context); //---------------------------------------------------------------------------- /// @brief Create a RuntimeController that shares as many resources as /// possible with the calling RuntimeController such that together /// they occupy less memory. /// @return A RuntimeController with a running isolate. /// @see RuntimeController::RuntimeController /// std::unique_ptr Spawn( RuntimeDelegate& p_client, const std::string& advisory_script_uri, const std::string& advisory_script_entrypoint, const std::function& idle_notification_callback, const fml::closure& isolate_create_callback, const fml::closure& isolate_shutdown_callback, const std::shared_ptr& persistent_isolate_data, fml::WeakPtr io_manager, fml::WeakPtr image_decoder, fml::WeakPtr image_generator_registry, fml::TaskRunnerAffineWeakPtr snapshot_delegate) const; // |PlatformConfigurationClient| ~RuntimeController() override; //---------------------------------------------------------------------------- /// @brief Launches the isolate using the window data associated with /// this runtime controller. Before this call, the Dart isolate /// has not been initialized. On successful return, the caller can /// assume that the isolate is in the /// `DartIsolate::Phase::Running` phase. /// /// This call will fail if a root isolate is already running. To /// re-create an isolate with the window data associated with this /// runtime controller, `Clone` this runtime controller and /// Launch an isolate in that runtime controller instead. /// /// @param[in] settings The per engine instance settings. /// @param[in] root_isolate_create_callback A callback invoked before the /// root isolate has launched the Dart /// program, but after it has been /// created. This is called without /// isolate scope, and after any root /// isolate callback in the settings. /// @param[in] dart_entrypoint The dart entrypoint. If /// `std::nullopt` or empty, `main` will /// be attempted. /// @param[in] dart_entrypoint_library The dart entrypoint library. If /// `std::nullopt` or empty, the core /// library will be attempted. /// @param[in] dart_entrypoint_args Arguments passed as a List /// to Dart's entrypoint function. /// @param[in] isolate_configuration The isolate configuration /// /// @return If the isolate could be launched and guided to the /// `DartIsolate::Phase::Running` phase. /// [[nodiscard]] bool LaunchRootIsolate( const Settings& settings, const fml::closure& root_isolate_create_callback, std::optional dart_entrypoint, std::optional dart_entrypoint_library, const std::vector& dart_entrypoint_args, std::unique_ptr isolate_configuration); //---------------------------------------------------------------------------- /// @brief Clone the runtime controller. Launching an isolate with a /// cloned runtime controller will use the same snapshots and /// copies all window data to the new instance. This is usually /// only used in the debug runtime mode to support the /// cold-restart scenario. /// /// @return A clone of the existing runtime controller. /// std::unique_ptr Clone() const; //---------------------------------------------------------------------------- /// @brief Notify the isolate that a new view is available. /// /// A view must be added before other methods can refer to it, /// including the implicit view. Adding a view that already exists /// is an error. /// /// The `callback` is invoked when the add operation is attempted, /// failed, or is cancelled. /// /// If the isolate is not running, the view add will be queued and /// flushed to the isolate when it starts. Calling `RemoveView` /// before the isolate is launched cancels the add operation. /// /// If the isolate is running, a frame will be scheduled. /// /// @param[in] view_id The ID of the new view. /// @param[in] viewport_metrics The initial viewport metrics for the view. /// @param[in] callback Callback that will be invoked after the add /// operation is attempted or cancelled. /// void AddView(int64_t view_id, const ViewportMetrics& view_metrics, AddViewCallback callback); //---------------------------------------------------------------------------- /// @brief Notify the isolate that a view is no longer available. /// /// Views that are added before the isolate is started are /// queued until the isolate is launched. If one of these /// "pending" views are removed, the view add is cancelled: /// the `AddViewCallback` will be invoked with an `added` of /// false and `RemoveView` will return false. /// /// The implicit view (kFlutterImplicitViewId) should never be /// removed. Doing so triggers an assertion. /// /// @param[in] view_id The ID of the view. /// /// @return If the remove view operation was forwarded to the running /// isolate. False if the view does not exist. If the Dart isolate /// is not running, then the pending view creation (if any) is /// cancelled and the return value is always false. bool RemoveView(int64_t view_id); //---------------------------------------------------------------------------- /// @brief Forward the specified viewport metrics to the running isolate. /// If the isolate is not running, these metrics will be saved and /// flushed to the isolate when it starts. /// /// @param[in] view_id The ID for the view that `metrics` describes. /// @param[in] metrics The window's viewport metrics. /// /// @return If the window metrics were forwarded to the running isolate. /// bool SetViewportMetrics(int64_t view_id, const ViewportMetrics& metrics); //---------------------------------------------------------------------------- /// @brief Forward the specified display metrics to the running isolate. /// If the isolate is not running, these metrics will be saved and /// flushed to the isolate when it starts. /// /// @param[in] displays The available displays. bool SetDisplays(const std::vector& displays); //---------------------------------------------------------------------------- /// @brief Forward the specified locale data to the running isolate. If /// the isolate is not running, this data will be saved and /// flushed to the isolate when it starts running. /// /// @deprecated The persistent isolate data must be used for this purpose /// instead. /// /// @param[in] locale_data The locale data. This should consist of groups of /// 4 strings, each group representing a single locale. /// /// @return If the locale data was forwarded to the running isolate. /// bool SetLocales(const std::vector& locale_data); //---------------------------------------------------------------------------- /// @brief Forward the user settings data to the running isolate. If the /// isolate is not running, this data will be saved and flushed to /// the isolate when it starts running. /// /// @deprecated The persistent isolate data must be used for this purpose /// instead. /// /// @param[in] data The user settings data. /// /// @return If the user settings data was forwarded to the running /// isolate. /// bool SetUserSettingsData(const std::string& data); //---------------------------------------------------------------------------- /// @brief Forward the initial lifecycle state data to the running /// isolate. If the isolate is not running, this data will be /// saved and flushed to the isolate when it starts running. /// After the isolate starts running, the current lifecycle /// state is pushed to it via the "flutter/lifecycle" channel. /// /// @deprecated The persistent isolate data must be used for this purpose /// instead. /// /// @param[in] data The lifecycle state data. /// /// @return If the lifecycle state data was forwarded to the running /// isolate. /// bool SetInitialLifecycleState(const std::string& data); //---------------------------------------------------------------------------- /// @brief Notifies the running isolate about whether the semantics tree /// should be generated or not. If the isolate is not running, /// this preference will be saved and flushed to the isolate when /// it starts running. /// /// @param[in] enabled Indicates whether to generate the semantics tree. /// /// @return If the semantics tree generation preference was forwarded to /// the running isolate. /// bool SetSemanticsEnabled(bool enabled); //---------------------------------------------------------------------------- /// @brief Forward the preference of accessibility features that must be /// enabled in the semantics tree to the running isolate. If the /// isolate is not running, this data will be saved and flushed to /// the isolate when it starts running. /// /// @param[in] flags The accessibility features that must be generated in /// the semantics tree. /// /// @return If the preference of accessibility features was forwarded to /// the running isolate. /// bool SetAccessibilityFeatures(int32_t flags); //---------------------------------------------------------------------------- /// @brief Notifies the running isolate that it should start generating a /// new frame. /// /// @see `Engine::BeginFrame` for more context. /// /// @param[in] frame_time The point at which the current frame interval /// began. May be used by animation interpolators, /// physics simulations, etc. /// /// @return If notification to begin frame rendering was delivered to the /// running isolate. /// bool BeginFrame(fml::TimePoint frame_time, uint64_t frame_number); //---------------------------------------------------------------------------- /// @brief Dart code cannot fully measure the time it takes for a /// specific frame to be rendered. This is because Dart code only /// runs on the UI task runner. That is only a small part of the /// overall frame workload. The raster task runner frame workload /// is executed on a thread where Dart code cannot run (and hence /// instrument). Besides, due to the pipelined nature of rendering /// in Flutter, there may be multiple frame workloads being /// processed at any given time. However, for non-Timeline based /// profiling, it is useful for trace collection and processing to /// happen in Dart. To do this, the raster task runner frame /// workloads need to be instrumented separately. After a set /// number of these profiles have been gathered, they need to be /// reported back to Dart code. The engine reports this extra /// instrumentation information back to Dart code running on the /// engine by invoking this method at predefined intervals. /// /// @see `Engine::ReportTimings`, `FrameTiming` /// /// @param[in] timings Collection of `FrameTiming::kCount` * `n` timestamps /// for `n` frames whose timings have not been reported /// yet. A collection of integers is reported here for /// easier conversions to Dart objects. The timestamps /// are measured against the system monotonic clock /// measured in microseconds. /// bool ReportTimings(std::vector timings); //---------------------------------------------------------------------------- /// @brief Notify the Dart VM that no frame workloads are expected on the /// UI task runner till the specified deadline. The VM uses this /// opportunity to perform garbage collection operations is a /// manner that interferes as little as possible with frame /// rendering. /// /// NotifyIdle is advisory. The VM may or may not run a garbage collection /// when this is called, and will eventually perform garbage collections even /// if it is not called or it is called with insufficient deadlines. /// /// The garbage collection mechanism and its thresholds are internal /// implementation details and absolutely no guarantees are made about the /// threshold discussed below. This discussion is also an oversimplification /// but hopefully serves to calibrate expectations about GC behavior: /// * When the Dart VM and its root isolate are initialized, the memory /// consumed upto that point are treated as a baseline. /// * A fixed percentage of the memory consumed (~20%) over the baseline is /// treated as the hard threshold. /// * The memory in play is divided into old space and new space. The new /// space is typically very small and fills up rapidly. /// * The baseline plus the threshold is considered the old space while the /// small new space is a separate region (typically a few pages). /// * The total old space size minus the max new space size is treated as the /// soft threshold. /// * In a world where there is no call to NotifyIdle, when the total /// allocation exceeds the soft threshold, a concurrent mark is initiated in /// the VM. There is a “small” pause that occurs when the concurrent mark is /// initiated and another pause when the mark concludes and a sweep is /// initiated. /// * If the total allocations exceeds the hard threshold, a “big” /// stop-the-world pause is initiated. /// * If after either the sweep after the concurrent mark, or, the /// stop-the-world pause, the consumption returns to be below the soft /// threshold, the dance begins anew. /// * If after both the “small” and “big” pauses, memory usage is still over /// the hard threshold, i.e, the objects are still reachable, that amount of /// memory is treated as the new baseline and a fixed percentage of the new /// baseline over the new baseline is now the new hard threshold. /// * Updating the baseline will continue till memory for the updated old /// space can be allocated from the operating system. These allocations will /// typically fail due to address space exhaustion on 32-bit systems and /// page table exhaustion on 64-bit systems. /// * NotifyIdle initiates the concurrent mark preemptively. The deadline is /// used by the VM to determine if the corresponding sweep can be performed /// within the deadline. This way, jank due to “small” pauses can be /// ameliorated. /// * There is no ability to stop a “big” pause on reaching the hard threshold /// in the old space. The best you can do is release (by making them /// unreachable) objects eagerly so that the are marked as unreachable in /// the concurrent mark initiated by either reaching the soft threshold or /// an explicit NotifyIdle. /// * If you are running out of memory, its because too many large objects /// were allocation and remained reachable such that the old space kept /// growing till it could grow no more. /// * At the edges of allocation thresholds, failures can occur gracefully if /// the instigating allocation was made in the Dart VM or rather gracelessly /// if the allocation is made by some native component. /// /// @see `Dart_TimelineGetMicros` /// /// @bug The `deadline` argument must be converted to `std::chrono` /// instead of a raw integer. /// /// @param[in] deadline The deadline is used by the VM to determine if the /// corresponding sweep can be performed within the deadline. /// /// @return If the idle notification was forwarded to the running isolate. /// virtual bool NotifyIdle(fml::TimeDelta deadline); //---------------------------------------------------------------------------- /// @brief Notify the Dart VM that the attached flutter view has been /// destroyed. This gives the Dart VM to perform some cleanup /// activities e.g: perform garbage collection to free up any /// unused memory. /// /// NotifyDestroyed is advisory. The VM may or may not perform any clean up /// activities. /// virtual bool NotifyDestroyed(); //---------------------------------------------------------------------------- /// @brief Returns if the root isolate is running. The isolate must be /// transitioned to the running phase manually. The isolate can /// stop running if it terminates execution on its own. /// /// @return True if root isolate running, False otherwise. /// virtual bool IsRootIsolateRunning() const; //---------------------------------------------------------------------------- /// @brief Dispatch the specified platform message to running root /// isolate. /// /// @param[in] message The message to dispatch to the isolate. /// /// @return If the message was dispatched to the running root isolate. /// This may fail is an isolate is not running. /// virtual bool DispatchPlatformMessage( std::unique_ptr message); //---------------------------------------------------------------------------- /// @brief Dispatch the specified pointer data message to the running /// root isolate. /// /// @param[in] packet The pointer data message to dispatch to the isolate. /// /// @return If the pointer data message was dispatched. This may fail is /// an isolate is not running. /// bool DispatchPointerDataPacket(const PointerDataPacket& packet); //---------------------------------------------------------------------------- /// @brief Dispatch the semantics action to the specified accessibility /// node. /// /// @param[in] node_id The identified of the accessibility node. /// @param[in] action The semantics action to perform on the specified /// accessibility node. /// @param[in] args Optional data that applies to the specified action. /// /// @return If the semantics action was dispatched. This may fail if an /// isolate is not running. /// bool DispatchSemanticsAction(int32_t node_id, SemanticsAction action, fml::MallocMapping args); //---------------------------------------------------------------------------- /// @brief Gets the main port identifier of the root isolate. /// /// @return The main port identifier. If no root isolate is running, /// returns `ILLEGAL_PORT`. /// Dart_Port GetMainPort(); //---------------------------------------------------------------------------- /// @brief Gets the debug name of the root isolate. But default, the /// debug name of the isolate is derived from its advisory script /// URI, advisory main entrypoint and its main port name. For /// example, "main.dart$main-1234" where the script URI is /// "main.dart", the entrypoint is "main" and the port name /// "1234". Once launched, the isolate may re-christen itself /// using a name it selects via `setIsolateDebugName` in /// `window.dart`. This name is purely advisory and only used by /// instrumentation and reporting purposes. /// /// @return The debug name of the root isolate. /// std::string GetIsolateName(); //---------------------------------------------------------------------------- /// @brief Returns if the root isolate has any live receive ports. /// /// @return True if there are live receive ports, False otherwise. Return /// False if the root isolate is not running as well. /// bool HasLivePorts(); //---------------------------------------------------------------------------- /// @brief Get the last error encountered by the microtask queue. /// /// @return The last error encountered by the microtask queue. /// tonic::DartErrorHandleType GetLastError(); //---------------------------------------------------------------------------- /// @brief Get the service ID of the root isolate if the root isolate is /// running. /// /// @return The root isolate service id. /// std::optional GetRootIsolateServiceID() const; //---------------------------------------------------------------------------- /// @brief Get the return code specified by the root isolate (if one is /// present). /// /// @return The root isolate return code if the isolate has specified one. /// std::optional GetRootIsolateReturnCode(); //---------------------------------------------------------------------------- /// @brief Get an identifier that represents the Dart isolate group the /// root isolate is in. /// /// @return The root isolate group identifier, zero if one can't /// be established. uint64_t GetRootIsolateGroup() const; //-------------------------------------------------------------------------- /// @brief Loads the Dart shared library into the Dart VM. When the /// Dart library is loaded successfully, the Dart future /// returned by the originating loadLibrary() call completes. /// /// The Dart compiler may generate separate shared libraries /// files called 'loading units' when libraries are imported /// as deferred. Each of these shared libraries are identified /// by a unique loading unit id. Callers should open and resolve /// a SymbolMapping from the shared library. The Mappings should /// be moved into this method, as ownership will be assumed by the /// dart root isolate after successful loading and released after /// shutdown of the root isolate. The loading unit may not be /// used after isolate shutdown. If loading fails, the mappings /// will be released. /// /// This method is paired with a RequestDartDeferredLibrary /// invocation that provides the embedder with the loading unit id /// of the deferred library to load. /// /// /// @param[in] loading_unit_id The unique id of the deferred library's /// loading unit, as passed in by /// RequestDartDeferredLibrary. /// /// @param[in] snapshot_data Dart snapshot data of the loading unit's /// shared library. /// /// @param[in] snapshot_data Dart snapshot instructions of the loading /// unit's shared library. /// void LoadDartDeferredLibrary( intptr_t loading_unit_id, std::unique_ptr snapshot_data, std::unique_ptr snapshot_instructions); //-------------------------------------------------------------------------- /// @brief Indicates to the dart VM that the request to load a deferred /// library with the specified loading unit id has failed. /// /// The dart future returned by the initiating loadLibrary() call /// will complete with an error. /// /// @param[in] loading_unit_id The unique id of the deferred library's /// loading unit, as passed in by /// RequestDartDeferredLibrary. /// /// @param[in] error_message The error message that will appear in the /// dart Future. /// /// @param[in] transient A transient error is a failure due to /// temporary conditions such as no network. /// Transient errors allow the dart VM to /// re-request the same deferred library and /// loading_unit_id again. Non-transient /// errors are permanent and attempts to /// re-request the library will instantly /// complete with an error. virtual void LoadDartDeferredLibraryError(intptr_t loading_unit_id, const std::string error_message, bool transient); // |PlatformConfigurationClient| void RequestDartDeferredLibrary(intptr_t loading_unit_id) override; // |PlatformConfigurationClient| std::shared_ptr GetPersistentIsolateData() override; const fml::WeakPtr& GetIOManager() const { return context_.io_manager; } virtual DartVM* GetDartVM() const { return vm_; } const fml::RefPtr& GetIsolateSnapshot() const { return isolate_snapshot_; } const PlatformData& GetPlatformData() const { return platform_data_; } const fml::RefPtr& GetSkiaUnrefQueue() const { return context_.unref_queue; } const fml::TaskRunnerAffineWeakPtr& GetSnapshotDelegate() const { return context_.snapshot_delegate; } std::weak_ptr GetRootIsolate() const { return root_isolate_; } std::shared_ptr GetPlatformIsolateManager() override { return platform_isolate_manager_; } //-------------------------------------------------------------------------- /// @brief Shuts down all registered platform isolates. Must be called /// from the platform thread. /// void ShutdownPlatformIsolates(); protected: /// Constructor for Mocks. RuntimeController(RuntimeDelegate& p_client, const TaskRunners& task_runners); private: struct Locale { Locale(std::string language_code_, std::string country_code_, std::string script_code_, std::string variant_code_); ~Locale(); std::string language_code; std::string country_code; std::string script_code; std::string variant_code; }; RuntimeDelegate& client_; DartVM* const vm_; fml::RefPtr isolate_snapshot_; std::function idle_notification_callback_; PlatformData platform_data_; std::weak_ptr root_isolate_; std::weak_ptr spawning_isolate_; std::optional root_isolate_return_code_; const fml::closure isolate_create_callback_; const fml::closure isolate_shutdown_callback_; std::shared_ptr persistent_isolate_data_; UIDartState::Context context_; PointerDataPacketConverter pointer_data_packet_converter_; std::shared_ptr platform_isolate_manager_ = std::shared_ptr(new PlatformIsolateManager()); bool has_flushed_runtime_state_ = false; // Callbacks when `AddView` was called before the Dart isolate is launched. // // These views will be added when `FlushRuntimeStateToIsolate` is called. // This is no longer used once the Dart isolate starts. std::unordered_map pending_add_view_callbacks_; // Tracks the views that have been called `Render` during a frame. // // If all views that have been registered by `AddView` have been called // `Render`, then the runtime controller notifies the client of the end of // frame immediately, allowing the client to submit the views to the pipeline // a bit earlier than having to wait for the end of `BeginFrame`. See also // `Animator::OnAllViewsRendered`. // // This mechanism fixes https://github.com/flutter/flutter/issues/144584 with // option 2 and // https://github.com/flutter/engine/pull/51186#issuecomment-1977820525 with // option a in most cases, except if there are multiple views and only part of // them are rendered. // TODO(dkwingsmt): Fix these problems for all cases. std::unordered_set rendered_views_during_frame_; void MarkAsFrameBorder(); void CheckIfAllViewsRendered(); PlatformConfiguration* GetPlatformConfigurationIfAvailable(); bool FlushRuntimeStateToIsolate(); // |PointerDataPacketConverter::Delegate| bool ViewExists(int64_t view_id) const override; // |PlatformConfigurationClient| std::string DefaultRouteName() override; // |PlatformConfigurationClient| void ScheduleFrame() override; // |PlatformConfigurationClient| void EndWarmUpFrame() override; // |PlatformConfigurationClient| void Render(int64_t view_id, Scene* scene, double width, double height) override; // |PlatformConfigurationClient| void UpdateSemantics(SemanticsUpdate* update) override; // |PlatformConfigurationClient| void HandlePlatformMessage(std::unique_ptr message) override; // |PlatformConfigurationClient| FontCollection& GetFontCollection() override; // |PlatformConfigurationClient| std::shared_ptr GetAssetManager() override; // |PlatformConfigurationClient| void UpdateIsolateDescription(const std::string isolate_name, int64_t isolate_port) override; // |PlatformConfigurationClient| void SetNeedsReportTimings(bool value) override; // |PlatformConfigurationClient| std::unique_ptr> ComputePlatformResolvedLocale( const std::vector& supported_locale_data) override; // |PlatformConfigurationClient| void SendChannelUpdate(std::string name, bool listening) override; // |PlatformConfigurationClient| double GetScaledFontSize(double unscaled_font_size, int configuration_id) const override; FML_DISALLOW_COPY_AND_ASSIGN(RuntimeController); }; } // namespace flutter #endif // FLUTTER_RUNTIME_RUNTIME_CONTROLLER_H_