[dart:ui] Introduce PlatformDispatcher.implicitView
(flutter/engine#39553)
This introduces `PlatformDispatcher.implicitView`, a low-level primitive for the framework's bootstrapping. Most code, including the framework after bootstrapping, will use `View.of(context)` instead of this new API. This new primitive will let us deprecate the `window` global. Goals: 1. **Enable multi-window**. The `PlatformDispatcher.implicitView` is nullable. If `null`, the app must create a window to get a view it can draw into. 2. **Backwards compatibility**. For "single window" apps, `PlatformDispatcher.instance.implicitView` should behave as similar to `window` as possible. 1. The `PlatformDispatcher.instance.implicitView.viewId` should be `0`. 1. The `PlatformDispatcher.instance.implicitView` must be available synchronously at root isolate startup. This allows the framework to determine if it can make single window assumptions at startup. 2. The `PlatformDispatcher.instance.implicitView` reference must not change after startup: if it is null at startup, it must always be null; if it is non-null at startup, it must always be non-null. If "single window" app enters headless mode, the implicit view must remain non-null. In the future, the embedder will control whether an implicit view is created: mobile & legacy desktop apps will have an implicit view, multi-window desktop apps won't have an implicit view. This requires updating the engine's embedder API and is out-of-scope for this change. For now, all apps will have an implicit view. Part of https://github.com/flutter/flutter/issues/120306
This commit is contained in:
parent
0a6f4a106e
commit
a9e12287f6
@ -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) \
|
||||
|
@ -168,6 +168,36 @@ class PlatformDispatcher {
|
||||
// A map of opaque platform view identifiers to view configurations.
|
||||
final Map<Object, ViewConfiguration> _viewConfigurations = <Object, ViewConfiguration>{};
|
||||
|
||||
/// 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<Handle Function()>(symbol: 'PlatformConfigurationNativeApi::ImplicitViewEnabled')
|
||||
external static bool _implicitViewEnabled();
|
||||
|
||||
/// A callback that is invoked whenever the [ViewConfiguration] of any of the
|
||||
/// [views] changes.
|
||||
///
|
||||
|
@ -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.
|
||||
|
@ -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<Window>(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<Window>(
|
||||
kImplicitViewId, ViewportMetrics{1.0, 0.0, 0.0, -1}));
|
||||
}
|
||||
|
||||
void PlatformConfiguration::UpdateLocales(
|
||||
@ -427,6 +434,16 @@ Dart_Handle PlatformConfigurationNativeApi::ComputePlatformResolvedLocale(
|
||||
return tonic::DartConverter<std::vector<std::string>>::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()
|
||||
|
@ -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();
|
||||
|
@ -33,6 +33,8 @@ abstract class PlatformDispatcher {
|
||||
|
||||
Iterable<FlutterView> get views;
|
||||
|
||||
FlutterView? get implicitView;
|
||||
|
||||
VoidCallback? get onMetricsChanged;
|
||||
set onMetricsChanged(VoidCallback? callback);
|
||||
|
||||
|
@ -148,6 +148,34 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
||||
final Map<Object, ui.ViewConfiguration> _windowConfigurations =
|
||||
<Object, ui.ViewConfiguration>{};
|
||||
|
||||
/// 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
|
||||
@ -474,7 +502,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((_) {
|
||||
@ -579,7 +607,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) {
|
||||
@ -1179,7 +1207,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.
|
||||
|
@ -27,8 +27,8 @@ typedef _HandleMessageCallBack = Future<bool> 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 {
|
||||
|
@ -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 = <dynamic, dynamic>{};
|
||||
final JsUrlStrategy jsUrlStrategy = JsUrlStrategy(
|
||||
|
@ -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();
|
||||
|
@ -610,6 +610,9 @@ class RuntimeController : public PlatformConfigurationClient {
|
||||
|
||||
bool FlushRuntimeStateToIsolate();
|
||||
|
||||
// |PlatformConfigurationClient|
|
||||
bool ImplicitViewEnabled() override;
|
||||
|
||||
// |PlatformConfigurationClient|
|
||||
std::string DefaultRouteName() override;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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_;
|
||||
|
@ -888,6 +888,9 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
|
||||
const std::weak_ptr<VsyncWaiter> GetVsyncWaiter() const;
|
||||
|
||||
private:
|
||||
// |RuntimeDelegate|
|
||||
bool ImplicitViewEnabled() override;
|
||||
|
||||
// |RuntimeDelegate|
|
||||
std::string DefaultRouteName() override;
|
||||
|
||||
|
@ -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<flutter::LayerTree>));
|
||||
|
@ -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() {
|
||||
|
@ -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<bool>::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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user