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
Show all changes
35 commits
Select commit Hold shift + click to select a range
e2f4825
Begin lifecycle additions
yaakovschectman Jul 10, 2023
030975b
Add state transitions
yaakovschectman Jul 10, 2023
be44662
Add WindowsLifecycleManager unit test file
yaakovschectman Jul 10, 2023
8ab87df
Test state transitions
yaakovschectman Jul 10, 2023
baadbb1
Capture FlutterWindow messages
yaakovschectman Jul 10, 2023
4612d90
Test top level messages
yaakovschectman Jul 10, 2023
958fe90
Testing FlutterWindow
yaakovschectman Jul 10, 2023
1811b77
Test FlutterWindow messages
yaakovschectman Jul 10, 2023
8ef5e58
Todo for non-flutter windows
yaakovschectman Jul 10, 2023
777215d
Formatting
yaakovschectman Jul 11, 2023
000ff16
LicensE
yaakovschectman Jul 11, 2023
cbea241
License
yaakovschectman Jul 11, 2023
5b7249f
PR feedback
yaakovschectman Jul 11, 2023
db03f83
PR feedback
yaakovschectman Jul 11, 2023
1d07015
[Impeller] Disable color attachment on clip pipelines for Metal & Vul…
bdero Jul 19, 2023
f50d5f4
Roll Skia from 4728980564b1 to a352521a3a7c (3 revisions) (#43816)
skia-flutter-autoroll Jul 19, 2023
98b96ab
Move API call to engine
yaakovschectman Jul 19, 2023
3b2826c
Merge branch 'main' into win_lifecycle
yaakovschectman Jul 19, 2023
b457d1e
Public API
yaakovschectman Jul 26, 2023
1bb2a1d
Test pre-view message
yaakovschectman Jul 26, 2023
6d8e612
Test external window proc
yaakovschectman Jul 26, 2023
b9b3ae0
Formatting
yaakovschectman Jul 26, 2023
8b1fdb2
Remove TODO
yaakovschectman Jul 26, 2023
0e25cfd
Test external window message
yaakovschectman Jul 27, 2023
df35a6a
Move logic to WindowsLifecycleManager
yaakovschectman Jul 27, 2023
bad6302
Comment
yaakovschectman Jul 27, 2023
1623cd9
PR feedback
yaakovschectman Jul 27, 2023
7c0c0bf
Return bool
yaakovschectman Jul 28, 2023
f292962
Add result and TODO
yaakovschectman Jul 28, 2023
addc73c
Update C++ API to optional
yaakovschectman Jul 28, 2023
23d93b3
Test HWNDs
yaakovschectman Jul 28, 2023
99f25cc
Rename lifecycle_manager getter
yaakovschectman Jul 28, 2023
3b3473f
Organize
yaakovschectman Jul 28, 2023
cc76ca4
Nit
yaakovschectman Jul 28, 2023
477f39b
Merge branch 'main' into win_lifecycle
yaakovschectman Jul 28, 2023
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 ci/licenses_golden/excluded_files
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@
../../../flutter/shell/platform/windows/text_input_plugin_unittest.cc
../../../flutter/shell/platform/windows/window_proc_delegate_manager_unittests.cc
../../../flutter/shell/platform/windows/window_unittests.cc
../../../flutter/shell/platform/windows/windows_lifecycle_manager_unittests.cc
../../../flutter/shell/profiling/sampling_profiler_unittest.cc
../../../flutter/shell/testing
../../../flutter/shell/vmservice/.dart_tool
Expand Down
1 change: 1 addition & 0 deletions shell/platform/windows/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ executable("flutter_windows_unittests") {
"text_input_plugin_unittest.cc",
"window_proc_delegate_manager_unittests.cc",
"window_unittests.cc",
"windows_lifecycle_manager_unittests.cc",
]

configs +=
Expand Down
13 changes: 13 additions & 0 deletions shell/platform/windows/client_wrapper/flutter_engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,19 @@ void FlutterEngine::SetNextFrameCallback(std::function<void()> callback) {
this);
}

std::optional<LRESULT> FlutterEngine::ProcessExternalWindowMessage(
HWND hwnd,
UINT message,
WPARAM wparam,
LPARAM lparam) {
LRESULT result;
if (FlutterDesktopEngineProcessExternalWindowMessage(
engine_, hwnd, message, wparam, lparam, &result)) {
return result;
}
return std::nullopt;
}

FlutterDesktopEngineRef FlutterEngine::RelinquishEngine() {
owns_engine_ = false;
return engine_;
Expand Down
26 changes: 26 additions & 0 deletions shell/platform/windows/client_wrapper/flutter_engine_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ class TestFlutterWindowsApi : public testing::StubFlutterWindowsApi {
// |flutter::testing::StubFlutterWindowsApi|
void EngineReloadSystemFonts() override { reload_fonts_called_ = true; }

// |flutter::testing::StubFlutterWindowsApi|
bool EngineProcessExternalWindowMessage(FlutterDesktopEngineRef engine,
HWND hwnd,
UINT message,
WPARAM wparam,
LPARAM lparam,
LRESULT* result) override {
last_external_message_ = message;
return false;
}

bool create_called() { return create_called_; }

bool run_called() { return run_called_; }
Expand All @@ -74,6 +85,8 @@ class TestFlutterWindowsApi : public testing::StubFlutterWindowsApi {
next_frame_callback_ = nullptr;
}

UINT last_external_message() { return last_external_message_; }

private:
bool create_called_ = false;
bool run_called_ = false;
Expand All @@ -82,6 +95,7 @@ class TestFlutterWindowsApi : public testing::StubFlutterWindowsApi {
std::vector<std::string> dart_entrypoint_arguments_;
VoidCallback next_frame_callback_ = nullptr;
void* next_frame_user_data_ = nullptr;
UINT last_external_message_ = 0;
};

} // namespace
Expand Down Expand Up @@ -201,4 +215,16 @@ TEST(FlutterEngineTest, SetNextFrameCallback) {
EXPECT_TRUE(success);
}

TEST(FlutterEngineTest, ProcessExternalWindowMessage) {
testing::ScopedStubFlutterWindowsApi scoped_api_stub(
std::make_unique<TestFlutterWindowsApi>());
auto test_api = static_cast<TestFlutterWindowsApi*>(scoped_api_stub.stub());

FlutterEngine engine(DartProject(L"fake/project/path"));

engine.ProcessExternalWindowMessage(reinterpret_cast<HWND>(1), 1234, 0, 0);

EXPECT_EQ(test_api->last_external_message(), 1234);
}

} // namespace flutter
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include <chrono>
#include <memory>
#include <optional>
#include <string>

#include "binary_messenger.h"
Expand Down Expand Up @@ -84,6 +85,16 @@ class FlutterEngine : public PluginRegistry {
// once on the platform thread.
void SetNextFrameCallback(std::function<void()> callback);

// Called to pass an external window message to the engine for lifecycle
// state updates. This does not consume the window message. Non-Flutter
// windows must call this method in their WndProc in order to be included in
// the logic for application lifecycle state updates. Returns a result when
// the message has been consumed.
std::optional<LRESULT> ProcessExternalWindowMessage(HWND hwnd,
UINT message,
WPARAM wparam,
LPARAM lparam);

private:
// For access to RelinquishEngine.
friend class FlutterViewController;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,20 @@ IDXGIAdapter* FlutterDesktopViewGetGraphicsAdapter(FlutterDesktopViewRef view) {
return nullptr;
}

bool FlutterDesktopEngineProcessExternalWindowMessage(
FlutterDesktopEngineRef engine,
HWND hwnd,
UINT message,
WPARAM wparam,
LPARAM lparam,
LRESULT* result) {
if (s_stub_implementation) {
return s_stub_implementation->EngineProcessExternalWindowMessage(
engine, hwnd, message, wparam, lparam, result);
}
return false;
}

FlutterDesktopViewRef FlutterDesktopPluginRegistrarGetView(
FlutterDesktopPluginRegistrarRef controller) {
// The stub ignores this, so just return an arbitrary non-zero value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,17 @@ class StubFlutterWindowsApi {
// FlutterDesktopPluginRegistrarUnregisterTopLevelWindowProcDelegate.
virtual void PluginRegistrarUnregisterTopLevelWindowProcDelegate(
FlutterDesktopWindowProcCallback delegate) {}

// Claled for FlutterDesktopEngineProcessExternalWindowMessage.
virtual bool EngineProcessExternalWindowMessage(
FlutterDesktopEngineRef engine,
HWND hwnd,
UINT message,
WPARAM wparam,
LPARAM lparam,
LRESULT* result) {
return false;
}
};

// A test helper that owns a stub implementation, making it the test stub for
Expand Down
32 changes: 31 additions & 1 deletion shell/platform/windows/flutter_window.cc
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,19 @@ FlutterWindow::FlutterWindow(int width, int height)
current_cursor_ = ::LoadCursor(nullptr, IDC_ARROW);
}

FlutterWindow::~FlutterWindow() {}
FlutterWindow::~FlutterWindow() {
OnWindowStateEvent(WindowStateEvent::kHide);
}

void FlutterWindow::SetView(WindowBindingHandlerDelegate* window) {
binding_handler_delegate_ = window;
direct_manipulation_owner_->SetBindingHandlerDelegate(window);
if (restored_) {
OnWindowStateEvent(WindowStateEvent::kShow);
}
if (focused_) {
OnWindowStateEvent(WindowStateEvent::kFocus);
}
}

WindowsRenderTarget FlutterWindow::GetRenderTarget() {
Expand Down Expand Up @@ -328,4 +336,26 @@ bool FlutterWindow::NeedsVSync() {
return true;
}

void FlutterWindow::OnWindowStateEvent(WindowStateEvent event) {
switch (event) {
case WindowStateEvent::kShow:
restored_ = true;
break;
case WindowStateEvent::kHide:
restored_ = false;
focused_ = false;
break;
case WindowStateEvent::kFocus:
focused_ = true;
break;
case WindowStateEvent::kUnfocus:
focused_ = false;
break;
}
HWND hwnd = GetPlatformWindow();
if (hwnd && binding_handler_delegate_) {
binding_handler_delegate_->OnWindowStateEvent(hwnd, event);
}
}

} // namespace flutter
9 changes: 9 additions & 0 deletions shell/platform/windows/flutter_window.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ class FlutterWindow : public Window, public WindowBindingHandler {
// |Window|
ui::AXFragmentRootDelegateWin* GetAxFragmentRootDelegate() override;

// |Window|
void OnWindowStateEvent(WindowStateEvent event) override;

private:
// A pointer to a FlutterWindowsView that can be used to update engine
// windowing and input state.
Expand All @@ -173,6 +176,12 @@ class FlutterWindow : public Window, public WindowBindingHandler {
// The cursor rect set by Flutter.
RECT cursor_rect_;

// The window receives resize and focus messages before its view is set, so
// these values cache the state of the window in the meantime so that the
// proper application lifecycle state can be updated once the view is set.
bool restored_ = false;
bool focused_ = false;

FML_DISALLOW_COPY_AND_ASSIGN(FlutterWindow);
};

Expand Down
62 changes: 61 additions & 1 deletion shell/platform/windows/flutter_window_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class MockFlutterWindow : public FlutterWindow {
ON_CALL(*this, GetDpiScale())
.WillByDefault(Return(this->FlutterWindow::GetDpiScale()));
}
virtual ~MockFlutterWindow() {}
virtual ~MockFlutterWindow() { SetView(nullptr); }

// Wrapper for GetCurrentDPI() which is a protected method.
UINT GetDpi() { return GetCurrentDPI(); }
Expand Down Expand Up @@ -229,6 +229,10 @@ TEST(FlutterWindowTest, OnPointerStarSendsDeviceType) {
kDefaultPointerDeviceId, WM_LBUTTONDOWN);
win32window.OnPointerLeave(10.0, 10.0, kFlutterPointerDeviceKindStylus,
kDefaultPointerDeviceId);

// Destruction of win32window sends a HIDE update. In situ, the window is
// owned by the delegate, and so is destructed first. Not so here.
win32window.SetView(nullptr);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added because FlutterWindow will try to send a HIDE update to the binding delegate upon its deconstruction, and this test sets the binding delegate to the address of a local stack variable, which means it is no longer valid once execution leaves the scope of this test function. Alternatively, we could replace delegate with a unique_ptr and get its raw pointer before moving it to pass to EXPECT_CALL

Copy link
Member

Choose a reason for hiding this comment

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

From a lifetime perspective, if the delegate is always strictly owned by the window and its lifetime is constrained to (at most) the lifetime of the owning window, then using a unique_ptr seems reasonable. We should do a quick audit of the code to be sure. If not, then I'd add a one-line comment explaining the rationale here as a breadcrumb for future archaeologists.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, it looks to be the other way around. The delegate i.e. FlutterWindowsView owns a pointer to the handler i.e. FlutterWindow, but where we're using a MockWindowBindingHandlerDelegate in these unit tests, the window is just scoped to the function it's created in. For now I will put a comment here, though moving ownership of win32window in these tests to inside the mock delegate may be feasible.

}

// Tests that calls to OnScroll in turn calls GetScrollOffsetMultiplier
Expand Down Expand Up @@ -324,5 +328,61 @@ TEST(FlutterWindowTest, AlertNode) {
EXPECT_EQ(role.lVal, ROLE_SYSTEM_ALERT);
}

TEST(FlutterWindowTest, LifecycleFocusMessages) {
MockFlutterWindow win32window;
ON_CALL(win32window, GetPlatformWindow).WillByDefault([]() {
return reinterpret_cast<HWND>(1);
});
MockWindowBindingHandlerDelegate delegate;
win32window.SetView(&delegate);

WindowStateEvent last_event;
ON_CALL(delegate, OnWindowStateEvent)
.WillByDefault([&last_event](HWND hwnd, WindowStateEvent event) {
last_event = event;
});

win32window.InjectWindowMessage(WM_SIZE, 0, 0);
EXPECT_EQ(last_event, WindowStateEvent::kHide);

win32window.InjectWindowMessage(WM_SIZE, 0, MAKEWORD(1, 1));
EXPECT_EQ(last_event, WindowStateEvent::kShow);

win32window.InjectWindowMessage(WM_SETFOCUS, 0, 0);
EXPECT_EQ(last_event, WindowStateEvent::kFocus);

win32window.InjectWindowMessage(WM_KILLFOCUS, 0, 0);
EXPECT_EQ(last_event, WindowStateEvent::kUnfocus);
}

TEST(FlutterWindowTest, CachedLifecycleMessage) {
MockFlutterWindow win32window;
ON_CALL(win32window, GetPlatformWindow).WillByDefault([]() {
return reinterpret_cast<HWND>(1);
});

// Restore
win32window.InjectWindowMessage(WM_SIZE, 0, MAKEWORD(1, 1));

// Focus
win32window.InjectWindowMessage(WM_SETFOCUS, 0, 0);

MockWindowBindingHandlerDelegate delegate;
bool focused = false;
bool restored = false;
ON_CALL(delegate, OnWindowStateEvent)
.WillByDefault([&](HWND hwnd, WindowStateEvent event) {
if (event == WindowStateEvent::kFocus) {
focused = true;
} else if (event == WindowStateEvent::kShow) {
restored = true;
}
});

win32window.SetView(&delegate);
EXPECT_TRUE(focused);
EXPECT_TRUE(restored);
}

} // namespace testing
} // namespace flutter
16 changes: 16 additions & 0 deletions shell/platform/windows/flutter_windows.cc
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,22 @@ IDXGIAdapter* FlutterDesktopViewGetGraphicsAdapter(FlutterDesktopViewRef view) {
return nullptr;
}

bool FlutterDesktopEngineProcessExternalWindowMessage(
FlutterDesktopEngineRef engine,
HWND hwnd,
UINT message,
WPARAM wparam,
LPARAM lparam,
LRESULT* result) {
std::optional<LRESULT> lresult =
EngineFromHandle(engine)->ProcessExternalWindowMessage(hwnd, message,
wparam, lparam);
if (result && lresult.has_value()) {
*result = lresult.value();
}
return lresult.has_value();
}

FlutterDesktopViewRef FlutterDesktopPluginRegistrarGetView(
FlutterDesktopPluginRegistrarRef registrar) {
return HandleForView(registrar->engine->view());
Expand Down
24 changes: 20 additions & 4 deletions shell/platform/windows/flutter_windows_engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -584,10 +584,9 @@ void FlutterWindowsEngine::SetNextFrameCallback(fml::closure callback) {
}

void FlutterWindowsEngine::SetLifecycleState(flutter::AppLifecycleState state) {
const char* state_name = flutter::AppLifecycleStateToString(state);
SendPlatformMessage("flutter/lifecycle",
reinterpret_cast<const uint8_t*>(state_name),
strlen(state_name), nullptr, nullptr);
if (lifecycle_manager_) {
lifecycle_manager_->SetLifecycleState(state);
}
}

void FlutterWindowsEngine::SendSystemLocales() {
Expand Down Expand Up @@ -796,4 +795,21 @@ void FlutterWindowsEngine::OnApplicationLifecycleEnabled() {
lifecycle_manager_->BeginProcessingClose();
}

void FlutterWindowsEngine::OnWindowStateEvent(HWND hwnd,
WindowStateEvent event) {
lifecycle_manager_->OnWindowStateEvent(hwnd, event);
}

std::optional<LRESULT> FlutterWindowsEngine::ProcessExternalWindowMessage(
HWND hwnd,
UINT message,
WPARAM wparam,
LPARAM lparam) {
if (lifecycle_manager_) {
return lifecycle_manager_->ExternalWindowMessage(hwnd, message, wparam,
lparam);
}
return std::nullopt;
}

} // namespace flutter
16 changes: 16 additions & 0 deletions shell/platform/windows/flutter_windows_engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,22 @@ class FlutterWindowsEngine {
// Registers the top level handler for the WM_CLOSE window message.
void OnApplicationLifecycleEnabled();

// Called when a Window receives an event that may alter the application
// lifecycle state.
void OnWindowStateEvent(HWND hwnd, WindowStateEvent event);

// Handle a message from a non-Flutter window in the same application.
// Returns a result when the message is consumed and should not be processed
// further.
std::optional<LRESULT> ProcessExternalWindowMessage(HWND hwnd,
UINT message,
WPARAM wparam,
LPARAM lparam);

WindowsLifecycleManager* lifecycle_manager() {
return lifecycle_manager_.get();
}

protected:
// Creates the keyboard key handler.
//
Expand Down
Loading