Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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/. */
6 #include "WinIMEHandler.h"
8 #include "mozilla/Preferences.h"
9 #include "nsIMM32Handler.h"
10 #include "nsWindowDefs.h"
12 #ifdef NS_ENABLE_TSF
13 #include "nsTextStore.h"
14 #endif // #ifdef NS_ENABLE_TSF
16 #include "nsWindow.h"
17 #include "WinUtils.h"
19 namespace mozilla {
20 namespace widget {
22 /******************************************************************************
23 * IMEHandler
24 ******************************************************************************/
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
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
55 nsIMM32Handler::Initialize();
56 }
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
69 nsIMM32Handler::Terminate();
70 }
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 }
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 }
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
128 return nsIMM32Handler::ProcessMessage(aWindow, aMessage, aWParam, aLParam,
129 aResult);
130 }
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
142 return nsIMM32Handler::IsComposing();
143 }
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
155 return nsIMM32Handler::IsComposingOn(aWindow);
156 }
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
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 }
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
230 return nsIMM32Handler::GetIMEUpdatePreference();
231 }
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
243 nsIMEContext IMEContext(aWindow->GetWindowHandle());
244 return IMEContext.GetOpenState();
245 }
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 }
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));
272 const InputContext& oldInputContext = aWindow->GetInputContext();
274 // Assume that SetInputContext() is called only when aWindow has focus.
275 sPluginHasFocus = (aInputContext.mIMEState.mEnabled == IMEState::PLUGIN);
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);
283 aInputContext.mNativeIMEContext = nullptr;
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
310 AssociateIMEContext(aWindow, enable);
312 nsIMEContext IMEContext(aWindow->GetWindowHandle());
313 if (adjustOpenState) {
314 IMEContext.SetOpenState(open);
315 }
317 if (aInputContext.mNativeIMEContext) {
318 return;
319 }
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 }
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 }
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;
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
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 }
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
387 return nsIMM32Handler::IsIMEAvailable();
388 }
389 #endif // #ifdef DEBUG
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 }
454 } // namespace widget
455 } // namespace mozilla