#include "ui/base/ime/ash/input_method_ash.h"
#include <stddef.h>
#include <algorithm>
#include <cstring>
#include <set>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>
#include "ash/constants/ash_features.h"
#include "base/check.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/i18n/char_iterator.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/third_party/icu/icu_utf.h"
#include "base/time/default_clock.h"
#include "ui/base/ime/ash/ime_bridge.h"
#include "ui/base/ime/ash/ime_keyboard.h"
#include "ui/base/ime/ash/input_method_manager.h"
#include "ui/base/ime/ash/text_input_method.h"
#include "ui/base/ime/ash/typing_session_manager.h"
#include "ui/base/ime/composition_text.h"
#include "ui/base/ime/constants.h"
#include "ui/base/ime/events.h"
#include "ui/base/ime/ime_key_event_dispatcher.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/base/ui_base_features.h"
#include "ui/events/event.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/events/ozone/events_ozone.h"
#include "ui/gfx/geometry/rect.h"
namespace ash {
namespace {
using ::ui::CompositionText;
using ::ui::TextInputClient;
template <typename T>
T ConvertTextInputFlagToEnum(int flags, int flag_on_value, int flag_off_value) {
if (flags & flag_on_value) {
return T::kEnabled;
}
if (flags & flag_off_value) {
return T::kDisabled;
}
return T::kUnspecified;
}
AutocapitalizationMode ConvertAutocapitalizationMode(int flags) {
if (flags & ui::TEXT_INPUT_FLAG_AUTOCAPITALIZE_NONE) {
return AutocapitalizationMode::kNone;
}
if (flags & ui::TEXT_INPUT_FLAG_AUTOCAPITALIZE_CHARACTERS) {
return AutocapitalizationMode::kCharacters;
}
if (flags & ui::TEXT_INPUT_FLAG_AUTOCAPITALIZE_WORDS) {
return AutocapitalizationMode::kWords;
}
if (flags & ui::TEXT_INPUT_FLAG_AUTOCAPITALIZE_SENTENCES) {
return AutocapitalizationMode::kSentences;
}
return AutocapitalizationMode::kUnspecified;
}
}
TextInputMethod* GetEngine() {
auto* bridge = IMEBridge::Get();
return bridge ? bridge->GetCurrentEngineHandler() : nullptr;
}
InputMethodAsh::InputMethodAsh(
ui::ImeKeyEventDispatcher* ime_key_event_dispatcher)
: InputMethodBase(ime_key_event_dispatcher),
typing_session_manager_(base::DefaultClock::GetInstance()) {
ResetContext();
}
InputMethodAsh::~InputMethodAsh() {
ConfirmComposition( true);
OnInputMethodChanged();
if (IMEBridge::Get() && IMEBridge::Get()->GetInputContextHandler() == this) {
IMEBridge::Get()->SetInputContextHandler(nullptr);
}
typing_session_manager_.EndAndRecordSession();
}
InputMethodAsh::PendingSetCompositionRange::PendingSetCompositionRange(
const gfx::Range& range,
const std::vector<ui::ImeTextSpan>& text_spans)
: range(range), text_spans(text_spans) {}
InputMethodAsh::PendingSetCompositionRange::PendingSetCompositionRange(
const PendingSetCompositionRange& other) = default;
InputMethodAsh::PendingSetCompositionRange::~PendingSetCompositionRange() =
default;
InputMethodAsh::PendingAutocorrectRange::PendingAutocorrectRange(
const gfx::Range& range,
TextInputTarget::SetAutocorrectRangeDoneCallback callback)
: range(range), callback(std::move(callback)) {}
InputMethodAsh::PendingAutocorrectRange::~PendingAutocorrectRange() = default;
ui::EventDispatchDetails InputMethodAsh::DispatchKeyEvent(ui::KeyEvent* event) {
DCHECK(!(event->flags() & ui::EF_IS_SYNTHESIZED));
auto* manager = input_method::InputMethodManager::Get();
if (manager) {
input_method::ImeKeyboard* keyboard = manager->GetImeKeyboard();
if (keyboard && event->type() == ui::EventType::kKeyPressed &&
event->key_code() != ui::VKEY_CAPITAL &&
keyboard->IsCapsLockEnabled() != event->IsCapsLockOn()) {
keyboard->SetCapsLockEnabled(event->IsCapsLockOn());
}
input_method::InputMethodManager::State* state =
manager->GetActiveIMEState().get();
if (event->type() == ui::EventType::kKeyPressed && state) {
bool language_input_key = true;
switch (event->key_code()) {
case ui::VKEY_CONVERT:
state->ChangeInputMethodToJpIme();
break;
case ui::VKEY_NONCONVERT:
state->ChangeInputMethodToJpKeyboard();
break;
case ui::VKEY_DBE_SBCSCHAR:
case ui::VKEY_DBE_DBCSCHAR:
state->ToggleInputMethodForJpIme();
break;
default:
language_input_key = false;
break;
}
if (language_input_key) {
return DispatchKeyEventPostIME(event);
}
}
}
if (GetTextInputClient() == nullptr) {
return DispatchKeyEventPostIME(event);
}
if (IsPasswordOrNoneInputFieldFocused() || !GetEngine()) {
if (event->type() == ui::EventType::kKeyPressed) {
if (ExecuteCharacterComposer(*event)) {
return ProcessKeyEventPostIME(
event, ui::ime::KeyEventHandledState::kHandledByIME,
true);
}
return ProcessUnfilteredKeyPressEvent(event);
}
return DispatchKeyEventPostIME(event);
}
dispatch_details_.reset();
handling_key_event_ = true;
GetEngine()->ProcessKeyEvent(
*event, base::BindOnce(&InputMethodAsh::ProcessKeyEventDone,
weak_ptr_factory_.GetWeakPtr(),
base::Owned(new ui::KeyEvent(*event))));
return dispatch_details_.value_or(ui::EventDispatchDetails());
}
void InputMethodAsh::ProcessKeyEventDone(
ui::KeyEvent* event,
ui::ime::KeyEventHandledState handled_state) {
DCHECK(event);
bool is_handled_by_char_composer = false;
if (event->type() == ui::EventType::kKeyPressed) {
if (handled_state != ui::ime::KeyEventHandledState::kNotHandled) {
character_composer_.Reset();
} else {
is_handled_by_char_composer = ExecuteCharacterComposer(*event);
if (!is_handled_by_char_composer &&
!ui::KeycodeConverter::IsDomKeyForModifier(event->GetDomKey())) {
ConfirmComposition( true);
}
}
}
if (event->type() == ui::EventType::kKeyPressed ||
event->type() == ui::EventType::kKeyReleased) {
ui::ime::KeyEventHandledState handled_state_to_process =
is_handled_by_char_composer
? ui::ime::KeyEventHandledState::kHandledByIME
: handled_state;
dispatch_details_ = ProcessKeyEventPostIME(event, handled_state_to_process,
false);
}
handling_key_event_ = false;
}
void InputMethodAsh::OnTextInputTypeChanged(TextInputClient* client) {
if (!IsTextInputClientFocused(client)) {
return;
}
UpdateContextFocusState();
TextInputMethod* engine = GetEngine();
if (engine) {
const TextInputMethod::InputContext context = GetInputContext();
engine->Blur();
engine->Focus(context);
}
OnCaretBoundsChanged(client);
InputMethodBase::OnTextInputTypeChanged(client);
}
void InputMethodAsh::OnCaretBoundsChanged(const TextInputClient* client) {
if (IsTextInputTypeNone() || !IsTextInputClientFocused(client)) {
return;
}
NotifyTextInputCaretBoundsChanged(client);
if (IsPasswordOrNoneInputFieldFocused()) {
return;
}
DCHECK(client == GetTextInputClient());
DCHECK(!IsTextInputTypeNone());
TextInputMethod* engine = GetEngine();
if (engine) {
engine->SetCaretBounds(client->GetCaretBounds());
}
IMECandidateWindowHandlerInterface* candidate_window =
IMEBridge::Get()->GetCandidateWindowHandler();
IMEAssistiveWindowHandlerInterface* assistive_window =
IMEBridge::Get()->GetAssistiveWindowHandler();
if (!candidate_window && !assistive_window) {
return;
}
const gfx::Rect caret_rect = client->GetCaretBounds();
gfx::Rect composition_bounds;
if (client->HasCompositionText()) {
client->GetCompositionCharacterBounds(0, &composition_bounds);
}
if (composition_bounds.IsEmpty()) {
composition_bounds = caret_rect;
}
if (candidate_window) {
candidate_window->SetCursorAndCompositionBounds(caret_rect,
composition_bounds);
}
if (assistive_window) {
Bounds bounds;
bounds.caret = caret_rect;
bounds.autocorrect = client->GetAutocorrectCharacterBounds();
assistive_window->SetBounds(bounds);
}
gfx::Range text_range;
gfx::Range selection_range;
std::u16string surrounding_text;
if (!client->GetTextRange(&text_range) ||
!client->GetTextFromRange(text_range, &surrounding_text) ||
!client->GetEditableSelectionRange(&selection_range)) {
previous_surrounding_text_.clear();
previous_selection_range_ = gfx::Range::InvalidRange();
return;
}
if (previous_selection_range_ == selection_range &&
previous_surrounding_text_ == surrounding_text) {
return;
}
previous_selection_range_ = selection_range;
previous_surrounding_text_ = surrounding_text;
if (!selection_range.IsValid()) {
return;
}
if (GetEngine()) {
const uint32_t offset = text_range.start();
DCHECK_GE(selection_range.start(), offset);
DCHECK_GE(selection_range.end(), offset);
const gfx::Range relative_selection_range(selection_range.start() - offset,
selection_range.end() - offset);
GetEngine()->SetSurroundingText(surrounding_text, relative_selection_range,
offset);
}
}
void InputMethodAsh::CancelComposition(const TextInputClient* client) {
if (!IsPasswordOrNoneInputFieldFocused() &&
IsTextInputClientFocused(client)) {
ResetContext();
}
}
bool InputMethodAsh::IsCandidatePopupOpen() const {
return false;
}
ui::VirtualKeyboardController* InputMethodAsh::GetVirtualKeyboardController() {
if (auto* engine = GetEngine()) {
if (auto* controller = engine->GetVirtualKeyboardController()) {
return controller;
}
}
return InputMethodBase::GetVirtualKeyboardController();
}
void InputMethodAsh::OnFocus() {
auto* bridge = IMEBridge::Get();
if (bridge) {
bridge->SetInputContextHandler(this);
}
}
void InputMethodAsh::OnBlur() {
if (IMEBridge::Get() && IMEBridge::Get()->GetInputContextHandler() == this) {
IMEBridge::Get()->SetInputContextHandler(nullptr);
}
}
void InputMethodAsh::OnWillChangeFocusedClient(TextInputClient* focused_before,
TextInputClient* focused) {
ConfirmComposition( true);
if (focused_before) {
focused_before->SetAutocorrectRange(gfx::Range());
}
if (GetEngine()) {
GetEngine()->Blur();
}
}
void InputMethodAsh::OnDidChangeFocusedClient(TextInputClient* focused_before,
TextInputClient* focused) {
UpdateContextFocusState();
if (GetEngine()) {
GetEngine()->Focus(GetInputContext());
}
OnCaretBoundsChanged(GetTextInputClient());
}
bool InputMethodAsh::SetCompositionRange(
uint32_t before,
uint32_t after,
const std::vector<ui::ImeTextSpan>& text_spans) {
TextInputClient* client = GetTextInputClient();
if (IsTextInputTypeNone()) {
return false;
}
typing_session_manager_.Heartbeat();
gfx::Range range;
if (!client->GetEditableSelectionRange(&range)) {
return false;
}
const gfx::Range composition_range(
range.start() >= before ? range.start() - before : 0,
range.end() + after);
gfx::Range text_range;
client->GetTextRange(&text_range);
if (!text_range.Contains(composition_range)) {
return false;
}
return SetComposingRange(composition_range.start(), composition_range.end(),
text_spans);
}
bool InputMethodAsh::SetComposingRange(
uint32_t start,
uint32_t end,
const std::vector<ui::ImeTextSpan>& text_spans) {
TextInputClient* client = GetTextInputClient();
if (IsTextInputTypeNone()) {
return false;
}
const auto ordered_range = std::minmax(start, end);
const gfx::Range composition_range(ordered_range.first, ordered_range.second);
auto non_empty_text_spans =
!text_spans.empty()
? text_spans
: std::vector<ui::ImeTextSpan>{ui::ImeTextSpan(
ui::ImeTextSpan::Type::kComposition,
0, composition_range.length())};
if (handling_key_event_) {
composition_changed_ = true;
pending_composition_range_ =
PendingSetCompositionRange{composition_range, non_empty_text_spans};
return true;
} else {
composing_text_ = true;
return client->SetCompositionFromExistingText(composition_range,
non_empty_text_spans);
}
}
gfx::Range InputMethodAsh::GetAutocorrectRange() {
if (IsTextInputTypeNone()) {
return gfx::Range();
}
return GetTextInputClient()->GetAutocorrectRange();
}
void InputMethodAsh::SetAutocorrectRange(
const gfx::Range& range,
SetAutocorrectRangeDoneCallback callback) {
if (IsTextInputTypeNone()) {
std::move(callback).Run(false);
return;
}
if (handling_key_event_) {
if (pending_autocorrect_range_) {
std::move(pending_autocorrect_range_->callback).Run(false);
}
pending_autocorrect_range_ =
std::make_unique<InputMethodAsh::PendingAutocorrectRange>(
range, std::move(callback));
} else {
std::move(callback).Run(GetTextInputClient()->SetAutocorrectRange(range));
}
}
std::optional<ui::GrammarFragment>
InputMethodAsh::GetGrammarFragmentAtCursor() {
if (IsTextInputTypeNone()) {
return std::nullopt;
}
return GetTextInputClient()->GetGrammarFragmentAtCursor();
}
bool InputMethodAsh::ClearGrammarFragments(const gfx::Range& range) {
if (IsTextInputTypeNone()) {
return false;
}
return GetTextInputClient()->ClearGrammarFragments(range);
}
bool InputMethodAsh::AddGrammarFragments(
const std::vector<ui::GrammarFragment>& fragments) {
if (IsTextInputTypeNone()) {
return false;
}
return GetTextInputClient()->AddGrammarFragments(fragments);
}
void InputMethodAsh::ConfirmComposition(bool reset_engine) {
TextInputClient* client = GetTextInputClient();
if (pending_commit_ && !pending_composition_range_ && !pending_composition_) {
return;
}
if (pending_composition_ && !pending_commit_ && !pending_composition_range_) {
GetTextInputClient()->SetCompositionText(*pending_composition_);
pending_composition_ = std::nullopt;
composition_changed_ = false;
}
if (client && (client->HasCompositionText() ||
client->SupportsAlwaysConfirmComposition())) {
const size_t characters_committed =
client->ConfirmCompositionText( true);
typing_session_manager_.CommitCharacters(characters_committed);
}
ResetContext(reset_engine);
}
void InputMethodAsh::ResetContext(bool reset_engine) {
if (IsPasswordOrNoneInputFieldFocused() || !GetTextInputClient()) {
return;
}
const bool was_composing = composing_text_;
pending_composition_ = std::nullopt;
pending_commit_ = std::nullopt;
composing_text_ = false;
composition_changed_ = false;
if (reset_engine && was_composing && GetEngine()) {
GetEngine()->Reset();
}
character_composer_.Reset();
}
void InputMethodAsh::UpdateContextFocusState() {
ResetContext();
OnInputMethodChanged();
IMECandidateWindowHandlerInterface* candidate_window =
IMEBridge::Get()->GetCandidateWindowHandler();
if (candidate_window) {
candidate_window->FocusStateChanged(!IsPasswordOrNoneInputFieldFocused());
}
IMEAssistiveWindowHandlerInterface* assistive_window =
IMEBridge::Get()->GetAssistiveWindowHandler();
if (assistive_window) {
assistive_window->FocusStateChanged();
}
IMEBridge::Get()->SetCurrentInputContext(GetInputContext());
TextInputClient* client = GetTextInputClient();
focused_url_ = client && !IsPasswordOrNoneInputFieldFocused()
? client->GetTextEditingContext().page_url
: GURL();
}
ui::EventDispatchDetails InputMethodAsh::ProcessKeyEventPostIME(
ui::KeyEvent* event,
ui::ime::KeyEventHandledState handled_state,
bool stopped_propagation) {
bool handled =
handled_state == ui::ime::KeyEventHandledState::kHandledByIME ||
handled_state ==
ui::ime::KeyEventHandledState::kHandledByAssistiveSuggester;
auto properties =
event->properties() ? *event->properties() : ui::Event::Properties();
ui::SetKeyboardImeFlagProperty(&properties,
handled ? ui::kPropertyKeyboardImeHandledFlag
: ui::kPropertyKeyboardImeIgnoredFlag);
if (handled_state ==
ui::ime::KeyEventHandledState::kNotHandledSuppressAutoRepeat) {
ui::SetKeyEventSuppressAutoRepeat(properties);
}
event->SetProperties(properties);
TextInputClient* client = GetTextInputClient();
if (!client) {
return DispatchKeyEventPostIME(event);
}
if (event->type() == ui::EventType::kKeyPressed && handled) {
bool only_dispatch_vkey_processkey =
(handled_state ==
ui::ime::KeyEventHandledState::kHandledByAssistiveSuggester);
ui::EventDispatchDetails dispatch_details =
ProcessFilteredKeyPressEvent(event, only_dispatch_vkey_processkey);
if (event->stopped_propagation()) {
ResetContext();
return dispatch_details;
}
}
ui::EventDispatchDetails dispatch_details;
if (client != GetTextInputClient()) {
return dispatch_details;
}
MaybeProcessPendingInputMethodResult(event, handled);
if (client != GetTextInputClient()) {
return dispatch_details;
}
if (handled) {
return dispatch_details;
}
if (event->type() == ui::EventType::kKeyPressed) {
return ProcessUnfilteredKeyPressEvent(event);
}
if (event->type() == ui::EventType::kKeyReleased) {
return DispatchKeyEventPostIME(event);
}
return dispatch_details;
}
ui::EventDispatchDetails InputMethodAsh::ProcessFilteredKeyPressEvent(
ui::KeyEvent* event,
bool only_dispatch_vkey_processkey) {
if (!only_dispatch_vkey_processkey) {
if (NeedInsertChar()) {
return DispatchKeyEventPostIME(event);
}
if (event->GetDomKey().IsDeadKey()) {
return DispatchKeyEventPostIME(event);
}
}
ui::KeyEvent fabricated_event(ui::EventType::kKeyPressed, ui::VKEY_PROCESSKEY,
event->code(), event->flags(),
ui::DomKey::PROCESS, event->time_stamp());
if (const auto* properties = event->properties()) {
fabricated_event.SetProperties(*properties);
}
ui::EventDispatchDetails dispatch_details =
DispatchKeyEventPostIME(&fabricated_event);
if (fabricated_event.stopped_propagation()) {
event->StopPropagation();
}
return dispatch_details;
}
ui::EventDispatchDetails InputMethodAsh::ProcessUnfilteredKeyPressEvent(
ui::KeyEvent* event) {
TextInputClient* prev_client = GetTextInputClient();
ui::EventDispatchDetails details = DispatchKeyEventPostIME(event);
if (event->stopped_propagation()) {
ResetContext();
return details;
}
TextInputClient* client = GetTextInputClient();
if (!client || client != prev_client) {
return details;
}
if (event->GetCharacter()) {
client->InsertChar(*event);
typing_session_manager_.CommitCharacters(1);
}
return details;
}
void InputMethodAsh::MaybeProcessPendingInputMethodResult(ui::KeyEvent* event,
bool handled) {
TextInputClient* client = GetTextInputClient();
DCHECK(client);
if (pending_commit_) {
if (handled && NeedInsertChar()) {
for (const auto& ch : pending_commit_->text) {
ui::KeyEvent ch_event(ui::EventType::kKeyPressed, ui::VKEY_UNKNOWN,
ui::EF_NONE);
ch_event.set_character(ch);
ui::SetKeyboardImeFlags(&ch_event, ui::kPropertyKeyboardImeHandledFlag);
client->InsertChar(ch_event);
}
} else if (pending_commit_->text.empty()) {
client->InsertText(
u"", TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
composing_text_ = false;
} else {
const std::u16string before_cursor =
pending_commit_->text.substr(0, pending_commit_->cursor);
if (!before_cursor.empty()) {
client->InsertText(
before_cursor,
TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
}
const std::u16string after_cursor =
pending_commit_->text.substr(pending_commit_->cursor);
if (!after_cursor.empty()) {
client->InsertText(
after_cursor,
TextInputClient::InsertTextCursorBehavior::kMoveCursorBeforeText);
}
composing_text_ = false;
}
typing_session_manager_.CommitCharacters(pending_commit_->text.length());
}
if (composition_changed_ && !IsTextInputTypeNone()) {
if (pending_composition_range_) {
client->SetCompositionFromExistingText(
pending_composition_range_->range,
pending_composition_range_->text_spans);
}
if (pending_composition_) {
composing_text_ = true;
client->SetCompositionText(*pending_composition_);
} else if (!pending_commit_ && !pending_composition_range_) {
client->ClearCompositionText();
}
pending_composition_ = std::nullopt;
pending_composition_range_.reset();
}
if (pending_autocorrect_range_) {
std::move(pending_autocorrect_range_->callback)
.Run(client->SetAutocorrectRange(pending_autocorrect_range_->range));
pending_autocorrect_range_.reset();
}
pending_commit_ = std::nullopt;
composition_changed_ = false;
}
bool InputMethodAsh::NeedInsertChar() const {
return GetTextInputClient() &&
(IsTextInputTypeNone() || (!composing_text_ && pending_commit_ &&
pending_commit_->text.length() == 1 &&
pending_commit_->cursor == 1));
}
bool InputMethodAsh::HasInputMethodResult() const {
return pending_commit_ || composition_changed_;
}
void InputMethodAsh::CommitText(
const std::u16string& text,
TextInputClient::InsertTextCursorBehavior cursor_behavior) {
if (!GetTextInputClient()) {
return;
}
if (!GetTextInputClient()->CanComposeInline()) {
UpdateCompositionText(CompositionText(), 0, false);
}
if (!pending_commit_) {
pending_commit_ = PendingCommit();
}
pending_commit_->text.insert(pending_commit_->cursor, text);
if (cursor_behavior ==
TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText) {
pending_commit_->cursor += text.length();
}
if (!handling_key_event_ && !IsTextInputTypeNone()) {
if (!SendFakeProcessKeyEvent(true)) {
GetTextInputClient()->InsertText(text, cursor_behavior);
typing_session_manager_.CommitCharacters(text.length());
}
SendFakeProcessKeyEvent(false);
pending_commit_ = std::nullopt;
}
}
void InputMethodAsh::UpdateCompositionText(const CompositionText& text,
uint32_t cursor_pos,
bool visible) {
if (IsTextInputTypeNone()) {
return;
}
if (!GetTextInputClient()->CanComposeInline()) {
IMECandidateWindowHandlerInterface* candidate_window =
IMEBridge::Get()->GetCandidateWindowHandler();
if (candidate_window) {
candidate_window->UpdatePreeditText(text.text, cursor_pos, visible);
}
}
if (!visible) {
HidePreeditText();
return;
}
pending_composition_ = ExtractCompositionText(text, cursor_pos);
composition_changed_ = true;
if (pending_composition_->text.length()) {
composing_text_ = true;
}
if (!handling_key_event_) {
if (!SendFakeProcessKeyEvent(true)) {
GetTextInputClient()->SetCompositionText(*pending_composition_);
}
SendFakeProcessKeyEvent(false);
composition_changed_ = false;
pending_composition_ = std::nullopt;
}
}
void InputMethodAsh::HidePreeditText() {
if (IsTextInputTypeNone()) {
return;
}
composition_changed_ = true;
pending_composition_ = std::nullopt;
if (!handling_key_event_) {
TextInputClient* client = GetTextInputClient();
if (client && client->HasCompositionText()) {
if (!SendFakeProcessKeyEvent(true)) {
client->ClearCompositionText();
}
SendFakeProcessKeyEvent(false);
}
composition_changed_ = false;
}
}
TextInputMethod::InputContext InputMethodAsh::GetInputContext() const {
TextInputClient* client = GetTextInputClient();
if (!client) {
return TextInputMethod::InputContext(ui::TEXT_INPUT_TYPE_NONE);
}
const int flags = client->GetTextInputFlags();
TextInputMethod::InputContext input_context(
flags & ui::TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD
? ui::TEXT_INPUT_TYPE_PASSWORD
: client->GetTextInputType());
input_context.mode = client->GetTextInputMode();
input_context.autocompletion_mode =
ConvertTextInputFlagToEnum<AutocompletionMode>(
flags, ui::TEXT_INPUT_FLAG_AUTOCOMPLETE_ON,
ui::TEXT_INPUT_FLAG_AUTOCOMPLETE_OFF);
input_context.autocorrection_mode =
ConvertTextInputFlagToEnum<AutocorrectionMode>(
flags, ui::TEXT_INPUT_FLAG_AUTOCORRECT_ON,
ui::TEXT_INPUT_FLAG_AUTOCORRECT_OFF);
input_context.autocapitalization_mode = ConvertAutocapitalizationMode(flags);
input_context.spellcheck_mode = ConvertTextInputFlagToEnum<SpellcheckMode>(
flags, ui::TEXT_INPUT_FLAG_SPELLCHECK_ON,
ui::TEXT_INPUT_FLAG_SPELLCHECK_OFF);
input_context.focus_reason = client->GetFocusReason();
input_context.personalization_mode = client->ShouldDoLearning()
? PersonalizationMode::kEnabled
: PersonalizationMode::kDisabled;
return input_context;
}
void InputMethodAsh::SendKeyEvent(ui::KeyEvent* event) {
ui::EventDispatchDetails details = DispatchKeyEvent(event);
DCHECK(!details.dispatcher_destroyed);
}
SurroundingTextInfo InputMethodAsh::GetSurroundingTextInfo() {
gfx::Range text_range;
SurroundingTextInfo info;
TextInputClient* client = GetTextInputClient();
if (!client || !client->GetTextRange(&text_range) ||
!client->GetTextFromRange(text_range, &info.surrounding_text) ||
!client->GetEditableSelectionRange(&info.selection_range)) {
return SurroundingTextInfo();
}
info.selection_range.set_start(info.selection_range.start() -
text_range.start());
info.selection_range.set_end(info.selection_range.end() - text_range.start());
info.offset = text_range.start();
return info;
}
void InputMethodAsh::DeleteSurroundingText(uint32_t num_char16s_before_cursor,
uint32_t num_char16s_after_cursor) {
if (!GetTextInputClient()) {
return;
}
if (GetTextInputClient()->HasCompositionText()) {
return;
}
GetTextInputClient()->ExtendSelectionAndDelete(num_char16s_before_cursor,
num_char16s_after_cursor);
}
void InputMethodAsh::ReplaceSurroundingText(
uint32_t length_before_selection,
uint32_t length_after_selection,
std::u16string_view replacement_text) {
if (!GetTextInputClient()) {
return;
}
GetTextInputClient()->ExtendSelectionAndReplace(
length_before_selection, length_after_selection, replacement_text);
}
bool InputMethodAsh::ExecuteCharacterComposer(const ui::KeyEvent& event) {
if (!character_composer_.FilterKeyPress(event)) {
return false;
}
CompositionText preedit;
preedit.text = character_composer_.preedit_string();
UpdateCompositionText(preedit, preedit.text.size(), !preedit.text.empty());
const std::u16string& commit_text = character_composer_.composed_character();
if (!commit_text.empty()) {
CommitText(commit_text,
TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
}
return true;
}
CompositionText InputMethodAsh::ExtractCompositionText(
const CompositionText& text,
uint32_t cursor_position) const {
CompositionText composition;
composition.text = text.text;
if (composition.text.empty()) {
return composition;
}
std::vector<size_t> char16_offsets;
size_t length = composition.text.length();
for (base::i18n::UTF16CharIterator char_iterator(composition.text);
!char_iterator.end(); char_iterator.Advance()) {
char16_offsets.push_back(char_iterator.array_pos());
}
auto char_length = static_cast<uint32_t>(char16_offsets.size());
char16_offsets.push_back(length);
size_t cursor_offset = char16_offsets[std::min(char_length, cursor_position)];
composition.selection = gfx::Range(cursor_offset);
const ui::ImeTextSpans text_ime_text_spans = text.ime_text_spans;
if (!text_ime_text_spans.empty()) {
for (const auto& text_ime_text_span : text_ime_text_spans) {
const uint32_t start = text_ime_text_span.start_offset;
const uint32_t end = text_ime_text_span.end_offset;
if (start >= end || end >= char16_offsets.size()) {
LOG(ERROR) << "IME composition invalid bounds.";
continue;
}
ui::ImeTextSpan ime_text_span(ui::ImeTextSpan::Type::kComposition,
char16_offsets[start], char16_offsets[end],
text_ime_text_span.thickness,
ui::ImeTextSpan::UnderlineStyle::kSolid,
text_ime_text_span.background_color);
ime_text_span.underline_color = text_ime_text_span.underline_color;
composition.ime_text_spans.push_back(ime_text_span);
}
}
DCHECK(text.selection.start() <= text.selection.end());
DCHECK(text.selection.end() <= char_length);
if (text.selection.start() < text.selection.end()) {
const size_t start =
std::min(text.selection.start(), static_cast<size_t>(char_length));
const size_t end =
std::min(text.selection.end(), static_cast<size_t>(char_length));
ui::ImeTextSpan ime_text_span(
ui::ImeTextSpan::Type::kComposition, char16_offsets[start],
char16_offsets[end], ui::ImeTextSpan::Thickness::kThick,
ui::ImeTextSpan::UnderlineStyle::kSolid, SK_ColorTRANSPARENT);
composition.ime_text_spans.push_back(ime_text_span);
if (ime_text_span.start_offset == cursor_offset) {
composition.selection.set_start(ime_text_span.end_offset);
composition.selection.set_end(cursor_offset);
} else if (ime_text_span.end_offset == cursor_offset) {
composition.selection.set_start(ime_text_span.start_offset);
composition.selection.set_end(cursor_offset);
}
}
if (composition.ime_text_spans.empty()) {
composition.ime_text_spans.push_back(ui::ImeTextSpan(
ui::ImeTextSpan::Type::kComposition, 0, length,
ui::ImeTextSpan::Thickness::kThin,
ui::ImeTextSpan::UnderlineStyle::kSolid, SK_ColorTRANSPARENT));
}
return composition;
}
bool InputMethodAsh::IsPasswordOrNoneInputFieldFocused() {
ui::TextInputType type = GetTextInputType();
return type == ui::TEXT_INPUT_TYPE_NONE ||
type == ui::TEXT_INPUT_TYPE_PASSWORD;
}
bool InputMethodAsh::HasCompositionText() {
TextInputClient* client = GetTextInputClient();
return client && client->HasCompositionText();
}
ukm::SourceId InputMethodAsh::GetClientSourceForMetrics() {
TextInputClient* client = GetTextInputClient();
return client ? client->GetClientSourceForMetrics() : ukm::kInvalidSourceId;
}
ui::InputMethod* InputMethodAsh::GetInputMethod() {
return this;
}
bool InputMethodAsh::SendFakeProcessKeyEvent(bool pressed) const {
ui::KeyEvent evt(
pressed ? ui::EventType::kKeyPressed : ui::EventType::kKeyReleased,
pressed ? ui::VKEY_PROCESSKEY : ui::VKEY_UNKNOWN,
ui::EF_IME_FABRICATED_KEY);
ui::SetKeyboardImeFlags(&evt, ui::kPropertyKeyboardImeHandledFlag);
std::ignore = DispatchKeyEventPostIME(&evt);
return evt.stopped_propagation();
}
}