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

Revert "Revert "Listen to window notifications to update application lifecycle"" #44288

Closed
Closed
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 @@ -352,6 +352,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);
}

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