Backed out changeset b71c8c052463 (bug 1943846) for causing mass failures. CLOSED...
[gecko.git] / widget / windows / WinIMEHandler.cpp
blob4b08fd79d317136f4964e15668fce33de408a093
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 "IMMHandler.h"
9 #include "KeyboardLayout.h"
10 #include "mozilla/Preferences.h"
11 #include "mozilla/StaticPrefs_intl.h"
12 #include "mozilla/StaticPrefs_ui.h"
13 #include "mozilla/TextEvents.h"
14 #include "mozilla/Unused.h"
15 #include "mozilla/WindowsVersion.h"
16 #include "nsWindowDefs.h"
17 #include "WinTextEventDispatcherListener.h"
19 #include "TSFTextStore.h"
21 #include "OSKInputPaneManager.h"
22 #include "OSKTabTipManager.h"
23 #include "OSKVRManager.h"
24 #include "nsLookAndFeel.h"
25 #include "nsWindow.h"
26 #include "WinUtils.h"
27 #include "nsIWindowsRegKey.h"
28 #include "WindowsUIUtils.h"
30 #ifdef ACCESSIBILITY
31 # include "nsAccessibilityService.h"
32 #endif // #ifdef ACCESSIBILITY
34 #include "shellapi.h"
35 #include "shlobj.h"
36 #include "powrprof.h"
37 #include "setupapi.h"
38 #include "cfgmgr32.h"
40 #include "FxRWindowManager.h"
41 #include "moz_external_vr.h"
43 const char* kOskEnabled = "ui.osk.enabled";
44 const char* kOskDetectPhysicalKeyboard = "ui.osk.detect_physical_keyboard";
45 const char* kOskDebugReason = "ui.osk.debug.keyboardDisplayReason";
47 namespace mozilla {
48 namespace widget {
50 /******************************************************************************
51 * IMEHandler
52 ******************************************************************************/
54 nsWindow* IMEHandler::sFocusedWindow = nullptr;
55 InputContextAction::Cause IMEHandler::sLastContextActionCause =
56 InputContextAction::CAUSE_UNKNOWN;
57 bool IMEHandler::sMaybeEditable = false;
58 bool IMEHandler::sForceDisableCurrentIMM_IME = false;
59 bool IMEHandler::sNativeCaretIsCreated = false;
60 bool IMEHandler::sHasNativeCaretBeenRequested = false;
62 bool IMEHandler::sIsInTSFMode = false;
63 bool IMEHandler::sIsIMMEnabled = true;
64 decltype(SetInputScopes)* IMEHandler::sSetInputScopes = nullptr;
66 static POWER_PLATFORM_ROLE sPowerPlatformRole = PlatformRoleUnspecified;
67 static bool sDeterminedPowerPlatformRole = false;
69 // static
70 void IMEHandler::Initialize() {
71 TSFTextStore::Initialize();
72 sIsInTSFMode = TSFTextStore::IsInTSFMode();
73 sIsIMMEnabled =
74 !sIsInTSFMode || StaticPrefs::intl_tsf_support_imm_AtStartup();
75 if (!sIsInTSFMode) {
76 // When full TSFTextStore is not available, try to use SetInputScopes API
77 // to enable at least InputScope. Use GET_MODULE_HANDLE_EX_FLAG_PIN to
78 // ensure that msctf.dll will not be unloaded.
79 HMODULE module = nullptr;
80 if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, L"msctf.dll",
81 &module)) {
82 sSetInputScopes = reinterpret_cast<decltype(SetInputScopes)*>(
83 GetProcAddress(module, "SetInputScopes"));
87 IMMHandler::Initialize();
89 sForceDisableCurrentIMM_IME = IMMHandler::IsActiveIMEInBlockList();
92 // static
93 void IMEHandler::Terminate() {
94 if (sIsInTSFMode) {
95 TSFTextStore::Terminate();
96 sIsInTSFMode = false;
99 IMMHandler::Terminate();
100 WinTextEventDispatcherListener::Shutdown();
103 // static
104 void* IMEHandler::GetNativeData(nsWindow* aWindow, uint32_t aDataType) {
105 if (aDataType == NS_RAW_NATIVE_IME_CONTEXT) {
106 if (IsTSFAvailable()) {
107 return TSFTextStore::GetThreadManager();
109 IMEContext context(aWindow);
110 if (context.IsValid()) {
111 return context.get();
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();
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;
126 void* result = TSFTextStore::GetNativeData(aDataType);
127 if (!result || !(*(static_cast<void**>(result)))) {
128 return nullptr;
130 // XXX During the TSF module test, sIsInTSFMode must be true. After that,
131 // the value should be restored but currently, there is no way for that.
132 // When the TSF test is enabled again, we need to fix this. Perhaps,
133 // sending a message can fix this.
134 sIsInTSFMode = true;
135 return result;
138 // static
139 bool IMEHandler::ProcessRawKeyMessage(const MSG& aMsg) {
140 if (StaticPrefs::ui_key_layout_load_when_first_needed()) {
141 // Getting instance creates the singleton instance and that will
142 // automatically load active keyboard layout data. We should do that
143 // before TSF or TranslateMessage handles a key message.
144 Unused << KeyboardLayout::GetInstance();
146 if (IsTSFAvailable()) {
147 return TSFTextStore::ProcessRawKeyMessage(aMsg);
149 return false; // noting to do in IMM mode.
152 // static
153 bool IMEHandler::ProcessMessage(nsWindow* aWindow, UINT aMessage,
154 WPARAM& aWParam, LPARAM& aLParam,
155 MSGResult& aResult) {
156 // If we're putting native caret over our caret, Windows dispatches
157 // EVENT_OBJECT_LOCATIONCHANGE event on other applications which hook
158 // the event with ::SetWinEventHook() and handles WM_GETOBJECT for
159 // OBJID_CARET (this is request of caret from such applications) instead
160 // of us. If a11y module is active, it observes every our caret change
161 // and put native caret over it automatically. However, if other
162 // applications require only caret information, activating a11y module is
163 // overwork and such applications may requires carets only in editors.
164 // Therefore, if it'd be possible, IMEHandler should put native caret over
165 // our caret, but there is a problem. Some versions of ATOK (Japanese TIP)
166 // refer native caret and if there is, the behavior is worse than the
167 // behavior without native caret. Therefore, we shouldn't put native caret
168 // as far as possible.
169 if (!sHasNativeCaretBeenRequested && aMessage == WM_GETOBJECT &&
170 static_cast<LONG>(aLParam) == OBJID_CARET) {
171 // So, when we receive first WM_GETOBJECT for OBJID_CARET, let's start to
172 // create native caret for such applications.
173 sHasNativeCaretBeenRequested = true;
174 // If an editable element has focus, we can put native caret now.
175 // XXX Should we avoid doing this if there is composition?
176 MaybeCreateNativeCaret(aWindow);
179 if (IsTSFAvailable()) {
180 TSFTextStore::ProcessMessage(aWindow, aMessage, aWParam, aLParam, aResult);
181 if (aResult.mConsumed) {
182 return true;
184 // If we don't support IMM in TSF mode, we don't use IMMHandler.
185 if (!sIsIMMEnabled) {
186 return false;
188 // IME isn't implemented with IMM, IMMHandler shouldn't handle any
189 // messages.
190 if (!IsIMMActive()) {
191 return false;
195 bool keepGoing =
196 IMMHandler::ProcessMessage(aWindow, aMessage, aWParam, aLParam, aResult);
198 // If user changes active IME to an IME which is listed in our block list,
199 // we should disassociate IMC from the window for preventing the IME to work
200 // and crash.
201 if (aMessage == WM_INPUTLANGCHANGE) {
202 bool disableIME = IMMHandler::IsActiveIMEInBlockList();
203 if (disableIME != sForceDisableCurrentIMM_IME) {
204 bool enable =
205 !disableIME && WinUtils::IsIMEEnabled(aWindow->InputContextRef());
206 AssociateIMEContext(aWindow, enable);
207 sForceDisableCurrentIMM_IME = disableIME;
211 return keepGoing;
214 // static
215 bool IMEHandler::IsA11yHandlingNativeCaret() {
216 #ifndef ACCESSIBILITY
217 return false;
218 #else // #ifndef ACCESSIBILITY
219 // Let's assume that when there is the service, it handles native caret.
220 return GetAccService() != nullptr;
221 #endif // #ifndef ACCESSIBILITY #else
224 // static
225 bool IMEHandler::IsIMMActive() { return TSFTextStore::IsIMM_IMEActive(); }
227 // static
228 bool IMEHandler::IsComposing() {
229 if (IsTSFAvailable()) {
230 return TSFTextStore::IsComposing() || IMMHandler::IsComposing();
233 return IMMHandler::IsComposing();
236 // static
237 bool IMEHandler::IsComposingOn(nsWindow* aWindow) {
238 if (IsTSFAvailable()) {
239 return TSFTextStore::IsComposingOn(aWindow) ||
240 IMMHandler::IsComposingOn(aWindow);
243 return IMMHandler::IsComposingOn(aWindow);
246 // static
247 nsresult IMEHandler::NotifyIME(nsWindow* aWindow,
248 const IMENotification& aIMENotification) {
249 if (IsTSFAvailable()) {
250 switch (aIMENotification.mMessage) {
251 case NOTIFY_IME_OF_SELECTION_CHANGE: {
252 nsresult rv = TSFTextStore::OnSelectionChange(aIMENotification);
253 // If IMM IME is active, we need to notify IMMHandler of updating
254 // composition change. It will adjust candidate window position or
255 // composition window position.
256 bool isIMMActive = IsIMMActive();
257 if (isIMMActive) {
258 IMMHandler::OnUpdateComposition(aWindow);
260 IMMHandler::OnSelectionChange(aWindow, aIMENotification, isIMMActive);
261 return rv;
263 case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
264 // If IMM IME is active, we need to notify IMMHandler of updating
265 // composition change. It will adjust candidate window position or
266 // composition window position.
267 if (IsIMMActive()) {
268 IMMHandler::OnUpdateComposition(aWindow);
269 } else {
270 TSFTextStore::OnUpdateComposition();
272 return NS_OK;
273 case NOTIFY_IME_OF_TEXT_CHANGE:
274 return TSFTextStore::OnTextChange(aIMENotification);
275 case NOTIFY_IME_OF_FOCUS: {
276 sFocusedWindow = aWindow;
277 IMMHandler::OnFocusChange(true, aWindow);
278 nsresult rv = TSFTextStore::OnFocusChange(true, aWindow,
279 aWindow->GetInputContext());
280 MaybeCreateNativeCaret(aWindow);
281 IMEHandler::MaybeShowOnScreenKeyboard(aWindow,
282 aWindow->GetInputContext());
283 return rv;
285 case NOTIFY_IME_OF_BLUR:
286 sFocusedWindow = nullptr;
287 IMEHandler::MaybeDismissOnScreenKeyboard(aWindow);
288 IMMHandler::OnFocusChange(false, aWindow);
289 return TSFTextStore::OnFocusChange(false, aWindow,
290 aWindow->GetInputContext());
291 case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
292 // If IMM IME is active, we should send a mouse button event via IMM.
293 if (IsIMMActive()) {
294 return IMMHandler::OnMouseButtonEvent(aWindow, aIMENotification);
296 return TSFTextStore::OnMouseButtonEvent(aIMENotification);
297 case REQUEST_TO_COMMIT_COMPOSITION:
298 // In the TSF world, a DLL might manage hidden composition and that
299 // might cause a crash if we don't terminate it and disassociate the
300 // context. Therefore, we should always try to commit composition.
301 if (IsTSFAvailable()) {
302 TSFTextStore::CommitComposition(false);
304 // Even if we're in the TSF mode, the active IME may be IMM. Therefore,
305 // we need to use IMM handler too.
306 if (IsIMMActive()) {
307 IMMHandler::CommitComposition(aWindow);
309 return NS_OK;
310 case REQUEST_TO_CANCEL_COMPOSITION:
311 // In the TSF world, a DLL might manage hidden composition and that
312 // might cause a crash if we don't terminate it and disassociate the
313 // context. Therefore, we should always try to commit composition.
314 if (IsTSFAvailable()) {
315 TSFTextStore::CommitComposition(true);
317 // Even if we're in the TSF mode, the active IME may be IMM. Therefore,
318 // we need to use IMM handler too.
319 if (IsIMMActive()) {
320 IMMHandler::CancelComposition(aWindow);
322 return NS_OK;
323 case NOTIFY_IME_OF_POSITION_CHANGE:
324 return TSFTextStore::OnLayoutChange();
325 default:
326 return NS_ERROR_NOT_IMPLEMENTED;
330 switch (aIMENotification.mMessage) {
331 case REQUEST_TO_COMMIT_COMPOSITION:
332 IMMHandler::CommitComposition(aWindow);
333 return NS_OK;
334 case REQUEST_TO_CANCEL_COMPOSITION:
335 IMMHandler::CancelComposition(aWindow);
336 return NS_OK;
337 case NOTIFY_IME_OF_POSITION_CHANGE:
338 case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
339 IMMHandler::OnUpdateComposition(aWindow);
340 return NS_OK;
341 case NOTIFY_IME_OF_SELECTION_CHANGE:
342 IMMHandler::OnSelectionChange(aWindow, aIMENotification, true);
343 // IMMHandler::OnSelectionChange() cannot work without its singleton
344 // instance. Therefore, IMEHandler needs to create native caret instead
345 // if it's necessary.
346 MaybeCreateNativeCaret(aWindow);
347 return NS_OK;
348 case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
349 return IMMHandler::OnMouseButtonEvent(aWindow, aIMENotification);
350 case NOTIFY_IME_OF_FOCUS:
351 sFocusedWindow = aWindow;
352 IMMHandler::OnFocusChange(true, aWindow);
353 IMEHandler::MaybeShowOnScreenKeyboard(aWindow,
354 aWindow->GetInputContext());
355 MaybeCreateNativeCaret(aWindow);
356 return NS_OK;
357 case NOTIFY_IME_OF_BLUR:
358 sFocusedWindow = nullptr;
359 IMEHandler::MaybeDismissOnScreenKeyboard(aWindow);
360 IMMHandler::OnFocusChange(false, aWindow);
361 // If a plugin gets focus while TSF has focus, we need to notify TSF of
362 // the blur.
363 if (TSFTextStore::ThinksHavingFocus()) {
364 return TSFTextStore::OnFocusChange(false, aWindow,
365 aWindow->GetInputContext());
367 return NS_OK;
368 default:
369 return NS_ERROR_NOT_IMPLEMENTED;
373 // static
374 IMENotificationRequests IMEHandler::GetIMENotificationRequests() {
375 if (IsTSFAvailable()) {
376 if (!sIsIMMEnabled) {
377 return TSFTextStore::GetIMENotificationRequests();
379 // Even if TSF is available, the active IME may be an IMM-IME.
380 // Unfortunately, changing the result of GetIMENotificationRequests() while
381 // an editor has focus isn't supported by IMEContentObserver nor
382 // ContentCacheInParent. Therefore, we need to request whole notifications
383 // which are necessary either IMMHandler or TSFTextStore.
384 return IMMHandler::GetIMENotificationRequests() |
385 TSFTextStore::GetIMENotificationRequests();
388 return IMMHandler::GetIMENotificationRequests();
391 // static
392 TextEventDispatcherListener*
393 IMEHandler::GetNativeTextEventDispatcherListener() {
394 return WinTextEventDispatcherListener::GetInstance();
397 // static
398 bool IMEHandler::GetOpenState(nsWindow* aWindow) {
399 if (IsTSFAvailable() && !IsIMMActive()) {
400 return TSFTextStore::GetIMEOpenState();
403 IMEContext context(aWindow);
404 return context.GetOpenState();
407 // static
408 void IMEHandler::OnDestroyWindow(nsWindow* aWindow) {
409 // When focus is in remote process, but the window is being destroyed, we
410 // need to clean up TSFTextStore here since NOTIFY_IME_OF_BLUR won't reach
411 // here because BrowserParent already lost the reference to the nsWindow when
412 // it receives from the remote process.
413 if (sFocusedWindow == aWindow) {
414 MOZ_ASSERT(aWindow->GetInputContext().IsOriginContentProcess(),
415 "input context of focused widget should've been set by a remote "
416 "process "
417 "if IME focus isn't cleared before destroying the widget");
418 NotifyIME(aWindow, IMENotification(NOTIFY_IME_OF_BLUR));
421 // We need to do nothing here for TSF. Just restore the default context
422 // if it's been disassociated.
423 if (!sIsInTSFMode) {
424 // MSDN says we need to set IS_DEFAULT to avoid memory leak when we use
425 // SetInputScopes API. Use an empty string to do this.
426 SetInputScopeForIMM32(aWindow, u""_ns, u""_ns, false);
428 AssociateIMEContext(aWindow, true);
431 // static
432 bool IMEHandler::NeedsToAssociateIMC() { return !sForceDisableCurrentIMM_IME; }
434 // static
435 void IMEHandler::SetInputContext(nsWindow* aWindow, InputContext& aInputContext,
436 const InputContextAction& aAction) {
437 sLastContextActionCause = aAction.mCause;
438 // FYI: If there is no composition, this call will do nothing.
439 NotifyIME(aWindow, IMENotification(REQUEST_TO_COMMIT_COMPOSITION));
441 if (aInputContext.mHTMLInputMode.EqualsLiteral("none")) {
442 IMEHandler::MaybeDismissOnScreenKeyboard(aWindow, Sync::Yes);
443 } else if (aAction.UserMightRequestOpenVKB()) {
444 IMEHandler::MaybeShowOnScreenKeyboard(aWindow, aInputContext);
447 bool enable = WinUtils::IsIMEEnabled(aInputContext);
448 bool adjustOpenState = (enable && aInputContext.mIMEState.mOpen !=
449 IMEState::DONT_CHANGE_OPEN_STATE);
450 bool open =
451 (adjustOpenState && aInputContext.mIMEState.mOpen == IMEState::OPEN);
453 // Note that even while a plugin has focus, we need to notify TSF of that.
454 if (sIsInTSFMode) {
455 TSFTextStore::SetInputContext(aWindow, aInputContext, aAction);
456 if (IsTSFAvailable()) {
457 if (sIsIMMEnabled) {
458 // Associate IMC with aWindow only when it's necessary.
459 AssociateIMEContext(aWindow, enable && NeedsToAssociateIMC());
461 if (adjustOpenState) {
462 TSFTextStore::SetIMEOpenState(open);
464 return;
466 } else {
467 // Set at least InputScope even when TextStore is not available.
468 SetInputScopeForIMM32(aWindow, aInputContext.mHTMLInputType,
469 aInputContext.mHTMLInputMode,
470 aInputContext.mInPrivateBrowsing);
473 AssociateIMEContext(aWindow, enable);
475 IMEContext context(aWindow);
476 if (adjustOpenState) {
477 context.SetOpenState(open);
481 // static
482 void IMEHandler::AssociateIMEContext(nsWindow* aWindowBase, bool aEnable) {
483 IMEContext context(aWindowBase);
484 if (aEnable) {
485 context.AssociateDefaultContext();
486 return;
488 // Don't disassociate the context after the window is destroyed.
489 if (aWindowBase->Destroyed()) {
490 return;
492 context.Disassociate();
495 // static
496 void IMEHandler::InitInputContext(nsWindow* aWindow,
497 InputContext& aInputContext) {
498 MOZ_ASSERT(aWindow);
499 MOZ_ASSERT(aWindow->GetWindowHandle(),
500 "IMEHandler::SetInputContext() requires non-nullptr HWND");
502 static bool sInitialized = false;
503 if (!sInitialized) {
504 sInitialized = true;
505 // Some TIPs like QQ Input (Simplified Chinese) may need normal window
506 // (i.e., windows except message window) when initializing themselves.
507 // Therefore, we need to initialize TSF/IMM modules after first normal
508 // window is created. InitInputContext() should be called immediately
509 // after creating each normal window, so, here is a good place to
510 // initialize these modules.
511 Initialize();
514 // For a11y, the default enabled state should be 'enabled'.
515 aInputContext.mIMEState.mEnabled = IMEEnabled::Enabled;
517 if (sIsInTSFMode) {
518 TSFTextStore::SetInputContext(
519 aWindow, aInputContext,
520 InputContextAction(InputContextAction::CAUSE_UNKNOWN,
521 InputContextAction::WIDGET_CREATED));
522 // IME context isn't necessary in pure TSF mode.
523 if (!sIsIMMEnabled) {
524 AssociateIMEContext(aWindow, false);
526 return;
529 #ifdef DEBUG
530 // NOTE: IMC may be null if IMM module isn't installed.
531 IMEContext context(aWindow);
532 MOZ_ASSERT(context.IsValid() || !CurrentKeyboardLayoutHasIME());
533 #endif // #ifdef DEBUG
536 #ifdef DEBUG
537 // static
538 bool IMEHandler::CurrentKeyboardLayoutHasIME() {
539 if (sIsInTSFMode) {
540 return TSFTextStore::CurrentKeyboardLayoutHasIME();
543 return IMMHandler::IsIMEAvailable();
545 #endif // #ifdef DEBUG
547 // static
548 void IMEHandler::OnKeyboardLayoutChanged() {
549 // Be aware, this method won't be called until TSFStaticSink starts to
550 // observe active TIP change. If you need to be notified of this, you
551 // need to create TSFStaticSink::Observe() or something and call it
552 // TSFStaticSink::EnsureInitActiveTIPKeyboard() forcibly.
554 if (!sIsIMMEnabled || !IsTSFAvailable()) {
555 return;
559 // static
560 void IMEHandler::SetInputScopeForIMM32(nsWindow* aWindow,
561 const nsAString& aHTMLInputType,
562 const nsAString& aHTMLInputMode,
563 bool aInPrivateBrowsing) {
564 if (sIsInTSFMode || !sSetInputScopes || aWindow->Destroyed()) {
565 return;
567 AutoTArray<InputScope, 3> scopes;
569 // IME may refer only first input scope, but we will append inputmode's
570 // input scopes since IME may refer it like Chrome.
571 AppendInputScopeFromType(aHTMLInputType, scopes);
572 AppendInputScopeFromInputMode(aHTMLInputMode, scopes);
574 if (aInPrivateBrowsing) {
575 scopes.AppendElement(IS_PRIVATE);
578 if (scopes.IsEmpty()) {
579 // At least, 1 item is necessary.
580 scopes.AppendElement(IS_DEFAULT);
583 sSetInputScopes(aWindow->GetWindowHandle(), scopes.Elements(),
584 scopes.Length(), nullptr, 0, nullptr, nullptr);
587 // static
588 void IMEHandler::AppendInputScopeFromInputMode(const nsAString& aHTMLInputMode,
589 nsTArray<InputScope>& aScopes) {
590 if (aHTMLInputMode.EqualsLiteral("mozAwesomebar")) {
591 // Even if Awesomebar has focus, user may not input URL directly.
592 // However, on-screen keyboard for URL should be shown because it has
593 // some useful additional keys like ".com" and they are not hindrances
594 // even when inputting non-URL text, e.g., words to search something in
595 // the web. On the other hand, a lot of Microsoft's IMEs and Google
596 // Japanese Input make their open state "closed" automatically if we
597 // notify them of URL as the input scope. However, this is very annoying
598 // for the users when they try to input some words to search the web or
599 // bookmark/history items. Therefore, if they are active, we need to
600 // notify them of the default input scope for avoiding this issue.
601 // FYI: We cannot check active TIP without TSF. Therefore, if it's
602 // not in TSF mode, this will check only if active IMM-IME is Google
603 // Japanese Input. Google Japanese Input is a TIP of TSF basically.
604 // However, if the OS is Win7 or it's installed on Win7 but has not
605 // been updated yet even after the OS is upgraded to Win8 or later,
606 // it's installed as IMM-IME.
607 if (TSFTextStore::ShouldSetInputScopeOfURLBarToDefault()) {
608 return;
610 // Don't append IS_SEARCH here for showing on-screen keyboard for URL.
611 if (!aScopes.Contains(IS_URL)) {
612 aScopes.AppendElement(IS_URL);
614 return;
617 // https://html.spec.whatwg.org/dev/interaction.html#attr-inputmode
618 if (aHTMLInputMode.EqualsLiteral("url")) {
619 if (!aScopes.Contains(IS_SEARCH)) {
620 aScopes.AppendElement(IS_URL);
622 return;
624 if (aHTMLInputMode.EqualsLiteral("email")) {
625 if (!aScopes.Contains(IS_EMAIL_SMTPEMAILADDRESS)) {
626 aScopes.AppendElement(IS_EMAIL_SMTPEMAILADDRESS);
628 return;
630 if (aHTMLInputMode.EqualsLiteral("tel")) {
631 if (!aScopes.Contains(IS_TELEPHONE_FULLTELEPHONENUMBER)) {
632 aScopes.AppendElement(IS_TELEPHONE_FULLTELEPHONENUMBER);
634 if (!aScopes.Contains(IS_TELEPHONE_LOCALNUMBER)) {
635 aScopes.AppendElement(IS_TELEPHONE_LOCALNUMBER);
637 return;
639 if (aHTMLInputMode.EqualsLiteral("numeric")) {
640 if (!aScopes.Contains(IS_DIGITS)) {
641 aScopes.AppendElement(IS_DIGITS);
643 return;
645 if (aHTMLInputMode.EqualsLiteral("decimal")) {
646 if (!aScopes.Contains(IS_NUMBER)) {
647 aScopes.AppendElement(IS_NUMBER);
649 return;
651 if (aHTMLInputMode.EqualsLiteral("search")) {
652 if (NeedsSearchInputScope() && !aScopes.Contains(IS_SEARCH)) {
653 aScopes.AppendElement(IS_SEARCH);
655 return;
659 // static
660 void IMEHandler::AppendInputScopeFromType(const nsAString& aHTMLInputType,
661 nsTArray<InputScope>& aScopes) {
662 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html
663 if (aHTMLInputType.EqualsLiteral("url")) {
664 aScopes.AppendElement(IS_URL);
665 return;
667 if (aHTMLInputType.EqualsLiteral("search")) {
668 if (NeedsSearchInputScope()) {
669 aScopes.AppendElement(IS_SEARCH);
671 return;
673 if (aHTMLInputType.EqualsLiteral("email")) {
674 aScopes.AppendElement(IS_EMAIL_SMTPEMAILADDRESS);
675 return;
677 if (aHTMLInputType.EqualsLiteral("password")) {
678 aScopes.AppendElement(IS_PASSWORD);
679 return;
681 if (aHTMLInputType.EqualsLiteral("datetime") ||
682 aHTMLInputType.EqualsLiteral("datetime-local")) {
683 aScopes.AppendElement(IS_DATE_FULLDATE);
684 aScopes.AppendElement(IS_TIME_FULLTIME);
685 return;
687 if (aHTMLInputType.EqualsLiteral("date") ||
688 aHTMLInputType.EqualsLiteral("month") ||
689 aHTMLInputType.EqualsLiteral("week")) {
690 aScopes.AppendElement(IS_DATE_FULLDATE);
691 return;
693 if (aHTMLInputType.EqualsLiteral("time")) {
694 aScopes.AppendElement(IS_TIME_FULLTIME);
695 return;
697 if (aHTMLInputType.EqualsLiteral("tel")) {
698 aScopes.AppendElement(IS_TELEPHONE_FULLTELEPHONENUMBER);
699 aScopes.AppendElement(IS_TELEPHONE_LOCALNUMBER);
700 return;
702 if (aHTMLInputType.EqualsLiteral("number")) {
703 aScopes.AppendElement(IS_NUMBER);
704 return;
708 // static
709 bool IMEHandler::NeedsSearchInputScope() {
710 return !StaticPrefs::intl_tsf_hack_atok_search_input_scope_disabled() ||
711 !TSFTextStore::IsATOKActive();
714 // static
715 bool IMEHandler::IsOnScreenKeyboardSupported() {
716 #ifdef NIGHTLY_BUILD
717 if (FxRWindowManager::GetInstance()->IsFxRWindow(sFocusedWindow)) {
718 return true;
720 #endif // NIGHTLY_BUILD
721 if (!Preferences::GetBool(kOskEnabled, true) ||
722 !IMEHandler::NeedOnScreenKeyboard()) {
723 return false;
726 // On Windows 11, we ignore tablet mode (see bug 1722208)
727 if (!IsWin11OrLater()) {
728 // On Windows 10 we require tablet mode, unless the user has set the
729 // relevant setting to enable the on-screen keyboard in desktop mode.
730 if (!IsInWin10TabletMode() && !AutoInvokeOnScreenKeyboardInDesktopMode()) {
731 return false;
735 return true;
738 // static
739 void IMEHandler::MaybeShowOnScreenKeyboard(nsWindow* aWindow,
740 const InputContext& aInputContext) {
741 if (aInputContext.mHTMLInputMode.EqualsLiteral("none")) {
742 return;
745 if (!IsOnScreenKeyboardSupported()) {
746 return;
749 IMEHandler::ShowOnScreenKeyboard(aWindow);
752 // static
753 void IMEHandler::MaybeDismissOnScreenKeyboard(nsWindow* aWindow, Sync aSync) {
754 #ifdef NIGHTLY_BUILD
755 if (FxRWindowManager::GetInstance()->IsFxRWindow(aWindow)) {
756 OSKVRManager::DismissOnScreenKeyboard();
758 #endif // NIGHTLY_BUILD
759 if (aSync == Sync::Yes) {
760 DismissOnScreenKeyboard(aWindow);
761 return;
764 RefPtr<nsWindow> window(aWindow);
765 NS_DispatchToCurrentThreadQueue(
766 NS_NewRunnableFunction("IMEHandler::MaybeDismissOnScreenKeyboard",
767 [window]() {
768 if (window->Destroyed()) {
769 return;
771 if (!sFocusedWindow) {
772 DismissOnScreenKeyboard(window);
775 EventQueuePriority::Idle);
778 // static
779 bool IMEHandler::WStringStartsWithCaseInsensitive(const std::wstring& aHaystack,
780 const std::wstring& aNeedle) {
781 std::wstring lowerCaseHaystack(aHaystack);
782 std::wstring lowerCaseNeedle(aNeedle);
783 std::transform(lowerCaseHaystack.begin(), lowerCaseHaystack.end(),
784 lowerCaseHaystack.begin(), ::tolower);
785 std::transform(lowerCaseNeedle.begin(), lowerCaseNeedle.end(),
786 lowerCaseNeedle.begin(), ::tolower);
787 return wcsstr(lowerCaseHaystack.c_str(), lowerCaseNeedle.c_str()) ==
788 lowerCaseHaystack.c_str();
791 // Returns false if a physical keyboard is detected on Windows 8 and up,
792 // or there is some other reason why an onscreen keyboard is not necessary.
793 // Returns true if no keyboard is found and this device looks like it needs
794 // an on-screen keyboard for text input.
795 // static
796 bool IMEHandler::NeedOnScreenKeyboard() {
797 if (!Preferences::GetBool(kOskDetectPhysicalKeyboard, true)) {
798 Preferences::SetString(kOskDebugReason, L"IKPOS: Detection disabled.");
799 return true;
802 // If the last focus cause was not user-initiated (ie a result of code
803 // setting focus to an element) then don't auto-show a keyboard. This
804 // avoids cases where the keyboard would pop up "just" because e.g. a
805 // web page chooses to focus a search field on the page, even when that
806 // really isn't what the user is trying to do at that moment.
807 if (!InputContextAction::IsHandlingUserInput(sLastContextActionCause)) {
808 return false;
811 // This function should be only invoked for machines with touch screens.
812 if ((::GetSystemMetrics(SM_DIGITIZER) & NID_INTEGRATED_TOUCH) !=
813 NID_INTEGRATED_TOUCH) {
814 Preferences::SetString(kOskDebugReason, L"IKPOS: Touch screen not found.");
815 return false;
818 // If the device is docked, the user is treating the device as a PC.
819 if (::GetSystemMetrics(SM_SYSTEMDOCKED) != 0) {
820 Preferences::SetString(kOskDebugReason, L"IKPOS: System docked.");
821 return false;
824 // To determine whether a keyboard is present on the device, we do the
825 // following:-
826 // 1. If the platform role is that of a mobile or slate device, check the
827 // system metric SM_CONVERTIBLESLATEMODE to see if it is being used
828 // in slate mode. If it is, also check that the last input was a touch.
829 // If all of this is true, then we should show the on-screen keyboard.
831 // 2. If step 1 didn't determine we should show the keyboard, we check if
832 // this device has keyboards attached to it.
834 // Check if the device is being used as a laptop or a tablet. This can be
835 // checked by first checking the role of the device and then the
836 // corresponding system metric (SM_CONVERTIBLESLATEMODE). If it is being
837 // used as a tablet then we want the OSK to show up.
838 if (!sDeterminedPowerPlatformRole) {
839 sDeterminedPowerPlatformRole = true;
840 sPowerPlatformRole = WinUtils::GetPowerPlatformRole();
843 // If this a mobile or slate (tablet) device, check if it is in slate mode.
844 // If the last input was touch, ignore whether or not a keyboard is present.
845 if ((sPowerPlatformRole == PlatformRoleMobile ||
846 sPowerPlatformRole == PlatformRoleSlate) &&
847 ::GetSystemMetrics(SM_CONVERTIBLESLATEMODE) == 0 &&
848 sLastContextActionCause == InputContextAction::CAUSE_TOUCH) {
849 Preferences::SetString(
850 kOskDebugReason,
851 L"IKPOS: Mobile/Slate Platform role, in slate mode with touch event.");
852 return true;
855 return !IMEHandler::IsKeyboardPresentOnSlate();
858 // Uses the Setup APIs to enumerate the attached keyboards and returns true
859 // if the keyboard count is 1 or more. While this will work in most cases
860 // it won't work if there are devices which expose keyboard interfaces which
861 // are attached to the machine.
862 // Based on IsKeyboardPresentOnSlate() in Chromium's base/win/win_util.cc.
863 // static
864 bool IMEHandler::IsKeyboardPresentOnSlate() {
865 const GUID KEYBOARD_CLASS_GUID = {
866 0x4D36E96B,
867 0xE325,
868 0x11CE,
869 {0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18}};
871 // Query for all the keyboard devices.
872 HDEVINFO device_info = ::SetupDiGetClassDevs(&KEYBOARD_CLASS_GUID, nullptr,
873 nullptr, DIGCF_PRESENT);
874 if (device_info == INVALID_HANDLE_VALUE) {
875 Preferences::SetString(kOskDebugReason, L"IKPOS: No keyboard info.");
876 return false;
879 // Enumerate all keyboards and look for ACPI\PNP and HID\VID devices. If
880 // the count is more than 1 we assume that a keyboard is present. This is
881 // under the assumption that there will always be one keyboard device.
882 for (DWORD i = 0;; ++i) {
883 SP_DEVINFO_DATA device_info_data = {0};
884 device_info_data.cbSize = sizeof(device_info_data);
885 if (!::SetupDiEnumDeviceInfo(device_info, i, &device_info_data)) {
886 break;
889 // Get the device ID.
890 wchar_t device_id[MAX_DEVICE_ID_LEN];
891 CONFIGRET status = ::CM_Get_Device_ID(device_info_data.DevInst, device_id,
892 MAX_DEVICE_ID_LEN, 0);
893 if (status == CR_SUCCESS) {
894 static const std::wstring BT_HID_DEVICE = L"HID\\{00001124";
895 static const std::wstring BT_HOGP_DEVICE = L"HID\\{00001812";
896 // To reduce the scope of the hack we only look for ACPI and HID\\VID
897 // prefixes in the keyboard device ids.
898 if (IMEHandler::WStringStartsWithCaseInsensitive(device_id, L"ACPI") ||
899 IMEHandler::WStringStartsWithCaseInsensitive(device_id,
900 L"HID\\VID") ||
901 IMEHandler::WStringStartsWithCaseInsensitive(device_id,
902 BT_HID_DEVICE) ||
903 IMEHandler::WStringStartsWithCaseInsensitive(device_id,
904 BT_HOGP_DEVICE)) {
905 // The heuristic we are using is to check the count of keyboards and
906 // return true if the API's report one or more keyboards. Please note
907 // that this will break for non keyboard devices which expose a
908 // keyboard PDO.
909 Preferences::SetString(kOskDebugReason,
910 L"IKPOS: Keyboard presence confirmed.");
911 return true;
915 Preferences::SetString(kOskDebugReason,
916 L"IKPOS: Lack of keyboard confirmed.");
917 return false;
920 // static
921 bool IMEHandler::IsInWin10TabletMode() {
922 bool isInTabletMode = WindowsUIUtils::GetInWin10TabletMode();
923 if (isInTabletMode) {
924 Preferences::SetString(kOskDebugReason, L"IITM: GetInTabletMode=true.");
925 } else {
926 Preferences::SetString(kOskDebugReason, L"IITM: GetInTabletMode=false.");
928 return isInTabletMode;
931 static bool ReadEnableDesktopModeAutoInvoke(uint32_t aRoot,
932 nsIWindowsRegKey* aRegKey,
933 uint32_t& aValue) {
934 nsresult rv;
935 rv = aRegKey->Open(aRoot, u"SOFTWARE\\Microsoft\\TabletTip\\1.7"_ns,
936 nsIWindowsRegKey::ACCESS_QUERY_VALUE);
937 if (NS_FAILED(rv)) {
938 Preferences::SetString(kOskDebugReason,
939 L"AIOSKIDM: failed opening regkey.");
940 return false;
942 // EnableDesktopModeAutoInvoke is an opt-in option from the Windows
943 // Settings to "Automatically show the touch keyboard in windowed apps
944 // when there's no keyboard attached to your device." If the user has
945 // opted-in to this behavior, the tablet-mode requirement is skipped.
946 rv = aRegKey->ReadIntValue(u"EnableDesktopModeAutoInvoke"_ns, &aValue);
947 if (NS_FAILED(rv)) {
948 Preferences::SetString(kOskDebugReason,
949 L"AIOSKIDM: failed reading value of regkey.");
950 return false;
952 return true;
955 // static
956 bool IMEHandler::AutoInvokeOnScreenKeyboardInDesktopMode() {
957 nsresult rv;
958 nsCOMPtr<nsIWindowsRegKey> regKey(
959 do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv));
960 if (NS_WARN_IF(NS_FAILED(rv))) {
961 Preferences::SetString(kOskDebugReason,
962 L"AIOSKIDM: "
963 L"nsIWindowsRegKey not available");
964 return false;
967 uint32_t value;
968 if (!ReadEnableDesktopModeAutoInvoke(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
969 regKey, value) &&
970 !ReadEnableDesktopModeAutoInvoke(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE,
971 regKey, value)) {
972 return false;
974 if (!!value) {
975 Preferences::SetString(kOskDebugReason, L"AIOSKIDM: regkey value=true.");
976 } else {
977 Preferences::SetString(kOskDebugReason, L"AIOSKIDM: regkey value=false.");
979 return !!value;
982 // Based on DisplayVirtualKeyboard() in Chromium's base/win/win_util.cc.
983 // static
984 void IMEHandler::ShowOnScreenKeyboard(nsWindow* aWindow) {
985 #ifdef NIGHTLY_BUILD
986 if (FxRWindowManager::GetInstance()->IsFxRWindow(sFocusedWindow)) {
987 OSKVRManager::ShowOnScreenKeyboard();
988 return;
990 #endif // NIGHTLY_BUILD
992 if (IsWin10AnniversaryUpdateOrLater()) {
993 OSKInputPaneManager::ShowOnScreenKeyboard(aWindow->GetWindowHandle());
994 return;
997 OSKTabTipManager::ShowOnScreenKeyboard();
1000 // Based on DismissVirtualKeyboard() in Chromium's base/win/win_util.cc.
1001 // static
1002 void IMEHandler::DismissOnScreenKeyboard(nsWindow* aWindow) {
1003 // Dismiss the virtual keyboard if it's open
1004 if (IsWin10AnniversaryUpdateOrLater()) {
1005 OSKInputPaneManager::DismissOnScreenKeyboard(aWindow->GetWindowHandle());
1006 return;
1009 OSKTabTipManager::DismissOnScreenKeyboard();
1012 bool IMEHandler::MaybeCreateNativeCaret(nsWindow* aWindow) {
1013 MOZ_ASSERT(aWindow);
1015 if (IsA11yHandlingNativeCaret()) {
1016 return false;
1019 if (!sHasNativeCaretBeenRequested) {
1020 // If we have not received WM_GETOBJECT for OBJID_CARET, there may be new
1021 // application which requires our caret information. For kicking its
1022 // window event proc, we should fire a window event here.
1023 // (If there is such application, sHasNativeCaretBeenRequested will be set
1024 // to true later.)
1025 // FYI: If we create native caret and move its position, native caret
1026 // causes EVENT_OBJECT_LOCATIONCHANGE event with OBJID_CARET and
1027 // OBJID_CLIENT.
1028 ::NotifyWinEvent(EVENT_OBJECT_LOCATIONCHANGE, aWindow->GetWindowHandle(),
1029 OBJID_CARET, OBJID_CLIENT);
1030 return false;
1033 MaybeDestroyNativeCaret();
1035 // If focused content is not text editable, we don't support caret
1036 // caret information without a11y module.
1037 if (!aWindow->GetInputContext().mIMEState.IsEditable()) {
1038 return false;
1041 WidgetQueryContentEvent queryCaretRectEvent(true, eQueryCaretRect, aWindow);
1042 aWindow->InitEvent(queryCaretRectEvent);
1044 WidgetQueryContentEvent::Options options;
1045 options.mRelativeToInsertionPoint = true;
1046 queryCaretRectEvent.InitForQueryCaretRect(0, options);
1048 aWindow->DispatchWindowEvent(queryCaretRectEvent);
1049 if (NS_WARN_IF(queryCaretRectEvent.Failed())) {
1050 return false;
1053 return CreateNativeCaret(aWindow, queryCaretRectEvent.mReply->mRect);
1056 bool IMEHandler::CreateNativeCaret(nsWindow* aWindow,
1057 const LayoutDeviceIntRect& aCaretRect) {
1058 MOZ_ASSERT(aWindow);
1060 MOZ_ASSERT(!IsA11yHandlingNativeCaret());
1062 sNativeCaretIsCreated =
1063 ::CreateCaret(aWindow->GetWindowHandle(), nullptr, aCaretRect.Width(),
1064 aCaretRect.Height());
1065 if (!sNativeCaretIsCreated) {
1066 return false;
1068 nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false);
1069 if (NS_WARN_IF(!toplevelWindow)) {
1070 MaybeDestroyNativeCaret();
1071 return false;
1074 LayoutDeviceIntPoint caretPosition(aCaretRect.TopLeft());
1075 if (toplevelWindow != aWindow) {
1076 caretPosition += toplevelWindow->WidgetToScreenOffset();
1077 caretPosition -= aWindow->WidgetToScreenOffset();
1080 ::SetCaretPos(caretPosition.x, caretPosition.y);
1081 return true;
1084 void IMEHandler::MaybeDestroyNativeCaret() {
1085 if (!sNativeCaretIsCreated) {
1086 return;
1088 ::DestroyCaret();
1089 sNativeCaretIsCreated = false;
1092 } // namespace widget
1093 } // namespace mozilla