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

Reintroduce Windows lifecycle with guard for posthumous OnWindowStateEvent #44344

Merged
merged 11 commits into from
Aug 10, 2023
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 ci/licenses_golden/excluded_files
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,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,15 @@ 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. 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 if the message should be 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) {}

// Called 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
33 changes: 32 additions & 1 deletion shell/platform/windows/flutter_window.cc
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,20 @@ FlutterWindow::FlutterWindow(int width, int height)
current_cursor_ = ::LoadCursor(nullptr, IDC_ARROW);
}

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

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

WindowsRenderTarget FlutterWindow::GetRenderTarget() {
Expand Down Expand Up @@ -328,4 +337,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|
virtual 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
110 changes: 108 additions & 2 deletions shell/platform/windows/flutter_window_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,16 @@ static constexpr int32_t kDefaultPointerDeviceId = 0;

class MockFlutterWindow : public FlutterWindow {
public:
MockFlutterWindow() : FlutterWindow(800, 600) {
MockFlutterWindow(bool reset_view_on_exit = true)
: FlutterWindow(800, 600), reset_view_on_exit_(reset_view_on_exit) {
ON_CALL(*this, GetDpiScale())
.WillByDefault(Return(this->FlutterWindow::GetDpiScale()));
}
virtual ~MockFlutterWindow() {}
virtual ~MockFlutterWindow() {
if (reset_view_on_exit_) {
SetView(nullptr);
}
}

// Wrapper for GetCurrentDPI() which is a protected method.
UINT GetDpi() { return GetCurrentDPI(); }
Expand Down Expand Up @@ -61,6 +66,7 @@ class MockFlutterWindow : public FlutterWindow {
MOCK_METHOD1(Win32MapVkToChar, uint32_t(uint32_t));
MOCK_METHOD0(GetPlatformWindow, HWND());
MOCK_METHOD0(GetAxFragmentRootDelegate, ui::AXFragmentRootDelegateWin*());
MOCK_METHOD1(OnWindowStateEvent, void(WindowStateEvent));

protected:
// |KeyboardManager::WindowDelegate|
Expand All @@ -72,6 +78,7 @@ class MockFlutterWindow : public FlutterWindow {
}

private:
bool reset_view_on_exit_;
FML_DISALLOW_COPY_AND_ASSIGN(MockFlutterWindow);
};

Expand Down Expand Up @@ -229,6 +236,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);
}

// Tests that calls to OnScroll in turn calls GetScrollOffsetMultiplier
Expand Down Expand Up @@ -324,5 +335,100 @@ 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;
});
ON_CALL(win32window, OnWindowStateEvent)
.WillByDefault([&](WindowStateEvent event) {
win32window.FlutterWindow::OnWindowStateEvent(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);
});
ON_CALL(win32window, OnWindowStateEvent)
.WillByDefault([&](WindowStateEvent event) {
win32window.FlutterWindow::OnWindowStateEvent(event);
});

// 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);
}

TEST(FlutterWindowTest, PosthumousWindowMessage) {
MockWindowBindingHandlerDelegate delegate;
int msg_count = 0;
HWND hwnd;
ON_CALL(delegate, OnWindowStateEvent)
.WillByDefault([&](HWND hwnd, WindowStateEvent event) { msg_count++; });

{
MockFlutterWindow win32window(false);
ON_CALL(win32window, GetPlatformWindow).WillByDefault([&]() {
return win32window.FlutterWindow::GetPlatformWindow();
});
ON_CALL(win32window, OnWindowStateEvent)
.WillByDefault([&](WindowStateEvent event) {
win32window.FlutterWindow::OnWindowStateEvent(event);
});
win32window.SetView(&delegate);
win32window.InitializeChild("Title", 1, 1);
hwnd = win32window.GetPlatformWindow();
SendMessage(hwnd, WM_SIZE, 0, MAKEWORD(1, 1));
SendMessage(hwnd, WM_SETFOCUS, 0, 0);

// By setting this to zero before exiting the scope that contains
// win32window, and then checking its value afterwards, enforce that the
// window receive and process messages from its destructor without
// accessing out-of-bounds memory.
msg_count = 0;
}
EXPECT_GE(msg_count, 1);
}

} // 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
Loading