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 "mozilla/Preferences.h"
10 #include "mozilla/TextEvents.h"
11 #include "mozilla/WindowsVersion.h"
12 #include "nsWindowDefs.h"
13 #include "WinTextEventDispatcherListener.h"
15 #include "TSFTextStore.h"
17 #include "OSKInputPaneManager.h"
18 #include "OSKTabTipManager.h"
19 #include "OSKVRManager.h"
20 #include "nsLookAndFeel.h"
23 #include "nsIWindowsRegKey.h"
24 #include "WindowsUIUtils.h"
27 # include "nsAccessibilityService.h"
28 #endif // #ifdef ACCESSIBILITY
36 #include "FxRWindowManager.h"
37 #include "moz_external_vr.h"
39 const char* kOskEnabled
= "ui.osk.enabled";
40 const char* kOskDetectPhysicalKeyboard
= "ui.osk.detect_physical_keyboard";
41 const char* kOskRequireWin10
= "ui.osk.require_win10";
42 const char* kOskDebugReason
= "ui.osk.debug.keyboardDisplayReason";
47 /******************************************************************************
49 ******************************************************************************/
51 nsWindow
* IMEHandler::sFocusedWindow
= nullptr;
52 InputContextAction::Cause
IMEHandler::sLastContextActionCause
=
53 InputContextAction::CAUSE_UNKNOWN
;
54 bool IMEHandler::sMaybeEditable
= false;
55 bool IMEHandler::sForceDisableCurrentIMM_IME
= false;
56 bool IMEHandler::sNativeCaretIsCreated
= false;
57 bool IMEHandler::sHasNativeCaretBeenRequested
= false;
59 bool IMEHandler::sIsInTSFMode
= false;
60 bool IMEHandler::sIsIMMEnabled
= true;
61 bool IMEHandler::sAssociateIMCOnlyWhenIMM_IMEActive
= false;
62 decltype(SetInputScopes
)* IMEHandler::sSetInputScopes
= nullptr;
64 static POWER_PLATFORM_ROLE sPowerPlatformRole
= PlatformRoleUnspecified
;
65 static bool sDeterminedPowerPlatformRole
= false;
68 void IMEHandler::Initialize() {
69 TSFTextStore::Initialize();
70 sIsInTSFMode
= TSFTextStore::IsInTSFMode();
72 !sIsInTSFMode
|| Preferences::GetBool("intl.tsf.support_imm", true);
73 sAssociateIMCOnlyWhenIMM_IMEActive
=
75 Preferences::GetBool("intl.tsf.associate_imc_only_when_imm_ime_is_active",
78 // When full TSFTextStore is not available, try to use SetInputScopes API
79 // to enable at least InputScope. Use GET_MODULE_HANDLE_EX_FLAG_PIN to
80 // ensure that msctf.dll will not be unloaded.
81 HMODULE module
= nullptr;
82 if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN
, L
"msctf.dll",
84 sSetInputScopes
= reinterpret_cast<decltype(SetInputScopes
)*>(
85 GetProcAddress(module
, "SetInputScopes"));
89 IMMHandler::Initialize();
91 sForceDisableCurrentIMM_IME
= IMMHandler::IsActiveIMEInBlockList();
95 void IMEHandler::Terminate() {
97 TSFTextStore::Terminate();
101 IMMHandler::Terminate();
102 WinTextEventDispatcherListener::Shutdown();
106 void* IMEHandler::GetNativeData(nsWindow
* aWindow
, uint32_t aDataType
) {
107 if (aDataType
== NS_RAW_NATIVE_IME_CONTEXT
) {
108 if (IsTSFAvailable()) {
109 return TSFTextStore::GetThreadManager();
111 IMEContext
context(aWindow
);
112 if (context
.IsValid()) {
113 return context
.get();
115 // If IMC isn't associated with the window, IME is disabled on the window
116 // now. In such case, we should return default IMC instead.
117 const IMEContext
& defaultIMC
= aWindow
->DefaultIMC();
118 if (defaultIMC
.IsValid()) {
119 return defaultIMC
.get();
121 // If there is no default IMC, we should return the pointer to the window
122 // since if we return nullptr, IMEStateManager cannot manage composition
123 // with TextComposition instance. This is possible if no IME is installed,
124 // but composition may occur with dead key sequence.
128 void* result
= TSFTextStore::GetNativeData(aDataType
);
129 if (!result
|| !(*(static_cast<void**>(result
)))) {
132 // XXX During the TSF module test, sIsInTSFMode must be true. After that,
133 // the value should be restored but currently, there is no way for that.
134 // When the TSF test is enabled again, we need to fix this. Perhaps,
135 // sending a message can fix this.
141 bool IMEHandler::ProcessRawKeyMessage(const MSG
& aMsg
) {
142 if (IsTSFAvailable()) {
143 return TSFTextStore::ProcessRawKeyMessage(aMsg
);
145 return false; // noting to do in IMM mode.
149 bool IMEHandler::ProcessMessage(nsWindow
* aWindow
, UINT aMessage
,
150 WPARAM
& aWParam
, LPARAM
& aLParam
,
151 MSGResult
& aResult
) {
152 // If we're putting native caret over our caret, Windows dispatches
153 // EVENT_OBJECT_LOCATIONCHANGE event on other applications which hook
154 // the event with ::SetWinEventHook() and handles WM_GETOBJECT for
155 // OBJID_CARET (this is request of caret from such applications) instead
156 // of us. If a11y module is active, it observes every our caret change
157 // and put native caret over it automatically. However, if other
158 // applications require only caret information, activating a11y module is
159 // overwork and such applications may requires carets only in editors.
160 // Therefore, if it'd be possible, IMEHandler should put native caret over
161 // our caret, but there is a problem. Some versions of ATOK (Japanese TIP)
162 // refer native caret and if there is, the behavior is worse than the
163 // behavior without native caret. Therefore, we shouldn't put native caret
164 // as far as possible.
165 if (!sHasNativeCaretBeenRequested
&& aMessage
== WM_GETOBJECT
&&
166 static_cast<DWORD
>(aLParam
) == OBJID_CARET
) {
167 // So, when we receive first WM_GETOBJECT for OBJID_CARET, let's start to
168 // create native caret for such applications.
169 sHasNativeCaretBeenRequested
= true;
170 // If an editable element has focus, we can put native caret now.
171 // XXX Should we avoid doing this if there is composition?
172 MaybeCreateNativeCaret(aWindow
);
175 if (IsTSFAvailable()) {
176 TSFTextStore::ProcessMessage(aWindow
, aMessage
, aWParam
, aLParam
, aResult
);
177 if (aResult
.mConsumed
) {
180 // If we don't support IMM in TSF mode, we don't use IMMHandler.
181 if (!sIsIMMEnabled
) {
184 // IME isn't implemented with IMM, IMMHandler shouldn't handle any
186 if (!IsIMMActive()) {
192 IMMHandler::ProcessMessage(aWindow
, aMessage
, aWParam
, aLParam
, aResult
);
194 // If user changes active IME to an IME which is listed in our block list,
195 // we should disassociate IMC from the window for preventing the IME to work
197 if (aMessage
== WM_INPUTLANGCHANGE
) {
198 bool disableIME
= IMMHandler::IsActiveIMEInBlockList();
199 if (disableIME
!= sForceDisableCurrentIMM_IME
) {
201 !disableIME
&& WinUtils::IsIMEEnabled(aWindow
->InputContextRef());
202 AssociateIMEContext(aWindow
, enable
);
203 sForceDisableCurrentIMM_IME
= disableIME
;
211 bool IMEHandler::IsA11yHandlingNativeCaret() {
212 #ifndef ACCESSIBILITY
214 #else // #ifndef ACCESSIBILITY
215 // Let's assume that when there is the service, it handles native caret.
216 return GetAccService() != nullptr;
217 #endif // #ifndef ACCESSIBILITY #else
221 bool IMEHandler::IsIMMActive() { return TSFTextStore::IsIMM_IMEActive(); }
224 bool IMEHandler::IsComposing() {
225 if (IsTSFAvailable()) {
226 return TSFTextStore::IsComposing() || IMMHandler::IsComposing();
229 return IMMHandler::IsComposing();
233 bool IMEHandler::IsComposingOn(nsWindow
* aWindow
) {
234 if (IsTSFAvailable()) {
235 return TSFTextStore::IsComposingOn(aWindow
) ||
236 IMMHandler::IsComposingOn(aWindow
);
239 return IMMHandler::IsComposingOn(aWindow
);
243 nsresult
IMEHandler::NotifyIME(nsWindow
* aWindow
,
244 const IMENotification
& aIMENotification
) {
245 if (IsTSFAvailable()) {
246 switch (aIMENotification
.mMessage
) {
247 case NOTIFY_IME_OF_SELECTION_CHANGE
: {
248 nsresult rv
= TSFTextStore::OnSelectionChange(aIMENotification
);
249 // If IMM IME is active, we need to notify IMMHandler of updating
250 // composition change. It will adjust candidate window position or
251 // composition window position.
252 bool isIMMActive
= IsIMMActive();
254 IMMHandler::OnUpdateComposition(aWindow
);
256 IMMHandler::OnSelectionChange(aWindow
, aIMENotification
, isIMMActive
);
259 case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED
:
260 // If IMM IME is active, we need to notify IMMHandler of updating
261 // composition change. It will adjust candidate window position or
262 // composition window position.
264 IMMHandler::OnUpdateComposition(aWindow
);
266 TSFTextStore::OnUpdateComposition();
269 case NOTIFY_IME_OF_TEXT_CHANGE
:
270 return TSFTextStore::OnTextChange(aIMENotification
);
271 case NOTIFY_IME_OF_FOCUS
: {
272 sFocusedWindow
= aWindow
;
273 IMMHandler::OnFocusChange(true, aWindow
);
274 nsresult rv
= TSFTextStore::OnFocusChange(true, aWindow
,
275 aWindow
->GetInputContext());
276 MaybeCreateNativeCaret(aWindow
);
277 IMEHandler::MaybeShowOnScreenKeyboard(aWindow
,
278 aWindow
->GetInputContext());
281 case NOTIFY_IME_OF_BLUR
:
282 sFocusedWindow
= nullptr;
283 IMEHandler::MaybeDismissOnScreenKeyboard(aWindow
);
284 IMMHandler::OnFocusChange(false, aWindow
);
285 return TSFTextStore::OnFocusChange(false, aWindow
,
286 aWindow
->GetInputContext());
287 case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT
:
288 // If IMM IME is active, we should send a mouse button event via IMM.
290 return IMMHandler::OnMouseButtonEvent(aWindow
, aIMENotification
);
292 return TSFTextStore::OnMouseButtonEvent(aIMENotification
);
293 case REQUEST_TO_COMMIT_COMPOSITION
:
294 if (TSFTextStore::IsComposingOn(aWindow
)) {
295 TSFTextStore::CommitComposition(false);
296 } else if (IsIMMActive()) {
297 IMMHandler::CommitComposition(aWindow
);
300 case REQUEST_TO_CANCEL_COMPOSITION
:
301 if (TSFTextStore::IsComposingOn(aWindow
)) {
302 TSFTextStore::CommitComposition(true);
303 } else if (IsIMMActive()) {
304 IMMHandler::CancelComposition(aWindow
);
307 case NOTIFY_IME_OF_POSITION_CHANGE
:
308 return TSFTextStore::OnLayoutChange();
310 return NS_ERROR_NOT_IMPLEMENTED
;
314 switch (aIMENotification
.mMessage
) {
315 case REQUEST_TO_COMMIT_COMPOSITION
:
316 IMMHandler::CommitComposition(aWindow
);
318 case REQUEST_TO_CANCEL_COMPOSITION
:
319 IMMHandler::CancelComposition(aWindow
);
321 case NOTIFY_IME_OF_POSITION_CHANGE
:
322 case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED
:
323 IMMHandler::OnUpdateComposition(aWindow
);
325 case NOTIFY_IME_OF_SELECTION_CHANGE
:
326 IMMHandler::OnSelectionChange(aWindow
, aIMENotification
, true);
327 // IMMHandler::OnSelectionChange() cannot work without its singleton
328 // instance. Therefore, IMEHandler needs to create native caret instead
329 // if it's necessary.
330 MaybeCreateNativeCaret(aWindow
);
332 case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT
:
333 return IMMHandler::OnMouseButtonEvent(aWindow
, aIMENotification
);
334 case NOTIFY_IME_OF_FOCUS
:
335 sFocusedWindow
= aWindow
;
336 IMMHandler::OnFocusChange(true, aWindow
);
337 IMEHandler::MaybeShowOnScreenKeyboard(aWindow
,
338 aWindow
->GetInputContext());
339 MaybeCreateNativeCaret(aWindow
);
341 case NOTIFY_IME_OF_BLUR
:
342 sFocusedWindow
= nullptr;
343 IMEHandler::MaybeDismissOnScreenKeyboard(aWindow
);
344 IMMHandler::OnFocusChange(false, aWindow
);
345 // If a plugin gets focus while TSF has focus, we need to notify TSF of
347 if (TSFTextStore::ThinksHavingFocus()) {
348 return TSFTextStore::OnFocusChange(false, aWindow
,
349 aWindow
->GetInputContext());
353 return NS_ERROR_NOT_IMPLEMENTED
;
358 IMENotificationRequests
IMEHandler::GetIMENotificationRequests() {
359 if (IsTSFAvailable()) {
360 if (!sIsIMMEnabled
) {
361 return TSFTextStore::GetIMENotificationRequests();
363 // Even if TSF is available, the active IME may be an IMM-IME.
364 // Unfortunately, changing the result of GetIMENotificationRequests() while
365 // an editor has focus isn't supported by IMEContentObserver nor
366 // ContentCacheInParent. Therefore, we need to request whole notifications
367 // which are necessary either IMMHandler or TSFTextStore.
368 return IMMHandler::GetIMENotificationRequests() |
369 TSFTextStore::GetIMENotificationRequests();
372 return IMMHandler::GetIMENotificationRequests();
376 TextEventDispatcherListener
*
377 IMEHandler::GetNativeTextEventDispatcherListener() {
378 return WinTextEventDispatcherListener::GetInstance();
382 bool IMEHandler::GetOpenState(nsWindow
* aWindow
) {
383 if (IsTSFAvailable() && !IsIMMActive()) {
384 return TSFTextStore::GetIMEOpenState();
387 IMEContext
context(aWindow
);
388 return context
.GetOpenState();
392 void IMEHandler::OnDestroyWindow(nsWindow
* aWindow
) {
393 // When focus is in remote process, but the window is being destroyed, we
394 // need to clean up TSFTextStore here since NOTIFY_IME_OF_BLUR won't reach
395 // here because BrowserParent already lost the reference to the nsWindow when
396 // it receives from the remote process.
397 if (sFocusedWindow
== aWindow
) {
398 MOZ_ASSERT(aWindow
->GetInputContext().IsOriginContentProcess(),
399 "input context of focused widget should've been set by a remote "
401 "if IME focus isn't cleared before destroying the widget");
402 NotifyIME(aWindow
, IMENotification(NOTIFY_IME_OF_BLUR
));
405 // We need to do nothing here for TSF. Just restore the default context
406 // if it's been disassociated.
408 // MSDN says we need to set IS_DEFAULT to avoid memory leak when we use
409 // SetInputScopes API. Use an empty string to do this.
410 SetInputScopeForIMM32(aWindow
, u
""_ns
, u
""_ns
, false);
412 AssociateIMEContext(aWindow
, true);
416 bool IMEHandler::NeedsToAssociateIMC() {
417 return !sForceDisableCurrentIMM_IME
&&
418 (!sAssociateIMCOnlyWhenIMM_IMEActive
|| !IsIMMActive());
422 void IMEHandler::SetInputContext(nsWindow
* aWindow
, InputContext
& aInputContext
,
423 const InputContextAction
& aAction
) {
424 sLastContextActionCause
= aAction
.mCause
;
425 // FYI: If there is no composition, this call will do nothing.
426 NotifyIME(aWindow
, IMENotification(REQUEST_TO_COMMIT_COMPOSITION
));
428 if (aInputContext
.mHTMLInputInputmode
.EqualsLiteral("none")) {
429 IMEHandler::MaybeDismissOnScreenKeyboard(aWindow
, Sync::Yes
);
430 } else if (aAction
.UserMightRequestOpenVKB()) {
431 IMEHandler::MaybeShowOnScreenKeyboard(aWindow
, aInputContext
);
434 bool enable
= WinUtils::IsIMEEnabled(aInputContext
);
435 bool adjustOpenState
= (enable
&& aInputContext
.mIMEState
.mOpen
!=
436 IMEState::DONT_CHANGE_OPEN_STATE
);
438 (adjustOpenState
&& aInputContext
.mIMEState
.mOpen
== IMEState::OPEN
);
440 // Note that even while a plugin has focus, we need to notify TSF of that.
442 TSFTextStore::SetInputContext(aWindow
, aInputContext
, aAction
);
443 if (IsTSFAvailable()) {
445 // Associate IMC with aWindow only when it's necessary.
446 AssociateIMEContext(aWindow
, enable
&& NeedsToAssociateIMC());
448 if (adjustOpenState
) {
449 TSFTextStore::SetIMEOpenState(open
);
454 // Set at least InputScope even when TextStore is not available.
455 SetInputScopeForIMM32(aWindow
, aInputContext
.mHTMLInputType
,
456 aInputContext
.mHTMLInputInputmode
,
457 aInputContext
.mInPrivateBrowsing
);
460 AssociateIMEContext(aWindow
, enable
);
462 IMEContext
context(aWindow
);
463 if (adjustOpenState
) {
464 context
.SetOpenState(open
);
469 void IMEHandler::AssociateIMEContext(nsWindow
* aWindowBase
, bool aEnable
) {
470 IMEContext
context(aWindowBase
);
472 context
.AssociateDefaultContext();
475 // Don't disassociate the context after the window is destroyed.
476 if (aWindowBase
->Destroyed()) {
479 context
.Disassociate();
483 void IMEHandler::InitInputContext(nsWindow
* aWindow
,
484 InputContext
& aInputContext
) {
486 MOZ_ASSERT(aWindow
->GetWindowHandle(),
487 "IMEHandler::SetInputContext() requires non-nullptr HWND");
489 static bool sInitialized
= false;
492 // Some TIPs like QQ Input (Simplified Chinese) may need normal window
493 // (i.e., windows except message window) when initializing themselves.
494 // Therefore, we need to initialize TSF/IMM modules after first normal
495 // window is created. InitInputContext() should be called immediately
496 // after creating each normal window, so, here is a good place to
497 // initialize these modules.
501 // For a11y, the default enabled state should be 'enabled'.
502 aInputContext
.mIMEState
.mEnabled
= IMEEnabled::Enabled
;
505 TSFTextStore::SetInputContext(
506 aWindow
, aInputContext
,
507 InputContextAction(InputContextAction::CAUSE_UNKNOWN
,
508 InputContextAction::WIDGET_CREATED
));
509 // IME context isn't necessary in pure TSF mode.
510 if (!sIsIMMEnabled
) {
511 AssociateIMEContext(aWindow
, false);
517 // NOTE: IMC may be null if IMM module isn't installed.
518 IMEContext
context(aWindow
);
519 MOZ_ASSERT(context
.IsValid() || !CurrentKeyboardLayoutHasIME());
520 #endif // #ifdef DEBUG
525 bool IMEHandler::CurrentKeyboardLayoutHasIME() {
527 return TSFTextStore::CurrentKeyboardLayoutHasIME();
530 return IMMHandler::IsIMEAvailable();
532 #endif // #ifdef DEBUG
535 void IMEHandler::OnKeyboardLayoutChanged() {
536 // Be aware, this method won't be called until TSFStaticSink starts to
537 // observe active TIP change. If you need to be notified of this, you
538 // need to create TSFStaticSink::Observe() or something and call it
539 // TSFStaticSink::EnsureInitActiveTIPKeyboard() forcibly.
541 if (!sIsIMMEnabled
|| !IsTSFAvailable()) {
545 // We don't need to do anything when sAssociateIMCOnlyWhenIMM_IMEActive is
546 // false because IMContext won't be associated/disassociated when changing
547 // active keyboard layout/IME.
548 if (!sAssociateIMCOnlyWhenIMM_IMEActive
) {
552 // If there is no TSFTextStore which has focus, i.e., no editor has focus,
553 // nothing to do here.
554 nsWindow
* windowBase
= TSFTextStore::GetEnabledWindowBase();
559 // If IME isn't available, nothing to do here.
560 InputContext inputContext
= windowBase
->GetInputContext();
561 if (!WinUtils::IsIMEEnabled(inputContext
)) {
565 // Associate or Disassociate IMC if it's necessary.
566 // Note that this does nothing if the window has already associated with or
567 // disassociated from the window.
568 AssociateIMEContext(windowBase
, NeedsToAssociateIMC());
572 void IMEHandler::SetInputScopeForIMM32(nsWindow
* aWindow
,
573 const nsAString
& aHTMLInputType
,
574 const nsAString
& aHTMLInputInputmode
,
575 bool aInPrivateBrowsing
) {
576 if (sIsInTSFMode
|| !sSetInputScopes
|| aWindow
->Destroyed()) {
579 AutoTArray
<InputScope
, 3> scopes
;
581 // IME may refer only first input scope, but we will append inputmode's
582 // input scopes since IME may refer it like Chrome.
583 AppendInputScopeFromType(aHTMLInputType
, scopes
);
584 AppendInputScopeFromInputmode(aHTMLInputInputmode
, scopes
);
586 if (aInPrivateBrowsing
) {
587 scopes
.AppendElement(IS_PRIVATE
);
590 if (scopes
.IsEmpty()) {
591 // At least, 1 item is necessary.
592 scopes
.AppendElement(IS_DEFAULT
);
595 sSetInputScopes(aWindow
->GetWindowHandle(), scopes
.Elements(),
596 scopes
.Length(), nullptr, 0, nullptr, nullptr);
600 void IMEHandler::AppendInputScopeFromInputmode(const nsAString
& aInputmode
,
601 nsTArray
<InputScope
>& aScopes
) {
602 if (aInputmode
.EqualsLiteral("mozAwesomebar")) {
603 // Even if Awesomebar has focus, user may not input URL directly.
604 // However, on-screen keyboard for URL should be shown because it has
605 // some useful additional keys like ".com" and they are not hindrances
606 // even when inputting non-URL text, e.g., words to search something in
607 // the web. On the other hand, a lot of Microsoft's IMEs and Google
608 // Japanese Input make their open state "closed" automatically if we
609 // notify them of URL as the input scope. However, this is very annoying
610 // for the users when they try to input some words to search the web or
611 // bookmark/history items. Therefore, if they are active, we need to
612 // notify them of the default input scope for avoiding this issue.
613 // FYI: We cannot check active TIP without TSF. Therefore, if it's
614 // not in TSF mode, this will check only if active IMM-IME is Google
615 // Japanese Input. Google Japanese Input is a TIP of TSF basically.
616 // However, if the OS is Win7 or it's installed on Win7 but has not
617 // been updated yet even after the OS is upgraded to Win8 or later,
618 // it's installed as IMM-IME.
619 if (TSFTextStore::ShouldSetInputScopeOfURLBarToDefault()) {
622 // Don't append IS_SEARCH here for showing on-screen keyboard for URL.
623 if (!aScopes
.Contains(IS_URL
)) {
624 aScopes
.AppendElement(IS_URL
);
629 // https://html.spec.whatwg.org/dev/interaction.html#attr-inputmode
630 if (aInputmode
.EqualsLiteral("url")) {
631 if (!aScopes
.Contains(IS_SEARCH
)) {
632 aScopes
.AppendElement(IS_URL
);
636 if (aInputmode
.EqualsLiteral("email")) {
637 if (!aScopes
.Contains(IS_EMAIL_SMTPEMAILADDRESS
)) {
638 aScopes
.AppendElement(IS_EMAIL_SMTPEMAILADDRESS
);
642 if (aInputmode
.EqualsLiteral("tel")) {
643 if (!aScopes
.Contains(IS_TELEPHONE_FULLTELEPHONENUMBER
)) {
644 aScopes
.AppendElement(IS_TELEPHONE_FULLTELEPHONENUMBER
);
646 if (!aScopes
.Contains(IS_TELEPHONE_LOCALNUMBER
)) {
647 aScopes
.AppendElement(IS_TELEPHONE_LOCALNUMBER
);
651 if (aInputmode
.EqualsLiteral("numeric")) {
652 if (!aScopes
.Contains(IS_DIGITS
)) {
653 aScopes
.AppendElement(IS_DIGITS
);
657 if (aInputmode
.EqualsLiteral("decimal")) {
658 if (!aScopes
.Contains(IS_NUMBER
)) {
659 aScopes
.AppendElement(IS_NUMBER
);
663 if (aInputmode
.EqualsLiteral("search")) {
664 if (NeedsSearchInputScope() && !aScopes
.Contains(IS_SEARCH
)) {
665 aScopes
.AppendElement(IS_SEARCH
);
672 void IMEHandler::AppendInputScopeFromType(const nsAString
& aHTMLInputType
,
673 nsTArray
<InputScope
>& aScopes
) {
674 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html
675 if (aHTMLInputType
.EqualsLiteral("url")) {
676 aScopes
.AppendElement(IS_URL
);
679 if (aHTMLInputType
.EqualsLiteral("search")) {
680 if (NeedsSearchInputScope()) {
681 aScopes
.AppendElement(IS_SEARCH
);
685 if (aHTMLInputType
.EqualsLiteral("email")) {
686 aScopes
.AppendElement(IS_EMAIL_SMTPEMAILADDRESS
);
689 if (aHTMLInputType
.EqualsLiteral("password")) {
690 aScopes
.AppendElement(IS_PASSWORD
);
693 if (aHTMLInputType
.EqualsLiteral("datetime") ||
694 aHTMLInputType
.EqualsLiteral("datetime-local")) {
695 aScopes
.AppendElement(IS_DATE_FULLDATE
);
696 aScopes
.AppendElement(IS_TIME_FULLTIME
);
699 if (aHTMLInputType
.EqualsLiteral("date") ||
700 aHTMLInputType
.EqualsLiteral("month") ||
701 aHTMLInputType
.EqualsLiteral("week")) {
702 aScopes
.AppendElement(IS_DATE_FULLDATE
);
705 if (aHTMLInputType
.EqualsLiteral("time")) {
706 aScopes
.AppendElement(IS_TIME_FULLTIME
);
709 if (aHTMLInputType
.EqualsLiteral("tel")) {
710 aScopes
.AppendElement(IS_TELEPHONE_FULLTELEPHONENUMBER
);
711 aScopes
.AppendElement(IS_TELEPHONE_LOCALNUMBER
);
714 if (aHTMLInputType
.EqualsLiteral("number")) {
715 aScopes
.AppendElement(IS_NUMBER
);
721 bool IMEHandler::NeedsSearchInputScope() {
722 return !(Preferences::GetBool(
723 "intl.tsf.hack.atok.search_input_scope_disabled", false) &&
724 TSFTextStore::IsATOKActive());
728 bool IMEHandler::IsOnScreenKeyboardSupported() {
730 if (FxRWindowManager::GetInstance()->IsFxRWindow(sFocusedWindow
)) {
733 #endif // NIGHTLY_BUILD
734 if (!IsWin8OrLater() || !Preferences::GetBool(kOskEnabled
, true) ||
735 !IMEHandler::NeedOnScreenKeyboard()) {
739 // On Windows 10 we require tablet mode, unless the user has set the relevant
740 // Windows setting to enable the on-screen keyboard in desktop mode.
741 // We might be disabled specifically on Win8(.1), so we check that afterwards.
742 if (IsWin10OrLater() && !IsWin11OrLater()) {
743 if (!IsInTabletMode() && !AutoInvokeOnScreenKeyboardInDesktopMode()) {
746 } else if (Preferences::GetBool(kOskRequireWin10
, true)) {
754 void IMEHandler::MaybeShowOnScreenKeyboard(nsWindow
* aWindow
,
755 const InputContext
& aInputContext
) {
756 if (aInputContext
.mHTMLInputInputmode
.EqualsLiteral("none")) {
760 if (!IsOnScreenKeyboardSupported()) {
764 IMEHandler::ShowOnScreenKeyboard(aWindow
);
768 void IMEHandler::MaybeDismissOnScreenKeyboard(nsWindow
* aWindow
, Sync aSync
) {
770 if (FxRWindowManager::GetInstance()->IsFxRWindow(aWindow
)) {
771 OSKVRManager::DismissOnScreenKeyboard();
773 #endif // NIGHTLY_BUILD
774 if (!IsWin8OrLater()) {
778 if (aSync
== Sync::Yes
) {
779 DismissOnScreenKeyboard(aWindow
);
783 RefPtr
<nsWindow
> window(aWindow
);
784 NS_DispatchToCurrentThreadQueue(
785 NS_NewRunnableFunction("IMEHandler::MaybeDismissOnScreenKeyboard",
787 if (window
->Destroyed()) {
790 if (!sFocusedWindow
) {
791 DismissOnScreenKeyboard(window
);
794 EventQueuePriority::Idle
);
798 bool IMEHandler::WStringStartsWithCaseInsensitive(const std::wstring
& aHaystack
,
799 const std::wstring
& aNeedle
) {
800 std::wstring
lowerCaseHaystack(aHaystack
);
801 std::wstring
lowerCaseNeedle(aNeedle
);
802 std::transform(lowerCaseHaystack
.begin(), lowerCaseHaystack
.end(),
803 lowerCaseHaystack
.begin(), ::tolower
);
804 std::transform(lowerCaseNeedle
.begin(), lowerCaseNeedle
.end(),
805 lowerCaseNeedle
.begin(), ::tolower
);
806 return wcsstr(lowerCaseHaystack
.c_str(), lowerCaseNeedle
.c_str()) ==
807 lowerCaseHaystack
.c_str();
810 // Returns false if a physical keyboard is detected on Windows 8 and up,
811 // or there is some other reason why an onscreen keyboard is not necessary.
812 // Returns true if no keyboard is found and this device looks like it needs
813 // an on-screen keyboard for text input.
815 bool IMEHandler::NeedOnScreenKeyboard() {
816 // This function is only supported for Windows 8 and up.
817 if (!IsWin8OrLater()) {
818 Preferences::SetString(kOskDebugReason
, L
"IKPOS: Requires Win8+.");
822 if (!Preferences::GetBool(kOskDetectPhysicalKeyboard
, true)) {
823 Preferences::SetString(kOskDebugReason
, L
"IKPOS: Detection disabled.");
827 // If the last focus cause was not user-initiated (ie a result of code
828 // setting focus to an element) then don't auto-show a keyboard. This
829 // avoids cases where the keyboard would pop up "just" because e.g. a
830 // web page chooses to focus a search field on the page, even when that
831 // really isn't what the user is trying to do at that moment.
832 if (!InputContextAction::IsHandlingUserInput(sLastContextActionCause
)) {
836 // This function should be only invoked for machines with touch screens.
837 if ((::GetSystemMetrics(SM_DIGITIZER
) & NID_INTEGRATED_TOUCH
) !=
838 NID_INTEGRATED_TOUCH
) {
839 Preferences::SetString(kOskDebugReason
, L
"IKPOS: Touch screen not found.");
843 // If the device is docked, the user is treating the device as a PC.
844 if (::GetSystemMetrics(SM_SYSTEMDOCKED
) != 0) {
845 Preferences::SetString(kOskDebugReason
, L
"IKPOS: System docked.");
849 // To determine whether a keyboard is present on the device, we do the
851 // 1. If the platform role is that of a mobile or slate device, check the
852 // system metric SM_CONVERTIBLESLATEMODE to see if it is being used
853 // in slate mode. If it is, also check that the last input was a touch.
854 // If all of this is true, then we should show the on-screen keyboard.
856 // 2. If step 1 didn't determine we should show the keyboard, we check if
857 // this device has keyboards attached to it.
859 // Check if the device is being used as a laptop or a tablet. This can be
860 // checked by first checking the role of the device and then the
861 // corresponding system metric (SM_CONVERTIBLESLATEMODE). If it is being
862 // used as a tablet then we want the OSK to show up.
863 if (!sDeterminedPowerPlatformRole
) {
864 sDeterminedPowerPlatformRole
= true;
865 sPowerPlatformRole
= WinUtils::GetPowerPlatformRole();
868 // If this a mobile or slate (tablet) device, check if it is in slate mode.
869 // If the last input was touch, ignore whether or not a keyboard is present.
870 if ((sPowerPlatformRole
== PlatformRoleMobile
||
871 sPowerPlatformRole
== PlatformRoleSlate
) &&
872 ::GetSystemMetrics(SM_CONVERTIBLESLATEMODE
) == 0 &&
873 sLastContextActionCause
== InputContextAction::CAUSE_TOUCH
) {
874 Preferences::SetString(
876 L
"IKPOS: Mobile/Slate Platform role, in slate mode with touch event.");
880 return !IMEHandler::IsKeyboardPresentOnSlate();
883 // Uses the Setup APIs to enumerate the attached keyboards and returns true
884 // if the keyboard count is 1 or more. While this will work in most cases
885 // it won't work if there are devices which expose keyboard interfaces which
886 // are attached to the machine.
887 // Based on IsKeyboardPresentOnSlate() in Chromium's base/win/win_util.cc.
889 bool IMEHandler::IsKeyboardPresentOnSlate() {
890 const GUID KEYBOARD_CLASS_GUID
= {
894 {0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18}};
896 // Query for all the keyboard devices.
897 HDEVINFO device_info
= ::SetupDiGetClassDevs(&KEYBOARD_CLASS_GUID
, nullptr,
898 nullptr, DIGCF_PRESENT
);
899 if (device_info
== INVALID_HANDLE_VALUE
) {
900 Preferences::SetString(kOskDebugReason
, L
"IKPOS: No keyboard info.");
904 // Enumerate all keyboards and look for ACPI\PNP and HID\VID devices. If
905 // the count is more than 1 we assume that a keyboard is present. This is
906 // under the assumption that there will always be one keyboard device.
907 for (DWORD i
= 0;; ++i
) {
908 SP_DEVINFO_DATA device_info_data
= {0};
909 device_info_data
.cbSize
= sizeof(device_info_data
);
910 if (!::SetupDiEnumDeviceInfo(device_info
, i
, &device_info_data
)) {
914 // Get the device ID.
915 wchar_t device_id
[MAX_DEVICE_ID_LEN
];
916 CONFIGRET status
= ::CM_Get_Device_ID(device_info_data
.DevInst
, device_id
,
917 MAX_DEVICE_ID_LEN
, 0);
918 if (status
== CR_SUCCESS
) {
919 static const std::wstring BT_HID_DEVICE
= L
"HID\\{00001124";
920 static const std::wstring BT_HOGP_DEVICE
= L
"HID\\{00001812";
921 // To reduce the scope of the hack we only look for ACPI and HID\\VID
922 // prefixes in the keyboard device ids.
923 if (IMEHandler::WStringStartsWithCaseInsensitive(device_id
, L
"ACPI") ||
924 IMEHandler::WStringStartsWithCaseInsensitive(device_id
,
926 IMEHandler::WStringStartsWithCaseInsensitive(device_id
,
928 IMEHandler::WStringStartsWithCaseInsensitive(device_id
,
930 // The heuristic we are using is to check the count of keyboards and
931 // return true if the API's report one or more keyboards. Please note
932 // that this will break for non keyboard devices which expose a
934 Preferences::SetString(kOskDebugReason
,
935 L
"IKPOS: Keyboard presence confirmed.");
940 Preferences::SetString(kOskDebugReason
,
941 L
"IKPOS: Lack of keyboard confirmed.");
946 bool IMEHandler::IsInTabletMode() {
947 bool isInTabletMode
= WindowsUIUtils::GetInTabletMode();
948 if (isInTabletMode
) {
949 Preferences::SetString(kOskDebugReason
, L
"IITM: GetInTabletMode=true.");
951 Preferences::SetString(kOskDebugReason
, L
"IITM: GetInTabletMode=false.");
953 return isInTabletMode
;
956 static bool ReadEnableDesktopModeAutoInvoke(uint32_t aRoot
,
957 nsIWindowsRegKey
* aRegKey
,
960 rv
= aRegKey
->Open(aRoot
, u
"SOFTWARE\\Microsoft\\TabletTip\\1.7"_ns
,
961 nsIWindowsRegKey::ACCESS_QUERY_VALUE
);
963 Preferences::SetString(kOskDebugReason
,
964 L
"AIOSKIDM: failed opening regkey.");
967 // EnableDesktopModeAutoInvoke is an opt-in option from the Windows
968 // Settings to "Automatically show the touch keyboard in windowed apps
969 // when there's no keyboard attached to your device." If the user has
970 // opted-in to this behavior, the tablet-mode requirement is skipped.
971 rv
= aRegKey
->ReadIntValue(u
"EnableDesktopModeAutoInvoke"_ns
, &aValue
);
973 Preferences::SetString(kOskDebugReason
,
974 L
"AIOSKIDM: failed reading value of regkey.");
981 bool IMEHandler::AutoInvokeOnScreenKeyboardInDesktopMode() {
983 nsCOMPtr
<nsIWindowsRegKey
> regKey(
984 do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv
));
985 if (NS_WARN_IF(NS_FAILED(rv
))) {
986 Preferences::SetString(kOskDebugReason
,
988 L
"nsIWindowsRegKey not available");
993 if (!ReadEnableDesktopModeAutoInvoke(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER
,
995 !ReadEnableDesktopModeAutoInvoke(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE
,
1000 Preferences::SetString(kOskDebugReason
, L
"AIOSKIDM: regkey value=true.");
1002 Preferences::SetString(kOskDebugReason
, L
"AIOSKIDM: regkey value=false.");
1007 // Based on DisplayVirtualKeyboard() in Chromium's base/win/win_util.cc.
1009 void IMEHandler::ShowOnScreenKeyboard(nsWindow
* aWindow
) {
1010 #ifdef NIGHTLY_BUILD
1011 if (FxRWindowManager::GetInstance()->IsFxRWindow(sFocusedWindow
)) {
1012 OSKVRManager::ShowOnScreenKeyboard();
1015 #endif // NIGHTLY_BUILD
1017 if (IsWin10AnniversaryUpdateOrLater()) {
1018 OSKInputPaneManager::ShowOnScreenKeyboard(aWindow
->GetWindowHandle());
1022 OSKTabTipManager::ShowOnScreenKeyboard();
1025 // Based on DismissVirtualKeyboard() in Chromium's base/win/win_util.cc.
1027 void IMEHandler::DismissOnScreenKeyboard(nsWindow
* aWindow
) {
1028 // Dismiss the virtual keyboard if it's open
1029 if (IsWin10AnniversaryUpdateOrLater()) {
1030 OSKInputPaneManager::DismissOnScreenKeyboard(aWindow
->GetWindowHandle());
1034 OSKTabTipManager::DismissOnScreenKeyboard();
1037 bool IMEHandler::MaybeCreateNativeCaret(nsWindow
* aWindow
) {
1038 MOZ_ASSERT(aWindow
);
1040 if (IsA11yHandlingNativeCaret()) {
1044 if (!sHasNativeCaretBeenRequested
) {
1045 // If we have not received WM_GETOBJECT for OBJID_CARET, there may be new
1046 // application which requires our caret information. For kicking its
1047 // window event proc, we should fire a window event here.
1048 // (If there is such application, sHasNativeCaretBeenRequested will be set
1050 // FYI: If we create native caret and move its position, native caret
1051 // causes EVENT_OBJECT_LOCATIONCHANGE event with OBJID_CARET and
1053 ::NotifyWinEvent(EVENT_OBJECT_LOCATIONCHANGE
, aWindow
->GetWindowHandle(),
1054 OBJID_CARET
, OBJID_CLIENT
);
1058 MaybeDestroyNativeCaret();
1060 // If focused content is not text editable, we don't support caret
1061 // caret information without a11y module.
1062 if (!aWindow
->GetInputContext().mIMEState
.IsEditable()) {
1066 WidgetQueryContentEvent
queryCaretRectEvent(true, eQueryCaretRect
, aWindow
);
1067 aWindow
->InitEvent(queryCaretRectEvent
);
1069 WidgetQueryContentEvent::Options options
;
1070 options
.mRelativeToInsertionPoint
= true;
1071 queryCaretRectEvent
.InitForQueryCaretRect(0, options
);
1073 aWindow
->DispatchWindowEvent(queryCaretRectEvent
);
1074 if (NS_WARN_IF(queryCaretRectEvent
.Failed())) {
1078 return CreateNativeCaret(aWindow
, queryCaretRectEvent
.mReply
->mRect
);
1081 bool IMEHandler::CreateNativeCaret(nsWindow
* aWindow
,
1082 const LayoutDeviceIntRect
& aCaretRect
) {
1083 MOZ_ASSERT(aWindow
);
1085 MOZ_ASSERT(!IsA11yHandlingNativeCaret());
1087 sNativeCaretIsCreated
=
1088 ::CreateCaret(aWindow
->GetWindowHandle(), nullptr, aCaretRect
.Width(),
1089 aCaretRect
.Height());
1090 if (!sNativeCaretIsCreated
) {
1093 nsWindow
* toplevelWindow
= aWindow
->GetTopLevelWindow(false);
1094 if (NS_WARN_IF(!toplevelWindow
)) {
1095 MaybeDestroyNativeCaret();
1099 LayoutDeviceIntPoint
caretPosition(aCaretRect
.TopLeft());
1100 if (toplevelWindow
!= aWindow
) {
1101 caretPosition
+= toplevelWindow
->WidgetToScreenOffset();
1102 caretPosition
-= aWindow
->WidgetToScreenOffset();
1105 ::SetCaretPos(caretPosition
.x
, caretPosition
.y
);
1109 void IMEHandler::MaybeDestroyNativeCaret() {
1110 if (!sNativeCaretIsCreated
) {
1114 sNativeCaretIsCreated
= false;
1117 } // namespace widget
1118 } // namespace mozilla