Skip to content

Commit

Permalink
[windows] Allow delegation of top-level WindowProc (flutter#20613)
Browse files Browse the repository at this point in the history
Adds APIs for runners to delegate WindowProc handlers into the Flutter
engine, and for plugins to register as possible delegates.

This allows for plugins to alter top-level window behavior in ways that
can only be done from the WindowProc, such as resize control. This
functionality remains entirely on the native side, so is synchronous.

Part of flutter#53168
  • Loading branch information
stuartmorgan committed Aug 19, 2020
1 parent 06e918e commit dcbe3d3
Show file tree
Hide file tree
Showing 19 changed files with 660 additions and 8 deletions.
3 changes: 3 additions & 0 deletions ci/licenses_golden/licenses_flutter
Expand Up @@ -1376,6 +1376,9 @@ FILE: ../../../flutter/shell/platform/windows/win32_task_runner.cc
FILE: ../../../flutter/shell/platform/windows/win32_task_runner.h
FILE: ../../../flutter/shell/platform/windows/win32_window.cc
FILE: ../../../flutter/shell/platform/windows/win32_window.h
FILE: ../../../flutter/shell/platform/windows/win32_window_proc_delegate_manager.cc
FILE: ../../../flutter/shell/platform/windows/win32_window_proc_delegate_manager.h
FILE: ../../../flutter/shell/platform/windows/win32_window_proc_delegate_manager_unittests.cc
FILE: ../../../flutter/shell/platform/windows/win32_window_unittests.cc
FILE: ../../../flutter/shell/platform/windows/window_binding_handler.h
FILE: ../../../flutter/shell/platform/windows/window_binding_handler_delegate.h
Expand Down
3 changes: 3 additions & 0 deletions shell/platform/windows/BUILD.gn
Expand Up @@ -68,6 +68,8 @@ source_set("flutter_windows_source") {
"win32_task_runner.h",
"win32_window.cc",
"win32_window.h",
"win32_window_proc_delegate_manager.cc",
"win32_window_proc_delegate_manager.h",
"window_binding_handler.h",
"window_binding_handler_delegate.h",
"window_state.h",
Expand Down Expand Up @@ -127,6 +129,7 @@ executable("flutter_windows_unittests") {
"testing/win32_window_test.h",
"win32_dpi_utils_unittests.cc",
"win32_flutter_window_unittests.cc",
"win32_window_proc_delegate_manager_unittests.cc",
"win32_window_unittests.cc",
]

Expand Down
11 changes: 11 additions & 0 deletions shell/platform/windows/client_wrapper/flutter_view_controller.cc
Expand Up @@ -29,6 +29,17 @@ FlutterViewController::~FlutterViewController() {
}
}

std::optional<LRESULT> FlutterViewController::HandleTopLevelWindowProc(
HWND hwnd,
UINT message,
WPARAM wparam,
LPARAM lparam) {
LRESULT result;
bool handled = FlutterDesktopViewControllerHandleTopLevelWindowProc(
controller_, hwnd, message, wparam, lparam, &result);
return handled ? result : std::optional<LRESULT>(std::nullopt);
}

std::chrono::nanoseconds FlutterViewController::ProcessMessages() {
return engine_->ProcessMessages();
}
Expand Down
Expand Up @@ -5,8 +5,12 @@
#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_CLIENT_WRAPPER_INCLUDE_FLUTTER_FLUTTER_VIEW_CONTROLLER_H_
#define FLUTTER_SHELL_PLATFORM_WINDOWS_CLIENT_WRAPPER_INCLUDE_FLUTTER_FLUTTER_VIEW_CONTROLLER_H_

#include <Windows.h>
#include <flutter_windows.h>

#include <memory>
#include <optional>

#include "dart_project.h"
#include "flutter_engine.h"
#include "flutter_view.h"
Expand Down Expand Up @@ -42,6 +46,16 @@ class FlutterViewController : public PluginRegistry {
// Returns the view managed by this controller.
FlutterView* view() { return view_.get(); }

// Allows the Flutter engine and any interested plugins an opportunity to
// handle the given message.
//
// If a result is returned, then the message was handled in such a way that
// further handling should not be done.
std::optional<LRESULT> HandleTopLevelWindowProc(HWND hwnd,
UINT message,
WPARAM wparam,
LPARAM lparam);

// DEPRECATED. Call engine()->ProcessMessages() instead.
std::chrono::nanoseconds ProcessMessages();

Expand Down
Expand Up @@ -5,15 +5,24 @@
#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_CLIENT_WRAPPER_INCLUDE_FLUTTER_PLUGIN_REGISTRAR_WINDOWS_H_
#define FLUTTER_SHELL_PLATFORM_WINDOWS_CLIENT_WRAPPER_INCLUDE_FLUTTER_PLUGIN_REGISTRAR_WINDOWS_H_

#include <Windows.h>
#include <flutter_windows.h>

#include <memory>
#include <optional>

#include "flutter_view.h"
#include "plugin_registrar.h"

namespace flutter {

// A delegate callback for WindowProc delegation.
//
// Implementations should return a value only if they have handled the message
// and want to stop all further handling.
using WindowProcDelegate = std::function<std::optional<
LRESULT>(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)>;

// An extension to PluginRegistrar providing access to Windows-specific
// functionality.
class PluginRegistrarWindows : public PluginRegistrar {
Expand All @@ -35,9 +44,78 @@ class PluginRegistrarWindows : public PluginRegistrar {

FlutterView* GetView() { return view_.get(); }

// Registers |delegate| to recieve WindowProc callbacks for the top-level
// window containing this Flutter instance. Returns an ID that can be used to
// unregister the handler.
//
// Delegates are not guaranteed to be called:
// - The application may choose not to delegate WindowProc calls.
// - If multiple plugins are registered, the first one that returns a value
// from the delegate message will "win", and others will not be called.
// The order of delegate calls is not defined.
//
// Delegates should be implemented as narrowly as possible, only returning
// a value in cases where it's important that other delegates not run, to
// minimize the chances of conflicts between plugins.
int RegisterTopLevelWindowProcDelegate(WindowProcDelegate delegate) {
if (window_proc_delegates_.empty()) {
FlutterDesktopPluginRegistrarRegisterTopLevelWindowProcDelegate(
registrar(), PluginRegistrarWindows::OnTopLevelWindowProc, this);
}
int delegate_id = next_window_proc_delegate_id_++;
window_proc_delegates_.emplace(delegate_id, std::move(delegate));
return delegate_id;
}

// Unregisters a previously registered delegate.
void UnregisterTopLevelWindowProcDelegate(int proc_id) {
window_proc_delegates_.erase(proc_id);
if (window_proc_delegates_.empty()) {
FlutterDesktopPluginRegistrarUnregisterTopLevelWindowProcDelegate(
registrar(), PluginRegistrarWindows::OnTopLevelWindowProc);
}
}

private:
// A FlutterDesktopWindowProcCallback implementation that forwards back to
// a PluginRegistarWindows instance provided as |user_data|.
static bool OnTopLevelWindowProc(HWND hwnd,
UINT message,
WPARAM wparam,
LPARAM lparam,
void* user_data,
LRESULT* result) {
const auto* registrar = static_cast<PluginRegistrarWindows*>(user_data);
std::optional optional_result = registrar->CallTopLevelWindowProcDelegates(
hwnd, message, wparam, lparam);
if (optional_result) {
*result = *optional_result;
}
return optional_result.has_value();
}

std::optional<LRESULT> CallTopLevelWindowProcDelegates(HWND hwnd,
UINT message,
WPARAM wparam,
LPARAM lparam) const {
std::optional<LRESULT> result;
for (const auto& pair : window_proc_delegates_) {
result = pair.second(hwnd, message, wparam, lparam);
// Stop as soon as any delegate indicates that it has handled the message.
if (result) {
break;
}
}
return result;
}

// The associated FlutterView, if any.
std::unique_ptr<FlutterView> view_;

// The next ID to return from RegisterWindowProcDelegate.
int next_window_proc_delegate_id_ = 1;

std::map<int, WindowProcDelegate> window_proc_delegates_;
};

} // namespace flutter
Expand Down
Expand Up @@ -14,7 +14,34 @@ namespace flutter {
namespace {

// Stub implementation to validate calls to the API.
class TestWindowsApi : public testing::StubFlutterWindowsApi {};
class TestWindowsApi : public testing::StubFlutterWindowsApi {
public:
void PluginRegistrarRegisterTopLevelWindowProcDelegate(
FlutterDesktopWindowProcCallback delegate,
void* user_data) override {
++registered_delegate_count_;
last_registered_delegate_ = delegate;
last_registered_user_data_ = user_data;
}

void PluginRegistrarUnregisterTopLevelWindowProcDelegate(
FlutterDesktopWindowProcCallback delegate) override {
--registered_delegate_count_;
}

int registered_delegate_count() { return registered_delegate_count_; }

FlutterDesktopWindowProcCallback last_registered_delegate() {
return last_registered_delegate_;
}

void* last_registered_user_data() { return last_registered_user_data_; }

private:
int registered_delegate_count_ = 0;
FlutterDesktopWindowProcCallback last_registered_delegate_ = nullptr;
void* last_registered_user_data_ = nullptr;
};

} // namespace

Expand All @@ -27,4 +54,104 @@ TEST(PluginRegistrarWindowsTest, GetView) {
EXPECT_NE(registrar.GetView(), nullptr);
}

TEST(PluginRegistrarWindowsTest, RegisterUnregister) {
testing::ScopedStubFlutterWindowsApi scoped_api_stub(
std::make_unique<TestWindowsApi>());
auto test_api = static_cast<TestWindowsApi*>(scoped_api_stub.stub());
PluginRegistrarWindows registrar(
reinterpret_cast<FlutterDesktopPluginRegistrarRef>(1));

WindowProcDelegate delegate = [](HWND hwnd, UINT message, WPARAM wparam,
LPARAM lparam) {
return std::optional<LRESULT>();
};
int id_a = registrar.RegisterTopLevelWindowProcDelegate(delegate);
EXPECT_EQ(test_api->registered_delegate_count(), 1);
int id_b = registrar.RegisterTopLevelWindowProcDelegate(delegate);
// All the C++-level delegates are driven by a since C callback, so the
// registration count should stay the same.
EXPECT_EQ(test_api->registered_delegate_count(), 1);

// Unregistering one of the two delegates shouldn't cause the underlying C
// callback to be unregistered.
registrar.UnregisterTopLevelWindowProcDelegate(id_a);
EXPECT_EQ(test_api->registered_delegate_count(), 1);
// Unregistering both should unregister it.
registrar.UnregisterTopLevelWindowProcDelegate(id_b);
EXPECT_EQ(test_api->registered_delegate_count(), 0);

EXPECT_NE(id_a, id_b);
}

TEST(PluginRegistrarWindowsTest, CallsRegisteredDelegates) {
testing::ScopedStubFlutterWindowsApi scoped_api_stub(
std::make_unique<TestWindowsApi>());
auto test_api = static_cast<TestWindowsApi*>(scoped_api_stub.stub());
PluginRegistrarWindows registrar(
reinterpret_cast<FlutterDesktopPluginRegistrarRef>(1));

HWND dummy_hwnd;
bool called_a = false;
WindowProcDelegate delegate_a = [&called_a, &dummy_hwnd](
HWND hwnd, UINT message, WPARAM wparam,
LPARAM lparam) {
called_a = true;
EXPECT_EQ(hwnd, dummy_hwnd);
EXPECT_EQ(message, 2);
EXPECT_EQ(wparam, 3);
EXPECT_EQ(lparam, 4);
return std::optional<LRESULT>();
};
bool called_b = false;
WindowProcDelegate delegate_b = [&called_b](HWND hwnd, UINT message,
WPARAM wparam, LPARAM lparam) {
called_b = true;
return std::optional<LRESULT>();
};
int id_a = registrar.RegisterTopLevelWindowProcDelegate(delegate_a);
int id_b = registrar.RegisterTopLevelWindowProcDelegate(delegate_b);

LRESULT result = 0;
bool handled = test_api->last_registered_delegate()(
dummy_hwnd, 2, 3, 4, test_api->last_registered_user_data(), &result);
EXPECT_TRUE(called_a);
EXPECT_TRUE(called_b);
EXPECT_FALSE(handled);
}

TEST(PluginRegistrarWindowsTest, StopsOnceHandled) {
testing::ScopedStubFlutterWindowsApi scoped_api_stub(
std::make_unique<TestWindowsApi>());
auto test_api = static_cast<TestWindowsApi*>(scoped_api_stub.stub());
PluginRegistrarWindows registrar(
reinterpret_cast<FlutterDesktopPluginRegistrarRef>(1));

bool called_a = false;
WindowProcDelegate delegate_a = [&called_a](HWND hwnd, UINT message,
WPARAM wparam, LPARAM lparam) {
called_a = true;
return std::optional<LRESULT>(7);
};
bool called_b = false;
WindowProcDelegate delegate_b = [&called_b](HWND hwnd, UINT message,
WPARAM wparam, LPARAM lparam) {
called_b = true;
return std::optional<LRESULT>(7);
};
int id_a = registrar.RegisterTopLevelWindowProcDelegate(delegate_a);
int id_b = registrar.RegisterTopLevelWindowProcDelegate(delegate_b);

HWND dummy_hwnd;
LRESULT result = 0;
bool handled = test_api->last_registered_delegate()(
dummy_hwnd, 2, 3, 4, test_api->last_registered_user_data(), &result);
// Only one of the delegates should have been called, since each claims to
// have fully handled the message.
EXPECT_TRUE(called_a || called_b);
EXPECT_NE(called_a, called_b);
// The return value should propagate through.
EXPECT_TRUE(handled);
EXPECT_EQ(result, 7);
}

} // namespace flutter
Expand Up @@ -64,6 +64,20 @@ FlutterDesktopViewRef FlutterDesktopViewControllerGetView(
return reinterpret_cast<FlutterDesktopViewRef>(1);
}

bool FlutterDesktopViewControllerHandleTopLevelWindowProc(
FlutterDesktopViewControllerRef controller,
HWND hwnd,
UINT message,
WPARAM wparam,
LPARAM lparam,
LRESULT* result) {
if (s_stub_implementation) {
return s_stub_implementation->ViewControllerHandleTopLevelWindowProc(
hwnd, message, wparam, lparam, result);
}
return false;
}

FlutterDesktopEngineRef FlutterDesktopEngineCreate(
const FlutterDesktopEngineProperties& engine_properties) {
if (s_stub_implementation) {
Expand Down Expand Up @@ -119,3 +133,23 @@ FlutterDesktopViewRef FlutterDesktopPluginRegistrarGetView(
// The stub ignores this, so just return an arbitrary non-zero value.
return reinterpret_cast<FlutterDesktopViewRef>(1);
}

void FlutterDesktopPluginRegistrarRegisterTopLevelWindowProcDelegate(
FlutterDesktopPluginRegistrarRef registrar,
FlutterDesktopWindowProcCallback delegate,
void* user_data) {
if (s_stub_implementation) {
return s_stub_implementation
->PluginRegistrarRegisterTopLevelWindowProcDelegate(delegate,
user_data);
}
}

void FlutterDesktopPluginRegistrarUnregisterTopLevelWindowProcDelegate(
FlutterDesktopPluginRegistrarRef registrar,
FlutterDesktopWindowProcCallback delegate) {
if (s_stub_implementation) {
return s_stub_implementation
->PluginRegistrarUnregisterTopLevelWindowProcDelegate(delegate);
}
}
Expand Up @@ -38,6 +38,15 @@ class StubFlutterWindowsApi {
// Called for FlutterDesktopViewControllerDestroy.
virtual void ViewControllerDestroy() {}

// Called for FlutterDesktopViewControllerHandleTopLevelWindowProc.
virtual bool ViewControllerHandleTopLevelWindowProc(HWND hwnd,
UINT message,
WPARAM wparam,
LPARAM lparam,
LRESULT* result) {
return false;
}

// Called for FlutterDesktopEngineCreate.
virtual FlutterDesktopEngineRef EngineCreate(
const FlutterDesktopEngineProperties& engine_properties) {
Expand All @@ -55,6 +64,16 @@ class StubFlutterWindowsApi {

// Called for FlutterDesktopViewGetHWND.
virtual HWND ViewGetHWND() { return reinterpret_cast<HWND>(1); }

// Called for FlutterDesktopPluginRegistrarRegisterTopLevelWindowProcDelegate.
virtual void PluginRegistrarRegisterTopLevelWindowProcDelegate(
FlutterDesktopWindowProcCallback delegate,
void* user_data) {}

// Called for
// FlutterDesktopPluginRegistrarUnregisterTopLevelWindowProcDelegate.
virtual void PluginRegistrarUnregisterTopLevelWindowProcDelegate(
FlutterDesktopWindowProcCallback delegate) {}
};

// A test helper that owns a stub implementation, making it the test stub for
Expand Down

0 comments on commit dcbe3d3

Please sign in to comment.