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 "IMMHandler.h"
9 #include "mozilla/Preferences.h"
10 #include "mozilla/WindowsVersion.h"
11 #include "nsWindowDefs.h"
12 #include "WinTextEventDispatcherListener.h"
13 
14 #ifdef NS_ENABLE_TSF
15 #include "TSFTextStore.h"
16 #endif  // #ifdef NS_ENABLE_TSF
17 
18 #include "nsLookAndFeel.h"
19 #include "nsWindow.h"
20 #include "WinUtils.h"
21 #include "nsIWindowsRegKey.h"
22 #include "nsIWindowsUIUtils.h"
23 
24 #include "shellapi.h"
25 #include "shlobj.h"
26 #include "powrprof.h"
27 #include "setupapi.h"
28 #include "cfgmgr32.h"
29 
30 const char* kOskPathPrefName = "ui.osk.on_screen_keyboard_path";
31 const char* kOskEnabled = "ui.osk.enabled";
32 const char* kOskDetectPhysicalKeyboard = "ui.osk.detect_physical_keyboard";
33 const char* kOskRequireWin10 = "ui.osk.require_win10";
34 const char* kOskDebugReason = "ui.osk.debug.keyboardDisplayReason";
35 
36 namespace mozilla {
37 namespace widget {
38 
39 /******************************************************************************
40  * IMEHandler
41  ******************************************************************************/
42 
43 nsWindow* IMEHandler::sFocusedWindow = nullptr;
44 InputContextAction::Cause IMEHandler::sLastContextActionCause =
45     InputContextAction::CAUSE_UNKNOWN;
46 bool IMEHandler::sForceDisableCurrentIMM_IME = false;
47 bool IMEHandler::sPluginHasFocus = false;
48 
49 #ifdef NS_ENABLE_TSF
50 bool IMEHandler::sIsInTSFMode = false;
51 bool IMEHandler::sIsIMMEnabled = true;
52 bool IMEHandler::sAssociateIMCOnlyWhenIMM_IMEActive = false;
53 decltype(SetInputScopes)* IMEHandler::sSetInputScopes = nullptr;
54 #endif  // #ifdef NS_ENABLE_TSF
55 
56 static POWER_PLATFORM_ROLE sPowerPlatformRole = PlatformRoleUnspecified;
57 static bool sDeterminedPowerPlatformRole = false;
58 
59 // static
Initialize()60 void IMEHandler::Initialize() {
61 #ifdef NS_ENABLE_TSF
62   TSFTextStore::Initialize();
63   sIsInTSFMode = TSFTextStore::IsInTSFMode();
64   sIsIMMEnabled =
65       !sIsInTSFMode || Preferences::GetBool("intl.tsf.support_imm", true);
66   sAssociateIMCOnlyWhenIMM_IMEActive =
67       sIsIMMEnabled &&
68       Preferences::GetBool("intl.tsf.associate_imc_only_when_imm_ime_is_active",
69                            false);
70   if (!sIsInTSFMode) {
71     // When full TSFTextStore is not available, try to use SetInputScopes API
72     // to enable at least InputScope. Use GET_MODULE_HANDLE_EX_FLAG_PIN to
73     // ensure that msctf.dll will not be unloaded.
74     HMODULE module = nullptr;
75     if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, L"msctf.dll",
76                            &module)) {
77       sSetInputScopes = reinterpret_cast<decltype(SetInputScopes)*>(
78           GetProcAddress(module, "SetInputScopes"));
79     }
80   }
81 #endif  // #ifdef NS_ENABLE_TSF
82 
83   IMMHandler::Initialize();
84 
85   sForceDisableCurrentIMM_IME = IMMHandler::IsActiveIMEInBlockList();
86 }
87 
88 // static
Terminate()89 void IMEHandler::Terminate() {
90 #ifdef NS_ENABLE_TSF
91   if (sIsInTSFMode) {
92     TSFTextStore::Terminate();
93     sIsInTSFMode = false;
94   }
95 #endif  // #ifdef NS_ENABLE_TSF
96 
97   IMMHandler::Terminate();
98   WinTextEventDispatcherListener::Shutdown();
99 }
100 
101 // static
GetNativeData(nsWindow * aWindow,uint32_t aDataType)102 void* IMEHandler::GetNativeData(nsWindow* aWindow, uint32_t aDataType) {
103   if (aDataType == NS_RAW_NATIVE_IME_CONTEXT) {
104 #ifdef NS_ENABLE_TSF
105     if (IsTSFAvailable()) {
106       return TSFTextStore::GetThreadManager();
107     }
108 #endif  // #ifdef NS_ENABLE_TSF
109     IMEContext context(aWindow);
110     if (context.IsValid()) {
111       return context.get();
112     }
113     // If IMC isn't associated with the window, IME is disabled on the window
114     // now.  In such case, we should return default IMC instead.
115     const IMEContext& defaultIMC = aWindow->DefaultIMC();
116     if (defaultIMC.IsValid()) {
117       return defaultIMC.get();
118     }
119     // If there is no default IMC, we should return the pointer to the window
120     // since if we return nullptr, IMEStateManager cannot manage composition
121     // with TextComposition instance.  This is possible if no IME is installed,
122     // but composition may occur with dead key sequence.
123     return aWindow;
124   }
125 
126 #ifdef NS_ENABLE_TSF
127   void* result = TSFTextStore::GetNativeData(aDataType);
128   if (!result || !(*(static_cast<void**>(result)))) {
129     return nullptr;
130   }
131   // XXX During the TSF module test, sIsInTSFMode must be true.  After that,
132   //     the value should be restored but currently, there is no way for that.
133   //     When the TSF test is enabled again, we need to fix this.  Perhaps,
134   //     sending a message can fix this.
135   sIsInTSFMode = true;
136   return result;
137 #else   // #ifdef NS_ENABLE_TSF
138   return nullptr;
139 #endif  // #ifdef NS_ENABLE_TSF #else
140 }
141 
142 // static
ProcessRawKeyMessage(const MSG & aMsg)143 bool IMEHandler::ProcessRawKeyMessage(const MSG& aMsg) {
144 #ifdef NS_ENABLE_TSF
145   if (IsTSFAvailable()) {
146     return TSFTextStore::ProcessRawKeyMessage(aMsg);
147   }
148 #endif           // #ifdef NS_ENABLE_TSF
149   return false;  // noting to do in IMM mode.
150 }
151 
152 // static
ProcessMessage(nsWindow * aWindow,UINT aMessage,WPARAM & aWParam,LPARAM & aLParam,MSGResult & aResult)153 bool IMEHandler::ProcessMessage(nsWindow* aWindow, UINT aMessage,
154                                 WPARAM& aWParam, LPARAM& aLParam,
155                                 MSGResult& aResult) {
156   if (aMessage == MOZ_WM_DISMISS_ONSCREEN_KEYBOARD) {
157     if (!sFocusedWindow) {
158       DismissOnScreenKeyboard();
159     }
160     return true;
161   }
162 
163 #ifdef NS_ENABLE_TSF
164   if (IsTSFAvailable()) {
165     TSFTextStore::ProcessMessage(aWindow, aMessage, aWParam, aLParam, aResult);
166     if (aResult.mConsumed) {
167       return true;
168     }
169     // If we don't support IMM in TSF mode, we don't use IMMHandler.
170     if (!sIsIMMEnabled) {
171       return false;
172     }
173     // IME isn't implemented with IMM, IMMHandler shouldn't handle any
174     // messages.
175     if (!IsIMMActive()) {
176       return false;
177     }
178   }
179 #endif  // #ifdef NS_ENABLE_TSF
180 
181   bool keepGoing =
182       IMMHandler::ProcessMessage(aWindow, aMessage, aWParam, aLParam, aResult);
183 
184   // If user changes active IME to an IME which is listed in our block list,
185   // we should disassociate IMC from the window for preventing the IME to work
186   // and crash.
187   if (aMessage == WM_INPUTLANGCHANGE) {
188     bool disableIME = IMMHandler::IsActiveIMEInBlockList();
189     if (disableIME != sForceDisableCurrentIMM_IME) {
190       bool enable =
191           !disableIME && WinUtils::IsIMEEnabled(aWindow->InputContextRef());
192       AssociateIMEContext(aWindow, enable);
193       sForceDisableCurrentIMM_IME = disableIME;
194     }
195   }
196 
197   return keepGoing;
198 }
199 
200 #ifdef NS_ENABLE_TSF
201 // static
IsIMMActive()202 bool IMEHandler::IsIMMActive() { return TSFTextStore::IsIMM_IMEActive(); }
203 
204 #endif  // #ifdef NS_ENABLE_TSF
205 
206 // static
IsComposing()207 bool IMEHandler::IsComposing() {
208 #ifdef NS_ENABLE_TSF
209   if (IsTSFAvailable()) {
210     return TSFTextStore::IsComposing() || IMMHandler::IsComposing();
211   }
212 #endif  // #ifdef NS_ENABLE_TSF
213 
214   return IMMHandler::IsComposing();
215 }
216 
217 // static
IsComposingOn(nsWindow * aWindow)218 bool IMEHandler::IsComposingOn(nsWindow* aWindow) {
219 #ifdef NS_ENABLE_TSF
220   if (IsTSFAvailable()) {
221     return TSFTextStore::IsComposingOn(aWindow) ||
222            IMMHandler::IsComposingOn(aWindow);
223   }
224 #endif  // #ifdef NS_ENABLE_TSF
225 
226   return IMMHandler::IsComposingOn(aWindow);
227 }
228 
229 // static
NotifyIME(nsWindow * aWindow,const IMENotification & aIMENotification)230 nsresult IMEHandler::NotifyIME(nsWindow* aWindow,
231                                const IMENotification& aIMENotification) {
232 #ifdef NS_ENABLE_TSF
233   if (IsTSFAvailable()) {
234     switch (aIMENotification.mMessage) {
235       case NOTIFY_IME_OF_SELECTION_CHANGE: {
236         nsresult rv = TSFTextStore::OnSelectionChange(aIMENotification);
237         // If IMM IME is active, we need to notify IMMHandler of updating
238         // composition change.  It will adjust candidate window position or
239         // composition window position.
240         bool isIMMActive = IsIMMActive();
241         if (isIMMActive) {
242           IMMHandler::OnUpdateComposition(aWindow);
243         }
244         IMMHandler::OnSelectionChange(aWindow, aIMENotification, isIMMActive);
245         return rv;
246       }
247       case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
248         // If IMM IME is active, we need to notify IMMHandler of updating
249         // composition change.  It will adjust candidate window position or
250         // composition window position.
251         if (IsIMMActive()) {
252           IMMHandler::OnUpdateComposition(aWindow);
253         } else {
254           TSFTextStore::OnUpdateComposition();
255         }
256         return NS_OK;
257       case NOTIFY_IME_OF_TEXT_CHANGE:
258         return TSFTextStore::OnTextChange(aIMENotification);
259       case NOTIFY_IME_OF_FOCUS: {
260         sFocusedWindow = aWindow;
261         IMMHandler::OnFocusChange(true, aWindow);
262         nsresult rv = TSFTextStore::OnFocusChange(true, aWindow,
263                                                   aWindow->GetInputContext());
264         IMEHandler::MaybeShowOnScreenKeyboard();
265         return rv;
266       }
267       case NOTIFY_IME_OF_BLUR:
268         sFocusedWindow = nullptr;
269         IMEHandler::MaybeDismissOnScreenKeyboard(aWindow);
270         IMMHandler::OnFocusChange(false, aWindow);
271         return TSFTextStore::OnFocusChange(false, aWindow,
272                                            aWindow->GetInputContext());
273       case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
274         // If IMM IME is active, we should send a mouse button event via IMM.
275         if (IsIMMActive()) {
276           return IMMHandler::OnMouseButtonEvent(aWindow, aIMENotification);
277         }
278         return TSFTextStore::OnMouseButtonEvent(aIMENotification);
279       case REQUEST_TO_COMMIT_COMPOSITION:
280         if (TSFTextStore::IsComposingOn(aWindow)) {
281           TSFTextStore::CommitComposition(false);
282         } else if (IsIMMActive()) {
283           IMMHandler::CommitComposition(aWindow);
284         }
285         return NS_OK;
286       case REQUEST_TO_CANCEL_COMPOSITION:
287         if (TSFTextStore::IsComposingOn(aWindow)) {
288           TSFTextStore::CommitComposition(true);
289         } else if (IsIMMActive()) {
290           IMMHandler::CancelComposition(aWindow);
291         }
292         return NS_OK;
293       case NOTIFY_IME_OF_POSITION_CHANGE:
294         return TSFTextStore::OnLayoutChange();
295       default:
296         return NS_ERROR_NOT_IMPLEMENTED;
297     }
298   }
299 #endif  // NS_ENABLE_TSF
300 
301   switch (aIMENotification.mMessage) {
302     case REQUEST_TO_COMMIT_COMPOSITION:
303       IMMHandler::CommitComposition(aWindow);
304       return NS_OK;
305     case REQUEST_TO_CANCEL_COMPOSITION:
306       IMMHandler::CancelComposition(aWindow);
307       return NS_OK;
308     case NOTIFY_IME_OF_POSITION_CHANGE:
309     case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
310       IMMHandler::OnUpdateComposition(aWindow);
311       return NS_OK;
312     case NOTIFY_IME_OF_SELECTION_CHANGE:
313       IMMHandler::OnSelectionChange(aWindow, aIMENotification, true);
314       return NS_OK;
315     case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
316       return IMMHandler::OnMouseButtonEvent(aWindow, aIMENotification);
317     case NOTIFY_IME_OF_FOCUS:
318       sFocusedWindow = aWindow;
319       IMMHandler::OnFocusChange(true, aWindow);
320       IMEHandler::MaybeShowOnScreenKeyboard();
321       return NS_OK;
322     case NOTIFY_IME_OF_BLUR:
323       sFocusedWindow = nullptr;
324       IMEHandler::MaybeDismissOnScreenKeyboard(aWindow);
325       IMMHandler::OnFocusChange(false, aWindow);
326 #ifdef NS_ENABLE_TSF
327       // If a plugin gets focus while TSF has focus, we need to notify TSF of
328       // the blur.
329       if (TSFTextStore::ThinksHavingFocus()) {
330         return TSFTextStore::OnFocusChange(false, aWindow,
331                                            aWindow->GetInputContext());
332       }
333 #endif  // NS_ENABLE_TSF
334       return NS_OK;
335     default:
336       return NS_ERROR_NOT_IMPLEMENTED;
337   }
338 }
339 
340 // static
GetIMENotificationRequests()341 IMENotificationRequests IMEHandler::GetIMENotificationRequests() {
342   // While a plugin has focus, neither TSFTextStore nor IMMHandler needs
343   // notifications.
344   if (sPluginHasFocus) {
345     return IMENotificationRequests();
346   }
347 
348 #ifdef NS_ENABLE_TSF
349   if (IsTSFAvailable()) {
350     if (!sIsIMMEnabled) {
351       return TSFTextStore::GetIMENotificationRequests();
352     }
353     // Even if TSF is available, the active IME may be an IMM-IME.
354     // Unfortunately, changing the result of GetIMENotificationRequests() while
355     // an editor has focus isn't supported by IMEContentObserver nor
356     // ContentCacheInParent.  Therefore, we need to request whole notifications
357     // which are necessary either IMMHandler or TSFTextStore.
358     return IMMHandler::GetIMENotificationRequests() |
359            TSFTextStore::GetIMENotificationRequests();
360   }
361 #endif  // NS_ENABLE_TSF
362 
363   return IMMHandler::GetIMENotificationRequests();
364 }
365 
366 // static
367 TextEventDispatcherListener*
GetNativeTextEventDispatcherListener()368 IMEHandler::GetNativeTextEventDispatcherListener() {
369   return WinTextEventDispatcherListener::GetInstance();
370 }
371 
372 // static
GetOpenState(nsWindow * aWindow)373 bool IMEHandler::GetOpenState(nsWindow* aWindow) {
374 #ifdef NS_ENABLE_TSF
375   if (IsTSFAvailable() && !IsIMMActive()) {
376     return TSFTextStore::GetIMEOpenState();
377   }
378 #endif  // NS_ENABLE_TSF
379 
380   IMEContext context(aWindow);
381   return context.GetOpenState();
382 }
383 
384 // static
OnDestroyWindow(nsWindow * aWindow)385 void IMEHandler::OnDestroyWindow(nsWindow* aWindow) {
386   // When focus is in remote process, but the window is being destroyed, we
387   // need to clean up TSFTextStore here since NOTIFY_IME_OF_BLUR won't reach
388   // here because TabParent already lost the reference to the nsWindow when
389   // it receives from the remote process.
390   if (sFocusedWindow == aWindow) {
391     MOZ_ASSERT(aWindow->GetInputContext().IsOriginContentProcess(),
392                "input context of focused widget should've been set by a remote "
393                "process "
394                "if IME focus isn't cleared before destroying the widget");
395     NotifyIME(aWindow, IMENotification(NOTIFY_IME_OF_BLUR));
396   }
397 
398 #ifdef NS_ENABLE_TSF
399   // We need to do nothing here for TSF. Just restore the default context
400   // if it's been disassociated.
401   if (!sIsInTSFMode) {
402     // MSDN says we need to set IS_DEFAULT to avoid memory leak when we use
403     // SetInputScopes API. Use an empty string to do this.
404     SetInputScopeForIMM32(aWindow, EmptyString(), EmptyString());
405   }
406 #endif  // #ifdef NS_ENABLE_TSF
407   AssociateIMEContext(aWindow, true);
408 }
409 
410 #ifdef NS_ENABLE_TSF
411 // static
NeedsToAssociateIMC()412 bool IMEHandler::NeedsToAssociateIMC() {
413   return !sForceDisableCurrentIMM_IME &&
414          (!sAssociateIMCOnlyWhenIMM_IMEActive || !IsIMMActive());
415 }
416 #endif  // #ifdef NS_ENABLE_TSF
417 
418 // static
SetInputContext(nsWindow * aWindow,InputContext & aInputContext,const InputContextAction & aAction)419 void IMEHandler::SetInputContext(nsWindow* aWindow, InputContext& aInputContext,
420                                  const InputContextAction& aAction) {
421   sLastContextActionCause = aAction.mCause;
422   // FYI: If there is no composition, this call will do nothing.
423   NotifyIME(aWindow, IMENotification(REQUEST_TO_COMMIT_COMPOSITION));
424 
425   const InputContext& oldInputContext = aWindow->GetInputContext();
426 
427   // Assume that SetInputContext() is called only when aWindow has focus.
428   sPluginHasFocus = (aInputContext.mIMEState.mEnabled == IMEState::PLUGIN);
429 
430   if (aAction.UserMightRequestOpenVKB()) {
431     IMEHandler::MaybeShowOnScreenKeyboard();
432   }
433 
434   bool enable = WinUtils::IsIMEEnabled(aInputContext);
435   bool adjustOpenState = (enable && aInputContext.mIMEState.mOpen !=
436                                         IMEState::DONT_CHANGE_OPEN_STATE);
437   bool open =
438       (adjustOpenState && aInputContext.mIMEState.mOpen == IMEState::OPEN);
439 
440 #ifdef NS_ENABLE_TSF
441   // Note that even while a plugin has focus, we need to notify TSF of that.
442   if (sIsInTSFMode) {
443     TSFTextStore::SetInputContext(aWindow, aInputContext, aAction);
444     if (IsTSFAvailable()) {
445       if (sIsIMMEnabled) {
446         // Associate IMC with aWindow only when it's necessary.
447         AssociateIMEContext(aWindow, enable && NeedsToAssociateIMC());
448       } else if (oldInputContext.mIMEState.mEnabled == IMEState::PLUGIN) {
449         // Disassociate the IME context from the window when plugin loses focus
450         // in pure TSF mode.
451         AssociateIMEContext(aWindow, false);
452       }
453       if (adjustOpenState) {
454         TSFTextStore::SetIMEOpenState(open);
455       }
456       return;
457     }
458   } else {
459     // Set at least InputScope even when TextStore is not available.
460     SetInputScopeForIMM32(aWindow, aInputContext.mHTMLInputType,
461                           aInputContext.mHTMLInputInputmode);
462   }
463 #endif  // #ifdef NS_ENABLE_TSF
464 
465   AssociateIMEContext(aWindow, enable);
466 
467   IMEContext context(aWindow);
468   if (adjustOpenState) {
469     context.SetOpenState(open);
470   }
471 }
472 
473 // static
AssociateIMEContext(nsWindowBase * aWindowBase,bool aEnable)474 void IMEHandler::AssociateIMEContext(nsWindowBase* aWindowBase, bool aEnable) {
475   IMEContext context(aWindowBase);
476   if (aEnable) {
477     context.AssociateDefaultContext();
478     return;
479   }
480   // Don't disassociate the context after the window is destroyed.
481   if (aWindowBase->Destroyed()) {
482     return;
483   }
484   context.Disassociate();
485 }
486 
487 // static
InitInputContext(nsWindow * aWindow,InputContext & aInputContext)488 void IMEHandler::InitInputContext(nsWindow* aWindow,
489                                   InputContext& aInputContext) {
490   MOZ_ASSERT(aWindow);
491   MOZ_ASSERT(aWindow->GetWindowHandle(),
492              "IMEHandler::SetInputContext() requires non-nullptr HWND");
493 
494   static bool sInitialized = false;
495   if (!sInitialized) {
496     sInitialized = true;
497     // Some TIPs like QQ Input (Simplified Chinese) may need normal window
498     // (i.e., windows except message window) when initializing themselves.
499     // Therefore, we need to initialize TSF/IMM modules after first normal
500     // window is created.  InitInputContext() should be called immediately
501     // after creating each normal window, so, here is a good place to
502     // initialize these modules.
503     Initialize();
504   }
505 
506   // For a11y, the default enabled state should be 'enabled'.
507   aInputContext.mIMEState.mEnabled = IMEState::ENABLED;
508 
509 #ifdef NS_ENABLE_TSF
510   if (sIsInTSFMode) {
511     TSFTextStore::SetInputContext(
512         aWindow, aInputContext,
513         InputContextAction(InputContextAction::CAUSE_UNKNOWN,
514                            InputContextAction::WIDGET_CREATED));
515     // IME context isn't necessary in pure TSF mode.
516     if (!sIsIMMEnabled) {
517       AssociateIMEContext(aWindow, false);
518     }
519     return;
520   }
521 #endif  // #ifdef NS_ENABLE_TSF
522 
523 #ifdef DEBUG
524   // NOTE: IMC may be null if IMM module isn't installed.
525   IMEContext context(aWindow);
526   MOZ_ASSERT(context.IsValid() || !CurrentKeyboardLayoutHasIME());
527 #endif  // #ifdef DEBUG
528 }
529 
530 #ifdef DEBUG
531 // static
CurrentKeyboardLayoutHasIME()532 bool IMEHandler::CurrentKeyboardLayoutHasIME() {
533 #ifdef NS_ENABLE_TSF
534   if (sIsInTSFMode) {
535     return TSFTextStore::CurrentKeyboardLayoutHasIME();
536   }
537 #endif  // #ifdef NS_ENABLE_TSF
538 
539   return IMMHandler::IsIMEAvailable();
540 }
541 #endif  // #ifdef DEBUG
542 
543 // static
OnKeyboardLayoutChanged()544 void IMEHandler::OnKeyboardLayoutChanged() {
545   // Be aware, this method won't be called until TSFStaticSink starts to
546   // observe active TIP change.  If you need to be notified of this, you
547   // need to create TSFStaticSink::Observe() or something and call it
548   // TSFStaticSink::EnsureInitActiveTIPKeyboard() forcibly.
549 
550   if (!sIsIMMEnabled || !IsTSFAvailable()) {
551     return;
552   }
553 
554   // We don't need to do anything when sAssociateIMCOnlyWhenIMM_IMEActive is
555   // false because IMContext won't be associated/disassociated when changing
556   // active keyboard layout/IME.
557   if (!sAssociateIMCOnlyWhenIMM_IMEActive) {
558     return;
559   }
560 
561   // If there is no TSFTextStore which has focus, i.e., no editor has focus,
562   // nothing to do here.
563   nsWindowBase* windowBase = TSFTextStore::GetEnabledWindowBase();
564   if (!windowBase) {
565     return;
566   }
567 
568   // If IME isn't available, nothing to do here.
569   InputContext inputContext = windowBase->GetInputContext();
570   if (!WinUtils::IsIMEEnabled(inputContext)) {
571     return;
572   }
573 
574   // Associate or Disassociate IMC if it's necessary.
575   // Note that this does nothing if the window has already associated with or
576   // disassociated from the window.
577   AssociateIMEContext(windowBase, NeedsToAssociateIMC());
578 }
579 
580 // static
SetInputScopeForIMM32(nsWindow * aWindow,const nsAString & aHTMLInputType,const nsAString & aHTMLInputInputmode)581 void IMEHandler::SetInputScopeForIMM32(nsWindow* aWindow,
582                                        const nsAString& aHTMLInputType,
583                                        const nsAString& aHTMLInputInputmode) {
584   if (sIsInTSFMode || !sSetInputScopes || aWindow->Destroyed()) {
585     return;
586   }
587   UINT arraySize = 0;
588   const InputScope* scopes = nullptr;
589   // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html
590   if (aHTMLInputType.IsEmpty() || aHTMLInputType.EqualsLiteral("text")) {
591     if (aHTMLInputInputmode.EqualsLiteral("url")) {
592       static const InputScope inputScopes[] = {IS_URL};
593       scopes = &inputScopes[0];
594       arraySize = ArrayLength(inputScopes);
595     } else if (aHTMLInputInputmode.EqualsLiteral("mozAwesomebar")) {
596       // Even if Awesomebar has focus, user may not input URL directly.
597       // However, on-screen keyboard for URL should be shown because it has
598       // some useful additional keys like ".com" and they are not hindrances
599       // even when inputting non-URL text, e.g., words to search something in
600       // the web.  On the other hand, a lot of Microsoft's IMEs and Google
601       // Japanese Input make their open state "closed" automatically if we
602       // notify them of URL as the input scope.  However, this is very annoying
603       // for the users when they try to input some words to search the web or
604       // bookmark/history items.  Therefore, if they are active, we need to
605       // notify them of the default input scope for avoiding this issue.
606       // FYI: We cannot check active TIP without TSF.  Therefore, if it's
607       //      not in TSF mode, this will check only if active IMM-IME is Google
608       //      Japanese Input.  Google Japanese Input is a TIP of TSF basically.
609       //      However, if the OS is Win7 or it's installed on Win7 but has not
610       //      been updated yet even after the OS is upgraded to Win8 or later,
611       //      it's installed as IMM-IME.
612       if (TSFTextStore::ShouldSetInputScopeOfURLBarToDefault()) {
613         static const InputScope inputScopes[] = {IS_DEFAULT};
614         scopes = &inputScopes[0];
615         arraySize = ArrayLength(inputScopes);
616       } else {
617         static const InputScope inputScopes[] = {IS_URL};
618         scopes = &inputScopes[0];
619         arraySize = ArrayLength(inputScopes);
620       }
621     } else if (aHTMLInputInputmode.EqualsLiteral("email")) {
622       static const InputScope inputScopes[] = {IS_EMAIL_SMTPEMAILADDRESS};
623       scopes = &inputScopes[0];
624       arraySize = ArrayLength(inputScopes);
625     } else if (aHTMLInputInputmode.EqualsLiteral("tel")) {
626       static const InputScope inputScopes[] = {
627           IS_TELEPHONE_LOCALNUMBER, IS_TELEPHONE_FULLTELEPHONENUMBER};
628       scopes = &inputScopes[0];
629       arraySize = ArrayLength(inputScopes);
630     } else if (aHTMLInputInputmode.EqualsLiteral("numeric")) {
631       static const InputScope inputScopes[] = {IS_NUMBER};
632       scopes = &inputScopes[0];
633       arraySize = ArrayLength(inputScopes);
634     } else {
635       static const InputScope inputScopes[] = {IS_DEFAULT};
636       scopes = &inputScopes[0];
637       arraySize = ArrayLength(inputScopes);
638     }
639   } else if (aHTMLInputType.EqualsLiteral("url")) {
640     static const InputScope inputScopes[] = {IS_URL};
641     scopes = &inputScopes[0];
642     arraySize = ArrayLength(inputScopes);
643   } else if (aHTMLInputType.EqualsLiteral("search")) {
644     static const InputScope inputScopes[] = {IS_SEARCH};
645     scopes = &inputScopes[0];
646     arraySize = ArrayLength(inputScopes);
647   } else if (aHTMLInputType.EqualsLiteral("email")) {
648     static const InputScope inputScopes[] = {IS_EMAIL_SMTPEMAILADDRESS};
649     scopes = &inputScopes[0];
650     arraySize = ArrayLength(inputScopes);
651   } else if (aHTMLInputType.EqualsLiteral("password")) {
652     static const InputScope inputScopes[] = {IS_PASSWORD};
653     scopes = &inputScopes[0];
654     arraySize = ArrayLength(inputScopes);
655   } else if (aHTMLInputType.EqualsLiteral("datetime") ||
656              aHTMLInputType.EqualsLiteral("datetime-local")) {
657     static const InputScope inputScopes[] = {IS_DATE_FULLDATE,
658                                              IS_TIME_FULLTIME};
659     scopes = &inputScopes[0];
660     arraySize = ArrayLength(inputScopes);
661   } else if (aHTMLInputType.EqualsLiteral("date") ||
662              aHTMLInputType.EqualsLiteral("month") ||
663              aHTMLInputType.EqualsLiteral("week")) {
664     static const InputScope inputScopes[] = {IS_DATE_FULLDATE};
665     scopes = &inputScopes[0];
666     arraySize = ArrayLength(inputScopes);
667   } else if (aHTMLInputType.EqualsLiteral("time")) {
668     static const InputScope inputScopes[] = {IS_TIME_FULLTIME};
669     scopes = &inputScopes[0];
670     arraySize = ArrayLength(inputScopes);
671   } else if (aHTMLInputType.EqualsLiteral("tel")) {
672     static const InputScope inputScopes[] = {IS_TELEPHONE_FULLTELEPHONENUMBER,
673                                              IS_TELEPHONE_LOCALNUMBER};
674     scopes = &inputScopes[0];
675     arraySize = ArrayLength(inputScopes);
676   } else if (aHTMLInputType.EqualsLiteral("number")) {
677     static const InputScope inputScopes[] = {IS_NUMBER};
678     scopes = &inputScopes[0];
679     arraySize = ArrayLength(inputScopes);
680   }
681   if (scopes && arraySize > 0) {
682     sSetInputScopes(aWindow->GetWindowHandle(), scopes, arraySize, nullptr, 0,
683                     nullptr, nullptr);
684   }
685 }
686 
687 // static
MaybeShowOnScreenKeyboard()688 void IMEHandler::MaybeShowOnScreenKeyboard() {
689   if (sPluginHasFocus || !IsWin8OrLater() ||
690       !Preferences::GetBool(kOskEnabled, true) || GetOnScreenKeyboardWindow() ||
691       !IMEHandler::NeedOnScreenKeyboard()) {
692     return;
693   }
694 
695   // On Windows 10 we require tablet mode, unless the user has set the relevant
696   // Windows setting to enable the on-screen keyboard in desktop mode.
697   // We might be disabled specifically on Win8(.1), so we check that afterwards.
698   if (IsWin10OrLater()) {
699     if (!IsInTabletMode() && !AutoInvokeOnScreenKeyboardInDesktopMode()) {
700       return;
701     }
702   } else if (Preferences::GetBool(kOskRequireWin10, true)) {
703     return;
704   }
705 
706   IMEHandler::ShowOnScreenKeyboard();
707 }
708 
709 // static
MaybeDismissOnScreenKeyboard(nsWindow * aWindow)710 void IMEHandler::MaybeDismissOnScreenKeyboard(nsWindow* aWindow) {
711   if (sPluginHasFocus || !IsWin8OrLater()) {
712     return;
713   }
714 
715   ::PostMessage(aWindow->GetWindowHandle(), MOZ_WM_DISMISS_ONSCREEN_KEYBOARD, 0,
716                 0);
717 }
718 
719 // static
WStringStartsWithCaseInsensitive(const std::wstring & aHaystack,const std::wstring & aNeedle)720 bool IMEHandler::WStringStartsWithCaseInsensitive(const std::wstring& aHaystack,
721                                                   const std::wstring& aNeedle) {
722   std::wstring lowerCaseHaystack(aHaystack);
723   std::wstring lowerCaseNeedle(aNeedle);
724   std::transform(lowerCaseHaystack.begin(), lowerCaseHaystack.end(),
725                  lowerCaseHaystack.begin(), ::tolower);
726   std::transform(lowerCaseNeedle.begin(), lowerCaseNeedle.end(),
727                  lowerCaseNeedle.begin(), ::tolower);
728   return wcsstr(lowerCaseHaystack.c_str(), lowerCaseNeedle.c_str()) ==
729          lowerCaseHaystack.c_str();
730 }
731 
732 // Returns false if a physical keyboard is detected on Windows 8 and up,
733 // or there is some other reason why an onscreen keyboard is not necessary.
734 // Returns true if no keyboard is found and this device looks like it needs
735 // an on-screen keyboard for text input.
736 // static
NeedOnScreenKeyboard()737 bool IMEHandler::NeedOnScreenKeyboard() {
738   // This function is only supported for Windows 8 and up.
739   if (!IsWin8OrLater()) {
740     Preferences::SetString(kOskDebugReason, L"IKPOS: Requires Win8+.");
741     return false;
742   }
743 
744   if (!Preferences::GetBool(kOskDetectPhysicalKeyboard, true)) {
745     Preferences::SetString(kOskDebugReason, L"IKPOS: Detection disabled.");
746     return true;
747   }
748 
749   // If the last focus cause was not user-initiated (ie a result of code
750   // setting focus to an element) then don't auto-show a keyboard. This
751   // avoids cases where the keyboard would pop up "just" because e.g. a
752   // web page chooses to focus a search field on the page, even when that
753   // really isn't what the user is trying to do at that moment.
754   if (!InputContextAction::IsHandlingUserInput(sLastContextActionCause)) {
755     return false;
756   }
757 
758   // This function should be only invoked for machines with touch screens.
759   if ((::GetSystemMetrics(SM_DIGITIZER) & NID_INTEGRATED_TOUCH) !=
760       NID_INTEGRATED_TOUCH) {
761     Preferences::SetString(kOskDebugReason, L"IKPOS: Touch screen not found.");
762     return false;
763   }
764 
765   // If the device is docked, the user is treating the device as a PC.
766   if (::GetSystemMetrics(SM_SYSTEMDOCKED) != 0) {
767     Preferences::SetString(kOskDebugReason, L"IKPOS: System docked.");
768     return false;
769   }
770 
771   // To determine whether a keyboard is present on the device, we do the
772   // following:-
773   // 1. If the platform role is that of a mobile or slate device, check the
774   //    system metric SM_CONVERTIBLESLATEMODE to see if it is being used
775   //    in slate mode. If it is, also check that the last input was a touch.
776   //    If all of this is true, then we should show the on-screen keyboard.
777 
778   // 2. If step 1 didn't determine we should show the keyboard, we check if
779   //    this device has keyboards attached to it.
780 
781   // Check if the device is being used as a laptop or a tablet. This can be
782   // checked by first checking the role of the device and then the
783   // corresponding system metric (SM_CONVERTIBLESLATEMODE). If it is being
784   // used as a tablet then we want the OSK to show up.
785   typedef POWER_PLATFORM_ROLE(WINAPI *
786                               PowerDeterminePlatformRoleEx)(ULONG Version);
787   if (!sDeterminedPowerPlatformRole) {
788     sDeterminedPowerPlatformRole = true;
789     PowerDeterminePlatformRoleEx power_determine_platform_role =
790         reinterpret_cast<PowerDeterminePlatformRoleEx>(::GetProcAddress(
791             ::LoadLibraryW(L"PowrProf.dll"), "PowerDeterminePlatformRoleEx"));
792     if (power_determine_platform_role) {
793       sPowerPlatformRole =
794           power_determine_platform_role(POWER_PLATFORM_ROLE_V2);
795     } else {
796       sPowerPlatformRole = PlatformRoleUnspecified;
797     }
798   }
799 
800   // If this a mobile or slate (tablet) device, check if it is in slate mode.
801   // If the last input was touch, ignore whether or not a keyboard is present.
802   if ((sPowerPlatformRole == PlatformRoleMobile ||
803        sPowerPlatformRole == PlatformRoleSlate) &&
804       ::GetSystemMetrics(SM_CONVERTIBLESLATEMODE) == 0 &&
805       sLastContextActionCause == InputContextAction::CAUSE_TOUCH) {
806     Preferences::SetString(
807         kOskDebugReason,
808         L"IKPOS: Mobile/Slate Platform role, in slate mode with touch event.");
809     return true;
810   }
811 
812   return !IMEHandler::IsKeyboardPresentOnSlate();
813 }
814 
815 // Uses the Setup APIs to enumerate the attached keyboards and returns true
816 // if the keyboard count is 1 or more. While this will work in most cases
817 // it won't work if there are devices which expose keyboard interfaces which
818 // are attached to the machine.
819 // Based on IsKeyboardPresentOnSlate() in Chromium's base/win/win_util.cc.
820 // static
IsKeyboardPresentOnSlate()821 bool IMEHandler::IsKeyboardPresentOnSlate() {
822   const GUID KEYBOARD_CLASS_GUID = {
823       0x4D36E96B,
824       0xE325,
825       0x11CE,
826       {0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18}};
827 
828   // Query for all the keyboard devices.
829   HDEVINFO device_info = ::SetupDiGetClassDevs(&KEYBOARD_CLASS_GUID, nullptr,
830                                                nullptr, DIGCF_PRESENT);
831   if (device_info == INVALID_HANDLE_VALUE) {
832     Preferences::SetString(kOskDebugReason, L"IKPOS: No keyboard info.");
833     return false;
834   }
835 
836   // Enumerate all keyboards and look for ACPI\PNP and HID\VID devices. If
837   // the count is more than 1 we assume that a keyboard is present. This is
838   // under the assumption that there will always be one keyboard device.
839   for (DWORD i = 0;; ++i) {
840     SP_DEVINFO_DATA device_info_data = {0};
841     device_info_data.cbSize = sizeof(device_info_data);
842     if (!::SetupDiEnumDeviceInfo(device_info, i, &device_info_data)) {
843       break;
844     }
845 
846     // Get the device ID.
847     wchar_t device_id[MAX_DEVICE_ID_LEN];
848     CONFIGRET status = ::CM_Get_Device_ID(device_info_data.DevInst, device_id,
849                                           MAX_DEVICE_ID_LEN, 0);
850     if (status == CR_SUCCESS) {
851       static const std::wstring BT_HID_DEVICE = L"HID\\{00001124";
852       static const std::wstring BT_HOGP_DEVICE = L"HID\\{00001812";
853       // To reduce the scope of the hack we only look for ACPI and HID\\VID
854       // prefixes in the keyboard device ids.
855       if (IMEHandler::WStringStartsWithCaseInsensitive(device_id, L"ACPI") ||
856           IMEHandler::WStringStartsWithCaseInsensitive(device_id,
857                                                        L"HID\\VID") ||
858           IMEHandler::WStringStartsWithCaseInsensitive(device_id,
859                                                        BT_HID_DEVICE) ||
860           IMEHandler::WStringStartsWithCaseInsensitive(device_id,
861                                                        BT_HOGP_DEVICE)) {
862         // The heuristic we are using is to check the count of keyboards and
863         // return true if the API's report one or more keyboards. Please note
864         // that this will break for non keyboard devices which expose a
865         // keyboard PDO.
866         Preferences::SetString(kOskDebugReason,
867                                L"IKPOS: Keyboard presence confirmed.");
868         return true;
869       }
870     }
871   }
872   Preferences::SetString(kOskDebugReason,
873                          L"IKPOS: Lack of keyboard confirmed.");
874   return false;
875 }
876 
877 // static
IsInTabletMode()878 bool IMEHandler::IsInTabletMode() {
879   nsCOMPtr<nsIWindowsUIUtils> uiUtils(
880       do_GetService("@mozilla.org/windows-ui-utils;1"));
881   if (NS_WARN_IF(!uiUtils)) {
882     Preferences::SetString(kOskDebugReason,
883                            L"IITM: nsIWindowsUIUtils not available.");
884     return false;
885   }
886   bool isInTabletMode = false;
887   uiUtils->GetInTabletMode(&isInTabletMode);
888   if (isInTabletMode) {
889     Preferences::SetString(kOskDebugReason, L"IITM: GetInTabletMode=true.");
890   } else {
891     Preferences::SetString(kOskDebugReason, L"IITM: GetInTabletMode=false.");
892   }
893   return isInTabletMode;
894 }
895 
896 // static
AutoInvokeOnScreenKeyboardInDesktopMode()897 bool IMEHandler::AutoInvokeOnScreenKeyboardInDesktopMode() {
898   nsresult rv;
899   nsCOMPtr<nsIWindowsRegKey> regKey(
900       do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv));
901   if (NS_WARN_IF(NS_FAILED(rv))) {
902     Preferences::SetString(kOskDebugReason,
903                            L"AIOSKIDM: "
904                            L"nsIWindowsRegKey not available");
905     return false;
906   }
907   rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
908                     NS_LITERAL_STRING("SOFTWARE\\Microsoft\\TabletTip\\1.7"),
909                     nsIWindowsRegKey::ACCESS_QUERY_VALUE);
910   if (NS_FAILED(rv)) {
911     Preferences::SetString(kOskDebugReason,
912                            L"AIOSKIDM: failed opening regkey.");
913     return false;
914   }
915   // EnableDesktopModeAutoInvoke is an opt-in option from the Windows
916   // Settings to "Automatically show the touch keyboard in windowed apps
917   // when there's no keyboard attached to your device." If the user has
918   // opted-in to this behavior, the tablet-mode requirement is skipped.
919   uint32_t value;
920   rv = regKey->ReadIntValue(NS_LITERAL_STRING("EnableDesktopModeAutoInvoke"),
921                             &value);
922   if (NS_FAILED(rv)) {
923     Preferences::SetString(kOskDebugReason,
924                            L"AIOSKIDM: failed reading value of regkey.");
925     return false;
926   }
927   if (!!value) {
928     Preferences::SetString(kOskDebugReason, L"AIOSKIDM: regkey value=true.");
929   } else {
930     Preferences::SetString(kOskDebugReason, L"AIOSKIDM: regkey value=false.");
931   }
932   return !!value;
933 }
934 
935 // Based on DisplayVirtualKeyboard() in Chromium's base/win/win_util.cc.
936 // static
ShowOnScreenKeyboard()937 void IMEHandler::ShowOnScreenKeyboard() {
938   nsAutoString cachedPath;
939   nsresult result = Preferences::GetString(kOskPathPrefName, cachedPath);
940   if (NS_FAILED(result) || cachedPath.IsEmpty()) {
941     wchar_t path[MAX_PATH];
942     // The path to TabTip.exe is defined at the following registry key.
943     // This is pulled out of the 64-bit registry hive directly.
944     const wchar_t kRegKeyName[] =
945         L"Software\\Classes\\CLSID\\"
946         L"{054AAE20-4BEA-4347-8A35-64A533254A9D}\\LocalServer32";
947     if (!WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE, kRegKeyName, nullptr,
948                                   path, sizeof path)) {
949       return;
950     }
951 
952     std::wstring wstrpath(path);
953     // The path provided by the registry will often contain
954     // %CommonProgramFiles%, which will need to be replaced if it is present.
955     size_t commonProgramFilesOffset = wstrpath.find(L"%CommonProgramFiles%");
956     if (commonProgramFilesOffset != std::wstring::npos) {
957       // The path read from the registry contains the %CommonProgramFiles%
958       // environment variable prefix. On 64 bit Windows the
959       // SHGetKnownFolderPath function returns the common program files path
960       // with the X86 suffix for the FOLDERID_ProgramFilesCommon value.
961       // To get the correct path to TabTip.exe we first read the environment
962       // variable CommonProgramW6432 which points to the desired common
963       // files path. Failing that we fallback to the SHGetKnownFolderPath API.
964 
965       // We then replace the %CommonProgramFiles% value with the actual common
966       // files path found in the process.
967       std::wstring commonProgramFilesPath;
968       std::vector<wchar_t> commonProgramFilesPathW6432;
969       DWORD bufferSize =
970           ::GetEnvironmentVariableW(L"CommonProgramW6432", nullptr, 0);
971       if (bufferSize) {
972         commonProgramFilesPathW6432.resize(bufferSize);
973         ::GetEnvironmentVariableW(L"CommonProgramW6432",
974                                   commonProgramFilesPathW6432.data(),
975                                   bufferSize);
976         commonProgramFilesPath =
977             std::wstring(commonProgramFilesPathW6432.data());
978       } else {
979         PWSTR path = nullptr;
980         HRESULT hres = SHGetKnownFolderPath(FOLDERID_ProgramFilesCommon, 0,
981                                             nullptr, &path);
982         if (FAILED(hres) || !path) {
983           return;
984         }
985         commonProgramFilesPath =
986             static_cast<const wchar_t*>(nsDependentString(path).get());
987         ::CoTaskMemFree(path);
988       }
989       wstrpath.replace(commonProgramFilesOffset,
990                        wcslen(L"%CommonProgramFiles%"), commonProgramFilesPath);
991     }
992 
993     cachedPath.Assign(wstrpath.data());
994     Preferences::SetString(kOskPathPrefName, cachedPath);
995   }
996 
997   const char16_t* cachedPathPtr;
998   cachedPath.GetData(&cachedPathPtr);
999   ShellExecuteW(nullptr, L"", char16ptr_t(cachedPathPtr), nullptr, nullptr,
1000                 SW_SHOW);
1001 }
1002 
1003 // Based on DismissVirtualKeyboard() in Chromium's base/win/win_util.cc.
1004 // static
DismissOnScreenKeyboard()1005 void IMEHandler::DismissOnScreenKeyboard() {
1006   // Dismiss the virtual keyboard if it's open
1007   HWND osk = GetOnScreenKeyboardWindow();
1008   if (osk) {
1009     ::PostMessage(osk, WM_SYSCOMMAND, SC_CLOSE, 0);
1010   }
1011 }
1012 
1013 // static
GetOnScreenKeyboardWindow()1014 HWND IMEHandler::GetOnScreenKeyboardWindow() {
1015   const wchar_t kOSKClassName[] = L"IPTip_Main_Window";
1016   HWND osk = ::FindWindowW(kOSKClassName, nullptr);
1017   if (::IsWindow(osk) && ::IsWindowEnabled(osk) && ::IsWindowVisible(osk)) {
1018     return osk;
1019   }
1020   return nullptr;
1021 }
1022 
1023 // static
SetCandidateWindow(nsWindow * aWindow,CANDIDATEFORM * aForm)1024 void IMEHandler::SetCandidateWindow(nsWindow* aWindow, CANDIDATEFORM* aForm) {
1025   if (!sPluginHasFocus) {
1026     return;
1027   }
1028 
1029   IMMHandler::SetCandidateWindow(aWindow, aForm);
1030 }
1031 
1032 // static
DefaultProcOfPluginEvent(nsWindow * aWindow,const NPEvent * aPluginEvent)1033 void IMEHandler::DefaultProcOfPluginEvent(nsWindow* aWindow,
1034                                           const NPEvent* aPluginEvent) {
1035   if (!sPluginHasFocus) {
1036     return;
1037   }
1038   IMMHandler::DefaultProcOfPluginEvent(aWindow, aPluginEvent);
1039 }
1040 
1041 }  // namespace widget
1042 }  // namespace mozilla
1043