Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / win8 / metro_driver / ime / text_service.cc
1 // Copyright 2013 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 "win8/metro_driver/ime/text_service.h"
6
7 #include <msctf.h>
8
9 #include "base/logging.h"
10 #include "base/win/scoped_variant.h"
11 #include "ui/metro_viewer/ime_types.h"
12 #include "win8/metro_driver/ime/text_service_delegate.h"
13 #include "win8/metro_driver/ime/text_store.h"
14 #include "win8/metro_driver/ime/text_store_delegate.h"
15
16 // Architecture overview of input method support on Ash mode:
17 //
18 // Overview:
19 // On Ash mode, the system keyboard focus is owned by the metro_driver process
20 // while most of event handling are still implemented in the browser process.
21 // Thus the metro_driver basically works as a proxy that simply forwards
22 // keyevents to the metro_driver process. IME support must be involved somewhere
23 // in this flow.
24 //
25 // In short, we need to interact with an IME in the metro_driver process since
26 // TSF (Text Services Framework) runtime wants to processes keyevents while
27 // (and only while) the attached UI thread owns keyboard focus.
28 //
29 // Due to this limitation, we need to split IME handling into two parts, one
30 // is in the metro_driver process and the other is in the browser process.
31 // The metro_driver process is responsible for implementing the primary data
32 // store for the composition text and wiring it up with an IME via TSF APIs.
33 // On the other hand, the browser process is responsible for calculating
34 // character position in the composition text whenever the composition text
35 // is updated.
36 //
37 // IPC overview:
38 // Fortunately, we don't need so many IPC messages to support IMEs. In fact,
39 // only 4 messages are required to enable basic IME functionality.
40 //
41 //   metro_driver process -> browser process
42 //     Message Type:
43 //       - MetroViewerHostMsg_ImeCompositionChanged
44 //       - MetroViewerHostMsg_ImeTextCommitted
45 //     Message Routing:
46 //       TextServiceImpl
47 //         -> ChromeAppViewAsh
48 //         -- (process boundary) --
49 //         -> RemoteWindowTreeHostWin
50 //         -> RemoteInputMethodWin
51 //
52 //   browser process -> metro_driver process
53 //     Message Type:
54 //       - MetroViewerHostMsg_ImeCancelComposition
55 //       - MetroViewerHostMsg_ImeTextInputClientUpdated
56 //     Message Routing:
57 //       RemoteInputMethodWin
58 //         -> RemoteWindowTreeHostWin
59 //         -- (process boundary) --
60 //         -> ChromeAppViewAsh
61 //         -> TextServiceImpl
62 //
63 // Note that a keyevent may be forwarded through a different path. When a
64 // keyevent is not handled by an IME, such keyevent and subsequent character
65 // events will be sent from the metro_driver process to the browser process as
66 // following IPC messages.
67 //  - MetroViewerHostMsg_KeyDown
68 //  - MetroViewerHostMsg_KeyUp
69 //  - MetroViewerHostMsg_Character
70 //
71 // How TextServiceImpl works:
72 // Here is the list of the major tasks that are handled in TextServiceImpl.
73 //  - Manages a session object obtained from TSF runtime. We need them to call
74 //    most of TSF APIs.
75 //  - Handles OnDocumentChanged event. Whenever the document type is changed,
76 //    TextServiceImpl destroyes the current document and initializes new one
77 //    according to the given |input_scopes|.
78 //  - Stores the |composition_character_bounds_| passed from OnDocumentChanged
79 //    event so that an IME or on-screen keyboard can query the character
80 //    position synchronously.
81 // The most complicated part is the OnDocumentChanged handler. Since some IMEs
82 // such as Japanese IMEs drastically change their behavior depending on
83 // properties exposed from the virtual document, we need to set up a lot
84 // properties carefully and correctly. See DocumentBinding class in this file
85 // about what will be involved in this multi-phase construction. See also
86 // text_store.cc and input_scope.cc for more underlying details.
87
88 namespace metro_driver {
89 namespace {
90
91 // Japanese IME expects the default value of this compartment is
92 // TF_SENTENCEMODE_PHRASEPREDICT to emulate IMM32 behavior. This value is
93 // managed per thread, thus setting this value at once is sufficient. This
94 // value never affects non-Japanese IMEs.
95 bool InitializeSentenceMode(ITfThreadMgr2* thread_manager,
96                             TfClientId client_id) {
97   base::win::ScopedComPtr<ITfCompartmentMgr> thread_compartment_manager;
98   HRESULT hr = thread_compartment_manager.QueryFrom(thread_manager);
99   if (FAILED(hr)) {
100     LOG(ERROR) << "QueryFrom failed. hr = " << hr;
101     return false;
102   }
103   base::win::ScopedComPtr<ITfCompartment> sentence_compartment;
104   hr = thread_compartment_manager->GetCompartment(
105       GUID_COMPARTMENT_KEYBOARD_INPUTMODE_SENTENCE,
106       sentence_compartment.Receive());
107   if (FAILED(hr)) {
108     LOG(ERROR) << "ITfCompartment::GetCompartment failed. hr = " << hr;
109     return false;
110   }
111
112   base::win::ScopedVariant sentence_variant;
113   sentence_variant.Set(TF_SENTENCEMODE_PHRASEPREDICT);
114   hr = sentence_compartment->SetValue(client_id, &sentence_variant);
115   if (FAILED(hr)) {
116     LOG(ERROR) << "ITfCompartment::SetValue failed. hr = " << hr;
117     return false;
118   }
119   return true;
120 }
121
122 // Initializes |context| as disabled context where IMEs will be disabled.
123 bool InitializeDisabledContext(ITfContext* context, TfClientId client_id) {
124   base::win::ScopedComPtr<ITfCompartmentMgr> compartment_mgr;
125   HRESULT hr = compartment_mgr.QueryFrom(context);
126   if (FAILED(hr)) {
127     LOG(ERROR) << "QueryFrom failed. hr = " << hr;
128     return false;
129   }
130
131   base::win::ScopedComPtr<ITfCompartment> disabled_compartment;
132   hr = compartment_mgr->GetCompartment(GUID_COMPARTMENT_KEYBOARD_DISABLED,
133                                        disabled_compartment.Receive());
134   if (FAILED(hr)) {
135     LOG(ERROR) << "ITfCompartment::GetCompartment failed. hr = " << hr;
136     return false;
137   }
138
139   base::win::ScopedVariant variant;
140   variant.Set(1);
141   hr = disabled_compartment->SetValue(client_id, &variant);
142   if (FAILED(hr)) {
143     LOG(ERROR) << "ITfCompartment::SetValue failed. hr = " << hr;
144     return false;
145   }
146
147   base::win::ScopedComPtr<ITfCompartment> empty_context;
148   hr = compartment_mgr->GetCompartment(GUID_COMPARTMENT_EMPTYCONTEXT,
149                                        empty_context.Receive());
150   if (FAILED(hr)) {
151     LOG(ERROR) << "ITfCompartment::GetCompartment failed. hr = " << hr;
152     return false;
153   }
154
155   base::win::ScopedVariant empty_context_variant;
156   empty_context_variant.Set(static_cast<int32>(1));
157   hr = empty_context->SetValue(client_id, &empty_context_variant);
158   if (FAILED(hr)) {
159     LOG(ERROR) << "ITfCompartment::SetValue failed. hr = " << hr;
160     return false;
161   }
162
163   return true;
164 }
165
166 bool IsPasswordField(const std::vector<InputScope>& input_scopes) {
167   return std::find(input_scopes.begin(), input_scopes.end(), IS_PASSWORD) !=
168       input_scopes.end();
169 }
170
171 // A class that manages the lifetime of the event callback registration. When
172 // this object is destroyed, corresponding event callback will be unregistered.
173 class EventSink {
174  public:
175   EventSink(DWORD cookie, base::win::ScopedComPtr<ITfSource> source)
176       : cookie_(cookie),
177         source_(source) {}
178   ~EventSink() {
179     if (!source_ || cookie_ != TF_INVALID_COOKIE)
180       return;
181     source_->UnadviseSink(cookie_);
182     cookie_ = TF_INVALID_COOKIE;
183     source_.Release();
184   }
185
186  private:
187   DWORD cookie_;
188   base::win::ScopedComPtr<ITfSource> source_;
189   DISALLOW_COPY_AND_ASSIGN(EventSink);
190 };
191
192 scoped_ptr<EventSink> CreateTextEditSink(ITfContext* context,
193                                          ITfTextEditSink* text_store) {
194   DCHECK(text_store);
195   base::win::ScopedComPtr<ITfSource> source;
196   DWORD cookie = TF_INVALID_EDIT_COOKIE;
197   HRESULT hr = source.QueryFrom(context);
198   if (FAILED(hr)) {
199     LOG(ERROR) << "QueryFrom failed, hr = " << hr;
200     return scoped_ptr<EventSink>();
201   }
202   hr = source->AdviseSink(IID_ITfTextEditSink, text_store, &cookie);
203   if (FAILED(hr)) {
204     LOG(ERROR) << "AdviseSink failed, hr = " << hr;
205     return scoped_ptr<EventSink>();
206   }
207   return scoped_ptr<EventSink>(new EventSink(cookie, source));
208 }
209
210 // A set of objects that should have the same lifetime. Following things
211 // are maintained.
212 //  - TextStore: a COM object that abstracts text buffer. This object is
213 //      actually implemented by us in text_store.cc
214 //  - ITfDocumentMgr: a focusable unit in TSF. This object is implemented by
215 //      TSF runtime and works as a container of TextStore.
216 //  - EventSink: an object that ensures that the event callback between
217 //      TSF runtime and TextStore is unregistered when this object is destroyed.
218 class DocumentBinding {
219  public:
220   ~DocumentBinding() {
221     if (!document_manager_)
222       return;
223     document_manager_->Pop(TF_POPF_ALL);
224   }
225
226   static scoped_ptr<DocumentBinding> Create(
227       ITfThreadMgr2* thread_manager,
228       TfClientId client_id,
229       const std::vector<InputScope>& input_scopes,
230       HWND window_handle,
231       TextStoreDelegate* delegate) {
232     base::win::ScopedComPtr<ITfDocumentMgr> document_manager;
233     HRESULT hr = thread_manager->CreateDocumentMgr(document_manager.Receive());
234     if (FAILED(hr)) {
235       LOG(ERROR) << "ITfThreadMgr2::CreateDocumentMgr failed. hr = " << hr;
236       return scoped_ptr<DocumentBinding>();
237     }
238
239     // Note: In our IPC protocol, an empty |input_scopes| is used to indicate
240     // that an IME must be disabled in this context. In such case, we need not
241     // instantiate TextStore.
242     const bool use_null_text_store = input_scopes.empty();
243
244     scoped_refptr<TextStore> text_store;
245     if (!use_null_text_store) {
246       text_store = TextStore::Create(window_handle, input_scopes, delegate);
247       if (!text_store) {
248         LOG(ERROR) << "Failed to create TextStore.";
249         return scoped_ptr<DocumentBinding>();
250       }
251     }
252
253     base::win::ScopedComPtr<ITfContext> context;
254     DWORD edit_cookie = TF_INVALID_EDIT_COOKIE;
255     hr = document_manager->CreateContext(
256         client_id,
257         0,
258         static_cast<ITextStoreACP*>(text_store.get()),
259         context.Receive(),
260         &edit_cookie);
261     if (FAILED(hr)) {
262       LOG(ERROR) << "ITfDocumentMgr::CreateContext failed. hr = " << hr;
263       return scoped_ptr<DocumentBinding>();
264     }
265
266     // If null-TextStore is used or |input_scopes| looks like a password field,
267     // set special properties to tell IMEs to be disabled.
268     if ((use_null_text_store || IsPasswordField(input_scopes)) &&
269         !InitializeDisabledContext(context, client_id)) {
270       LOG(ERROR) << "InitializeDisabledContext failed.";
271       return scoped_ptr<DocumentBinding>();
272     }
273
274     scoped_ptr<EventSink> text_edit_sink;
275     if (!use_null_text_store) {
276       text_edit_sink = CreateTextEditSink(context, text_store);
277       if (!text_edit_sink) {
278         LOG(ERROR) << "CreateTextEditSink failed.";
279         return scoped_ptr<DocumentBinding>();
280       }
281     }
282     hr = document_manager->Push(context);
283     if (FAILED(hr)) {
284       LOG(ERROR) << "ITfDocumentMgr::Push failed. hr = " << hr;
285       return scoped_ptr<DocumentBinding>();
286     }
287     return scoped_ptr<DocumentBinding>(
288         new DocumentBinding(text_store,
289                             document_manager,
290                             text_edit_sink.Pass()));
291   }
292
293   ITfDocumentMgr* document_manager() const {
294     return document_manager_;
295   }
296
297   scoped_refptr<TextStore> text_store() const {
298     return text_store_;
299   }
300
301  private:
302   DocumentBinding(scoped_refptr<TextStore> text_store,
303                   base::win::ScopedComPtr<ITfDocumentMgr> document_manager,
304                   scoped_ptr<EventSink> text_edit_sink)
305       : text_store_(text_store),
306         document_manager_(document_manager),
307         text_edit_sink_(text_edit_sink.Pass()) {}
308
309   scoped_refptr<TextStore> text_store_;
310   base::win::ScopedComPtr<ITfDocumentMgr> document_manager_;
311   scoped_ptr<EventSink> text_edit_sink_;
312
313   DISALLOW_COPY_AND_ASSIGN(DocumentBinding);
314 };
315
316 class TextServiceImpl : public TextService,
317                         public TextStoreDelegate {
318  public:
319   TextServiceImpl(ITfThreadMgr2* thread_manager,
320                   TfClientId client_id,
321                   HWND window_handle,
322                   TextServiceDelegate* delegate)
323       : client_id_(client_id),
324         window_handle_(window_handle),
325         delegate_(delegate),
326         thread_manager_(thread_manager) {
327     DCHECK_NE(TF_CLIENTID_NULL, client_id);
328     DCHECK(window_handle != NULL);
329     DCHECK(thread_manager_);
330   }
331   virtual ~TextServiceImpl() {
332     thread_manager_->Deactivate();
333   }
334
335  private:
336   // TextService overrides:
337   virtual void CancelComposition() OVERRIDE {
338     if (!current_document_) {
339       VLOG(0) << "|current_document_| is NULL due to the previous error.";
340       return;
341     }
342     TextStore* text_store = current_document_->text_store();
343     if (!text_store)
344       return;
345     text_store->CancelComposition();
346   }
347
348   virtual void OnDocumentChanged(
349       const std::vector<int32>& input_scopes,
350       const std::vector<metro_viewer::CharacterBounds>& character_bounds)
351       OVERRIDE {
352     bool document_type_changed = input_scopes_ != input_scopes;
353     input_scopes_ = input_scopes;
354     composition_character_bounds_ = character_bounds;
355     if (document_type_changed)
356       OnDocumentTypeChanged(input_scopes);
357   }
358
359   virtual void OnWindowActivated() OVERRIDE {
360     if (!current_document_) {
361       VLOG(0) << "|current_document_| is NULL due to the previous error.";
362       return;
363     }
364     ITfDocumentMgr* document_manager = current_document_->document_manager();
365     if (!document_manager) {
366       VLOG(0) << "|document_manager| is NULL due to the previous error.";
367       return;
368     }
369     HRESULT hr = thread_manager_->SetFocus(document_manager);
370     if (FAILED(hr)) {
371       LOG(ERROR) << "ITfThreadMgr2::SetFocus failed. hr = " << hr;
372       return;
373     }
374   }
375
376   virtual void OnCompositionChanged(
377       const base::string16& text,
378       int32 selection_start,
379       int32 selection_end,
380       const std::vector<metro_viewer::UnderlineInfo>& underlines) OVERRIDE {
381     if (!delegate_)
382       return;
383     delegate_->OnCompositionChanged(text,
384                                     selection_start,
385                                     selection_end,
386                                     underlines);
387   }
388
389   virtual void OnTextCommitted(const base::string16& text) OVERRIDE {
390     if (!delegate_)
391       return;
392     delegate_->OnTextCommitted(text);
393   }
394
395   virtual RECT GetCaretBounds() {
396     if (composition_character_bounds_.empty()) {
397       const RECT rect = {};
398       return rect;
399     }
400     const metro_viewer::CharacterBounds& bounds =
401         composition_character_bounds_[0];
402     POINT left_top = { bounds.left, bounds.top };
403     POINT right_bottom = { bounds.right, bounds.bottom };
404     ClientToScreen(window_handle_, &left_top);
405     ClientToScreen(window_handle_, &right_bottom);
406     const RECT rect = {
407       left_top.x,
408       left_top.y,
409       right_bottom.x,
410       right_bottom.y,
411     };
412     return rect;
413   }
414
415   virtual bool GetCompositionCharacterBounds(uint32 index,
416                                              RECT* rect) OVERRIDE {
417     if (index >= composition_character_bounds_.size()) {
418       return false;
419     }
420     const metro_viewer::CharacterBounds& bounds =
421         composition_character_bounds_[index];
422     POINT left_top = { bounds.left, bounds.top };
423     POINT right_bottom = { bounds.right, bounds.bottom };
424     ClientToScreen(window_handle_, &left_top);
425     ClientToScreen(window_handle_, &right_bottom);
426     SetRect(rect, left_top.x, left_top.y, right_bottom.x, right_bottom.y);
427     return true;
428   }
429
430   void OnDocumentTypeChanged(const std::vector<int32>& input_scopes) {
431     std::vector<InputScope> native_input_scopes(input_scopes.size());
432     for (size_t i = 0; i < input_scopes.size(); ++i)
433       native_input_scopes[i] = static_cast<InputScope>(input_scopes[i]);
434     scoped_ptr<DocumentBinding> new_document =
435         DocumentBinding::Create(thread_manager_.get(),
436                                 client_id_,
437                                 native_input_scopes,
438                                 window_handle_,
439                                 this);
440     LOG_IF(ERROR, !new_document) << "Failed to create a new document.";
441     current_document_.swap(new_document);
442     OnWindowActivated();
443   }
444
445   TfClientId client_id_;
446   HWND window_handle_;
447   TextServiceDelegate* delegate_;
448   scoped_ptr<DocumentBinding> current_document_;
449   base::win::ScopedComPtr<ITfThreadMgr2> thread_manager_;
450
451   // A vector of InputScope enumeration, which represents the document type of
452   // the focused text field. Note that in our IPC message protocol, an empty
453   // |input_scopes_| has special meaning that IMEs must be disabled on this
454   // document.
455   std::vector<int32> input_scopes_;
456   // Character bounds of the composition. When there is no composition but this
457   // vector is not empty, the first element contains the caret bounds.
458   std::vector<metro_viewer::CharacterBounds> composition_character_bounds_;
459
460   DISALLOW_COPY_AND_ASSIGN(TextServiceImpl);
461 };
462
463 }  // namespace
464
465 scoped_ptr<TextService>
466 CreateTextService(TextServiceDelegate* delegate, HWND window_handle) {
467   if (!delegate)
468     return scoped_ptr<TextService>();
469   base::win::ScopedComPtr<ITfThreadMgr2> thread_manager;
470   HRESULT hr = thread_manager.CreateInstance(CLSID_TF_ThreadMgr);
471   if (FAILED(hr)) {
472     LOG(ERROR) << "Failed to create instance of CLSID_TF_ThreadMgr. hr = "
473                << hr;
474     return scoped_ptr<TextService>();
475   }
476   TfClientId client_id = TF_CLIENTID_NULL;
477   hr = thread_manager->ActivateEx(&client_id, 0);
478   if (FAILED(hr)) {
479     LOG(ERROR) << "ITfThreadMgr2::ActivateEx failed. hr = " << hr;
480     return scoped_ptr<TextService>();
481   }
482   if (!InitializeSentenceMode(thread_manager, client_id)) {
483     LOG(ERROR) << "InitializeSentenceMode failed.";
484     thread_manager->Deactivate();
485     return scoped_ptr<TextService>();
486   }
487   return scoped_ptr<TextService>(new TextServiceImpl(thread_manager,
488                                                      client_id,
489                                                      window_handle,
490                                                      delegate));
491 }
492
493 }  // namespace metro_driver