Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 62833e5

Browse files
committed
[Windows] Allow adding/removing views
1 parent 8c93994 commit 62833e5

File tree

9 files changed

+273
-25
lines changed

9 files changed

+273
-25
lines changed

shell/platform/embedder/embedder.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3375,6 +3375,8 @@ FlutterEngineResult FlutterEngineGetProcAddresses(
33753375
SET_PROC(NotifyDisplayUpdate, FlutterEngineNotifyDisplayUpdate);
33763376
SET_PROC(ScheduleFrame, FlutterEngineScheduleFrame);
33773377
SET_PROC(SetNextFrameCallback, FlutterEngineSetNextFrameCallback);
3378+
SET_PROC(AddView, FlutterEngineAddView);
3379+
SET_PROC(RemoveView, FlutterEngineRemoveView);
33783380
#undef SET_PROC
33793381

33803382
return kSuccess;

shell/platform/embedder/embedder.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3287,6 +3287,12 @@ typedef FlutterEngineResult (*FlutterEngineSetNextFrameCallbackFnPtr)(
32873287
FLUTTER_API_SYMBOL(FlutterEngine) engine,
32883288
VoidCallback callback,
32893289
void* user_data);
3290+
typedef FlutterEngineResult (*FlutterEngineAddViewFnPtr)(
3291+
FLUTTER_API_SYMBOL(FlutterEngine) engine,
3292+
const FlutterAddViewInfo* info);
3293+
typedef FlutterEngineResult (*FlutterEngineRemoveViewFnPtr)(
3294+
FLUTTER_API_SYMBOL(FlutterEngine) engine,
3295+
const FlutterRemoveViewInfo* info);
32903296

32913297
/// Function-pointer-based versions of the APIs above.
32923298
typedef struct {
@@ -3333,6 +3339,8 @@ typedef struct {
33333339
FlutterEngineNotifyDisplayUpdateFnPtr NotifyDisplayUpdate;
33343340
FlutterEngineScheduleFrameFnPtr ScheduleFrame;
33353341
FlutterEngineSetNextFrameCallbackFnPtr SetNextFrameCallback;
3342+
FlutterEngineAddViewFnPtr AddView;
3343+
FlutterEngineRemoveViewFnPtr RemoveView;
33363344
} FlutterEngineProcTable;
33373345

33383346
//------------------------------------------------------------------------------

shell/platform/windows/fixtures/main.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,3 +361,12 @@ void signalViewIds() {
361361

362362
signalStringValue('View IDs: [${viewIds.join(', ')}]');
363363
}
364+
365+
@pragma('vm:entry-point')
366+
void onMetricsChangedSignalViewIds() {
367+
ui.PlatformDispatcher.instance.onMetricsChanged = () {
368+
signalViewIds();
369+
};
370+
371+
signal();
372+
}

shell/platform/windows/flutter_windows.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ static FlutterDesktopViewControllerRef CreateViewController(
9393

9494
std::unique_ptr<flutter::FlutterWindowsView> view =
9595
engine_ptr->CreateView(std::move(window_wrapper));
96+
if (!view) {
97+
return nullptr;
98+
}
99+
96100
auto controller = std::make_unique<flutter::FlutterWindowsViewController>(
97101
std::move(engine), std::move(view));
98102

shell/platform/windows/flutter_windows_engine.cc

Lines changed: 112 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "flutter/fml/logging.h"
1313
#include "flutter/fml/paths.h"
1414
#include "flutter/fml/platform/win/wstring_conversion.h"
15+
#include "flutter/fml/synchronization/waitable_event.h"
1516
#include "flutter/shell/platform/common/client_wrapper/binary_messenger_impl.h"
1617
#include "flutter/shell/platform/common/client_wrapper/include/flutter/standard_message_codec.h"
1718
#include "flutter/shell/platform/common/path_utils.h"
@@ -149,6 +150,7 @@ FlutterWindowsEngine::FlutterWindowsEngine(
149150
: project_(std::make_unique<FlutterProjectBundle>(project)),
150151
windows_proc_table_(std::move(windows_proc_table)),
151152
aot_data_(nullptr, nullptr),
153+
views_mutex_(fml::SharedMutex::Create()),
152154
lifecycle_manager_(std::make_unique<WindowsLifecycleManager>(this)) {
153155
if (windows_proc_table_ == nullptr) {
154156
windows_proc_table_ = std::make_shared<WindowsProcTable>();
@@ -492,34 +494,118 @@ bool FlutterWindowsEngine::Stop() {
492494

493495
std::unique_ptr<FlutterWindowsView> FlutterWindowsEngine::CreateView(
494496
std::unique_ptr<WindowBindingHandler> window) {
495-
// TODO(loicsharma): Remove implicit view assumption.
496-
// https://github.com/flutter/flutter/issues/142845
497+
auto view_id = next_view_id_;
497498
auto view = std::make_unique<FlutterWindowsView>(
498-
kImplicitViewId, this, std::move(window), windows_proc_table_);
499+
view_id, this, std::move(window), windows_proc_table_);
499500

500501
view->CreateRenderSurface();
501502

502-
views_[kImplicitViewId] = view.get();
503+
next_view_id_++;
504+
505+
{
506+
// Add the view to the embedder. This must happen before the engine
507+
// is notified the view exists and starts presenting to it.
508+
fml::UniqueLock write_lock{*views_mutex_};
509+
FML_DCHECK(views_.find(view_id) == views_.end());
510+
views_[view_id] = view.get();
511+
}
512+
513+
if (!view->IsImplicitView()) {
514+
FML_DCHECK(running());
515+
516+
struct Captures {
517+
fml::AutoResetWaitableEvent latch;
518+
bool added;
519+
};
520+
Captures captures;
521+
522+
FlutterWindowMetricsEvent metrics = view->CreateWindowMetricsEvent();
523+
524+
FlutterAddViewInfo info = {};
525+
info.struct_size = sizeof(FlutterAddViewInfo);
526+
info.view_id = view_id;
527+
info.view_metrics = &metrics;
528+
info.user_data = &captures;
529+
info.add_view_callback = [](const FlutterAddViewResult* result) {
530+
Captures* captures = reinterpret_cast<Captures*>(result->user_data);
531+
captures->added = result->added;
532+
captures->latch.Signal();
533+
};
534+
535+
embedder_api_.AddView(engine_, &info);
536+
537+
// Block the platform thread until the engine has added the view.
538+
// TODO(loicsharma): This blocks the platform thread eagerly and can
539+
// cause unnecessary delay in input processing. Instead, this should block
540+
// lazily only when the app does an operation which needs the view.
541+
// https://github.com/flutter/flutter/issues/146248
542+
captures.latch.Wait();
543+
544+
if (!captures.added) {
545+
// Adding the view failed. Update the embedder's state to match the
546+
// engine's state. This is unexpected and indicates a bug in the Windows
547+
// embedder.
548+
FML_LOG(ERROR) << "FlutterEngineAddView failed to add view";
549+
fml::UniqueLock write_lock{*views_mutex_};
550+
views_.erase(view_id);
551+
return nullptr;
552+
}
553+
}
503554

504555
return std::move(view);
505556
}
506557

507558
void FlutterWindowsEngine::RemoveView(FlutterViewId view_id) {
508559
FML_DCHECK(running());
509-
FML_DCHECK(views_.find(view_id) != views_.end());
510560

511-
if (view_id == kImplicitViewId) {
512-
// The engine and framework assume the implicit view always exists.
513-
// Attempts to render to the implicit view will be ignored.
514-
views_.erase(view_id);
515-
return;
561+
// Notify the engine to stop rendering to the view if it isn't the implicit
562+
// view. The engine and framework assume the implicit view always exists and
563+
// can continue presenting.
564+
if (view_id != kImplicitViewId) {
565+
struct Captures {
566+
fml::AutoResetWaitableEvent latch;
567+
bool removed;
568+
};
569+
Captures captures;
570+
571+
FlutterRemoveViewInfo info = {};
572+
info.struct_size = sizeof(FlutterRemoveViewInfo);
573+
info.view_id = view_id;
574+
info.user_data = &captures;
575+
info.remove_view_callback = [](const FlutterRemoveViewResult* result) {
576+
// This is invoked on the raster thread, the same thread that the present
577+
// callback is invoked. If |FlutterRemoveViewResult.removed| is `true`,
578+
// the engine guarantees the view won't be presented.
579+
Captures* captures = reinterpret_cast<Captures*>(result->user_data);
580+
captures->removed = result->removed;
581+
captures->latch.Signal();
582+
};
583+
584+
embedder_api_.RemoveView(engine_, &info);
585+
586+
// Block the platform thread until the engine has removed the view.
587+
// TODO(loicsharma): This blocks the platform thread eagerly and can
588+
// cause unnecessary delay in input processing. Instead, this should block
589+
// lazily only when an operation needs the view.
590+
// https://github.com/flutter/flutter/issues/146248
591+
captures.latch.Wait();
592+
593+
if (!captures.removed) {
594+
// Removing the view failed. This is unexpected and indicates a bug in the
595+
// Windows embedder.
596+
FML_LOG(ERROR) << "FlutterEngineRemoveView failed to remove view";
597+
return;
598+
}
516599
}
517600

518-
// TODO(loicsharma): Remove the view from the engine using the
519-
// `FlutterEngineRemoveView` embedder API. Windows does not
520-
// support views other than the implicit view yet.
521-
// https://github.com/flutter/flutter/issues/144810
522-
FML_UNREACHABLE();
601+
{
602+
// The engine no longer presents to the view. Remove the view from the
603+
// embedder.
604+
fml::UniqueLock write_lock{*views_mutex_};
605+
606+
FML_DCHECK(views_.find(view_id) != views_.end());
607+
views_.erase(view_id);
608+
}
523609
}
524610

525611
void FlutterWindowsEngine::OnVsync(intptr_t baton) {
@@ -551,6 +637,8 @@ std::chrono::nanoseconds FlutterWindowsEngine::FrameInterval() {
551637
}
552638

553639
FlutterWindowsView* FlutterWindowsEngine::view(FlutterViewId view_id) const {
640+
fml::SharedLock read_lock{*views_mutex_};
641+
554642
auto iterator = views_.find(view_id);
555643
if (iterator == views_.end()) {
556644
return nullptr;
@@ -779,6 +867,8 @@ bool FlutterWindowsEngine::DispatchSemanticsAction(
779867

780868
void FlutterWindowsEngine::UpdateSemanticsEnabled(bool enabled) {
781869
if (engine_ && semantics_enabled_ != enabled) {
870+
fml::SharedLock read_lock{*views_mutex_};
871+
782872
semantics_enabled_ = enabled;
783873
embedder_api_.UpdateSemanticsEnabled(engine_, enabled);
784874
for (auto iterator = views_.begin(); iterator != views_.end(); iterator++) {
@@ -844,6 +934,8 @@ void FlutterWindowsEngine::OnQuit(std::optional<HWND> hwnd,
844934
}
845935

846936
void FlutterWindowsEngine::OnDwmCompositionChanged() {
937+
fml::SharedLock read_lock{*views_mutex_};
938+
847939
for (auto iterator = views_.begin(); iterator != views_.end(); iterator++) {
848940
iterator->second->OnDwmCompositionChanged();
849941
}
@@ -875,6 +967,11 @@ void FlutterWindowsEngine::OnChannelUpdate(std::string name, bool listening) {
875967
}
876968

877969
bool FlutterWindowsEngine::Present(const FlutterPresentViewInfo* info) {
970+
// This runs on the raster thread. Lock the views map for the entirety of the
971+
// present operation to block the platform thread from destroying the
972+
// view during the present.
973+
fml::SharedLock read_lock{*views_mutex_};
974+
878975
auto iterator = views_.find(info->view_id);
879976
if (iterator == views_.end()) {
880977
return false;

shell/platform/windows/flutter_windows_engine.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include "flutter/fml/closure.h"
1818
#include "flutter/fml/macros.h"
19+
#include "flutter/fml/synchronization/shared_mutex.h"
1920
#include "flutter/shell/platform/common/accessibility_bridge.h"
2021
#include "flutter/shell/platform/common/app_lifecycle_state.h"
2122
#include "flutter/shell/platform/common/client_wrapper/binary_messenger_impl.h"
@@ -121,6 +122,8 @@ class FlutterWindowsEngine {
121122
virtual bool Stop();
122123

123124
// Create a view that can display this engine's content.
125+
//
126+
// Returns null on failure.
124127
std::unique_ptr<FlutterWindowsView> CreateView(
125128
std::unique_ptr<WindowBindingHandler> window);
126129

@@ -359,9 +362,30 @@ class FlutterWindowsEngine {
359362
// AOT data, if any.
360363
UniqueAotDataPtr aot_data_;
361364

365+
// The ID that the next view will have.
366+
FlutterViewId next_view_id_ = kImplicitViewId;
367+
362368
// The views displaying the content running in this engine, if any.
369+
//
370+
// This is read and mutated by the platform thread. This is read by the raster
371+
// thread to present content to a view.
372+
//
373+
// Reads to this object on non-platform threads must be protected
374+
// by acquiring a shared lock on |views_mutex_|.
375+
//
376+
// Writes to this object must only happen on the platform thread
377+
// and must be protected by acquiring an exclusive lock on |views_mutex_|.
363378
std::unordered_map<FlutterViewId, FlutterWindowsView*> views_;
364379

380+
// The mutex that protects the |views_| map.
381+
//
382+
// The raster thread acquires a shared lock to present to a view.
383+
//
384+
// The platform thread acquires a shared lock to access the view.
385+
// The platform thread acquires an exclusive lock before adding
386+
// a view to the engine or after removing a view from the engine.
387+
std::unique_ptr<fml::SharedMutex> views_mutex_;
388+
365389
// Task runner for tasks posted from the engine.
366390
std::unique_ptr<TaskRunner> task_runner_;
367391

shell/platform/windows/flutter_windows_unittests.cc

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,5 +570,65 @@ TEST_F(WindowsTest, GetKeyboardStateHeadless) {
570570
}
571571
}
572572

573+
// Verify the embedder can add and remove views.
574+
TEST_F(WindowsTest, AddRemoveView) {
575+
std::mutex mutex;
576+
std::string view_ids;
577+
578+
auto& context = GetContext();
579+
WindowsConfigBuilder builder(context);
580+
builder.SetDartEntrypoint("onMetricsChangedSignalViewIds");
581+
582+
fml::AutoResetWaitableEvent ready_latch;
583+
context.AddNativeFunction(
584+
"Signal", CREATE_NATIVE_ENTRY(
585+
[&](Dart_NativeArguments args) { ready_latch.Signal(); }));
586+
587+
context.AddNativeFunction(
588+
"SignalStringValue", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
589+
auto handle = Dart_GetNativeArgument(args, 0);
590+
ASSERT_FALSE(Dart_IsError(handle));
591+
592+
std::scoped_lock lock{mutex};
593+
view_ids = tonic::DartConverter<std::string>::FromDart(handle);
594+
}));
595+
596+
// Create the implicit view.
597+
ViewControllerPtr first_controller{builder.Run()};
598+
ASSERT_NE(first_controller, nullptr);
599+
600+
ready_latch.Wait();
601+
602+
// Create a second view.
603+
FlutterDesktopEngineRef engine =
604+
FlutterDesktopViewControllerGetEngine(first_controller.get());
605+
FlutterDesktopViewControllerProperties properties = {};
606+
properties.width = 100;
607+
properties.height = 100;
608+
ViewControllerPtr second_controller{
609+
FlutterDesktopEngineCreateViewController(engine, &properties)};
610+
ASSERT_NE(second_controller, nullptr);
611+
612+
// Pump messages for the Windows platform task runner until the view is added.
613+
while (true) {
614+
PumpMessage();
615+
std::scoped_lock lock{mutex};
616+
if (view_ids == "View IDs: [0, 1]") {
617+
break;
618+
}
619+
}
620+
621+
// Delete the second view and pump messages for the Windows platform task
622+
// runner until the view is removed.
623+
second_controller.reset();
624+
while (true) {
625+
PumpMessage();
626+
std::scoped_lock lock{mutex};
627+
if (view_ids == "View IDs: [0]") {
628+
break;
629+
}
630+
}
631+
}
632+
573633
} // namespace testing
574634
} // namespace flutter

0 commit comments

Comments
 (0)