Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/ui/dart_ui.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Expand Down
30 changes: 30 additions & 0 deletions lib/ui/platform_dispatcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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).
///
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add some kind of a warning here that applications and packages should not rely on this property being set and instead acquire the view via other means (e.g. View.of(context) is appropriate for most use cases)? This property is basically just interesting for low-level app bootstrapping.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I guess you're sort of hinting at this with the "see also" section)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you take another look? I updated this comment to push people towards View.of instead. The new comment is less clear as to when an implicit view is provided by the platform though...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took a stab below at how I would write the documentation for this property. Feel free to copy it as a whole or bits and pieces that you like or ignore it completely.


The implicit [FlutterView] provided by the platform, if any.

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 (e.g. on 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:

  • [PlatformDisptacher.views] for a list of all [FlutterView]s provided by the platform.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's perfect, will update!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated! FYI, I added [View.of] to the "See also" section. It's a little repetitive, but I want to nudge folks towards View.of as much as possible :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added just a couple more suggestions above but this is looking pretty great!

/// 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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will/should this always return the 0th view even in multi-window cases?

Copy link
Member Author

@loic-sharma loic-sharma Feb 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, if there is an implicit view it is guaranteed to have view ID 0. Note that this is a map and not a list. Furthermore, the implicit view & underlying view exist even if the app transitions to headless mode - this matches today's window behavior.

"Modern" desktop apps won't have an implicit view. These are apps that use the new runner templates that don't exist yet. For these future apps, view ID 0 won't have any special meaning.


@Native<Handle Function()>(symbol: 'PlatformConfigurationNativeApi::ImplicitViewEnabled')
external static bool _implicitViewEnabled();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if this maybe should just return the implicit view ID (or null) to avoid that we have to hard-code the magic number here and in platform_configuration.cc.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had this approach originally but I switched to a boolean instead as we don't have any scenarios where the implicit view will have a non-zero ID. I don't feel strongly either ways though, so let me know if you have a preference.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No preference, really. It's all private and internal anyways, so easy to change if it has to.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. I'll leave this open so others can chime in :)


/// A callback that is invoked whenever the [ViewConfiguration] of any of the
/// [views] changes.
///
Expand Down
1 change: 1 addition & 0 deletions lib/ui/window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
21 changes: 19 additions & 2 deletions lib/ui/window/platform_configuration.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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()
Expand Down
14 changes: 13 additions & 1 deletion lib/ui/window/platform_configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
///
Expand Down Expand Up @@ -469,6 +479,8 @@ class PlatformMessageHandlerStorage {
//----------------------------------------------------------------------------
class PlatformConfigurationNativeApi {
public:
static Dart_Handle ImplicitViewEnabled();

static std::string DefaultRouteName();

static void ScheduleFrame();
Expand Down
2 changes: 2 additions & 0 deletions lib/web_ui/lib/platform_dispatcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ abstract class PlatformDispatcher {

Iterable<FlutterView> get views;

FlutterView? get implicitView;

VoidCallback? get onMetricsChanged;
set onMetricsChanged(VoidCallback? callback);

Expand Down
34 changes: 31 additions & 3 deletions lib/web_ui/lib/src/engine/platform_dispatcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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((_) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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.
Expand Down
6 changes: 3 additions & 3 deletions lib/web_ui/lib/src/engine/window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -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 {
Expand Down
7 changes: 7 additions & 0 deletions lib/web_ui/test/window_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same.

Copy link
Member Author

@loic-sharma loic-sharma Feb 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my understanding, only desktop platforms will be able to create windows (and therefore views). All other platforms - mobile and web - will have an implicit view instead. In other words, this test will be correct for the foreseeable future.

Side note: in theory we could allow all embedders to opt-out of the implicit view for add-to-app scenarios. We don't have any concrete plans there though... /cc @goderbauer

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All other platforms - mobile and web - will have an implicit view instead. In other words, this test will be correct for the foreseeable future.

I think we'd rather not assume that (and we don't have to, right?). The user might very possibly attach multiple views from the native code, and the framework will handle them. And with that, the user might even choose not to use the implicit view.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All other platforms - mobile and web - will have an implicit view instead.

Web will (possibly) want to render to multiple different target DIVs at some point, so I guess we're going to have multiple "views" too?

Copy link
Member Author

@loic-sharma loic-sharma Feb 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All other platforms - mobile and web - will have an implicit view instead.

Web will (possibly) want to render to multiple different target DIVs at some point, so I guess we're going to have multiple "views" too?

To clarify there's two semi-related things here:

  1. Multi-view: all platforms, including web, will have multi-view support from the framework side
  2. Implicit view: this replaces the window global

Having an implicit view does not block multi-view. It's just the replacement for the window global. For now, we don't have plans to move web off the implicit view as that's not required for supporting multi-window on desktop.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarification @loic-sharma (expect a quick call from me soon-ish though, I want to start redoing stuff in the web engine so the multi-view lands more easily!)

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(
Expand Down
5 changes: 5 additions & 0 deletions runtime/runtime_controller.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
3 changes: 3 additions & 0 deletions runtime/runtime_controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,9 @@ class RuntimeController : public PlatformConfigurationClient {

bool FlushRuntimeStateToIsolate();

// |PlatformConfigurationClient|
bool ImplicitViewEnabled() override;

// |PlatformConfigurationClient|
std::string DefaultRouteName() override;

Expand Down
2 changes: 2 additions & 0 deletions runtime/runtime_delegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 8 additions & 0 deletions shell/common/engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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_;
Expand Down
3 changes: 3 additions & 0 deletions shell/common/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
1 change: 1 addition & 0 deletions shell/common/engine_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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>));
Expand Down
7 changes: 7 additions & 0 deletions shell/platform/embedder/fixtures/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,15 @@ void executableNameNotNull() {
notifyStringValue(Platform.executable);
}

@pragma('vm:entry-point')
void implicitViewNotNull() {
notifyBoolValue(PlatformDispatcher.instance.implicitView != null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this test do? To ensure that PlatformDispatcher.instance.implicitView isn't null? But this is only because we don't have an option to control it right? Should we add a TODO or at least comment to explain that this test might (should) be broken once we allow non-implicitView?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a fixture to check whether the implicit view is non-null. The real test is EmbedderTest.ImplicitViewNotNull.

But this is only because we don't have an option to control it right?

Yup that's correct.

Should we add a TODO or at least comment to explain that this test might (should) be broken once we allow non-implicitView?

Good idea, added a TODO to EmbedderTest.ImplicitViewNotNull.

}

@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() {
Expand Down
24 changes: 24 additions & 0 deletions shell/platform/embedder/tests/embedder_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down