1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/base/ime/chromeos/input_method_chromeos.h"
6
7 #include <stddef.h>
8
9 #include <algorithm>
10 #include <cstring>
11 #include <set>
12 #include <utility>
13 #include <vector>
14
15 #include "base/bind.h"
16 #include "base/check.h"
17 #include "base/i18n/char_iterator.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/third_party/icu/icu_utf.h"
21 #include "chromeos/system/devicemode.h"
22 #include "ui/base/ime/chromeos/ime_bridge.h"
23 #include "ui/base/ime/chromeos/ime_engine_handler_interface.h"
24 #include "ui/base/ime/chromeos/ime_keyboard.h"
25 #include "ui/base/ime/chromeos/input_method_manager.h"
26 #include "ui/base/ime/composition_text.h"
27 #include "ui/base/ime/input_method_delegate.h"
28 #include "ui/base/ime/text_input_client.h"
29 #include "ui/events/event.h"
30 #include "ui/gfx/geometry/rect.h"
31
32 namespace ui {
33
GetEngine()34 ui::IMEEngineHandlerInterface* GetEngine() {
35 auto* bridge = ui::IMEBridge::Get();
36 return bridge ? bridge->GetCurrentEngineHandler() : nullptr;
37 }
38
39 // InputMethodChromeOS implementation -----------------------------------------
InputMethodChromeOS(internal::InputMethodDelegate * delegate)40 InputMethodChromeOS::InputMethodChromeOS(
41 internal::InputMethodDelegate* delegate)
42 : InputMethodBase(delegate),
43 composing_text_(false),
44 composition_changed_(false),
45 handling_key_event_(false) {
46 ResetContext();
47 }
48
~InputMethodChromeOS()49 InputMethodChromeOS::~InputMethodChromeOS() {
50 ConfirmCompositionText(/* reset_engine */ true, /* keep_selection */ false);
51 // We are dead, so we need to ask the client to stop relying on us.
52 OnInputMethodChanged();
53
54 if (ui::IMEBridge::Get() &&
55 ui::IMEBridge::Get()->GetInputContextHandler() == this) {
56 ui::IMEBridge::Get()->SetInputContextHandler(nullptr);
57 }
58 }
59
PendingSetCompositionRange(const gfx::Range & range,const std::vector<ui::ImeTextSpan> & text_spans)60 InputMethodChromeOS::PendingSetCompositionRange::PendingSetCompositionRange(
61 const gfx::Range& range,
62 const std::vector<ui::ImeTextSpan>& text_spans)
63 : range(range), text_spans(text_spans) {}
64
65 InputMethodChromeOS::PendingSetCompositionRange::PendingSetCompositionRange(
66 const PendingSetCompositionRange& other) = default;
67
68 InputMethodChromeOS::PendingSetCompositionRange::~PendingSetCompositionRange() =
69 default;
70
DispatchKeyEvent(ui::KeyEvent * event)71 ui::EventDispatchDetails InputMethodChromeOS::DispatchKeyEvent(
72 ui::KeyEvent* event) {
73 DCHECK(event->IsKeyEvent());
74 DCHECK(!(event->flags() & ui::EF_IS_SYNTHESIZED));
75
76 // For OS_CHROMEOS build of Chrome running on Linux, the IME keyboard cannot
77 // track the Caps Lock state by itself, so need to call SetCapsLockEnabled()
78 // method to reflect the Caps Lock state by the key event.
79 chromeos::input_method::InputMethodManager* manager =
80 chromeos::input_method::InputMethodManager::Get();
81 if (manager) {
82 chromeos::input_method::ImeKeyboard* keyboard = manager->GetImeKeyboard();
83 if (keyboard && event->type() == ET_KEY_PRESSED &&
84 event->key_code() != ui::VKEY_CAPITAL &&
85 keyboard->CapsLockIsEnabled() != event->IsCapsLockOn()) {
86 // Synchronize the keyboard state with event's state if they do not
87 // match. Do not synchronize for Caps Lock key because it is already
88 // handled in event rewriter.
89 keyboard->SetCapsLockEnabled(event->IsCapsLockOn());
90 }
91
92 // For JP106 language input keys, makes sure they can be passed to the app
93 // so that the VDI web apps can be supported. See https://crbug.com/816341.
94 // VKEY_CONVERT: Henkan key
95 // VKEY_NONCONVERT: Muhenkan key
96 // VKEY_DBE_SBCSCHAR/VKEY_DBE_DBCSCHAR: ZenkakuHankaku key
97 chromeos::input_method::InputMethodManager::State* state =
98 manager->GetActiveIMEState().get();
99 if (event->type() == ET_KEY_PRESSED && state) {
100 bool language_input_key = true;
101 switch (event->key_code()) {
102 case ui::VKEY_CONVERT:
103 state->ChangeInputMethodToJpIme();
104 break;
105 case ui::VKEY_NONCONVERT:
106 state->ChangeInputMethodToJpKeyboard();
107 break;
108 case ui::VKEY_DBE_SBCSCHAR:
109 case ui::VKEY_DBE_DBCSCHAR:
110 state->ToggleInputMethodForJpIme();
111 break;
112 default:
113 language_input_key = false;
114 break;
115 }
116 if (language_input_key) {
117 // Dispatches the event to app/blink.
118 // TODO(shuchen): Eventually, the language input keys should be handed
119 // over to the IME extension to process. And IMF can handle if the IME
120 // extension didn't handle.
121 return DispatchKeyEventPostIME(event);
122 }
123 }
124 }
125
126 // If |context_| is not usable, then we can only dispatch the key event as is.
127 // We only dispatch the key event to input method when the |context_| is an
128 // normal input field (not a password field).
129 // Note: We need to send the key event to ibus even if the |context_| is not
130 // enabled, so that ibus can have a chance to enable the |context_|.
131 if (IsPasswordOrNoneInputFieldFocused() || !GetEngine()) {
132 if (event->type() == ET_KEY_PRESSED) {
133 if (ExecuteCharacterComposer(*event)) {
134 // Treating as PostIME event if character composer handles key event and
135 // generates some IME event,
136 return ProcessKeyEventPostIME(event, false,
137 /* handled */ true,
138 /* stopped_propagation */ true);
139 }
140 return ProcessUnfilteredKeyPressEvent(event);
141 }
142 return DispatchKeyEventPostIME(event);
143 }
144
145 handling_key_event_ = true;
146 GetEngine()->ProcessKeyEvent(
147 *event, base::BindOnce(&InputMethodChromeOS::ProcessKeyEventDone,
148 weak_ptr_factory_.GetWeakPtr(),
149 // Pass the ownership of the new copied event.
150 base::Owned(new ui::KeyEvent(*event))));
151 return ui::EventDispatchDetails();
152 }
153
ProcessKeyEventDone(ui::KeyEvent * event,bool is_handled)154 void InputMethodChromeOS::ProcessKeyEventDone(ui::KeyEvent* event,
155 bool is_handled) {
156 DCHECK(event);
157 if (event->type() == ET_KEY_PRESSED) {
158 if (is_handled) {
159 // IME event has a priority to be handled, so that character composer
160 // should be reset.
161 character_composer_.Reset();
162 } else {
163 // If IME does not handle key event, passes keyevent to character composer
164 // to be able to compose complex characters.
165 is_handled = ExecuteCharacterComposer(*event);
166 }
167 }
168 if (event->type() == ET_KEY_PRESSED || event->type() == ET_KEY_RELEASED) {
169 ignore_result(ProcessKeyEventPostIME(event, false, is_handled,
170 /* stopped_propagation */ false));
171 }
172 handling_key_event_ = false;
173 }
174
OnTextInputTypeChanged(const TextInputClient * client)175 void InputMethodChromeOS::OnTextInputTypeChanged(
176 const TextInputClient* client) {
177 if (!IsTextInputClientFocused(client))
178 return;
179
180 UpdateContextFocusState();
181
182 ui::IMEEngineHandlerInterface* engine = GetEngine();
183 if (engine) {
184 ui::IMEEngineHandlerInterface::InputContext context(
185 GetTextInputType(), GetTextInputMode(), GetTextInputFlags(),
186 GetClientFocusReason(), GetClientShouldDoLearning());
187 // When focused input client is not changed, a text input type change
188 // should cause blur/focus events to engine. The focus in to or out from
189 // password field should also notify engine.
190 engine->FocusOut();
191 engine->FocusIn(context);
192 }
193
194 OnCaretBoundsChanged(client);
195
196 InputMethodBase::OnTextInputTypeChanged(client);
197 }
198
OnCaretBoundsChanged(const TextInputClient * client)199 void InputMethodChromeOS::OnCaretBoundsChanged(const TextInputClient* client) {
200 if (IsTextInputTypeNone() || !IsTextInputClientFocused(client))
201 return;
202
203 NotifyTextInputCaretBoundsChanged(client);
204
205 if (IsPasswordOrNoneInputFieldFocused())
206 return;
207
208 // The current text input type should not be NONE if |context_| is focused.
209 DCHECK(client == GetTextInputClient());
210 DCHECK(!IsTextInputTypeNone());
211
212 if (GetEngine())
213 GetEngine()->SetCompositionBounds(GetCompositionBounds(client));
214
215 chromeos::IMECandidateWindowHandlerInterface* candidate_window =
216 ui::IMEBridge::Get()->GetCandidateWindowHandler();
217 chromeos::IMEAssistiveWindowHandlerInterface* assistive_window =
218 ui::IMEBridge::Get()->GetAssistiveWindowHandler();
219 if (!candidate_window && !assistive_window)
220 return;
221
222 const gfx::Rect caret_rect = client->GetCaretBounds();
223
224 gfx::Rect composition_head;
225 if (client->HasCompositionText())
226 client->GetCompositionCharacterBounds(0, &composition_head);
227
228 // Pepper doesn't support composition bounds, so fall back to caret bounds to
229 // avoid a bad user experience (the IME window moved to upper left corner).
230 if (composition_head.IsEmpty())
231 composition_head = caret_rect;
232 if (candidate_window)
233 candidate_window->SetCursorBounds(caret_rect, composition_head);
234
235 if (assistive_window) {
236 chromeos::Bounds bounds;
237 bounds.caret = caret_rect;
238 bounds.autocorrect = client->GetAutocorrectCharacterBounds();
239 assistive_window->SetBounds(bounds);
240 }
241
242 gfx::Range text_range;
243 gfx::Range selection_range;
244 base::string16 surrounding_text;
245 if (!client->GetTextRange(&text_range) ||
246 !client->GetTextFromRange(text_range, &surrounding_text) ||
247 !client->GetEditableSelectionRange(&selection_range)) {
248 previous_surrounding_text_.clear();
249 previous_selection_range_ = gfx::Range::InvalidRange();
250 return;
251 }
252
253 if (previous_selection_range_ == selection_range &&
254 previous_surrounding_text_ == surrounding_text)
255 return;
256
257 previous_selection_range_ = selection_range;
258 previous_surrounding_text_ = surrounding_text;
259
260 if (!selection_range.IsValid()) {
261 // TODO(nona): Ideally selection_range should not be invalid.
262 // TODO(nona): If javascript changes the focus on page loading, even (0,0)
263 // can not be obtained. Need investigation.
264 return;
265 }
266
267 // Here SetSurroundingText accepts relative position of |surrounding_text|, so
268 // we have to convert |selection_range| from node coordinates to
269 // |surrounding_text| coordinates.
270 if (GetEngine()) {
271 GetEngine()->SetSurroundingText(
272 surrounding_text, selection_range.start() - text_range.start(),
273 selection_range.end() - text_range.start(), text_range.start());
274 }
275 }
276
CancelComposition(const TextInputClient * client)277 void InputMethodChromeOS::CancelComposition(const TextInputClient* client) {
278 if (!IsPasswordOrNoneInputFieldFocused() && IsTextInputClientFocused(client))
279 ResetContext();
280 }
281
IsCandidatePopupOpen() const282 bool InputMethodChromeOS::IsCandidatePopupOpen() const {
283 // TODO(yukishiino): Implement this method.
284 return false;
285 }
286
287 InputMethodKeyboardController*
GetInputMethodKeyboardController()288 InputMethodChromeOS::GetInputMethodKeyboardController() {
289 chromeos::input_method::InputMethodManager* manager =
290 chromeos::input_method::InputMethodManager::Get();
291 if (manager) {
292 if (auto* controller = manager->GetInputMethodKeyboardController())
293 return controller;
294 }
295 return InputMethodBase::GetInputMethodKeyboardController();
296 }
297
OnFocus()298 void InputMethodChromeOS::OnFocus() {
299 ui::IMEBridge* bridge = ui::IMEBridge::Get();
300 if (bridge) {
301 bridge->SetInputContextHandler(this);
302 bridge->MaybeSwitchEngine();
303 }
304 }
305
OnBlur()306 void InputMethodChromeOS::OnBlur() {
307 if (ui::IMEBridge::Get() &&
308 ui::IMEBridge::Get()->GetInputContextHandler() == this)
309 ui::IMEBridge::Get()->SetInputContextHandler(nullptr);
310 }
311
OnWillChangeFocusedClient(TextInputClient * focused_before,TextInputClient * focused)312 void InputMethodChromeOS::OnWillChangeFocusedClient(
313 TextInputClient* focused_before,
314 TextInputClient* focused) {
315 ConfirmCompositionText(/* reset_engine */ true, /* keep_selection */ false);
316
317 // Removes any autocorrect range in the unfocused TextInputClient.
318 gfx::Range text_range;
319 if (focused_before && focused_before->GetTextRange(&text_range)) {
320 // This is currently only implemented in RenderWidgetHostViewAura.
321 focused_before->SetAutocorrectRange(base::EmptyString16(), text_range);
322 }
323
324 if (GetEngine())
325 GetEngine()->FocusOut();
326 }
327
OnDidChangeFocusedClient(TextInputClient * focused_before,TextInputClient * focused)328 void InputMethodChromeOS::OnDidChangeFocusedClient(
329 TextInputClient* focused_before,
330 TextInputClient* focused) {
331 // Force to update the input type since client's TextInputStateChanged()
332 // function might not be called if text input types before the client loses
333 // focus and after it acquires focus again are the same.
334 UpdateContextFocusState();
335
336 if (GetEngine()) {
337 ui::IMEEngineHandlerInterface::InputContext context(
338 GetTextInputType(), GetTextInputMode(), GetTextInputFlags(),
339 GetClientFocusReason(), GetClientShouldDoLearning());
340 GetEngine()->FocusIn(context);
341 }
342
343 OnCaretBoundsChanged(GetTextInputClient());
344 }
345
SetCompositionRange(uint32_t before,uint32_t after,const std::vector<ui::ImeTextSpan> & text_spans)346 bool InputMethodChromeOS::SetCompositionRange(
347 uint32_t before,
348 uint32_t after,
349 const std::vector<ui::ImeTextSpan>& text_spans) {
350 TextInputClient* client = GetTextInputClient();
351
352 if (IsTextInputTypeNone())
353 return false;
354
355 // The given range and spans are relative to the current selection.
356 gfx::Range range;
357 if (!client->GetEditableSelectionRange(&range))
358 return false;
359
360 const gfx::Range composition_range(range.start() - before,
361 range.end() + after);
362
363 // Check that the composition range is valid.
364 gfx::Range text_range;
365 client->GetTextRange(&text_range);
366 if (!text_range.Contains(composition_range))
367 return false;
368
369 return SetComposingRange(composition_range.start(), composition_range.end(),
370 text_spans);
371 }
372
SetComposingRange(uint32_t start,uint32_t end,const std::vector<ui::ImeTextSpan> & text_spans)373 bool InputMethodChromeOS::SetComposingRange(
374 uint32_t start,
375 uint32_t end,
376 const std::vector<ui::ImeTextSpan>& text_spans) {
377 TextInputClient* client = GetTextInputClient();
378
379 if (IsTextInputTypeNone())
380 return false;
381
382 const auto ordered_range = std::minmax(start, end);
383 const gfx::Range composition_range(ordered_range.first, ordered_range.second);
384
385 // If we have pending key events, then delay the operation until
386 // |ProcessKeyEventPostIME|. Otherwise, process it immediately.
387 if (handling_key_event_) {
388 composition_changed_ = true;
389 pending_composition_range_ =
390 PendingSetCompositionRange{composition_range, text_spans};
391 return true;
392 } else {
393 return client->SetCompositionFromExistingText(composition_range,
394 text_spans);
395 }
396 }
397
GetAutocorrectRange()398 gfx::Range InputMethodChromeOS::GetAutocorrectRange() {
399 if (IsTextInputTypeNone())
400 return gfx::Range();
401 return GetTextInputClient()->GetAutocorrectRange();
402 }
403
GetAutocorrectCharacterBounds()404 gfx::Rect InputMethodChromeOS::GetAutocorrectCharacterBounds() {
405 if (IsTextInputTypeNone())
406 return gfx::Rect();
407 return GetTextInputClient()->GetAutocorrectCharacterBounds();
408 }
409
SetAutocorrectRange(const base::string16 & autocorrect_text,uint32_t start,uint32_t end)410 bool InputMethodChromeOS::SetAutocorrectRange(
411 const base::string16& autocorrect_text,
412 uint32_t start,
413 uint32_t end) {
414 if (IsTextInputTypeNone())
415 return false;
416 return GetTextInputClient()->SetAutocorrectRange(autocorrect_text,
417 gfx::Range(start, end));
418 }
419
SetSelectionRange(uint32_t start,uint32_t end)420 bool InputMethodChromeOS::SetSelectionRange(uint32_t start, uint32_t end) {
421 if (IsTextInputTypeNone())
422 return false;
423 return GetTextInputClient()->SetEditableSelectionRange(
424 gfx::Range(start, end));
425 }
426
ConfirmCompositionText(bool reset_engine,bool keep_selection)427 void InputMethodChromeOS::ConfirmCompositionText(bool reset_engine,
428 bool keep_selection) {
429 TextInputClient* client = GetTextInputClient();
430 if (client && client->HasCompositionText())
431 client->ConfirmCompositionText(keep_selection);
432
433 // See https://crbug.com/984472.
434 ResetContext(reset_engine);
435 }
436
ResetContext(bool reset_engine)437 void InputMethodChromeOS::ResetContext(bool reset_engine) {
438 if (IsPasswordOrNoneInputFieldFocused() || !GetTextInputClient())
439 return;
440
441 pending_composition_ = base::nullopt;
442 result_text_.clear();
443 composing_text_ = false;
444 composition_changed_ = false;
445
446 if (reset_engine && GetEngine())
447 GetEngine()->Reset();
448
449 character_composer_.Reset();
450 }
451
UpdateContextFocusState()452 void InputMethodChromeOS::UpdateContextFocusState() {
453 ResetContext();
454 OnInputMethodChanged();
455
456 // Propagate the focus event to the candidate window handler which also
457 // manages the input method mode indicator.
458 chromeos::IMECandidateWindowHandlerInterface* candidate_window =
459 ui::IMEBridge::Get()->GetCandidateWindowHandler();
460 if (candidate_window)
461 candidate_window->FocusStateChanged(!IsPasswordOrNoneInputFieldFocused());
462
463 // Propagate focus event to assistive window handler.
464 chromeos::IMEAssistiveWindowHandlerInterface* assistive_window =
465 ui::IMEBridge::Get()->GetAssistiveWindowHandler();
466 if (assistive_window)
467 assistive_window->FocusStateChanged();
468
469 ui::IMEEngineHandlerInterface::InputContext context(
470 GetTextInputType(), GetTextInputMode(), GetTextInputFlags(),
471 GetClientFocusReason(), GetClientShouldDoLearning());
472 ui::IMEBridge::Get()->SetCurrentInputContext(context);
473 }
474
ProcessKeyEventPostIME(ui::KeyEvent * event,bool skip_process_filtered,bool handled,bool stopped_propagation)475 ui::EventDispatchDetails InputMethodChromeOS::ProcessKeyEventPostIME(
476 ui::KeyEvent* event,
477 bool skip_process_filtered,
478 bool handled,
479 bool stopped_propagation) {
480 TextInputClient* client = GetTextInputClient();
481 if (!client) {
482 // As ibus works asynchronously, there is a chance that the focused client
483 // loses focus before this method gets called.
484 return DispatchKeyEventPostIME(event);
485 }
486
487 if (event->type() == ET_KEY_PRESSED && handled && !skip_process_filtered) {
488 ui::EventDispatchDetails dispatch_details =
489 ProcessFilteredKeyPressEvent(event);
490 if (event->stopped_propagation()) {
491 ResetContext();
492 return dispatch_details;
493 }
494 }
495 ui::EventDispatchDetails dispatch_details;
496
497 // In case the focus was changed by the key event. The |context_| should have
498 // been reset when the focused window changed.
499 if (client != GetTextInputClient())
500 return dispatch_details;
501
502 if (HasInputMethodResult())
503 ProcessInputMethodResult(event, handled);
504
505 // In case the focus was changed when sending input method results to the
506 // focused window.
507 if (client != GetTextInputClient())
508 return dispatch_details;
509
510 if (handled)
511 return dispatch_details; // IME handled the key event. do not forward.
512
513 if (event->type() == ET_KEY_PRESSED)
514 return ProcessUnfilteredKeyPressEvent(event);
515
516 if (event->type() == ET_KEY_RELEASED)
517 return DispatchKeyEventPostIME(event);
518 return dispatch_details;
519 }
520
ProcessFilteredKeyPressEvent(ui::KeyEvent * event)521 ui::EventDispatchDetails InputMethodChromeOS::ProcessFilteredKeyPressEvent(
522 ui::KeyEvent* event) {
523 if (NeedInsertChar())
524 return DispatchKeyEventPostIME(event);
525
526 ui::KeyEvent fabricated_event(ET_KEY_PRESSED, VKEY_PROCESSKEY, event->code(),
527 event->flags(), DomKey::PROCESS,
528 event->time_stamp());
529 ui::EventDispatchDetails dispatch_details =
530 DispatchKeyEventPostIME(&fabricated_event);
531 if (fabricated_event.stopped_propagation())
532 event->StopPropagation();
533 return dispatch_details;
534 }
535
ProcessUnfilteredKeyPressEvent(ui::KeyEvent * event)536 ui::EventDispatchDetails InputMethodChromeOS::ProcessUnfilteredKeyPressEvent(
537 ui::KeyEvent* event) {
538 TextInputClient* prev_client = GetTextInputClient();
539 ui::EventDispatchDetails details = DispatchKeyEventPostIME(event);
540 if (event->stopped_propagation()) {
541 ResetContext();
542 return details;
543 }
544
545 // We shouldn't dispatch the character anymore if the key event dispatch
546 // caused focus change. For example, in the following scenario,
547 // 1. visit a web page which has a <textarea>.
548 // 2. click Omnibox.
549 // 3. enable Korean IME, press A, then press Tab to move the focus to the web
550 // page.
551 // We should return here not to send the Tab key event to RWHV.
552 TextInputClient* client = GetTextInputClient();
553 if (!client || client != prev_client)
554 return details;
555
556 // If a key event was not filtered by |context_| and |character_composer_|,
557 // then it means the key event didn't generate any result text. So we need
558 // to send corresponding character to the focused text input client.
559 uint16_t ch = event->GetCharacter();
560 if (ch)
561 client->InsertChar(*event);
562 return details;
563 }
564
ProcessInputMethodResult(ui::KeyEvent * event,bool handled)565 void InputMethodChromeOS::ProcessInputMethodResult(ui::KeyEvent* event,
566 bool handled) {
567 TextInputClient* client = GetTextInputClient();
568 DCHECK(client);
569
570 if (result_text_.length()) {
571 if (handled && NeedInsertChar()) {
572 for (base::string16::const_iterator i = result_text_.begin();
573 i != result_text_.end(); ++i) {
574 KeyEvent ch_event(ET_KEY_PRESSED, VKEY_UNKNOWN, EF_NONE);
575 ch_event.set_character(*i);
576 client->InsertChar(ch_event);
577 }
578 } else {
579 client->InsertText(result_text_);
580 composing_text_ = false;
581 }
582 }
583
584 // TODO(https://crbug.com/952757): Refactor this code to be clearer and less
585 // error-prone.
586 if (composition_changed_ && !IsTextInputTypeNone()) {
587 if (pending_composition_range_) {
588 client->SetCompositionFromExistingText(
589 pending_composition_range_->range,
590 pending_composition_range_->text_spans);
591 }
592 if (pending_composition_) {
593 composing_text_ = true;
594 client->SetCompositionText(*pending_composition_);
595 } else if (result_text_.empty() && !pending_composition_range_) {
596 client->ClearCompositionText();
597 }
598
599 pending_composition_ = base::nullopt;
600 pending_composition_range_.reset();
601 }
602
603 // We should not clear composition text here, as it may belong to the next
604 // composition session.
605 result_text_.clear();
606 composition_changed_ = false;
607 }
608
NeedInsertChar() const609 bool InputMethodChromeOS::NeedInsertChar() const {
610 return GetTextInputClient() &&
611 (IsTextInputTypeNone() ||
612 (!composing_text_ && result_text_.length() == 1));
613 }
614
HasInputMethodResult() const615 bool InputMethodChromeOS::HasInputMethodResult() const {
616 return result_text_.length() || composition_changed_;
617 }
618
CommitText(const std::string & text)619 void InputMethodChromeOS::CommitText(const std::string& text) {
620 if (text.empty())
621 return;
622
623 // We need to receive input method result even if the text input type is
624 // TEXT_INPUT_TYPE_NONE, to make sure we can always send correct
625 // character for each key event to the focused text input client.
626 if (!GetTextInputClient())
627 return;
628
629 const base::string16 utf16_text = base::UTF8ToUTF16(text);
630 if (utf16_text.empty())
631 return;
632
633 if (!CanComposeInline()) {
634 // Hides the candidate window for preedit text.
635 UpdateCompositionText(CompositionText(), 0, false);
636 }
637
638 // Append the text to the buffer, because commit signal might be fired
639 // multiple times when processing a key event.
640 result_text_.append(utf16_text);
641
642 // If we are not handling key event, do not bother sending text result if the
643 // focused text input client does not support text input.
644 if (!handling_key_event_ && !IsTextInputTypeNone()) {
645 if (!SendFakeProcessKeyEvent(true))
646 GetTextInputClient()->InsertText(utf16_text);
647 SendFakeProcessKeyEvent(false);
648 result_text_.clear();
649 }
650 }
651
UpdateCompositionText(const CompositionText & text,uint32_t cursor_pos,bool visible)652 void InputMethodChromeOS::UpdateCompositionText(const CompositionText& text,
653 uint32_t cursor_pos,
654 bool visible) {
655 if (IsTextInputTypeNone())
656 return;
657
658 if (!CanComposeInline()) {
659 chromeos::IMECandidateWindowHandlerInterface* candidate_window =
660 ui::IMEBridge::Get()->GetCandidateWindowHandler();
661 if (candidate_window)
662 candidate_window->UpdatePreeditText(text.text, cursor_pos, visible);
663 }
664
665 // |visible| argument is very confusing. For example, what's the correct
666 // behavior when:
667 // 1. OnUpdatePreeditText() is called with a text and visible == false, then
668 // 2. OnShowPreeditText() is called afterwards.
669 //
670 // If it's only for clearing the current preedit text, then why not just use
671 // OnHidePreeditText()?
672 if (!visible) {
673 HidePreeditText();
674 return;
675 }
676
677 pending_composition_ = ExtractCompositionText(text, cursor_pos);
678 composition_changed_ = true;
679
680 // In case OnShowPreeditText() is not called.
681 if (pending_composition_->text.length())
682 composing_text_ = true;
683
684 if (!handling_key_event_) {
685 // If we receive a composition text without pending key event, then we need
686 // to send it to the focused text input client directly.
687 if (!SendFakeProcessKeyEvent(true)) {
688 GetTextInputClient()->SetCompositionText(*pending_composition_);
689 }
690 SendFakeProcessKeyEvent(false);
691 composition_changed_ = false;
692 pending_composition_ = base::nullopt;
693 }
694 }
695
HidePreeditText()696 void InputMethodChromeOS::HidePreeditText() {
697 if (IsTextInputTypeNone())
698 return;
699
700 // Intentionally leaves |composing_text_| unchanged.
701 composition_changed_ = true;
702 pending_composition_ = base::nullopt;
703
704 if (!handling_key_event_) {
705 TextInputClient* client = GetTextInputClient();
706 if (client && client->HasCompositionText()) {
707 if (!SendFakeProcessKeyEvent(true))
708 client->ClearCompositionText();
709 SendFakeProcessKeyEvent(false);
710 }
711 composition_changed_ = false;
712 }
713 }
714
SendKeyEvent(KeyEvent * event)715 void InputMethodChromeOS::SendKeyEvent(KeyEvent* event) {
716 ui::EventDispatchDetails details = DispatchKeyEvent(event);
717 DCHECK(!details.dispatcher_destroyed);
718 }
719
GetSurroundingTextInfo()720 SurroundingTextInfo InputMethodChromeOS::GetSurroundingTextInfo() {
721 gfx::Range text_range;
722 SurroundingTextInfo info;
723 TextInputClient* client = GetTextInputClient();
724 if (!client->GetTextRange(&text_range) ||
725 !client->GetTextFromRange(text_range, &info.surrounding_text) ||
726 !client->GetEditableSelectionRange(&info.selection_range)) {
727 return SurroundingTextInfo();
728 }
729 // Makes the |selection_range| be relative to the |surrounding_text|.
730 info.selection_range.set_start(info.selection_range.start() -
731 text_range.start());
732 info.selection_range.set_end(info.selection_range.end() - text_range.start());
733 return info;
734 }
735
DeleteSurroundingText(int32_t offset,uint32_t length)736 void InputMethodChromeOS::DeleteSurroundingText(int32_t offset,
737 uint32_t length) {
738 if (!GetTextInputClient())
739 return;
740
741 if (GetTextInputClient()->HasCompositionText())
742 return;
743
744 uint32_t before = offset >= 0 ? 0U : static_cast<uint32_t>(-1 * offset);
745 GetTextInputClient()->ExtendSelectionAndDelete(before, length - before);
746 }
747
ExecuteCharacterComposer(const ui::KeyEvent & event)748 bool InputMethodChromeOS::ExecuteCharacterComposer(const ui::KeyEvent& event) {
749 if (!character_composer_.FilterKeyPress(event))
750 return false;
751
752 // CharacterComposer consumed the key event. Update the composition text.
753 CompositionText preedit;
754 preedit.text = character_composer_.preedit_string();
755 UpdateCompositionText(preedit, preedit.text.size(), !preedit.text.empty());
756 std::string commit_text =
757 base::UTF16ToUTF8(character_composer_.composed_character());
758 if (!commit_text.empty()) {
759 CommitText(commit_text);
760 }
761 return true;
762 }
763
ExtractCompositionText(const CompositionText & text,uint32_t cursor_position) const764 CompositionText InputMethodChromeOS::ExtractCompositionText(
765 const CompositionText& text,
766 uint32_t cursor_position) const {
767 CompositionText composition;
768 composition.text = text.text;
769
770 if (composition.text.empty())
771 return composition;
772
773 // ibus uses character index for cursor position and attribute range, but we
774 // use char16 offset for them. So we need to do conversion here.
775 std::vector<size_t> char16_offsets;
776 size_t length = composition.text.length();
777 for (base::i18n::UTF16CharIterator char_iterator(composition.text);
778 !char_iterator.end(); char_iterator.Advance()) {
779 char16_offsets.push_back(char_iterator.array_pos());
780 }
781
782 // The text length in Unicode characters.
783 auto char_length = static_cast<uint32_t>(char16_offsets.size());
784 // Make sure we can convert the value of |char_length| as well.
785 char16_offsets.push_back(length);
786
787 size_t cursor_offset =
788 char16_offsets[std::min(char_length, cursor_position)];
789
790 composition.selection = gfx::Range(cursor_offset);
791
792 const ImeTextSpans text_ime_text_spans = text.ime_text_spans;
793 if (!text_ime_text_spans.empty()) {
794 for (const auto& text_ime_text_span : text_ime_text_spans) {
795 const uint32_t start = text_ime_text_span.start_offset;
796 const uint32_t end = text_ime_text_span.end_offset;
797 if (start >= end)
798 continue;
799 ImeTextSpan ime_text_span(ui::ImeTextSpan::Type::kComposition,
800 char16_offsets[start], char16_offsets[end],
801 text_ime_text_span.thickness,
802 ui::ImeTextSpan::UnderlineStyle::kSolid,
803 text_ime_text_span.background_color);
804 ime_text_span.underline_color = text_ime_text_span.underline_color;
805 composition.ime_text_spans.push_back(ime_text_span);
806 }
807 }
808
809 DCHECK(text.selection.start() <= text.selection.end());
810 if (text.selection.start() < text.selection.end()) {
811 const uint32_t start = text.selection.start();
812 const uint32_t end = text.selection.end();
813 ImeTextSpan ime_text_span(
814 ui::ImeTextSpan::Type::kComposition, char16_offsets[start],
815 char16_offsets[end], ui::ImeTextSpan::Thickness::kThick,
816 ui::ImeTextSpan::UnderlineStyle::kSolid, SK_ColorTRANSPARENT);
817 composition.ime_text_spans.push_back(ime_text_span);
818
819 // If the cursor is at start or end of this ime_text_span, then we treat
820 // it as the selection range as well, but make sure to set the cursor
821 // position to the selection end.
822 if (ime_text_span.start_offset == cursor_offset) {
823 composition.selection.set_start(ime_text_span.end_offset);
824 composition.selection.set_end(cursor_offset);
825 } else if (ime_text_span.end_offset == cursor_offset) {
826 composition.selection.set_start(ime_text_span.start_offset);
827 composition.selection.set_end(cursor_offset);
828 }
829 }
830
831 // Use a thin underline with text color by default.
832 if (composition.ime_text_spans.empty()) {
833 composition.ime_text_spans.push_back(ImeTextSpan(
834 ui::ImeTextSpan::Type::kComposition, 0, length,
835 ui::ImeTextSpan::Thickness::kThin,
836 ui::ImeTextSpan::UnderlineStyle::kSolid, SK_ColorTRANSPARENT));
837 }
838
839 return composition;
840 }
841
IsPasswordOrNoneInputFieldFocused()842 bool InputMethodChromeOS::IsPasswordOrNoneInputFieldFocused() {
843 TextInputType type = GetTextInputType();
844 return type == TEXT_INPUT_TYPE_NONE || type == TEXT_INPUT_TYPE_PASSWORD;
845 }
846
IsInputFieldFocused()847 bool InputMethodChromeOS::IsInputFieldFocused() {
848 return GetTextInputType() != TEXT_INPUT_TYPE_NONE;
849 }
850
GetClientFocusReason() const851 TextInputClient::FocusReason InputMethodChromeOS::GetClientFocusReason() const {
852 TextInputClient* client = GetTextInputClient();
853 return client ? client->GetFocusReason() : TextInputClient::FOCUS_REASON_NONE;
854 }
855
HasCompositionText()856 bool InputMethodChromeOS::HasCompositionText() {
857 TextInputClient* client = GetTextInputClient();
858 return client && client->HasCompositionText();
859 }
860
GetInputMethod()861 InputMethod* InputMethodChromeOS::GetInputMethod() {
862 return this;
863 }
864
865 } // namespace ui
866