diff --git a/lib/ui/dart_ui.cc b/lib/ui/dart_ui.cc index 8d14a2fb822de..aabdaa43faf3e 100644 --- a/lib/ui/dart_ui.cc +++ b/lib/ui/dart_ui.cc @@ -95,6 +95,7 @@ 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) \ diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index 5f3e0812b12ea..c5e3b9fe85806 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -168,6 +168,36 @@ class PlatformDispatcher { // A map of opaque platform view identifiers to view configurations. final Map _viewConfigurations = {}; + /// The [FlutterView] provided by the engine if the platform is unable to + /// create windows, or, for backwards compatibility. + /// + /// If the platform provides an implicit view, it can be used to bootstrap + /// the framework. This is common for platforms designed for single-view + /// applications like mobile devices with a single display. + /// + /// Applications and libraries must not rely on this property being set + /// as it may be null depending on the engine's configuration. Instead, + /// consider using [View.of] to lookup the [FlutterView] the current + /// [BuildContext] is drawing into. + /// + /// While the properties on the referenced [FlutterView] may change, + /// the reference itself is guaranteed to never change over the lifetime + /// of the application: if this property is null at startup, it will remain + /// so throughout the entire lifetime of the application. If it points to a + /// specific [FlutterView], it will continue to point to the same view until + /// the application is shut down (although the engine may replace or remove + /// the underlying backing surface of the view at its discretion). + /// + /// See also: + /// + /// * [View.of], for accessing the current view. + /// * [PlatformDisptacher.views] for a list of all [FlutterView]s provided + /// by the platform. + FlutterView? get implicitView => _implicitViewEnabled() ? _views[0] : null; + + @Native(symbol: 'PlatformConfigurationNativeApi::ImplicitViewEnabled') + external static bool _implicitViewEnabled(); + /// A callback that is invoked whenever the [ViewConfiguration] of any of the /// [views] changes. /// diff --git a/lib/ui/window.dart b/lib/ui/window.dart index e37a4854475e2..a167331ebb778 100644 --- a/lib/ui/window.dart +++ b/lib/ui/window.dart @@ -896,6 +896,7 @@ enum Brightness { /// * [PlatformDispatcher.views], contains the current list of Flutter windows /// belonging to the application, including top level application windows like /// this one. +/// * [PlatformDispatcher.implicitView], this window's view. final SingletonFlutterWindow window = SingletonFlutterWindow._(0, PlatformDispatcher.instance); /// Additional data available on each flutter frame. diff --git a/lib/ui/window/platform_configuration.cc b/lib/ui/window/platform_configuration.cc index 22f6c77962e7a..95a2714929995 100644 --- a/lib/ui/window/platform_configuration.cc +++ b/lib/ui/window/platform_configuration.cc @@ -22,6 +22,8 @@ namespace flutter { namespace { +constexpr int kImplicitViewId = 0; + Dart_Handle ToByteData(const fml::Mapping& buffer) { return tonic::DartByteData::Create(buffer.GetMapping(), buffer.GetSize()); } @@ -67,8 +69,13 @@ void PlatformConfiguration::DidCreateIsolate() { Dart_GetField(library, tonic::ToDart("_drawFrame"))); report_timings_.Set(tonic::DartState::Current(), Dart_GetField(library, tonic::ToDart("_reportTimings"))); - windows_.insert(std::make_pair( - 0, std::make_unique(0, ViewportMetrics{1.0, 0.0, 0.0, -1}))); + + // 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})); } void PlatformConfiguration::UpdateLocales( @@ -427,6 +434,16 @@ 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 2f86727c59c30..c6f3c7811edf2 100644 --- a/lib/ui/window/platform_configuration.h +++ b/lib/ui/window/platform_configuration.h @@ -49,6 +49,16 @@ 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. @@ -71,7 +81,7 @@ class PlatformConfigurationClient { virtual void Render(Scene* scene) = 0; //-------------------------------------------------------------------------- - /// @brief Receives a updated semantics tree from the Framework. + /// @brief Receives an updated semantics tree from the Framework. /// /// @param[in] update The updated semantic tree to apply. /// @@ -469,6 +479,8 @@ class PlatformMessageHandlerStorage { //---------------------------------------------------------------------------- class PlatformConfigurationNativeApi { public: + static Dart_Handle ImplicitViewEnabled(); + static std::string DefaultRouteName(); static void ScheduleFrame(); diff --git a/lib/web_ui/lib/platform_dispatcher.dart b/lib/web_ui/lib/platform_dispatcher.dart index a6db5dbb11c8b..cad5776d1c2e2 100644 --- a/lib/web_ui/lib/platform_dispatcher.dart +++ b/lib/web_ui/lib/platform_dispatcher.dart @@ -33,6 +33,8 @@ abstract class PlatformDispatcher { Iterable get views; + FlutterView? get implicitView; + VoidCallback? get onMetricsChanged; set onMetricsChanged(VoidCallback? callback); diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart index 06859c6684d2c..148a0e872a794 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -148,6 +148,34 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { final Map _windowConfigurations = {}; + /// The [FlutterView] provided by the engine if the platform is unable to + /// create windows, or, for backwards compatibility. + /// + /// If the platform provides an implicit view, it can be used to bootstrap + /// the framework. This is common for platforms designed for single-view + /// applications like mobile devices with a single display. + /// + /// Applications and libraries must not rely on this property being set + /// as it may be null depending on the engine's configuration. Instead, + /// consider using [View.of] to lookup the [FlutterView] the current + /// [BuildContext] is drawing into. + /// + /// While the properties on the referenced [FlutterView] may change, + /// the reference itself is guaranteed to never change over the lifetime + /// of the application: if this property is null at startup, it will remain + /// so throughout the entire lifetime of the application. If it points to a + /// specific [FlutterView], it will continue to point to the same view until + /// the application is shut down (although the engine may replace or remove + /// the underlying backing surface of the view at its discretion). + /// + /// See also: + /// + /// * [View.of], for accessing the current view. + /// * [PlatformDisptacher.views] for a list of all [FlutterView]s provided + /// by the platform. + @override + ui.FlutterView? get implicitView => viewData[kImplicitViewId]; + /// A callback that is invoked whenever the platform's [devicePixelRatio], /// [physicalSize], [padding], [viewInsets], or [systemGestureInsets] /// values change, for example when the device is rotated or when the @@ -479,7 +507,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { // TODO(a-wallen): As multi-window support expands, the pop call // will need to include the view ID. Right now only one view is // supported. - (viewData[kSingletonViewId]! as EngineFlutterWindow) + (viewData[kImplicitViewId]! as EngineFlutterWindow) .browserHistory .exit() .then((_) { @@ -584,7 +612,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { // TODO(a-wallen): As multi-window support expands, the navigation call // will need to include the view ID. Right now only one view is // supported. - (viewData[kSingletonViewId]! as EngineFlutterWindow) + (viewData[kImplicitViewId]! as EngineFlutterWindow) .handleNavigationMessage(data) .then((bool handled) { if (handled) { @@ -1173,7 +1201,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { @override String get defaultRouteName { return _defaultRouteName ??= - (viewData[kSingletonViewId]! as EngineFlutterWindow).browserHistory.currentPath; + (viewData[kImplicitViewId]! as EngineFlutterWindow).browserHistory.currentPath; } /// Lazily initialized when the `defaultRouteName` getter is invoked. diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index 1d8e2d13bcef9..952830008e582 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -27,8 +27,8 @@ typedef _HandleMessageCallBack = Future Function(); /// When set to true, all platform messages will be printed to the console. const bool debugPrintPlatformMessages = false; -/// The view ID for a singleton flutter window. -const int kSingletonViewId = 0; +/// The view ID for the implicit flutter view provided by the platform. +const int kImplicitViewId = 0; /// Whether [_customUrlStrategy] has been set or not. /// @@ -349,7 +349,7 @@ class EngineSingletonFlutterWindow extends EngineFlutterWindow { /// API surface, providing Web-specific functionality that the standard /// `dart:ui` version does not. final EngineSingletonFlutterWindow window = - EngineSingletonFlutterWindow(kSingletonViewId, EnginePlatformDispatcher.instance); + EngineSingletonFlutterWindow(kImplicitViewId, EnginePlatformDispatcher.instance); /// The Web implementation of [ui.WindowPadding]. class WindowPadding implements ui.WindowPadding { diff --git a/lib/web_ui/test/window_test.dart b/lib/web_ui/test/window_test.dart index 01c4eaae7a377..44a6fe6b6ee73 100644 --- a/lib/web_ui/test/window_test.dart +++ b/lib/web_ui/test/window_test.dart @@ -41,6 +41,13 @@ void testMain() { await window.resetHistory(); }); + // For now, web always has an implicit view provided by the web engine. + test('EnginePlatformDispatcher.instance.implicitView should be non-null', () async { + expect(EnginePlatformDispatcher.instance.implicitView, isNotNull); + expect(EnginePlatformDispatcher.instance.implicitView?.viewId, 0); + expect(window.viewId, 0); + }); + test('window.defaultRouteName should work with JsUrlStrategy', () async { dynamic state = {}; final JsUrlStrategy jsUrlStrategy = JsUrlStrategy( diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index b874ae698d55d..bf923ed5a7876 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -298,6 +298,11 @@ 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(); diff --git a/runtime/runtime_controller.h b/runtime/runtime_controller.h index ecfb6f2d2c210..98fbf1146453e 100644 --- a/runtime/runtime_controller.h +++ b/runtime/runtime_controller.h @@ -610,6 +610,9 @@ class RuntimeController : public PlatformConfigurationClient { bool FlushRuntimeStateToIsolate(); + // |PlatformConfigurationClient| + bool ImplicitViewEnabled() override; + // |PlatformConfigurationClient| std::string DefaultRouteName() override; diff --git a/runtime/runtime_delegate.h b/runtime/runtime_delegate.h index 679922e29d732..0fbb43391e113 100644 --- a/runtime/runtime_delegate.h +++ b/runtime/runtime_delegate.h @@ -21,6 +21,8 @@ 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 34acee5fa77a9..278f84fad44f1 100644 --- a/shell/common/engine.cc +++ b/shell/common/engine.cc @@ -431,6 +431,14 @@ 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 400736735e06b..c6ff37eae37a5 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -888,6 +888,9 @@ 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 c2c9a84f634da..3bfdd000c37eb 100644 --- a/shell/common/engine_unittests.cc +++ b/shell/common/engine_unittests.cc @@ -48,6 +48,7 @@ class MockResponse : public PlatformMessageResponse { class MockRuntimeDelegate : public RuntimeDelegate { public: + MOCK_METHOD0(ImplicitViewEnabled, bool()); MOCK_METHOD0(DefaultRouteName, std::string()); MOCK_METHOD1(ScheduleFrame, void(bool)); MOCK_METHOD1(Render, void(std::shared_ptr)); diff --git a/shell/platform/embedder/fixtures/main.dart b/shell/platform/embedder/fixtures/main.dart index 1b9c4f2469cc8..bf122f32d38ef 100644 --- a/shell/platform/embedder/fixtures/main.dart +++ b/shell/platform/embedder/fixtures/main.dart @@ -45,8 +45,15 @@ void executableNameNotNull() { notifyStringValue(Platform.executable); } +@pragma('vm:entry-point') +void implicitViewNotNull() { + notifyBoolValue(PlatformDispatcher.instance.implicitView != null); +} + @pragma('vm:external-name', 'NotifyStringValue') external void notifyStringValue(String value); +@pragma('vm:external-name', 'NotifyBoolValue') +external void notifyBoolValue(bool value); @pragma('vm:entry-point') void invokePlatformTaskRunner() { diff --git a/shell/platform/embedder/tests/embedder_unittests.cc b/shell/platform/embedder/tests/embedder_unittests.cc index dd9be015a84e9..1518e242d81bb 100644 --- a/shell/platform/embedder/tests/embedder_unittests.cc +++ b/shell/platform/embedder/tests/embedder_unittests.cc @@ -174,6 +174,30 @@ TEST_F(EmbedderTest, ExecutableNameNotNull) { latch.Wait(); } +TEST_F(EmbedderTest, ImplicitViewNotNull) { + // TODO(loicsharma): Update this test when embedders can opt-out + // of the implicit view. + // See: https://github.com/flutter/flutter/issues/120306 + auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + + bool implicitViewNotNull = false; + fml::AutoResetWaitableEvent latch; + context.AddNativeCallback( + "NotifyBoolValue", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { + implicitViewNotNull = tonic::DartConverter::FromDart( + Dart_GetNativeArgument(args, 0)); + latch.Signal(); + })); + + EmbedderConfigBuilder builder(context); + builder.SetSoftwareRendererConfig(); + builder.SetDartEntrypoint("implicitViewNotNull"); + auto engine = builder.LaunchEngine(); + latch.Wait(); + + EXPECT_TRUE(implicitViewNotNull); +} + std::atomic_size_t EmbedderTestTaskRunner::sEmbedderTaskRunnerIdentifiers = {}; TEST_F(EmbedderTest, CanSpecifyCustomPlatformTaskRunner) {