diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 0a19c690fc6a5..0bbba217b2fd0 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1904,8 +1904,6 @@ ORIGIN: ../../../flutter/lib/ui/window/pointer_data_packet_converter.cc + ../../ ORIGIN: ../../../flutter/lib/ui/window/pointer_data_packet_converter.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/ui/window/viewport_metrics.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/ui/window/viewport_metrics.h + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/lib/ui/window/window.cc + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/lib/ui/window/window.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/annotations.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/canvas.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/channel_buffers.dart + ../../../flutter/LICENSE @@ -4634,8 +4632,6 @@ FILE: ../../../flutter/lib/ui/window/pointer_data_packet_converter.cc FILE: ../../../flutter/lib/ui/window/pointer_data_packet_converter.h FILE: ../../../flutter/lib/ui/window/viewport_metrics.cc FILE: ../../../flutter/lib/ui/window/viewport_metrics.h -FILE: ../../../flutter/lib/ui/window/window.cc -FILE: ../../../flutter/lib/ui/window/window.h FILE: ../../../flutter/lib/web_ui/lib/annotations.dart FILE: ../../../flutter/lib/web_ui/lib/canvas.dart FILE: ../../../flutter/lib/web_ui/lib/channel_buffers.dart diff --git a/common/constants.h b/common/constants.h index 378f7c84e7c0f..668aa4f64ebd2 100644 --- a/common/constants.h +++ b/common/constants.h @@ -7,6 +7,32 @@ namespace flutter { constexpr double kMegaByteSizeInBytes = (1 << 20); + +// The ID for the implicit view if the implicit view is enabled. +// +// The implicit view is a compatibility mechanism to help the transition from +// the older single-view APIs to the newer multi-view APIs. The two sets of APIs +// use different models for view management. The implicit view mechanism allows +// single-view APIs to operate a special view as if other views don't exist. +// +// In the regular multi-view model, all views should be created by +// `Shell::AddView` before being used, and removed by `Shell::RemoveView` to +// signify that they are gone. If a view is added or removed, the framework +// (`PlatformDispatcher`) will be notified. New view IDs are always unique, +// never reused. Operating a non-existing view is an error. +// +// The implicit view is another special view in addition to the "regular views" +// as above. The shell starts up having the implicit view, which has a fixed +// view ID of `kFlutterImplicitViewId` and is available throughout the lifetime +// of the shell. `Shell::AddView` or `RemoveView` must not be called for this +// view. Even when the window that shows the view is closed, the framework is +// unaware and might continue rendering into or operating this view. +// +// The single-view APIs, which are APIs that do not specify view IDs, operate +// the implicit view. The multi-view APIs can operate all views, including the +// implicit view if the target ID is `kFlutterImplicitViewId`, unless specified +// otherwise. +constexpr int64_t kFlutterImplicitViewId = 0; } // namespace flutter #endif // FLUTTER_COMMON_CONSTANTS_H_ diff --git a/lib/ui/BUILD.gn b/lib/ui/BUILD.gn index 596dac782b3d4..8ad0197bb1d8d 100644 --- a/lib/ui/BUILD.gn +++ b/lib/ui/BUILD.gn @@ -155,8 +155,6 @@ source_set("ui") { "window/pointer_data_packet_converter.h", "window/viewport_metrics.cc", "window/viewport_metrics.h", - "window/window.cc", - "window/window.h", ] public_configs = [ "//flutter:config" ] diff --git a/lib/ui/compositing/scene.cc b/lib/ui/compositing/scene.cc index 17d00ea794c2a..5e44f3fcc27bf 100644 --- a/lib/ui/compositing/scene.cc +++ b/lib/ui/compositing/scene.cc @@ -10,7 +10,6 @@ #include "flutter/lib/ui/painting/picture.h" #include "flutter/lib/ui/ui_dart_state.h" #include "flutter/lib/ui/window/platform_configuration.h" -#include "flutter/lib/ui/window/window.h" #if IMPELLER_SUPPORTS_RENDERING #include "flutter/lib/ui/painting/display_list_deferred_image_gpu_impeller.h" #endif // IMPELLER_SUPPORTS_RENDERING diff --git a/lib/ui/dart_ui.cc b/lib/ui/dart_ui.cc index b2fdbf88d4fc4..02a6b79978241 100644 --- a/lib/ui/dart_ui.cc +++ b/lib/ui/dart_ui.cc @@ -7,6 +7,7 @@ #include #include +#include "flutter/common/constants.h" #include "flutter/common/settings.h" #include "flutter/fml/build_config.h" #include "flutter/lib/ui/compositing/scene.h" @@ -95,7 +96,6 @@ typedef CanvasPath Path; V(IsolateNameServerNatives::RemovePortNameMapping, 1) \ V(NativeStringAttribute::initLocaleStringAttribute, 4) \ V(NativeStringAttribute::initSpellOutStringAttribute, 3) \ - V(PlatformConfigurationNativeApi::ImplicitViewEnabled, 0) \ V(PlatformConfigurationNativeApi::DefaultRouteName, 0) \ V(PlatformConfigurationNativeApi::ScheduleFrame, 0) \ V(PlatformConfigurationNativeApi::Render, 1) \ @@ -373,6 +373,12 @@ void DartUI::InitForIsolate(const Settings& settings) { Dart_PropagateError(result); } } + + result = Dart_SetField(dart_ui, ToDart("_implicitViewId"), + Dart_NewInteger(kFlutterImplicitViewId)); + if (Dart_IsError(result)) { + Dart_PropagateError(result); + } } } // namespace flutter diff --git a/lib/ui/hooks.dart b/lib/ui/hooks.dart index 7d31bd4f0c2f1..5c2244d43a9ad 100644 --- a/lib/ui/hooks.dart +++ b/lib/ui/hooks.dart @@ -4,6 +4,60 @@ part of dart.ui; +@pragma('vm:entry-point') +void _addView( + int viewId, + double devicePixelRatio, + double width, + double height, + double viewPaddingTop, + double viewPaddingRight, + double viewPaddingBottom, + double viewPaddingLeft, + double viewInsetTop, + double viewInsetRight, + double viewInsetBottom, + double viewInsetLeft, + double systemGestureInsetTop, + double systemGestureInsetRight, + double systemGestureInsetBottom, + double systemGestureInsetLeft, + double physicalTouchSlop, + List displayFeaturesBounds, + List displayFeaturesType, + List displayFeaturesState, + int displayId, +) { + final _ViewConfiguration viewConfiguration = _buildViewConfiguration( + devicePixelRatio, + width, + height, + viewPaddingTop, + viewPaddingRight, + viewPaddingBottom, + viewPaddingLeft, + viewInsetTop, + viewInsetRight, + viewInsetBottom, + viewInsetLeft, + systemGestureInsetTop, + systemGestureInsetRight, + systemGestureInsetBottom, + systemGestureInsetLeft, + physicalTouchSlop, + displayFeaturesBounds, + displayFeaturesType, + displayFeaturesState, + displayId, + ); + PlatformDispatcher.instance._addView(viewId, viewConfiguration); +} + +@pragma('vm:entry-point') +void _removeView(int viewId) { + PlatformDispatcher.instance._removeView(viewId); +} + @pragma('vm:entry-point') void _updateDisplays( List ids, diff --git a/lib/ui/natives.dart b/lib/ui/natives.dart index 44ab3e6092e0f..72a55ab8b7d57 100644 --- a/lib/ui/natives.dart +++ b/lib/ui/natives.dart @@ -112,3 +112,12 @@ _ScheduleImmediateClosure _getScheduleMicrotaskClosure() => _scheduleMicrotask; // rendering. @pragma('vm:entry-point') bool _impellerEnabled = false; + +// Used internally to indicate whether the embedder enables the implicit view, +// and the implicit view's ID if so. +// +// The exact value of this variable is an implementation detail that may change +// at any time. Apps should always use PlatformDispatcher.implicitView to +// determine the current implicit view, if any. +@pragma('vm:entry-point') +int? _implicitViewId; diff --git a/lib/ui/painting/canvas.cc b/lib/ui/painting/canvas.cc index 403a4c3f440a1..0b090b68c9a7b 100644 --- a/lib/ui/painting/canvas.cc +++ b/lib/ui/painting/canvas.cc @@ -13,7 +13,6 @@ #include "flutter/lib/ui/painting/paint.h" #include "flutter/lib/ui/ui_dart_state.h" #include "flutter/lib/ui/window/platform_configuration.h" -#include "flutter/lib/ui/window/window.h" using tonic::ToDart; @@ -621,11 +620,16 @@ void Canvas::drawShadow(const CanvasPath* path, } // Not using SafeNarrow because DPR will always be a relatively small number. - SkScalar dpr = static_cast(UIDartState::Current() - ->platform_configuration() - ->get_window(0) - ->viewport_metrics() - .device_pixel_ratio); + const ViewportMetrics* metrics = + UIDartState::Current()->platform_configuration()->GetMetrics(0); + SkScalar dpr; + // TODO(dkwingsmt): We should support rendering shadows on non-implicit views. + // However, currently this method has no way to get the target view ID. + if (metrics == nullptr) { + dpr = 1.0f; + } else { + dpr = static_cast(metrics->device_pixel_ratio); + } if (display_list_builder_) { // The DrawShadow mechanism results in non-public operations to be // performed on the canvas involving an SkDrawShadowRec. Since we diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index a8ff3144b6a28..c8ebb246ae548 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -66,14 +66,6 @@ typedef ErrorCallback = bool Function(Object exception, StackTrace stackTrace); // A gesture setting value that indicates it has not been set by the engine. const double _kUnsetGestureSetting = -1.0; -// The view ID of PlatformDispatcher.implicitView. This is an -// implementation detail that may change at any time. Apps -// should always use PlatformDispatcher.implicitView to determine -// the current implicit view, if any. -// -// Keep this in sync with kImplicitViewId in window/platform_configuration.cc. -const int _kImplicitViewId = 0; - // A message channel to receive KeyData from the platform. // // See embedder.cc::kFlutterKeyDataChannel for more information. @@ -218,10 +210,29 @@ class PlatformDispatcher { /// * [View.of], for accessing the current view. /// * [PlatformDispatcher.views] for a list of all [FlutterView]s provided /// by the platform. - FlutterView? get implicitView => _implicitViewEnabled() ? _views[_kImplicitViewId] : null; - - @Native(symbol: 'PlatformConfigurationNativeApi::ImplicitViewEnabled') - external static bool _implicitViewEnabled(); + FlutterView? get implicitView { + final FlutterView? result = _views[_implicitViewId]; + // Make sure [implicitView] agrees with `_implicitViewId`. + assert((result != null) == (_implicitViewId != null), + (_implicitViewId != null) ? + 'The implicit view ID is $_implicitViewId, but the implicit view does not exist.' : + 'The implicit view ID is null, but the implicit view exists.'); + // Make sure [implicitView] never chages. + assert(() { + if (_debugRecordedLastImplicitView) { + assert(identical(_debugLastImplicitView, result), + 'The implicitView has changed:\n' + 'Last: $_debugLastImplicitView\nCurrent: $result'); + } else { + _debugLastImplicitView = result; + _debugRecordedLastImplicitView = true; + } + return true; + }()); + return result; + } + FlutterView? _debugLastImplicitView; + bool _debugRecordedLastImplicitView = false; /// A callback that is invoked whenever the [ViewConfiguration] of any of the /// [views] changes. @@ -249,6 +260,34 @@ class PlatformDispatcher { _onMetricsChangedZone = Zone.current; } + // Called from the engine, via hooks.dart + // + // Adds a new view with the specific view configuration. + // + // The implicit view must be added before [implicitView] is first called, + // which is typically the main function. + void _addView(int id, _ViewConfiguration viewConfiguration) { + assert(!_views.containsKey(id), 'View ID $id already exists.'); + _views[id] = FlutterView._(id, this, viewConfiguration); + _invoke(onMetricsChanged, _onMetricsChangedZone); + } + + // Called from the engine, via hooks.dart + // + // Removes the specific view. + // + // The target view must must exist. The implicit view must not be removed, + // or an assertion will be triggered. + void _removeView(int id) { + assert(id != _implicitViewId, 'The implicit view #$id can not be removed.'); + if (id == _implicitViewId) { + return; + } + assert(_views.containsKey(id), 'View ID $id does not exist.'); + _views.remove(id); + _invoke(onMetricsChanged, _onMetricsChangedZone); + } + // Called from the engine, via hooks.dart. // // Updates the available displays. @@ -264,15 +303,8 @@ class PlatformDispatcher { // // Updates the metrics of the window with the given id. void _updateWindowMetrics(int viewId, _ViewConfiguration viewConfiguration) { - final FlutterView? view = _views[viewId]; - if (viewId == _kImplicitViewId && view == null) { - // TODO(goderbauer): Remove the implicit creation of the implicit view - // when we have an addView API and the implicit view is added via that. - _views[viewId] = FlutterView._(viewId, this, viewConfiguration); - } else { - assert(view != null); - view!._viewConfiguration = viewConfiguration; - } + assert(_views.containsKey(viewId), 'View $viewId does not exist.'); + _views[viewId]!._viewConfiguration = viewConfiguration; _invoke(onMetricsChanged, _onMetricsChangedZone); } diff --git a/lib/ui/ui_dart_state.cc b/lib/ui/ui_dart_state.cc index e66059fc515b3..4af7f0837d81e 100644 --- a/lib/ui/ui_dart_state.cc +++ b/lib/ui/ui_dart_state.cc @@ -9,6 +9,7 @@ #include "flutter/fml/message_loop.h" #include "flutter/lib/ui/window/platform_configuration.h" +#include "flutter/lib/ui/window/platform_message.h" #include "third_party/tonic/converter/dart_converter.h" #include "third_party/tonic/dart_message_handler.h" diff --git a/lib/ui/window.dart b/lib/ui/window.dart index fb0a65e800963..d86781ef2567f 100644 --- a/lib/ui/window.dart +++ b/lib/ui/window.dart @@ -406,7 +406,11 @@ class SingletonFlutterWindow extends FlutterView { 'This feature was deprecated after v3.7.0-32.0.pre.' ) SingletonFlutterWindow._() : super._( - _kImplicitViewId, + // TODO(dkwingsmt): This crashes if the implicit view is disabled. We need + // to resolve this by the time embedders are allowed to disable the implicit + // view. + // https://github.com/flutter/flutter/issues/131651 + _implicitViewId!, PlatformDispatcher.instance, const _ViewConfiguration(), ); diff --git a/lib/ui/window/platform_configuration.cc b/lib/ui/window/platform_configuration.cc index 8d11b747fd53b..c796bdfac5ef4 100644 --- a/lib/ui/window/platform_configuration.cc +++ b/lib/ui/window/platform_configuration.cc @@ -6,12 +6,13 @@ #include +#include "flutter/common/constants.h" #include "flutter/lib/ui/compositing/scene.h" #include "flutter/lib/ui/ui_dart_state.h" +#include "flutter/lib/ui/window/platform_message.h" #include "flutter/lib/ui/window/platform_message_response_dart.h" #include "flutter/lib/ui/window/platform_message_response_dart_port.h" #include "flutter/lib/ui/window/viewport_metrics.h" -#include "flutter/lib/ui/window/window.h" #include "third_party/tonic/converter/dart_converter.h" #include "third_party/tonic/dart_args.h" #include "third_party/tonic/dart_library_natives.h" @@ -22,9 +23,6 @@ namespace flutter { namespace { -// Keep this in sync with _kImplicitViewId in ../platform_dispatcher.dart. -constexpr int kImplicitViewId = 0; - Dart_Handle ToByteData(const fml::Mapping& buffer) { return tonic::DartByteData::Create(buffer.GetMapping(), buffer.GetSize()); } @@ -44,6 +42,13 @@ void PlatformConfiguration::DidCreateIsolate() { on_error_.Set(tonic::DartState::Current(), Dart_GetField(library, tonic::ToDart("_onError"))); + add_view_.Set(tonic::DartState::Current(), + Dart_GetField(library, tonic::ToDart("_addView"))); + remove_view_.Set(tonic::DartState::Current(), + Dart_GetField(library, tonic::ToDart("_removeView"))); + update_window_metrics_.Set( + tonic::DartState::Current(), + Dart_GetField(library, tonic::ToDart("_updateWindowMetrics"))); update_displays_.Set( tonic::DartState::Current(), Dart_GetField(library, tonic::ToDart("_updateDisplays"))); @@ -76,13 +81,110 @@ void PlatformConfiguration::DidCreateIsolate() { Dart_GetField(library, tonic::ToDart("_drawFrame"))); report_timings_.Set(tonic::DartState::Current(), Dart_GetField(library, tonic::ToDart("_reportTimings"))); +} - // TODO(loicsharma): This should only be created if the embedder enables the - // implicit view. - // See: https://github.com/flutter/flutter/issues/120306 - windows_.emplace(kImplicitViewId, - std::make_unique( - kImplicitViewId, ViewportMetrics{1.0, 0.0, 0.0, -1, 0})); +void PlatformConfiguration::AddView(int64_t view_id, + const ViewportMetrics& view_metrics) { + auto [view_iterator, insertion_happened] = + metrics_.emplace(view_id, view_metrics); + FML_DCHECK(insertion_happened); + + std::shared_ptr dart_state = add_view_.dart_state().lock(); + if (!dart_state) { + return; + } + tonic::DartState::Scope scope(dart_state); + tonic::CheckAndHandleError(tonic::DartInvoke( + add_view_.Get(), + { + tonic::ToDart(view_id), + tonic::ToDart(view_metrics.device_pixel_ratio), + tonic::ToDart(view_metrics.physical_width), + tonic::ToDart(view_metrics.physical_height), + tonic::ToDart(view_metrics.physical_padding_top), + tonic::ToDart(view_metrics.physical_padding_right), + tonic::ToDart(view_metrics.physical_padding_bottom), + tonic::ToDart(view_metrics.physical_padding_left), + tonic::ToDart(view_metrics.physical_view_inset_top), + tonic::ToDart(view_metrics.physical_view_inset_right), + tonic::ToDart(view_metrics.physical_view_inset_bottom), + tonic::ToDart(view_metrics.physical_view_inset_left), + tonic::ToDart(view_metrics.physical_system_gesture_inset_top), + tonic::ToDart(view_metrics.physical_system_gesture_inset_right), + tonic::ToDart(view_metrics.physical_system_gesture_inset_bottom), + tonic::ToDart(view_metrics.physical_system_gesture_inset_left), + tonic::ToDart(view_metrics.physical_touch_slop), + tonic::ToDart(view_metrics.physical_display_features_bounds), + tonic::ToDart(view_metrics.physical_display_features_type), + tonic::ToDart(view_metrics.physical_display_features_state), + tonic::ToDart(view_metrics.display_id), + })); +} + +void PlatformConfiguration::RemoveView(int64_t view_id) { + if (view_id == kFlutterImplicitViewId) { + FML_LOG(ERROR) << "The implicit view #" << view_id << " cannot be removed."; + FML_DCHECK(false); + return; + } + size_t erased_elements = metrics_.erase(view_id); + FML_DCHECK(erased_elements != 0) << "View #" << view_id << " doesn't exist."; + (void)erased_elements; // Suppress unused variable warning + + std::shared_ptr dart_state = + remove_view_.dart_state().lock(); + if (!dart_state) { + return; + } + tonic::DartState::Scope scope(dart_state); + tonic::CheckAndHandleError( + tonic::DartInvoke(remove_view_.Get(), { + tonic::ToDart(view_id), + })); +} + +bool PlatformConfiguration::UpdateViewMetrics( + int64_t view_id, + const ViewportMetrics& view_metrics) { + auto found_iter = metrics_.find(view_id); + if (found_iter == metrics_.end()) { + return false; + } + + found_iter->second = view_metrics; + + std::shared_ptr dart_state = + update_window_metrics_.dart_state().lock(); + if (!dart_state) { + return false; + } + tonic::DartState::Scope scope(dart_state); + tonic::CheckAndHandleError(tonic::DartInvoke( + update_window_metrics_.Get(), + { + tonic::ToDart(view_id), + tonic::ToDart(view_metrics.device_pixel_ratio), + tonic::ToDart(view_metrics.physical_width), + tonic::ToDart(view_metrics.physical_height), + tonic::ToDart(view_metrics.physical_padding_top), + tonic::ToDart(view_metrics.physical_padding_right), + tonic::ToDart(view_metrics.physical_padding_bottom), + tonic::ToDart(view_metrics.physical_padding_left), + tonic::ToDart(view_metrics.physical_view_inset_top), + tonic::ToDart(view_metrics.physical_view_inset_right), + tonic::ToDart(view_metrics.physical_view_inset_bottom), + tonic::ToDart(view_metrics.physical_view_inset_left), + tonic::ToDart(view_metrics.physical_system_gesture_inset_top), + tonic::ToDart(view_metrics.physical_system_gesture_inset_right), + tonic::ToDart(view_metrics.physical_system_gesture_inset_bottom), + tonic::ToDart(view_metrics.physical_system_gesture_inset_left), + tonic::ToDart(view_metrics.physical_touch_slop), + tonic::ToDart(view_metrics.physical_display_features_bounds), + tonic::ToDart(view_metrics.physical_display_features_type), + tonic::ToDart(view_metrics.physical_display_features_state), + tonic::ToDart(view_metrics.display_id), + })); + return true; } void PlatformConfiguration::UpdateDisplays( @@ -309,10 +411,10 @@ void PlatformConfiguration::ReportTimings(std::vector timings) { })); } -Window* PlatformConfiguration::get_window(int window_id) { - auto found = windows_.find(window_id); - if (found != windows_.end()) { - return found->second.get(); +const ViewportMetrics* PlatformConfiguration::GetMetrics(int view_id) { + auto found = metrics_.find(view_id); + if (found != metrics_.end()) { + return &found->second; } else { return nullptr; } @@ -502,16 +604,6 @@ Dart_Handle PlatformConfigurationNativeApi::ComputePlatformResolvedLocale( return tonic::DartConverter>::ToDart(results); } -Dart_Handle PlatformConfigurationNativeApi::ImplicitViewEnabled() { - UIDartState::ThrowIfUIOperationsProhibited(); - bool enabled = UIDartState::Current() - ->platform_configuration() - ->client() - ->ImplicitViewEnabled(); - - return Dart_NewBoolean(enabled); -} - std::string PlatformConfigurationNativeApi::DefaultRouteName() { UIDartState::ThrowIfUIOperationsProhibited(); return UIDartState::Current() diff --git a/lib/ui/window/platform_configuration.h b/lib/ui/window/platform_configuration.h index 9226b457d07e8..90000890dde9f 100644 --- a/lib/ui/window/platform_configuration.h +++ b/lib/ui/window/platform_configuration.h @@ -14,9 +14,9 @@ #include "flutter/assets/asset_manager.h" #include "flutter/fml/time/time_point.h" #include "flutter/lib/ui/semantics/semantics_update.h" +#include "flutter/lib/ui/window/platform_message_response.h" #include "flutter/lib/ui/window/pointer_data_packet.h" #include "flutter/lib/ui/window/viewport_metrics.h" -#include "flutter/lib/ui/window/window.h" #include "flutter/shell/common/display.h" #include "third_party/tonic/dart_persistent_value.h" #include "third_party/tonic/typed_data/dart_byte_data.h" @@ -50,16 +50,6 @@ enum class AccessibilityFeatureFlag : int32_t { /// class PlatformConfigurationClient { public: - //-------------------------------------------------------------------------- - /// @brief Whether the platform provides an implicit view. If true, - /// the Framework may assume that it can always render into - /// the view with ID 0. - /// - /// This value must not change for the lifetime of the - /// application. - /// - virtual bool ImplicitViewEnabled() = 0; - //-------------------------------------------------------------------------- /// @brief The route or path that the embedder requested when the /// application was launched. @@ -268,6 +258,42 @@ class PlatformConfiguration final { /// void DidCreateIsolate(); + //---------------------------------------------------------------------------- + /// @brief Notify the framework 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 + /// triggers an assertion. + /// + /// @param[in] view_id The ID of the new view. + /// @param[in] viewport_metrics The initial viewport metrics for the view. + /// + void AddView(int64_t view_id, const ViewportMetrics& view_metrics); + + //---------------------------------------------------------------------------- + /// @brief Notify the framework that a view is no longer available. + /// + /// Removing a view that does not exist triggers an assertion. + /// + /// The implicit view (kFlutterImplicitViewId) should never be + /// removed. Doing so triggers an assertion. + /// + /// @param[in] view_id The ID of the view. + /// + void RemoveView(int64_t view_id); + + //---------------------------------------------------------------------------- + /// @brief Update the view metrics for the specified view. + /// + /// If the view is not found, silently return false. + /// + /// @param[in] view_id The ID of the view. + /// @param[in] metrics The new metrics of the view. + /// + /// @return Whether the view is found. + /// + bool UpdateViewMetrics(int64_t view_id, const ViewportMetrics& metrics); + //---------------------------------------------------------------------------- /// @brief Update the specified display data in the framework. /// @@ -416,7 +442,7 @@ class PlatformConfiguration final { /// @return a pointer to the Window. Returns nullptr if the ID is not /// found. /// - Window* get_window(int window_id); + const ViewportMetrics* GetMetrics(int view_id); //---------------------------------------------------------------------------- /// @brief Responds to a previous platform message to the engine from the @@ -443,6 +469,9 @@ class PlatformConfiguration final { private: PlatformConfigurationClient* client_; tonic::DartPersistentValue on_error_; + tonic::DartPersistentValue add_view_; + tonic::DartPersistentValue remove_view_; + tonic::DartPersistentValue update_window_metrics_; tonic::DartPersistentValue update_displays_; tonic::DartPersistentValue update_locales_; tonic::DartPersistentValue update_user_settings_data_; @@ -456,7 +485,8 @@ class PlatformConfiguration final { tonic::DartPersistentValue draw_frame_; tonic::DartPersistentValue report_timings_; - std::unordered_map> windows_; + // All current views' view metrics mapped from view IDs. + std::unordered_map metrics_; // ID starts at 1 because an ID of 0 indicates that no response is expected. int next_response_id_ = 1; @@ -490,8 +520,6 @@ class PlatformMessageHandlerStorage { //---------------------------------------------------------------------------- class PlatformConfigurationNativeApi { public: - static Dart_Handle ImplicitViewEnabled(); - static std::string DefaultRouteName(); static void ScheduleFrame(); diff --git a/lib/ui/window/platform_configuration_unittests.cc b/lib/ui/window/platform_configuration_unittests.cc index 017055a41d91e..7410caeb66d6c 100644 --- a/lib/ui/window/platform_configuration_unittests.cc +++ b/lib/ui/window/platform_configuration_unittests.cc @@ -24,21 +24,17 @@ class PlatformConfigurationTest : public ShellTest {}; TEST_F(PlatformConfigurationTest, Initialization) { auto message_latch = std::make_shared(); - auto nativeValidateConfiguration = [message_latch]( - Dart_NativeArguments args) { - PlatformConfiguration* configuration = - UIDartState::Current()->platform_configuration(); - ASSERT_NE(configuration->get_window(0), nullptr); - ASSERT_EQ( - configuration->get_window(0)->viewport_metrics().device_pixel_ratio, - 1.0); - ASSERT_EQ(configuration->get_window(0)->viewport_metrics().physical_width, - 0.0); - ASSERT_EQ(configuration->get_window(0)->viewport_metrics().physical_height, - 0.0); + auto nativeValidateConfiguration = + [message_latch](Dart_NativeArguments args) { + PlatformConfiguration* configuration = + UIDartState::Current()->platform_configuration(); + ASSERT_NE(configuration->GetMetrics(0), nullptr); + ASSERT_EQ(configuration->GetMetrics(0)->device_pixel_ratio, 1.0); + ASSERT_EQ(configuration->GetMetrics(0)->physical_width, 0.0); + ASSERT_EQ(configuration->GetMetrics(0)->physical_height, 0.0); - message_latch->Signal(); - }; + message_latch->Signal(); + }; Settings settings = CreateSettingsForFixture(); TaskRunners task_runners("test", // label @@ -68,27 +64,22 @@ TEST_F(PlatformConfigurationTest, Initialization) { TEST_F(PlatformConfigurationTest, WindowMetricsUpdate) { auto message_latch = std::make_shared(); - auto nativeValidateConfiguration = [message_latch]( - Dart_NativeArguments args) { - PlatformConfiguration* configuration = - UIDartState::Current()->platform_configuration(); - - ASSERT_NE(configuration->get_window(0), nullptr); - configuration->get_window(0)->UpdateWindowMetrics( - ViewportMetrics{2.0, 10.0, 20.0, 22, 0}); - ASSERT_EQ( - configuration->get_window(0)->viewport_metrics().device_pixel_ratio, - 2.0); - ASSERT_EQ(configuration->get_window(0)->viewport_metrics().physical_width, - 10.0); - ASSERT_EQ(configuration->get_window(0)->viewport_metrics().physical_height, - 20.0); - ASSERT_EQ( - configuration->get_window(0)->viewport_metrics().physical_touch_slop, - 22); + auto nativeValidateConfiguration = + [message_latch](Dart_NativeArguments args) { + PlatformConfiguration* configuration = + UIDartState::Current()->platform_configuration(); - message_latch->Signal(); - }; + ASSERT_NE(configuration->GetMetrics(0), nullptr); + bool has_view = configuration->UpdateViewMetrics( + 0, ViewportMetrics{2.0, 10.0, 20.0, 22, 0}); + ASSERT_TRUE(has_view); + ASSERT_EQ(configuration->GetMetrics(0)->device_pixel_ratio, 2.0); + ASSERT_EQ(configuration->GetMetrics(0)->physical_width, 10.0); + ASSERT_EQ(configuration->GetMetrics(0)->physical_height, 20.0); + ASSERT_EQ(configuration->GetMetrics(0)->physical_touch_slop, 22); + + message_latch->Signal(); + }; Settings settings = CreateSettingsForFixture(); TaskRunners task_runners("test", // label @@ -123,8 +114,8 @@ TEST_F(PlatformConfigurationTest, GetWindowReturnsNullForNonexistentId) { PlatformConfiguration* configuration = UIDartState::Current()->platform_configuration(); - ASSERT_EQ(configuration->get_window(1), nullptr); - ASSERT_EQ(configuration->get_window(2), nullptr); + ASSERT_EQ(configuration->GetMetrics(1), nullptr); + ASSERT_EQ(configuration->GetMetrics(2), nullptr); message_latch->Signal(); }; diff --git a/lib/ui/window/window.cc b/lib/ui/window/window.cc deleted file mode 100644 index f3866f2ebc56a..0000000000000 --- a/lib/ui/window/window.cc +++ /dev/null @@ -1,59 +0,0 @@ -// 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. - -#include "flutter/lib/ui/window/window.h" - -#include - -#include "third_party/tonic/converter/dart_converter.h" -#include "third_party/tonic/dart_args.h" -#include "third_party/tonic/logging/dart_invoke.h" -#include "third_party/tonic/typed_data/dart_byte_data.h" - -namespace flutter { - -Window::Window(int64_t window_id, ViewportMetrics metrics) - : window_id_(window_id), viewport_metrics_(std::move(metrics)) { - library_.Set(tonic::DartState::Current(), - Dart_LookupLibrary(tonic::ToDart("dart:ui"))); -} - -Window::~Window() {} - -void Window::UpdateWindowMetrics(const ViewportMetrics& metrics) { - viewport_metrics_ = metrics; - - std::shared_ptr dart_state = library_.dart_state().lock(); - if (!dart_state) { - return; - } - tonic::DartState::Scope scope(dart_state); - tonic::CheckAndHandleError(tonic::DartInvokeField( - library_.value(), "_updateWindowMetrics", - { - tonic::ToDart(window_id_), - tonic::ToDart(metrics.device_pixel_ratio), - tonic::ToDart(metrics.physical_width), - tonic::ToDart(metrics.physical_height), - tonic::ToDart(metrics.physical_padding_top), - tonic::ToDart(metrics.physical_padding_right), - tonic::ToDart(metrics.physical_padding_bottom), - tonic::ToDart(metrics.physical_padding_left), - tonic::ToDart(metrics.physical_view_inset_top), - tonic::ToDart(metrics.physical_view_inset_right), - tonic::ToDart(metrics.physical_view_inset_bottom), - tonic::ToDart(metrics.physical_view_inset_left), - tonic::ToDart(metrics.physical_system_gesture_inset_top), - tonic::ToDart(metrics.physical_system_gesture_inset_right), - tonic::ToDart(metrics.physical_system_gesture_inset_bottom), - tonic::ToDart(metrics.physical_system_gesture_inset_left), - tonic::ToDart(metrics.physical_touch_slop), - tonic::ToDart(metrics.physical_display_features_bounds), - tonic::ToDart(metrics.physical_display_features_type), - tonic::ToDart(metrics.physical_display_features_state), - tonic::ToDart(metrics.display_id), - })); -} - -} // namespace flutter diff --git a/lib/ui/window/window.h b/lib/ui/window/window.h deleted file mode 100644 index 95153f6559b7b..0000000000000 --- a/lib/ui/window/window.h +++ /dev/null @@ -1,41 +0,0 @@ -// 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_LIB_UI_WINDOW_WINDOW_H_ -#define FLUTTER_LIB_UI_WINDOW_WINDOW_H_ - -#include -#include -#include -#include - -#include "flutter/lib/ui/window/key_data_packet.h" -#include "flutter/lib/ui/window/platform_message.h" -#include "flutter/lib/ui/window/pointer_data_packet.h" -#include "flutter/lib/ui/window/viewport_metrics.h" -#include "third_party/skia/include/gpu/GrDirectContext.h" -#include "third_party/tonic/dart_persistent_value.h" - -namespace flutter { -class Window final { - public: - Window(int64_t window_id, ViewportMetrics metrics); - - ~Window(); - - int window_id() const { return window_id_; } - - const ViewportMetrics& viewport_metrics() const { return viewport_metrics_; } - - void UpdateWindowMetrics(const ViewportMetrics& metrics); - - private: - tonic::DartPersistentValue library_; - int64_t window_id_; - ViewportMetrics viewport_metrics_; -}; - -} // namespace flutter - -#endif // FLUTTER_LIB_UI_WINDOW_WINDOW_H_ diff --git a/runtime/platform_data.h b/runtime/platform_data.h index b6d4c65f94d19..f7f00e1380b76 100644 --- a/runtime/platform_data.h +++ b/runtime/platform_data.h @@ -32,7 +32,9 @@ struct PlatformData { ~PlatformData(); - ViewportMetrics viewport_metrics; + // A map from view IDs of existing views to their viewport metrics. + std::unordered_map viewport_metrics_for_views; + std::string language_code; std::string country_code; std::string script_code; diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index 751fa696a5719..a11b9c2ff24f3 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -6,13 +6,14 @@ #include +#include "flutter/common/constants.h" +#include "flutter/common/settings.h" #include "flutter/fml/message_loop.h" #include "flutter/fml/trace_event.h" #include "flutter/lib/ui/compositing/scene.h" #include "flutter/lib/ui/ui_dart_state.h" #include "flutter/lib/ui/window/platform_configuration.h" #include "flutter/lib/ui/window/viewport_metrics.h" -#include "flutter/lib/ui/window/window.h" #include "flutter/runtime/dart_isolate_group_data.h" #include "flutter/runtime/isolate_configuration.h" #include "flutter/runtime/runtime_delegate.h" @@ -20,8 +21,6 @@ namespace flutter { -constexpr uint64_t kFlutterImplicitViewId = 0ll; - RuntimeController::RuntimeController(RuntimeDelegate& p_client, const TaskRunners& task_runners) : client_(p_client), vm_(nullptr), context_(task_runners) {} @@ -76,7 +75,6 @@ std::unique_ptr RuntimeController::Spawn( p_persistent_isolate_data, // spawned_context); // result->spawning_isolate_ = root_isolate_; - result->platform_data_.viewport_metrics = ViewportMetrics(); return result; } @@ -115,11 +113,16 @@ std::unique_ptr RuntimeController::Clone() const { } bool RuntimeController::FlushRuntimeStateToIsolate() { - // TODO(dkwingsmt): Needs a view ID here (or platform_data should probably - // have multiple view metrics). - return SetViewportMetrics(kFlutterImplicitViewId, - platform_data_.viewport_metrics) && - SetLocales(platform_data_.locale_data) && + FML_DCHECK(!has_flushed_runtime_state_) + << "FlushRuntimeStateToIsolate is called more than once somehow."; + has_flushed_runtime_state_ = true; + for (auto const& [view_id, viewport_metrics] : + platform_data_.viewport_metrics_for_views) { + if (!AddView(view_id, viewport_metrics)) { + return false; + } + } + return SetLocales(platform_data_.locale_data) && SetSemanticsEnabled(platform_data_.semantics_enabled) && SetAccessibilityFeatures( platform_data_.accessibility_feature_flags_) && @@ -128,19 +131,35 @@ bool RuntimeController::FlushRuntimeStateToIsolate() { SetDisplays(platform_data_.displays); } +bool RuntimeController::AddView(int64_t view_id, + const ViewportMetrics& view_metrics) { + platform_data_.viewport_metrics_for_views[view_id] = view_metrics; + if (auto* platform_configuration = GetPlatformConfigurationIfAvailable()) { + platform_configuration->AddView(view_id, view_metrics); + + return true; + } + + return false; +} + +bool RuntimeController::RemoveView(int64_t view_id) { + platform_data_.viewport_metrics_for_views.erase(view_id); + if (auto* platform_configuration = GetPlatformConfigurationIfAvailable()) { + platform_configuration->RemoveView(view_id); + return true; + } + + return false; +} + bool RuntimeController::SetViewportMetrics(int64_t view_id, const ViewportMetrics& metrics) { TRACE_EVENT0("flutter", "SetViewportMetrics"); - platform_data_.viewport_metrics = metrics; + platform_data_.viewport_metrics_for_views[view_id] = metrics; if (auto* platform_configuration = GetPlatformConfigurationIfAvailable()) { - Window* window = platform_configuration->get_window(view_id); - if (window) { - window->UpdateWindowMetrics(metrics); - return true; - } else { - FML_LOG(WARNING) << "View ID " << view_id << " does not exist."; - } + return platform_configuration->UpdateViewMetrics(view_id, metrics); } return false; @@ -311,11 +330,6 @@ RuntimeController::GetPlatformConfigurationIfAvailable() { return root_isolate ? root_isolate->platform_configuration() : nullptr; } -// |PlatformConfigurationClient| -bool RuntimeController::ImplicitViewEnabled() { - return client_.ImplicitViewEnabled(); -} - // |PlatformConfigurationClient| std::string RuntimeController::DefaultRouteName() { return client_.DefaultRouteName(); @@ -330,15 +344,14 @@ void RuntimeController::ScheduleFrame() { void RuntimeController::Render(Scene* scene) { // TODO(dkwingsmt): Currently only supports a single window. int64_t view_id = kFlutterImplicitViewId; - auto window = - UIDartState::Current()->platform_configuration()->get_window(view_id); - if (window == nullptr) { + const ViewportMetrics* view_metrics = + UIDartState::Current()->platform_configuration()->GetMetrics(view_id); + if (view_metrics == nullptr) { return; } - const auto& viewport_metrics = window->viewport_metrics(); - client_.Render(scene->takeLayerTree(viewport_metrics.physical_width, - viewport_metrics.physical_height), - viewport_metrics.device_pixel_ratio); + client_.Render(scene->takeLayerTree(view_metrics->physical_width, + view_metrics->physical_height), + view_metrics->device_pixel_ratio); } // |PlatformConfigurationClient| diff --git a/runtime/runtime_controller.h b/runtime/runtime_controller.h index aaef6da631694..1d1b0d7407e62 100644 --- a/runtime/runtime_controller.h +++ b/runtime/runtime_controller.h @@ -42,6 +42,11 @@ class Window; /// 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 { public: //---------------------------------------------------------------------------- @@ -163,6 +168,30 @@ class RuntimeController : public PlatformConfigurationClient { /// 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 + /// triggers an assertion. + /// + /// @param[in] view_id The ID of the new view. + /// @param[in] viewport_metrics The initial viewport metrics for the view. + /// + bool AddView(int64_t view_id, const ViewportMetrics& view_metrics); + + //---------------------------------------------------------------------------- + /// @brief Notify the isolate that a view is no longer available. + /// + /// Removing a view that does not exist triggers an assertion. + /// + /// The implicit view (kFlutterImplicitViewId) should never be + /// removed. Doing so triggers an assertion. + /// + /// @param[in] view_id The ID of the view. + /// + 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 @@ -616,14 +645,12 @@ class RuntimeController : public PlatformConfigurationClient { const fml::closure isolate_shutdown_callback_; std::shared_ptr persistent_isolate_data_; UIDartState::Context context_; + bool has_flushed_runtime_state_ = false; PlatformConfiguration* GetPlatformConfigurationIfAvailable(); bool FlushRuntimeStateToIsolate(); - // |PlatformConfigurationClient| - bool ImplicitViewEnabled() override; - // |PlatformConfigurationClient| std::string DefaultRouteName() override; diff --git a/runtime/runtime_delegate.h b/runtime/runtime_delegate.h index 18e4dbfdfd31c..330fc46e1d6a5 100644 --- a/runtime/runtime_delegate.h +++ b/runtime/runtime_delegate.h @@ -21,8 +21,6 @@ namespace flutter { class RuntimeDelegate { public: - virtual bool ImplicitViewEnabled() = 0; - virtual std::string DefaultRouteName() = 0; virtual void ScheduleFrame(bool regenerate_layer_tree = true) = 0; diff --git a/shell/common/engine.cc b/shell/common/engine.cc index 829e3fd60bed0..d87acb8b879ba 100644 --- a/shell/common/engine.cc +++ b/shell/common/engine.cc @@ -292,6 +292,14 @@ tonic::DartErrorHandleType Engine::GetUIIsolateLastError() { return runtime_controller_->GetLastError(); } +void Engine::AddView(int64_t view_id, const ViewportMetrics& view_metrics) { + runtime_controller_->AddView(view_id, view_metrics); +} + +void Engine::RemoveView(int64_t view_id) { + runtime_controller_->RemoveView(view_id); +} + void Engine::SetViewportMetrics(int64_t view_id, const ViewportMetrics& metrics) { runtime_controller_->SetViewportMetrics(view_id, metrics); @@ -440,14 +448,6 @@ void Engine::SetAccessibilityFeatures(int32_t flags) { runtime_controller_->SetAccessibilityFeatures(flags); } -bool Engine::ImplicitViewEnabled() { - // TODO(loicsharma): This value should be provided by the embedder - // when it launches the engine. For now, assume the embedder always creates a - // view. - // See: https://github.com/flutter/flutter/issues/120306 - return true; -} - std::string Engine::DefaultRouteName() { if (!initial_route_.empty()) { return initial_route_; diff --git a/shell/common/engine.h b/shell/common/engine.h index e0a4701f87b0e..e53efc9eb3882 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -675,6 +675,31 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { /// std::optional GetUIIsolateReturnCode(); + //---------------------------------------------------------------------------- + /// @brief Notify the Flutter application 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 + /// triggers an assertion. + /// + /// @param[in] view_id The ID of the new view. + /// @param[in] viewport_metrics The initial viewport metrics for the view. + /// + void AddView(int64_t view_id, const ViewportMetrics& view_metrics); + + //---------------------------------------------------------------------------- + /// @brief Notify the Flutter application that a view is no + /// longer available. + /// + /// Removing a view that does not exist triggers an assertion. + /// + /// The implicit view (kFlutterImplicitViewId) should never be + /// removed. Doing so triggers an assertion. + /// + /// @param[in] view_id The ID of the view. + /// + void RemoveView(int64_t view_id); + //---------------------------------------------------------------------------- /// @brief Updates the viewport metrics for a view. The viewport metrics /// detail the size of the rendering viewport in texels as well as @@ -898,9 +923,6 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { const std::weak_ptr GetVsyncWaiter() const; private: - // |RuntimeDelegate| - bool ImplicitViewEnabled() override; - // |RuntimeDelegate| std::string DefaultRouteName() override; diff --git a/shell/common/engine_unittests.cc b/shell/common/engine_unittests.cc index d1a7d24d39cbc..a6bbc12adb644 100644 --- a/shell/common/engine_unittests.cc +++ b/shell/common/engine_unittests.cc @@ -21,7 +21,6 @@ namespace flutter { namespace { -constexpr int64_t kImplicitViewId = 0ll; class MockDelegate : public Engine::Delegate { public: @@ -319,48 +318,6 @@ TEST_F(EngineTest, SpawnWithCustomInitialRoute) { }); } -TEST_F(EngineTest, SpawnResetsViewportMetrics) { - PostUITaskSync([this] { - MockRuntimeDelegate client; - auto mock_runtime_controller = - std::make_unique(client, task_runners_); - auto vm_ref = DartVMRef::Create(settings_); - EXPECT_CALL(*mock_runtime_controller, GetDartVM()) - .WillRepeatedly(::testing::Return(vm_ref.get())); - ViewportMetrics old_viewport_metrics = ViewportMetrics(); - const double kViewWidth = 768; - const double kViewHeight = 1024; - old_viewport_metrics.physical_width = kViewWidth; - old_viewport_metrics.physical_height = kViewHeight; - mock_runtime_controller->SetViewportMetrics(kImplicitViewId, - old_viewport_metrics); - auto engine = std::make_unique( - /*delegate=*/delegate_, - /*dispatcher_maker=*/dispatcher_maker_, - /*image_decoder_task_runner=*/image_decoder_task_runner_, - /*task_runners=*/task_runners_, - /*settings=*/settings_, - /*animator=*/std::move(animator_), - /*io_manager=*/io_manager_, - /*font_collection=*/std::make_shared(), - /*runtime_controller=*/std::move(mock_runtime_controller), - /*gpu_disabled_switch=*/std::make_shared()); - - auto& old_platform_data = engine->GetRuntimeController()->GetPlatformData(); - EXPECT_EQ(old_platform_data.viewport_metrics.physical_width, kViewWidth); - EXPECT_EQ(old_platform_data.viewport_metrics.physical_height, kViewHeight); - - auto spawn = - engine->Spawn(delegate_, dispatcher_maker_, settings_, nullptr, - std::string(), io_manager_, snapshot_delegate_, nullptr); - EXPECT_TRUE(spawn != nullptr); - auto& new_viewport_metrics = - spawn->GetRuntimeController()->GetPlatformData().viewport_metrics; - EXPECT_EQ(new_viewport_metrics.physical_width, 0); - EXPECT_EQ(new_viewport_metrics.physical_height, 0); - }); -} - TEST_F(EngineTest, SpawnWithCustomSettings) { PostUITaskSync([this] { MockRuntimeDelegate client; diff --git a/shell/common/fixtures/shell_test.dart b/shell/common/fixtures/shell_test.dart index ea7361915d7c7..e4b0d98833f44 100644 --- a/shell/common/fixtures/shell_test.dart +++ b/shell/common/fixtures/shell_test.dart @@ -482,3 +482,68 @@ Future testThatAssetLoadingHappensOnWorkerThread() async { } catch (err) { /* Do nothing */ } notifyNative(); } + +@pragma('vm:external-name', 'NativeReportViewIdsCallback') +external void nativeReportViewIdsCallback(bool hasImplicitView, List viewIds); + +List getCurrentViewIds() { + final List result = PlatformDispatcher.instance.views + .map((FlutterView view) => view.viewId) + .toList() + ..sort(); + assert(result.toSet().length == result.length, + 'Unexpected duplicate view ID found: $result'); + return result; +} + +bool listEquals(List a, List b) { + if (a.length != b.length) { + return false; + } + for (int i = 0; i < a.length; i += 1) { + if (a[i] != b[i]) { + return false; + } + } + return true; +} + +// This entrypoint reports whether there's an implicit view and the list of view +// IDs using nativeReportViewIdsCallback on initialization and every time the +// list of view IDs changes. +@pragma('vm:entry-point') +void testReportViewIds() { + List viewIds = getCurrentViewIds(); + nativeReportViewIdsCallback(PlatformDispatcher.instance.implicitView != null, viewIds); + PlatformDispatcher.instance.onMetricsChanged = () { + final List newViewIds = getCurrentViewIds(); + if (!listEquals(viewIds, newViewIds)) { + viewIds = newViewIds; + nativeReportViewIdsCallback(PlatformDispatcher.instance.implicitView != null, viewIds); + } + }; +} + +// Returns a list of [view_id 1, view_width 1, view_id 2, view_width 2, ...] +// for all views. +List getCurrentViewWidths() { + final List result = []; + for (final FlutterView view in PlatformDispatcher.instance.views) { + result.add(view.viewId); + result.add(view.physicalGeometry.width.round()); + } + return result; +} + +@pragma('vm:external-name', 'NativeReportViewWidthsCallback') +external void nativeReportViewWidthsCallback(List viewWidthPacket); + +// This entrypoint reports the list of views and their widths using +// nativeReportViewWidthsCallback on initialization and every onMetricsChanged. +@pragma('vm:entry-point') +void testReportViewWidths() { + nativeReportViewWidthsCallback(getCurrentViewWidths()); + PlatformDispatcher.instance.onMetricsChanged = () { + nativeReportViewWidthsCallback(getCurrentViewWidths()); + }; +} diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc index d7d1338c1a533..7cc45fa1f5b5a 100644 --- a/shell/common/rasterizer.cc +++ b/shell/common/rasterizer.cc @@ -9,6 +9,7 @@ #include #include "flow/frame_timings.h" +#include "flutter/common/constants.h" #include "flutter/common/graphics/persistent_cache.h" #include "flutter/flow/layers/offscreen_surface.h" #include "flutter/fml/time/time_delta.h" @@ -156,6 +157,14 @@ void Rasterizer::NotifyLowMemoryWarning() const { context->performDeferredCleanup(std::chrono::milliseconds(0)); } +void Rasterizer::CollectView(int64_t view_id) { + // TODO(dkwingsmt): When Rasterizer supports multi-view, this method should + // correctly clear the view corresponding to the ID. + if (view_id == kFlutterImplicitViewId) { + last_layer_tree_.reset(); + } +} + std::shared_ptr Rasterizer::GetTextureRegistry() { return compositor_context_->texture_registry(); } @@ -201,11 +210,14 @@ RasterStatus Rasterizer::Draw( RasterStatus raster_status = RasterStatus::kFailed; LayerTreePipeline::Consumer consumer = [&](std::unique_ptr item) { + // TODO(dkwingsmt): Use a proper view ID when Rasterizer supports + // multi-view. + int64_t view_id = kFlutterImplicitViewId; std::unique_ptr layer_tree = std::move(item->layer_tree); std::unique_ptr frame_timings_recorder = std::move(item->frame_timings_recorder); float device_pixel_ratio = item->device_pixel_ratio; - if (discard_callback(*layer_tree.get())) { + if (discard_callback(view_id, *layer_tree.get())) { raster_status = RasterStatus::kDiscarded; } else { raster_status = DoDraw(std::move(frame_timings_recorder), diff --git a/shell/common/rasterizer.h b/shell/common/rasterizer.h index b17242342c084..bb05e4475f998 100644 --- a/shell/common/rasterizer.h +++ b/shell/common/rasterizer.h @@ -204,6 +204,19 @@ class Rasterizer final : public SnapshotDelegate, fml::TaskRunnerAffineWeakPtr GetSnapshotDelegate() const; + //---------------------------------------------------------------------------- + /// @brief Deallocate the resources for displaying a view. + /// + /// This method should be called when a view is removed. + /// + /// The rasterizer don't need views to be registered. Last-frame + /// states for views are recorded when layer trees are rasterized + /// to the view and used during `Rasterizer::DrawLastLayerTree`. + /// + /// @param[in] view_id The ID of the view. + /// + void CollectView(int64_t view_id); + //---------------------------------------------------------------------------- /// @brief Sometimes, it may be necessary to render the same frame again /// without having to wait for the framework to build a whole new @@ -240,7 +253,8 @@ class Rasterizer final : public SnapshotDelegate, std::shared_ptr GetTextureRegistry() override; - using LayerTreeDiscardCallback = std::function; + using LayerTreeDiscardCallback = + std::function; //---------------------------------------------------------------------------- /// @brief Takes the next item from the layer tree pipeline and executes @@ -569,7 +583,9 @@ class Rasterizer final : public SnapshotDelegate, void FireNextFrameCallbackIfPresent(); - static bool NoDiscard(const flutter::LayerTree& layer_tree) { return false; } + static bool NoDiscard(int64_t view_id, const flutter::LayerTree& layer_tree) { + return false; + } static bool ShouldResubmitFrame(const RasterStatus& raster_status); Delegate& delegate_; diff --git a/shell/common/rasterizer_unittests.cc b/shell/common/rasterizer_unittests.cc index 5307dcacccfdf..55694a4554278 100644 --- a/shell/common/rasterizer_unittests.cc +++ b/shell/common/rasterizer_unittests.cc @@ -29,6 +29,7 @@ using testing::ReturnRef; namespace flutter { namespace { + constexpr float kDevicePixelRatio = 2.0f; class MockDelegate : public Rasterizer::Delegate { @@ -41,7 +42,6 @@ class MockDelegate : public Rasterizer::Delegate { const fml::RefPtr()); MOCK_CONST_METHOD0(GetIsGpuDisabledSyncSwitch, std::shared_ptr()); - MOCK_METHOD0(CreateSnapshotSurface, std::unique_ptr()); MOCK_CONST_METHOD0(GetSettings, const Settings&()); }; @@ -199,7 +199,7 @@ TEST(RasterizerTest, PipelineProduceResult result = pipeline->Produce().Complete(std::move(layer_tree_item)); EXPECT_TRUE(result.success); - auto no_discard = [](LayerTree&) { return false; }; + auto no_discard = [](int64_t, LayerTree&) { return false; }; rasterizer->Draw(pipeline, no_discard); latch.Signal(); }); @@ -265,7 +265,7 @@ TEST( PipelineProduceResult result = pipeline->Produce().Complete(std::move(layer_tree_item)); EXPECT_TRUE(result.success); - auto no_discard = [](LayerTree&) { return false; }; + auto no_discard = [](int64_t, LayerTree&) { return false; }; rasterizer->Draw(pipeline, no_discard); latch.Signal(); }); @@ -336,7 +336,7 @@ TEST( PipelineProduceResult result = pipeline->Produce().Complete(std::move(layer_tree_item)); EXPECT_TRUE(result.success); - auto no_discard = [](LayerTree&) { return false; }; + auto no_discard = [](int64_t, LayerTree&) { return false; }; rasterizer->Draw(pipeline, no_discard); } @@ -410,7 +410,7 @@ TEST(RasterizerTest, PipelineProduceResult result = pipeline->Produce().Complete(std::move(layer_tree_item)); EXPECT_TRUE(result.success); - auto no_discard = [](LayerTree&) { return false; }; + auto no_discard = [](int64_t, LayerTree&) { return false; }; // The Draw() will respectively call BeginFrame(), SubmitFrame() and // EndFrame() one time. @@ -460,7 +460,7 @@ TEST(RasterizerTest, externalViewEmbedderDoesntEndFrameWhenNoSurfaceIsSet) { PipelineProduceResult result = pipeline->Produce().Complete(std::move(layer_tree_item)); EXPECT_TRUE(result.success); - auto no_discard = [](LayerTree&) { return false; }; + auto no_discard = [](int64_t, LayerTree&) { return false; }; rasterizer->Draw(pipeline, no_discard); latch.Signal(); }); @@ -517,7 +517,7 @@ TEST(RasterizerTest, externalViewEmbedderDoesntEndFrameWhenNotUsedThisFrame) { pipeline->Produce().Complete(std::move(layer_tree_item)); EXPECT_TRUE(result.success); // Always discard the layer tree. - auto discard_callback = [](LayerTree&) { return true; }; + auto discard_callback = [](int64_t, LayerTree&) { return true; }; RasterStatus status = rasterizer->Draw(pipeline, discard_callback); EXPECT_EQ(status, RasterStatus::kDiscarded); latch.Signal(); @@ -561,7 +561,7 @@ TEST(RasterizerTest, externalViewEmbedderDoesntEndFrameWhenPipelineIsEmpty) { fml::AutoResetWaitableEvent latch; thread_host.raster_thread->GetTaskRunner()->PostTask([&] { auto pipeline = std::make_shared(/*depth=*/10); - auto no_discard = [](LayerTree&) { return false; }; + auto no_discard = [](int64_t, LayerTree&) { return false; }; RasterStatus status = rasterizer->Draw(pipeline, no_discard); EXPECT_EQ(status, RasterStatus::kFailed); latch.Signal(); @@ -619,7 +619,7 @@ TEST(RasterizerTest, PipelineProduceResult result = pipeline->Produce().Complete(std::move(layer_tree_item)); EXPECT_TRUE(result.success); - auto no_discard = [](LayerTree&) { return false; }; + auto no_discard = [](int64_t, LayerTree&) { return false; }; rasterizer->Draw(pipeline, no_discard); latch.Signal(); }); @@ -677,7 +677,7 @@ TEST( PipelineProduceResult result = pipeline->Produce().Complete(std::move(layer_tree_item)); EXPECT_TRUE(result.success); - auto no_discard = [](LayerTree&) { return false; }; + auto no_discard = [](int64_t, LayerTree&) { return false; }; RasterStatus status = rasterizer->Draw(pipeline, no_discard); EXPECT_EQ(status, RasterStatus::kSuccess); latch.Signal(); @@ -735,7 +735,7 @@ TEST( PipelineProduceResult result = pipeline->Produce().Complete(std::move(layer_tree_item)); EXPECT_TRUE(result.success); - auto no_discard = [](LayerTree&) { return false; }; + auto no_discard = [](int64_t, LayerTree&) { return false; }; RasterStatus status = rasterizer->Draw(pipeline, no_discard); EXPECT_EQ(status, RasterStatus::kSuccess); latch.Signal(); @@ -792,7 +792,7 @@ TEST( PipelineProduceResult result = pipeline->Produce().Complete(std::move(layer_tree_item)); EXPECT_TRUE(result.success); - auto no_discard = [](LayerTree&) { return false; }; + auto no_discard = [](int64_t, LayerTree&) { return false; }; RasterStatus status = rasterizer->Draw(pipeline, no_discard); EXPECT_EQ(status, RasterStatus::kDiscarded); latch.Signal(); @@ -848,7 +848,7 @@ TEST( PipelineProduceResult result = pipeline->Produce().Complete(std::move(layer_tree_item)); EXPECT_TRUE(result.success); - auto no_discard = [](LayerTree&) { return false; }; + auto no_discard = [](int64_t, LayerTree&) { return false; }; RasterStatus status = rasterizer->Draw(pipeline, no_discard); EXPECT_EQ(status, RasterStatus::kFailed); latch.Signal(); @@ -929,7 +929,7 @@ TEST(RasterizerTest, EXPECT_TRUE(result.success); EXPECT_EQ(result.is_first_item, i == 0); } - auto no_discard = [](LayerTree&) { return false; }; + auto no_discard = [](int64_t, LayerTree&) { return false; }; // Although we only call 'Rasterizer::Draw' once, it will be called twice // finally because there are two items in the pipeline. rasterizer->Draw(pipeline, no_discard); @@ -1100,7 +1100,7 @@ TEST(RasterizerTest, presentationTimeSetWhenVsyncTargetInFuture) { EXPECT_TRUE(result.success); EXPECT_EQ(result.is_first_item, i == 0); } - auto no_discard = [](LayerTree&) { return false; }; + auto no_discard = [](int64_t, LayerTree&) { return false; }; // Although we only call 'Rasterizer::Draw' once, it will be called twice // finally because there are two items in the pipeline. rasterizer->Draw(pipeline, no_discard); @@ -1181,7 +1181,7 @@ TEST(RasterizerTest, presentationTimeNotSetWhenVsyncTargetInPast) { pipeline->Produce().Complete(std::move(layer_tree_item)); EXPECT_TRUE(result.success); EXPECT_EQ(result.is_first_item, true); - auto no_discard = [](LayerTree&) { return false; }; + auto no_discard = [](int64_t, LayerTree&) { return false; }; rasterizer->Draw(pipeline, no_discard); }); diff --git a/shell/common/shell.cc b/shell/common/shell.cc index 136c09b9c39d0..7964a530031db 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -11,6 +11,7 @@ #include #include "flutter/assets/directory_asset_bundle.h" +#include "flutter/common/constants.h" #include "flutter/common/graphics/persistent_cache.h" #include "flutter/fml/base32.h" #include "flutter/fml/file.h" @@ -715,6 +716,7 @@ bool Shell::Setup(std::unique_ptr platform_view, weak_rasterizer_ = rasterizer_->GetWeakPtr(); weak_platform_view_ = platform_view_->GetWeakPtr(); + engine_->AddView(kFlutterImplicitViewId, ViewportMetrics{}); // Setup the time-consuming default font manager right after engine created. if (!settings_.prefetched_default_font_manager) { fml::TaskRunner::RunNowOrPostTask(task_runners_.GetUITaskRunner(), @@ -808,10 +810,11 @@ void Shell::OnPlatformViewCreated(std::unique_ptr surface) { !task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread(); fml::AutoResetWaitableEvent latch; - auto raster_task = - fml::MakeCopyable([&waiting_for_first_frame = waiting_for_first_frame_, - rasterizer = rasterizer_->GetWeakPtr(), // - surface = std::move(surface)]() mutable { + auto raster_task = fml::MakeCopyable( + [&waiting_for_first_frame = waiting_for_first_frame_, // + rasterizer = rasterizer_->GetWeakPtr(), // + surface = std::move(surface) // + ]() mutable { if (rasterizer) { // Enables the thread merger which may be used by the external view // embedder. @@ -989,7 +992,7 @@ void Shell::OnPlatformViewSetViewportMetrics(int64_t view_id, { std::scoped_lock lock(resize_mutex_); - expected_frame_size_ = + expected_frame_sizes_[view_id] = SkISize::Make(metrics.physical_width, metrics.physical_height); device_pixel_ratio_ = metrics.device_pixel_ratio; } @@ -1215,10 +1218,11 @@ void Shell::OnAnimatorUpdateLatestFrameTargetTime( void Shell::OnAnimatorDraw(std::shared_ptr pipeline) { FML_DCHECK(is_set_up_); - auto discard_callback = [this](flutter::LayerTree& tree) { + auto discard_callback = [this](int64_t view_id, flutter::LayerTree& tree) { std::scoped_lock lock(resize_mutex_); - return !expected_frame_size_.isEmpty() && - tree.frame_size() != expected_frame_size_; + auto expected_frame_size = ExpectedFrameSize(view_id); + return !expected_frame_size.isEmpty() && + tree.frame_size() != expected_frame_size; }; task_runners_.GetRasterTaskRunner()->PostTask(fml::MakeCopyable( @@ -1940,6 +1944,9 @@ bool Shell::OnServiceProtocolRenderFrameWithRasterStats( rapidjson::Document* response) { FML_DCHECK(task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread()); + // TODO(dkwingsmt): This method only handles view #0, including the snapshot + // and the frame size. We need to adapt this method to multi-view. + // https://github.com/flutter/flutter/issues/131892 if (auto last_layer_tree = rasterizer_->GetLastLayerTree()) { auto& allocator = response->GetAllocator(); response->SetObject(); @@ -1971,7 +1978,7 @@ bool Shell::OnServiceProtocolRenderFrameWithRasterStats( response->AddMember("snapshots", snapshots, allocator); - const auto& frame_size = expected_frame_size_; + const auto& frame_size = ExpectedFrameSize(kFlutterImplicitViewId); response->AddMember("frame_width", frame_size.width(), allocator); response->AddMember("frame_height", frame_size.height(), allocator); @@ -2027,6 +2034,54 @@ bool Shell::OnServiceProtocolReloadAssetFonts( return true; } +void Shell::AddView(int64_t view_id, const ViewportMetrics& viewport_metrics) { + TRACE_EVENT0("flutter", "Shell::AddView"); + FML_DCHECK(is_set_up_); + FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); + FML_DCHECK(view_id != kFlutterImplicitViewId) + << "Unexpected request to add the implicit view #" + << kFlutterImplicitViewId << ". This view should never be added."; + + task_runners_.GetUITaskRunner()->PostTask([engine = engine_->GetWeakPtr(), // + viewport_metrics, // + view_id // + ] { + if (engine) { + engine->AddView(view_id, viewport_metrics); + } + }); +} + +void Shell::RemoveView(int64_t view_id) { + TRACE_EVENT0("flutter", "Shell::RemoveView"); + FML_DCHECK(is_set_up_); + FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); + FML_DCHECK(view_id != kFlutterImplicitViewId) + << "Unexpected request to remove the implicit view #" + << kFlutterImplicitViewId << ". This view should never be removed."; + + expected_frame_sizes_.erase(view_id); + task_runners_.GetUITaskRunner()->PostTask( + [&task_runners = task_runners_, // + engine = engine_->GetWeakPtr(), // + rasterizer = rasterizer_->GetWeakPtr(), // + view_id // + ] { + if (engine) { + engine->RemoveView(view_id); + } + // Don't wait for the raster task here, which only cleans up memory and + // does not affect functionality. Make sure it is done after Dart + // removes the view to avoid receiving another rasterization request + // that adds back the view record. + task_runners.GetRasterTaskRunner()->PostTask([rasterizer, view_id]() { + if (rasterizer) { + rasterizer->CollectView(view_id); + } + }); + }); +} + Rasterizer::Screenshot Shell::Screenshot( Rasterizer::ScreenshotType screenshot_type, bool base64_encode) { @@ -2167,4 +2222,12 @@ Shell::GetConcurrentWorkerTaskRunner() const { return vm_->GetConcurrentWorkerTaskRunner(); } +SkISize Shell::ExpectedFrameSize(int64_t view_id) { + auto found = expected_frame_sizes_.find(view_id); + if (found == expected_frame_sizes_.end()) { + return SkISize::MakeEmpty(); + } + return found->second; +} + } // namespace flutter diff --git a/shell/common/shell.h b/shell/common/shell.h index 23689332233c9..c4b585550b082 100644 --- a/shell/common/shell.h +++ b/shell/common/shell.h @@ -298,6 +298,37 @@ class Shell final : public PlatformView::Delegate, /// bool IsSetup() const; + /// @brief Allocates resources for a new non-implicit view. + /// + /// This method returns immediately and does not wait for the task on + /// the UI thread to finish. This is safe because operations are + /// either initiated from the UI thread (such as rendering), or are + /// sent as posted tasks that are queued. In either case, it's ok for + /// the engine to have views that the Dart VM doesn't. + /// + /// The implicit view should never be added with this function. + /// Instead, it is added internally on Shell initialization. Trying to + /// add `kFlutterImplicitViewId` triggers an assertion. + /// + /// @param[in] view_id The view ID of the new view. + /// @param[in] viewport_metrics The initial viewport metrics for the view. + /// + void AddView(int64_t view_id, const ViewportMetrics& viewport_metrics); + + /// @brief Deallocates resources for a non-implicit view. + /// + /// This method returns immediately and does not wait for the task on + /// the UI thread to finish. This means that the Dart VM might still + /// send messages regarding this view ID for a short while, even + /// though this view ID is already invalid. + /// + /// The implicit view should never be removed. Trying to remove + /// `kFlutterImplicitViewId` triggers an assertion. + /// + /// @param[in] view_id The view ID of the view to be removed. + /// + void RemoveView(int64_t view_id); + //---------------------------------------------------------------------------- /// @brief Captures a screenshot and optionally Base64 encodes the data /// of the last layer tree rendered by the rasterizer in this @@ -479,7 +510,7 @@ class Shell final : public PlatformView::Delegate, std::mutex resize_mutex_; // used to discard wrong size layer tree produced during interactive resizing - SkISize expected_frame_size_ = SkISize::MakeEmpty(); + std::unordered_map expected_frame_sizes_; // Used to communicate the right frame bounds via service protocol. double device_pixel_ratio_ = 0.0; @@ -741,6 +772,8 @@ class Shell final : public PlatformView::Delegate, // directory. std::unique_ptr RestoreOriginalAssetResolver(); + SkISize ExpectedFrameSize(int64_t view_id); + // For accessing the Shell via the raster thread, necessary for various // rasterizer callbacks. std::unique_ptr> weak_factory_gpu_; diff --git a/shell/common/shell_test_platform_view_vulkan.h b/shell/common/shell_test_platform_view_vulkan.h index 718f7fe6dec06..e617ea3f7f9e2 100644 --- a/shell/common/shell_test_platform_view_vulkan.h +++ b/shell/common/shell_test_platform_view_vulkan.h @@ -43,6 +43,7 @@ class ShellTestPlatformViewVulkan : public ShellTestPlatformView { // |Surface| std::unique_ptr AcquireFrame(const SkISize& size) override; + // |Surface| SkMatrix GetRootTransformation() const override; // |Surface| diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index cdfaf54961088..43a14bb13b75f 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -4376,6 +4376,197 @@ TEST_F(ShellTest, DiesIfSoftwareRenderingAndImpellerAreEnabledDeathTest) { #endif // OS_FUCHSIA } +// Parse the arguments of NativeReportViewIdsCallback and +// store them in hasImplicitView and viewIds. +static void ParseViewIdsCallback(const Dart_NativeArguments& args, + bool* hasImplicitView, + std::vector* viewIds) { + Dart_Handle exception = nullptr; + viewIds->clear(); + *hasImplicitView = + tonic::DartConverter::FromArguments(args, 0, exception); + ASSERT_EQ(exception, nullptr); + *viewIds = tonic::DartConverter>::FromArguments( + args, 1, exception); + ASSERT_EQ(exception, nullptr); +} + +// Run the given task in the platform runner, and blocks until its finished. +static void RunOnPlatformTaskRunner(Shell& shell, const fml::closure& task) { + fml::AutoResetWaitableEvent latch; + fml::TaskRunner::RunNowOrPostTask( + shell.GetTaskRunners().GetPlatformTaskRunner(), [&task, &latch]() { + task(); + latch.Signal(); + }); + latch.Wait(); +} + +TEST_F(ShellTest, ShellStartsWithImplicitView) { + ASSERT_FALSE(DartVMRef::IsInstanceRunning()); + Settings settings = CreateSettingsForFixture(); + auto task_runner = CreateNewThread(); + TaskRunners task_runners("test", task_runner, task_runner, task_runner, + task_runner); + std::unique_ptr shell = CreateShell(settings, task_runners); + ASSERT_TRUE(shell); + + bool hasImplicitView; + std::vector viewIds; + fml::AutoResetWaitableEvent reportLatch; + auto nativeViewIdsCallback = [&reportLatch, &hasImplicitView, + &viewIds](Dart_NativeArguments args) { + ParseViewIdsCallback(args, &hasImplicitView, &viewIds); + reportLatch.Signal(); + }; + AddNativeCallback("NativeReportViewIdsCallback", + CREATE_NATIVE_ENTRY(nativeViewIdsCallback)); + + PlatformViewNotifyCreated(shell.get()); + auto configuration = RunConfiguration::InferFromSettings(settings); + configuration.SetEntrypoint("testReportViewIds"); + RunEngine(shell.get(), std::move(configuration)); + reportLatch.Wait(); + + ASSERT_TRUE(hasImplicitView); + ASSERT_EQ(viewIds.size(), 1u); + ASSERT_EQ(viewIds[0], 0ll); + + PlatformViewNotifyDestroyed(shell.get()); + DestroyShell(std::move(shell), task_runners); +} + +// Tests that Shell::AddView and Shell::RemoveView works. +TEST_F(ShellTest, ShellCanAddViewOrRemoveView) { + ASSERT_FALSE(DartVMRef::IsInstanceRunning()); + Settings settings = CreateSettingsForFixture(); + ThreadHost thread_host(ThreadHost::ThreadHostConfig( + "io.flutter.test." + GetCurrentTestName() + ".", + ThreadHost::Type::Platform | ThreadHost::Type::RASTER | + ThreadHost::Type::IO | ThreadHost::Type::UI)); + TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(), + thread_host.raster_thread->GetTaskRunner(), + thread_host.ui_thread->GetTaskRunner(), + thread_host.io_thread->GetTaskRunner()); + std::unique_ptr shell = CreateShell(settings, task_runners); + ASSERT_TRUE(shell); + + bool hasImplicitView; + std::vector viewIds; + fml::AutoResetWaitableEvent reportLatch; + auto nativeViewIdsCallback = [&reportLatch, &hasImplicitView, + &viewIds](Dart_NativeArguments args) { + ParseViewIdsCallback(args, &hasImplicitView, &viewIds); + reportLatch.Signal(); + }; + AddNativeCallback("NativeReportViewIdsCallback", + CREATE_NATIVE_ENTRY(nativeViewIdsCallback)); + + PlatformViewNotifyCreated(shell.get()); + auto configuration = RunConfiguration::InferFromSettings(settings); + configuration.SetEntrypoint("testReportViewIds"); + RunEngine(shell.get(), std::move(configuration)); + + reportLatch.Wait(); + ASSERT_TRUE(hasImplicitView); + ASSERT_EQ(viewIds.size(), 1u); + ASSERT_EQ(viewIds[0], 0ll); + + RunOnPlatformTaskRunner(*shell, + [&shell] { shell->AddView(2, ViewportMetrics{}); }); + reportLatch.Wait(); + ASSERT_TRUE(hasImplicitView); + ASSERT_EQ(viewIds.size(), 2u); + ASSERT_EQ(viewIds[1], 2ll); + + RunOnPlatformTaskRunner(*shell, [&shell] { shell->RemoveView(2); }); + reportLatch.Wait(); + ASSERT_TRUE(hasImplicitView); + ASSERT_EQ(viewIds.size(), 1u); + ASSERT_EQ(viewIds[0], 0ll); + + RunOnPlatformTaskRunner(*shell, + [&shell] { shell->AddView(4, ViewportMetrics{}); }); + reportLatch.Wait(); + ASSERT_TRUE(hasImplicitView); + ASSERT_EQ(viewIds.size(), 2u); + ASSERT_EQ(viewIds[1], 4ll); + + PlatformViewNotifyDestroyed(shell.get()); + DestroyShell(std::move(shell), task_runners); +} + +// Parse the arguments of NativeReportViewWidthsCallback and +// store them in viewWidths. +static void ParseViewWidthsCallback(const Dart_NativeArguments& args, + std::map* viewWidths) { + Dart_Handle exception = nullptr; + viewWidths->clear(); + std::vector viewWidthPacket = + tonic::DartConverter>::FromArguments(args, 0, + exception); + ASSERT_EQ(exception, nullptr); + ASSERT_EQ(viewWidthPacket.size() % 2, 0ul); + for (size_t packetIndex = 0; packetIndex < viewWidthPacket.size(); + packetIndex += 2) { + (*viewWidths)[viewWidthPacket[packetIndex]] = + viewWidthPacket[packetIndex + 1]; + } +} + +// Ensure that PlatformView::SetViewportMetrics and Shell::AddView that were +// dispatched before the isolate is run have been flushed to the Dart VM when +// the main function starts. +TEST_F(ShellTest, ShellFlushesPlatformStatesByMain) { + ASSERT_FALSE(DartVMRef::IsInstanceRunning()); + Settings settings = CreateSettingsForFixture(); + ThreadHost thread_host(ThreadHost::ThreadHostConfig( + "io.flutter.test." + GetCurrentTestName() + ".", + ThreadHost::Type::Platform | ThreadHost::Type::RASTER | + ThreadHost::Type::IO | ThreadHost::Type::UI)); + TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(), + thread_host.raster_thread->GetTaskRunner(), + thread_host.ui_thread->GetTaskRunner(), + thread_host.io_thread->GetTaskRunner()); + std::unique_ptr shell = CreateShell(settings, task_runners); + ASSERT_TRUE(shell); + + RunOnPlatformTaskRunner(*shell, [&shell] { + auto platform_view = shell->GetPlatformView(); + // The construtor for ViewportMetrics{_, width, _, _, _} (only the 2nd + // argument matters in this test). + platform_view->SetViewportMetrics(0, ViewportMetrics{1, 10, 1, 0, 0}); + shell->AddView(1, ViewportMetrics{1, 30, 1, 0, 0}); + platform_view->SetViewportMetrics(0, ViewportMetrics{1, 20, 1, 0, 0}); + }); + + bool first_report = true; + std::map viewWidths; + fml::AutoResetWaitableEvent reportLatch; + auto nativeViewWidthsCallback = [&reportLatch, &viewWidths, + &first_report](Dart_NativeArguments args) { + EXPECT_TRUE(first_report); + first_report = false; + ParseViewWidthsCallback(args, &viewWidths); + reportLatch.Signal(); + }; + AddNativeCallback("NativeReportViewWidthsCallback", + CREATE_NATIVE_ENTRY(nativeViewWidthsCallback)); + + PlatformViewNotifyCreated(shell.get()); + auto configuration = RunConfiguration::InferFromSettings(settings); + configuration.SetEntrypoint("testReportViewWidths"); + RunEngine(shell.get(), std::move(configuration)); + + reportLatch.Wait(); + EXPECT_EQ(viewWidths.size(), 2u); + EXPECT_EQ(viewWidths[0], 20ll); + EXPECT_EQ(viewWidths[1], 30ll); + + PlatformViewNotifyDestroyed(shell.get()); + DestroyShell(std::move(shell), task_runners); +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index 7b9b2e12b37cf..b755e4f4d2ed0 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -17,7 +17,7 @@ #include "unicode/uchar.h" #include "flutter/assets/directory_asset_bundle.h" -#include "flutter/common/settings.h" +#include "flutter/common/constants.h" #include "flutter/fml/file.h" #include "flutter/fml/mapping.h" #include "flutter/fml/native_library.h" @@ -41,8 +41,6 @@ namespace flutter { -static constexpr int64_t kFlutterImplicitViewId = 0ll; - static fml::jni::ScopedJavaGlobalRef* g_flutter_callback_info_class = nullptr; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 6759626b39eff..b300714f166ad 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -8,6 +8,7 @@ #include +#include "flutter/common/constants.h" #include "flutter/fml/message_loop.h" #include "flutter/fml/platform/darwin/platform_version.h" #include "flutter/fml/trace_event.h" @@ -39,8 +40,6 @@ #import "flutter/shell/platform/darwin/ios/rendering_api_selection.h" #include "flutter/shell/profiling/sampling_profiler.h" -static constexpr int64_t kFlutterImplicitViewId = 0ll; - /// Inheriting ThreadConfigurer and use iOS platform thread API to configure the thread priorities /// Using iOS platform thread API to configure thread priority static void IOSPlatformThreadConfigSetter(const fml::Thread::ThreadConfig& config) { @@ -333,7 +332,7 @@ - (void)updateViewportMetrics:(flutter::ViewportMetrics)viewportMetrics { if (!self.platformView) { return; } - self.platformView->SetViewportMetrics(kFlutterImplicitViewId, viewportMetrics); + self.platformView->SetViewportMetrics(flutter::kFlutterImplicitViewId, viewportMetrics); } - (void)dispatchPointerDataPacket:(std::unique_ptr)packet { diff --git a/shell/platform/fuchsia/flutter/platform_view.cc b/shell/platform/fuchsia/flutter/platform_view.cc index 22fd9dbfa854c..0074361fafda8 100644 --- a/shell/platform/fuchsia/flutter/platform_view.cc +++ b/shell/platform/fuchsia/flutter/platform_view.cc @@ -17,7 +17,6 @@ #include "flutter/fml/logging.h" #include "flutter/fml/make_copyable.h" #include "flutter/lib/ui/window/pointer_data.h" -#include "flutter/lib/ui/window/window.h" #include "flutter/shell/platform/common/client_wrapper/include/flutter/encodable_value.h" #include "flutter/shell/platform/common/client_wrapper/include/flutter/standard_message_codec.h" #include "third_party/rapidjson/include/rapidjson/document.h"