blob: 3e283bc8f4f908c24ad559bb0a182f3766476675 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/crosapi/input_method_test_interface_ash.h"
#include <utility>
#include "base/strings/utf_offset_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "chromeos/crosapi/cpp/input_method_test_interface_constants.h"
#include "ui/base/ime/ash/ime_bridge.h"
#include "ui/base/ime/ash/input_method_ash.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/keycodes/dom/dom_code.h"
namespace crosapi {
namespace {
ash::InputMethodAsh* GetTextInputTarget() {
const ash::IMEBridge* bridge = ash::IMEBridge::Get();
if (!bridge)
return nullptr;
ash::TextInputTarget* handler = bridge->GetInputContextHandler();
if (!handler)
return nullptr;
// Guaranteed to be an ash::InputMethodAsh*.
return static_cast<ash::InputMethodAsh*>(handler->GetInputMethod());
}
void OverrideTextInputMethod(ash::TextInputMethod* text_input_method) {
ash::IMEBridge* bridge = ash::IMEBridge::Get();
if (!bridge) {
return;
}
bridge->SetCurrentEngineHandler(text_input_method);
}
bool HasCapability(const base::StringPiece capability) {
return capability == kInputMethodTestCapabilitySendKeyModifiers ||
capability == kInputMethodTestCapabilityConfirmComposition ||
capability == kInputMethodTestCapabilityAlwaysConfirmComposition ||
capability == kInputMethodTestCapabilityDeleteSurroundingText;
}
} // namespace
FakeTextInputMethod::FakeTextInputMethod() = default;
FakeTextInputMethod::~FakeTextInputMethod() = default;
void FakeTextInputMethod::Focus(const InputContext& input_context) {
for (auto& observer : observers_) {
observer.OnFocus();
}
}
ui::VirtualKeyboardController*
FakeTextInputMethod::GetVirtualKeyboardController() const {
return nullptr;
}
bool FakeTextInputMethod::IsReadyForTesting() {
return true;
}
void FakeTextInputMethod::ProcessKeyEvent(const ui::KeyEvent& key_event,
KeyEventDoneCallback callback) {
++current_key_event_id_;
pending_key_event_callbacks_.emplace(current_key_event_id_,
std::move(callback));
}
void FakeTextInputMethod::SetSurroundingText(const std::u16string& text,
const gfx::Range selection_range,
uint32_t offset_pos) {
// TODO(b/238838841): Handle `offset_pos`.
// Don't send surrounding text changed event if the surrounding text hasn't
// changed.
if (previous_surrounding_text_ == text &&
previous_selection_range_ == selection_range) {
return;
}
previous_surrounding_text_ = text;
previous_selection_range_ = selection_range;
for (auto& observer : observers_) {
observer.OnSurroundingTextChanged(text, selection_range);
}
}
void FakeTextInputMethod::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void FakeTextInputMethod::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
uint64_t FakeTextInputMethod::GetCurrentKeyEventId() const {
return current_key_event_id_;
}
void FakeTextInputMethod::KeyEventHandled(uint64_t key_event_id, bool handled) {
if (const auto it = pending_key_event_callbacks_.find(key_event_id);
it != pending_key_event_callbacks_.end()) {
std::move(it->second)
.Run(handled ? ui::ime::KeyEventHandledState::kHandledByIME
: ui::ime::KeyEventHandledState::kNotHandled);
pending_key_event_callbacks_.erase(it);
}
}
InputMethodTestInterfaceAsh::InputMethodTestInterfaceAsh()
: text_input_target_(GetTextInputTarget()) {
DCHECK(text_input_target_);
OverrideTextInputMethod(&fake_text_input_method_);
text_input_method_observation_.Observe(&fake_text_input_method_);
}
InputMethodTestInterfaceAsh::~InputMethodTestInterfaceAsh() {
OverrideTextInputMethod(nullptr);
}
void InputMethodTestInterfaceAsh::WaitForFocus(WaitForFocusCallback callback) {
// If `GetTextInputClient` is not null, then it's already focused.
if (text_input_target_->GetTextInputClient()) {
std::move(callback).Run();
return;
}
// `callback` is assumed to outlive this class.
focus_callbacks_.AddUnsafe(std::move(callback));
}
void InputMethodTestInterfaceAsh::CommitText(const std::string& text,
CommitTextCallback callback) {
text_input_target_->CommitText(
base::UTF8ToUTF16(text),
ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
std::move(callback).Run();
}
void InputMethodTestInterfaceAsh::SetComposition(
const std::string& text,
uint32_t index,
SetCompositionCallback callback) {
ui::CompositionText composition;
composition.text = base::UTF8ToUTF16(text);
text_input_target_->UpdateCompositionText(composition, index,
/*visible=*/true);
std::move(callback).Run();
}
void InputMethodTestInterfaceAsh::SendKeyEvent(mojom::KeyEventPtr event,
SendKeyEventCallback callback) {
ui::KeyEvent key_press(
event->type == mojom::KeyEventType::kKeyPress ? ui::ET_KEY_PRESSED
: ui::ET_KEY_RELEASED,
static_cast<ui::KeyboardCode>(event->key_code),
static_cast<ui::DomCode>(event->dom_code), event->flags,
static_cast<ui::DomKey>(event->dom_key), ui::EventTimeForNow());
text_input_target_->SendKeyEvent(&key_press);
std::move(callback).Run(fake_text_input_method_.GetCurrentKeyEventId());
}
void InputMethodTestInterfaceAsh::KeyEventHandled(
uint64_t key_event_id,
bool handled,
KeyEventHandledCallback callback) {
fake_text_input_method_.KeyEventHandled(key_event_id, handled);
std::move(callback).Run();
}
void InputMethodTestInterfaceAsh::WaitForNextSurroundingTextChange(
WaitForNextSurroundingTextChangeCallback callback) {
// If there are no queued surrounding text changes, then save the callback to
// be called by the next surrounding text change. Otherwise, pop the first
// pending surrounding text and pass it to the callback.
if (surrounding_text_changes_.empty()) {
DCHECK(surrounding_text_change_callback_.is_null());
// `callback` is assumed to outlive this class.
surrounding_text_change_callback_ = std::move(callback);
return;
}
const auto& [text, selection_range] = surrounding_text_changes_.front();
surrounding_text_changes_.pop();
std::move(callback).Run(text, selection_range);
}
void InputMethodTestInterfaceAsh::HasCapabilities(
const std::vector<std::string>& capabilities,
HasCapabilitiesCallback callback) {
for (const std::string& capability : capabilities) {
if (!HasCapability(capability)) {
std::move(callback).Run(false);
return;
}
}
std::move(callback).Run(true);
}
void InputMethodTestInterfaceAsh::ConfirmComposition(
ConfirmCompositionCallback callback) {
text_input_target_->ConfirmComposition(/*reset_engine=*/false);
std::move(callback).Run();
}
void InputMethodTestInterfaceAsh::DeleteSurroundingText(
uint32_t length_before_selection,
uint32_t length_after_selection,
DeleteSurroundingTextCallback callback) {
text_input_target_->DeleteSurroundingText(length_before_selection,
length_after_selection);
std::move(callback).Run();
}
void InputMethodTestInterfaceAsh::OnFocus() {
focus_callbacks_.Notify();
}
void InputMethodTestInterfaceAsh::OnSurroundingTextChanged(
const std::u16string& text,
const gfx::Range& selection_range) {
std::vector<size_t> offsets = {selection_range.start(),
selection_range.end()};
const std::string text_utf8 =
base::UTF16ToUTF8AndAdjustOffsets(text, &offsets);
const gfx::Range selection_range_utf8(offsets[0], offsets[1]);
// If there is no pending WaitForNextSurroundingTextChange callback, queue the
// surrounding text change to be returned by the next
// WaitForNextSurroundingTextChange call. Otherwise, resolve the pending
// callback with the current surrounding text change.
if (surrounding_text_change_callback_.is_null()) {
surrounding_text_changes_.push({text_utf8, selection_range_utf8});
return;
}
std::move(surrounding_text_change_callback_)
.Run(text_utf8, selection_range_utf8);
}
} // namespace crosapi