|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "WinIMEHandler.h" |
|
7 |
|
8 #include "mozilla/Preferences.h" |
|
9 #include "nsIMM32Handler.h" |
|
10 #include "nsWindowDefs.h" |
|
11 |
|
12 #ifdef NS_ENABLE_TSF |
|
13 #include "nsTextStore.h" |
|
14 #endif // #ifdef NS_ENABLE_TSF |
|
15 |
|
16 #include "nsWindow.h" |
|
17 #include "WinUtils.h" |
|
18 |
|
19 namespace mozilla { |
|
20 namespace widget { |
|
21 |
|
22 /****************************************************************************** |
|
23 * IMEHandler |
|
24 ******************************************************************************/ |
|
25 |
|
26 #ifdef NS_ENABLE_TSF |
|
27 bool IMEHandler::sIsInTSFMode = false; |
|
28 bool IMEHandler::sIsIMMEnabled = true; |
|
29 bool IMEHandler::sPluginHasFocus = false; |
|
30 decltype(SetInputScopes)* IMEHandler::sSetInputScopes = nullptr; |
|
31 #endif // #ifdef NS_ENABLE_TSF |
|
32 |
|
33 // static |
|
34 void |
|
35 IMEHandler::Initialize() |
|
36 { |
|
37 #ifdef NS_ENABLE_TSF |
|
38 nsTextStore::Initialize(); |
|
39 sIsInTSFMode = nsTextStore::IsInTSFMode(); |
|
40 sIsIMMEnabled = |
|
41 !sIsInTSFMode || Preferences::GetBool("intl.tsf.support_imm", true); |
|
42 if (!sIsInTSFMode) { |
|
43 // When full nsTextStore is not available, try to use SetInputScopes API |
|
44 // to enable at least InputScope. Use GET_MODULE_HANDLE_EX_FLAG_PIN to |
|
45 // ensure that msctf.dll will not be unloaded. |
|
46 HMODULE module = nullptr; |
|
47 if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, L"msctf.dll", |
|
48 &module)) { |
|
49 sSetInputScopes = reinterpret_cast<decltype(SetInputScopes)*>( |
|
50 GetProcAddress(module, "SetInputScopes")); |
|
51 } |
|
52 } |
|
53 #endif // #ifdef NS_ENABLE_TSF |
|
54 |
|
55 nsIMM32Handler::Initialize(); |
|
56 } |
|
57 |
|
58 // static |
|
59 void |
|
60 IMEHandler::Terminate() |
|
61 { |
|
62 #ifdef NS_ENABLE_TSF |
|
63 if (sIsInTSFMode) { |
|
64 nsTextStore::Terminate(); |
|
65 sIsInTSFMode = false; |
|
66 } |
|
67 #endif // #ifdef NS_ENABLE_TSF |
|
68 |
|
69 nsIMM32Handler::Terminate(); |
|
70 } |
|
71 |
|
72 // static |
|
73 void* |
|
74 IMEHandler::GetNativeData(uint32_t aDataType) |
|
75 { |
|
76 #ifdef NS_ENABLE_TSF |
|
77 void* result = nsTextStore::GetNativeData(aDataType); |
|
78 if (!result || !(*(static_cast<void**>(result)))) { |
|
79 return nullptr; |
|
80 } |
|
81 // XXX During the TSF module test, sIsInTSFMode must be true. After that, |
|
82 // the value should be restored but currently, there is no way for that. |
|
83 // When the TSF test is enabled again, we need to fix this. Perhaps, |
|
84 // sending a message can fix this. |
|
85 sIsInTSFMode = true; |
|
86 return result; |
|
87 #else // #ifdef NS_ENABLE_TSF |
|
88 return nullptr; |
|
89 #endif // #ifdef NS_ENABLE_TSF #else |
|
90 } |
|
91 |
|
92 // static |
|
93 bool |
|
94 IMEHandler::ProcessRawKeyMessage(const MSG& aMsg) |
|
95 { |
|
96 #ifdef NS_ENABLE_TSF |
|
97 if (IsTSFAvailable()) { |
|
98 return nsTextStore::ProcessRawKeyMessage(aMsg); |
|
99 } |
|
100 #endif // #ifdef NS_ENABLE_TSF |
|
101 return false; // noting to do in IMM mode. |
|
102 } |
|
103 |
|
104 // static |
|
105 bool |
|
106 IMEHandler::ProcessMessage(nsWindow* aWindow, UINT aMessage, |
|
107 WPARAM& aWParam, LPARAM& aLParam, |
|
108 MSGResult& aResult) |
|
109 { |
|
110 #ifdef NS_ENABLE_TSF |
|
111 if (IsTSFAvailable()) { |
|
112 nsTextStore::ProcessMessage(aWindow, aMessage, aWParam, aLParam, aResult); |
|
113 if (aResult.mConsumed) { |
|
114 return true; |
|
115 } |
|
116 // If we don't support IMM in TSF mode, we don't use nsIMM32Handler. |
|
117 if (!sIsIMMEnabled) { |
|
118 return false; |
|
119 } |
|
120 // IME isn't implemented with IMM, nsIMM32Handler shouldn't handle any |
|
121 // messages. |
|
122 if (!nsTextStore::IsIMM_IME()) { |
|
123 return false; |
|
124 } |
|
125 } |
|
126 #endif // #ifdef NS_ENABLE_TSF |
|
127 |
|
128 return nsIMM32Handler::ProcessMessage(aWindow, aMessage, aWParam, aLParam, |
|
129 aResult); |
|
130 } |
|
131 |
|
132 // static |
|
133 bool |
|
134 IMEHandler::IsComposing() |
|
135 { |
|
136 #ifdef NS_ENABLE_TSF |
|
137 if (IsTSFAvailable()) { |
|
138 return nsTextStore::IsComposing(); |
|
139 } |
|
140 #endif // #ifdef NS_ENABLE_TSF |
|
141 |
|
142 return nsIMM32Handler::IsComposing(); |
|
143 } |
|
144 |
|
145 // static |
|
146 bool |
|
147 IMEHandler::IsComposingOn(nsWindow* aWindow) |
|
148 { |
|
149 #ifdef NS_ENABLE_TSF |
|
150 if (IsTSFAvailable()) { |
|
151 return nsTextStore::IsComposingOn(aWindow); |
|
152 } |
|
153 #endif // #ifdef NS_ENABLE_TSF |
|
154 |
|
155 return nsIMM32Handler::IsComposingOn(aWindow); |
|
156 } |
|
157 |
|
158 // static |
|
159 nsresult |
|
160 IMEHandler::NotifyIME(nsWindow* aWindow, |
|
161 const IMENotification& aIMENotification) |
|
162 { |
|
163 #ifdef NS_ENABLE_TSF |
|
164 if (IsTSFAvailable()) { |
|
165 switch (aIMENotification.mMessage) { |
|
166 case NOTIFY_IME_OF_SELECTION_CHANGE: |
|
167 return nsTextStore::OnSelectionChange(); |
|
168 case NOTIFY_IME_OF_TEXT_CHANGE: |
|
169 return nsTextStore::OnTextChange(aIMENotification); |
|
170 case NOTIFY_IME_OF_FOCUS: |
|
171 return nsTextStore::OnFocusChange(true, aWindow, |
|
172 aWindow->GetInputContext().mIMEState.mEnabled); |
|
173 case NOTIFY_IME_OF_BLUR: |
|
174 return nsTextStore::OnFocusChange(false, aWindow, |
|
175 aWindow->GetInputContext().mIMEState.mEnabled); |
|
176 case REQUEST_TO_COMMIT_COMPOSITION: |
|
177 if (nsTextStore::IsComposingOn(aWindow)) { |
|
178 nsTextStore::CommitComposition(false); |
|
179 } |
|
180 return NS_OK; |
|
181 case REQUEST_TO_CANCEL_COMPOSITION: |
|
182 if (nsTextStore::IsComposingOn(aWindow)) { |
|
183 nsTextStore::CommitComposition(true); |
|
184 } |
|
185 return NS_OK; |
|
186 case NOTIFY_IME_OF_POSITION_CHANGE: |
|
187 return nsTextStore::OnLayoutChange(); |
|
188 default: |
|
189 return NS_ERROR_NOT_IMPLEMENTED; |
|
190 } |
|
191 } |
|
192 #endif //NS_ENABLE_TSF |
|
193 |
|
194 switch (aIMENotification.mMessage) { |
|
195 case REQUEST_TO_COMMIT_COMPOSITION: |
|
196 nsIMM32Handler::CommitComposition(aWindow); |
|
197 return NS_OK; |
|
198 case REQUEST_TO_CANCEL_COMPOSITION: |
|
199 nsIMM32Handler::CancelComposition(aWindow); |
|
200 return NS_OK; |
|
201 case NOTIFY_IME_OF_POSITION_CHANGE: |
|
202 case NOTIFY_IME_OF_COMPOSITION_UPDATE: |
|
203 nsIMM32Handler::OnUpdateComposition(aWindow); |
|
204 return NS_OK; |
|
205 #ifdef NS_ENABLE_TSF |
|
206 case NOTIFY_IME_OF_BLUR: |
|
207 // If a plugin gets focus while TSF has focus, we need to notify TSF of |
|
208 // the blur. |
|
209 if (nsTextStore::ThinksHavingFocus()) { |
|
210 return nsTextStore::OnFocusChange(false, aWindow, |
|
211 aWindow->GetInputContext().mIMEState.mEnabled); |
|
212 } |
|
213 return NS_ERROR_NOT_IMPLEMENTED; |
|
214 #endif //NS_ENABLE_TSF |
|
215 default: |
|
216 return NS_ERROR_NOT_IMPLEMENTED; |
|
217 } |
|
218 } |
|
219 |
|
220 // static |
|
221 nsIMEUpdatePreference |
|
222 IMEHandler::GetUpdatePreference() |
|
223 { |
|
224 #ifdef NS_ENABLE_TSF |
|
225 if (IsTSFAvailable()) { |
|
226 return nsTextStore::GetIMEUpdatePreference(); |
|
227 } |
|
228 #endif //NS_ENABLE_TSF |
|
229 |
|
230 return nsIMM32Handler::GetIMEUpdatePreference(); |
|
231 } |
|
232 |
|
233 // static |
|
234 bool |
|
235 IMEHandler::GetOpenState(nsWindow* aWindow) |
|
236 { |
|
237 #ifdef NS_ENABLE_TSF |
|
238 if (IsTSFAvailable()) { |
|
239 return nsTextStore::GetIMEOpenState(); |
|
240 } |
|
241 #endif //NS_ENABLE_TSF |
|
242 |
|
243 nsIMEContext IMEContext(aWindow->GetWindowHandle()); |
|
244 return IMEContext.GetOpenState(); |
|
245 } |
|
246 |
|
247 // static |
|
248 void |
|
249 IMEHandler::OnDestroyWindow(nsWindow* aWindow) |
|
250 { |
|
251 #ifdef NS_ENABLE_TSF |
|
252 // We need to do nothing here for TSF. Just restore the default context |
|
253 // if it's been disassociated. |
|
254 if (!sIsInTSFMode) { |
|
255 // MSDN says we need to set IS_DEFAULT to avoid memory leak when we use |
|
256 // SetInputScopes API. Use an empty string to do this. |
|
257 SetInputScopeForIMM32(aWindow, EmptyString()); |
|
258 } |
|
259 #endif // #ifdef NS_ENABLE_TSF |
|
260 AssociateIMEContext(aWindow, true); |
|
261 } |
|
262 |
|
263 // static |
|
264 void |
|
265 IMEHandler::SetInputContext(nsWindow* aWindow, |
|
266 InputContext& aInputContext, |
|
267 const InputContextAction& aAction) |
|
268 { |
|
269 // FYI: If there is no composition, this call will do nothing. |
|
270 NotifyIME(aWindow, IMENotification(REQUEST_TO_COMMIT_COMPOSITION)); |
|
271 |
|
272 const InputContext& oldInputContext = aWindow->GetInputContext(); |
|
273 |
|
274 // Assume that SetInputContext() is called only when aWindow has focus. |
|
275 sPluginHasFocus = (aInputContext.mIMEState.mEnabled == IMEState::PLUGIN); |
|
276 |
|
277 bool enable = WinUtils::IsIMEEnabled(aInputContext); |
|
278 bool adjustOpenState = (enable && |
|
279 aInputContext.mIMEState.mOpen != IMEState::DONT_CHANGE_OPEN_STATE); |
|
280 bool open = (adjustOpenState && |
|
281 aInputContext.mIMEState.mOpen == IMEState::OPEN); |
|
282 |
|
283 aInputContext.mNativeIMEContext = nullptr; |
|
284 |
|
285 #ifdef NS_ENABLE_TSF |
|
286 // Note that even while a plugin has focus, we need to notify TSF of that. |
|
287 if (sIsInTSFMode) { |
|
288 nsTextStore::SetInputContext(aWindow, aInputContext, aAction); |
|
289 if (IsTSFAvailable()) { |
|
290 aInputContext.mNativeIMEContext = nsTextStore::GetTextStore(); |
|
291 if (sIsIMMEnabled) { |
|
292 // Associate IME context for IMM-IMEs. |
|
293 AssociateIMEContext(aWindow, enable); |
|
294 } else if (oldInputContext.mIMEState.mEnabled == IMEState::PLUGIN) { |
|
295 // Disassociate the IME context from the window when plugin loses focus |
|
296 // in pure TSF mode. |
|
297 AssociateIMEContext(aWindow, false); |
|
298 } |
|
299 if (adjustOpenState) { |
|
300 nsTextStore::SetIMEOpenState(open); |
|
301 } |
|
302 return; |
|
303 } |
|
304 } else { |
|
305 // Set at least InputScope even when TextStore is not available. |
|
306 SetInputScopeForIMM32(aWindow, aInputContext.mHTMLInputType); |
|
307 } |
|
308 #endif // #ifdef NS_ENABLE_TSF |
|
309 |
|
310 AssociateIMEContext(aWindow, enable); |
|
311 |
|
312 nsIMEContext IMEContext(aWindow->GetWindowHandle()); |
|
313 if (adjustOpenState) { |
|
314 IMEContext.SetOpenState(open); |
|
315 } |
|
316 |
|
317 if (aInputContext.mNativeIMEContext) { |
|
318 return; |
|
319 } |
|
320 |
|
321 // The old InputContext must store the default IMC or old TextStore. |
|
322 // When IME context is disassociated from the window, use it. |
|
323 aInputContext.mNativeIMEContext = enable ? |
|
324 static_cast<void*>(IMEContext.get()) : oldInputContext.mNativeIMEContext; |
|
325 } |
|
326 |
|
327 // static |
|
328 void |
|
329 IMEHandler::AssociateIMEContext(nsWindow* aWindow, bool aEnable) |
|
330 { |
|
331 nsIMEContext IMEContext(aWindow->GetWindowHandle()); |
|
332 if (aEnable) { |
|
333 IMEContext.AssociateDefaultContext(); |
|
334 return; |
|
335 } |
|
336 // Don't disassociate the context after the window is destroyed. |
|
337 if (aWindow->Destroyed()) { |
|
338 return; |
|
339 } |
|
340 IMEContext.Disassociate(); |
|
341 } |
|
342 |
|
343 // static |
|
344 void |
|
345 IMEHandler::InitInputContext(nsWindow* aWindow, InputContext& aInputContext) |
|
346 { |
|
347 // For a11y, the default enabled state should be 'enabled'. |
|
348 aInputContext.mIMEState.mEnabled = IMEState::ENABLED; |
|
349 |
|
350 #ifdef NS_ENABLE_TSF |
|
351 if (sIsInTSFMode) { |
|
352 nsTextStore::SetInputContext(aWindow, aInputContext, |
|
353 InputContextAction(InputContextAction::CAUSE_UNKNOWN, |
|
354 InputContextAction::GOT_FOCUS)); |
|
355 aInputContext.mNativeIMEContext = nsTextStore::GetTextStore(); |
|
356 MOZ_ASSERT(aInputContext.mNativeIMEContext); |
|
357 // IME context isn't necessary in pure TSF mode. |
|
358 if (!sIsIMMEnabled) { |
|
359 AssociateIMEContext(aWindow, false); |
|
360 } |
|
361 return; |
|
362 } |
|
363 #endif // #ifdef NS_ENABLE_TSF |
|
364 |
|
365 // NOTE: mNativeIMEContext may be null if IMM module isn't installed. |
|
366 nsIMEContext IMEContext(aWindow->GetWindowHandle()); |
|
367 aInputContext.mNativeIMEContext = static_cast<void*>(IMEContext.get()); |
|
368 MOZ_ASSERT(aInputContext.mNativeIMEContext || !CurrentKeyboardLayoutHasIME()); |
|
369 // If no IME context is available, we should set the widget's pointer since |
|
370 // nullptr indicates there is only one context per process on the platform. |
|
371 if (!aInputContext.mNativeIMEContext) { |
|
372 aInputContext.mNativeIMEContext = static_cast<void*>(aWindow); |
|
373 } |
|
374 } |
|
375 |
|
376 #ifdef DEBUG |
|
377 // static |
|
378 bool |
|
379 IMEHandler::CurrentKeyboardLayoutHasIME() |
|
380 { |
|
381 #ifdef NS_ENABLE_TSF |
|
382 if (sIsInTSFMode) { |
|
383 return nsTextStore::CurrentKeyboardLayoutHasIME(); |
|
384 } |
|
385 #endif // #ifdef NS_ENABLE_TSF |
|
386 |
|
387 return nsIMM32Handler::IsIMEAvailable(); |
|
388 } |
|
389 #endif // #ifdef DEBUG |
|
390 |
|
391 // static |
|
392 void |
|
393 IMEHandler::SetInputScopeForIMM32(nsWindow* aWindow, |
|
394 const nsAString& aHTMLInputType) |
|
395 { |
|
396 if (sIsInTSFMode || !sSetInputScopes || aWindow->Destroyed()) { |
|
397 return; |
|
398 } |
|
399 UINT arraySize = 0; |
|
400 const InputScope* scopes = nullptr; |
|
401 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html |
|
402 if (aHTMLInputType.IsEmpty() || aHTMLInputType.EqualsLiteral("text")) { |
|
403 static const InputScope inputScopes[] = { IS_DEFAULT }; |
|
404 scopes = &inputScopes[0]; |
|
405 arraySize = ArrayLength(inputScopes); |
|
406 } else if (aHTMLInputType.EqualsLiteral("url")) { |
|
407 static const InputScope inputScopes[] = { IS_URL }; |
|
408 scopes = &inputScopes[0]; |
|
409 arraySize = ArrayLength(inputScopes); |
|
410 } else if (aHTMLInputType.EqualsLiteral("search")) { |
|
411 static const InputScope inputScopes[] = { IS_SEARCH }; |
|
412 scopes = &inputScopes[0]; |
|
413 arraySize = ArrayLength(inputScopes); |
|
414 } else if (aHTMLInputType.EqualsLiteral("email")) { |
|
415 static const InputScope inputScopes[] = { IS_EMAIL_SMTPEMAILADDRESS }; |
|
416 scopes = &inputScopes[0]; |
|
417 arraySize = ArrayLength(inputScopes); |
|
418 } else if (aHTMLInputType.EqualsLiteral("password")) { |
|
419 static const InputScope inputScopes[] = { IS_PASSWORD }; |
|
420 scopes = &inputScopes[0]; |
|
421 arraySize = ArrayLength(inputScopes); |
|
422 } else if (aHTMLInputType.EqualsLiteral("datetime") || |
|
423 aHTMLInputType.EqualsLiteral("datetime-local")) { |
|
424 static const InputScope inputScopes[] = { |
|
425 IS_DATE_FULLDATE, IS_TIME_FULLTIME }; |
|
426 scopes = &inputScopes[0]; |
|
427 arraySize = ArrayLength(inputScopes); |
|
428 } else if (aHTMLInputType.EqualsLiteral("date") || |
|
429 aHTMLInputType.EqualsLiteral("month") || |
|
430 aHTMLInputType.EqualsLiteral("week")) { |
|
431 static const InputScope inputScopes[] = { IS_DATE_FULLDATE }; |
|
432 scopes = &inputScopes[0]; |
|
433 arraySize = ArrayLength(inputScopes); |
|
434 } else if (aHTMLInputType.EqualsLiteral("time")) { |
|
435 static const InputScope inputScopes[] = { IS_TIME_FULLTIME }; |
|
436 scopes = &inputScopes[0]; |
|
437 arraySize = ArrayLength(inputScopes); |
|
438 } else if (aHTMLInputType.EqualsLiteral("tel")) { |
|
439 static const InputScope inputScopes[] = { |
|
440 IS_TELEPHONE_FULLTELEPHONENUMBER, IS_TELEPHONE_LOCALNUMBER }; |
|
441 scopes = &inputScopes[0]; |
|
442 arraySize = ArrayLength(inputScopes); |
|
443 } else if (aHTMLInputType.EqualsLiteral("number")) { |
|
444 static const InputScope inputScopes[] = { IS_NUMBER }; |
|
445 scopes = &inputScopes[0]; |
|
446 arraySize = ArrayLength(inputScopes); |
|
447 } |
|
448 if (scopes && arraySize > 0) { |
|
449 sSetInputScopes(aWindow->GetWindowHandle(), scopes, arraySize, nullptr, 0, |
|
450 nullptr, nullptr); |
|
451 } |
|
452 } |
|
453 |
|
454 } // namespace widget |
|
455 } // namespace mozilla |