1// Copyright (c) 2012 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 "content/child/npapi/webplugin_ime_win.h"
6
7#include <cstring>
8#include <string>
9#include <vector>
10
11#include "base/lazy_instance.h"
12#include "base/logging.h"
13#include "base/strings/utf_string_conversions.h"
14#include "base/synchronization/lock.h"
15#include "content/child/npapi/plugin_instance.h"
16#include "content/common/plugin_constants_win.h"
17
18#pragma comment(lib, "imm32.lib")
19
20namespace content {
21
22// A critical section that prevents two or more plug-ins from accessing a
23// WebPluginIMEWin instance through our patch function.
24base::LazyInstance<base::Lock>::Leaky
25    g_webplugin_ime_lock = LAZY_INSTANCE_INITIALIZER;
26
27WebPluginIMEWin* WebPluginIMEWin::instance_ = NULL;
28
29WebPluginIMEWin::WebPluginIMEWin()
30    : cursor_position_(0),
31      delta_start_(0),
32      composing_text_(false),
33      support_ime_messages_(false),
34      status_updated_(false),
35      input_type_(1) {
36  memset(result_clauses_, 0, sizeof(result_clauses_));
37}
38
39WebPluginIMEWin::~WebPluginIMEWin() {
40}
41
42void WebPluginIMEWin::CompositionUpdated(const base::string16& text,
43                                         std::vector<int> clauses,
44                                         std::vector<int> target,
45                                         int cursor_position) {
46  // Send a WM_IME_STARTCOMPOSITION message when a user starts a composition.
47  NPEvent np_event;
48  if (!composing_text_) {
49    composing_text_ = true;
50    result_text_.clear();
51
52    np_event.event = WM_IME_STARTCOMPOSITION;
53    np_event.wParam = 0;
54    np_event.lParam = 0;
55    events_.push_back(np_event);
56  }
57
58  // We can update the following values from this event: GCS_COMPSTR,
59  // GCS_COMPATTR, GCSCOMPCLAUSE, GCS_CURSORPOS, and GCS_DELTASTART. We send a
60  // WM_IME_COMPOSITION message to notify the list of updated values.
61  np_event.event = WM_IME_COMPOSITION;
62  np_event.wParam = 0;
63  np_event.lParam = GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE |
64      GCS_CURSORPOS | GCS_DELTASTART;
65  events_.push_back(np_event);
66
67  // Converts this event to the IMM32 data so we do not have to convert it every
68  // time when a plug-in call an IMM32 function.
69  composition_text_ = text;
70
71  // Create the composition clauses returned when a plug-in calls
72  // ImmGetCompositionString() with GCS_COMPCLAUSE.
73  composition_clauses_.clear();
74  for (size_t i = 0; i < clauses.size(); ++i)
75    composition_clauses_.push_back(clauses[i]);
76
77  // Create the composition attributes used by GCS_COMPATTR.
78  if (target.size() == 2) {
79    composition_attributes_.assign(text.length(), ATTR_CONVERTED);
80    for (int i = target[0]; i < target[1]; ++i)
81        composition_attributes_[i] = ATTR_TARGET_CONVERTED;
82  } else {
83    composition_attributes_.assign(text.length(), ATTR_INPUT);
84  }
85
86  cursor_position_ = cursor_position;
87  delta_start_ = cursor_position;
88}
89
90void WebPluginIMEWin::CompositionCompleted(const base::string16& text) {
91  composing_text_ = false;
92
93  // We should update the following values when we finish a composition:
94  // GCS_RESULTSTR, GCS_RESULTCLAUSE, GCS_CURSORPOS, and GCS_DELTASTART. We
95  // send a WM_IME_COMPOSITION message to notify the list of updated values.
96  NPEvent np_event;
97  np_event.event = WM_IME_COMPOSITION;
98  np_event.wParam = 0;
99  np_event.lParam = GCS_CURSORPOS | GCS_DELTASTART | GCS_RESULTSTR |
100      GCS_RESULTCLAUSE;
101  events_.push_back(np_event);
102
103  // We also send a WM_IME_ENDCOMPOSITION message after the final
104  // WM_IME_COMPOSITION message (i.e. after finishing a composition).
105  np_event.event = WM_IME_ENDCOMPOSITION;
106  np_event.wParam = 0;
107  np_event.lParam = 0;
108  events_.push_back(np_event);
109
110  // If the target plug-in does not seem to support IME messages, we send
111  // each character in IME text with a WM_CHAR message so the plug-in can
112  // insert the IME text.
113  if (!support_ime_messages_) {
114    np_event.event = WM_CHAR;
115    np_event.wParam = 0;
116    np_event.lParam = 0;
117    for (size_t i = 0; i < result_text_.length(); ++i) {
118      np_event.wParam = result_text_[i];
119      events_.push_back(np_event);
120    }
121  }
122
123  // Updated the result text and its clause. (Unlike composition clauses, a
124  // result clause consists of only one region.)
125  result_text_ = text;
126
127  result_clauses_[0] = 0;
128  result_clauses_[1] = result_text_.length();
129
130  cursor_position_ = result_clauses_[1];
131  delta_start_ = result_clauses_[1];
132}
133
134bool WebPluginIMEWin::SendEvents(PluginInstance* instance) {
135  // We allow the patch functions to access this WebPluginIMEWin instance only
136  // while we send IME events to the plug-in.
137  ScopedLock lock(this);
138
139  bool ret = true;
140  for (std::vector<NPEvent>::iterator it = events_.begin();
141       it != events_.end(); ++it) {
142    if (!instance->NPP_HandleEvent(&(*it)))
143      ret = false;
144  }
145
146  events_.clear();
147  return ret;
148}
149
150bool WebPluginIMEWin::GetStatus(int* input_type, gfx::Rect* caret_rect) {
151  *input_type = input_type_;
152  *caret_rect = caret_rect_;
153  return true;
154}
155
156// static
157FARPROC WebPluginIMEWin::GetProcAddress(LPCSTR name) {
158  static const struct {
159    const char* name;
160    FARPROC function;
161  } kImm32Functions[] = {
162    { "ImmAssociateContextEx",
163        reinterpret_cast<FARPROC>(ImmAssociateContextEx) },
164    { "ImmGetCompositionStringW",
165        reinterpret_cast<FARPROC>(ImmGetCompositionStringW) },
166    { "ImmGetContext", reinterpret_cast<FARPROC>(ImmGetContext) },
167    { "ImmReleaseContext", reinterpret_cast<FARPROC>(ImmReleaseContext) },
168    { "ImmSetCandidateWindow",
169        reinterpret_cast<FARPROC>(ImmSetCandidateWindow) },
170    { "ImmSetOpenStatus", reinterpret_cast<FARPROC>(ImmSetOpenStatus) },
171  };
172  for (int i = 0; i < arraysize(kImm32Functions); ++i) {
173    if (!lstrcmpiA(name, kImm32Functions[i].name))
174      return kImm32Functions[i].function;
175  }
176  return NULL;
177}
178
179void WebPluginIMEWin::Lock() {
180  g_webplugin_ime_lock.Pointer()->Acquire();
181  instance_ = this;
182}
183
184void WebPluginIMEWin::Unlock() {
185  instance_ = NULL;
186  g_webplugin_ime_lock.Pointer()->Release();
187}
188
189// static
190WebPluginIMEWin* WebPluginIMEWin::GetInstance(HIMC context) {
191  return instance_ && context == reinterpret_cast<HIMC>(instance_) ?
192      instance_ : NULL;
193}
194
195// static
196BOOL WINAPI WebPluginIMEWin::ImmAssociateContextEx(HWND window,
197                                                   HIMC context,
198                                                   DWORD flags) {
199  WebPluginIMEWin* instance = GetInstance(context);
200  if (!instance)
201    return ::ImmAssociateContextEx(window, context, flags);
202
203  int input_type = !context && !flags;
204  instance->input_type_ = input_type;
205  instance->status_updated_ = true;
206  return TRUE;
207}
208
209// static
210LONG WINAPI WebPluginIMEWin::ImmGetCompositionStringW(HIMC context,
211                                                      DWORD index,
212                                                      LPVOID dst_data,
213                                                      DWORD dst_size) {
214  WebPluginIMEWin* instance = GetInstance(context);
215  if (!instance)
216    return ::ImmGetCompositionStringW(context, index, dst_data, dst_size);
217
218  const void* src_data = NULL;
219  DWORD src_size = 0;
220  switch (index) {
221    case GCS_COMPSTR:
222      src_data = instance->composition_text_.c_str();
223      src_size = instance->composition_text_.length() * sizeof(wchar_t);
224      break;
225
226    case GCS_COMPATTR:
227      src_data = instance->composition_attributes_.c_str();
228      src_size = instance->composition_attributes_.length();
229      break;
230
231    case GCS_COMPCLAUSE:
232      src_data = &instance->composition_clauses_[0];
233      src_size = instance->composition_clauses_.size() * sizeof(uint32);
234      break;
235
236    case GCS_CURSORPOS:
237      return instance->cursor_position_;
238
239    case GCS_DELTASTART:
240      return instance->delta_start_;
241
242    case GCS_RESULTSTR:
243      src_data = instance->result_text_.c_str();
244      src_size = instance->result_text_.length() * sizeof(wchar_t);
245      break;
246
247    case GCS_RESULTCLAUSE:
248      src_data = &instance->result_clauses_[0];
249      src_size = sizeof(instance->result_clauses_);
250      break;
251
252    default:
253      break;
254  }
255  if (!src_data || !src_size)
256    return IMM_ERROR_NODATA;
257
258  if (dst_size >= src_size)
259    memcpy(dst_data, src_data, src_size);
260
261  return src_size;
262}
263
264// static
265HIMC WINAPI WebPluginIMEWin::ImmGetContext(HWND window) {
266  // Call the original ImmGetContext() function if the given window is the one
267  // created in WebPluginDelegateImpl::WindowedCreatePlugin(). (We attached IME
268  // context only with the windows created in this function.) On the other hand,
269  // some windowless plug-ins (such as Flash) call this function with a dummy
270  // window handle. We return our dummy IME context for these plug-ins so they
271  // can use our IME emulator.
272  if (IsWindow(window)) {
273    wchar_t name[128];
274    GetClassName(window, &name[0], arraysize(name));
275    if (!wcscmp(&name[0], kNativeWindowClassName))
276      return ::ImmGetContext(window);
277  }
278
279  WebPluginIMEWin* instance = instance_;
280  if (instance)
281    instance->support_ime_messages_ = true;
282  return reinterpret_cast<HIMC>(instance);
283}
284
285// static
286BOOL WINAPI WebPluginIMEWin::ImmReleaseContext(HWND window, HIMC context) {
287  if (!GetInstance(context))
288    return ::ImmReleaseContext(window, context);
289  return TRUE;
290}
291
292// static
293BOOL WINAPI WebPluginIMEWin::ImmSetCandidateWindow(HIMC context,
294                                                   CANDIDATEFORM* candidate) {
295  WebPluginIMEWin* instance = GetInstance(context);
296  if (!instance)
297    return ::ImmSetCandidateWindow(context, candidate);
298
299  gfx::Rect caret_rect(candidate->rcArea);
300  if ((candidate->dwStyle & CFS_EXCLUDE) &&
301      instance->caret_rect_ != caret_rect) {
302    instance->caret_rect_ = caret_rect;
303    instance->status_updated_ = true;
304  }
305  return TRUE;
306}
307
308// static
309BOOL WINAPI WebPluginIMEWin::ImmSetOpenStatus(HIMC context, BOOL open) {
310  WebPluginIMEWin* instance = GetInstance(context);
311  if (!instance)
312    return ::ImmSetOpenStatus(context, open);
313
314  int input_type = open ? 1 : 0;
315  if (instance->input_type_ != input_type) {
316    instance->input_type_ = input_type;
317    instance->status_updated_ = true;
318  }
319
320  return TRUE;
321}
322
323}  // namespace content
324