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 3 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
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// state updates. This does not consume the window message. Non-Flutter
// 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 when
// the message has been consumed.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// the logic for application lifecycle state updates. Returns a result when
// the message has been consumed.
// 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) {}

// Claled for FlutterDesktopEngineProcessExternalWindowMessage.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// Claled for FlutterDesktopEngineProcessExternalWindowMessage.
// 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);
vtable_is_alive = false;
}

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 +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|
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
84 changes: 83 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,83 @@ 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);
}

TEST(FlutterWindowTest, PosthumousWindowMessage) {
MockWindowBindingHandlerDelegate delegate;
bool expect_messages = true;
ON_CALL(delegate, OnWindowStateEvent)
.WillByDefault([&](HWND hwnd, WindowStateEvent event) {
FML_LOG(ERROR) << "Got event " << static_cast<int>(event);
EXPECT_TRUE(expect_messages);
});

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

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