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 20 namespace content { 21 22 // A critical section that prevents two or more plug-ins from accessing a 23 // WebPluginIMEWin instance through our patch function. 24 base::LazyInstance<base::Lock>::Leaky 25 g_webplugin_ime_lock = LAZY_INSTANCE_INITIALIZER; 26 27 WebPluginIMEWin* WebPluginIMEWin::instance_ = NULL; 28 29 WebPluginIMEWin::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 39 WebPluginIMEWin::~WebPluginIMEWin() { 40 } 41 42 void 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 90 void 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 134 bool 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 150 bool 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 157 FARPROC 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 179 void WebPluginIMEWin::Lock() { 180 g_webplugin_ime_lock.Pointer()->Acquire(); 181 instance_ = this; 182 } 183 184 void WebPluginIMEWin::Unlock() { 185 instance_ = NULL; 186 g_webplugin_ime_lock.Pointer()->Release(); 187 } 188 189 // static 190 WebPluginIMEWin* WebPluginIMEWin::GetInstance(HIMC context) { 191 return instance_ && context == reinterpret_cast<HIMC>(instance_) ? 192 instance_ : NULL; 193 } 194 195 // static 196 BOOL 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 210 LONG 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 265 HIMC 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 286 BOOL WINAPI WebPluginIMEWin::ImmReleaseContext(HWND window, HIMC context) { 287 if (!GetInstance(context)) 288 return ::ImmReleaseContext(window, context); 289 return TRUE; 290 } 291 292 // static 293 BOOL 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 309 BOOL 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