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