mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[Windows] Begin decoupling text input plugin from the view (flutter/engine#47833)
Currently the text input plugin is strongly tied to a single view. This change makes the text input plugin tied to the engine in preparation for multi-view world. Part of https://github.com/flutter/flutter/issues/115611 [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
This commit is contained in:
parent
0b04bedb47
commit
f5cacea14a
@ -7166,7 +7166,6 @@ ORIGIN: ../../../flutter/shell/platform/windows/text_input_manager.cc + ../../..
|
||||
ORIGIN: ../../../flutter/shell/platform/windows/text_input_manager.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/windows/text_input_plugin.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/windows/text_input_plugin.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/windows/text_input_plugin_delegate.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/windows/window_binding_handler.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/windows/window_binding_handler_delegate.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/windows/window_proc_delegate_manager.cc + ../../../flutter/LICENSE
|
||||
@ -9990,7 +9989,6 @@ FILE: ../../../flutter/shell/platform/windows/text_input_manager.cc
|
||||
FILE: ../../../flutter/shell/platform/windows/text_input_manager.h
|
||||
FILE: ../../../flutter/shell/platform/windows/text_input_plugin.cc
|
||||
FILE: ../../../flutter/shell/platform/windows/text_input_plugin.h
|
||||
FILE: ../../../flutter/shell/platform/windows/text_input_plugin_delegate.h
|
||||
FILE: ../../../flutter/shell/platform/windows/window_binding_handler.h
|
||||
FILE: ../../../flutter/shell/platform/windows/window_binding_handler_delegate.h
|
||||
FILE: ../../../flutter/shell/platform/windows/window_proc_delegate_manager.cc
|
||||
|
||||
@ -63,13 +63,13 @@ class CursorHandlerTest : public WindowsTest {
|
||||
FlutterWindowsView* view() { return view_.get(); }
|
||||
MockWindowBindingHandler* window() { return window_; }
|
||||
|
||||
void use_headless_engine() {
|
||||
void UseHeadlessEngine() {
|
||||
FlutterWindowsEngineBuilder builder{GetContext()};
|
||||
|
||||
engine_ = builder.Build();
|
||||
}
|
||||
|
||||
void use_engine_with_view() {
|
||||
void UseEngineWithView() {
|
||||
FlutterWindowsEngineBuilder builder{GetContext()};
|
||||
|
||||
auto window = std::make_unique<MockWindowBindingHandler>();
|
||||
@ -93,7 +93,7 @@ class CursorHandlerTest : public WindowsTest {
|
||||
};
|
||||
|
||||
TEST_F(CursorHandlerTest, ActivateSystemCursor) {
|
||||
use_engine_with_view();
|
||||
UseEngineWithView();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
CursorHandler cursor_handler(&messenger, engine());
|
||||
@ -119,7 +119,7 @@ TEST_F(CursorHandlerTest, ActivateSystemCursor) {
|
||||
}
|
||||
|
||||
TEST_F(CursorHandlerTest, ActivateSystemCursorRequiresView) {
|
||||
use_headless_engine();
|
||||
UseHeadlessEngine();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
CursorHandler cursor_handler(&messenger, engine());
|
||||
@ -146,7 +146,7 @@ TEST_F(CursorHandlerTest, ActivateSystemCursorRequiresView) {
|
||||
}
|
||||
|
||||
TEST_F(CursorHandlerTest, CreateCustomCursor) {
|
||||
use_engine_with_view();
|
||||
UseEngineWithView();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
CursorHandler cursor_handler(&messenger, engine());
|
||||
@ -177,7 +177,7 @@ TEST_F(CursorHandlerTest, CreateCustomCursor) {
|
||||
}
|
||||
|
||||
TEST_F(CursorHandlerTest, SetCustomCursor) {
|
||||
use_engine_with_view();
|
||||
UseEngineWithView();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
CursorHandler cursor_handler(&messenger, engine());
|
||||
@ -217,7 +217,7 @@ TEST_F(CursorHandlerTest, SetCustomCursor) {
|
||||
}
|
||||
|
||||
TEST_F(CursorHandlerTest, SetCustomCursorRequiresView) {
|
||||
use_headless_engine();
|
||||
UseHeadlessEngine();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
CursorHandler cursor_handler(&messenger, engine());
|
||||
@ -258,7 +258,7 @@ TEST_F(CursorHandlerTest, SetCustomCursorRequiresView) {
|
||||
}
|
||||
|
||||
TEST_F(CursorHandlerTest, SetNonexistentCustomCursor) {
|
||||
use_engine_with_view();
|
||||
UseEngineWithView();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
CursorHandler cursor_handler(&messenger, engine());
|
||||
@ -287,7 +287,7 @@ TEST_F(CursorHandlerTest, SetNonexistentCustomCursor) {
|
||||
}
|
||||
|
||||
TEST_F(CursorHandlerTest, DeleteCustomCursor) {
|
||||
use_engine_with_view();
|
||||
UseEngineWithView();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
CursorHandler cursor_handler(&messenger, engine());
|
||||
@ -325,7 +325,7 @@ TEST_F(CursorHandlerTest, DeleteCustomCursor) {
|
||||
}
|
||||
|
||||
TEST_F(CursorHandlerTest, DeleteNonexistentCustomCursor) {
|
||||
use_engine_with_view();
|
||||
UseEngineWithView();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
CursorHandler cursor_handler(&messenger, engine());
|
||||
|
||||
@ -666,7 +666,7 @@ FlutterWindowsEngine::CreateKeyboardKeyHandler(
|
||||
|
||||
std::unique_ptr<TextInputPlugin> FlutterWindowsEngine::CreateTextInputPlugin(
|
||||
BinaryMessenger* messenger) {
|
||||
return std::make_unique<TextInputPlugin>(messenger, view_);
|
||||
return std::make_unique<TextInputPlugin>(messenger, this);
|
||||
}
|
||||
|
||||
bool FlutterWindowsEngine::RegisterExternalTexture(int64_t texture_id) {
|
||||
|
||||
@ -20,7 +20,6 @@
|
||||
#include "flutter/shell/platform/windows/angle_surface_manager.h"
|
||||
#include "flutter/shell/platform/windows/flutter_windows_engine.h"
|
||||
#include "flutter/shell/platform/windows/public/flutter_windows.h"
|
||||
#include "flutter/shell/platform/windows/text_input_plugin_delegate.h"
|
||||
#include "flutter/shell/platform/windows/window_binding_handler.h"
|
||||
#include "flutter/shell/platform/windows/window_binding_handler_delegate.h"
|
||||
#include "flutter/shell/platform/windows/window_state.h"
|
||||
@ -30,10 +29,9 @@ namespace flutter {
|
||||
// ID for the window frame buffer.
|
||||
inline constexpr uint32_t kWindowFrameBufferID = 0;
|
||||
|
||||
// An OS-windowing neutral abstration for flutter
|
||||
// view that works with win32 hwnds and Windows::UI::Composition visuals.
|
||||
class FlutterWindowsView : public WindowBindingHandlerDelegate,
|
||||
public TextInputPluginDelegate {
|
||||
// An OS-windowing neutral abstration for a Flutter view that works
|
||||
// with win32 HWNDs.
|
||||
class FlutterWindowsView : public WindowBindingHandlerDelegate {
|
||||
public:
|
||||
// Creates a FlutterWindowsView with the given implementor of
|
||||
// WindowBindingHandler.
|
||||
@ -186,11 +184,12 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate,
|
||||
// |WindowBindingHandlerDelegate|
|
||||
virtual gfx::NativeViewAccessible GetNativeViewAccessible() override;
|
||||
|
||||
// |TextInputPluginDelegate|
|
||||
void OnCursorRectUpdated(const Rect& rect) override;
|
||||
// Notifies the delegate of the updated the cursor rect in Flutter root view
|
||||
// coordinates.
|
||||
virtual void OnCursorRectUpdated(const Rect& rect);
|
||||
|
||||
// |TextInputPluginDelegate|
|
||||
void OnResetImeComposing() override;
|
||||
// Notifies the delegate that the system IME composing state should be reset.
|
||||
virtual void OnResetImeComposing();
|
||||
|
||||
// Called when a WM_ONCOMPOSITIONCHANGED message is received.
|
||||
void OnDwmCompositionChanged();
|
||||
|
||||
@ -142,13 +142,13 @@ class PlatformHandlerTest : public WindowsTest {
|
||||
protected:
|
||||
FlutterWindowsEngine* engine() { return engine_.get(); }
|
||||
|
||||
void use_headless_engine() {
|
||||
void UseHeadlessEngine() {
|
||||
FlutterWindowsEngineBuilder builder{GetContext()};
|
||||
|
||||
engine_ = builder.Build();
|
||||
}
|
||||
|
||||
void use_engine_with_view() {
|
||||
void UseEngineWithView() {
|
||||
FlutterWindowsEngineBuilder builder{GetContext()};
|
||||
|
||||
auto window = std::make_unique<NiceMock<MockWindowBindingHandler>>();
|
||||
@ -166,7 +166,7 @@ class PlatformHandlerTest : public WindowsTest {
|
||||
};
|
||||
|
||||
TEST_F(PlatformHandlerTest, GetClipboardData) {
|
||||
use_engine_with_view();
|
||||
UseEngineWithView();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
PlatformHandler platform_handler(&messenger, engine(), []() {
|
||||
@ -190,7 +190,7 @@ TEST_F(PlatformHandlerTest, GetClipboardData) {
|
||||
}
|
||||
|
||||
TEST_F(PlatformHandlerTest, GetClipboardDataRejectsUnknownContentType) {
|
||||
use_engine_with_view();
|
||||
UseEngineWithView();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
PlatformHandler platform_handler(&messenger, engine());
|
||||
@ -203,7 +203,7 @@ TEST_F(PlatformHandlerTest, GetClipboardDataRejectsUnknownContentType) {
|
||||
}
|
||||
|
||||
TEST_F(PlatformHandlerTest, GetClipboardDataRequiresView) {
|
||||
use_headless_engine();
|
||||
UseHeadlessEngine();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
PlatformHandler platform_handler(&messenger, engine());
|
||||
@ -217,7 +217,7 @@ TEST_F(PlatformHandlerTest, GetClipboardDataRequiresView) {
|
||||
}
|
||||
|
||||
TEST_F(PlatformHandlerTest, GetClipboardDataReportsOpenFailure) {
|
||||
use_engine_with_view();
|
||||
UseEngineWithView();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
PlatformHandler platform_handler(&messenger, engine(), []() {
|
||||
@ -237,7 +237,7 @@ TEST_F(PlatformHandlerTest, GetClipboardDataReportsOpenFailure) {
|
||||
}
|
||||
|
||||
TEST_F(PlatformHandlerTest, GetClipboardDataReportsGetDataFailure) {
|
||||
use_engine_with_view();
|
||||
UseEngineWithView();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
PlatformHandler platform_handler(&messenger, engine(), []() {
|
||||
@ -261,7 +261,7 @@ TEST_F(PlatformHandlerTest, GetClipboardDataReportsGetDataFailure) {
|
||||
}
|
||||
|
||||
TEST_F(PlatformHandlerTest, ClipboardHasStrings) {
|
||||
use_engine_with_view();
|
||||
UseEngineWithView();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
PlatformHandler platform_handler(&messenger, engine(), []() {
|
||||
@ -282,7 +282,7 @@ TEST_F(PlatformHandlerTest, ClipboardHasStrings) {
|
||||
}
|
||||
|
||||
TEST_F(PlatformHandlerTest, ClipboardHasStringsReturnsFalse) {
|
||||
use_engine_with_view();
|
||||
UseEngineWithView();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
PlatformHandler platform_handler(&messenger, engine(), []() {
|
||||
@ -303,7 +303,7 @@ TEST_F(PlatformHandlerTest, ClipboardHasStringsReturnsFalse) {
|
||||
}
|
||||
|
||||
TEST_F(PlatformHandlerTest, ClipboardHasStringsRejectsUnknownContentType) {
|
||||
use_engine_with_view();
|
||||
UseEngineWithView();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
PlatformHandler platform_handler(&messenger, engine());
|
||||
@ -315,7 +315,7 @@ TEST_F(PlatformHandlerTest, ClipboardHasStringsRejectsUnknownContentType) {
|
||||
}
|
||||
|
||||
TEST_F(PlatformHandlerTest, ClipboardHasStringsRequiresView) {
|
||||
use_headless_engine();
|
||||
UseHeadlessEngine();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
PlatformHandler platform_handler(&messenger, engine());
|
||||
@ -330,7 +330,7 @@ TEST_F(PlatformHandlerTest, ClipboardHasStringsRequiresView) {
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/95817.
|
||||
TEST_F(PlatformHandlerTest, ClipboardHasStringsIgnoresPermissionErrors) {
|
||||
use_engine_with_view();
|
||||
UseEngineWithView();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
PlatformHandler platform_handler(&messenger, engine(), []() {
|
||||
@ -350,7 +350,7 @@ TEST_F(PlatformHandlerTest, ClipboardHasStringsIgnoresPermissionErrors) {
|
||||
}
|
||||
|
||||
TEST_F(PlatformHandlerTest, ClipboardHasStringsReportsErrors) {
|
||||
use_engine_with_view();
|
||||
UseEngineWithView();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
PlatformHandler platform_handler(&messenger, engine(), []() {
|
||||
@ -370,7 +370,7 @@ TEST_F(PlatformHandlerTest, ClipboardHasStringsReportsErrors) {
|
||||
}
|
||||
|
||||
TEST_F(PlatformHandlerTest, ClipboardSetData) {
|
||||
use_engine_with_view();
|
||||
UseEngineWithView();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
PlatformHandler platform_handler(&messenger, engine(), []() {
|
||||
@ -397,7 +397,7 @@ TEST_F(PlatformHandlerTest, ClipboardSetData) {
|
||||
|
||||
// Regression test for: https://github.com/flutter/flutter/issues/121976
|
||||
TEST_F(PlatformHandlerTest, ClipboardSetDataTextMustBeString) {
|
||||
use_engine_with_view();
|
||||
UseEngineWithView();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
PlatformHandler platform_handler(&messenger, engine());
|
||||
@ -409,7 +409,7 @@ TEST_F(PlatformHandlerTest, ClipboardSetDataTextMustBeString) {
|
||||
}
|
||||
|
||||
TEST_F(PlatformHandlerTest, ClipboardSetDataUnknownType) {
|
||||
use_engine_with_view();
|
||||
UseEngineWithView();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
PlatformHandler platform_handler(&messenger, engine());
|
||||
@ -421,7 +421,7 @@ TEST_F(PlatformHandlerTest, ClipboardSetDataUnknownType) {
|
||||
}
|
||||
|
||||
TEST_F(PlatformHandlerTest, ClipboardSetDataRequiresView) {
|
||||
use_headless_engine();
|
||||
UseHeadlessEngine();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
PlatformHandler platform_handler(&messenger, engine());
|
||||
@ -435,7 +435,7 @@ TEST_F(PlatformHandlerTest, ClipboardSetDataRequiresView) {
|
||||
}
|
||||
|
||||
TEST_F(PlatformHandlerTest, ClipboardSetDataReportsOpenFailure) {
|
||||
use_engine_with_view();
|
||||
UseEngineWithView();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
PlatformHandler platform_handler(&messenger, engine(), []() {
|
||||
@ -455,7 +455,7 @@ TEST_F(PlatformHandlerTest, ClipboardSetDataReportsOpenFailure) {
|
||||
}
|
||||
|
||||
TEST_F(PlatformHandlerTest, ClipboardSetDataReportsSetDataFailure) {
|
||||
use_engine_with_view();
|
||||
UseEngineWithView();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
PlatformHandler platform_handler(&messenger, engine(), []() {
|
||||
@ -478,7 +478,7 @@ TEST_F(PlatformHandlerTest, ClipboardSetDataReportsSetDataFailure) {
|
||||
}
|
||||
|
||||
TEST_F(PlatformHandlerTest, PlaySystemSound) {
|
||||
use_headless_engine();
|
||||
UseHeadlessEngine();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
MockPlatformHandler platform_handler(&messenger, engine());
|
||||
@ -496,7 +496,7 @@ TEST_F(PlatformHandlerTest, PlaySystemSound) {
|
||||
}
|
||||
|
||||
TEST_F(PlatformHandlerTest, SystemExitApplicationRequired) {
|
||||
use_headless_engine();
|
||||
UseHeadlessEngine();
|
||||
UINT exit_code = 0;
|
||||
|
||||
TestBinaryMessenger messenger([](const std::string& channel,
|
||||
@ -518,7 +518,7 @@ TEST_F(PlatformHandlerTest, SystemExitApplicationRequired) {
|
||||
}
|
||||
|
||||
TEST_F(PlatformHandlerTest, SystemExitApplicationCancelableCancel) {
|
||||
use_headless_engine();
|
||||
UseHeadlessEngine();
|
||||
bool called_cancel = false;
|
||||
|
||||
TestBinaryMessenger messenger(
|
||||
@ -539,7 +539,7 @@ TEST_F(PlatformHandlerTest, SystemExitApplicationCancelableCancel) {
|
||||
}
|
||||
|
||||
TEST_F(PlatformHandlerTest, SystemExitApplicationCancelableExit) {
|
||||
use_headless_engine();
|
||||
UseHeadlessEngine();
|
||||
bool called_cancel = false;
|
||||
UINT exit_code = 0;
|
||||
|
||||
|
||||
@ -3,15 +3,16 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "flutter/shell/platform/windows/text_input_plugin.h"
|
||||
#include "flutter/fml/string_conversion.h"
|
||||
#include "flutter/shell/platform/common/text_editing_delta.h"
|
||||
#include "flutter/shell/platform/windows/text_input_plugin_delegate.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "flutter/fml/string_conversion.h"
|
||||
#include "flutter/shell/platform/common/json_method_codec.h"
|
||||
#include "flutter/shell/platform/common/text_editing_delta.h"
|
||||
#include "flutter/shell/platform/windows/flutter_windows_engine.h"
|
||||
#include "flutter/shell/platform/windows/flutter_windows_view.h"
|
||||
|
||||
static constexpr char kSetEditingStateMethod[] = "TextInput.setEditingState";
|
||||
static constexpr char kClearClientMethod[] = "TextInput.clearClient";
|
||||
@ -104,12 +105,12 @@ void TextInputPlugin::KeyboardHook(int key,
|
||||
}
|
||||
|
||||
TextInputPlugin::TextInputPlugin(flutter::BinaryMessenger* messenger,
|
||||
TextInputPluginDelegate* delegate)
|
||||
FlutterWindowsEngine* engine)
|
||||
: channel_(std::make_unique<flutter::MethodChannel<rapidjson::Document>>(
|
||||
messenger,
|
||||
kChannelName,
|
||||
&flutter::JsonMethodCodec::GetInstance())),
|
||||
delegate_(delegate),
|
||||
engine_(engine),
|
||||
active_model_(nullptr) {
|
||||
channel_->SetMethodCallHandler(
|
||||
[this](
|
||||
@ -217,12 +218,18 @@ void TextInputPlugin::HandleMethodCall(
|
||||
if (method.compare(kShowMethod) == 0 || method.compare(kHideMethod) == 0) {
|
||||
// These methods are no-ops.
|
||||
} else if (method.compare(kClearClientMethod) == 0) {
|
||||
FlutterWindowsView* view = engine_->view();
|
||||
if (view == nullptr) {
|
||||
result->Error(kInternalConsistencyError,
|
||||
"Text input is not available in Windows headless mode");
|
||||
return;
|
||||
}
|
||||
if (active_model_ != nullptr && active_model_->composing()) {
|
||||
active_model_->CommitComposing();
|
||||
active_model_->EndComposing();
|
||||
SendStateUpdate(*active_model_);
|
||||
}
|
||||
delegate_->OnResetImeComposing();
|
||||
view->OnResetImeComposing();
|
||||
active_model_ = nullptr;
|
||||
} else if (method.compare(kSetClientMethod) == 0) {
|
||||
if (!method_call.arguments() || method_call.arguments()->IsNull()) {
|
||||
@ -321,6 +328,12 @@ void TextInputPlugin::HandleMethodCall(
|
||||
TextRange(composing_base, composing_extent), cursor_offset);
|
||||
}
|
||||
} else if (method.compare(kSetMarkedTextRect) == 0) {
|
||||
FlutterWindowsView* view = engine_->view();
|
||||
if (view == nullptr) {
|
||||
result->Error(kInternalConsistencyError,
|
||||
"Text input is not available in Windows headless mode");
|
||||
return;
|
||||
}
|
||||
if (!method_call.arguments() || method_call.arguments()->IsNull()) {
|
||||
result->Error(kBadArgumentError, "Method invoked without args");
|
||||
return;
|
||||
@ -342,8 +355,14 @@ void TextInputPlugin::HandleMethodCall(
|
||||
{width->value.GetDouble(), height->value.GetDouble()}};
|
||||
|
||||
Rect transformed_rect = GetCursorRect();
|
||||
delegate_->OnCursorRectUpdated(transformed_rect);
|
||||
view->OnCursorRectUpdated(transformed_rect);
|
||||
} else if (method.compare(kSetEditableSizeAndTransform) == 0) {
|
||||
FlutterWindowsView* view = engine_->view();
|
||||
if (view == nullptr) {
|
||||
result->Error(kInternalConsistencyError,
|
||||
"Text input is not available in Windows headless mode");
|
||||
return;
|
||||
}
|
||||
if (!method_call.arguments() || method_call.arguments()->IsNull()) {
|
||||
result->Error(kBadArgumentError, "Method invoked without args");
|
||||
return;
|
||||
@ -367,7 +386,7 @@ void TextInputPlugin::HandleMethodCall(
|
||||
++i;
|
||||
}
|
||||
Rect transformed_rect = GetCursorRect();
|
||||
delegate_->OnCursorRectUpdated(transformed_rect);
|
||||
view->OnCursorRectUpdated(transformed_rect);
|
||||
} else {
|
||||
result->NotImplemented();
|
||||
return;
|
||||
|
||||
@ -20,18 +20,19 @@
|
||||
|
||||
namespace flutter {
|
||||
|
||||
class TextInputPluginDelegate;
|
||||
class FlutterWindowsEngine;
|
||||
|
||||
// Implements a text input plugin.
|
||||
//
|
||||
// Specifically handles window events within windows.
|
||||
class TextInputPlugin {
|
||||
public:
|
||||
explicit TextInputPlugin(flutter::BinaryMessenger* messenger,
|
||||
TextInputPluginDelegate* delegate);
|
||||
TextInputPlugin(flutter::BinaryMessenger* messenger,
|
||||
FlutterWindowsEngine* engine);
|
||||
|
||||
virtual ~TextInputPlugin();
|
||||
|
||||
// Called when the Flutter engine receives a raw keyboard message.
|
||||
virtual void KeyboardHook(int key,
|
||||
int scancode,
|
||||
int action,
|
||||
@ -39,14 +40,33 @@ class TextInputPlugin {
|
||||
bool extended,
|
||||
bool was_down);
|
||||
|
||||
// Called when the Flutter engine receives a keyboard character.
|
||||
virtual void TextHook(const std::u16string& text);
|
||||
|
||||
// Called on an IME compose begin event.
|
||||
//
|
||||
// Triggered when the user begins editing composing text using a multi-step
|
||||
// input method such as in CJK text input.
|
||||
virtual void ComposeBeginHook();
|
||||
|
||||
// Called on an IME compose commit event.
|
||||
//
|
||||
// Triggered when the user triggers a commit of the current composing text
|
||||
// while using a multi-step input method such as in CJK text input. Composing
|
||||
// continues with the next keypress.
|
||||
virtual void ComposeCommitHook();
|
||||
|
||||
// Called on an IME compose end event.
|
||||
//
|
||||
// Triggered when the composing ends, for example when the user presses
|
||||
// ESC or when the user triggers a commit of the composing text while using a
|
||||
// multi-step input method such as in CJK text input.
|
||||
virtual void ComposeEndHook();
|
||||
|
||||
// Called on an IME composing region change event.
|
||||
//
|
||||
// Triggered when the user edits the composing text while using a multi-step
|
||||
// input method such as in CJK text input.
|
||||
virtual void ComposeChangeHook(const std::u16string& text, int cursor_pos);
|
||||
|
||||
private:
|
||||
@ -72,8 +92,8 @@ class TextInputPlugin {
|
||||
// The MethodChannel used for communication with the Flutter engine.
|
||||
std::unique_ptr<flutter::MethodChannel<rapidjson::Document>> channel_;
|
||||
|
||||
// The associated |TextInputPluginDelegate|.
|
||||
TextInputPluginDelegate* delegate_;
|
||||
// The associated |FlutterWindowsEngine|.
|
||||
FlutterWindowsEngine* engine_;
|
||||
|
||||
// The active client id.
|
||||
int client_id_;
|
||||
@ -85,7 +105,7 @@ class TextInputPlugin {
|
||||
// as TextEditingDeltas or as one TextEditingValue.
|
||||
// For more information on the delta model, see:
|
||||
// https://master-api.flutter.dev/flutter/services/TextInputConfiguration/enableDeltaModel.html
|
||||
bool enable_delta_model;
|
||||
bool enable_delta_model = false;
|
||||
|
||||
// Keyboard type of the client. See available options:
|
||||
// https://api.flutter.dev/flutter/services/TextInputType-class.html
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_TEXT_INPUT_PLUGIN_DELEGATE_H_
|
||||
#define FLUTTER_SHELL_PLATFORM_WINDOWS_TEXT_INPUT_PLUGIN_DELEGATE_H_
|
||||
|
||||
#include "flutter/shell/platform/common/geometry.h"
|
||||
#include "flutter/shell/platform/embedder/embedder.h"
|
||||
|
||||
namespace flutter {
|
||||
|
||||
class TextInputPluginDelegate {
|
||||
public:
|
||||
// Notifies the delegate of the updated the cursor rect in Flutter root view
|
||||
// coordinates.
|
||||
virtual void OnCursorRectUpdated(const Rect& rect) = 0;
|
||||
|
||||
// Notifies the delegate that the system IME composing state should be reset.
|
||||
virtual void OnResetImeComposing() = 0;
|
||||
};
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_TEXT_INPUT_PLUGIN_DELEGATE_H_
|
||||
@ -10,8 +10,11 @@
|
||||
#include "flutter/fml/macros.h"
|
||||
#include "flutter/shell/platform/common/json_message_codec.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/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/text_input_plugin_delegate.h"
|
||||
#include "flutter/shell/platform/windows/testing/windows_test.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
@ -19,6 +22,8 @@ namespace flutter {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
using ::testing::Return;
|
||||
|
||||
static constexpr char kScanCodeKey[] = "scanCode";
|
||||
static constexpr int kHandledScanCode = 20;
|
||||
static constexpr int kUnhandledScanCode = 21;
|
||||
@ -97,21 +102,63 @@ static std::unique_ptr<rapidjson::Document> EncodedEditingState(
|
||||
return arguments;
|
||||
}
|
||||
|
||||
class MockTextInputPluginDelegate : public TextInputPluginDelegate {
|
||||
class MockFlutterWindowsView : public FlutterWindowsView {
|
||||
public:
|
||||
MockTextInputPluginDelegate() {}
|
||||
virtual ~MockTextInputPluginDelegate() = default;
|
||||
MockFlutterWindowsView(std::unique_ptr<WindowBindingHandler> window)
|
||||
: FlutterWindowsView(std::move(window)) {}
|
||||
virtual ~MockFlutterWindowsView() = default;
|
||||
|
||||
MOCK_METHOD(void, OnCursorRectUpdated, (const Rect&), (override));
|
||||
MOCK_METHOD(void, OnResetImeComposing, (), (override));
|
||||
|
||||
private:
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(MockTextInputPluginDelegate);
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(MockFlutterWindowsView);
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(TextInputPluginTest, TextMethodsWorksWithEmptyModel) {
|
||||
class TextInputPluginTest : public WindowsTest {
|
||||
public:
|
||||
TextInputPluginTest() = default;
|
||||
virtual ~TextInputPluginTest() = default;
|
||||
|
||||
protected:
|
||||
FlutterWindowsEngine* engine() { return engine_.get(); }
|
||||
MockFlutterWindowsView* view() { return view_.get(); }
|
||||
MockWindowBindingHandler* window() { return window_; }
|
||||
|
||||
void UseHeadlessEngine() {
|
||||
FlutterWindowsEngineBuilder builder{GetContext()};
|
||||
|
||||
engine_ = builder.Build();
|
||||
}
|
||||
|
||||
void UseEngineWithView() {
|
||||
FlutterWindowsEngineBuilder builder{GetContext()};
|
||||
|
||||
auto window = std::make_unique<MockWindowBindingHandler>();
|
||||
|
||||
window_ = window.get();
|
||||
EXPECT_CALL(*window_, SetView).Times(1);
|
||||
EXPECT_CALL(*window, GetRenderTarget).WillRepeatedly(Return(nullptr));
|
||||
|
||||
engine_ = builder.Build();
|
||||
view_ = std::make_unique<MockFlutterWindowsView>(std::move(window));
|
||||
|
||||
engine_->SetView(view_.get());
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<FlutterWindowsEngine> engine_;
|
||||
std::unique_ptr<MockFlutterWindowsView> view_;
|
||||
MockWindowBindingHandler* window_;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(TextInputPluginTest);
|
||||
};
|
||||
|
||||
TEST_F(TextInputPluginTest, TextMethodsWorksWithEmptyModel) {
|
||||
UseEngineWithView();
|
||||
|
||||
auto handled_message = CreateResponse(true);
|
||||
auto unhandled_message = CreateResponse(false);
|
||||
int received_scancode = 0;
|
||||
@ -120,10 +167,9 @@ TEST(TextInputPluginTest, TextMethodsWorksWithEmptyModel) {
|
||||
[&received_scancode, &handled_message, &unhandled_message](
|
||||
const std::string& channel, const uint8_t* message,
|
||||
size_t message_size, BinaryReply reply) {});
|
||||
MockTextInputPluginDelegate delegate;
|
||||
|
||||
int redispatch_scancode = 0;
|
||||
TextInputPlugin handler(&messenger, &delegate);
|
||||
TextInputPlugin handler(&messenger, engine());
|
||||
|
||||
handler.KeyboardHook(VK_RETURN, 100, WM_KEYDOWN, '\n', false, false);
|
||||
handler.ComposeBeginHook();
|
||||
@ -135,16 +181,17 @@ TEST(TextInputPluginTest, TextMethodsWorksWithEmptyModel) {
|
||||
// Passes if it did not crash
|
||||
}
|
||||
|
||||
TEST(TextInputPluginTest, ClearClientResetsComposing) {
|
||||
TEST_F(TextInputPluginTest, ClearClientResetsComposing) {
|
||||
UseEngineWithView();
|
||||
|
||||
TestBinaryMessenger messenger([](const std::string& channel,
|
||||
const uint8_t* message, size_t message_size,
|
||||
BinaryReply reply) {});
|
||||
BinaryReply reply_handler = [](const uint8_t* reply, size_t reply_size) {};
|
||||
|
||||
MockTextInputPluginDelegate delegate;
|
||||
TextInputPlugin handler(&messenger, &delegate);
|
||||
TextInputPlugin handler(&messenger, engine());
|
||||
|
||||
EXPECT_CALL(delegate, OnResetImeComposing());
|
||||
EXPECT_CALL(*view(), OnResetImeComposing());
|
||||
|
||||
auto& codec = JsonMethodCodec::GetInstance();
|
||||
auto message = codec.EncodeMethodCall({"TextInput.clearClient", nullptr});
|
||||
@ -152,9 +199,37 @@ TEST(TextInputPluginTest, ClearClientResetsComposing) {
|
||||
message->size(), reply_handler);
|
||||
}
|
||||
|
||||
// Verify that clear client fails if in headless mode.
|
||||
TEST_F(TextInputPluginTest, ClearClientRequiresView) {
|
||||
UseHeadlessEngine();
|
||||
|
||||
TestBinaryMessenger messenger([](const std::string& channel,
|
||||
const uint8_t* message, size_t message_size,
|
||||
BinaryReply reply) {});
|
||||
|
||||
std::string reply;
|
||||
BinaryReply reply_handler = [&reply](const uint8_t* reply_bytes,
|
||||
size_t reply_size) {
|
||||
reply = std::string(reinterpret_cast<const char*>(reply_bytes), reply_size);
|
||||
};
|
||||
|
||||
TextInputPlugin handler(&messenger, engine());
|
||||
|
||||
auto& codec = JsonMethodCodec::GetInstance();
|
||||
auto message = codec.EncodeMethodCall({"TextInput.clearClient", nullptr});
|
||||
messenger.SimulateEngineMessage(kChannelName, message->data(),
|
||||
message->size(), reply_handler);
|
||||
|
||||
EXPECT_EQ(reply,
|
||||
"[\"Internal Consistency Error\",\"Text input is not available in "
|
||||
"Windows headless mode\",null]");
|
||||
}
|
||||
|
||||
// Verify that the embedder sends state update messages to the framework during
|
||||
// IME composing.
|
||||
TEST(TextInputPluginTest, VerifyComposingSendStateUpdate) {
|
||||
TEST_F(TextInputPluginTest, VerifyComposingSendStateUpdate) {
|
||||
UseEngineWithView();
|
||||
|
||||
bool sent_message = false;
|
||||
TestBinaryMessenger messenger(
|
||||
[&sent_message](const std::string& channel, const uint8_t* message,
|
||||
@ -162,8 +237,7 @@ TEST(TextInputPluginTest, VerifyComposingSendStateUpdate) {
|
||||
BinaryReply reply) { sent_message = true; });
|
||||
BinaryReply reply_handler = [](const uint8_t* reply, size_t reply_size) {};
|
||||
|
||||
MockTextInputPluginDelegate delegate;
|
||||
TextInputPlugin handler(&messenger, &delegate);
|
||||
TextInputPlugin handler(&messenger, engine());
|
||||
|
||||
auto& codec = JsonMethodCodec::GetInstance();
|
||||
|
||||
@ -209,7 +283,9 @@ TEST(TextInputPluginTest, VerifyComposingSendStateUpdate) {
|
||||
EXPECT_TRUE(sent_message);
|
||||
}
|
||||
|
||||
TEST(TextInputPluginTest, VerifyInputActionNewlineInsertNewLine) {
|
||||
TEST_F(TextInputPluginTest, VerifyInputActionNewlineInsertNewLine) {
|
||||
UseEngineWithView();
|
||||
|
||||
// Store messages as std::string for convenience.
|
||||
std::vector<std::string> messages;
|
||||
|
||||
@ -222,8 +298,7 @@ TEST(TextInputPluginTest, VerifyInputActionNewlineInsertNewLine) {
|
||||
});
|
||||
BinaryReply reply_handler = [](const uint8_t* reply, size_t reply_size) {};
|
||||
|
||||
MockTextInputPluginDelegate delegate;
|
||||
TextInputPlugin handler(&messenger, &delegate);
|
||||
TextInputPlugin handler(&messenger, engine());
|
||||
|
||||
auto& codec = JsonMethodCodec::GetInstance();
|
||||
|
||||
@ -266,7 +341,9 @@ TEST(TextInputPluginTest, VerifyInputActionNewlineInsertNewLine) {
|
||||
}
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/125879.
|
||||
TEST(TextInputPluginTest, VerifyInputActionSendDoesNotInsertNewLine) {
|
||||
TEST_F(TextInputPluginTest, VerifyInputActionSendDoesNotInsertNewLine) {
|
||||
UseEngineWithView();
|
||||
|
||||
std::vector<std::vector<uint8_t>> messages;
|
||||
|
||||
TestBinaryMessenger messenger(
|
||||
@ -279,8 +356,7 @@ TEST(TextInputPluginTest, VerifyInputActionSendDoesNotInsertNewLine) {
|
||||
});
|
||||
BinaryReply reply_handler = [](const uint8_t* reply, size_t reply_size) {};
|
||||
|
||||
MockTextInputPluginDelegate delegate;
|
||||
TextInputPlugin handler(&messenger, &delegate);
|
||||
TextInputPlugin handler(&messenger, engine());
|
||||
|
||||
auto& codec = JsonMethodCodec::GetInstance();
|
||||
|
||||
@ -312,7 +388,9 @@ TEST(TextInputPluginTest, VerifyInputActionSendDoesNotInsertNewLine) {
|
||||
messages.front().begin()));
|
||||
}
|
||||
|
||||
TEST(TextInputPluginTest, TextEditingWorksWithDeltaModel) {
|
||||
TEST_F(TextInputPluginTest, TextEditingWorksWithDeltaModel) {
|
||||
UseEngineWithView();
|
||||
|
||||
auto handled_message = CreateResponse(true);
|
||||
auto unhandled_message = CreateResponse(false);
|
||||
int received_scancode = 0;
|
||||
@ -321,10 +399,9 @@ TEST(TextInputPluginTest, TextEditingWorksWithDeltaModel) {
|
||||
[&received_scancode, &handled_message, &unhandled_message](
|
||||
const std::string& channel, const uint8_t* message,
|
||||
size_t message_size, BinaryReply reply) {});
|
||||
MockTextInputPluginDelegate delegate;
|
||||
|
||||
int redispatch_scancode = 0;
|
||||
TextInputPlugin handler(&messenger, &delegate);
|
||||
TextInputPlugin handler(&messenger, engine());
|
||||
|
||||
auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
|
||||
auto& allocator = args->GetAllocator();
|
||||
@ -369,7 +446,9 @@ TEST(TextInputPluginTest, TextEditingWorksWithDeltaModel) {
|
||||
}
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/123749
|
||||
TEST(TextInputPluginTest, CompositionCursorPos) {
|
||||
TEST_F(TextInputPluginTest, CompositionCursorPos) {
|
||||
UseEngineWithView();
|
||||
|
||||
int selection_base = -1;
|
||||
TestBinaryMessenger messenger([&](const std::string& channel,
|
||||
const uint8_t* message, size_t size,
|
||||
@ -389,9 +468,8 @@ TEST(TextInputPluginTest, CompositionCursorPos) {
|
||||
EXPECT_EQ(extent->value.GetInt(), selection_base);
|
||||
}
|
||||
});
|
||||
MockTextInputPluginDelegate delegate;
|
||||
|
||||
TextInputPlugin plugin(&messenger, &delegate);
|
||||
TextInputPlugin plugin(&messenger, engine());
|
||||
|
||||
auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
|
||||
auto& allocator = args->GetAllocator();
|
||||
@ -427,7 +505,9 @@ TEST(TextInputPluginTest, CompositionCursorPos) {
|
||||
EXPECT_EQ(selection_base, 5);
|
||||
}
|
||||
|
||||
TEST(TextInputPluginTest, TransformCursorRect) {
|
||||
TEST_F(TextInputPluginTest, TransformCursorRect) {
|
||||
UseEngineWithView();
|
||||
|
||||
// A position of `EditableText`.
|
||||
double view_x = 100;
|
||||
double view_y = 200;
|
||||
@ -450,12 +530,11 @@ TEST(TextInputPluginTest, TransformCursorRect) {
|
||||
BinaryReply reply) {});
|
||||
BinaryReply reply_handler = [](const uint8_t* reply, size_t reply_size) {};
|
||||
|
||||
MockTextInputPluginDelegate delegate;
|
||||
TextInputPlugin handler(&messenger, &delegate);
|
||||
TextInputPlugin handler(&messenger, engine());
|
||||
|
||||
auto& codec = JsonMethodCodec::GetInstance();
|
||||
|
||||
EXPECT_CALL(delegate, OnCursorRectUpdated(Rect{{view_x, view_y}, {0, 0}}));
|
||||
EXPECT_CALL(*view(), OnCursorRectUpdated(Rect{{view_x, view_y}, {0, 0}}));
|
||||
|
||||
{
|
||||
auto arguments =
|
||||
@ -476,7 +555,7 @@ TEST(TextInputPluginTest, TransformCursorRect) {
|
||||
message->size(), reply_handler);
|
||||
}
|
||||
|
||||
EXPECT_CALL(delegate,
|
||||
EXPECT_CALL(*view(),
|
||||
OnCursorRectUpdated(Rect{{view_x + ime_x, view_y + ime_y},
|
||||
{ime_width, ime_height}}));
|
||||
|
||||
@ -497,5 +576,41 @@ TEST(TextInputPluginTest, TransformCursorRect) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TextInputPluginTest, SetMarkedTextRectRequiresView) {
|
||||
UseHeadlessEngine();
|
||||
|
||||
TestBinaryMessenger messenger([](const std::string& channel,
|
||||
const uint8_t* message, size_t message_size,
|
||||
BinaryReply reply) {});
|
||||
|
||||
std::string reply;
|
||||
BinaryReply reply_handler = [&reply](const uint8_t* reply_bytes,
|
||||
size_t reply_size) {
|
||||
reply = std::string(reinterpret_cast<const char*>(reply_bytes), reply_size);
|
||||
};
|
||||
|
||||
TextInputPlugin handler(&messenger, engine());
|
||||
|
||||
auto& codec = JsonMethodCodec::GetInstance();
|
||||
|
||||
auto arguments =
|
||||
std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
|
||||
auto& allocator = arguments->GetAllocator();
|
||||
|
||||
arguments->AddMember("x", 0, allocator);
|
||||
arguments->AddMember("y", 0, allocator);
|
||||
arguments->AddMember("width", 0, allocator);
|
||||
arguments->AddMember("height", 0, allocator);
|
||||
|
||||
auto message = codec.EncodeMethodCall(
|
||||
{"TextInput.setMarkedTextRect", std::move(arguments)});
|
||||
messenger.SimulateEngineMessage(kChannelName, message->data(),
|
||||
message->size(), reply_handler);
|
||||
|
||||
EXPECT_EQ(reply,
|
||||
"[\"Internal Consistency Error\",\"Text input is not available in "
|
||||
"Windows headless mode\",null]");
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace flutter
|
||||
|
||||
@ -98,15 +98,16 @@ class WindowBindingHandlerDelegate {
|
||||
|
||||
// Notifies the delegate that IME composing region have been committed.
|
||||
//
|
||||
// Triggered when the user commits the current composing text while using a
|
||||
// multi-step input method such as in CJK text input. Composing continues with
|
||||
// the next keypress.
|
||||
// Triggered when the user triggers a commit of the current composing text
|
||||
// while using a multi-step input method such as in CJK text input. Composing
|
||||
// continues with the next keypress.
|
||||
virtual void OnComposeCommit() = 0;
|
||||
|
||||
// Notifies the delegate that IME composing mode has ended.
|
||||
//
|
||||
// Triggered when the user commits the composing text while using a multi-step
|
||||
// input method such as in CJK text input.
|
||||
// Triggered when the composing ends, for example when the user presses
|
||||
// ESC or when the user triggers a commit of the composing text while using a
|
||||
// multi-step input method such as in CJK text input.
|
||||
virtual void OnComposeEnd() = 0;
|
||||
|
||||
// Notifies the delegate that IME composing region contents have changed.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user