blob: 5e87e8a4273b9f5a1c59a6044ea465a1e4b43177 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <text-input-unstable-v1-server-protocol.h>
#include <wayland-server.h>
#include <memory>
#include "base/i18n/break_iterator.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/base/ime/linux/linux_input_method_context.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/base/ime/text_input_flags.h"
#include "ui/base/ime/text_input_type.h"
#include "ui/events/event.h"
#include "ui/gfx/range/range.h"
#include "ui/ozone/platform/wayland/host/wayland_event_source.h"
#include "ui/ozone/platform/wayland/host/wayland_input_method_context.h"
#include "ui/ozone/platform/wayland/host/wayland_seat.h"
#include "ui/ozone/platform/wayland/host/wayland_window.h"
#include "ui/ozone/platform/wayland/test/mock_surface.h"
#include "ui/ozone/platform/wayland/test/mock_zcr_extended_text_input.h"
#include "ui/ozone/platform/wayland/test/mock_zwp_text_input.h"
#include "ui/ozone/platform/wayland/test/test_util.h"
#include "ui/ozone/platform/wayland/test/test_wayland_server_thread.h"
#include "ui/ozone/platform/wayland/test/test_zcr_text_input_extension.h"
#include "ui/ozone/platform/wayland/test/wayland_test.h"
using ::testing::_;
using ::testing::DoAll;
using ::testing::InSequence;
using ::testing::Mock;
using ::testing::SaveArg;
using ::testing::Values;
namespace ui {
// Returns the number of grapheme clusters in the text.
absl::optional<size_t> CountGraphemeCluster(base::StringPiece16 text) {
base::i18n::BreakIterator iter(text,
base::i18n::BreakIterator::BREAK_CHARACTER);
if (!iter.Init())
return absl::nullopt;
size_t result = 0;
while (iter.Advance())
++result;
return result;
}
// TODO(crbug.com/1370046): Subclass FakeTextInputClient after pruning deps.
class MockTextInputClient : public TextInputClient {
public:
explicit MockTextInputClient(TextInputType text_input_type) {
text_input_type_ = text_input_type;
}
MockTextInputClient(const MockTextInputClient& other) = delete;
MockTextInputClient& operator=(const MockTextInputClient& other) = delete;
~MockTextInputClient() override = default;
TextInputType GetTextInputType() const override { return text_input_type_; }
MOCK_METHOD(void,
SetCompositionText,
(const ui::CompositionText&),
(override));
MOCK_METHOD(size_t, ConfirmCompositionText, (bool), (override));
MOCK_METHOD(void, ClearCompositionText, (), (override));
MOCK_METHOD(void,
InsertText,
(const std::u16string&,
ui::TextInputClient::InsertTextCursorBehavior cursor_behavior),
(override));
MOCK_METHOD(void, InsertChar, (const ui::KeyEvent&), (override));
MOCK_METHOD(ui::TextInputMode, GetTextInputMode, (), (const, override));
MOCK_METHOD(base::i18n::TextDirection,
GetTextDirection,
(),
(const, override));
MOCK_METHOD(int, GetTextInputFlags, (), (const, override));
MOCK_METHOD(bool, CanComposeInline, (), (const, override));
MOCK_METHOD(gfx::Rect, GetCaretBounds, (), (const, override));
MOCK_METHOD(gfx::Rect, GetSelectionBoundingBox, (), (const, override));
MOCK_METHOD(bool,
GetCompositionCharacterBounds,
(size_t, gfx::Rect*),
(const, override));
MOCK_METHOD(bool, HasCompositionText, (), (const, override));
MOCK_METHOD(ui::TextInputClient::FocusReason,
GetFocusReason,
(),
(const, override));
MOCK_METHOD(bool, GetTextRange, (gfx::Range*), (const, override));
MOCK_METHOD(bool, GetCompositionTextRange, (gfx::Range*), (const, override));
MOCK_METHOD(bool,
GetEditableSelectionRange,
(gfx::Range*),
(const, override));
MOCK_METHOD(bool, SetEditableSelectionRange, (const gfx::Range&), (override));
MOCK_METHOD(bool,
GetTextFromRange,
(const gfx::Range&, std::u16string*),
(const, override));
MOCK_METHOD(void, OnInputMethodChanged, (), (override));
MOCK_METHOD(bool,
ChangeTextDirectionAndLayoutAlignment,
(base::i18n::TextDirection),
(override));
MOCK_METHOD(void, ExtendSelectionAndDelete, (size_t, size_t), (override));
MOCK_METHOD(void, EnsureCaretNotInRect, (const gfx::Rect&), (override));
MOCK_METHOD(bool,
IsTextEditCommandEnabled,
(TextEditCommand),
(const, override));
MOCK_METHOD(void,
SetTextEditCommandForNextKeyEvent,
(TextEditCommand),
(override));
MOCK_METHOD(ukm::SourceId, GetClientSourceForMetrics, (), (const, override));
MOCK_METHOD(bool, ShouldDoLearning, (), (override));
MOCK_METHOD(bool,
SetCompositionFromExistingText,
(const gfx::Range&, const std::vector<ui::ImeTextSpan>&),
(override));
#if BUILDFLAG(IS_CHROMEOS)
MOCK_METHOD(gfx::Range, GetAutocorrectRange, (), (const, override));
MOCK_METHOD(gfx::Rect, GetAutocorrectCharacterBounds, (), (const, override));
MOCK_METHOD(bool, SetAutocorrectRange, (const gfx::Range& range), (override));
MOCK_METHOD(void,
GetActiveTextInputControlLayoutBounds,
(absl::optional<gfx::Rect> * control_bounds,
absl::optional<gfx::Rect>* selection_bounds),
(override));
#endif
private:
TextInputType text_input_type_;
};
class TestInputMethodContextDelegate : public LinuxInputMethodContextDelegate {
public:
TestInputMethodContextDelegate() = default;
TestInputMethodContextDelegate(const TestInputMethodContextDelegate&) =
delete;
TestInputMethodContextDelegate& operator=(
const TestInputMethodContextDelegate&) = delete;
~TestInputMethodContextDelegate() override = default;
void OnCommit(const std::u16string& text) override {
was_on_commit_called_ = true;
}
void OnConfirmCompositionText(bool keep_selection) override {
was_on_confirm_composition_text_called_ = true;
}
void OnPreeditChanged(const ui::CompositionText& composition_text) override {
was_on_preedit_changed_called_ = true;
}
void OnClearGrammarFragments(const gfx::Range& range) override {
was_on_clear_grammar_fragments_called_ = true;
}
void OnAddGrammarFragment(const ui::GrammarFragment& fragment) override {
was_on_add_grammar_fragment_called_ = true;
}
void OnSetAutocorrectRange(const gfx::Range& range) override {
was_on_set_autocorrect_range_called_ = true;
}
void OnPreeditEnd() override {}
void OnPreeditStart() override {}
void OnDeleteSurroundingText(size_t before, size_t after) override {
last_on_delete_surrounding_text_args_ = std::make_pair(before, after);
}
void OnSetPreeditRegion(const gfx::Range& range,
const std::vector<ImeTextSpan>& spans) override {
was_on_set_preedit_region_called_ = true;
}
void OnSetVirtualKeyboardOccludedBounds(
const gfx::Rect& screen_bounds) override {
virtual_keyboard_bounds_ = screen_bounds;
}
bool was_on_commit_called() const { return was_on_commit_called_; }
bool was_on_confirm_composition_text_called() const {
return was_on_confirm_composition_text_called_;
}
bool was_on_preedit_changed_called() const {
return was_on_preedit_changed_called_;
}
bool was_on_set_preedit_region_called() const {
return was_on_set_preedit_region_called_;
}
bool was_on_clear_grammar_fragments_called() const {
return was_on_clear_grammar_fragments_called_;
}
bool was_on_add_grammar_fragment_called() const {
return was_on_add_grammar_fragment_called_;
}
bool was_on_set_autocorrect_range_called() const {
return was_on_set_autocorrect_range_called_;
}
const absl::optional<std::pair<size_t, size_t>>&
last_on_delete_surrounding_text_args() const {
return last_on_delete_surrounding_text_args_;
}
const absl::optional<gfx::Rect>& virtual_keyboard_bounds() const {
return virtual_keyboard_bounds_;
}
private:
bool was_on_commit_called_ = false;
bool was_on_confirm_composition_text_called_ = false;
bool was_on_preedit_changed_called_ = false;
bool was_on_set_preedit_region_called_ = false;
bool was_on_clear_grammar_fragments_called_ = false;
bool was_on_add_grammar_fragment_called_ = false;
bool was_on_set_autocorrect_range_called_ = false;
absl::optional<std::pair<size_t, size_t>>
last_on_delete_surrounding_text_args_;
absl::optional<gfx::Rect> virtual_keyboard_bounds_;
};
class WaylandInputMethodContextTestBase : public WaylandTest {
public:
void SetUp() override {
WaylandTest::SetUp();
surface_id_ = window_->root_surface()->get_surface_id();
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
// WaylandInputMethodContext behaves differently when no keyboard is
// attached.
wl_seat_send_capabilities(server->seat()->resource(),
WL_SEAT_CAPABILITY_KEYBOARD);
});
ASSERT_TRUE(connection_->seat()->keyboard());
SetUpInternal();
}
protected:
void SetUpInternal() {
input_method_context_delegate_ =
std::make_unique<TestInputMethodContextDelegate>();
input_method_context_ = std::make_unique<WaylandInputMethodContext>(
connection_.get(), connection_->event_source(),
input_method_context_delegate_.get());
input_method_context_->Init(true);
connection_->Flush();
wl::SyncDisplay(connection_->display_wrapper(), *connection_->display());
// Unset Keyboard focus.
connection_->window_manager()->SetKeyboardFocusedWindow(nullptr);
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
ASSERT_TRUE(server->text_input_manager_v1()->text_input());
ASSERT_TRUE(server->text_input_extension_v1()->extended_text_input());
});
ASSERT_TRUE(connection_->text_input_manager_v1());
ASSERT_TRUE(connection_->text_input_extension_v1());
}
std::unique_ptr<TestInputMethodContextDelegate>
input_method_context_delegate_;
std::unique_ptr<WaylandInputMethodContext> input_method_context_;
raw_ptr<wl::MockZwpTextInput> zwp_text_input_ = nullptr;
raw_ptr<wl::MockZcrExtendedTextInput> zcr_extended_text_input_ = nullptr;
uint32_t surface_id_ = 0u;
};
using WaylandInputMethodContextTest = WaylandInputMethodContextTestBase;
using WaylandInputMethodContextOldServerTest =
WaylandInputMethodContextTestBase;
INSTANTIATE_TEST_SUITE_P(
TextInputExtensionLatestVersion,
WaylandInputMethodContextTest,
::testing::Values(wl::ServerConfig{.use_ime_keep_selection_fix = true},
wl::ServerConfig{.use_ime_keep_selection_fix = false}));
INSTANTIATE_TEST_SUITE_P(
TextInputExtensionV7,
WaylandInputMethodContextOldServerTest,
::testing::Values(wl::ServerConfig{
.text_input_extension_version =
wl::TestZcrTextInputExtensionV1::Version::kV7}));
TEST_P(WaylandInputMethodContextOldServerTest, SetContentType) {
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
EXPECT_CALL(*server->text_input_extension_v1()->extended_text_input(),
DeprecatedSetInputType(
ZCR_EXTENDED_TEXT_INPUT_V1_INPUT_TYPE_URL,
ZCR_EXTENDED_TEXT_INPUT_V1_INPUT_MODE_DEFAULT,
ZCR_EXTENDED_TEXT_INPUT_V1_INPUT_FLAGS_AUTOCOMPLETE_ON,
ZCR_EXTENDED_TEXT_INPUT_V1_LEARNING_MODE_ENABLED))
.Times(1);
});
input_method_context_->SetContentType(TEXT_INPUT_TYPE_URL,
TEXT_INPUT_MODE_DEFAULT,
TEXT_INPUT_FLAG_AUTOCOMPLETE_ON,
/*should_do_learning=*/true,
/*can_compose_inline=*/false);
connection_->Flush();
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
Mock::VerifyAndClearExpectations(
server->text_input_extension_v1()->extended_text_input());
});
}
TEST_P(WaylandInputMethodContextTest, ActivateDeactivate) {
// Activate is called only when both InputMethod's TextInputClient focus and
// Wayland's keyboard focus is met.
// Scenario 1: InputMethod focus is set, then Keyboard focus is set.
// Unset them in the reversed order.
InSequence s;
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
auto* zwp_text_input = server->text_input_manager_v1()->text_input();
ASSERT_TRUE(zwp_text_input);
EXPECT_CALL(*zwp_text_input,
Activate(server->GetObject<wl::MockSurface>(id)->resource()))
.Times(0);
EXPECT_CALL(*zwp_text_input, ShowInputPanel()).Times(0);
});
input_method_context_->UpdateFocus(true, ui::TEXT_INPUT_TYPE_NONE,
ui::TEXT_INPUT_TYPE_TEXT,
ui::TextInputClient::FOCUS_REASON_OTHER);
connection_->Flush();
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
auto* zwp_text_input = server->text_input_manager_v1()->text_input();
Mock::VerifyAndClearExpectations(zwp_text_input);
EXPECT_CALL(*zwp_text_input,
Activate(server->GetObject<wl::MockSurface>(id)->resource()));
EXPECT_CALL(*zwp_text_input, ShowInputPanel());
});
connection_->window_manager()->SetKeyboardFocusedWindow(window_.get());
connection_->Flush();
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
auto* zwp_text_input = server->text_input_manager_v1()->text_input();
Mock::VerifyAndClearExpectations(zwp_text_input);
EXPECT_CALL(*zwp_text_input, HideInputPanel());
EXPECT_CALL(*zwp_text_input, Deactivate());
});
connection_->window_manager()->SetKeyboardFocusedWindow(nullptr);
connection_->Flush();
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
auto* zwp_text_input = server->text_input_manager_v1()->text_input();
Mock::VerifyAndClearExpectations(zwp_text_input);
EXPECT_CALL(*zwp_text_input, HideInputPanel()).Times(0);
EXPECT_CALL(*zwp_text_input, Deactivate()).Times(0);
});
input_method_context_->UpdateFocus(true, ui::TEXT_INPUT_TYPE_TEXT,
ui::TEXT_INPUT_TYPE_NONE,
ui::TextInputClient::FOCUS_REASON_NONE);
connection_->Flush();
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
auto* zwp_text_input = server->text_input_manager_v1()->text_input();
Mock::VerifyAndClearExpectations(zwp_text_input);
// Scenario 2: Keyboard focus is set, then InputMethod focus is set.
// Unset them in the reversed order.
EXPECT_CALL(*zwp_text_input,
Activate(server->GetObject<wl::MockSurface>(id)->resource()))
.Times(0);
EXPECT_CALL(*zwp_text_input, ShowInputPanel()).Times(0);
});
connection_->window_manager()->SetKeyboardFocusedWindow(window_.get());
connection_->Flush();
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
auto* zwp_text_input = server->text_input_manager_v1()->text_input();
Mock::VerifyAndClearExpectations(zwp_text_input);
EXPECT_CALL(*zwp_text_input,
Activate(server->GetObject<wl::MockSurface>(id)->resource()));
EXPECT_CALL(*zwp_text_input, ShowInputPanel());
});
input_method_context_->UpdateFocus(true, ui::TEXT_INPUT_TYPE_NONE,
ui::TEXT_INPUT_TYPE_TEXT,
ui::TextInputClient::FOCUS_REASON_OTHER);
connection_->Flush();
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
auto* zwp_text_input = server->text_input_manager_v1()->text_input();
Mock::VerifyAndClearExpectations(zwp_text_input);
EXPECT_CALL(*zwp_text_input, HideInputPanel());
EXPECT_CALL(*zwp_text_input, Deactivate());
});
input_method_context_->UpdateFocus(true, ui::TEXT_INPUT_TYPE_TEXT,
ui::TEXT_INPUT_TYPE_NONE,
ui::TextInputClient::FOCUS_REASON_NONE);
connection_->Flush();
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
auto* zwp_text_input = server->text_input_manager_v1()->text_input();
Mock::VerifyAndClearExpectations(zwp_text_input);
EXPECT_CALL(*zwp_text_input, HideInputPanel()).Times(0);
EXPECT_CALL(*zwp_text_input, Deactivate()).Times(0);
});
connection_->window_manager()->SetKeyboardFocusedWindow(nullptr);
connection_->Flush();
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
auto* zwp_text_input = server->text_input_manager_v1()->text_input();
Mock::VerifyAndClearExpectations(zwp_text_input);
});
}
TEST_P(WaylandInputMethodContextTest, Reset) {
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
EXPECT_CALL(*server->text_input_manager_v1()->text_input(), Reset());
});
input_method_context_->Reset();
connection_->Flush();
}
TEST_P(WaylandInputMethodContextTest, SetCursorLocation) {
constexpr gfx::Rect cursor_location(50, 20, 1, 1);
constexpr gfx::Rect window_bounds(20, 10, 100, 100);
PostToServerAndWait(
[cursor_location, window_bounds](wl::TestWaylandServerThread* server) {
EXPECT_CALL(
*server->text_input_manager_v1()->text_input(),
SetCursorRect(cursor_location.x() - window_bounds.x(),
cursor_location.y() - window_bounds.y(),
cursor_location.width(), cursor_location.height()));
});
window_->SetBoundsInDIP(window_bounds);
connection_->window_manager()->SetKeyboardFocusedWindow(window_.get());
input_method_context_->SetCursorLocation(cursor_location);
connection_->Flush();
}
TEST_P(WaylandInputMethodContextTest, SetSurroundingTextForShortText) {
const std::u16string text(50, u'あ');
constexpr gfx::Range range(20, 30);
const std::string kExpectedSentText(base::UTF16ToUTF8(text));
constexpr gfx::Range kExpectedSentRange(60, 90);
PostToServerAndWait([kExpectedSentText, kExpectedSentRange](
wl::TestWaylandServerThread* server) {
// The text and range sent as wayland protocol must be same to the original
// text and range where the original text is shorter than 4000 byte.
EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
SetSurroundingText(kExpectedSentText, kExpectedSentRange))
.Times(1);
});
input_method_context_->SetSurroundingText(text, gfx::Range(0, 50), range,
absl::nullopt, absl::nullopt);
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
text);
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
range);
connection_->Flush();
PostToServerAndWait(
[kExpectedSentRange](wl::TestWaylandServerThread* server) {
auto* text_input = server->text_input_manager_v1()->text_input();
Mock::VerifyAndClearExpectations(text_input);
// Test OnDeleteSurroundingText with this input.
zwp_text_input_v1_send_delete_surrounding_text(
text_input->resource(), kExpectedSentRange.start(),
kExpectedSentRange.length());
});
EXPECT_EQ(
input_method_context_delegate_->last_on_delete_surrounding_text_args(),
(std::pair<size_t, size_t>(0, 0)));
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
std::u16string(40, u'あ'));
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
gfx::Range(20));
}
TEST_P(WaylandInputMethodContextTest, SetSurroundingTextForLongText) {
const std::u16string text(5000, u'あ');
constexpr gfx::Range range(2800, 3200);
// The text sent as wayland protocol must be at most 4000 byte and long
// enough in the limitation.
const std::string kExpectedSentText(
base::UTF16ToUTF8(std::u16string(1332, u'あ')));
// The selection range must be relocated accordingly to the sent text.
constexpr gfx::Range kExpectedSentRange(1398, 2598);
PostToServerAndWait([kExpectedSentText, kExpectedSentRange](
wl::TestWaylandServerThread* server) {
EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
SetSurroundingText(kExpectedSentText, kExpectedSentRange))
.Times(1);
});
input_method_context_->SetSurroundingText(text, gfx::Range(0, 5000), range,
absl::nullopt, absl::nullopt);
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
text);
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
range);
connection_->Flush();
PostToServerAndWait(
[kExpectedSentRange](wl::TestWaylandServerThread* server) {
auto* text_input = server->text_input_manager_v1()->text_input();
Mock::VerifyAndClearExpectations(text_input);
// Test OnDeleteSurroundingText with this input.
zwp_text_input_v1_send_delete_surrounding_text(
text_input->resource(), kExpectedSentRange.start(),
kExpectedSentRange.length());
});
EXPECT_EQ(
input_method_context_delegate_->last_on_delete_surrounding_text_args(),
(std::pair<size_t, size_t>(0, 0)));
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
std::u16string(4600, u'あ'));
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
gfx::Range(2800));
}
TEST_P(WaylandInputMethodContextTest, SetSurroundingTextForLongTextInLeftEdge) {
const std::u16string text(5000, u'あ');
constexpr gfx::Range range(0, 500);
// The text sent as wayland protocol must be at most 4000 byte and large
// enough in the limitation.
const std::string kExpectedSentText(
base::UTF16ToUTF8(std::u16string(1333, u'あ')));
// The selection range must be relocated accordingly to the sent text.
constexpr gfx::Range kExpectedSentRange(0, 1500);
PostToServerAndWait([kExpectedSentText, kExpectedSentRange](
wl::TestWaylandServerThread* server) {
EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
SetSurroundingText(kExpectedSentText, kExpectedSentRange))
.Times(1);
});
input_method_context_->SetSurroundingText(text, gfx::Range(0, 5000), range,
absl::nullopt, absl::nullopt);
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
text);
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
range);
connection_->Flush();
PostToServerAndWait(
[kExpectedSentRange](wl::TestWaylandServerThread* server) {
auto* text_input = server->text_input_manager_v1()->text_input();
Mock::VerifyAndClearExpectations(text_input);
// Test OnDeleteSurroundingText with this input.
zwp_text_input_v1_send_delete_surrounding_text(
text_input->resource(), kExpectedSentRange.start(),
kExpectedSentRange.length());
});
EXPECT_EQ(
input_method_context_delegate_->last_on_delete_surrounding_text_args(),
(std::pair<size_t, size_t>(0, 0)));
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
std::u16string(4500, u'あ'));
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
gfx::Range(0));
}
TEST_P(WaylandInputMethodContextTest,
SetSurroundingTextForLongTextInRightEdge) {
const std::u16string text(5000, u'あ');
constexpr gfx::Range range(4500, 5000);
// The text sent as wayland protocol must be at most 4000 byte and large
// enough in the limitation.
const std::string kExpectedSentText(
base::UTF16ToUTF8(std::u16string(1333, u'あ')));
// The selection range must be relocated accordingly to the sent text.
constexpr gfx::Range kExpectedSentRange(2499, 3999);
PostToServerAndWait([kExpectedSentText, kExpectedSentRange](
wl::TestWaylandServerThread* server) {
EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
SetSurroundingText(kExpectedSentText, kExpectedSentRange))
.Times(1);
});
input_method_context_->SetSurroundingText(text, gfx::Range(0, 5000), range,
absl::nullopt, absl::nullopt);
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
text);
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
range);
connection_->Flush();
PostToServerAndWait(
[kExpectedSentRange](wl::TestWaylandServerThread* server) {
auto* text_input = server->text_input_manager_v1()->text_input();
Mock::VerifyAndClearExpectations(text_input);
// Test OnDeleteSurroundingText with this input.
zwp_text_input_v1_send_delete_surrounding_text(
text_input->resource(), kExpectedSentRange.start(),
kExpectedSentRange.length());
});
EXPECT_EQ(
input_method_context_delegate_->last_on_delete_surrounding_text_args(),
(std::pair<size_t, size_t>(0, 0)));
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
std::u16string(4500, u'あ'));
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
gfx::Range(4500));
}
TEST_P(WaylandInputMethodContextTest, SetSurroundingTextForLongRange) {
const std::u16string text(5000, u'あ');
constexpr gfx::Range range(1000, 4000);
// set_surrounding_text request should be skipped when the selection range in
// UTF8 form is longer than 4000 byte.
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
SetSurroundingText(_, _))
.Times(0);
});
input_method_context_->SetSurroundingText(text, gfx::Range(0, 5000), range,
absl::nullopt, absl::nullopt);
// Predicted state in SurroundingTextTracker is reset when the range is longer
// than wayland message size maximum.
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
u"");
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
gfx::Range(0));
connection_->Flush();
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
Mock::VerifyAndClearExpectations(
server->text_input_manager_v1()->text_input());
});
}
TEST_P(WaylandInputMethodContextTest,
SetSurroundingTextForShortTextWithGrammmarFragment) {
const std::u16string text(50, u'あ');
constexpr gfx::Range range(20, 30);
const std::string kExpectedSentText(base::UTF16ToUTF8(text));
constexpr gfx::Range kExpectedSentRange(60, 90);
PostToServerAndWait([kExpectedSentText, kExpectedSentRange](
wl::TestWaylandServerThread* server) {
// The text and range sent as wayland protocol must be same to the original
// text and range where the original text is shorter than 4000 byte.
EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
SetSurroundingText(kExpectedSentText, kExpectedSentRange))
.Times(1);
EXPECT_CALL(*server->text_input_extension_v1()->extended_text_input(),
SetGrammarFragmentAtCursor(gfx::Range(0, 30), "abc"))
.Times(1);
});
input_method_context_->SetSurroundingText(
text, gfx::Range(0, 50), range, GrammarFragment(gfx::Range(0, 10), "abc"),
absl::nullopt);
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
text);
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
range);
connection_->Flush();
}
TEST_P(WaylandInputMethodContextTest,
SetSurroundingTextForLongTextWithGrammmarFragment) {
const std::u16string text(5000, u'あ');
constexpr gfx::Range range(2800, 3200);
// The text sent as wayland protocol must be at most 4000 byte and long
// enough in the limitation.
const std::string kExpectedSentText(
base::UTF16ToUTF8(std::u16string(1332, u'あ')));
// The selection range must be relocated accordingly to the sent text.
constexpr gfx::Range kExpectedSentRange(1398, 2598);
PostToServerAndWait([kExpectedSentText, kExpectedSentRange](
wl::TestWaylandServerThread* server) {
EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
SetSurroundingText(kExpectedSentText, kExpectedSentRange))
.Times(1);
EXPECT_CALL(*server->text_input_extension_v1()->extended_text_input(),
SetGrammarFragmentAtCursor(gfx::Range(1098, 1128), "abc"))
.Times(1);
});
input_method_context_->SetSurroundingText(
text, gfx::Range(0, 50), range,
GrammarFragment(gfx::Range(2700, 2710), "abc"), absl::nullopt);
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
text);
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
range);
connection_->Flush();
}
TEST_P(WaylandInputMethodContextTest,
SetSurroundingTextForShortTextWithAutocorrect) {
const std::u16string text(50, u'あ');
constexpr gfx::Range range(20, 30);
const std::string kExpectedSentText(base::UTF16ToUTF8(text));
constexpr gfx::Range kExpectedSentRange(60, 90);
PostToServerAndWait([kExpectedSentText, kExpectedSentRange](
wl::TestWaylandServerThread* server) {
// The text and range sent as wayland protocol must be same to the original
// text and range where the original text is shorter than 4000 byte.
EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
SetSurroundingText(kExpectedSentText, kExpectedSentRange))
.Times(1);
// Note: No conversion of range for autocorrect now.
EXPECT_CALL(*server->text_input_extension_v1()->extended_text_input(),
SetAutocorrectInfo(gfx::Range(15, 18), gfx::Rect(10, 20)));
});
input_method_context_->SetSurroundingText(
text, gfx::Range(0, 50), range, absl::nullopt,
AutocorrectInfo{gfx::Range(15, 18), gfx::Rect(10, 20)});
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
text);
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
range);
connection_->Flush();
}
TEST_P(WaylandInputMethodContextTest, DeleteSurroundingTextWithExtendedRange) {
const std::u16string text(50, u'あ');
const gfx::Range range(20, 30);
// The text and range sent as wayland protocol must be same to the original
// text and range where the original text is shorter than 4000 byte.
const std::string kExpectedSentText(base::UTF16ToUTF8(text));
// The selection range must be relocated accordingly to the sent text.
constexpr gfx::Range kExpectedSentRange(60, 90);
PostToServerAndWait([kExpectedSentText, kExpectedSentRange](
wl::TestWaylandServerThread* server) {
EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
SetSurroundingText(kExpectedSentText, kExpectedSentRange))
.Times(1);
});
input_method_context_->SetSurroundingText(text, gfx::Range(0, 5000), range,
absl::nullopt, absl::nullopt);
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
text);
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
range);
connection_->Flush();
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
auto* text_input = server->text_input_manager_v1()->text_input();
Mock::VerifyAndClearExpectations(text_input);
// Test OnDeleteSurroundingText with this input.
// One char more deletion for each before and after the selection.
zwp_text_input_v1_send_delete_surrounding_text(text_input->resource(), 57,
36);
});
EXPECT_EQ(
input_method_context_delegate_->last_on_delete_surrounding_text_args(),
(std::pair<size_t, size_t>(1, 1)));
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
std::u16string(38, u'あ'));
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
gfx::Range(19));
}
TEST_P(WaylandInputMethodContextTest, DeleteSurroundingTextInIncorrectOrder) {
// This test aims to check the scenario where OnDeleteSurroundingText event is
// not received in correct order due to the timing issue.
constexpr char16_t text[] = u"aあb";
const gfx::Range range(3);
input_method_context_->SetSurroundingText(text, gfx::Range(0, 3), range,
absl::nullopt, absl::nullopt);
connection_->Flush();
// 1. Delete the second character 'b'.
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
auto* text_input = server->text_input_manager_v1()->text_input();
Mock::VerifyAndClearExpectations(text_input);
zwp_text_input_v1_send_delete_surrounding_text(text_input->resource(), 4,
1);
});
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
u"aあ");
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
gfx::Range(2));
// 2. Delete the third character 'あ'.
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
auto* text_input = server->text_input_manager_v1()->text_input();
Mock::VerifyAndClearExpectations(text_input);
zwp_text_input_v1_send_delete_surrounding_text(text_input->resource(), 1,
3);
});
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
u"a");
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
gfx::Range(1));
// 3. Set surrounding text for step 1. Ideally this thould be called before
// step 2, but the order could be different due to the timing issue.
input_method_context_->SetSurroundingText(
u"aあ", gfx::Range(0, 2), gfx::Range(2), absl::nullopt, absl::nullopt);
connection_->Flush();
// Surrounding text tracker should predict "a" instead of "aあ" here as that
// is the correct state on server. On setting "aあ" as a surrounding text,
// surrounding text tracker looks up the expected state queue and consumes the
// state of "aあ" .
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
u"a");
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
gfx::Range(1));
// 4. Set surrounding text for step 2.
input_method_context_->SetSurroundingText(
u"a", gfx::Range(0, 1), gfx::Range(1), absl::nullopt, absl::nullopt);
connection_->Flush();
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
u"a");
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
gfx::Range(1));
}
TEST_P(WaylandInputMethodContextTest,
DeleteSurroundingTextAndCommitInIncorrectOrder) {
// This test aims to check the scenario where SetSurroundingText event is
// received from application later than receiving delete/commit event from
// server.
// 1. Set CommitString as a initial state. Cursor is between "Commit" and
// "String".
input_method_context_->SetSurroundingText(u"CommitString", gfx::Range(0, 12),
gfx::Range(6), absl::nullopt,
absl::nullopt);
connection_->Flush();
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
u"CommitString");
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
gfx::Range(6));
// 2. Delete surrounding text for "Commit" received.
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
auto* text_input = server->text_input_manager_v1()->text_input();
Mock::VerifyAndClearExpectations(text_input);
zwp_text_input_v1_send_delete_surrounding_text(text_input->resource(), 0,
6);
});
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
u"String");
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
gfx::Range(0));
// 3. Commit for "Updated" received.
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
auto* text_input = server->text_input_manager_v1()->text_input();
Mock::VerifyAndClearExpectations(text_input);
zwp_text_input_v1_send_commit_string(
server->text_input_manager_v1()->text_input()->resource(),
server->GetNextSerial(), "Updated");
});
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
u"UpdatedString");
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
gfx::Range(7));
// 4. Set surrounding text for step 2. Ideally this should be sent before step
// 3.
input_method_context_->SetSurroundingText(
u"String", gfx::Range(0, 6), gfx::Range(0), absl::nullopt, absl::nullopt);
connection_->Flush();
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
u"UpdatedString");
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
gfx::Range(7));
// 5. Set surrounding text for step 3.
input_method_context_->SetSurroundingText(u"UpdatedString", gfx::Range(0, 13),
gfx::Range(7), absl::nullopt,
absl::nullopt);
connection_->Flush();
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
u"UpdatedString");
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
gfx::Range(7));
}
TEST_P(WaylandInputMethodContextTest, SetContentType) {
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
EXPECT_CALL(
*server->text_input_extension_v1()->extended_text_input(),
SetInputType(
ZCR_EXTENDED_TEXT_INPUT_V1_INPUT_TYPE_URL,
ZCR_EXTENDED_TEXT_INPUT_V1_INPUT_MODE_DEFAULT,
ZCR_EXTENDED_TEXT_INPUT_V1_INPUT_FLAGS_AUTOCOMPLETE_ON,
ZCR_EXTENDED_TEXT_INPUT_V1_LEARNING_MODE_ENABLED,
ZCR_EXTENDED_TEXT_INPUT_V1_INLINE_COMPOSITION_SUPPORT_SUPPORTED))
.Times(1);
});
input_method_context_->SetContentType(TEXT_INPUT_TYPE_URL,
TEXT_INPUT_MODE_DEFAULT,
TEXT_INPUT_FLAG_AUTOCOMPLETE_ON,
/*should_do_learning=*/true,
/*can_compose_inline=*/true);
connection_->Flush();
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
Mock::VerifyAndClearExpectations(
server->text_input_extension_v1()->extended_text_input());
});
}
TEST_P(WaylandInputMethodContextTest, SetContentTypeWithoutLearning) {
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
EXPECT_CALL(
*server->text_input_extension_v1()->extended_text_input(),
SetInputType(
ZCR_EXTENDED_TEXT_INPUT_V1_INPUT_TYPE_URL,
ZCR_EXTENDED_TEXT_INPUT_V1_INPUT_MODE_DEFAULT,
ZCR_EXTENDED_TEXT_INPUT_V1_INPUT_FLAGS_AUTOCOMPLETE_ON,
ZCR_EXTENDED_TEXT_INPUT_V1_LEARNING_MODE_DISABLED,
ZCR_EXTENDED_TEXT_INPUT_V1_INLINE_COMPOSITION_SUPPORT_SUPPORTED))
.Times(1);
});
input_method_context_->SetContentType(TEXT_INPUT_TYPE_URL,
TEXT_INPUT_MODE_DEFAULT,
TEXT_INPUT_FLAG_AUTOCOMPLETE_ON,
/*should_do_learning=*/false,
/*can_compose_inline=*/true);
connection_->Flush();
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
Mock::VerifyAndClearExpectations(
server->text_input_extension_v1()->extended_text_input());
});
}
TEST_P(WaylandInputMethodContextTest,
SetContentTypeWithoutInlineCompositionSupport) {
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
EXPECT_CALL(
*server->text_input_extension_v1()->extended_text_input(),
SetInputType(
ZCR_EXTENDED_TEXT_INPUT_V1_INPUT_TYPE_URL,
ZCR_EXTENDED_TEXT_INPUT_V1_INPUT_MODE_DEFAULT,
ZCR_EXTENDED_TEXT_INPUT_V1_INPUT_FLAGS_AUTOCOMPLETE_ON,
ZCR_EXTENDED_TEXT_INPUT_V1_LEARNING_MODE_ENABLED,
ZCR_EXTENDED_TEXT_INPUT_V1_INLINE_COMPOSITION_SUPPORT_UNSUPPORTED))
.Times(1);
});
input_method_context_->SetContentType(TEXT_INPUT_TYPE_URL,
TEXT_INPUT_MODE_DEFAULT,
TEXT_INPUT_FLAG_AUTOCOMPLETE_ON,
/*should_do_learning=*/true,
/*can_compose_inline=*/false);
connection_->Flush();
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
Mock::VerifyAndClearExpectations(
server->text_input_extension_v1()->extended_text_input());
});
}
TEST_P(WaylandInputMethodContextTest, OnPreeditChanged) {
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
zwp_text_input_v1_send_preedit_string(
server->text_input_manager_v1()->text_input()->resource(),
server->GetNextSerial(), "PreeditString", "");
});
EXPECT_TRUE(input_method_context_delegate_->was_on_preedit_changed_called());
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
u"PreeditString");
EXPECT_EQ(input_method_context_->predicted_state_for_testing().composition,
gfx::Range(0, 13));
}
TEST_P(WaylandInputMethodContextTest, OnCommit) {
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
zwp_text_input_v1_send_commit_string(
server->text_input_manager_v1()->text_input()->resource(),
server->GetNextSerial(), "CommitString");
});
EXPECT_TRUE(input_method_context_delegate_->was_on_commit_called());
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
u"CommitString");
// On commit string, selection is placed next to the last character unless the
// cursor position is specified by OnCursorPosition.
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
gfx::Range(12));
EXPECT_EQ(input_method_context_->predicted_state_for_testing().composition,
gfx::Range(0));
}
// TODO(1353668): WaylandInputMethodContext::OnCursorPosition sets
// |pending_keep_selection| only on lacros. That's the reason why this test
// doesn't pass on Linux. We need to clarify that.
#if BUILDFLAG(IS_CHROMEOS_LACROS)
#define MAYBE(x) x
#else
#define MAYBE(x) DISABLED_##x
#endif
TEST_P(WaylandInputMethodContextTest, MAYBE(OnConfirmCompositionText)) {
constexpr char16_t text[] = u"ab😀cあdef";
constexpr gfx::Range range(5, 6); // あ is selected.
// SetSurroundingText should be called in UTF-8.
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
SetSurroundingText("ab😀cあdef", gfx::Range(7, 10)));
});
input_method_context_->SetSurroundingText(text, gfx::Range(0, 9), range,
absl::nullopt, absl::nullopt);
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
text);
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
range);
connection_->Flush();
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
auto* text_input = server->text_input_manager_v1()->text_input();
Mock::VerifyAndClearExpectations(text_input);
const auto sent_range = GetParam().use_ime_keep_selection_fix
? gfx::Range(10, 7)
: gfx::Range(7, 10);
zwp_text_input_v1_send_cursor_position(
text_input->resource(), sent_range.start(), sent_range.end());
zwp_text_input_v1_send_commit_string(text_input->resource(), 0,
"ab😀cあdef");
});
EXPECT_TRUE(
input_method_context_delegate_->was_on_confirm_composition_text_called());
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
text);
// Cursor position is set to `range` position explicitly by OnCursorPosition.
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
range);
EXPECT_EQ(input_method_context_->predicted_state_for_testing().composition,
gfx::Range(0));
}
TEST_P(WaylandInputMethodContextTest,
MAYBE(OnConfirmCompositionTextForLongRange)) {
const std::u16string text(5000, u'あ');
constexpr gfx::Range range(4000, 4500);
// Text longer than 4000 bytes is trimmed to meet the limitation.
// Selection range is also adjusted by the trimmed text before sendin to Exo.
const std::string kExpectedSentText(
base::UTF16ToUTF8(std::u16string(1332, u'あ')));
constexpr gfx::Range kExpectedSentRange(1248, 2748);
// SetSurroundingText should be called in UTF-8.
PostToServerAndWait([kExpectedSentText, kExpectedSentRange](
wl::TestWaylandServerThread* server) {
EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
SetSurroundingText(kExpectedSentText, kExpectedSentRange));
});
input_method_context_->SetSurroundingText(text, gfx::Range(0, 5000), range,
absl::nullopt, absl::nullopt);
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
text);
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
range);
connection_->Flush();
PostToServerAndWait([kExpectedSentText, kExpectedSentRange](
wl::TestWaylandServerThread* server) {
auto* text_input = server->text_input_manager_v1()->text_input();
Mock::VerifyAndClearExpectations(text_input);
const auto expected_sent_range =
GetParam().use_ime_keep_selection_fix
? gfx::Range(kExpectedSentRange.end(), kExpectedSentRange.start())
: kExpectedSentRange;
zwp_text_input_v1_send_cursor_position(text_input->resource(),
expected_sent_range.start(),
expected_sent_range.end());
zwp_text_input_v1_send_commit_string(text_input->resource(), 0,
kExpectedSentText.c_str());
});
EXPECT_TRUE(
input_method_context_delegate_->was_on_confirm_composition_text_called());
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
text);
// Cursor position is set to `range` position explicitly by OnCursorPosition.
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
range);
EXPECT_EQ(input_method_context_->predicted_state_for_testing().composition,
gfx::Range(0));
}
TEST_P(WaylandInputMethodContextTest, OnSetPreeditRegion_Success) {
constexpr char16_t text[] = u"abcあdef";
const gfx::Range range(3, 4); // あ is selected.
// SetSurroundingText should be called in UTF-8.
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
SetSurroundingText("abcあdef", gfx::Range(3, 6)));
});
input_method_context_->SetSurroundingText(text, gfx::Range(0, 7), range,
absl::nullopt, absl::nullopt);
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
text);
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
range);
connection_->Flush();
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
Mock::VerifyAndClearExpectations(
server->text_input_manager_v1()->text_input());
// Specify "cあd" as a new preedit region.
zcr_extended_text_input_v1_send_set_preedit_region(
server->text_input_extension_v1()->extended_text_input()->resource(),
-4, 5);
});
EXPECT_TRUE(
input_method_context_delegate_->was_on_set_preedit_region_called());
EXPECT_EQ(input_method_context_->predicted_state_for_testing().composition,
gfx::Range(2, 5));
}
TEST_P(WaylandInputMethodContextTest, OnSetPreeditRegion_NoSurroundingText) {
// If no surrounding text is set yet, set_preedit_region would fail.
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
zcr_extended_text_input_v1_send_set_preedit_region(
server->text_input_extension_v1()->extended_text_input()->resource(),
-1, 3);
});
EXPECT_FALSE(
input_method_context_delegate_->was_on_set_preedit_region_called());
}
// The range is represented in UTF-16 code points, so it is independent from
// grapheme clusters.
TEST_P(WaylandInputMethodContextTest,
OnSetPreeditRegion_GraphemeClusterIndependeceSimple) {
// Single code point representation of é.
constexpr char16_t u16_text[] = u"\u00E9";
constexpr char u8_text[] = "\xC3\xA9"; // In UTF-8 encode.
constexpr gfx::Range u16_range(0, 1);
constexpr gfx::Range u8_range(0, 2);
// Double check the text has one grapheme cluster.
ASSERT_EQ(1u, CountGraphemeCluster(u16_text));
// SetSurroundingText should be called in UTF-8.
PostToServerAndWait([u8_range, u8_text](wl::TestWaylandServerThread* server) {
EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
SetSurroundingText(u8_text, u8_range));
});
input_method_context_->SetSurroundingText(
u16_text, gfx::Range(0, 1), u16_range, absl::nullopt, absl::nullopt);
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
u16_text);
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
u16_range);
connection_->Flush();
PostToServerAndWait([u8_range](wl::TestWaylandServerThread* server) {
Mock::VerifyAndClearExpectations(
server->text_input_manager_v1()->text_input());
// Specify the whole range as a new preedit region.
zcr_extended_text_input_v1_send_set_preedit_region(
server->text_input_extension_v1()->extended_text_input()->resource(),
-static_cast<int32_t>(u8_range.length()), u8_range.length());
});
EXPECT_TRUE(
input_method_context_delegate_->was_on_set_preedit_region_called());
EXPECT_EQ(input_method_context_->predicted_state_for_testing().composition,
u16_range);
}
TEST_P(WaylandInputMethodContextTest,
OnSetPreeditRegion_GraphemeClusterIndependeceCombined) {
// Decomposed code point representation of é.
constexpr char16_t u16_text[] = u"\u0065\u0301";
constexpr char u8_text[] = "\x65\xCC\x81"; // In UTF-8 encode.
constexpr gfx::Range u16_range(0, 2);
constexpr gfx::Range u8_range(0, 3);
// Double check the text has one grapheme cluster.
ASSERT_EQ(1u, CountGraphemeCluster(u16_text));
// SetSurroundingText should be called in UTF-8.
PostToServerAndWait([u8_range, u8_text](wl::TestWaylandServerThread* server) {
EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
SetSurroundingText(u8_text, u8_range));
});
input_method_context_->SetSurroundingText(
u16_text, gfx::Range(0, 2), u16_range, absl::nullopt, absl::nullopt);
EXPECT_EQ(
input_method_context_->predicted_state_for_testing().surrounding_text,
u16_text);
EXPECT_EQ(input_method_context_->predicted_state_for_testing().selection,
u16_range);
connection_->Flush();
PostToServerAndWait([u8_range](wl::TestWaylandServerThread* server) {
Mock::VerifyAndClearExpectations(
server->text_input_manager_v1()->text_input());
// Specify the whole range as a new preedit region.
zcr_extended_text_input_v1_send_set_preedit_region(
server->text_input_extension_v1()->extended_text_input()->resource(),
-static_cast<int32_t>(u8_range.length()), u8_range.length());
});
EXPECT_TRUE(
input_method_context_delegate_->was_on_set_preedit_region_called());
EXPECT_EQ(input_method_context_->predicted_state_for_testing().composition,
u16_range);
}
TEST_P(WaylandInputMethodContextTest, OnClearGrammarFragments) {
input_method_context_->OnClearGrammarFragments(gfx::Range(1, 5));
wl::SyncDisplay(connection_->display_wrapper(), *connection_->display());
EXPECT_TRUE(
input_method_context_delegate_->was_on_clear_grammar_fragments_called());
}
TEST_P(WaylandInputMethodContextTest, OnAddGrammarFragments) {
input_method_context_->OnAddGrammarFragment(
ui::GrammarFragment(gfx::Range(1, 5), "test"));
wl::SyncDisplay(connection_->display_wrapper(), *connection_->display());
EXPECT_TRUE(
input_method_context_delegate_->was_on_add_grammar_fragment_called());
}
TEST_P(WaylandInputMethodContextTest, OnSetAutocorrectRange) {
input_method_context_->OnSetAutocorrectRange(gfx::Range(1, 5));
wl::SyncDisplay(connection_->display_wrapper(), *connection_->display());
EXPECT_TRUE(
input_method_context_delegate_->was_on_set_autocorrect_range_called());
}
TEST_P(WaylandInputMethodContextTest, OnSetVirtualKeyboardOccludedBounds) {
constexpr gfx::Rect kBounds(10, 20, 300, 400);
input_method_context_->OnSetVirtualKeyboardOccludedBounds(kBounds);
wl::SyncDisplay(connection_->display_wrapper(), *connection_->display());
EXPECT_EQ(input_method_context_delegate_->virtual_keyboard_bounds(), kBounds);
}
TEST_P(WaylandInputMethodContextTest,
OnSetVirtualKeyboardOccludedBoundsUpdatesPastTextInputClients) {
auto client1 = std::make_unique<MockTextInputClient>(TEXT_INPUT_TYPE_TEXT);
auto client2 = std::make_unique<MockTextInputClient>(TEXT_INPUT_TYPE_URL);
input_method_context_->WillUpdateFocus(client1.get(), client2.get());
input_method_context_->UpdateFocus(true, client1->GetTextInputType(),
client2->GetTextInputType(),
ui::TextInputClient::FOCUS_REASON_OTHER);
input_method_context_->WillUpdateFocus(client2.get(), nullptr);
input_method_context_->UpdateFocus(false, client2->GetTextInputType(),
ui::TEXT_INPUT_TYPE_NONE,
ui::TextInputClient::FOCUS_REASON_NONE);
// Clients should get further bounds updates.
constexpr gfx::Rect kBounds(10, 20, 300, 400);
EXPECT_CALL(*client1, EnsureCaretNotInRect(kBounds));
EXPECT_CALL(*client2, EnsureCaretNotInRect(kBounds));
input_method_context_->OnSetVirtualKeyboardOccludedBounds(kBounds);
wl::SyncDisplay(connection_->display_wrapper(), *connection_->display());
Mock::VerifyAndClearExpectations(client1.get());
Mock::VerifyAndClearExpectations(client2.get());
// Clients should get the empty bounds then be removed.
const gfx::Rect kBoundsEmpty(0, 30, 0, 0);
EXPECT_CALL(*client1, EnsureCaretNotInRect(kBoundsEmpty));
EXPECT_CALL(*client2, EnsureCaretNotInRect(kBoundsEmpty));
input_method_context_->OnSetVirtualKeyboardOccludedBounds(kBoundsEmpty);
wl::SyncDisplay(connection_->display_wrapper(), *connection_->display());
Mock::VerifyAndClearExpectations(client1.get());
Mock::VerifyAndClearExpectations(client2.get());
// Verify client no longer gets bounds updates.
const gfx::Rect kBounds2(0, 40, 100, 200);
EXPECT_CALL(*client1, EnsureCaretNotInRect).Times(0);
EXPECT_CALL(*client2, EnsureCaretNotInRect).Times(0);
input_method_context_->OnSetVirtualKeyboardOccludedBounds(kBounds2);
wl::SyncDisplay(connection_->display_wrapper(), *connection_->display());
Mock::VerifyAndClearExpectations(client1.get());
Mock::VerifyAndClearExpectations(client2.get());
}
TEST_P(WaylandInputMethodContextTest,
OnSetVirtualKeyboardOccludedBoundsWithDeletedPastTextInputClient) {
auto client = std::make_unique<MockTextInputClient>(TEXT_INPUT_TYPE_TEXT);
input_method_context_->WillUpdateFocus(client.get(), nullptr);
input_method_context_->UpdateFocus(false, client->GetTextInputType(),
ui::TEXT_INPUT_TYPE_NONE,
ui::TextInputClient::FOCUS_REASON_NONE);
const gfx::Rect kBounds(10, 20, 300, 400);
EXPECT_CALL(*client, EnsureCaretNotInRect(kBounds));
input_method_context_->OnSetVirtualKeyboardOccludedBounds(kBounds);
wl::SyncDisplay(connection_->display_wrapper(), *connection_->display());
Mock::VerifyAndClearExpectations(client.get());
client.reset();
input_method_context_->OnSetVirtualKeyboardOccludedBounds(kBounds);
wl::SyncDisplay(connection_->display_wrapper(), *connection_->display());
}
TEST_P(WaylandInputMethodContextTest, DisplayVirtualKeyboard) {
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
ShowInputPanel())
.Times(1);
});
EXPECT_TRUE(input_method_context_->DisplayVirtualKeyboard());
connection_->Flush();
wl::SyncDisplay(connection_->display_wrapper(), *connection_->display());
}
TEST_P(WaylandInputMethodContextTest, DismissVirtualKeyboard) {
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
EXPECT_CALL(*server->text_input_manager_v1()->text_input(),
HideInputPanel());
});
input_method_context_->DismissVirtualKeyboard();
connection_->Flush();
wl::SyncDisplay(connection_->display_wrapper(), *connection_->display());
}
TEST_P(WaylandInputMethodContextTest, UpdateVirtualKeyboardState) {
EXPECT_FALSE(input_method_context_->IsKeyboardVisible());
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
zwp_text_input_v1_send_input_panel_state(
server->text_input_manager_v1()->text_input()->resource(), 1);
});
EXPECT_TRUE(input_method_context_->IsKeyboardVisible());
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
zwp_text_input_v1_send_input_panel_state(
server->text_input_manager_v1()->text_input()->resource(), 0);
});
EXPECT_FALSE(input_method_context_->IsKeyboardVisible());
}
class WaylandInputMethodContextNoKeyboardTest
: public WaylandInputMethodContextTest {
public:
void SetUp() override {
// Call the skip base implementation to avoid setting up the keyboard.
WaylandTest::SetUp();
ASSERT_FALSE(connection_->seat()->keyboard());
SetUpInternal();
}
};
INSTANTIATE_TEST_SUITE_P(TextInputExtensionLatestVersion,
WaylandInputMethodContextNoKeyboardTest,
::testing::Values(wl::ServerConfig{}));
TEST_P(WaylandInputMethodContextNoKeyboardTest, ActivateDeactivate) {
const uint32_t surface_id = window_->root_surface()->get_surface_id();
// Because there is no keyboard, Activate is called as soon as InputMethod's
// TextInputClient focus is met.
InSequence s;
PostToServerAndWait([id = surface_id](wl::TestWaylandServerThread* server) {
auto* zwp_text_input = server->text_input_manager_v1()->text_input();
EXPECT_CALL(*zwp_text_input,
Activate(server->GetObject<wl::MockSurface>(id)->resource()));
EXPECT_CALL(*zwp_text_input, ShowInputPanel());
});
input_method_context_->UpdateFocus(true, ui::TEXT_INPUT_TYPE_NONE,
ui::TEXT_INPUT_TYPE_TEXT,
ui::TextInputClient::FOCUS_REASON_OTHER);
connection_->Flush();
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
auto* zwp_text_input = server->text_input_manager_v1()->text_input();
Mock::VerifyAndClearExpectations(zwp_text_input);
EXPECT_CALL(*zwp_text_input, HideInputPanel());
EXPECT_CALL(*zwp_text_input, Deactivate());
});
input_method_context_->UpdateFocus(false, ui::TEXT_INPUT_TYPE_TEXT,
ui::TEXT_INPUT_TYPE_NONE,
ui::TextInputClient::FOCUS_REASON_NONE);
connection_->Flush();
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
Mock::VerifyAndClearExpectations(
server->text_input_manager_v1()->text_input());
});
}
TEST_P(WaylandInputMethodContextNoKeyboardTest, UpdateFocusBetweenTextFields) {
const uint32_t surface_id = window_->root_surface()->get_surface_id();
// Because there is no keyboard, Activate is called as soon as InputMethod's
// TextInputClient focus is met.
InSequence s;
PostToServerAndWait([id = surface_id](wl::TestWaylandServerThread* server) {
auto* zwp_text_input = server->text_input_manager_v1()->text_input();
EXPECT_CALL(*zwp_text_input,
Activate(server->GetObject<wl::MockSurface>(id)->resource()));
EXPECT_CALL(*zwp_text_input, ShowInputPanel());
});
input_method_context_->UpdateFocus(true, ui::TEXT_INPUT_TYPE_NONE,
ui::TEXT_INPUT_TYPE_TEXT,
ui::TextInputClient::FOCUS_REASON_OTHER);
connection_->Flush();
PostToServerAndWait([id = surface_id](wl::TestWaylandServerThread* server) {
auto* zwp_text_input = server->text_input_manager_v1()->text_input();
Mock::VerifyAndClearExpectations(zwp_text_input);
// Make sure virtual keyboard is not unnecessarily hidden.
EXPECT_CALL(*zwp_text_input, HideInputPanel()).Times(0);
EXPECT_CALL(*zwp_text_input, Deactivate());
EXPECT_CALL(*zwp_text_input,
Activate(server->GetObject<wl::MockSurface>(id)->resource()));
EXPECT_CALL(*zwp_text_input, ShowInputPanel()).Times(0);
});
input_method_context_->UpdateFocus(false, ui::TEXT_INPUT_TYPE_TEXT,
ui::TEXT_INPUT_TYPE_TEXT,
ui::TextInputClient::FOCUS_REASON_OTHER);
connection_->Flush();
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
Mock::VerifyAndClearExpectations(
server->text_input_manager_v1()->text_input());
});
}
} // namespace ui