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/TextEvents.h"
11 #include "mozilla/WindowsVersion.h"
12 #include "nsWindowDefs.h"
13 #include "WinTextEventDispatcherListener.h"
14
15 #include "TSFTextStore.h"
16
17 #include "OSKInputPaneManager.h"
18 #include "OSKTabTipManager.h"
19 #include "OSKVRManager.h"
20 #include "nsLookAndFeel.h"
21 #include "nsWindow.h"
22 #include "WinUtils.h"
23 #include "nsIWindowsRegKey.h"
24 #include "nsIWindowsUIUtils.h"
25
26 #ifdef ACCESSIBILITY
27 # include "nsAccessibilityService.h"
28 #endif // #ifdef ACCESSIBILITY
29
30 #include "shellapi.h"
31 #include "shlobj.h"
32 #include "powrprof.h"
33 #include "setupapi.h"
34 #include "cfgmgr32.h"
35
36 #include "FxRWindowManager.h"
37 #include "moz_external_vr.h"
38
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";
43
44 namespace mozilla {
45 namespace widget {
46
47 /******************************************************************************
48 * IMEHandler
49 ******************************************************************************/
50
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;
58
59 bool IMEHandler::sIsInTSFMode = false;
60 bool IMEHandler::sIsIMMEnabled = true;
61 bool IMEHandler::sAssociateIMCOnlyWhenIMM_IMEActive = false;
62 decltype(SetInputScopes)* IMEHandler::sSetInputScopes = nullptr;
63
64 static POWER_PLATFORM_ROLE sPowerPlatformRole = PlatformRoleUnspecified;
65 static bool sDeterminedPowerPlatformRole = false;
66
67 // static
Initialize()68 void IMEHandler::Initialize() {
69 TSFTextStore::Initialize();
70 sIsInTSFMode = TSFTextStore::IsInTSFMode();
71 sIsIMMEnabled =
72 !sIsInTSFMode || Preferences::GetBool("intl.tsf.support_imm", true);
73 sAssociateIMCOnlyWhenIMM_IMEActive =
74 sIsIMMEnabled &&
75 Preferences::GetBool("intl.tsf.associate_imc_only_when_imm_ime_is_active",
76 false);
77 if (!sIsInTSFMode) {
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",
83 &module)) {
84 sSetInputScopes = reinterpret_cast<decltype(SetInputScopes)*>(
85 GetProcAddress(module, "SetInputScopes"));
86 }
87 }
88
89 IMMHandler::Initialize();
90
91 sForceDisableCurrentIMM_IME = IMMHandler::IsActiveIMEInBlockList();
92 }
93
94 // static
Terminate()95 void IMEHandler::Terminate() {
96 if (sIsInTSFMode) {
97 TSFTextStore::Terminate();
98 sIsInTSFMode = false;
99 }
100
101 IMMHandler::Terminate();
102 WinTextEventDispatcherListener::Shutdown();
103 }
104
105 // static
GetNativeData(nsWindow * aWindow,uint32_t aDataType)106 void* IMEHandler::GetNativeData(nsWindow* aWindow, uint32_t aDataType) {
107 if (aDataType == NS_RAW_NATIVE_IME_CONTEXT) {
108 if (IsTSFAvailable()) {
109 return TSFTextStore::GetThreadManager();
110 }
111 IMEContext context(aWindow);
112 if (context.IsValid()) {
113 return context.get();
114 }
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();
120 }
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.
125 return aWindow;
126 }
127
128 void* result = TSFTextStore::GetNativeData(aDataType);
129 if (!result || !(*(static_cast<void**>(result)))) {
130 return nullptr;
131 }
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.
136 sIsInTSFMode = true;
137 return result;
138 }
139
140 // static
ProcessRawKeyMessage(const MSG & aMsg)141 bool IMEHandler::ProcessRawKeyMessage(const MSG& aMsg) {
142 if (IsTSFAvailable()) {
143 return TSFTextStore::ProcessRawKeyMessage(aMsg);
144 }
145 return false; // noting to do in IMM mode.
146 }
147
148 // static
ProcessMessage(nsWindow * aWindow,UINT aMessage,WPARAM & aWParam,LPARAM & aLParam,MSGResult & aResult)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);
173 }
174
175 if (IsTSFAvailable()) {
176 TSFTextStore::ProcessMessage(aWindow, aMessage, aWParam, aLParam, aResult);
177 if (aResult.mConsumed) {
178 return true;
179 }
180 // If we don't support IMM in TSF mode, we don't use IMMHandler.
181 if (!sIsIMMEnabled) {
182 return false;
183 }
184 // IME isn't implemented with IMM, IMMHandler shouldn't handle any
185 // messages.
186 if (!IsIMMActive()) {
187 return false;
188 }
189 }
190
191 bool keepGoing =
192 IMMHandler::ProcessMessage(aWindow, aMessage, aWParam, aLParam, aResult);
193
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
196 // and crash.
197 if (aMessage == WM_INPUTLANGCHANGE) {
198 bool disableIME = IMMHandler::IsActiveIMEInBlockList();
199 if (disableIME != sForceDisableCurrentIMM_IME) {
200 bool enable =
201 !disableIME && WinUtils::IsIMEEnabled(aWindow->InputContextRef());
202 AssociateIMEContext(aWindow, enable);
203 sForceDisableCurrentIMM_IME = disableIME;
204 }
205 }
206
207 return keepGoing;
208 }
209
210 // static
IsA11yHandlingNativeCaret()211 bool IMEHandler::IsA11yHandlingNativeCaret() {
212 #ifndef ACCESSIBILITY
213 return false;
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
218 }
219
220 // static
IsIMMActive()221 bool IMEHandler::IsIMMActive() { return TSFTextStore::IsIMM_IMEActive(); }
222
223 // static
IsComposing()224 bool IMEHandler::IsComposing() {
225 if (IsTSFAvailable()) {
226 return TSFTextStore::IsComposing() || IMMHandler::IsComposing();
227 }
228
229 return IMMHandler::IsComposing();
230 }
231
232 // static
IsComposingOn(nsWindow * aWindow)233 bool IMEHandler::IsComposingOn(nsWindow* aWindow) {
234 if (IsTSFAvailable()) {
235 return TSFTextStore::IsComposingOn(aWindow) ||
236 IMMHandler::IsComposingOn(aWindow);
237 }
238
239 return IMMHandler::IsComposingOn(aWindow);
240 }
241
242 // static
NotifyIME(nsWindow * aWindow,const IMENotification & aIMENotification)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();
253 if (isIMMActive) {
254 IMMHandler::OnUpdateComposition(aWindow);
255 }
256 IMMHandler::OnSelectionChange(aWindow, aIMENotification, isIMMActive);
257 return rv;
258 }
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.
263 if (IsIMMActive()) {
264 IMMHandler::OnUpdateComposition(aWindow);
265 } else {
266 TSFTextStore::OnUpdateComposition();
267 }
268 return NS_OK;
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());
279 return rv;
280 }
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.
289 if (IsIMMActive()) {
290 return IMMHandler::OnMouseButtonEvent(aWindow, aIMENotification);
291 }
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);
298 }
299 return NS_OK;
300 case REQUEST_TO_CANCEL_COMPOSITION:
301 if (TSFTextStore::IsComposingOn(aWindow)) {
302 TSFTextStore::CommitComposition(true);
303 } else if (IsIMMActive()) {
304 IMMHandler::CancelComposition(aWindow);
305 }
306 return NS_OK;
307 case NOTIFY_IME_OF_POSITION_CHANGE:
308 return TSFTextStore::OnLayoutChange();
309 default:
310 return NS_ERROR_NOT_IMPLEMENTED;
311 }
312 }
313
314 switch (aIMENotification.mMessage) {
315 case REQUEST_TO_COMMIT_COMPOSITION:
316 IMMHandler::CommitComposition(aWindow);
317 return NS_OK;
318 case REQUEST_TO_CANCEL_COMPOSITION:
319 IMMHandler::CancelComposition(aWindow);
320 return NS_OK;
321 case NOTIFY_IME_OF_POSITION_CHANGE:
322 case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
323 IMMHandler::OnUpdateComposition(aWindow);
324 return NS_OK;
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);
331 return NS_OK;
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);
340 return NS_OK;
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
346 // the blur.
347 if (TSFTextStore::ThinksHavingFocus()) {
348 return TSFTextStore::OnFocusChange(false, aWindow,
349 aWindow->GetInputContext());
350 }
351 return NS_OK;
352 default:
353 return NS_ERROR_NOT_IMPLEMENTED;
354 }
355 }
356
357 // static
GetIMENotificationRequests()358 IMENotificationRequests IMEHandler::GetIMENotificationRequests() {
359 if (IsTSFAvailable()) {
360 if (!sIsIMMEnabled) {
361 return TSFTextStore::GetIMENotificationRequests();
362 }
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();
370 }
371
372 return IMMHandler::GetIMENotificationRequests();
373 }
374
375 // static
376 TextEventDispatcherListener*
GetNativeTextEventDispatcherListener()377 IMEHandler::GetNativeTextEventDispatcherListener() {
378 return WinTextEventDispatcherListener::GetInstance();
379 }
380
381 // static
GetOpenState(nsWindow * aWindow)382 bool IMEHandler::GetOpenState(nsWindow* aWindow) {
383 if (IsTSFAvailable() && !IsIMMActive()) {
384 return TSFTextStore::GetIMEOpenState();
385 }
386
387 IMEContext context(aWindow);
388 return context.GetOpenState();
389 }
390
391 // static
OnDestroyWindow(nsWindow * aWindow)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 "
400 "process "
401 "if IME focus isn't cleared before destroying the widget");
402 NotifyIME(aWindow, IMENotification(NOTIFY_IME_OF_BLUR));
403 }
404
405 // We need to do nothing here for TSF. Just restore the default context
406 // if it's been disassociated.
407 if (!sIsInTSFMode) {
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);
411 }
412 AssociateIMEContext(aWindow, true);
413 }
414
415 // static
NeedsToAssociateIMC()416 bool IMEHandler::NeedsToAssociateIMC() {
417 return !sForceDisableCurrentIMM_IME &&
418 (!sAssociateIMCOnlyWhenIMM_IMEActive || !IsIMMActive());
419 }
420
421 // static
SetInputContext(nsWindow * aWindow,InputContext & aInputContext,const InputContextAction & aAction)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));
427
428 if (aInputContext.mHTMLInputInputmode.EqualsLiteral("none")) {
429 IMEHandler::MaybeDismissOnScreenKeyboard(aWindow, Sync::Yes);
430 } else if (aAction.UserMightRequestOpenVKB()) {
431 IMEHandler::MaybeShowOnScreenKeyboard(aWindow, aInputContext);
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 // Note that even while a plugin has focus, we need to notify TSF of that.
441 if (sIsInTSFMode) {
442 TSFTextStore::SetInputContext(aWindow, aInputContext, aAction);
443 if (IsTSFAvailable()) {
444 if (sIsIMMEnabled) {
445 // Associate IMC with aWindow only when it's necessary.
446 AssociateIMEContext(aWindow, enable && NeedsToAssociateIMC());
447 }
448 if (adjustOpenState) {
449 TSFTextStore::SetIMEOpenState(open);
450 }
451 return;
452 }
453 } else {
454 // Set at least InputScope even when TextStore is not available.
455 SetInputScopeForIMM32(aWindow, aInputContext.mHTMLInputType,
456 aInputContext.mHTMLInputInputmode,
457 aInputContext.mInPrivateBrowsing);
458 }
459
460 AssociateIMEContext(aWindow, enable);
461
462 IMEContext context(aWindow);
463 if (adjustOpenState) {
464 context.SetOpenState(open);
465 }
466 }
467
468 // static
AssociateIMEContext(nsWindowBase * aWindowBase,bool aEnable)469 void IMEHandler::AssociateIMEContext(nsWindowBase* aWindowBase, bool aEnable) {
470 IMEContext context(aWindowBase);
471 if (aEnable) {
472 context.AssociateDefaultContext();
473 return;
474 }
475 // Don't disassociate the context after the window is destroyed.
476 if (aWindowBase->Destroyed()) {
477 return;
478 }
479 context.Disassociate();
480 }
481
482 // static
InitInputContext(nsWindow * aWindow,InputContext & aInputContext)483 void IMEHandler::InitInputContext(nsWindow* aWindow,
484 InputContext& aInputContext) {
485 MOZ_ASSERT(aWindow);
486 MOZ_ASSERT(aWindow->GetWindowHandle(),
487 "IMEHandler::SetInputContext() requires non-nullptr HWND");
488
489 static bool sInitialized = false;
490 if (!sInitialized) {
491 sInitialized = true;
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.
498 Initialize();
499 }
500
501 // For a11y, the default enabled state should be 'enabled'.
502 aInputContext.mIMEState.mEnabled = IMEEnabled::Enabled;
503
504 if (sIsInTSFMode) {
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);
512 }
513 return;
514 }
515
516 #ifdef DEBUG
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
521 }
522
523 #ifdef DEBUG
524 // static
CurrentKeyboardLayoutHasIME()525 bool IMEHandler::CurrentKeyboardLayoutHasIME() {
526 if (sIsInTSFMode) {
527 return TSFTextStore::CurrentKeyboardLayoutHasIME();
528 }
529
530 return IMMHandler::IsIMEAvailable();
531 }
532 #endif // #ifdef DEBUG
533
534 // static
OnKeyboardLayoutChanged()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.
540
541 if (!sIsIMMEnabled || !IsTSFAvailable()) {
542 return;
543 }
544
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) {
549 return;
550 }
551
552 // If there is no TSFTextStore which has focus, i.e., no editor has focus,
553 // nothing to do here.
554 nsWindowBase* windowBase = TSFTextStore::GetEnabledWindowBase();
555 if (!windowBase) {
556 return;
557 }
558
559 // If IME isn't available, nothing to do here.
560 InputContext inputContext = windowBase->GetInputContext();
561 if (!WinUtils::IsIMEEnabled(inputContext)) {
562 return;
563 }
564
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());
569 }
570
571 // static
SetInputScopeForIMM32(nsWindow * aWindow,const nsAString & aHTMLInputType,const nsAString & aHTMLInputInputmode,bool aInPrivateBrowsing)572 void IMEHandler::SetInputScopeForIMM32(nsWindow* aWindow,
573 const nsAString& aHTMLInputType,
574 const nsAString& aHTMLInputInputmode,
575 bool aInPrivateBrowsing) {
576 if (sIsInTSFMode || !sSetInputScopes || aWindow->Destroyed()) {
577 return;
578 }
579 AutoTArray<InputScope, 3> scopes;
580
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);
585
586 if (aInPrivateBrowsing) {
587 scopes.AppendElement(IS_PRIVATE);
588 }
589
590 if (scopes.IsEmpty()) {
591 // At least, 1 item is necessary.
592 scopes.AppendElement(IS_DEFAULT);
593 }
594
595 sSetInputScopes(aWindow->GetWindowHandle(), scopes.Elements(),
596 scopes.Length(), nullptr, 0, nullptr, nullptr);
597 }
598
599 // static
AppendInputScopeFromInputmode(const nsAString & aInputmode,nsTArray<InputScope> & aScopes)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()) {
620 return;
621 }
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);
625 }
626 return;
627 }
628
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);
633 }
634 return;
635 }
636 if (aInputmode.EqualsLiteral("email")) {
637 if (!aScopes.Contains(IS_EMAIL_SMTPEMAILADDRESS)) {
638 aScopes.AppendElement(IS_EMAIL_SMTPEMAILADDRESS);
639 }
640 return;
641 }
642 if (aInputmode.EqualsLiteral("tel")) {
643 if (!aScopes.Contains(IS_TELEPHONE_FULLTELEPHONENUMBER)) {
644 aScopes.AppendElement(IS_TELEPHONE_FULLTELEPHONENUMBER);
645 }
646 if (!aScopes.Contains(IS_TELEPHONE_LOCALNUMBER)) {
647 aScopes.AppendElement(IS_TELEPHONE_LOCALNUMBER);
648 }
649 return;
650 }
651 if (aInputmode.EqualsLiteral("numeric")) {
652 if (!aScopes.Contains(IS_DIGITS)) {
653 aScopes.AppendElement(IS_DIGITS);
654 }
655 return;
656 }
657 if (aInputmode.EqualsLiteral("decimal")) {
658 if (!aScopes.Contains(IS_NUMBER)) {
659 aScopes.AppendElement(IS_NUMBER);
660 }
661 return;
662 }
663 if (aInputmode.EqualsLiteral("search")) {
664 if (!aScopes.Contains(IS_SEARCH)) {
665 aScopes.AppendElement(IS_SEARCH);
666 }
667 return;
668 }
669 }
670
671 // static
AppendInputScopeFromType(const nsAString & aHTMLInputType,nsTArray<InputScope> & aScopes)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);
677 return;
678 }
679 if (aHTMLInputType.EqualsLiteral("search")) {
680 aScopes.AppendElement(IS_SEARCH);
681 return;
682 }
683 if (aHTMLInputType.EqualsLiteral("email")) {
684 aScopes.AppendElement(IS_EMAIL_SMTPEMAILADDRESS);
685 return;
686 }
687 if (aHTMLInputType.EqualsLiteral("password")) {
688 aScopes.AppendElement(IS_PASSWORD);
689 return;
690 }
691 if (aHTMLInputType.EqualsLiteral("datetime") ||
692 aHTMLInputType.EqualsLiteral("datetime-local")) {
693 aScopes.AppendElement(IS_DATE_FULLDATE);
694 aScopes.AppendElement(IS_TIME_FULLTIME);
695 return;
696 }
697 if (aHTMLInputType.EqualsLiteral("date") ||
698 aHTMLInputType.EqualsLiteral("month") ||
699 aHTMLInputType.EqualsLiteral("week")) {
700 aScopes.AppendElement(IS_DATE_FULLDATE);
701 return;
702 }
703 if (aHTMLInputType.EqualsLiteral("time")) {
704 aScopes.AppendElement(IS_TIME_FULLTIME);
705 return;
706 }
707 if (aHTMLInputType.EqualsLiteral("tel")) {
708 aScopes.AppendElement(IS_TELEPHONE_FULLTELEPHONENUMBER);
709 aScopes.AppendElement(IS_TELEPHONE_LOCALNUMBER);
710 return;
711 }
712 if (aHTMLInputType.EqualsLiteral("number")) {
713 aScopes.AppendElement(IS_NUMBER);
714 return;
715 }
716 }
717
718 // static
IsOnScreenKeyboardSupported()719 bool IMEHandler::IsOnScreenKeyboardSupported() {
720 #ifdef NIGHTLY_BUILD
721 if (FxRWindowManager::GetInstance()->IsFxRWindow(sFocusedWindow)) {
722 return true;
723 }
724 #endif // NIGHTLY_BUILD
725 if (!IsWin8OrLater() || !Preferences::GetBool(kOskEnabled, true) ||
726 !IMEHandler::NeedOnScreenKeyboard()) {
727 return false;
728 }
729
730 // On Windows 10 we require tablet mode, unless the user has set the relevant
731 // Windows setting to enable the on-screen keyboard in desktop mode.
732 // We might be disabled specifically on Win8(.1), so we check that afterwards.
733 if (IsWin10OrLater()) {
734 if (!IsInTabletMode() && !AutoInvokeOnScreenKeyboardInDesktopMode()) {
735 return false;
736 }
737 } else if (Preferences::GetBool(kOskRequireWin10, true)) {
738 return false;
739 }
740
741 return true;
742 }
743
744 // static
MaybeShowOnScreenKeyboard(nsWindow * aWindow,const InputContext & aInputContext)745 void IMEHandler::MaybeShowOnScreenKeyboard(nsWindow* aWindow,
746 const InputContext& aInputContext) {
747 if (aInputContext.mHTMLInputInputmode.EqualsLiteral("none")) {
748 return;
749 }
750
751 if (!IsOnScreenKeyboardSupported()) {
752 return;
753 }
754
755 IMEHandler::ShowOnScreenKeyboard(aWindow);
756 }
757
758 // static
MaybeDismissOnScreenKeyboard(nsWindow * aWindow,Sync aSync)759 void IMEHandler::MaybeDismissOnScreenKeyboard(nsWindow* aWindow, Sync aSync) {
760 #ifdef NIGHTLY_BUILD
761 if (FxRWindowManager::GetInstance()->IsFxRWindow(aWindow)) {
762 OSKVRManager::DismissOnScreenKeyboard();
763 }
764 #endif // NIGHTLY_BUILD
765 if (!IsWin8OrLater()) {
766 return;
767 }
768
769 if (aSync == Sync::Yes) {
770 DismissOnScreenKeyboard(aWindow);
771 return;
772 }
773
774 RefPtr<nsWindow> window(aWindow);
775 NS_DispatchToCurrentThreadQueue(
776 NS_NewRunnableFunction("IMEHandler::MaybeDismissOnScreenKeyboard",
777 [window]() {
778 if (window->Destroyed()) {
779 return;
780 }
781 if (!sFocusedWindow) {
782 DismissOnScreenKeyboard(window);
783 }
784 }),
785 EventQueuePriority::Idle);
786 }
787
788 // static
WStringStartsWithCaseInsensitive(const std::wstring & aHaystack,const std::wstring & aNeedle)789 bool IMEHandler::WStringStartsWithCaseInsensitive(const std::wstring& aHaystack,
790 const std::wstring& aNeedle) {
791 std::wstring lowerCaseHaystack(aHaystack);
792 std::wstring lowerCaseNeedle(aNeedle);
793 std::transform(lowerCaseHaystack.begin(), lowerCaseHaystack.end(),
794 lowerCaseHaystack.begin(), ::tolower);
795 std::transform(lowerCaseNeedle.begin(), lowerCaseNeedle.end(),
796 lowerCaseNeedle.begin(), ::tolower);
797 return wcsstr(lowerCaseHaystack.c_str(), lowerCaseNeedle.c_str()) ==
798 lowerCaseHaystack.c_str();
799 }
800
801 // Returns false if a physical keyboard is detected on Windows 8 and up,
802 // or there is some other reason why an onscreen keyboard is not necessary.
803 // Returns true if no keyboard is found and this device looks like it needs
804 // an on-screen keyboard for text input.
805 // static
NeedOnScreenKeyboard()806 bool IMEHandler::NeedOnScreenKeyboard() {
807 // This function is only supported for Windows 8 and up.
808 if (!IsWin8OrLater()) {
809 Preferences::SetString(kOskDebugReason, L"IKPOS: Requires Win8+.");
810 return false;
811 }
812
813 if (!Preferences::GetBool(kOskDetectPhysicalKeyboard, true)) {
814 Preferences::SetString(kOskDebugReason, L"IKPOS: Detection disabled.");
815 return true;
816 }
817
818 // If the last focus cause was not user-initiated (ie a result of code
819 // setting focus to an element) then don't auto-show a keyboard. This
820 // avoids cases where the keyboard would pop up "just" because e.g. a
821 // web page chooses to focus a search field on the page, even when that
822 // really isn't what the user is trying to do at that moment.
823 if (!InputContextAction::IsHandlingUserInput(sLastContextActionCause)) {
824 return false;
825 }
826
827 // This function should be only invoked for machines with touch screens.
828 if ((::GetSystemMetrics(SM_DIGITIZER) & NID_INTEGRATED_TOUCH) !=
829 NID_INTEGRATED_TOUCH) {
830 Preferences::SetString(kOskDebugReason, L"IKPOS: Touch screen not found.");
831 return false;
832 }
833
834 // If the device is docked, the user is treating the device as a PC.
835 if (::GetSystemMetrics(SM_SYSTEMDOCKED) != 0) {
836 Preferences::SetString(kOskDebugReason, L"IKPOS: System docked.");
837 return false;
838 }
839
840 // To determine whether a keyboard is present on the device, we do the
841 // following:-
842 // 1. If the platform role is that of a mobile or slate device, check the
843 // system metric SM_CONVERTIBLESLATEMODE to see if it is being used
844 // in slate mode. If it is, also check that the last input was a touch.
845 // If all of this is true, then we should show the on-screen keyboard.
846
847 // 2. If step 1 didn't determine we should show the keyboard, we check if
848 // this device has keyboards attached to it.
849
850 // Check if the device is being used as a laptop or a tablet. This can be
851 // checked by first checking the role of the device and then the
852 // corresponding system metric (SM_CONVERTIBLESLATEMODE). If it is being
853 // used as a tablet then we want the OSK to show up.
854 if (!sDeterminedPowerPlatformRole) {
855 sDeterminedPowerPlatformRole = true;
856 sPowerPlatformRole = WinUtils::GetPowerPlatformRole();
857 }
858
859 // If this a mobile or slate (tablet) device, check if it is in slate mode.
860 // If the last input was touch, ignore whether or not a keyboard is present.
861 if ((sPowerPlatformRole == PlatformRoleMobile ||
862 sPowerPlatformRole == PlatformRoleSlate) &&
863 ::GetSystemMetrics(SM_CONVERTIBLESLATEMODE) == 0 &&
864 sLastContextActionCause == InputContextAction::CAUSE_TOUCH) {
865 Preferences::SetString(
866 kOskDebugReason,
867 L"IKPOS: Mobile/Slate Platform role, in slate mode with touch event.");
868 return true;
869 }
870
871 return !IMEHandler::IsKeyboardPresentOnSlate();
872 }
873
874 // Uses the Setup APIs to enumerate the attached keyboards and returns true
875 // if the keyboard count is 1 or more. While this will work in most cases
876 // it won't work if there are devices which expose keyboard interfaces which
877 // are attached to the machine.
878 // Based on IsKeyboardPresentOnSlate() in Chromium's base/win/win_util.cc.
879 // static
IsKeyboardPresentOnSlate()880 bool IMEHandler::IsKeyboardPresentOnSlate() {
881 const GUID KEYBOARD_CLASS_GUID = {
882 0x4D36E96B,
883 0xE325,
884 0x11CE,
885 {0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18}};
886
887 // Query for all the keyboard devices.
888 HDEVINFO device_info = ::SetupDiGetClassDevs(&KEYBOARD_CLASS_GUID, nullptr,
889 nullptr, DIGCF_PRESENT);
890 if (device_info == INVALID_HANDLE_VALUE) {
891 Preferences::SetString(kOskDebugReason, L"IKPOS: No keyboard info.");
892 return false;
893 }
894
895 // Enumerate all keyboards and look for ACPI\PNP and HID\VID devices. If
896 // the count is more than 1 we assume that a keyboard is present. This is
897 // under the assumption that there will always be one keyboard device.
898 for (DWORD i = 0;; ++i) {
899 SP_DEVINFO_DATA device_info_data = {0};
900 device_info_data.cbSize = sizeof(device_info_data);
901 if (!::SetupDiEnumDeviceInfo(device_info, i, &device_info_data)) {
902 break;
903 }
904
905 // Get the device ID.
906 wchar_t device_id[MAX_DEVICE_ID_LEN];
907 CONFIGRET status = ::CM_Get_Device_ID(device_info_data.DevInst, device_id,
908 MAX_DEVICE_ID_LEN, 0);
909 if (status == CR_SUCCESS) {
910 static const std::wstring BT_HID_DEVICE = L"HID\\{00001124";
911 static const std::wstring BT_HOGP_DEVICE = L"HID\\{00001812";
912 // To reduce the scope of the hack we only look for ACPI and HID\\VID
913 // prefixes in the keyboard device ids.
914 if (IMEHandler::WStringStartsWithCaseInsensitive(device_id, L"ACPI") ||
915 IMEHandler::WStringStartsWithCaseInsensitive(device_id,
916 L"HID\\VID") ||
917 IMEHandler::WStringStartsWithCaseInsensitive(device_id,
918 BT_HID_DEVICE) ||
919 IMEHandler::WStringStartsWithCaseInsensitive(device_id,
920 BT_HOGP_DEVICE)) {
921 // The heuristic we are using is to check the count of keyboards and
922 // return true if the API's report one or more keyboards. Please note
923 // that this will break for non keyboard devices which expose a
924 // keyboard PDO.
925 Preferences::SetString(kOskDebugReason,
926 L"IKPOS: Keyboard presence confirmed.");
927 return true;
928 }
929 }
930 }
931 Preferences::SetString(kOskDebugReason,
932 L"IKPOS: Lack of keyboard confirmed.");
933 return false;
934 }
935
936 // static
IsInTabletMode()937 bool IMEHandler::IsInTabletMode() {
938 nsCOMPtr<nsIWindowsUIUtils> uiUtils(
939 do_GetService("@mozilla.org/windows-ui-utils;1"));
940 if (NS_WARN_IF(!uiUtils)) {
941 Preferences::SetString(kOskDebugReason,
942 L"IITM: nsIWindowsUIUtils not available.");
943 return false;
944 }
945 bool isInTabletMode = false;
946 uiUtils->GetInTabletMode(&isInTabletMode);
947 if (isInTabletMode) {
948 Preferences::SetString(kOskDebugReason, L"IITM: GetInTabletMode=true.");
949 } else {
950 Preferences::SetString(kOskDebugReason, L"IITM: GetInTabletMode=false.");
951 }
952 return isInTabletMode;
953 }
954
ReadEnableDesktopModeAutoInvoke(uint32_t aRoot,nsIWindowsRegKey * aRegKey,uint32_t & aValue)955 static bool ReadEnableDesktopModeAutoInvoke(uint32_t aRoot,
956 nsIWindowsRegKey* aRegKey,
957 uint32_t& aValue) {
958 nsresult rv;
959 rv = aRegKey->Open(aRoot, u"SOFTWARE\\Microsoft\\TabletTip\\1.7"_ns,
960 nsIWindowsRegKey::ACCESS_QUERY_VALUE);
961 if (NS_FAILED(rv)) {
962 Preferences::SetString(kOskDebugReason,
963 L"AIOSKIDM: failed opening regkey.");
964 return false;
965 }
966 // EnableDesktopModeAutoInvoke is an opt-in option from the Windows
967 // Settings to "Automatically show the touch keyboard in windowed apps
968 // when there's no keyboard attached to your device." If the user has
969 // opted-in to this behavior, the tablet-mode requirement is skipped.
970 rv = aRegKey->ReadIntValue(u"EnableDesktopModeAutoInvoke"_ns, &aValue);
971 if (NS_FAILED(rv)) {
972 Preferences::SetString(kOskDebugReason,
973 L"AIOSKIDM: failed reading value of regkey.");
974 return false;
975 }
976 return true;
977 }
978
979 // static
AutoInvokeOnScreenKeyboardInDesktopMode()980 bool IMEHandler::AutoInvokeOnScreenKeyboardInDesktopMode() {
981 nsresult rv;
982 nsCOMPtr<nsIWindowsRegKey> regKey(
983 do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv));
984 if (NS_WARN_IF(NS_FAILED(rv))) {
985 Preferences::SetString(kOskDebugReason,
986 L"AIOSKIDM: "
987 L"nsIWindowsRegKey not available");
988 return false;
989 }
990
991 uint32_t value;
992 if (!ReadEnableDesktopModeAutoInvoke(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
993 regKey, value) &&
994 !ReadEnableDesktopModeAutoInvoke(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE,
995 regKey, value)) {
996 return false;
997 }
998 if (!!value) {
999 Preferences::SetString(kOskDebugReason, L"AIOSKIDM: regkey value=true.");
1000 } else {
1001 Preferences::SetString(kOskDebugReason, L"AIOSKIDM: regkey value=false.");
1002 }
1003 return !!value;
1004 }
1005
1006 // Based on DisplayVirtualKeyboard() in Chromium's base/win/win_util.cc.
1007 // static
ShowOnScreenKeyboard(nsWindow * aWindow)1008 void IMEHandler::ShowOnScreenKeyboard(nsWindow* aWindow) {
1009 #ifdef NIGHTLY_BUILD
1010 if (FxRWindowManager::GetInstance()->IsFxRWindow(sFocusedWindow)) {
1011 OSKVRManager::ShowOnScreenKeyboard();
1012 return;
1013 }
1014 #endif // NIGHTLY_BUILD
1015
1016 if (IsWin10AnniversaryUpdateOrLater()) {
1017 OSKInputPaneManager::ShowOnScreenKeyboard(aWindow->GetWindowHandle());
1018 return;
1019 }
1020
1021 OSKTabTipManager::ShowOnScreenKeyboard();
1022 }
1023
1024 // Based on DismissVirtualKeyboard() in Chromium's base/win/win_util.cc.
1025 // static
DismissOnScreenKeyboard(nsWindow * aWindow)1026 void IMEHandler::DismissOnScreenKeyboard(nsWindow* aWindow) {
1027 // Dismiss the virtual keyboard if it's open
1028 if (IsWin10AnniversaryUpdateOrLater()) {
1029 OSKInputPaneManager::DismissOnScreenKeyboard(aWindow->GetWindowHandle());
1030 return;
1031 }
1032
1033 OSKTabTipManager::DismissOnScreenKeyboard();
1034 }
1035
MaybeCreateNativeCaret(nsWindow * aWindow)1036 bool IMEHandler::MaybeCreateNativeCaret(nsWindow* aWindow) {
1037 MOZ_ASSERT(aWindow);
1038
1039 if (IsA11yHandlingNativeCaret()) {
1040 return false;
1041 }
1042
1043 if (!sHasNativeCaretBeenRequested) {
1044 // If we have not received WM_GETOBJECT for OBJID_CARET, there may be new
1045 // application which requires our caret information. For kicking its
1046 // window event proc, we should fire a window event here.
1047 // (If there is such application, sHasNativeCaretBeenRequested will be set
1048 // to true later.)
1049 // FYI: If we create native caret and move its position, native caret
1050 // causes EVENT_OBJECT_LOCATIONCHANGE event with OBJID_CARET and
1051 // OBJID_CLIENT.
1052 ::NotifyWinEvent(EVENT_OBJECT_LOCATIONCHANGE, aWindow->GetWindowHandle(),
1053 OBJID_CARET, OBJID_CLIENT);
1054 return false;
1055 }
1056
1057 MaybeDestroyNativeCaret();
1058
1059 // If focused content is not text editable, we don't support caret
1060 // caret information without a11y module.
1061 if (!aWindow->GetInputContext().mIMEState.IsEditable()) {
1062 return false;
1063 }
1064
1065 WidgetQueryContentEvent queryCaretRectEvent(true, eQueryCaretRect, aWindow);
1066 aWindow->InitEvent(queryCaretRectEvent);
1067
1068 WidgetQueryContentEvent::Options options;
1069 options.mRelativeToInsertionPoint = true;
1070 queryCaretRectEvent.InitForQueryCaretRect(0, options);
1071
1072 aWindow->DispatchWindowEvent(&queryCaretRectEvent);
1073 if (NS_WARN_IF(queryCaretRectEvent.Failed())) {
1074 return false;
1075 }
1076
1077 return CreateNativeCaret(aWindow, queryCaretRectEvent.mReply->mRect);
1078 }
1079
CreateNativeCaret(nsWindow * aWindow,const LayoutDeviceIntRect & aCaretRect)1080 bool IMEHandler::CreateNativeCaret(nsWindow* aWindow,
1081 const LayoutDeviceIntRect& aCaretRect) {
1082 MOZ_ASSERT(aWindow);
1083
1084 MOZ_ASSERT(!IsA11yHandlingNativeCaret());
1085
1086 sNativeCaretIsCreated =
1087 ::CreateCaret(aWindow->GetWindowHandle(), nullptr, aCaretRect.Width(),
1088 aCaretRect.Height());
1089 if (!sNativeCaretIsCreated) {
1090 return false;
1091 }
1092 nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false);
1093 if (NS_WARN_IF(!toplevelWindow)) {
1094 MaybeDestroyNativeCaret();
1095 return false;
1096 }
1097
1098 LayoutDeviceIntPoint caretPosition(aCaretRect.TopLeft());
1099 if (toplevelWindow != aWindow) {
1100 caretPosition += toplevelWindow->WidgetToScreenOffset();
1101 caretPosition -= aWindow->WidgetToScreenOffset();
1102 }
1103
1104 ::SetCaretPos(caretPosition.x, caretPosition.y);
1105 return true;
1106 }
1107
MaybeDestroyNativeCaret()1108 void IMEHandler::MaybeDestroyNativeCaret() {
1109 if (!sNativeCaretIsCreated) {
1110 return;
1111 }
1112 ::DestroyCaret();
1113 sNativeCaretIsCreated = false;
1114 }
1115
1116 } // namespace widget
1117 } // namespace mozilla
1118