blob: 7e9f98811c994be4d74bddd001fdb178eaff2eca [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/shell/platform/windows/platform_handler.h"
#include <memory>
#include "flutter/fml/macros.h"
#include "flutter/shell/platform/common/json_method_codec.h"
#include "flutter/shell/platform/windows/flutter_windows_view.h"
#include "flutter/shell/platform/windows/testing/engine_modifier.h"
#include "flutter/shell/platform/windows/testing/flutter_windows_engine_builder.h"
#include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h"
#include "flutter/shell/platform/windows/testing/test_binary_messenger.h"
#include "flutter/shell/platform/windows/testing/windows_test.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "rapidjson/document.h"
namespace flutter {
namespace testing {
namespace {
using ::testing::_;
using ::testing::NiceMock;
using ::testing::Return;
static constexpr char kChannelName[] = "flutter/platform";
static constexpr char kClipboardGetDataMessage[] =
"{\"method\":\"Clipboard.getData\",\"args\":\"text/plain\"}";
static constexpr char kClipboardGetDataFakeContentTypeMessage[] =
"{\"method\":\"Clipboard.getData\",\"args\":\"text/madeupcontenttype\"}";
static constexpr char kClipboardHasStringsMessage[] =
"{\"method\":\"Clipboard.hasStrings\",\"args\":\"text/plain\"}";
static constexpr char kClipboardHasStringsFakeContentTypeMessage[] =
"{\"method\":\"Clipboard.hasStrings\",\"args\":\"text/madeupcontenttype\"}";
static constexpr char kClipboardSetDataMessage[] =
"{\"method\":\"Clipboard.setData\",\"args\":{\"text\":\"hello\"}}";
static constexpr char kClipboardSetDataNullTextMessage[] =
"{\"method\":\"Clipboard.setData\",\"args\":{\"text\":null}}";
static constexpr char kClipboardSetDataUnknownTypeMessage[] =
"{\"method\":\"Clipboard.setData\",\"args\":{\"madeuptype\":\"hello\"}}";
static constexpr char kSystemSoundTypeAlertMessage[] =
"{\"method\":\"SystemSound.play\",\"args\":\"SystemSoundType.alert\"}";
static constexpr char kSystemExitApplicationRequiredMessage[] =
"{\"method\":\"System.exitApplication\",\"args\":{\"type\":\"required\","
"\"exitCode\":1}}";
static constexpr char kSystemExitApplicationCancelableMessage[] =
"{\"method\":\"System.exitApplication\",\"args\":{\"type\":\"cancelable\","
"\"exitCode\":2}}";
static constexpr char kExitResponseCancelMessage[] =
"[{\"response\":\"cancel\"}]";
static constexpr char kExitResponseExitMessage[] = "[{\"response\":\"exit\"}]";
static constexpr int kAccessDeniedErrorCode = 5;
static constexpr int kErrorSuccess = 0;
static constexpr int kArbitraryErrorCode = 1;
// Test implementation of PlatformHandler to allow testing the PlatformHandler
// logic.
class MockPlatformHandler : public PlatformHandler {
public:
explicit MockPlatformHandler(
BinaryMessenger* messenger,
FlutterWindowsEngine* engine,
std::optional<std::function<std::unique_ptr<ScopedClipboardInterface>()>>
scoped_clipboard_provider = std::nullopt)
: PlatformHandler(messenger, engine, scoped_clipboard_provider) {}
virtual ~MockPlatformHandler() = default;
MOCK_METHOD(void,
GetPlainText,
(std::unique_ptr<MethodResult<rapidjson::Document>>,
std::string_view key),
(override));
MOCK_METHOD(void,
GetHasStrings,
(std::unique_ptr<MethodResult<rapidjson::Document>>),
(override));
MOCK_METHOD(void,
SetPlainText,
(const std::string&,
std::unique_ptr<MethodResult<rapidjson::Document>>),
(override));
MOCK_METHOD(void,
SystemSoundPlay,
(const std::string&,
std::unique_ptr<MethodResult<rapidjson::Document>>),
(override));
MOCK_METHOD(void,
QuitApplication,
(std::optional<HWND> hwnd,
std::optional<WPARAM> wparam,
std::optional<LPARAM> lparam,
UINT exit_code),
(override));
private:
FML_DISALLOW_COPY_AND_ASSIGN(MockPlatformHandler);
};
// A test version of the private ScopedClipboard.
class MockScopedClipboard : public ScopedClipboardInterface {
public:
MockScopedClipboard() = default;
virtual ~MockScopedClipboard() = default;
MOCK_METHOD(int, Open, (HWND window), (override));
MOCK_METHOD(bool, HasString, (), (override));
MOCK_METHOD((std::variant<std::wstring, int>), GetString, (), (override));
MOCK_METHOD(int, SetString, (const std::wstring string), (override));
private:
FML_DISALLOW_COPY_AND_ASSIGN(MockScopedClipboard);
};
std::string SimulatePlatformMessage(TestBinaryMessenger* messenger,
std::string message) {
std::string result;
EXPECT_TRUE(messenger->SimulateEngineMessage(
kChannelName, reinterpret_cast<const uint8_t*>(message.c_str()),
message.size(),
[result = &result](const uint8_t* reply, size_t reply_size) {
std::string response(reinterpret_cast<const char*>(reply), reply_size);
*result = response;
}));
return result;
}
} // namespace
class PlatformHandlerTest : public WindowsTest {
public:
PlatformHandlerTest() = default;
virtual ~PlatformHandlerTest() = default;
protected:
FlutterWindowsEngine* engine() { return engine_.get(); }
void UseHeadlessEngine() {
FlutterWindowsEngineBuilder builder{GetContext()};
engine_ = builder.Build();
}
void UseEngineWithView() {
FlutterWindowsEngineBuilder builder{GetContext()};
auto window = std::make_unique<NiceMock<MockWindowBindingHandler>>();
engine_ = builder.Build();
view_ = std::make_unique<FlutterWindowsView>(kImplicitViewId, engine_.get(),
std::move(window));
EngineModifier modifier{engine_.get()};
modifier.SetImplicitView(view_.get());
}
private:
std::unique_ptr<FlutterWindowsEngine> engine_;
std::unique_ptr<FlutterWindowsView> view_;
FML_DISALLOW_COPY_AND_ASSIGN(PlatformHandlerTest);
};
TEST_F(PlatformHandlerTest, GetClipboardData) {
UseEngineWithView();
TestBinaryMessenger messenger;
PlatformHandler platform_handler(&messenger, engine(), []() {
auto clipboard = std::make_unique<MockScopedClipboard>();
EXPECT_CALL(*clipboard.get(), Open)
.Times(1)
.WillOnce(Return(kErrorSuccess));
EXPECT_CALL(*clipboard.get(), HasString).Times(1).WillOnce(Return(true));
EXPECT_CALL(*clipboard.get(), GetString)
.Times(1)
.WillOnce(Return(std::wstring(L"Hello world")));
return clipboard;
});
std::string result =
SimulatePlatformMessage(&messenger, kClipboardGetDataMessage);
EXPECT_EQ(result, "[{\"text\":\"Hello world\"}]");
}
TEST_F(PlatformHandlerTest, GetClipboardDataRejectsUnknownContentType) {
UseEngineWithView();
TestBinaryMessenger messenger;
PlatformHandler platform_handler(&messenger, engine());
// Requesting an unknown content type is an error.
std::string result = SimulatePlatformMessage(
&messenger, kClipboardGetDataFakeContentTypeMessage);
EXPECT_EQ(result, "[\"Clipboard error\",\"Unknown clipboard format\",null]");
}
TEST_F(PlatformHandlerTest, GetClipboardDataRequiresView) {
UseHeadlessEngine();
TestBinaryMessenger messenger;
PlatformHandler platform_handler(&messenger, engine());
std::string result =
SimulatePlatformMessage(&messenger, kClipboardGetDataMessage);
EXPECT_EQ(result,
"[\"Clipboard error\",\"Clipboard is not available in "
"Windows headless mode\",null]");
}
TEST_F(PlatformHandlerTest, GetClipboardDataReportsOpenFailure) {
UseEngineWithView();
TestBinaryMessenger messenger;
PlatformHandler platform_handler(&messenger, engine(), []() {
auto clipboard = std::make_unique<MockScopedClipboard>();
EXPECT_CALL(*clipboard.get(), Open)
.Times(1)
.WillOnce(Return(kArbitraryErrorCode));
return clipboard;
});
std::string result =
SimulatePlatformMessage(&messenger, kClipboardGetDataMessage);
EXPECT_EQ(result, "[\"Clipboard error\",\"Unable to open clipboard\",1]");
}
TEST_F(PlatformHandlerTest, GetClipboardDataReportsGetDataFailure) {
UseEngineWithView();
TestBinaryMessenger messenger;
PlatformHandler platform_handler(&messenger, engine(), []() {
auto clipboard = std::make_unique<MockScopedClipboard>();
EXPECT_CALL(*clipboard.get(), Open)
.Times(1)
.WillOnce(Return(kErrorSuccess));
EXPECT_CALL(*clipboard.get(), HasString).Times(1).WillOnce(Return(true));
EXPECT_CALL(*clipboard.get(), GetString)
.Times(1)
.WillOnce(Return(kArbitraryErrorCode));
return clipboard;
});
std::string result =
SimulatePlatformMessage(&messenger, kClipboardGetDataMessage);
EXPECT_EQ(result, "[\"Clipboard error\",\"Unable to get clipboard data\",1]");
}
TEST_F(PlatformHandlerTest, ClipboardHasStrings) {
UseEngineWithView();
TestBinaryMessenger messenger;
PlatformHandler platform_handler(&messenger, engine(), []() {
auto clipboard = std::make_unique<MockScopedClipboard>();
EXPECT_CALL(*clipboard.get(), Open)
.Times(1)
.WillOnce(Return(kErrorSuccess));
EXPECT_CALL(*clipboard.get(), HasString).Times(1).WillOnce(Return(true));
return clipboard;
});
std::string result =
SimulatePlatformMessage(&messenger, kClipboardHasStringsMessage);
EXPECT_EQ(result, "[{\"value\":true}]");
}
TEST_F(PlatformHandlerTest, ClipboardHasStringsReturnsFalse) {
UseEngineWithView();
TestBinaryMessenger messenger;
PlatformHandler platform_handler(&messenger, engine(), []() {
auto clipboard = std::make_unique<MockScopedClipboard>();
EXPECT_CALL(*clipboard.get(), Open)
.Times(1)
.WillOnce(Return(kErrorSuccess));
EXPECT_CALL(*clipboard.get(), HasString).Times(1).WillOnce(Return(false));
return clipboard;
});
std::string result =
SimulatePlatformMessage(&messenger, kClipboardHasStringsMessage);
EXPECT_EQ(result, "[{\"value\":false}]");
}
TEST_F(PlatformHandlerTest, ClipboardHasStringsRejectsUnknownContentType) {
UseEngineWithView();
TestBinaryMessenger messenger;
PlatformHandler platform_handler(&messenger, engine());
std::string result = SimulatePlatformMessage(
&messenger, kClipboardHasStringsFakeContentTypeMessage);
EXPECT_EQ(result, "[\"Clipboard error\",\"Unknown clipboard format\",null]");
}
TEST_F(PlatformHandlerTest, ClipboardHasStringsRequiresView) {
UseHeadlessEngine();
TestBinaryMessenger messenger;
PlatformHandler platform_handler(&messenger, engine());
std::string result =
SimulatePlatformMessage(&messenger, kClipboardHasStringsMessage);
EXPECT_EQ(result,
"[\"Clipboard error\",\"Clipboard is not available in Windows "
"headless mode\",null]");
}
// Regression test for https://github.com/flutter/flutter/issues/95817.
TEST_F(PlatformHandlerTest, ClipboardHasStringsIgnoresPermissionErrors) {
UseEngineWithView();
TestBinaryMessenger messenger;
PlatformHandler platform_handler(&messenger, engine(), []() {
auto clipboard = std::make_unique<MockScopedClipboard>();
EXPECT_CALL(*clipboard.get(), Open)
.Times(1)
.WillOnce(Return(kAccessDeniedErrorCode));
return clipboard;
});
std::string result =
SimulatePlatformMessage(&messenger, kClipboardHasStringsMessage);
EXPECT_EQ(result, "[{\"value\":false}]");
}
TEST_F(PlatformHandlerTest, ClipboardHasStringsReportsErrors) {
UseEngineWithView();
TestBinaryMessenger messenger;
PlatformHandler platform_handler(&messenger, engine(), []() {
auto clipboard = std::make_unique<MockScopedClipboard>();
EXPECT_CALL(*clipboard.get(), Open)
.Times(1)
.WillOnce(Return(kArbitraryErrorCode));
return clipboard;
});
std::string result =
SimulatePlatformMessage(&messenger, kClipboardHasStringsMessage);
EXPECT_EQ(result, "[\"Clipboard error\",\"Unable to open clipboard\",1]");
}
TEST_F(PlatformHandlerTest, ClipboardSetData) {
UseEngineWithView();
TestBinaryMessenger messenger;
PlatformHandler platform_handler(&messenger, engine(), []() {
auto clipboard = std::make_unique<MockScopedClipboard>();
EXPECT_CALL(*clipboard.get(), Open)
.Times(1)
.WillOnce(Return(kErrorSuccess));
EXPECT_CALL(*clipboard.get(), SetString)
.Times(1)
.WillOnce([](std::wstring string) {
EXPECT_EQ(string, L"hello");
return kErrorSuccess;
});
return clipboard;
});
std::string result =
SimulatePlatformMessage(&messenger, kClipboardSetDataMessage);
EXPECT_EQ(result, "[null]");
}
// Regression test for: https://github.com/flutter/flutter/issues/121976
TEST_F(PlatformHandlerTest, ClipboardSetDataTextMustBeString) {
UseEngineWithView();
TestBinaryMessenger messenger;
PlatformHandler platform_handler(&messenger, engine());
std::string result =
SimulatePlatformMessage(&messenger, kClipboardSetDataNullTextMessage);
EXPECT_EQ(result, "[\"Clipboard error\",\"Unknown clipboard format\",null]");
}
TEST_F(PlatformHandlerTest, ClipboardSetDataUnknownType) {
UseEngineWithView();
TestBinaryMessenger messenger;
PlatformHandler platform_handler(&messenger, engine());
std::string result =
SimulatePlatformMessage(&messenger, kClipboardSetDataUnknownTypeMessage);
EXPECT_EQ(result, "[\"Clipboard error\",\"Unknown clipboard format\",null]");
}
TEST_F(PlatformHandlerTest, ClipboardSetDataRequiresView) {
UseHeadlessEngine();
TestBinaryMessenger messenger;
PlatformHandler platform_handler(&messenger, engine());
std::string result =
SimulatePlatformMessage(&messenger, kClipboardSetDataMessage);
EXPECT_EQ(result,
"[\"Clipboard error\",\"Clipboard is not available in Windows "
"headless mode\",null]");
}
TEST_F(PlatformHandlerTest, ClipboardSetDataReportsOpenFailure) {
UseEngineWithView();
TestBinaryMessenger messenger;
PlatformHandler platform_handler(&messenger, engine(), []() {
auto clipboard = std::make_unique<MockScopedClipboard>();
EXPECT_CALL(*clipboard.get(), Open)
.Times(1)
.WillOnce(Return(kArbitraryErrorCode));
return clipboard;
});
std::string result =
SimulatePlatformMessage(&messenger, kClipboardSetDataMessage);
EXPECT_EQ(result, "[\"Clipboard error\",\"Unable to open clipboard\",1]");
}
TEST_F(PlatformHandlerTest, ClipboardSetDataReportsSetDataFailure) {
UseEngineWithView();
TestBinaryMessenger messenger;
PlatformHandler platform_handler(&messenger, engine(), []() {
auto clipboard = std::make_unique<MockScopedClipboard>();
EXPECT_CALL(*clipboard.get(), Open)
.Times(1)
.WillOnce(Return(kErrorSuccess));
EXPECT_CALL(*clipboard.get(), SetString)
.Times(1)
.WillOnce(Return(kArbitraryErrorCode));
return clipboard;
});
std::string result =
SimulatePlatformMessage(&messenger, kClipboardSetDataMessage);
EXPECT_EQ(result, "[\"Clipboard error\",\"Unable to set clipboard data\",1]");
}
TEST_F(PlatformHandlerTest, PlaySystemSound) {
UseHeadlessEngine();
TestBinaryMessenger messenger;
MockPlatformHandler platform_handler(&messenger, engine());
EXPECT_CALL(platform_handler, SystemSoundPlay("SystemSoundType.alert", _))
.WillOnce([](const std::string& sound,
std::unique_ptr<MethodResult<rapidjson::Document>> result) {
result->Success();
});
std::string result =
SimulatePlatformMessage(&messenger, kSystemSoundTypeAlertMessage);
EXPECT_EQ(result, "[null]");
}
TEST_F(PlatformHandlerTest, SystemExitApplicationRequired) {
UseHeadlessEngine();
UINT exit_code = 0;
TestBinaryMessenger messenger([](const std::string& channel,
const uint8_t* message, size_t size,
BinaryReply reply) {});
MockPlatformHandler platform_handler(&messenger, engine());
ON_CALL(platform_handler, QuitApplication)
.WillByDefault([&exit_code](std::optional<HWND> hwnd,
std::optional<WPARAM> wparam,
std::optional<LPARAM> lparam,
UINT ec) { exit_code = ec; });
EXPECT_CALL(platform_handler, QuitApplication).Times(1);
std::string result = SimulatePlatformMessage(
&messenger, kSystemExitApplicationRequiredMessage);
EXPECT_EQ(result, "[{\"response\":\"exit\"}]");
EXPECT_EQ(exit_code, 1);
}
TEST_F(PlatformHandlerTest, SystemExitApplicationCancelableCancel) {
UseHeadlessEngine();
bool called_cancel = false;
TestBinaryMessenger messenger(
[&called_cancel](const std::string& channel, const uint8_t* message,
size_t size, BinaryReply reply) {
reply(reinterpret_cast<const uint8_t*>(kExitResponseCancelMessage),
sizeof(kExitResponseCancelMessage));
called_cancel = true;
});
MockPlatformHandler platform_handler(&messenger, engine());
EXPECT_CALL(platform_handler, QuitApplication).Times(0);
std::string result = SimulatePlatformMessage(
&messenger, kSystemExitApplicationCancelableMessage);
EXPECT_EQ(result, "[{\"response\":\"cancel\"}]");
EXPECT_TRUE(called_cancel);
}
TEST_F(PlatformHandlerTest, SystemExitApplicationCancelableExit) {
UseHeadlessEngine();
bool called_cancel = false;
UINT exit_code = 0;
TestBinaryMessenger messenger(
[&called_cancel](const std::string& channel, const uint8_t* message,
size_t size, BinaryReply reply) {
reply(reinterpret_cast<const uint8_t*>(kExitResponseExitMessage),
sizeof(kExitResponseExitMessage));
called_cancel = true;
});
MockPlatformHandler platform_handler(&messenger, engine());
ON_CALL(platform_handler, QuitApplication)
.WillByDefault([&exit_code](std::optional<HWND> hwnd,
std::optional<WPARAM> wparam,
std::optional<LPARAM> lparam,
UINT ec) { exit_code = ec; });
EXPECT_CALL(platform_handler, QuitApplication).Times(1);
std::string result = SimulatePlatformMessage(
&messenger, kSystemExitApplicationCancelableMessage);
EXPECT_EQ(result, "[{\"response\":\"cancel\"}]");
EXPECT_TRUE(called_cancel);
EXPECT_EQ(exit_code, 2);
}
} // namespace testing
} // namespace flutter