1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 et tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "TextInputHandler.h"
9 #include "mozilla/Logging.h"
11 #include "mozilla/ArrayUtils.h"
12 #include "mozilla/AutoRestore.h"
13 #include "mozilla/MiscEvents.h"
14 #include "mozilla/MouseEvents.h"
15 #include "mozilla/StaticPrefs_intl.h"
16 #include "mozilla/Telemetry.h"
17 #include "mozilla/TextEventDispatcher.h"
18 #include "mozilla/TextEvents.h"
19 #include "mozilla/ToString.h"
21 #include "nsChildView.h"
22 #include "nsCocoaFeatures.h"
23 #include "nsObjCExceptions.h"
24 #include "nsBidiUtils.h"
25 #include "nsToolkit.h"
26 #include "nsCocoaUtils.h"
27 #include "WidgetUtils.h"
28 #include "nsPrintfCString.h"
30 using namespace mozilla;
31 using namespace mozilla::widget;
33 // For collecting other people's log, tell them `MOZ_LOG=IMEHandler:4,sync`
34 // rather than `MOZ_LOG=IMEHandler:5,sync` since using `5` may create too
36 // Therefore you shouldn't use `LogLevel::Verbose` for logging usual behavior.
37 mozilla::LazyLogModule gIMELog("IMEHandler");
39 // For collecting other people's log, tell them `MOZ_LOG=KeyboardHandler:4,sync`
40 // rather than `MOZ_LOG=KeyboardHandler:5,sync` since using `5` may create too
42 // Therefore you shouldn't use `LogLevel::Verbose` for logging usual behavior.
43 mozilla::LazyLogModule gKeyLog("KeyboardHandler");
45 // The behavior of `TextInputHandler` class is important both for logging
46 // keyboard handler and IME handler. Therefore, the behavior is logged when
47 // either `IMEHandler` or `KeyboardHandler` is set to `MOZ_LOG`. Therefore,
48 // you may not need to tell people `MOZ_LOG=IMEHandler:4,KeyboardHandler:4,sync`.
49 #define MOZ_LOG_KEY_OR_IME(aLogLevel, aArgs) \
50 MOZ_LOG(MOZ_LOG_TEST(gIMELog, aLogLevel) ? gIMELog : gKeyLog, aLogLevel, aArgs)
52 static const char* OnOrOff(bool aBool) { return aBool ? "ON" : "off"; }
54 static const char* TrueOrFalse(bool aBool) { return aBool ? "TRUE" : "FALSE"; }
56 static const char* GetKeyNameForNativeKeyCode(unsigned short aNativeKeyCode) {
57 switch (aNativeKeyCode) {
60 case kVK_RightCommand:
61 return "Right-Command";
75 return "Right-Option";
76 case kVK_RightControl:
77 return "Right-Control";
78 case kVK_ANSI_KeypadClear:
106 return "F13/PrintScreen";
108 return "F14/ScrollLock";
112 case kVK_ANSI_Keypad0:
114 case kVK_ANSI_Keypad1:
116 case kVK_ANSI_Keypad2:
118 case kVK_ANSI_Keypad3:
120 case kVK_ANSI_Keypad4:
122 case kVK_ANSI_Keypad5:
124 case kVK_ANSI_Keypad6:
126 case kVK_ANSI_Keypad7:
128 case kVK_ANSI_Keypad8:
130 case kVK_ANSI_Keypad9:
133 case kVK_ANSI_KeypadMultiply:
135 case kVK_ANSI_KeypadPlus:
137 case kVK_ANSI_KeypadMinus:
139 case kVK_ANSI_KeypadDecimal:
141 case kVK_ANSI_KeypadDivide:
143 case kVK_ANSI_KeypadEquals:
145 case kVK_ANSI_KeypadEnter:
146 return "NumPad-Enter";
149 case kVK_Powerbook_KeypadEnter:
150 return "NumPad-EnterOnPowerBook";
153 return "Insert/Help";
158 case kVK_PC_Backspace:
176 case kVK_PC_ContextMenu:
177 return "ContextMenu";
188 case kVK_ISO_Section:
189 return "ISO_Section";
193 case kVK_JIS_Underscore:
194 return "JIS_Underscore";
195 case kVK_JIS_KeypadComma:
196 return "JIS_KeypadComma";
279 case kVK_ANSI_RightBracket:
280 return "RightBracket";
281 case kVK_ANSI_LeftBracket:
282 return "LeftBracket";
285 case kVK_ANSI_Semicolon:
287 case kVK_ANSI_Backslash:
293 case kVK_ANSI_Period:
303 static const char* GetCharacters(const nsAString& aString) {
304 if (aString.IsEmpty()) {
307 nsAutoString escapedStr;
308 for (uint32_t i = 0; i < aString.Length(); i++) {
309 char16_t ch = aString.CharAt(i);
311 nsPrintfCString utf8str("(U+%04X)", ch);
312 escapedStr += NS_ConvertUTF8toUTF16(utf8str);
313 } else if (ch <= 0x7E) {
316 nsPrintfCString utf8str("(U+%04X)", ch);
318 escapedStr += NS_ConvertUTF8toUTF16(utf8str);
322 // the result will be freed automatically by cocoa.
323 NSString* result = nsCocoaUtils::ToNSString(escapedStr);
324 return [result UTF8String];
327 static const char* GetCharacters(const NSString* aString) {
329 nsCocoaUtils::GetStringForNSString(aString, str);
330 return GetCharacters(str);
333 static const char* GetCharacters(const CFStringRef aString) {
334 const NSString* str = reinterpret_cast<const NSString*>(aString);
335 return GetCharacters(str);
338 static const char* GetNativeKeyEventType(NSEvent* aNativeEvent) {
339 switch ([aNativeEvent type]) {
340 case NSEventTypeKeyDown:
341 return "NSEventTypeKeyDown";
342 case NSEventTypeKeyUp:
343 return "NSEventTypeKeyUp";
344 case NSEventTypeFlagsChanged:
345 return "NSEventTypeFlagsChanged";
347 return "not key event";
351 static const char* GetGeckoKeyEventType(const WidgetEvent& aEvent) {
352 switch (aEvent.mMessage) {
360 return "not key event";
364 static const char* GetWindowLevelName(NSInteger aWindowLevel) {
365 switch (aWindowLevel) {
366 case kCGBaseWindowLevelKey:
367 return "kCGBaseWindowLevelKey (NSNormalWindowLevel)";
368 case kCGMinimumWindowLevelKey:
369 return "kCGMinimumWindowLevelKey";
370 case kCGDesktopWindowLevelKey:
371 return "kCGDesktopWindowLevelKey";
372 case kCGBackstopMenuLevelKey:
373 return "kCGBackstopMenuLevelKey";
374 case kCGNormalWindowLevelKey:
375 return "kCGNormalWindowLevelKey";
376 case kCGFloatingWindowLevelKey:
377 return "kCGFloatingWindowLevelKey (NSFloatingWindowLevel)";
378 case kCGTornOffMenuWindowLevelKey:
379 return "kCGTornOffMenuWindowLevelKey (NSSubmenuWindowLevel, NSTornOffMenuWindowLevel)";
380 case kCGDockWindowLevelKey:
381 return "kCGDockWindowLevelKey (NSDockWindowLevel)";
382 case kCGMainMenuWindowLevelKey:
383 return "kCGMainMenuWindowLevelKey (NSMainMenuWindowLevel)";
384 case kCGStatusWindowLevelKey:
385 return "kCGStatusWindowLevelKey (NSStatusWindowLevel)";
386 case kCGModalPanelWindowLevelKey:
387 return "kCGModalPanelWindowLevelKey (NSModalPanelWindowLevel)";
388 case kCGPopUpMenuWindowLevelKey:
389 return "kCGPopUpMenuWindowLevelKey (NSPopUpMenuWindowLevel)";
390 case kCGDraggingWindowLevelKey:
391 return "kCGDraggingWindowLevelKey";
392 case kCGScreenSaverWindowLevelKey:
393 return "kCGScreenSaverWindowLevelKey (NSScreenSaverWindowLevel)";
394 case kCGMaximumWindowLevelKey:
395 return "kCGMaximumWindowLevelKey";
396 case kCGOverlayWindowLevelKey:
397 return "kCGOverlayWindowLevelKey";
398 case kCGHelpWindowLevelKey:
399 return "kCGHelpWindowLevelKey";
400 case kCGUtilityWindowLevelKey:
401 return "kCGUtilityWindowLevelKey";
402 case kCGDesktopIconWindowLevelKey:
403 return "kCGDesktopIconWindowLevelKey";
404 case kCGCursorWindowLevelKey:
405 return "kCGCursorWindowLevelKey";
406 case kCGNumberOfWindowLevelKeys:
407 return "kCGNumberOfWindowLevelKeys";
409 return "unknown window level";
413 static bool IsControlChar(uint32_t aCharCode) { return aCharCode < ' ' || aCharCode == 0x7F; }
415 static uint32_t gHandlerInstanceCount = 0;
417 static void EnsureToLogAllKeyboardLayoutsAndIMEs() {
418 static bool sDone = false;
421 TextInputHandler::DebugPrintAllKeyboardLayouts();
422 IMEInputHandler::DebugPrintAllIMEModes();
426 inline NSRange MakeNSRangeFrom(const Maybe<OffsetAndData<uint32_t>>& aOffsetAndData) {
427 if (aOffsetAndData.isNothing()) {
428 return NSMakeRange(NSNotFound, 0);
430 return NSMakeRange(aOffsetAndData->StartOffset(), aOffsetAndData->Length());
435 /******************************************************************************
437 * TISInputSourceWrapper implementation
439 ******************************************************************************/
441 TISInputSourceWrapper* TISInputSourceWrapper::sCurrentInputSource = nullptr;
444 TISInputSourceWrapper& TISInputSourceWrapper::CurrentInputSource() {
445 if (!sCurrentInputSource) {
446 sCurrentInputSource = new TISInputSourceWrapper();
448 if (!sCurrentInputSource->IsInitializedByCurrentInputSource()) {
449 sCurrentInputSource->InitByCurrentInputSource();
451 return *sCurrentInputSource;
455 void TISInputSourceWrapper::Shutdown() {
456 if (!sCurrentInputSource) {
459 sCurrentInputSource->Clear();
460 delete sCurrentInputSource;
461 sCurrentInputSource = nullptr;
464 bool TISInputSourceWrapper::TranslateToString(UInt32 aKeyCode, UInt32 aModifiers, UInt32 aKbType,
468 const UCKeyboardLayout* UCKey = GetUCKeyboardLayout();
470 MOZ_LOG(gKeyLog, LogLevel::Info,
471 ("%p TISInputSourceWrapper::TranslateToString, aKeyCode=0x%X, "
472 "aModifiers=0x%X, aKbType=0x%X UCKey=%p\n "
473 "Shift: %s, Ctrl: %s, Opt: %s, Cmd: %s, CapsLock: %s, NumLock: %s",
474 this, static_cast<unsigned int>(aKeyCode), static_cast<unsigned int>(aModifiers),
475 static_cast<unsigned int>(aKbType), UCKey, OnOrOff(aModifiers & shiftKey),
476 OnOrOff(aModifiers & controlKey), OnOrOff(aModifiers & optionKey),
477 OnOrOff(aModifiers & cmdKey), OnOrOff(aModifiers & alphaLock),
478 OnOrOff(aModifiers & kEventKeyModifierNumLockMask)));
480 NS_ENSURE_TRUE(UCKey, false);
482 UInt32 deadKeyState = 0;
485 OSStatus err = ::UCKeyTranslate(UCKey, aKeyCode, kUCKeyActionDown, aModifiers >> 8, aKbType,
486 kUCKeyTranslateNoDeadKeysMask, &deadKeyState, 5, &len, chars);
488 MOZ_LOG(gKeyLog, LogLevel::Info,
489 ("%p TISInputSourceWrapper::TranslateToString, err=0x%X, len=%zu", this,
490 static_cast<int>(err), len));
492 NS_ENSURE_TRUE(err == noErr, false);
496 if (!aStr.SetLength(len, fallible)) {
499 NS_ASSERTION(sizeof(char16_t) == sizeof(UniChar),
500 "size of char16_t and size of UniChar are different");
501 memcpy(aStr.BeginWriting(), chars, len * sizeof(char16_t));
503 MOZ_LOG(gKeyLog, LogLevel::Info,
504 ("%p TISInputSourceWrapper::TranslateToString, aStr=\"%s\"", this,
505 NS_ConvertUTF16toUTF8(aStr).get()));
510 uint32_t TISInputSourceWrapper::TranslateToChar(UInt32 aKeyCode, UInt32 aModifiers,
513 if (!TranslateToString(aKeyCode, aModifiers, aKbType, str) || str.Length() != 1) {
516 return static_cast<uint32_t>(str.CharAt(0));
519 bool TISInputSourceWrapper::IsDeadKey(NSEvent* aNativeKeyEvent) {
520 if ([[aNativeKeyEvent characters] length]) {
524 // Assmue that if control key or command key is pressed, it's not a dead key.
525 NSUInteger cocoaState = [aNativeKeyEvent modifierFlags];
526 if (cocoaState & (NSEventModifierFlagControl | NSEventModifierFlagCommand)) {
530 UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
531 switch (nativeKeyCode) {
570 case kVK_ANSI_RightBracket:
571 case kVK_ANSI_LeftBracket:
573 case kVK_ANSI_Semicolon:
574 case kVK_ANSI_Backslash:
577 case kVK_ANSI_Period:
580 case kVK_JIS_Underscore:
583 // Let's assume that dead key can be only a printable key in standard
588 // If TranslateToChar() returns non-zero value, that means that
589 // the key may input a character with different dead key state.
590 UInt32 kbType = GetKbdType();
591 UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState);
592 return IsDeadKey(nativeKeyCode, carbonState, kbType);
595 bool TISInputSourceWrapper::IsDeadKey(UInt32 aKeyCode, UInt32 aModifiers, UInt32 aKbType) {
596 const UCKeyboardLayout* UCKey = GetUCKeyboardLayout();
598 MOZ_LOG(gKeyLog, LogLevel::Info,
599 ("%p TISInputSourceWrapper::IsDeadKey, aKeyCode=0x%X, "
600 "aModifiers=0x%X, aKbType=0x%X UCKey=%p\n "
601 "Shift: %s, Ctrl: %s, Opt: %s, Cmd: %s, CapsLock: %s, NumLock: %s",
602 this, static_cast<unsigned int>(aKeyCode), static_cast<unsigned int>(aModifiers),
603 static_cast<unsigned int>(aKbType), UCKey, OnOrOff(aModifiers & shiftKey),
604 OnOrOff(aModifiers & controlKey), OnOrOff(aModifiers & optionKey),
605 OnOrOff(aModifiers & cmdKey), OnOrOff(aModifiers & alphaLock),
606 OnOrOff(aModifiers & kEventKeyModifierNumLockMask)));
608 if (NS_WARN_IF(!UCKey)) {
612 UInt32 deadKeyState = 0;
615 OSStatus err = ::UCKeyTranslate(UCKey, aKeyCode, kUCKeyActionDown, aModifiers >> 8, aKbType, 0,
616 &deadKeyState, 5, &len, chars);
618 MOZ_LOG(gKeyLog, LogLevel::Info,
619 ("%p TISInputSourceWrapper::IsDeadKey, err=0x%X, "
620 "len=%zu, deadKeyState=%u",
621 this, static_cast<int>(err), len, deadKeyState));
623 if (NS_WARN_IF(err != noErr)) {
627 return deadKeyState != 0;
630 void TISInputSourceWrapper::InitByInputSourceID(const char* aID) {
634 CFStringRef idstr = ::CFStringCreateWithCString(kCFAllocatorDefault, aID, kCFStringEncodingASCII);
635 InitByInputSourceID(idstr);
639 void TISInputSourceWrapper::InitByInputSourceID(const nsString& aID) {
641 if (aID.IsEmpty()) return;
642 CFStringRef idstr = ::CFStringCreateWithCharacters(
643 kCFAllocatorDefault, reinterpret_cast<const UniChar*>(aID.get()), aID.Length());
644 InitByInputSourceID(idstr);
648 void TISInputSourceWrapper::InitByInputSourceID(const CFStringRef aID) {
651 const void* keys[] = {kTISPropertyInputSourceID};
652 const void* values[] = {aID};
653 CFDictionaryRef filter = ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
654 NS_ASSERTION(filter, "failed to create the filter");
655 mInputSourceList = ::TISCreateInputSourceList(filter, true);
657 if (::CFArrayGetCount(mInputSourceList) > 0) {
658 mInputSource = static_cast<TISInputSourceRef>(
659 const_cast<void*>(::CFArrayGetValueAtIndex(mInputSourceList, 0)));
660 if (IsKeyboardLayout()) {
661 mKeyboardLayout = mInputSource;
666 void TISInputSourceWrapper::InitByLayoutID(SInt32 aLayoutID, bool aOverrideKeyboard) {
667 // NOTE: Doument new layout IDs in TextInputHandler.h when you add ones.
670 InitByInputSourceID("com.apple.keylayout.US");
673 InitByInputSourceID("com.apple.keylayout.Greek");
676 InitByInputSourceID("com.apple.keylayout.German");
679 InitByInputSourceID("com.apple.keylayout.Swedish-Pro");
682 InitByInputSourceID("com.apple.keylayout.DVORAK-QWERTYCMD");
685 InitByInputSourceID("com.apple.keylayout.Thai");
688 InitByInputSourceID("com.apple.keylayout.Arabic");
691 InitByInputSourceID("com.apple.keylayout.ArabicPC");
694 InitByInputSourceID("com.apple.keylayout.French");
697 InitByInputSourceID("com.apple.keylayout.Hebrew");
700 InitByInputSourceID("com.apple.keylayout.Lithuanian");
703 InitByInputSourceID("com.apple.keylayout.Norwegian");
706 InitByInputSourceID("com.apple.keylayout.Spanish");
712 mOverrideKeyboard = aOverrideKeyboard;
715 void TISInputSourceWrapper::InitByCurrentInputSource() {
717 mInputSource = ::TISCopyCurrentKeyboardInputSource();
718 mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride();
719 if (!mKeyboardLayout) {
720 mKeyboardLayout = ::TISCopyCurrentKeyboardLayoutInputSource();
722 // If this causes composition, the current keyboard layout may input non-ASCII
723 // characters such as Japanese Kana characters or Hangul characters.
724 // However, we need to set ASCII characters to DOM key events for consistency
725 // with other platforms.
726 if (IsOpenedIMEMode()) {
727 TISInputSourceWrapper tis(mKeyboardLayout);
728 if (!tis.IsASCIICapable()) {
729 mKeyboardLayout = ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
734 void TISInputSourceWrapper::InitByCurrentKeyboardLayout() {
736 mInputSource = ::TISCopyCurrentKeyboardLayoutInputSource();
737 mKeyboardLayout = mInputSource;
740 void TISInputSourceWrapper::InitByCurrentASCIICapableInputSource() {
742 mInputSource = ::TISCopyCurrentASCIICapableKeyboardInputSource();
743 mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride();
744 if (mKeyboardLayout) {
745 TISInputSourceWrapper tis(mKeyboardLayout);
746 if (!tis.IsASCIICapable()) {
747 mKeyboardLayout = nullptr;
750 if (!mKeyboardLayout) {
751 mKeyboardLayout = ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
755 void TISInputSourceWrapper::InitByCurrentASCIICapableKeyboardLayout() {
757 mInputSource = ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
758 mKeyboardLayout = mInputSource;
761 void TISInputSourceWrapper::InitByCurrentInputMethodKeyboardLayoutOverride() {
763 mInputSource = ::TISCopyInputMethodKeyboardLayoutOverride();
764 mKeyboardLayout = mInputSource;
767 void TISInputSourceWrapper::InitByTISInputSourceRef(TISInputSourceRef aInputSource) {
769 mInputSource = aInputSource;
770 if (IsKeyboardLayout()) {
771 mKeyboardLayout = mInputSource;
775 void TISInputSourceWrapper::InitByLanguage(CFStringRef aLanguage) {
777 mInputSource = ::TISCopyInputSourceForLanguage(aLanguage);
778 if (IsKeyboardLayout()) {
779 mKeyboardLayout = mInputSource;
783 const UCKeyboardLayout* TISInputSourceWrapper::GetUCKeyboardLayout() {
784 NS_ENSURE_TRUE(mKeyboardLayout, nullptr);
785 if (mUCKeyboardLayout) {
786 return mUCKeyboardLayout;
788 CFDataRef uchr = static_cast<CFDataRef>(
789 ::TISGetInputSourceProperty(mKeyboardLayout, kTISPropertyUnicodeKeyLayoutData));
791 // We should be always able to get the layout here.
792 NS_ENSURE_TRUE(uchr, nullptr);
793 mUCKeyboardLayout = reinterpret_cast<const UCKeyboardLayout*>(CFDataGetBytePtr(uchr));
794 return mUCKeyboardLayout;
797 bool TISInputSourceWrapper::GetBoolProperty(const CFStringRef aKey) {
798 CFBooleanRef ret = static_cast<CFBooleanRef>(::TISGetInputSourceProperty(mInputSource, aKey));
799 return ::CFBooleanGetValue(ret);
802 bool TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey, CFStringRef& aStr) {
803 aStr = static_cast<CFStringRef>(::TISGetInputSourceProperty(mInputSource, aKey));
804 return aStr != nullptr;
807 bool TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey, nsAString& aStr) {
809 GetStringProperty(aKey, str);
810 nsCocoaUtils::GetStringForNSString((const NSString*)str, aStr);
811 return !aStr.IsEmpty();
814 bool TISInputSourceWrapper::IsOpenedIMEMode() {
815 NS_ENSURE_TRUE(mInputSource, false);
816 if (!IsIMEMode()) return false;
817 return !IsASCIICapable();
820 bool TISInputSourceWrapper::IsIMEMode() {
821 NS_ENSURE_TRUE(mInputSource, false);
823 GetInputSourceType(str);
824 NS_ENSURE_TRUE(str, false);
825 return ::CFStringCompare(kTISTypeKeyboardInputMode, str, 0) == kCFCompareEqualTo;
828 bool TISInputSourceWrapper::IsKeyboardLayout() {
829 NS_ENSURE_TRUE(mInputSource, false);
831 GetInputSourceType(str);
832 NS_ENSURE_TRUE(str, false);
833 return ::CFStringCompare(kTISTypeKeyboardLayout, str, 0) == kCFCompareEqualTo;
836 bool TISInputSourceWrapper::GetLanguageList(CFArrayRef& aLanguageList) {
837 NS_ENSURE_TRUE(mInputSource, false);
838 aLanguageList = static_cast<CFArrayRef>(
839 ::TISGetInputSourceProperty(mInputSource, kTISPropertyInputSourceLanguages));
840 return aLanguageList != nullptr;
843 bool TISInputSourceWrapper::GetPrimaryLanguage(CFStringRef& aPrimaryLanguage) {
844 NS_ENSURE_TRUE(mInputSource, false);
846 NS_ENSURE_TRUE(GetLanguageList(langList), false);
847 if (::CFArrayGetCount(langList) == 0) return false;
848 aPrimaryLanguage = static_cast<CFStringRef>(::CFArrayGetValueAtIndex(langList, 0));
849 return aPrimaryLanguage != nullptr;
852 bool TISInputSourceWrapper::GetPrimaryLanguage(nsAString& aPrimaryLanguage) {
853 NS_ENSURE_TRUE(mInputSource, false);
854 CFStringRef primaryLanguage;
855 NS_ENSURE_TRUE(GetPrimaryLanguage(primaryLanguage), false);
856 nsCocoaUtils::GetStringForNSString((const NSString*)primaryLanguage, aPrimaryLanguage);
857 return !aPrimaryLanguage.IsEmpty();
860 bool TISInputSourceWrapper::IsForRTLLanguage() {
862 // Get the input character of the 'A' key of ANSI keyboard layout.
864 bool ret = TranslateToString(kVK_ANSI_A, 0, eKbdType_ANSI, str);
865 NS_ENSURE_TRUE(ret, ret);
866 char16_t ch = str.IsEmpty() ? char16_t(0) : str.CharAt(0);
867 mIsRTL = UTF16_CODE_UNIT_IS_BIDI(ch);
872 bool TISInputSourceWrapper::IsForJapaneseLanguage() {
874 GetPrimaryLanguage(lang);
875 return lang.EqualsLiteral("ja");
878 bool TISInputSourceWrapper::IsInitializedByCurrentInputSource() {
879 return mInputSource == ::TISCopyCurrentKeyboardInputSource();
882 void TISInputSourceWrapper::Select() {
883 if (!mInputSource) return;
884 ::TISSelectInputSource(mInputSource);
887 void TISInputSourceWrapper::Clear() {
888 // Clear() is always called when TISInputSourceWrappper is created.
889 EnsureToLogAllKeyboardLayoutsAndIMEs();
891 if (mInputSourceList) {
892 ::CFRelease(mInputSourceList);
894 mInputSourceList = nullptr;
895 mInputSource = nullptr;
896 mKeyboardLayout = nullptr;
898 mUCKeyboardLayout = nullptr;
899 mOverrideKeyboard = false;
902 bool TISInputSourceWrapper::IsPrintableKeyEvent(NSEvent* aNativeKeyEvent) const {
903 UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
905 bool isPrintableKey = !TextInputHandler::IsSpecialGeckoKey(nativeKeyCode);
906 if (isPrintableKey && [aNativeKeyEvent type] != NSEventTypeKeyDown &&
907 [aNativeKeyEvent type] != NSEventTypeKeyUp) {
908 NS_WARNING("Why would a printable key not be an NSEventTypeKeyDown or NSEventTypeKeyUp event?");
909 isPrintableKey = false;
911 return isPrintableKey;
914 UInt32 TISInputSourceWrapper::GetKbdType() const {
915 // If a keyboard layout override is set, we also need to force the keyboard
916 // type to something ANSI to avoid test failures on machines with JIS
917 // keyboards (since the pair of keyboard layout and physical keyboard type
918 // form the actual key layout). This assumes that the test setting the
919 // override was written assuming an ANSI keyboard.
920 return mOverrideKeyboard ? eKbdType_ANSI : ::LMGetKbdType();
923 void TISInputSourceWrapper::ComputeInsertStringForCharCode(NSEvent* aNativeKeyEvent,
924 const WidgetKeyboardEvent& aKeyEvent,
925 const nsAString* aInsertString,
926 nsAString& aResult) {
928 // If the caller expects that the aInsertString will be input, we shouldn't
930 aResult = *aInsertString;
931 } else if (IsPrintableKeyEvent(aNativeKeyEvent)) {
932 // If IME is open, [aNativeKeyEvent characters] may be a character
933 // which will be appended to the composition string. However, especially,
934 // while IME is disabled, most users and developers expect the key event
935 // works as IME closed. So, we should compute the aResult with
936 // the ASCII capable keyboard layout.
937 // NOTE: Such keyboard layouts typically change the layout to its ASCII
938 // capable layout when Command key is pressed. And we don't worry
939 // when Control key is pressed too because it causes inputting
940 // control characters.
941 // Additionally, if the key event doesn't input any text, the event may be
942 // dead key event. In this case, the charCode value should be the dead
944 UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
945 if ((!aKeyEvent.IsMeta() && !aKeyEvent.IsControl() && IsOpenedIMEMode()) ||
946 ![[aNativeKeyEvent characters] length]) {
947 UInt32 state = nsCocoaUtils::ConvertToCarbonModifier([aNativeKeyEvent modifierFlags]);
948 uint32_t ch = TranslateToChar(nativeKeyCode, state, GetKbdType());
953 // If the caller isn't sure what string will be input, let's use
954 // characters of NSEvent.
955 nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], aResult);
958 // If control key is pressed and the eventChars is a non-printable control
959 // character, we should convert it to ASCII alphabet.
960 if (aKeyEvent.IsControl() && !aResult.IsEmpty() && aResult[0] <= char16_t(26)) {
961 aResult = (aKeyEvent.IsShift() ^ aKeyEvent.IsCapsLocked())
962 ? static_cast<char16_t>(aResult[0] + ('A' - 1))
963 : static_cast<char16_t>(aResult[0] + ('a' - 1));
965 // If Meta key is pressed, it may cause to switch the keyboard layout like
966 // Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY.
967 else if (aKeyEvent.IsMeta() && !(aKeyEvent.IsControl() || aKeyEvent.IsAlt())) {
968 UInt32 kbType = GetKbdType();
969 UInt32 numLockState = aKeyEvent.IsNumLocked() ? kEventKeyModifierNumLockMask : 0;
970 UInt32 capsLockState = aKeyEvent.IsCapsLocked() ? alphaLock : 0;
971 UInt32 shiftState = aKeyEvent.IsShift() ? shiftKey : 0;
972 uint32_t uncmdedChar = TranslateToChar(nativeKeyCode, numLockState, kbType);
973 uint32_t cmdedChar = TranslateToChar(nativeKeyCode, cmdKey | numLockState, kbType);
974 // If we can make a good guess at the characters that the user would
975 // expect this key combination to produce (with and without Shift) then
976 // use those characters. This also corrects for CapsLock.
978 if (uncmdedChar == cmdedChar) {
979 // The characters produced with Command seem similar to those without
981 ch = TranslateToChar(nativeKeyCode, shiftState | capsLockState | numLockState, kbType);
983 TISInputSourceWrapper USLayout("com.apple.keylayout.US");
984 uint32_t uncmdedUSChar = USLayout.TranslateToChar(nativeKeyCode, numLockState, kbType);
985 // If it looks like characters from US keyboard layout when Command key
986 // is pressed, we should compute a character in the layout.
987 if (uncmdedUSChar == cmdedChar) {
988 ch = USLayout.TranslateToChar(nativeKeyCode, shiftState | capsLockState | numLockState,
993 // If there is a more preferred character for the commanded key event,
1001 // Remove control characters which shouldn't be inputted on editor.
1002 // XXX Currently, we don't find any cases inserting control characters with
1003 // printable character. So, just checking first character is enough.
1004 if (!aResult.IsEmpty() && IsControlChar(aResult[0])) {
1009 void TISInputSourceWrapper::InitKeyEvent(NSEvent* aNativeKeyEvent, WidgetKeyboardEvent& aKeyEvent,
1010 bool aIsProcessedByIME, const nsAString* aInsertString) {
1011 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1013 MOZ_ASSERT(!aIsProcessedByIME || aKeyEvent.mMessage != eKeyPress,
1014 "eKeyPress event should not be marked as proccessed by IME");
1016 MOZ_LOG(gKeyLog, LogLevel::Info,
1017 ("%p TISInputSourceWrapper::InitKeyEvent, aNativeKeyEvent=%p, "
1018 "aKeyEvent.mMessage=%s, aProcessedByIME=%s, aInsertString=%p, "
1019 "IsOpenedIMEMode()=%s",
1020 this, aNativeKeyEvent, GetGeckoKeyEventType(aKeyEvent), TrueOrFalse(aIsProcessedByIME),
1021 aInsertString, TrueOrFalse(IsOpenedIMEMode())));
1023 if (NS_WARN_IF(!aNativeKeyEvent)) {
1027 nsCocoaUtils::InitInputEvent(aKeyEvent, aNativeKeyEvent);
1029 // This is used only while dispatching the event (which is a synchronous
1030 // call), so there is no need to retain and release this data.
1031 aKeyEvent.mNativeKeyEvent = aNativeKeyEvent;
1033 aKeyEvent.mRefPoint = LayoutDeviceIntPoint(0, 0);
1035 UInt32 kbType = GetKbdType();
1036 UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
1038 // macOS handles dead key as IME. If the key is first key press of dead
1039 // key, we should use KEY_NAME_INDEX_Dead for first (dead) key event.
1040 // So, if aIsProcessedByIME is true, it may be dead key. Let's check
1041 // if current key event is a dead key's keydown event.
1042 bool isProcessedByIME =
1043 aIsProcessedByIME && !TISInputSourceWrapper::CurrentInputSource().IsDeadKey(aNativeKeyEvent);
1045 aKeyEvent.mKeyCode = isProcessedByIME
1047 : ComputeGeckoKeyCode(nativeKeyCode, kbType, aKeyEvent.IsMeta());
1049 switch (nativeKeyCode) {
1054 aKeyEvent.mLocation = eKeyLocationLeft;
1057 case kVK_RightCommand:
1058 case kVK_RightShift:
1059 case kVK_RightOption:
1060 case kVK_RightControl:
1061 aKeyEvent.mLocation = eKeyLocationRight;
1064 case kVK_ANSI_Keypad0:
1065 case kVK_ANSI_Keypad1:
1066 case kVK_ANSI_Keypad2:
1067 case kVK_ANSI_Keypad3:
1068 case kVK_ANSI_Keypad4:
1069 case kVK_ANSI_Keypad5:
1070 case kVK_ANSI_Keypad6:
1071 case kVK_ANSI_Keypad7:
1072 case kVK_ANSI_Keypad8:
1073 case kVK_ANSI_Keypad9:
1074 case kVK_ANSI_KeypadMultiply:
1075 case kVK_ANSI_KeypadPlus:
1076 case kVK_ANSI_KeypadMinus:
1077 case kVK_ANSI_KeypadDecimal:
1078 case kVK_ANSI_KeypadDivide:
1079 case kVK_ANSI_KeypadEquals:
1080 case kVK_ANSI_KeypadEnter:
1081 case kVK_JIS_KeypadComma:
1082 case kVK_Powerbook_KeypadEnter:
1083 aKeyEvent.mLocation = eKeyLocationNumpad;
1087 aKeyEvent.mLocation = eKeyLocationStandard;
1091 aKeyEvent.mIsRepeat =
1092 ([aNativeKeyEvent type] == NSEventTypeKeyDown) ? [aNativeKeyEvent isARepeat] : false;
1094 MOZ_LOG(gKeyLog, LogLevel::Info,
1095 ("%p TISInputSourceWrapper::InitKeyEvent, "
1096 "shift=%s, ctrl=%s, alt=%s, meta=%s",
1097 this, OnOrOff(aKeyEvent.IsShift()), OnOrOff(aKeyEvent.IsControl()),
1098 OnOrOff(aKeyEvent.IsAlt()), OnOrOff(aKeyEvent.IsMeta())));
1100 if (isProcessedByIME) {
1101 aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_Process;
1102 } else if (IsPrintableKeyEvent(aNativeKeyEvent)) {
1103 aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
1104 // If insertText calls this method, let's use the string.
1105 if (aInsertString && !aInsertString->IsEmpty() && !IsControlChar((*aInsertString)[0])) {
1106 aKeyEvent.mKeyValue = *aInsertString;
1108 // If meta key is pressed, the printable key layout may be switched from
1109 // non-ASCII capable layout to ASCII capable, or from Dvorak to QWERTY.
1110 // KeyboardEvent.key value should be the switched layout's character.
1111 else if (aKeyEvent.IsMeta()) {
1112 nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], aKeyEvent.mKeyValue);
1114 // If control key is pressed, some keys may produce printable character via
1115 // [aNativeKeyEvent characters]. Otherwise, translate input character of
1116 // the key without control key.
1117 else if (aKeyEvent.IsControl()) {
1118 NSUInteger cocoaState = [aNativeKeyEvent modifierFlags] & ~NSEventModifierFlagControl;
1119 UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState);
1120 if (IsDeadKey(nativeKeyCode, carbonState, kbType)) {
1121 aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_Dead;
1123 aKeyEvent.mKeyValue = TranslateToChar(nativeKeyCode, carbonState, kbType);
1124 if (!aKeyEvent.mKeyValue.IsEmpty() && IsControlChar(aKeyEvent.mKeyValue[0])) {
1125 // Don't expose control character to the web.
1126 aKeyEvent.mKeyValue.Truncate();
1130 // Otherwise, KeyboardEvent.key expose
1131 // [aNativeKeyEvent characters] value. However, if IME is open and the
1132 // keyboard layout isn't ASCII capable, exposing the non-ASCII character
1133 // doesn't match with other platform's behavior. For the compatibility
1134 // with other platform's Gecko, we need to set a translated character.
1135 else if (IsOpenedIMEMode()) {
1136 UInt32 state = nsCocoaUtils::ConvertToCarbonModifier([aNativeKeyEvent modifierFlags]);
1137 aKeyEvent.mKeyValue = TranslateToChar(nativeKeyCode, state, kbType);
1139 nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], aKeyEvent.mKeyValue);
1140 // If the key value is empty, the event may be a dead key event.
1141 // If TranslateToChar() returns non-zero value, that means that
1142 // the key may input a character with different dead key state.
1143 if (aKeyEvent.mKeyValue.IsEmpty()) {
1144 NSUInteger cocoaState = [aNativeKeyEvent modifierFlags];
1145 UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState);
1146 if (TranslateToChar(nativeKeyCode, carbonState, kbType)) {
1147 aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_Dead;
1152 // Last resort. If .key value becomes empty string, we should use
1153 // charactersIgnoringModifiers, if it's available.
1154 if (aKeyEvent.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING &&
1155 (aKeyEvent.mKeyValue.IsEmpty() || IsControlChar(aKeyEvent.mKeyValue[0]))) {
1156 nsCocoaUtils::GetStringForNSString([aNativeKeyEvent charactersIgnoringModifiers],
1157 aKeyEvent.mKeyValue);
1158 // But don't expose it if it's a control character.
1159 if (!aKeyEvent.mKeyValue.IsEmpty() && IsControlChar(aKeyEvent.mKeyValue[0])) {
1160 aKeyEvent.mKeyValue.Truncate();
1164 // Compute the key for non-printable keys and some special printable keys.
1165 aKeyEvent.mKeyNameIndex = ComputeGeckoKeyNameIndex(nativeKeyCode);
1168 aKeyEvent.mCodeNameIndex = ComputeGeckoCodeNameIndex(nativeKeyCode, kbType);
1169 MOZ_ASSERT(aKeyEvent.mCodeNameIndex != CODE_NAME_INDEX_USE_STRING);
1171 NS_OBJC_END_TRY_IGNORE_BLOCK
1174 void TISInputSourceWrapper::WillDispatchKeyboardEvent(NSEvent* aNativeKeyEvent,
1175 const nsAString* aInsertString,
1176 uint32_t aIndexOfKeypress,
1177 WidgetKeyboardEvent& aKeyEvent) {
1178 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1180 // Nothing to do here if the native key event is neither NSEventTypeKeyDown nor
1181 // NSEventTypeKeyUp because accessing [aNativeKeyEvent characters] causes throwing
1183 if ([aNativeKeyEvent type] != NSEventTypeKeyDown && [aNativeKeyEvent type] != NSEventTypeKeyUp) {
1187 UInt32 kbType = GetKbdType();
1189 if (MOZ_LOG_TEST(gKeyLog, LogLevel::Info)) {
1191 nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], chars);
1192 NS_ConvertUTF16toUTF8 utf8Chars(chars);
1193 char16_t uniChar = static_cast<char16_t>(aKeyEvent.mCharCode);
1194 MOZ_LOG(gKeyLog, LogLevel::Info,
1195 ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
1196 "aNativeKeyEvent=%p, aInsertString=%p (\"%s\"), "
1197 "aIndexOfKeypress=%u, [aNativeKeyEvent characters]=\"%s\", "
1198 "aKeyEvent={ mMessage=%s, mCharCode=0x%X(%s) }, kbType=0x%X, "
1199 "IsOpenedIMEMode()=%s",
1200 this, aNativeKeyEvent, aInsertString,
1201 aInsertString ? GetCharacters(*aInsertString) : "", aIndexOfKeypress,
1202 GetCharacters([aNativeKeyEvent characters]), GetGeckoKeyEventType(aKeyEvent),
1203 aKeyEvent.mCharCode, uniChar ? NS_ConvertUTF16toUTF8(&uniChar, 1).get() : "",
1204 static_cast<unsigned int>(kbType), TrueOrFalse(IsOpenedIMEMode())));
1207 nsAutoString insertStringForCharCode;
1208 ComputeInsertStringForCharCode(aNativeKeyEvent, aKeyEvent, aInsertString,
1209 insertStringForCharCode);
1211 // The mCharCode was set from mKeyValue. However, for example, when Ctrl key
1212 // is pressed, its value should indicate an ASCII character for backward
1213 // compatibility rather than inputting character without the modifiers.
1214 // Therefore, we need to modify mCharCode value here.
1215 uint32_t charCode = 0;
1216 if (aIndexOfKeypress < insertStringForCharCode.Length()) {
1217 charCode = insertStringForCharCode[aIndexOfKeypress];
1219 aKeyEvent.SetCharCode(charCode);
1221 MOZ_LOG(gKeyLog, LogLevel::Info,
1222 ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
1223 "aKeyEvent.mKeyCode=0x%X, aKeyEvent.mCharCode=0x%X",
1224 this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode));
1226 // If aInsertString is not nullptr (it means InsertText() is called)
1227 // and it acutally inputs a character, we don't need to append alternative
1228 // charCode values since such keyboard event shouldn't be handled as
1230 if (aInsertString && charCode) {
1234 TISInputSourceWrapper USLayout("com.apple.keylayout.US");
1235 bool isRomanKeyboardLayout = IsASCIICapable();
1237 UInt32 key = [aNativeKeyEvent keyCode];
1239 // Caps lock and num lock modifier state:
1240 UInt32 lockState = 0;
1241 if ([aNativeKeyEvent modifierFlags] & NSEventModifierFlagCapsLock) {
1242 lockState |= alphaLock;
1244 if ([aNativeKeyEvent modifierFlags] & NSEventModifierFlagNumericPad) {
1245 lockState |= kEventKeyModifierNumLockMask;
1248 MOZ_LOG(gKeyLog, LogLevel::Info,
1249 ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
1250 "isRomanKeyboardLayout=%s, kbType=0x%X, key=0x%X",
1251 this, TrueOrFalse(isRomanKeyboardLayout), static_cast<unsigned int>(kbType),
1252 static_cast<unsigned int>(key)));
1257 uint32_t unshiftedChar = TranslateToChar(key, lockState, kbType);
1258 UInt32 shiftLockMod = shiftKey | lockState;
1259 uint32_t shiftedChar = TranslateToChar(key, shiftLockMod, kbType);
1261 // characters generated with Cmd key
1262 // XXX we should remove CapsLock state, which changes characters from
1263 // Latin to Cyrillic with Russian layout on 10.4 only when Cmd key
1265 UInt32 numState = (lockState & ~alphaLock); // only num lock state
1266 uint32_t uncmdedChar = TranslateToChar(key, numState, kbType);
1267 UInt32 shiftNumMod = numState | shiftKey;
1268 uint32_t uncmdedShiftChar = TranslateToChar(key, shiftNumMod, kbType);
1269 uint32_t uncmdedUSChar = USLayout.TranslateToChar(key, numState, kbType);
1270 UInt32 cmdNumMod = cmdKey | numState;
1271 uint32_t cmdedChar = TranslateToChar(key, cmdNumMod, kbType);
1272 UInt32 cmdShiftNumMod = shiftKey | cmdNumMod;
1273 uint32_t cmdedShiftChar = TranslateToChar(key, cmdShiftNumMod, kbType);
1275 // Is the keyboard layout changed by Cmd key?
1276 // E.g., Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY.
1277 bool isCmdSwitchLayout = uncmdedChar != cmdedChar;
1278 // Is the keyboard layout for Latin, but Cmd key switches the layout?
1279 // I.e., Dvorak-QWERTY
1280 bool isDvorakQWERTY = isCmdSwitchLayout && isRomanKeyboardLayout;
1282 // If the current keyboard is not Dvorak-QWERTY or Cmd is not pressed,
1283 // we should append unshiftedChar and shiftedChar for handling the
1284 // normal characters. These are the characters that the user is most
1285 // likely to associate with this key.
1286 if ((unshiftedChar || shiftedChar) && (!aKeyEvent.IsMeta() || !isDvorakQWERTY)) {
1287 AlternativeCharCode altCharCodes(unshiftedChar, shiftedChar);
1288 aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes);
1291 gKeyLog, LogLevel::Info,
1292 ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
1293 "aKeyEvent.isMeta=%s, isDvorakQWERTY=%s, "
1294 "unshiftedChar=U+%X, shiftedChar=U+%X",
1295 this, OnOrOff(aKeyEvent.IsMeta()), TrueOrFalse(isDvorakQWERTY), unshiftedChar, shiftedChar));
1297 // Most keyboard layouts provide the same characters in the NSEvents
1298 // with Command+Shift as with Command. However, with Command+Shift we
1299 // want the character on the second level. e.g. With a US QWERTY
1300 // layout, we want "?" when the "/","?" key is pressed with
1303 // On a German layout, the OS gives us '/' with Cmd+Shift+SS(eszett)
1304 // even though Cmd+SS is 'SS' and Shift+'SS' is '?'. This '/' seems
1305 // like a hack to make the Cmd+"?" event look the same as the Cmd+"?"
1306 // event on a US keyboard. The user thinks they are typing Cmd+"?", so
1307 // we'll prefer the "?" character, replacing mCharCode with shiftedChar
1308 // when Shift is pressed. However, in case there is a layout where the
1309 // character unique to Cmd+Shift is the character that the user expects,
1310 // we'll send it as an alternative char.
1311 bool hasCmdShiftOnlyChar = cmdedChar != cmdedShiftChar && uncmdedShiftChar != cmdedShiftChar;
1312 uint32_t originalCmdedShiftChar = cmdedShiftChar;
1314 // If we can make a good guess at the characters that the user would
1315 // expect this key combination to produce (with and without Shift) then
1316 // use those characters. This also corrects for CapsLock, which was
1318 if (!isCmdSwitchLayout) {
1319 // The characters produced with Command seem similar to those without
1321 if (unshiftedChar) {
1322 cmdedChar = unshiftedChar;
1325 cmdedShiftChar = shiftedChar;
1327 } else if (uncmdedUSChar == cmdedChar) {
1328 // It looks like characters from a US layout are provided when Command
1330 uint32_t ch = USLayout.TranslateToChar(key, lockState, kbType);
1334 ch = USLayout.TranslateToChar(key, shiftLockMod, kbType);
1336 cmdedShiftChar = ch;
1340 // If the current keyboard layout is switched by the Cmd key,
1341 // we should append cmdedChar and shiftedCmdChar that are
1342 // Latin char for the key.
1343 // If the keyboard layout is Dvorak-QWERTY, we should append them only when
1344 // command key is pressed because when command key isn't pressed, uncmded
1345 // chars have been appended already.
1346 if ((cmdedChar || cmdedShiftChar) && isCmdSwitchLayout &&
1347 (aKeyEvent.IsMeta() || !isDvorakQWERTY)) {
1348 AlternativeCharCode altCharCodes(cmdedChar, cmdedShiftChar);
1349 aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes);
1351 MOZ_LOG(gKeyLog, LogLevel::Info,
1352 ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
1353 "hasCmdShiftOnlyChar=%s, isCmdSwitchLayout=%s, isDvorakQWERTY=%s, "
1354 "cmdedChar=U+%X, cmdedShiftChar=U+%X",
1355 this, TrueOrFalse(hasCmdShiftOnlyChar), TrueOrFalse(isDvorakQWERTY),
1356 TrueOrFalse(isDvorakQWERTY), cmdedChar, cmdedShiftChar));
1357 // Special case for 'SS' key of German layout. See the comment of
1358 // hasCmdShiftOnlyChar definition for the detail.
1359 if (hasCmdShiftOnlyChar && originalCmdedShiftChar) {
1360 AlternativeCharCode altCharCodes(0, originalCmdedShiftChar);
1361 aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes);
1363 MOZ_LOG(gKeyLog, LogLevel::Info,
1364 ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
1365 "hasCmdShiftOnlyChar=%s, originalCmdedShiftChar=U+%X",
1366 this, TrueOrFalse(hasCmdShiftOnlyChar), originalCmdedShiftChar));
1368 NS_OBJC_END_TRY_IGNORE_BLOCK
1371 uint32_t TISInputSourceWrapper::ComputeGeckoKeyCode(UInt32 aNativeKeyCode, UInt32 aKbType,
1372 bool aCmdIsPressed) {
1374 gKeyLog, LogLevel::Info,
1375 ("%p TISInputSourceWrapper::ComputeGeckoKeyCode, aNativeKeyCode=0x%X, "
1376 "aKbType=0x%X, aCmdIsPressed=%s, IsOpenedIMEMode()=%s, "
1377 "IsASCIICapable()=%s",
1378 this, static_cast<unsigned int>(aNativeKeyCode), static_cast<unsigned int>(aKbType),
1379 TrueOrFalse(aCmdIsPressed), TrueOrFalse(IsOpenedIMEMode()), TrueOrFalse(IsASCIICapable())));
1381 switch (aNativeKeyCode) {
1385 return NS_VK_ESCAPE;
1388 case kVK_RightCommand:
1391 case kVK_RightShift:
1395 return NS_VK_CAPS_LOCK;
1396 case kVK_RightControl:
1398 return NS_VK_CONTROL;
1399 case kVK_RightOption:
1403 case kVK_ANSI_KeypadClear:
1431 // case kVK_F13: return NS_VK_F13; // clash with the 3 below
1432 // case kVK_F14: return NS_VK_F14;
1433 // case kVK_F15: return NS_VK_F15;
1445 case kVK_PC_ScrollLock:
1446 return NS_VK_SCROLL_LOCK;
1447 case kVK_PC_PrintScreen:
1448 return NS_VK_PRINTSCREEN;
1451 case kVK_ANSI_Keypad0:
1452 return NS_VK_NUMPAD0;
1453 case kVK_ANSI_Keypad1:
1454 return NS_VK_NUMPAD1;
1455 case kVK_ANSI_Keypad2:
1456 return NS_VK_NUMPAD2;
1457 case kVK_ANSI_Keypad3:
1458 return NS_VK_NUMPAD3;
1459 case kVK_ANSI_Keypad4:
1460 return NS_VK_NUMPAD4;
1461 case kVK_ANSI_Keypad5:
1462 return NS_VK_NUMPAD5;
1463 case kVK_ANSI_Keypad6:
1464 return NS_VK_NUMPAD6;
1465 case kVK_ANSI_Keypad7:
1466 return NS_VK_NUMPAD7;
1467 case kVK_ANSI_Keypad8:
1468 return NS_VK_NUMPAD8;
1469 case kVK_ANSI_Keypad9:
1470 return NS_VK_NUMPAD9;
1472 case kVK_ANSI_KeypadMultiply:
1473 return NS_VK_MULTIPLY;
1474 case kVK_ANSI_KeypadPlus:
1476 case kVK_ANSI_KeypadMinus:
1477 return NS_VK_SUBTRACT;
1478 case kVK_ANSI_KeypadDecimal:
1479 return NS_VK_DECIMAL;
1480 case kVK_ANSI_KeypadDivide:
1481 return NS_VK_DIVIDE;
1483 case kVK_JIS_KeypadComma:
1484 return NS_VK_SEPARATOR;
1492 // these may clash with forward delete and help
1494 return NS_VK_INSERT;
1496 return NS_VK_DELETE;
1498 case kVK_PC_Backspace:
1509 return NS_VK_PAGE_UP;
1511 return NS_VK_PAGE_DOWN;
1515 case kVK_RightArrow:
1522 case kVK_PC_ContextMenu:
1523 return NS_VK_CONTEXT_MENU;
1546 case kVK_ANSI_KeypadEnter:
1548 case kVK_Powerbook_KeypadEnter:
1549 return NS_VK_RETURN;
1552 // If Cmd key is pressed, that causes switching keyboard layout temporarily.
1553 // E.g., Dvorak-QWERTY. Therefore, if Cmd key is pressed, we should honor it.
1554 UInt32 modifiers = aCmdIsPressed ? cmdKey : 0;
1556 uint32_t charCode = TranslateToChar(aNativeKeyCode, modifiers, aKbType);
1558 // Special case for Mac. Mac inputs Yen sign (U+00A5) directly instead of
1559 // Back slash (U+005C). We should return NS_VK_BACK_SLASH for compatibility
1560 // with other platforms.
1561 // XXX How about Won sign (U+20A9) which has same problem as Yen sign?
1562 if (charCode == 0x00A5) {
1563 return NS_VK_BACK_SLASH;
1566 uint32_t keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode);
1571 // If the unshifed char isn't an ASCII character, use shifted char.
1572 charCode = TranslateToChar(aNativeKeyCode, modifiers | shiftKey, aKbType);
1573 keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode);
1578 if (!IsASCIICapable()) {
1579 // Retry with ASCII capable keyboard layout.
1580 TISInputSourceWrapper currentKeyboardLayout;
1581 currentKeyboardLayout.InitByCurrentASCIICapableKeyboardLayout();
1582 NS_ENSURE_TRUE(mInputSource != currentKeyboardLayout.mInputSource, 0);
1583 keyCode = currentKeyboardLayout.ComputeGeckoKeyCode(aNativeKeyCode, aKbType, aCmdIsPressed);
1584 // We've returned 0 for long time if keyCode isn't for an alphabet keys or
1585 // a numeric key even in alternative ASCII capable keyboard layout because
1586 // we decided that we should avoid setting same keyCode value to 2 or
1587 // more keys since active keyboard layout may have a key to input the
1588 // punctuation with different key. However, setting keyCode to 0 makes
1589 // some web applications which are aware of neither KeyboardEvent.key nor
1590 // KeyboardEvent.code not work with Firefox when user selects non-ASCII
1591 // capable keyboard layout such as Russian and Thai. So, if alternative
1592 // ASCII capable keyboard layout has keyCode value for the key, we should
1593 // use it. In other words, this behavior does that non-ASCII capable
1594 // keyboard layout overrides some keys' keyCode value only if the key
1595 // produces ASCII character by itself or with Shift key.
1601 // Otherwise, let's decide keyCode value from the native virtual keycode
1602 // value on major keyboard layout.
1603 CodeNameIndex code = ComputeGeckoCodeNameIndex(aNativeKeyCode, aKbType);
1604 return WidgetKeyboardEvent::GetFallbackKeyCodeOfPunctuationKey(code);
1608 KeyNameIndex TISInputSourceWrapper::ComputeGeckoKeyNameIndex(UInt32 aNativeKeyCode) {
1610 // When unsupported keys like Convert, Nonconvert of Japanese keyboard is
1612 // on 10.6.x, 'A' key event is fired (and also actually 'a' is inserted).
1613 // on 10.7.x, Nothing happens.
1614 // on 10.8.x, Nothing happens.
1615 // on 10.9.x, FlagsChanged event is fired with keyCode 0xFF.
1616 switch (aNativeKeyCode) {
1617 #define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
1619 return aKeyNameIndex;
1621 #include "NativeKeyToDOMKeyName.h"
1623 #undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
1626 return KEY_NAME_INDEX_Unidentified;
1631 CodeNameIndex TISInputSourceWrapper::ComputeGeckoCodeNameIndex(UInt32 aNativeKeyCode,
1633 // macOS swaps native key code between Backquote key and IntlBackslash key
1634 // only when the keyboard type is ISO. Let's treat the key code after
1635 // swapping them here because Chromium does so only when computing .code
1637 if (::KBGetLayoutType(aKbType) == kKeyboardISO) {
1638 if (aNativeKeyCode == kVK_ISO_Section) {
1639 aNativeKeyCode = kVK_ANSI_Grave;
1640 } else if (aNativeKeyCode == kVK_ANSI_Grave) {
1641 aNativeKeyCode = kVK_ISO_Section;
1645 switch (aNativeKeyCode) {
1646 #define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \
1648 return aCodeNameIndex;
1650 #include "NativeKeyToDOMCodeName.h"
1652 #undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX
1655 return CODE_NAME_INDEX_UNKNOWN;
1661 /******************************************************************************
1663 * TextInputHandler implementation (static methods)
1665 ******************************************************************************/
1667 NSUInteger TextInputHandler::sLastModifierState = 0;
1670 CFArrayRef TextInputHandler::CreateAllKeyboardLayoutList() {
1671 const void* keys[] = {kTISPropertyInputSourceType};
1672 const void* values[] = {kTISTypeKeyboardLayout};
1673 CFDictionaryRef filter = ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
1674 NS_ASSERTION(filter, "failed to create the filter");
1675 CFArrayRef list = ::TISCreateInputSourceList(filter, true);
1676 ::CFRelease(filter);
1681 void TextInputHandler::DebugPrintAllKeyboardLayouts() {
1682 if (MOZ_LOG_TEST(gKeyLog, LogLevel::Info)) {
1683 CFArrayRef list = CreateAllKeyboardLayoutList();
1684 MOZ_LOG(gKeyLog, LogLevel::Info, ("Keyboard layout configuration:"));
1685 CFIndex idx = ::CFArrayGetCount(list);
1686 TISInputSourceWrapper tis;
1687 for (CFIndex i = 0; i < idx; ++i) {
1688 TISInputSourceRef inputSource =
1689 static_cast<TISInputSourceRef>(const_cast<void*>(::CFArrayGetValueAtIndex(list, i)));
1690 tis.InitByTISInputSourceRef(inputSource);
1691 nsAutoString name, isid;
1692 tis.GetLocalizedName(name);
1693 tis.GetInputSourceID(isid);
1695 gKeyLog, LogLevel::Info,
1696 (" %s\t<%s>%s%s\n", NS_ConvertUTF16toUTF8(name).get(), NS_ConvertUTF16toUTF8(isid).get(),
1697 tis.IsASCIICapable() ? "" : "\t(Isn't ASCII capable)",
1698 tis.IsKeyboardLayout() && tis.GetUCKeyboardLayout() ? "" : "\t(uchr is NOT AVAILABLE)"));
1706 /******************************************************************************
1708 * TextInputHandler implementation
1710 ******************************************************************************/
1712 TextInputHandler::TextInputHandler(nsChildView* aWidget, NSView<mozView>* aNativeView)
1713 : IMEInputHandler(aWidget, aNativeView) {
1714 EnsureToLogAllKeyboardLayoutsAndIMEs();
1715 [mView installTextInputHandler:this];
1718 TextInputHandler::~TextInputHandler() { [mView uninstallTextInputHandler]; }
1720 bool TextInputHandler::HandleKeyDownEvent(NSEvent* aNativeEvent, uint32_t aUniqueId) {
1721 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
1724 MOZ_LOG_KEY_OR_IME(LogLevel::Info, ("%p TextInputHandler::HandleKeyDownEvent, "
1725 "widget has been already destroyed",
1730 // Insert empty line to the log for easier to read.
1731 MOZ_LOG_KEY_OR_IME(LogLevel::Info, (""));
1734 ("%p TextInputHandler::HandleKeyDownEvent, aNativeEvent=%p, "
1735 "type=%s, keyCode=%u (0x%X), modifierFlags=0x%lX, characters=\"%s\", "
1736 "charactersIgnoringModifiers=\"%s\"",
1737 this, aNativeEvent, GetNativeKeyEventType(aNativeEvent), [aNativeEvent keyCode],
1738 [aNativeEvent keyCode], static_cast<unsigned long>([aNativeEvent modifierFlags]),
1739 GetCharacters([aNativeEvent characters]),
1740 GetCharacters([aNativeEvent charactersIgnoringModifiers])));
1742 // Except when Command key is pressed, we should hide mouse cursor until
1743 // next mousemove. Handling here means that:
1744 // - Don't hide mouse cursor at pressing modifier key
1745 // - Hide mouse cursor even if the key event will be handled by IME (i.e.,
1746 // even without dispatching eKeyPress events)
1747 // - Hide mouse cursor even when a plugin has focus
1748 if (!([aNativeEvent modifierFlags] & NSEventModifierFlagCommand)) {
1749 [NSCursor setHiddenUntilMouseMoves:YES];
1752 RefPtr<nsChildView> widget(mWidget);
1754 KeyEventState* currentKeyEvent = PushKeyEvent(aNativeEvent, aUniqueId);
1755 AutoKeyEventStateCleaner remover(this);
1757 RefPtr<TextInputHandler> kungFuDeathGrip(this);
1759 // When we're already in a composition, we need always to mark the eKeyDown
1760 // event as "processed by IME". So, let's dispatch eKeyDown event here in
1762 if (IsIMEComposing() && !MaybeDispatchCurrentKeydownEvent(true)) {
1763 MOZ_LOG_KEY_OR_IME(LogLevel::Info,
1764 ("%p TextInputHandler::HandleKeyDownEvent, eKeyDown caused focus move or "
1765 "something and canceling the composition",
1770 // Let Cocoa interpret the key events, caching IsIMEComposing first.
1771 bool wasComposing = IsIMEComposing();
1772 bool interpretKeyEventsCalled = false;
1773 // Don't call interpretKeyEvents when a plugin has focus. If we call it,
1774 // for example, a character is inputted twice during a composition in e10s
1776 if (IsIMEEnabled() || IsASCIICapableOnly()) {
1779 ("%p TextInputHandler::HandleKeyDownEvent, calling interpretKeyEvents", this));
1780 [mView interpretKeyEvents:[NSArray arrayWithObject:aNativeEvent]];
1781 interpretKeyEventsCalled = true;
1784 ("%p TextInputHandler::HandleKeyDownEvent, called interpretKeyEvents", this));
1788 MOZ_LOG_KEY_OR_IME(LogLevel::Info,
1789 ("%p TextInputHandler::HandleKeyDownEvent, widget was destroyed", this));
1790 return currentKeyEvent->IsDefaultPrevented();
1793 MOZ_LOG_KEY_OR_IME(LogLevel::Info,
1794 ("%p TextInputHandler::HandleKeyDownEvent, wasComposing=%s, "
1795 "IsIMEComposing()=%s",
1796 this, TrueOrFalse(wasComposing), TrueOrFalse(IsIMEComposing())));
1798 if (currentKeyEvent->CanDispatchKeyDownEvent()) {
1799 // Dispatch eKeyDown event if nobody has dispatched it yet.
1800 // NOTE: Although reaching here means that the native keydown event may
1801 // not be handled by IME. However, we cannot know if it is.
1802 // For example, Japanese IME of Apple shows candidate window for
1803 // typing window. They, you can switch the sort order with Tab key.
1804 // However, when you choose "Symbol" of the sort order, there may
1805 // be no candiate words. In this case, IME handles the Tab key
1806 // actually, but we cannot know it because composition string is
1807 // not updated. So, let's mark eKeyDown event as "processed by IME"
1808 // when there is composition string. This is same as Chrome.
1809 MOZ_LOG_KEY_OR_IME(LogLevel::Info,
1810 ("%p TextInputHandler::HandleKeyDownEvent, trying to dispatch eKeyDown "
1811 "event since it's not yet dispatched",
1813 if (!MaybeDispatchCurrentKeydownEvent(IsIMEComposing())) {
1814 return true; // treat the eKeydDown event as consumed.
1816 MOZ_LOG_KEY_OR_IME(LogLevel::Info,
1817 ("%p TextInputHandler::HandleKeyDownEvent, eKeyDown event has been "
1822 if (currentKeyEvent->CanDispatchKeyPressEvent() && !wasComposing && !IsIMEComposing()) {
1823 nsresult rv = mDispatcher->BeginNativeInputTransaction();
1824 if (NS_WARN_IF(NS_FAILED(rv))) {
1825 MOZ_LOG_KEY_OR_IME(LogLevel::Error, ("%p TextInputHandler::HandleKeyDownEvent, "
1826 "FAILED, due to BeginNativeInputTransaction() failure "
1827 "at dispatching keypress",
1832 WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget);
1833 currentKeyEvent->InitKeyEvent(this, keypressEvent, false);
1835 // If we called interpretKeyEvents and this isn't normal character input
1836 // then IME probably ate the event for some reason. We do not want to
1837 // send a key press event in that case.
1839 // There are some other cases which IME eats the current event.
1840 // 1. If key events were nested during calling interpretKeyEvents, it means
1841 // that IME did something. Then, we should do nothing.
1842 // 2. If one or more commands are called like "deleteBackward", we should
1843 // dispatch keypress event at that time. Note that the command may have
1844 // been a converted or generated action by IME. Then, we shouldn't do
1845 // our default action for this key.
1846 if (!(interpretKeyEventsCalled && IsNormalCharInputtingEvent(aNativeEvent))) {
1847 MOZ_LOG_KEY_OR_IME(LogLevel::Info,
1848 ("%p TextInputHandler::HandleKeyDownEvent, trying to dispatch "
1849 "eKeyPress event since it's not yet dispatched",
1851 nsEventStatus status = nsEventStatus_eIgnore;
1852 currentKeyEvent->mKeyPressDispatched =
1853 mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status, currentKeyEvent);
1854 currentKeyEvent->mKeyPressHandled = (status == nsEventStatus_eConsumeNoDefault);
1855 currentKeyEvent->mKeyPressDispatched = true;
1856 MOZ_LOG_KEY_OR_IME(LogLevel::Info,
1857 ("%p TextInputHandler::HandleKeyDownEvent, eKeyPress event has been "
1863 // Note: mWidget might have become null here. Don't count on it from here on.
1865 MOZ_LOG_KEY_OR_IME(LogLevel::Info,
1866 ("%p TextInputHandler::HandleKeyDownEvent, "
1867 "keydown handled=%s, keypress handled=%s, causedOtherKeyEvents=%s, "
1868 "compositionDispatched=%s",
1869 this, TrueOrFalse(currentKeyEvent->mKeyDownHandled),
1870 TrueOrFalse(currentKeyEvent->mKeyPressHandled),
1871 TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents),
1872 TrueOrFalse(currentKeyEvent->mCompositionDispatched)));
1873 // Insert empty line to the log for easier to read.
1874 MOZ_LOG_KEY_OR_IME(LogLevel::Info, (""));
1875 return currentKeyEvent->IsDefaultPrevented();
1877 NS_OBJC_END_TRY_BLOCK_RETURN(false);
1880 void TextInputHandler::HandleKeyUpEvent(NSEvent* aNativeEvent) {
1881 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1885 ("%p TextInputHandler::HandleKeyUpEvent, aNativeEvent=%p, "
1886 "type=%s, keyCode=%u (0x%X), modifierFlags=0x%lX, characters=\"%s\", "
1887 "charactersIgnoringModifiers=\"%s\", "
1888 "IsIMEComposing()=%s",
1889 this, aNativeEvent, GetNativeKeyEventType(aNativeEvent), [aNativeEvent keyCode],
1890 [aNativeEvent keyCode], static_cast<unsigned long>([aNativeEvent modifierFlags]),
1891 GetCharacters([aNativeEvent characters]),
1892 GetCharacters([aNativeEvent charactersIgnoringModifiers]), TrueOrFalse(IsIMEComposing())));
1895 MOZ_LOG_KEY_OR_IME(LogLevel::Info, ("%p TextInputHandler::HandleKeyUpEvent, "
1896 "widget has been already destroyed",
1901 nsresult rv = mDispatcher->BeginNativeInputTransaction();
1902 if (NS_WARN_IF(NS_FAILED(rv))) {
1903 MOZ_LOG_KEY_OR_IME(LogLevel::Error, ("%p TextInputHandler::HandleKeyUpEvent, "
1904 "FAILED, due to BeginNativeInputTransaction() failure",
1909 // Neither Chrome for macOS nor Safari marks "keyup" event as "processed by
1910 // IME" even during composition. So, let's follow this behavior.
1911 WidgetKeyboardEvent keyupEvent(true, eKeyUp, mWidget);
1912 InitKeyEvent(aNativeEvent, keyupEvent, false);
1914 KeyEventState currentKeyEvent(aNativeEvent);
1915 nsEventStatus status = nsEventStatus_eIgnore;
1916 mDispatcher->DispatchKeyboardEvent(eKeyUp, keyupEvent, status, ¤tKeyEvent);
1918 NS_OBJC_END_TRY_IGNORE_BLOCK;
1921 void TextInputHandler::HandleFlagsChanged(NSEvent* aNativeEvent) {
1922 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1925 MOZ_LOG_KEY_OR_IME(LogLevel::Info, ("%p TextInputHandler::HandleFlagsChanged, "
1926 "widget has been already destroyed",
1931 RefPtr<nsChildView> kungFuDeathGrip(mWidget);
1932 mozilla::Unused << kungFuDeathGrip; // Not referenced within this function
1936 ("%p TextInputHandler::HandleFlagsChanged, aNativeEvent=%p, "
1937 "type=%s, keyCode=%s (0x%X), modifierFlags=0x%08lX, "
1938 "sLastModifierState=0x%08lX, IsIMEComposing()=%s",
1939 this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
1940 GetKeyNameForNativeKeyCode([aNativeEvent keyCode]), [aNativeEvent keyCode],
1941 static_cast<unsigned long>([aNativeEvent modifierFlags]),
1942 static_cast<unsigned long>(sLastModifierState), TrueOrFalse(IsIMEComposing())));
1944 MOZ_ASSERT([aNativeEvent type] == NSEventTypeFlagsChanged);
1946 NSUInteger diff = [aNativeEvent modifierFlags] ^ sLastModifierState;
1947 // Device dependent flags for left-control key, both shift keys, both command
1948 // keys and both option keys have been defined in Next's SDK. But we
1949 // shouldn't use it directly as far as possible since Cocoa SDK doesn't
1950 // define them. Fortunately, we need them only when we dispatch keyup
1951 // events. So, we can usually know the actual relation between keyCode and
1952 // device dependent flags. However, we need to remove following flags first
1953 // since the differences don't indicate modifier key state.
1954 // NX_STYLUSPROXIMITYMASK: Probably used for pen like device.
1955 // kCGEventFlagMaskNonCoalesced (= NX_NONCOALSESCEDMASK): See the document for
1956 // Quartz Event Services.
1957 diff &= ~(NX_STYLUSPROXIMITYMASK | kCGEventFlagMaskNonCoalesced);
1959 switch ([aNativeEvent keyCode]) {
1960 // CapsLock state and other modifier states are different:
1961 // CapsLock state does not revert when the CapsLock key goes up, as the
1962 // modifier state does for other modifier keys on key up.
1963 case kVK_CapsLock: {
1964 // Fire key down event for caps lock.
1965 DispatchKeyEventForFlagsChanged(aNativeEvent, true);
1966 // XXX should we fire keyup event too? The keyup event for CapsLock key
1967 // is never dispatched on Gecko.
1968 // XXX WebKit dispatches keydown event when CapsLock is locked, otherwise,
1969 // keyup event. If we do so, we cannot keep the consistency with other
1970 // platform's behavior...
1974 // If the event is caused by pressing or releasing a modifier key, just
1975 // dispatch the key's event.
1977 case kVK_RightShift:
1979 case kVK_RightCommand:
1981 case kVK_RightControl:
1983 case kVK_RightOption:
1985 // We assume that at most one modifier is changed per event if the event
1986 // is caused by pressing or releasing a modifier key.
1987 bool isKeyDown = ([aNativeEvent modifierFlags] & diff) != 0;
1988 DispatchKeyEventForFlagsChanged(aNativeEvent, isKeyDown);
1989 // XXX Some applications might send the event with incorrect device-
1991 if (isKeyDown && ((diff & ~NSEventModifierFlagDeviceIndependentFlagsMask) != 0)) {
1992 unsigned short keyCode = [aNativeEvent keyCode];
1993 const ModifierKey* modifierKey = GetModifierKeyForDeviceDependentFlags(diff);
1994 if (modifierKey && modifierKey->keyCode != keyCode) {
1995 // Although, we're not sure the actual cause of this case, the stored
1996 // modifier information and the latest key event information may be
1997 // mismatched. Then, let's reset the stored information.
1998 // NOTE: If this happens, it may fail to handle NSEventTypeFlagsChanged event
1999 // in the default case (below). However, it's the rare case handler
2000 // and this case occurs rarely. So, we can ignore the edge case bug.
2001 NS_WARNING("Resetting stored modifier key information");
2002 mModifierKeys.Clear();
2003 modifierKey = nullptr;
2006 mModifierKeys.AppendElement(ModifierKey(diff, keyCode));
2012 // Currently we don't support Fn key since other browsers don't dispatch
2013 // events for it and we don't have keyCode for this key.
2014 // It should be supported when we implement .key and .char.
2018 // If the event is caused by something else than pressing or releasing a
2019 // single modifier key (for example by the app having been deactivated
2020 // using command-tab), use the modifiers themselves to determine which
2021 // key's event to dispatch, and whether it's a keyup or keydown event.
2022 // In all cases we assume one or more modifiers are being deactivated
2023 // (never activated) -- otherwise we'd have received one or more events
2024 // corresponding to a single modifier key being pressed.
2026 NSUInteger modifiers = sLastModifierState;
2027 AutoTArray<unsigned short, 10> dispatchedKeyCodes;
2028 for (int32_t bit = 0; bit < 32; ++bit) {
2029 NSUInteger flag = 1 << bit;
2030 if (!(diff & flag)) {
2034 // Given correct information from the application, a flag change here
2035 // will normally be a deactivation (except for some lockable modifiers
2036 // such as CapsLock). But some applications (like VNC) can send an
2037 // activating event with a zero keyCode. So we need to check for that
2039 bool dispatchKeyDown = ((flag & [aNativeEvent modifierFlags]) != 0);
2041 unsigned short keyCode = 0;
2042 if (flag & NSEventModifierFlagDeviceIndependentFlagsMask) {
2044 case NSEventModifierFlagCapsLock:
2045 keyCode = kVK_CapsLock;
2046 dispatchKeyDown = true;
2049 case NSEventModifierFlagNumericPad:
2050 // NSEventModifierFlagNumericPad is fired by VNC a lot. But not all of
2051 // these events can really be Clear key events, so we just ignore
2055 case NSEventModifierFlagHelp:
2059 case NSEventModifierFlagFunction:
2060 // An NSEventModifierFlagFunction change here will normally be a
2061 // deactivation. But sometimes it will be an activation send (by
2062 // VNC for example) with a zero keyCode.
2065 // These cases (NSEventModifierFlagShift, NSEventModifierFlagControl,
2066 // NSEventModifierFlagOption and NSEventModifierFlagCommand) should be handled by the
2067 // other branch of the if statement, below (which handles device dependent flags).
2068 // However, some applications (like VNC) can send key events without
2069 // any device dependent flags, so we handle them here instead.
2070 case NSEventModifierFlagShift:
2071 keyCode = (modifiers & 0x0004) ? kVK_RightShift : kVK_Shift;
2073 case NSEventModifierFlagControl:
2074 keyCode = (modifiers & 0x2000) ? kVK_RightControl : kVK_Control;
2076 case NSEventModifierFlagOption:
2077 keyCode = (modifiers & 0x0040) ? kVK_RightOption : kVK_Option;
2079 case NSEventModifierFlagCommand:
2080 keyCode = (modifiers & 0x0010) ? kVK_RightCommand : kVK_Command;
2087 const ModifierKey* modifierKey = GetModifierKeyForDeviceDependentFlags(flag);
2089 // See the note above (in the other branch of the if statement)
2090 // about the NSEventModifierFlagShift, NSEventModifierFlagControl,
2091 // NSEventModifierFlagOption and NSEventModifierFlagCommand cases.
2094 keyCode = modifierKey->keyCode;
2101 const ModifierKey* modifierKey = GetModifierKeyForNativeKeyCode(kVK_RightShift);
2102 if (!modifierKey || !(modifiers & modifierKey->GetDeviceDependentFlags())) {
2103 modifiers &= ~NSEventModifierFlagShift;
2107 case kVK_RightShift: {
2108 const ModifierKey* modifierKey = GetModifierKeyForNativeKeyCode(kVK_Shift);
2109 if (!modifierKey || !(modifiers & modifierKey->GetDeviceDependentFlags())) {
2110 modifiers &= ~NSEventModifierFlagShift;
2115 const ModifierKey* modifierKey = GetModifierKeyForNativeKeyCode(kVK_RightCommand);
2116 if (!modifierKey || !(modifiers & modifierKey->GetDeviceDependentFlags())) {
2117 modifiers &= ~NSEventModifierFlagCommand;
2121 case kVK_RightCommand: {
2122 const ModifierKey* modifierKey = GetModifierKeyForNativeKeyCode(kVK_Command);
2123 if (!modifierKey || !(modifiers & modifierKey->GetDeviceDependentFlags())) {
2124 modifiers &= ~NSEventModifierFlagCommand;
2129 const ModifierKey* modifierKey = GetModifierKeyForNativeKeyCode(kVK_RightControl);
2130 if (!modifierKey || !(modifiers & modifierKey->GetDeviceDependentFlags())) {
2131 modifiers &= ~NSEventModifierFlagControl;
2135 case kVK_RightControl: {
2136 const ModifierKey* modifierKey = GetModifierKeyForNativeKeyCode(kVK_Control);
2137 if (!modifierKey || !(modifiers & modifierKey->GetDeviceDependentFlags())) {
2138 modifiers &= ~NSEventModifierFlagControl;
2143 const ModifierKey* modifierKey = GetModifierKeyForNativeKeyCode(kVK_RightOption);
2144 if (!modifierKey || !(modifiers & modifierKey->GetDeviceDependentFlags())) {
2145 modifiers &= ~NSEventModifierFlagOption;
2149 case kVK_RightOption: {
2150 const ModifierKey* modifierKey = GetModifierKeyForNativeKeyCode(kVK_Option);
2151 if (!modifierKey || !(modifiers & modifierKey->GetDeviceDependentFlags())) {
2152 modifiers &= ~NSEventModifierFlagOption;
2157 modifiers &= ~NSEventModifierFlagHelp;
2163 // Avoid dispatching same keydown/keyup events twice or more.
2164 // We must be able to assume that there is no case to dispatch
2165 // both keydown and keyup events with same key code value here.
2166 if (dispatchedKeyCodes.Contains(keyCode)) {
2169 dispatchedKeyCodes.AppendElement(keyCode);
2171 NSEvent* event = [NSEvent keyEventWithType:NSEventTypeFlagsChanged
2172 location:[aNativeEvent locationInWindow]
2173 modifierFlags:modifiers
2174 timestamp:[aNativeEvent timestamp]
2175 windowNumber:[aNativeEvent windowNumber]
2178 charactersIgnoringModifiers:@""
2181 DispatchKeyEventForFlagsChanged(event, dispatchKeyDown);
2186 // Stop if focus has changed.
2187 // Check to see if mView is still the first responder.
2188 if (![mView isFirstResponder]) {
2196 // Be aware, the widget may have been destroyed.
2197 sLastModifierState = [aNativeEvent modifierFlags];
2199 NS_OBJC_END_TRY_IGNORE_BLOCK;
2202 const TextInputHandler::ModifierKey* TextInputHandler::GetModifierKeyForNativeKeyCode(
2203 unsigned short aKeyCode) const {
2204 for (ModifierKeyArray::index_type i = 0; i < mModifierKeys.Length(); ++i) {
2205 if (mModifierKeys[i].keyCode == aKeyCode) {
2206 return &((ModifierKey&)mModifierKeys[i]);
2212 const TextInputHandler::ModifierKey* TextInputHandler::GetModifierKeyForDeviceDependentFlags(
2213 NSUInteger aFlags) const {
2214 for (ModifierKeyArray::index_type i = 0; i < mModifierKeys.Length(); ++i) {
2215 if (mModifierKeys[i].GetDeviceDependentFlags() ==
2216 (aFlags & ~NSEventModifierFlagDeviceIndependentFlagsMask)) {
2217 return &((ModifierKey&)mModifierKeys[i]);
2223 void TextInputHandler::DispatchKeyEventForFlagsChanged(NSEvent* aNativeEvent,
2224 bool aDispatchKeyDown) {
2225 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2231 MOZ_LOG_KEY_OR_IME(LogLevel::Info,
2232 ("%p TextInputHandler::DispatchKeyEventForFlagsChanged, aNativeEvent=%p, "
2233 "type=%s, keyCode=%s (0x%X), aDispatchKeyDown=%s, IsIMEComposing()=%s",
2234 this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
2235 GetKeyNameForNativeKeyCode([aNativeEvent keyCode]), [aNativeEvent keyCode],
2236 TrueOrFalse(aDispatchKeyDown), TrueOrFalse(IsIMEComposing())));
2238 if ([aNativeEvent type] != NSEventTypeFlagsChanged) {
2242 nsresult rv = mDispatcher->BeginNativeInputTransaction();
2243 if (NS_WARN_IF(NS_FAILED(rv))) {
2244 MOZ_LOG_KEY_OR_IME(LogLevel::Error, ("%p TextInputHandler::DispatchKeyEventForFlagsChanged, "
2245 "FAILED, due to BeginNativeInputTransaction() failure",
2250 EventMessage message = aDispatchKeyDown ? eKeyDown : eKeyUp;
2252 // Fire a key event for the modifier key. Note that even if modifier key
2253 // is pressed during composition, we shouldn't mark the keyboard event as
2254 // "processed by IME" since neither Chrome for macOS nor Safari does it.
2255 WidgetKeyboardEvent keyEvent(true, message, mWidget);
2256 InitKeyEvent(aNativeEvent, keyEvent, false);
2258 KeyEventState currentKeyEvent(aNativeEvent);
2259 nsEventStatus status = nsEventStatus_eIgnore;
2260 mDispatcher->DispatchKeyboardEvent(message, keyEvent, status, ¤tKeyEvent);
2262 NS_OBJC_END_TRY_IGNORE_BLOCK;
2265 void TextInputHandler::InsertText(NSAttributedString* aAttrString, NSRange* aReplacementRange) {
2266 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2272 KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
2276 ("%p TextInputHandler::InsertText, aAttrString=\"%s\", "
2277 "aReplacementRange=%p { location=%lu, length=%lu }, "
2278 "IsIMEComposing()=%s, "
2279 "keyevent=%p, keydownDispatched=%s, "
2280 "keydownHandled=%s, keypressDispatched=%s, "
2281 "causedOtherKeyEvents=%s, compositionDispatched=%s",
2282 this, GetCharacters([aAttrString string]), aReplacementRange,
2283 static_cast<unsigned long>(aReplacementRange ? aReplacementRange->location : 0),
2284 static_cast<unsigned long>(aReplacementRange ? aReplacementRange->length : 0),
2285 TrueOrFalse(IsIMEComposing()), currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr,
2286 currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyDownDispatched) : "N/A",
2287 currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
2288 currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A",
2289 currentKeyEvent ? TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A",
2290 currentKeyEvent ? TrueOrFalse(currentKeyEvent->mCompositionDispatched) : "N/A"));
2292 InputContext context = mWidget->GetInputContext();
2293 bool isEditable = (context.mIMEState.mEnabled == IMEEnabled::Enabled ||
2294 context.mIMEState.mEnabled == IMEEnabled::Password);
2295 NSRange selectedRange = SelectedRange();
2298 nsCocoaUtils::GetStringForNSString([aAttrString string], str);
2300 AutoInsertStringClearer clearer(currentKeyEvent);
2301 if (currentKeyEvent) {
2302 currentKeyEvent->mInsertString = &str;
2305 if (!IsIMEComposing() && str.IsEmpty()) {
2306 // nothing to do if there is no content which can be removed.
2310 // If replacement range is specified, we need to remove the range.
2311 // Otherwise, we need to remove the selected range if it's not collapsed.
2312 if (aReplacementRange && aReplacementRange->location != NSNotFound) {
2313 // nothing to do since the range is collapsed.
2314 if (aReplacementRange->length == 0) {
2317 // If the replacement range is different from current selected range,
2318 // select the range.
2319 if (!NSEqualRanges(selectedRange, *aReplacementRange)) {
2320 NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
2322 selectedRange = SelectedRange();
2324 NS_ENSURE_TRUE_VOID(selectedRange.location != NSNotFound);
2325 if (selectedRange.length == 0) {
2326 return; // nothing to do
2328 // If this is caused by a key input, the keypress event which will be
2329 // dispatched later should cause the delete. Therefore, nothing to do here.
2330 // Although, we're not sure if such case is actually possible.
2331 if (!currentKeyEvent) {
2335 // When current keydown event causes this empty text input, let's
2336 // dispatch eKeyDown event before any other events. Note that if we're
2337 // in a composition, we've already dispatched eKeyDown event from
2338 // TextInputHandler::HandleKeyDownEvent().
2339 // XXX Should we mark this eKeyDown event as "processed by IME"?
2340 RefPtr<TextInputHandler> kungFuDeathGrip(this);
2341 if (!IsIMEComposing() && !MaybeDispatchCurrentKeydownEvent(false)) {
2342 MOZ_LOG_KEY_OR_IME(LogLevel::Info,
2343 ("%p TextInputHandler::InsertText, eKeyDown caused focus move or "
2344 "something and canceling the composition",
2349 // Delete the selected range.
2350 WidgetContentCommandEvent deleteCommandEvent(true, eContentCommandDelete, mWidget);
2351 DispatchEvent(deleteCommandEvent);
2352 NS_ENSURE_TRUE_VOID(deleteCommandEvent.mSucceeded);
2353 // Be aware! The widget might be destroyed here.
2357 bool isReplacingSpecifiedRange = isEditable && aReplacementRange &&
2358 aReplacementRange->location != NSNotFound &&
2359 !NSEqualRanges(selectedRange, *aReplacementRange);
2361 // If this is not caused by pressing a key, there is a composition or
2362 // replacing a range which is different from current selection, let's
2363 // insert the text as committing a composition.
2364 // If InsertText() is called two or more times, we should insert all
2365 // text with composition events.
2366 // XXX When InsertText() is called multiple times, Chromium dispatches
2367 // only one composition event. So, we need to store InsertText()
2368 // calls and flush later.
2369 if (!currentKeyEvent || currentKeyEvent->mCompositionDispatched || IsIMEComposing() ||
2370 isReplacingSpecifiedRange) {
2371 InsertTextAsCommittingComposition(aAttrString, aReplacementRange);
2372 if (currentKeyEvent) {
2373 currentKeyEvent->mCompositionDispatched = true;
2378 // Don't let the same event be fired twice when hitting
2379 // enter/return for Bug 420502. However, Korean IME (or some other
2380 // simple IME) may work without marked text. For example, composing
2381 // character may be inserted as committed text and it's modified with
2382 // aReplacementRange. When a keydown starts new composition with
2383 // committing previous character, InsertText() may be called twice,
2384 // one is for committing previous character and then, inserting new
2385 // composing character as committed character. In the latter case,
2386 // |CanDispatchKeyPressEvent()| returns true but we need to dispatch
2387 // keypress event for the new character. So, when IME tries to insert
2388 // printable characters, we should ignore current key event state even
2389 // after the keydown has already caused dispatching composition event.
2390 // XXX Anyway, we should sort out around this at fixing bug 1338460.
2391 if (currentKeyEvent && !currentKeyEvent->CanDispatchKeyPressEvent() &&
2392 (str.IsEmpty() || (str.Length() == 1 && !IsPrintableChar(str[0])))) {
2396 // This is the normal path to input a character when you press a key.
2397 // Let's dispatch eKeyDown event now.
2398 RefPtr<TextInputHandler> kungFuDeathGrip(this);
2399 if (!MaybeDispatchCurrentKeydownEvent(false)) {
2400 MOZ_LOG_KEY_OR_IME(LogLevel::Info,
2401 ("%p TextInputHandler::InsertText, eKeyDown caused focus move or "
2402 "something and canceling the composition",
2407 // XXX Shouldn't we hold mDispatcher instead of mWidget?
2408 RefPtr<nsChildView> widget(mWidget);
2409 nsresult rv = mDispatcher->BeginNativeInputTransaction();
2410 if (NS_WARN_IF(NS_FAILED(rv))) {
2411 MOZ_LOG_KEY_OR_IME(LogLevel::Error, ("%p TextInputHandler::InsertText, "
2412 "FAILED, due to BeginNativeInputTransaction() failure",
2417 MOZ_LOG_KEY_OR_IME(LogLevel::Info,
2418 ("%p TextInputHandler::InsertText, "
2419 "maybe dispatches eKeyPress event without control, alt and meta modifiers",
2422 // Dispatch keypress event with char instead of compositionchange event
2423 WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget);
2424 // XXX Why do we need to dispatch keypress event for not inputting any
2425 // string? If it wants to delete the specified range, should we
2426 // dispatch an eContentCommandDelete event instead? Because this
2427 // must not be caused by a key operation, a part of IME's processing.
2429 // Don't set other modifiers from the current event, because here in
2430 // -insertText: they've already been taken into account in creating
2431 // the input string.
2433 if (currentKeyEvent) {
2434 currentKeyEvent->InitKeyEvent(this, keypressEvent, false);
2436 nsCocoaUtils::InitInputEvent(keypressEvent, static_cast<NSEvent*>(nullptr));
2437 keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
2438 keypressEvent.mKeyValue = str;
2439 // FYI: TextEventDispatcher will set mKeyCode to 0 for printable key's
2440 // keypress events even if they don't cause inputting non-empty string.
2444 // If mCurrentKeyEvent.mKeyEvent is null, the text should be inputted as
2445 // composition events.
2446 nsEventStatus status = nsEventStatus_eIgnore;
2447 bool keyPressDispatched =
2448 mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status, currentKeyEvent);
2449 bool keyPressHandled = (status == nsEventStatus_eConsumeNoDefault);
2451 // Note: mWidget might have become null here. Don't count on it from here on.
2453 if (currentKeyEvent) {
2454 currentKeyEvent->mKeyPressHandled = keyPressHandled;
2455 currentKeyEvent->mKeyPressDispatched = keyPressDispatched;
2458 NS_OBJC_END_TRY_IGNORE_BLOCK;
2461 bool TextInputHandler::HandleCommand(Command aCommand) {
2462 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2468 KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
2472 ("%p TextInputHandler::HandleCommand, "
2473 "aCommand=%s, IsIMEComposing()=%s, "
2474 "keyevent=%p, keydownHandled=%s, keypressDispatched=%s, "
2475 "causedOtherKeyEvents=%s, compositionDispatched=%s",
2476 this, ToChar(aCommand), TrueOrFalse(IsIMEComposing()),
2477 currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr,
2478 currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
2479 currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A",
2480 currentKeyEvent ? TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A",
2481 currentKeyEvent ? TrueOrFalse(currentKeyEvent->mCompositionDispatched) : "N/A"));
2483 // The command shouldn't be handled, let's ignore it.
2484 if (currentKeyEvent && !currentKeyEvent->CanHandleCommand()) {
2488 // When current keydown event causes this command, let's dispatch
2489 // eKeyDown event before any other events. Note that if we're in a
2490 // composition, we've already dispatched eKeyDown event from
2491 // TextInputHandler::HandleKeyDownEvent().
2492 RefPtr<TextInputHandler> kungFuDeathGrip(this);
2493 if (!IsIMEComposing() && !MaybeDispatchCurrentKeydownEvent(false)) {
2494 MOZ_LOG_KEY_OR_IME(LogLevel::Info,
2495 ("%p TextInputHandler::SetMarkedText, eKeyDown caused focus move or "
2496 "something and canceling the composition",
2501 // If it's in composition, we cannot dispatch keypress event.
2502 // Therefore, we should use different approach or give up to handle
2504 if (IsIMEComposing()) {
2506 case Command::InsertLineBreak:
2507 case Command::InsertParagraph: {
2508 // Insert '\n' as committing composition.
2509 // Otherwise, we need to dispatch keypress event because HTMLEditor
2510 // doesn't treat "\n" in composition string as a line break unless
2511 // the whitespace is treated as pre (see bug 1350541). In strictly
2512 // speaking, we should dispatch keypress event as-is if it's handling
2513 // NSEventTypeKeyDown event or should insert it with committing composition.
2514 NSAttributedString* lineBreaker = [[NSAttributedString alloc] initWithString:@"\n"];
2515 InsertTextAsCommittingComposition(lineBreaker, nullptr);
2516 if (currentKeyEvent) {
2517 currentKeyEvent->mCompositionDispatched = true;
2519 [lineBreaker release];
2522 case Command::DeleteCharBackward:
2523 case Command::DeleteCharForward:
2524 case Command::DeleteToBeginningOfLine:
2525 case Command::DeleteWordBackward:
2526 case Command::DeleteWordForward:
2527 // Don't remove any contents during composition.
2529 case Command::InsertTab:
2530 case Command::InsertBacktab:
2531 // Don't move focus during composition.
2533 case Command::CharNext:
2534 case Command::SelectCharNext:
2535 case Command::WordNext:
2536 case Command::SelectWordNext:
2537 case Command::EndLine:
2538 case Command::SelectEndLine:
2539 case Command::CharPrevious:
2540 case Command::SelectCharPrevious:
2541 case Command::WordPrevious:
2542 case Command::SelectWordPrevious:
2543 case Command::BeginLine:
2544 case Command::SelectBeginLine:
2545 case Command::LinePrevious:
2546 case Command::SelectLinePrevious:
2547 case Command::MoveTop:
2548 case Command::LineNext:
2549 case Command::SelectLineNext:
2550 case Command::MoveBottom:
2551 case Command::SelectBottom:
2552 case Command::SelectPageUp:
2553 case Command::SelectPageDown:
2554 case Command::ScrollBottom:
2555 case Command::ScrollTop:
2556 // Don't move selection during composition.
2558 case Command::CancelOperation:
2559 case Command::Complete:
2560 // Don't handle Escape key by ourselves during composition.
2562 case Command::ScrollPageUp:
2563 case Command::ScrollPageDown:
2571 RefPtr<nsChildView> widget(mWidget);
2572 nsresult rv = mDispatcher->BeginNativeInputTransaction();
2573 if (NS_WARN_IF(NS_FAILED(rv))) {
2574 MOZ_LOG_KEY_OR_IME(LogLevel::Error, ("%p, TextInputHandler::HandleCommand, "
2575 "FAILED, due to BeginNativeInputTransaction() failure",
2580 // TODO: If it's not appropriate keypress but user customized the OS
2581 // settings to do the command with other key, we should just set
2582 // command to the keypress event and it should be handled as
2583 // the key press in editor.
2585 // If it's handling actual key event and hasn't cause any composition
2586 // events nor other key events, we should expose actual modifier state.
2587 // Otherwise, we should adjust Control, Option and Command state since
2588 // editor may behave differently if some of them are active.
2589 bool dispatchFakeKeyPress = !(currentKeyEvent && currentKeyEvent->IsProperKeyEvent(aCommand));
2591 WidgetKeyboardEvent keydownEvent(true, eKeyDown, widget);
2592 WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget);
2593 if (!dispatchFakeKeyPress) {
2594 // If we're acutally handling a key press, we should dispatch
2595 // the keypress event as-is.
2596 currentKeyEvent->InitKeyEvent(this, keydownEvent, false);
2597 currentKeyEvent->InitKeyEvent(this, keypressEvent, false);
2599 // Otherwise, we should dispatch "fake" keypress event.
2600 // However, for making it possible to compute edit commands, we need to
2601 // set current native key event to the fake keyboard event even if it's
2602 // not same as what we expect since the native keyboard event caused
2604 NSEvent* keyEvent = currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr;
2605 keydownEvent.mNativeKeyEvent = keypressEvent.mNativeKeyEvent = keyEvent;
2606 NS_WARNING_ASSERTION(keypressEvent.mNativeKeyEvent,
2607 "Without native key event, NativeKeyBindings cannot compute aCommand");
2609 case Command::InsertLineBreak:
2610 case Command::InsertParagraph: {
2611 // Although, Shift+Enter and Enter are work differently in HTML
2612 // editor, we should expose actual Shift state if it's caused by
2613 // Enter key for compatibility with Chromium. Chromium breaks
2614 // line in HTML editor with default pargraph separator when Enter
2615 // is pressed, with <br> element when Shift+Enter. Safari breaks
2616 // line in HTML editor with default paragraph separator when
2617 // Enter, Shift+Enter or Option+Enter. So, we should not change
2618 // Shift+Enter meaning when there was composition string or not.
2619 nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
2620 keypressEvent.mKeyCode = NS_VK_RETURN;
2621 keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_Enter;
2622 keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
2623 if (aCommand == Command::InsertLineBreak) {
2624 // In default settings, Ctrl + Enter causes insertLineBreak command.
2625 // So, let's make Ctrl state active of the keypress event.
2626 keypressEvent.mModifiers |= MODIFIER_CONTROL;
2630 case Command::InsertTab:
2631 case Command::InsertBacktab:
2632 nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
2633 keypressEvent.mKeyCode = NS_VK_TAB;
2634 keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_Tab;
2635 keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
2636 if (aCommand == Command::InsertBacktab) {
2637 keypressEvent.mModifiers |= MODIFIER_SHIFT;
2640 case Command::DeleteCharBackward:
2641 case Command::DeleteToBeginningOfLine:
2642 case Command::DeleteWordBackward: {
2643 nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
2644 keypressEvent.mKeyCode = NS_VK_BACK;
2645 keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_Backspace;
2646 keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
2647 if (aCommand == Command::DeleteToBeginningOfLine) {
2648 keypressEvent.mModifiers |= MODIFIER_META;
2649 } else if (aCommand == Command::DeleteWordBackward) {
2650 keypressEvent.mModifiers |= MODIFIER_ALT;
2654 case Command::DeleteCharForward:
2655 case Command::DeleteWordForward: {
2656 nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
2657 keypressEvent.mKeyCode = NS_VK_DELETE;
2658 keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_Delete;
2659 keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
2660 if (aCommand == Command::DeleteWordForward) {
2661 keypressEvent.mModifiers |= MODIFIER_ALT;
2665 case Command::CharNext:
2666 case Command::SelectCharNext:
2667 case Command::WordNext:
2668 case Command::SelectWordNext:
2669 case Command::EndLine:
2670 case Command::SelectEndLine: {
2671 nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
2672 keypressEvent.mKeyCode = NS_VK_RIGHT;
2673 keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_ArrowRight;
2674 keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
2675 if (aCommand == Command::SelectCharNext || aCommand == Command::SelectWordNext ||
2676 aCommand == Command::SelectEndLine) {
2677 keypressEvent.mModifiers |= MODIFIER_SHIFT;
2679 if (aCommand == Command::WordNext || aCommand == Command::SelectWordNext) {
2680 keypressEvent.mModifiers |= MODIFIER_ALT;
2682 if (aCommand == Command::EndLine || aCommand == Command::SelectEndLine) {
2683 keypressEvent.mModifiers |= MODIFIER_META;
2687 case Command::CharPrevious:
2688 case Command::SelectCharPrevious:
2689 case Command::WordPrevious:
2690 case Command::SelectWordPrevious:
2691 case Command::BeginLine:
2692 case Command::SelectBeginLine: {
2693 nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
2694 keypressEvent.mKeyCode = NS_VK_LEFT;
2695 keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_ArrowLeft;
2696 keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
2697 if (aCommand == Command::SelectCharPrevious || aCommand == Command::SelectWordPrevious ||
2698 aCommand == Command::SelectBeginLine) {
2699 keypressEvent.mModifiers |= MODIFIER_SHIFT;
2701 if (aCommand == Command::WordPrevious || aCommand == Command::SelectWordPrevious) {
2702 keypressEvent.mModifiers |= MODIFIER_ALT;
2704 if (aCommand == Command::BeginLine || aCommand == Command::SelectBeginLine) {
2705 keypressEvent.mModifiers |= MODIFIER_META;
2709 case Command::LinePrevious:
2710 case Command::SelectLinePrevious:
2711 case Command::MoveTop:
2712 case Command::SelectTop: {
2713 nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
2714 keypressEvent.mKeyCode = NS_VK_UP;
2715 keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_ArrowUp;
2716 keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
2717 if (aCommand == Command::SelectLinePrevious || aCommand == Command::SelectTop) {
2718 keypressEvent.mModifiers |= MODIFIER_SHIFT;
2720 if (aCommand == Command::MoveTop || aCommand == Command::SelectTop) {
2721 keypressEvent.mModifiers |= MODIFIER_META;
2725 case Command::LineNext:
2726 case Command::SelectLineNext:
2727 case Command::MoveBottom:
2728 case Command::SelectBottom: {
2729 nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
2730 keypressEvent.mKeyCode = NS_VK_DOWN;
2731 keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_ArrowDown;
2732 keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
2733 if (aCommand == Command::SelectLineNext || aCommand == Command::SelectBottom) {
2734 keypressEvent.mModifiers |= MODIFIER_SHIFT;
2736 if (aCommand == Command::MoveBottom || aCommand == Command::SelectBottom) {
2737 keypressEvent.mModifiers |= MODIFIER_META;
2741 case Command::ScrollPageUp:
2742 case Command::SelectPageUp: {
2743 nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
2744 keypressEvent.mKeyCode = NS_VK_PAGE_UP;
2745 keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_PageUp;
2746 keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
2747 if (aCommand == Command::SelectPageUp) {
2748 keypressEvent.mModifiers |= MODIFIER_SHIFT;
2752 case Command::ScrollPageDown:
2753 case Command::SelectPageDown: {
2754 nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
2755 keypressEvent.mKeyCode = NS_VK_PAGE_DOWN;
2756 keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_PageDown;
2757 keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
2758 if (aCommand == Command::SelectPageDown) {
2759 keypressEvent.mModifiers |= MODIFIER_SHIFT;
2763 case Command::ScrollBottom:
2764 case Command::ScrollTop: {
2765 nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
2766 if (aCommand == Command::ScrollBottom) {
2767 keypressEvent.mKeyCode = NS_VK_END;
2768 keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_End;
2770 keypressEvent.mKeyCode = NS_VK_HOME;
2771 keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_Home;
2773 keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
2776 case Command::CancelOperation:
2777 case Command::Complete: {
2778 nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
2779 keypressEvent.mKeyCode = NS_VK_ESCAPE;
2780 keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_Escape;
2781 keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
2782 if (aCommand == Command::Complete) {
2783 keypressEvent.mModifiers |= MODIFIER_ALT;
2791 nsCocoaUtils::InitInputEvent(keydownEvent, keyEvent);
2792 keydownEvent.mKeyCode = keypressEvent.mKeyCode;
2793 keydownEvent.mKeyNameIndex = keypressEvent.mKeyNameIndex;
2794 keydownEvent.mModifiers = keypressEvent.mModifiers;
2797 // We've stopped dispatching "keypress" events of non-printable keys on
2798 // the web. Therefore, we need to dispatch eKeyDown event here for web
2799 // apps. This is non-standard behavior if we've already dispatched a
2800 // "keydown" event. However, Chrome also dispatches such fake "keydown"
2801 // (and "keypress") event for making same behavior as Safari.
2802 nsEventStatus status = nsEventStatus_eIgnore;
2803 if (mDispatcher->DispatchKeyboardEvent(eKeyDown, keydownEvent, status, nullptr)) {
2804 bool keydownHandled = status == nsEventStatus_eConsumeNoDefault;
2805 if (currentKeyEvent) {
2806 currentKeyEvent->mKeyDownDispatched = true;
2807 currentKeyEvent->mKeyDownHandled |= keydownHandled;
2809 if (keydownHandled) {
2810 // Don't dispatch eKeyPress event if preceding eKeyDown event is
2811 // consumed for conforming to UI Events.
2812 // XXX Perhaps, we should ignore previous eKeyDown event result
2813 // even if we've already dispatched because it may notify web apps
2814 // of different key information, e.g., it's handled by IME, but
2815 // web apps want to handle only this key.
2820 bool keyPressDispatched =
2821 mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status, currentKeyEvent);
2822 bool keyPressHandled = (status == nsEventStatus_eConsumeNoDefault);
2824 // NOTE: mWidget might have become null here.
2826 if (keyPressDispatched) {
2827 // Record the keypress event state only when it dispatched actual Enter
2828 // keypress event because in other cases, the keypress event just a
2829 // messenger. E.g., if it's caused by different key, keypress event for
2830 // the actual key should be dispatched.
2831 if (!dispatchFakeKeyPress && currentKeyEvent) {
2832 currentKeyEvent->mKeyPressHandled = keyPressHandled;
2833 currentKeyEvent->mKeyPressDispatched = keyPressDispatched;
2838 // If keypress event isn't dispatched as expected, we should fallback to
2839 // using composition events.
2840 if (aCommand == Command::InsertLineBreak || aCommand == Command::InsertParagraph) {
2841 NSAttributedString* lineBreaker = [[NSAttributedString alloc] initWithString:@"\n"];
2842 InsertTextAsCommittingComposition(lineBreaker, nullptr);
2843 if (currentKeyEvent) {
2844 currentKeyEvent->mCompositionDispatched = true;
2846 [lineBreaker release];
2852 NS_OBJC_END_TRY_BLOCK_RETURN(false);
2855 bool TextInputHandler::DoCommandBySelector(const char* aSelector) {
2856 RefPtr<nsChildView> widget(mWidget);
2858 KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
2862 ("%p TextInputHandler::DoCommandBySelector, aSelector=\"%s\", "
2863 "Destroyed()=%s, keydownDispatched=%s, keydownHandled=%s, "
2864 "keypressDispatched=%s, keypressHandled=%s, causedOtherKeyEvents=%s",
2865 this, aSelector ? aSelector : "", TrueOrFalse(Destroyed()),
2866 currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyDownDispatched) : "N/A",
2867 currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
2868 currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A",
2869 currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyPressHandled) : "N/A",
2870 currentKeyEvent ? TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A"));
2872 // If the command isn't caused by key operation, the command should
2873 // be handled in the super class of the caller.
2874 if (!currentKeyEvent) {
2878 // When current keydown event causes this command, let's dispatch
2879 // eKeyDown event before any other events. Note that if we're in a
2880 // composition, we've already dispatched eKeyDown event from
2881 // TextInputHandler::HandleKeyDownEvent().
2882 RefPtr<TextInputHandler> kungFuDeathGrip(this);
2883 if (!IsIMEComposing() && !MaybeDispatchCurrentKeydownEvent(false)) {
2884 MOZ_LOG_KEY_OR_IME(LogLevel::Info,
2885 ("%p TextInputHandler::SetMarkedText, eKeyDown caused focus move or "
2886 "something and canceling the composition",
2891 // If the key operation causes this command, should dispatch a keypress
2893 // XXX This must be worng. Even if this command is caused by the key
2894 // operation, its our default action can be different from the
2895 // command. So, in this case, we should dispatch a keypress event
2896 // which have the command and editor should handle it.
2897 if (currentKeyEvent->CanDispatchKeyPressEvent()) {
2898 nsresult rv = mDispatcher->BeginNativeInputTransaction();
2899 if (NS_WARN_IF(NS_FAILED(rv))) {
2900 MOZ_LOG_KEY_OR_IME(LogLevel::Error, ("%p TextInputHandler::DoCommandBySelector, "
2901 "FAILED, due to BeginNativeInputTransaction() failure "
2902 "at dispatching keypress",
2907 WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget);
2908 currentKeyEvent->InitKeyEvent(this, keypressEvent, false);
2910 nsEventStatus status = nsEventStatus_eIgnore;
2911 currentKeyEvent->mKeyPressDispatched =
2912 mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status, currentKeyEvent);
2913 currentKeyEvent->mKeyPressHandled = (status == nsEventStatus_eConsumeNoDefault);
2916 ("%p TextInputHandler::DoCommandBySelector, keypress event "
2917 "dispatched, Destroyed()=%s, keypressHandled=%s",
2918 this, TrueOrFalse(Destroyed()), TrueOrFalse(currentKeyEvent->mKeyPressHandled)));
2919 // This command is now dispatched with keypress event.
2920 // So, this shouldn't be handled by nobody anymore.
2924 // If the key operation didn't cause keypress event or caused keypress event
2925 // but not prevented its default, we need to honor the command. For example,
2926 // Korean IME sends "insertNewline:" when committing existing composition
2927 // with Enter key press. In such case, the key operation has been consumed
2928 // by the committing composition but we still need to handle the command.
2929 if (Destroyed() || !currentKeyEvent->CanHandleCommand()) {
2933 // cancelOperation: command is fired after Escape or Command + Period.
2934 // However, if ChildView implements cancelOperation:, calling
2935 // [[ChildView super] doCommandBySelector:aSelector] when Command + Period
2936 // causes only a call of [ChildView cancelOperation:sender]. I.e.,
2937 // [ChildView keyDown:theEvent] becomes to be never called. For avoiding
2938 // this odd behavior, we need to handle the command before super class of
2939 // ChildView only when current key event is proper event to fire Escape
2941 if (!strcmp(aSelector, "cancelOperation:") && currentKeyEvent &&
2942 currentKeyEvent->IsProperKeyEvent(Command::CancelOperation)) {
2943 return HandleCommand(Command::CancelOperation);
2946 // Otherwise, we've not handled the command yet. Propagate the command
2947 // to the super class of ChildView.
2953 /******************************************************************************
2955 * IMEInputHandler implementation (static methods)
2957 ******************************************************************************/
2959 bool IMEInputHandler::sStaticMembersInitialized = false;
2960 bool IMEInputHandler::sCachedIsForRTLLangage = false;
2961 CFStringRef IMEInputHandler::sLatestIMEOpenedModeInputSourceID = nullptr;
2962 IMEInputHandler* IMEInputHandler::sFocusedIMEHandler = nullptr;
2965 void IMEInputHandler::InitStaticMembers() {
2966 if (sStaticMembersInitialized) return;
2967 sStaticMembersInitialized = true;
2968 // We need to check the keyboard layout changes on all applications.
2969 CFNotificationCenterRef center = ::CFNotificationCenterGetDistributedCenter();
2970 // XXX Don't we need to remove the observer at shut down?
2971 // Mac Dev Center's document doesn't say how to remove the observer if
2972 // the second parameter is NULL.
2973 ::CFNotificationCenterAddObserver(center, NULL, OnCurrentTextInputSourceChange,
2974 kTISNotifySelectedKeyboardInputSourceChanged, NULL,
2975 CFNotificationSuspensionBehaviorDeliverImmediately);
2976 // Initiailize with the current keyboard layout
2977 OnCurrentTextInputSourceChange(NULL, NULL, kTISNotifySelectedKeyboardInputSourceChanged, NULL,
2982 void IMEInputHandler::OnCurrentTextInputSourceChange(CFNotificationCenterRef aCenter,
2983 void* aObserver, CFStringRef aName,
2984 const void* aObject,
2985 CFDictionaryRef aUserInfo) {
2986 // Cache the latest IME opened mode to sLatestIMEOpenedModeInputSourceID.
2987 TISInputSourceWrapper tis;
2988 tis.InitByCurrentInputSource();
2989 if (tis.IsOpenedIMEMode()) {
2990 tis.GetInputSourceID(sLatestIMEOpenedModeInputSourceID);
2991 // Collect Input Source ID which includes input mode in most cases.
2992 // However, if it's Japanese IME, collecting input mode (e.g.,
2993 // "HiraganaKotei") does not make sense because in most languages,
2994 // input mode changes "how to input", but Japanese IME changes
2995 // "which type of characters to input". I.e., only Japanese IME
2996 // users may use multiple input modes. If we'd collect each type of
2997 // input mode of Japanese IMEs, it'd be difficult to count actual
2998 // users of each IME from the result. So, only when active IME is
2999 // a Japanese IME, we should use Bundle ID which does not contain
3000 // input mode instead.
3002 if (tis.IsForJapaneseLanguage()) {
3003 tis.GetBundleID(key);
3005 tis.GetInputSourceID(key);
3007 // 72 is kMaximumKeyStringLength in TelemetryScalar.cpp
3008 if (key.Length() > 72) {
3009 if (NS_IS_SURROGATE_PAIR(key[72 - 2], key[72 - 1])) {
3010 key.Truncate(72 - 2);
3012 key.Truncate(72 - 1);
3015 key.Append(char16_t(0x2026));
3017 Telemetry::ScalarSet(Telemetry::ScalarID::WIDGET_IME_NAME_ON_MAC, key, true);
3020 if (MOZ_LOG_TEST(gIMELog, LogLevel::Info)) {
3021 static CFStringRef sLastTIS = nullptr;
3023 tis.GetInputSourceID(newTIS);
3024 if (!sLastTIS || ::CFStringCompare(sLastTIS, newTIS, 0) != kCFCompareEqualTo) {
3025 TISInputSourceWrapper tis1, tis2, tis3, tis4, tis5;
3026 tis1.InitByCurrentKeyboardLayout();
3027 tis2.InitByCurrentASCIICapableInputSource();
3028 tis3.InitByCurrentASCIICapableKeyboardLayout();
3029 tis4.InitByCurrentInputMethodKeyboardLayoutOverride();
3030 tis5.InitByTISInputSourceRef(tis.GetKeyboardLayoutInputSource());
3031 CFStringRef is0 = nullptr, is1 = nullptr, is2 = nullptr, is3 = nullptr, is4 = nullptr,
3032 is5 = nullptr, type0 = nullptr, lang0 = nullptr, bundleID0 = nullptr;
3033 tis.GetInputSourceID(is0);
3034 tis1.GetInputSourceID(is1);
3035 tis2.GetInputSourceID(is2);
3036 tis3.GetInputSourceID(is3);
3037 tis4.GetInputSourceID(is4);
3038 tis5.GetInputSourceID(is5);
3039 tis.GetInputSourceType(type0);
3040 tis.GetPrimaryLanguage(lang0);
3041 tis.GetBundleID(bundleID0);
3043 MOZ_LOG(gIMELog, LogLevel::Info,
3044 ("IMEInputHandler::OnCurrentTextInputSourceChange,\n"
3045 " Current Input Source is changed to:\n"
3046 " currentInputContext=%p\n"
3049 " overridden keyboard layout=%s\n"
3050 " used keyboard layout for translation=%s\n"
3051 " primary language=%s\n"
3053 " current ASCII capable Input Source=%s\n"
3054 " current Keyboard Layout=%s\n"
3055 " current ASCII capable Keyboard Layout=%s",
3056 [NSTextInputContext currentInputContext], GetCharacters(is0), GetCharacters(type0),
3057 tis.IsASCIICapable() ? "- ASCII capable " : "", GetCharacters(is4),
3058 GetCharacters(is5), GetCharacters(lang0), GetCharacters(bundleID0),
3059 GetCharacters(is2), GetCharacters(is1), GetCharacters(is3)));
3065 * When the direction is changed, all the children are notified.
3066 * No need to treat the initial case separately because it is covered
3067 * by the general case (sCachedIsForRTLLangage is initially false)
3069 if (sCachedIsForRTLLangage != tis.IsForRTLLanguage()) {
3070 WidgetUtils::SendBidiKeyboardInfoToContent();
3071 sCachedIsForRTLLangage = tis.IsForRTLLanguage();
3076 void IMEInputHandler::FlushPendingMethods(nsITimer* aTimer, void* aClosure) {
3077 NS_ASSERTION(aClosure, "aClosure is null");
3078 static_cast<IMEInputHandler*>(aClosure)->ExecutePendingMethods();
3082 CFArrayRef IMEInputHandler::CreateAllIMEModeList() {
3083 const void* keys[] = {kTISPropertyInputSourceType};
3084 const void* values[] = {kTISTypeKeyboardInputMode};
3085 CFDictionaryRef filter = ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
3086 NS_ASSERTION(filter, "failed to create the filter");
3087 CFArrayRef list = ::TISCreateInputSourceList(filter, true);
3088 ::CFRelease(filter);
3093 void IMEInputHandler::DebugPrintAllIMEModes() {
3094 if (MOZ_LOG_TEST(gIMELog, LogLevel::Info)) {
3095 CFArrayRef list = CreateAllIMEModeList();
3096 MOZ_LOG(gIMELog, LogLevel::Info, ("IME mode configuration:"));
3097 CFIndex idx = ::CFArrayGetCount(list);
3098 TISInputSourceWrapper tis;
3099 for (CFIndex i = 0; i < idx; ++i) {
3100 TISInputSourceRef inputSource =
3101 static_cast<TISInputSourceRef>(const_cast<void*>(::CFArrayGetValueAtIndex(list, i)));
3102 tis.InitByTISInputSourceRef(inputSource);
3103 nsAutoString name, isid, bundleID;
3104 tis.GetLocalizedName(name);
3105 tis.GetInputSourceID(isid);
3106 tis.GetBundleID(bundleID);
3107 MOZ_LOG(gIMELog, LogLevel::Info,
3109 " bundled in <%s>\n",
3110 NS_ConvertUTF16toUTF8(name).get(), NS_ConvertUTF16toUTF8(isid).get(),
3111 tis.IsASCIICapable() ? "" : "\t(Isn't ASCII capable)",
3112 tis.IsEnabled() ? "" : "\t(Isn't Enabled)", NS_ConvertUTF16toUTF8(bundleID).get()));
3119 TSMDocumentID IMEInputHandler::GetCurrentTSMDocumentID() {
3120 // At least on Mac OS X 10.6.x and 10.7.x, ::TSMGetActiveDocument() has a bug.
3121 // The result of ::TSMGetActiveDocument() isn't modified for new active text
3122 // input context until [NSTextInputContext currentInputContext] is called.
3123 // Therefore, we need to call it here.
3124 [NSTextInputContext currentInputContext];
3125 return ::TSMGetActiveDocument();
3130 /******************************************************************************
3132 * IMEInputHandler implementation #1
3133 * The methods are releated to the pending methods. Some jobs should be
3134 * run after the stack is finished, e.g, some methods cannot run the jobs
3135 * during processing the focus event. And also some other jobs should be
3136 * run at the next focus event is processed.
3137 * The pending methods are recorded in mPendingMethods. They are executed
3138 * by ExecutePendingMethods via FlushPendingMethods.
3140 ******************************************************************************/
3142 nsresult IMEInputHandler::NotifyIME(TextEventDispatcher* aTextEventDispatcher,
3143 const IMENotification& aNotification) {
3144 switch (aNotification.mMessage) {
3145 case REQUEST_TO_COMMIT_COMPOSITION:
3146 CommitIMEComposition();
3148 case REQUEST_TO_CANCEL_COMPOSITION:
3149 CancelIMEComposition();
3151 case NOTIFY_IME_OF_FOCUS:
3153 nsIWidget* widget = aTextEventDispatcher->GetWidget();
3154 if (widget && widget->GetInputContext().IsPasswordEditor()) {
3155 EnableSecureEventInput();
3157 EnsureSecureEventInputDisabled();
3160 OnFocusChangeInGecko(true);
3162 case NOTIFY_IME_OF_BLUR:
3163 OnFocusChangeInGecko(false);
3165 case NOTIFY_IME_OF_SELECTION_CHANGE:
3166 OnSelectionChange(aNotification);
3168 case NOTIFY_IME_OF_POSITION_CHANGE:
3172 return NS_ERROR_NOT_IMPLEMENTED;
3176 NS_IMETHODIMP_(IMENotificationRequests)
3177 IMEInputHandler::GetIMENotificationRequests() {
3178 // XXX Shouldn't we move floating window which shows composition string
3179 // when plugin has focus and its parent is scrolled or the window is
3181 return IMENotificationRequests();
3184 NS_IMETHODIMP_(void)
3185 IMEInputHandler::OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) {
3186 // XXX When input transaction is being stolen by add-on, what should we do?
3189 NS_IMETHODIMP_(void)
3190 IMEInputHandler::WillDispatchKeyboardEvent(TextEventDispatcher* aTextEventDispatcher,
3191 WidgetKeyboardEvent& aKeyboardEvent,
3192 uint32_t aIndexOfKeypress, void* aData) {
3193 // If the keyboard event is not caused by a native key event, we can do
3199 KeyEventState* currentKeyEvent = static_cast<KeyEventState*>(aData);
3200 NSEvent* nativeEvent = currentKeyEvent->mKeyEvent;
3201 nsAString* insertString = currentKeyEvent->mInsertString;
3202 if (aKeyboardEvent.mMessage == eKeyPress && aIndexOfKeypress == 0 &&
3203 (!insertString || insertString->IsEmpty())) {
3204 // Inform the child process that this is an event that we want a reply
3206 // XXX This should be called only when the target is a remote process.
3207 // However, it's difficult to check it under widget/.
3208 // So, let's do this here for now, then,
3209 // EventStateManager::PreHandleEvent() will reset the flags if
3210 // the event target isn't in remote process.
3211 aKeyboardEvent.MarkAsWaitingReplyFromRemoteProcess();
3213 if (KeyboardLayoutOverrideRef().mOverrideEnabled) {
3214 TISInputSourceWrapper tis;
3215 tis.InitByLayoutID(KeyboardLayoutOverrideRef().mKeyboardLayout, true);
3216 tis.WillDispatchKeyboardEvent(nativeEvent, insertString, aIndexOfKeypress, aKeyboardEvent);
3218 TISInputSourceWrapper::CurrentInputSource().WillDispatchKeyboardEvent(
3219 nativeEvent, insertString, aIndexOfKeypress, aKeyboardEvent);
3222 // Remove basic modifiers from keypress event because if they are included
3223 // but this causes inputting text, since TextEditor won't handle eKeyPress
3224 // events whose ctrlKey, altKey or metaKey is true as text input.
3225 // Note that this hack should be used only when an editor has focus because
3226 // this is a hack for TextEditor and modifier key information may be
3227 // important for current web app.
3228 if (IsEditableContent() && insertString && aKeyboardEvent.mMessage == eKeyPress &&
3229 aKeyboardEvent.mCharCode) {
3230 aKeyboardEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
3234 void IMEInputHandler::NotifyIMEOfFocusChangeInGecko() {
3235 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3237 MOZ_LOG(gIMELog, LogLevel::Info,
3238 ("%p IMEInputHandler::NotifyIMEOfFocusChangeInGecko, "
3239 "Destroyed()=%s, IsFocused()=%s, inputContext=%p",
3240 this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
3241 mView ? [mView inputContext] : nullptr));
3248 // retry at next focus event
3249 mPendingMethods |= kNotifyIMEOfFocusChangeInGecko;
3254 NSTextInputContext* inputContext = [mView inputContext];
3255 NS_ENSURE_TRUE_VOID(inputContext);
3257 // When an <input> element on a XUL <panel> element gets focus from an <input>
3258 // element on the opener window of the <panel> element, the owner window
3259 // still has native focus. Therefore, IMEs may store the opener window's
3260 // level at this time because they don't know the actual focus is moved to
3261 // different window. If IMEs try to get the newest window level after the
3262 // focus change, we return the window level of the XUL <panel>'s widget.
3263 // Therefore, let's emulate the native focus change. Then, IMEs can refresh
3264 // the stored window level.
3265 [inputContext deactivate];
3266 [inputContext activate];
3268 NS_OBJC_END_TRY_IGNORE_BLOCK;
3271 void IMEInputHandler::SyncASCIICapableOnly() {
3272 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3274 MOZ_LOG(gIMELog, LogLevel::Info,
3275 ("%p IMEInputHandler::SyncASCIICapableOnly, "
3276 "Destroyed()=%s, IsFocused()=%s, mIsASCIICapableOnly=%s, "
3277 "GetCurrentTSMDocumentID()=%p",
3278 this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
3279 TrueOrFalse(mIsASCIICapableOnly), GetCurrentTSMDocumentID()));
3286 // retry at next focus event
3287 mPendingMethods |= kSyncASCIICapableOnly;
3291 TSMDocumentID doc = GetCurrentTSMDocumentID();
3294 mPendingMethods |= kSyncASCIICapableOnly;
3295 NS_WARNING("Application is active but there is no active document");
3300 if (mIsASCIICapableOnly) {
3301 CFArrayRef ASCIICapableTISList = ::TISCreateASCIICapableInputSourceList();
3302 ::TSMSetDocumentProperty(doc, kTSMDocumentEnabledInputSourcesPropertyTag, sizeof(CFArrayRef),
3303 &ASCIICapableTISList);
3304 ::CFRelease(ASCIICapableTISList);
3306 ::TSMRemoveDocumentProperty(doc, kTSMDocumentEnabledInputSourcesPropertyTag);
3309 NS_OBJC_END_TRY_IGNORE_BLOCK;
3312 void IMEInputHandler::ResetTimer() {
3313 NS_ASSERTION(mPendingMethods != 0, "There are not pending methods, why this is called?");
3317 mTimer = NS_NewTimer();
3318 NS_ENSURE_TRUE(mTimer, );
3320 mTimer->InitWithNamedFuncCallback(FlushPendingMethods, this, 0, nsITimer::TYPE_ONE_SHOT,
3321 "IMEInputHandler::FlushPendingMethods");
3324 void IMEInputHandler::ExecutePendingMethods() {
3325 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3332 if (![[NSApplication sharedApplication] isActive]) {
3333 // If we're not active, we should retry at focus event
3337 uint32_t pendingMethods = mPendingMethods;
3338 // First, reset the pending method flags because if each methods cannot
3339 // run now, they can reentry to the pending flags by theirselves.
3340 mPendingMethods = 0;
3342 if (pendingMethods & kSyncASCIICapableOnly) SyncASCIICapableOnly();
3343 if (pendingMethods & kNotifyIMEOfFocusChangeInGecko) {
3344 NotifyIMEOfFocusChangeInGecko();
3347 NS_OBJC_END_TRY_IGNORE_BLOCK;
3352 /******************************************************************************
3354 * IMEInputHandler implementation (native event handlers)
3356 ******************************************************************************/
3358 TextRangeType IMEInputHandler::ConvertToTextRangeType(uint32_t aUnderlineStyle,
3359 NSRange& aSelectedRange) {
3360 MOZ_LOG(gIMELog, LogLevel::Info,
3361 ("%p IMEInputHandler::ConvertToTextRangeType, "
3362 "aUnderlineStyle=%u, aSelectedRange.length=%lu,",
3363 this, aUnderlineStyle, static_cast<unsigned long>(aSelectedRange.length)));
3365 // We assume that aUnderlineStyle is NSUnderlineStyleSingle or
3366 // NSUnderlineStyleThick. NSUnderlineStyleThick should indicate a selected
3367 // clause. Otherwise, should indicate non-selected clause.
3369 if (aSelectedRange.length == 0) {
3370 switch (aUnderlineStyle) {
3371 case NSUnderlineStyleSingle:
3372 return TextRangeType::eRawClause;
3373 case NSUnderlineStyleThick:
3374 return TextRangeType::eSelectedRawClause;
3376 NS_WARNING("Unexpected line style");
3377 return TextRangeType::eSelectedRawClause;
3381 switch (aUnderlineStyle) {
3382 case NSUnderlineStyleSingle:
3383 return TextRangeType::eConvertedClause;
3384 case NSUnderlineStyleThick:
3385 return TextRangeType::eSelectedClause;
3387 NS_WARNING("Unexpected line style");
3388 return TextRangeType::eSelectedClause;
3392 uint32_t IMEInputHandler::GetRangeCount(NSAttributedString* aAttrString) {
3393 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3395 // Iterate through aAttrString for the NSUnderlineStyleAttributeName and
3396 // count the different segments adjusting limitRange as we go.
3398 NSRange effectiveRange;
3399 NSRange limitRange = NSMakeRange(0, [aAttrString length]);
3400 while (limitRange.length > 0) {
3401 [aAttrString attribute:NSUnderlineStyleAttributeName
3402 atIndex:limitRange.location
3403 longestEffectiveRange:&effectiveRange
3404 inRange:limitRange];
3405 limitRange = NSMakeRange(NSMaxRange(effectiveRange),
3406 NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
3410 MOZ_LOG(gIMELog, LogLevel::Info,
3411 ("%p IMEInputHandler::GetRangeCount, aAttrString=\"%s\", count=%u", this,
3412 GetCharacters([aAttrString string]), count));
3416 NS_OBJC_END_TRY_BLOCK_RETURN(0);
3419 already_AddRefed<mozilla::TextRangeArray> IMEInputHandler::CreateTextRangeArray(
3420 NSAttributedString* aAttrString, NSRange& aSelectedRange) {
3421 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3423 RefPtr<mozilla::TextRangeArray> textRangeArray = new mozilla::TextRangeArray();
3425 // Note that we shouldn't append ranges when composition string
3426 // is empty because it may cause TextComposition confused.
3427 if (![aAttrString length]) {
3428 return textRangeArray.forget();
3431 // Convert the Cocoa range into the TextRange Array used in Gecko.
3432 // Iterate through the attributed string and map the underline attribute to
3433 // Gecko IME textrange attributes. We may need to change the code here if
3434 // we change the implementation of validAttributesForMarkedText.
3435 NSRange limitRange = NSMakeRange(0, [aAttrString length]);
3436 uint32_t rangeCount = GetRangeCount(aAttrString);
3437 for (uint32_t i = 0; i < rangeCount && limitRange.length > 0; i++) {
3438 NSRange effectiveRange;
3439 id attributeValue = [aAttrString attribute:NSUnderlineStyleAttributeName
3440 atIndex:limitRange.location
3441 longestEffectiveRange:&effectiveRange
3442 inRange:limitRange];
3445 range.mStartOffset = effectiveRange.location;
3446 range.mEndOffset = NSMaxRange(effectiveRange);
3447 range.mRangeType = ConvertToTextRangeType([attributeValue intValue], aSelectedRange);
3448 textRangeArray->AppendElement(range);
3450 MOZ_LOG(gIMELog, LogLevel::Info,
3451 ("%p IMEInputHandler::CreateTextRangeArray, "
3452 "range={ mStartOffset=%u, mEndOffset=%u, mRangeType=%s }",
3453 this, range.mStartOffset, range.mEndOffset, ToChar(range.mRangeType)));
3455 limitRange = NSMakeRange(NSMaxRange(effectiveRange),
3456 NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
3459 // Get current caret position.
3461 range.mStartOffset = aSelectedRange.location + aSelectedRange.length;
3462 range.mEndOffset = range.mStartOffset;
3463 range.mRangeType = TextRangeType::eCaret;
3464 textRangeArray->AppendElement(range);
3466 MOZ_LOG(gIMELog, LogLevel::Info,
3467 ("%p IMEInputHandler::CreateTextRangeArray, "
3468 "range={ mStartOffset=%u, mEndOffset=%u, mRangeType=%s }",
3469 this, range.mStartOffset, range.mEndOffset, ToChar(range.mRangeType)));
3471 return textRangeArray.forget();
3473 NS_OBJC_END_TRY_BLOCK_RETURN(nullptr);
3476 bool IMEInputHandler::DispatchCompositionStartEvent() {
3477 MOZ_LOG(gIMELog, LogLevel::Info,
3478 ("%p IMEInputHandler::DispatchCompositionStartEvent, "
3479 "mSelectedRange={ location=%lu, length=%lu }, Destroyed()=%s, "
3480 "mView=%p, mWidget=%p, inputContext=%p, mIsIMEComposing=%s",
3481 this, static_cast<unsigned long>(SelectedRange().location),
3482 static_cast<unsigned long>(mSelectedRange.length), TrueOrFalse(Destroyed()), mView,
3483 mWidget, mView ? [mView inputContext] : nullptr, TrueOrFalse(mIsIMEComposing)));
3485 RefPtr<IMEInputHandler> kungFuDeathGrip(this);
3487 nsresult rv = mDispatcher->BeginNativeInputTransaction();
3488 if (NS_WARN_IF(NS_FAILED(rv))) {
3489 MOZ_LOG(gIMELog, LogLevel::Error,
3490 ("%p IMEInputHandler::DispatchCompositionStartEvent, "
3491 "FAILED, due to BeginNativeInputTransaction() failure",
3496 NS_ASSERTION(!mIsIMEComposing, "There is a composition already");
3497 mIsIMEComposing = true;
3498 KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
3499 mIsDeadKeyComposing =
3500 currentKeyEvent && currentKeyEvent->mKeyEvent &&
3501 TISInputSourceWrapper::CurrentInputSource().IsDeadKey(currentKeyEvent->mKeyEvent);
3503 nsEventStatus status;
3504 rv = mDispatcher->StartComposition(status);
3505 if (NS_WARN_IF(NS_FAILED(rv))) {
3506 MOZ_LOG(gIMELog, LogLevel::Error,
3507 ("%p IMEInputHandler::DispatchCompositionStartEvent, "
3508 "FAILED, due to StartComposition() failure",
3514 MOZ_LOG(gIMELog, LogLevel::Info,
3515 ("%p IMEInputHandler::DispatchCompositionStartEvent, "
3516 "destroyed by compositionstart event",
3521 // FYI: compositionstart may cause committing composition by the webapp.
3522 if (!mIsIMEComposing) {
3526 // FYI: The selection range might have been modified by a compositionstart
3528 mIMECompositionStart = SelectedRange().location;
3532 bool IMEInputHandler::DispatchCompositionChangeEvent(const nsString& aText,
3533 NSAttributedString* aAttrString,
3534 NSRange& aSelectedRange) {
3535 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3537 MOZ_LOG(gIMELog, LogLevel::Info,
3538 ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
3539 "aText=\"%s\", aAttrString=\"%s\", "
3540 "aSelectedRange={ location=%lu, length=%lu }, Destroyed()=%s, mView=%p, "
3541 "mWidget=%p, inputContext=%p, mIsIMEComposing=%s",
3542 this, NS_ConvertUTF16toUTF8(aText).get(), GetCharacters([aAttrString string]),
3543 static_cast<unsigned long>(aSelectedRange.location),
3544 static_cast<unsigned long>(aSelectedRange.length), TrueOrFalse(Destroyed()), mView,
3545 mWidget, mView ? [mView inputContext] : nullptr, TrueOrFalse(mIsIMEComposing)));
3547 NS_ENSURE_TRUE(!Destroyed(), false);
3549 NS_ASSERTION(mIsIMEComposing, "We're not in composition");
3551 RefPtr<IMEInputHandler> kungFuDeathGrip(this);
3553 nsresult rv = mDispatcher->BeginNativeInputTransaction();
3554 if (NS_WARN_IF(NS_FAILED(rv))) {
3555 MOZ_LOG(gIMELog, LogLevel::Error,
3556 ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
3557 "FAILED, due to BeginNativeInputTransaction() failure",
3562 RefPtr<TextRangeArray> rangeArray = CreateTextRangeArray(aAttrString, aSelectedRange);
3564 rv = mDispatcher->SetPendingComposition(aText, rangeArray);
3565 if (NS_WARN_IF(NS_FAILED(rv))) {
3566 MOZ_LOG(gIMELog, LogLevel::Error,
3567 ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
3568 "FAILED, due to SetPendingComposition() failure",
3573 mSelectedRange.location = mIMECompositionStart + aSelectedRange.location;
3574 mSelectedRange.length = aSelectedRange.length;
3576 if (mIMECompositionString) {
3577 [mIMECompositionString release];
3579 mIMECompositionString = [[aAttrString string] retain];
3581 nsEventStatus status;
3582 rv = mDispatcher->FlushPendingComposition(status);
3583 if (NS_WARN_IF(NS_FAILED(rv))) {
3584 MOZ_LOG(gIMELog, LogLevel::Error,
3585 ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
3586 "FAILED, due to FlushPendingComposition() failure",
3592 MOZ_LOG(gIMELog, LogLevel::Info,
3593 ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
3594 "destroyed by compositionchange event",
3599 // FYI: compositionstart may cause committing composition by the webapp.
3600 return mIsIMEComposing;
3602 NS_OBJC_END_TRY_BLOCK_RETURN(false);
3605 bool IMEInputHandler::DispatchCompositionCommitEvent(const nsAString* aCommitString) {
3606 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3608 MOZ_LOG(gIMELog, LogLevel::Info,
3609 ("%p IMEInputHandler::DispatchCompositionCommitEvent, "
3610 "aCommitString=0x%p (\"%s\"), Destroyed()=%s, mView=%p, mWidget=%p, "
3611 "inputContext=%p, mIsIMEComposing=%s",
3612 this, aCommitString, aCommitString ? NS_ConvertUTF16toUTF8(*aCommitString).get() : "",
3613 TrueOrFalse(Destroyed()), mView, mWidget, mView ? [mView inputContext] : nullptr,
3614 TrueOrFalse(mIsIMEComposing)));
3616 NS_ASSERTION(mIsIMEComposing, "We're not in composition");
3618 RefPtr<IMEInputHandler> kungFuDeathGrip(this);
3621 // IME may query selection immediately after this, however, in e10s mode,
3622 // OnSelectionChange() will be called asynchronously. Until then, we
3623 // should emulate expected selection range if the webapp does nothing.
3624 mSelectedRange.location = mIMECompositionStart;
3625 if (aCommitString) {
3626 mSelectedRange.location += aCommitString->Length();
3627 } else if (mIMECompositionString) {
3628 nsAutoString commitString;
3629 nsCocoaUtils::GetStringForNSString(mIMECompositionString, commitString);
3630 mSelectedRange.location += commitString.Length();
3632 mSelectedRange.length = 0;
3634 nsresult rv = mDispatcher->BeginNativeInputTransaction();
3635 if (NS_WARN_IF(NS_FAILED(rv))) {
3636 MOZ_LOG(gIMELog, LogLevel::Error,
3637 ("%p IMEInputHandler::DispatchCompositionCommitEvent, "
3638 "FAILED, due to BeginNativeInputTransaction() failure",
3641 nsEventStatus status;
3642 rv = mDispatcher->CommitComposition(status, aCommitString);
3643 if (NS_WARN_IF(NS_FAILED(rv))) {
3644 MOZ_LOG(gIMELog, LogLevel::Error,
3645 ("%p IMEInputHandler::DispatchCompositionCommitEvent, "
3646 "FAILED, due to BeginNativeInputTransaction() failure",
3652 mIsIMEComposing = mIsDeadKeyComposing = false;
3653 mIMECompositionStart = UINT32_MAX;
3654 if (mIMECompositionString) {
3655 [mIMECompositionString release];
3656 mIMECompositionString = nullptr;
3660 MOZ_LOG(gIMELog, LogLevel::Info,
3661 ("%p IMEInputHandler::DispatchCompositionCommitEvent, "
3662 "destroyed by compositioncommit event",
3669 NS_OBJC_END_TRY_BLOCK_RETURN(false);
3672 bool IMEInputHandler::MaybeDispatchCurrentKeydownEvent(bool aIsProcessedByIME) {
3673 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3678 MOZ_ASSERT(mWidget);
3680 KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
3681 if (!currentKeyEvent || !currentKeyEvent->CanDispatchKeyDownEvent()) {
3685 NSEvent* nativeEvent = currentKeyEvent->mKeyEvent;
3686 if (NS_WARN_IF(!nativeEvent) || [nativeEvent type] != NSEventTypeKeyDown) {
3690 MOZ_LOG(gIMELog, LogLevel::Info,
3691 ("%p IMEInputHandler::MaybeDispatchKeydownEvent, aIsProcessedByIME=%s "
3692 "currentKeyEvent={ mKeyEvent(%p)={ type=%s, keyCode=%s (0x%X) } }, "
3693 "aIsProcessedBy=%s, IsDeadKeyComposing()=%s",
3694 this, TrueOrFalse(aIsProcessedByIME), nativeEvent, GetNativeKeyEventType(nativeEvent),
3695 GetKeyNameForNativeKeyCode([nativeEvent keyCode]), [nativeEvent keyCode],
3696 TrueOrFalse(IsIMEComposing()), TrueOrFalse(IsDeadKeyComposing())));
3698 RefPtr<IMEInputHandler> kungFuDeathGrip(this);
3699 RefPtr<TextEventDispatcher> dispatcher(mDispatcher);
3700 nsresult rv = dispatcher->BeginNativeInputTransaction();
3701 if (NS_WARN_IF(NS_FAILED(rv))) {
3702 MOZ_LOG(gIMELog, LogLevel::Error,
3703 ("%p IMEInputHandler::DispatchKeyEventForFlagsChanged, "
3704 "FAILED, due to BeginNativeInputTransaction() failure",
3709 NSResponder* firstResponder = [[mView window] firstResponder];
3711 // Mark currentKeyEvent as "dispatched eKeyDown event" and actually do it.
3712 currentKeyEvent->mKeyDownDispatched = true;
3714 RefPtr<nsChildView> widget(mWidget);
3716 WidgetKeyboardEvent keydownEvent(true, eKeyDown, widget);
3717 // Don't mark the eKeyDown event as "processed by IME" if the composition
3718 // is started with dead key.
3719 currentKeyEvent->InitKeyEvent(this, keydownEvent, aIsProcessedByIME && !IsDeadKeyComposing());
3721 nsEventStatus status = nsEventStatus_eIgnore;
3722 dispatcher->DispatchKeyboardEvent(eKeyDown, keydownEvent, status, currentKeyEvent);
3723 currentKeyEvent->mKeyDownHandled = (status == nsEventStatus_eConsumeNoDefault);
3726 MOZ_LOG(gIMELog, LogLevel::Info,
3727 ("%p IMEInputHandler::MaybeDispatchKeydownEvent, "
3728 "widget was destroyed by keydown event",
3733 // The key down event may have shifted the focus, in which case, we should
3734 // not continue to handle current key sequence and let's commit current
3736 if (firstResponder != [[mView window] firstResponder]) {
3737 MOZ_LOG(gIMELog, LogLevel::Info,
3738 ("%p IMEInputHandler::MaybeDispatchKeydownEvent, "
3739 "view lost focus by keydown event",
3741 CommitIMEComposition();
3747 NS_OBJC_END_TRY_BLOCK_RETURN(false);
3750 void IMEInputHandler::InsertTextAsCommittingComposition(NSAttributedString* aAttrString,
3751 NSRange* aReplacementRange) {
3752 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3754 MOZ_LOG(gIMELog, LogLevel::Info,
3755 ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
3756 "aAttrString=\"%s\", aReplacementRange=%p { location=%lu, length=%lu }, "
3757 "Destroyed()=%s, IsIMEComposing()=%s, "
3758 "mMarkedRange={ location=%lu, length=%lu }",
3759 this, GetCharacters([aAttrString string]), aReplacementRange,
3760 static_cast<unsigned long>(aReplacementRange ? aReplacementRange->location : 0),
3761 static_cast<unsigned long>(aReplacementRange ? aReplacementRange->length : 0),
3762 TrueOrFalse(Destroyed()), TrueOrFalse(IsIMEComposing()),
3763 static_cast<unsigned long>(mMarkedRange.location),
3764 static_cast<unsigned long>(mMarkedRange.length)));
3766 if (IgnoreIMECommit()) {
3767 MOZ_CRASH("IMEInputHandler::InsertTextAsCommittingComposition() must not"
3768 "be called while canceling the composition");
3775 // When current keydown event causes this text input, let's dispatch
3776 // eKeyDown event before any other events. Note that if we're in a
3777 // composition, we've already dispatched eKeyDown event from
3778 // TextInputHandler::HandleKeyDownEvent().
3779 // XXX Should we mark the eKeyDown event as "processed by IME"?
3780 // However, if the key causes two or more Unicode characters as
3781 // UTF-16 string, this is used. So, perhaps, we need to improve
3782 // HandleKeyDownEvent() before do that.
3783 RefPtr<IMEInputHandler> kungFuDeathGrip(this);
3784 if (!IsIMEComposing() && !MaybeDispatchCurrentKeydownEvent(false)) {
3785 MOZ_LOG(gIMELog, LogLevel::Info,
3786 ("%p IMEInputHandler::InsertTextAsCommittingComposition, eKeyDown "
3787 "caused focus move or something and canceling the composition",
3792 // First, commit current composition with the latest composition string if the
3793 // replacement range is different from marked range.
3794 if (IsIMEComposing() && aReplacementRange && aReplacementRange->location != NSNotFound &&
3795 !NSEqualRanges(MarkedRange(), *aReplacementRange)) {
3796 if (!DispatchCompositionCommitEvent()) {
3797 MOZ_LOG(gIMELog, LogLevel::Info,
3798 ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
3799 "destroyed by commiting composition for setting replacement range",
3806 nsCocoaUtils::GetStringForNSString([aAttrString string], str);
3808 if (!IsIMEComposing()) {
3809 MOZ_DIAGNOSTIC_ASSERT(!str.IsEmpty());
3811 // If there is no selection and replacement range is specified, set the
3812 // range as selection.
3813 if (aReplacementRange && aReplacementRange->location != NSNotFound &&
3814 !NSEqualRanges(SelectedRange(), *aReplacementRange)) {
3815 NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
3818 if (!StaticPrefs::intl_ime_use_composition_events_for_insert_text()) {
3819 // In the default settings, we should not use composition events for
3820 // inserting text without key press nor IME composition because the
3821 // other browsers do so. This will cause only a cancelable `beforeinput`
3822 // event whose `inputType` is `insertText`.
3823 WidgetContentCommandEvent insertTextEvent(true, eContentCommandInsertText, mWidget);
3824 insertTextEvent.mString = Some(str);
3825 DispatchEvent(insertTextEvent);
3829 // Otherise, emulate an IME composition. This is our traditional behavior,
3830 // but `beforeinput` events are not cancelable since they should be so for
3831 // native IME limitation. So, this is now seriously imcompatible with the
3833 if (!DispatchCompositionStartEvent()) {
3834 MOZ_LOG(gIMELog, LogLevel::Info,
3835 ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
3836 "cannot continue handling composition after compositionstart",
3842 if (!DispatchCompositionCommitEvent(&str)) {
3843 MOZ_LOG(gIMELog, LogLevel::Info,
3844 ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
3845 "destroyed by compositioncommit event",
3850 mMarkedRange = NSMakeRange(NSNotFound, 0);
3852 NS_OBJC_END_TRY_IGNORE_BLOCK;
3855 void IMEInputHandler::SetMarkedText(NSAttributedString* aAttrString, NSRange& aSelectedRange,
3856 NSRange* aReplacementRange) {
3857 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3859 KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
3861 MOZ_LOG(gIMELog, LogLevel::Info,
3862 ("%p IMEInputHandler::SetMarkedText, "
3863 "aAttrString=\"%s\", aSelectedRange={ location=%lu, length=%lu }, "
3864 "aReplacementRange=%p { location=%lu, length=%lu }, "
3865 "Destroyed()=%s, IsIMEComposing()=%s, "
3866 "mMarkedRange={ location=%lu, length=%lu }, keyevent=%p, "
3867 "keydownDispatched=%s, keydownHandled=%s, "
3868 "keypressDispatched=%s, causedOtherKeyEvents=%s, "
3869 "compositionDispatched=%s",
3870 this, GetCharacters([aAttrString string]),
3871 static_cast<unsigned long>(aSelectedRange.location),
3872 static_cast<unsigned long>(aSelectedRange.length), aReplacementRange,
3873 static_cast<unsigned long>(aReplacementRange ? aReplacementRange->location : 0),
3874 static_cast<unsigned long>(aReplacementRange ? aReplacementRange->length : 0),
3875 TrueOrFalse(Destroyed()), TrueOrFalse(IsIMEComposing()),
3876 static_cast<unsigned long>(mMarkedRange.location),
3877 static_cast<unsigned long>(mMarkedRange.length),
3878 currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr,
3879 currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyDownDispatched) : "N/A",
3880 currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
3881 currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A",
3882 currentKeyEvent ? TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A",
3883 currentKeyEvent ? TrueOrFalse(currentKeyEvent->mCompositionDispatched) : "N/A"));
3885 RefPtr<IMEInputHandler> kungFuDeathGrip(this);
3887 // If SetMarkedText() is called during handling a key press, that means that
3888 // the key event caused this composition. So, keypress event shouldn't
3889 // be dispatched later, let's mark the key event causing composition event.
3890 if (currentKeyEvent) {
3891 currentKeyEvent->mCompositionDispatched = true;
3893 // When current keydown event causes this text input, let's dispatch
3894 // eKeyDown event before any other events. Note that if we're in a
3895 // composition, we've already dispatched eKeyDown event from
3896 // TextInputHandler::HandleKeyDownEvent(). On the other hand, if we're
3897 // not in composition, the key event starts new composition. So, we
3898 // need to mark the eKeyDown event as "processed by IME".
3899 if (!IsIMEComposing() && !MaybeDispatchCurrentKeydownEvent(true)) {
3900 MOZ_LOG(gIMELog, LogLevel::Info,
3901 ("%p IMEInputHandler::SetMarkedText, eKeyDown caused focus move or "
3902 "something and canceling the composition",
3912 // First, commit current composition with the latest composition string if the
3913 // replacement range is different from marked range.
3914 if (IsIMEComposing() && aReplacementRange && aReplacementRange->location != NSNotFound &&
3915 !NSEqualRanges(MarkedRange(), *aReplacementRange)) {
3916 AutoRestore<bool> ignoreIMECommit(mIgnoreIMECommit);
3917 mIgnoreIMECommit = false;
3918 if (!DispatchCompositionCommitEvent()) {
3919 MOZ_LOG(gIMELog, LogLevel::Info,
3920 ("%p IMEInputHandler::SetMarkedText, "
3921 "destroyed by commiting composition for setting replacement range",
3928 nsCocoaUtils::GetStringForNSString([aAttrString string], str);
3930 mMarkedRange.length = str.Length();
3932 if (!IsIMEComposing() && !str.IsEmpty()) {
3933 // If there is no selection and replacement range is specified, set the
3934 // range as selection.
3935 if (aReplacementRange && aReplacementRange->location != NSNotFound &&
3936 !NSEqualRanges(SelectedRange(), *aReplacementRange)) {
3937 // Set temporary selection range since OnSelectionChange is async.
3938 mSelectedRange = *aReplacementRange;
3939 if (NS_WARN_IF(!SetSelection(*aReplacementRange))) {
3940 mSelectedRange.location = NSNotFound; // Marking dirty
3945 mMarkedRange.location = SelectedRange().location;
3947 if (!DispatchCompositionStartEvent()) {
3948 MOZ_LOG(gIMELog, LogLevel::Info,
3949 ("%p IMEInputHandler::SetMarkedText, cannot continue handling "
3950 "composition after dispatching compositionstart",
3956 if (!str.IsEmpty()) {
3957 if (!DispatchCompositionChangeEvent(str, aAttrString, aSelectedRange)) {
3958 MOZ_LOG(gIMELog, LogLevel::Info,
3959 ("%p IMEInputHandler::SetMarkedText, cannot continue handling "
3960 "composition after dispatching compositionchange",
3966 // If the composition string becomes empty string, we should commit
3967 // current composition.
3968 if (!DispatchCompositionCommitEvent(&EmptyString())) {
3969 MOZ_LOG(gIMELog, LogLevel::Info,
3970 ("%p IMEInputHandler::SetMarkedText, "
3971 "destroyed by compositioncommit event",
3975 NS_OBJC_END_TRY_IGNORE_BLOCK;
3978 NSAttributedString* IMEInputHandler::GetAttributedSubstringFromRange(NSRange& aRange,
3979 NSRange* aActualRange) {
3980 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3982 MOZ_LOG(gIMELog, LogLevel::Info,
3983 ("%p IMEInputHandler::GetAttributedSubstringFromRange, "
3984 "aRange={ location=%lu, length=%lu }, aActualRange=%p, Destroyed()=%s",
3985 this, static_cast<unsigned long>(aRange.location),
3986 static_cast<unsigned long>(aRange.length), aActualRange, TrueOrFalse(Destroyed())));
3989 *aActualRange = NSMakeRange(NSNotFound, 0);
3992 if (Destroyed() || aRange.location == NSNotFound || aRange.length == 0) {
3996 RefPtr<IMEInputHandler> kungFuDeathGrip(this);
3998 // If we're in composing, the queried range may be in the composition string.
3999 // In such case, we should use mIMECompositionString since if the composition
4000 // string is handled by a remote process, the content cache may be out of
4002 // XXX Should we set composition string attributes? Although, Blink claims
4003 // that some attributes of marked text are supported, but they return
4004 // just marked string without any style. So, let's keep current behavior
4005 // at least for now.
4006 NSUInteger compositionLength = mIMECompositionString ? [mIMECompositionString length] : 0;
4007 if (mIMECompositionStart != UINT32_MAX && aRange.location >= mIMECompositionStart &&
4008 aRange.location + aRange.length <= mIMECompositionStart + compositionLength) {
4009 NSRange range = NSMakeRange(aRange.location - mIMECompositionStart, aRange.length);
4010 NSString* nsstr = [mIMECompositionString substringWithRange:range];
4011 NSMutableAttributedString* result =
4012 [[[NSMutableAttributedString alloc] initWithString:nsstr attributes:nil] autorelease];
4013 // XXX We cannot return font information in this case. However, this
4014 // case must occur only when IME tries to confirm if composing string
4015 // is handled as expected.
4017 *aActualRange = aRange;
4020 if (MOZ_LOG_TEST(gIMELog, LogLevel::Info)) {
4022 nsCocoaUtils::GetStringForNSString(nsstr, str);
4023 MOZ_LOG(gIMELog, LogLevel::Info,
4024 ("%p IMEInputHandler::GetAttributedSubstringFromRange, "
4025 "computed with mIMECompositionString (result string=\"%s\")",
4026 this, NS_ConvertUTF16toUTF8(str).get()));
4032 WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent, mWidget);
4033 WidgetQueryContentEvent::Options options;
4034 int64_t startOffset = aRange.location;
4035 if (IsIMEComposing()) {
4036 // The composition may be at different offset from the selection start
4037 // offset at dispatching compositionstart because start of composition
4038 // is fixed when composition string becomes non-empty in the editor.
4039 // Therefore, we need to use query event which is relative to insertion
4041 options.mRelativeToInsertionPoint = true;
4042 startOffset -= mIMECompositionStart;
4044 queryTextContentEvent.InitForQueryTextContent(startOffset, aRange.length, options);
4045 queryTextContentEvent.RequestFontRanges();
4046 DispatchEvent(queryTextContentEvent);
4048 MOZ_LOG(gIMELog, LogLevel::Info,
4049 ("%p IMEInputHandler::GetAttributedSubstringFromRange, "
4050 "queryTextContentEvent={ mReply=%s }",
4051 this, ToString(queryTextContentEvent.mReply).c_str()));
4053 if (queryTextContentEvent.Failed()) {
4057 // We don't set vertical information at this point. If required,
4058 // OS will calls drawsVerticallyForCharacterAtIndex.
4059 NSMutableAttributedString* result = nsCocoaUtils::GetNSMutableAttributedString(
4060 queryTextContentEvent.mReply->DataRef(), queryTextContentEvent.mReply->mFontRanges, false,
4061 mWidget->BackingScaleFactor());
4063 *aActualRange = MakeNSRangeFrom(queryTextContentEvent.mReply->mOffsetAndData);
4067 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
4070 bool IMEInputHandler::HasMarkedText() {
4071 MOZ_LOG(gIMELog, LogLevel::Info,
4072 ("%p IMEInputHandler::HasMarkedText, "
4073 "mMarkedRange={ location=%lu, length=%lu }",
4074 this, static_cast<unsigned long>(mMarkedRange.location),
4075 static_cast<unsigned long>(mMarkedRange.length)));
4077 return (mMarkedRange.location != NSNotFound) && (mMarkedRange.length != 0);
4080 NSRange IMEInputHandler::MarkedRange() {
4081 MOZ_LOG(gIMELog, LogLevel::Info,
4082 ("%p IMEInputHandler::MarkedRange, "
4083 "mMarkedRange={ location=%lu, length=%lu }",
4084 this, static_cast<unsigned long>(mMarkedRange.location),
4085 static_cast<unsigned long>(mMarkedRange.length)));
4087 if (!HasMarkedText()) {
4088 return NSMakeRange(NSNotFound, 0);
4090 return mMarkedRange;
4093 NSRange IMEInputHandler::SelectedRange() {
4094 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4096 MOZ_LOG(gIMELog, LogLevel::Info,
4097 ("%p IMEInputHandler::SelectedRange, Destroyed()=%s, mSelectedRange={ "
4098 "location=%lu, length=%lu }",
4099 this, TrueOrFalse(Destroyed()), static_cast<unsigned long>(mSelectedRange.location),
4100 static_cast<unsigned long>(mSelectedRange.length)));
4103 return mSelectedRange;
4106 if (mSelectedRange.location != NSNotFound) {
4107 MOZ_ASSERT(mIMEHasFocus);
4108 return mSelectedRange;
4111 RefPtr<IMEInputHandler> kungFuDeathGrip(this);
4113 WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText, mWidget);
4114 DispatchEvent(querySelectedTextEvent);
4116 MOZ_LOG(gIMELog, LogLevel::Info,
4117 ("%p IMEInputHandler::SelectedRange, querySelectedTextEvent={ mReply=%s }", this,
4118 ToString(querySelectedTextEvent.mReply).c_str()));
4120 if (querySelectedTextEvent.Failed()) {
4121 return mSelectedRange;
4124 mWritingMode = querySelectedTextEvent.mReply->WritingModeRef();
4125 mRangeForWritingMode = MakeNSRangeFrom(querySelectedTextEvent.mReply->mOffsetAndData);
4128 mSelectedRange = mRangeForWritingMode;
4131 return mRangeForWritingMode;
4133 NS_OBJC_END_TRY_BLOCK_RETURN(mSelectedRange);
4136 bool IMEInputHandler::DrawsVerticallyForCharacterAtIndex(uint32_t aCharIndex) {
4137 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4143 if (mRangeForWritingMode.location == NSNotFound) {
4144 // Update cached writing-mode value for the current selection.
4148 if (aCharIndex < mRangeForWritingMode.location ||
4149 aCharIndex > mRangeForWritingMode.location + mRangeForWritingMode.length) {
4150 // It's not clear to me whether this ever happens in practice, but if an
4151 // IME ever wants to query writing mode at an offset outside the current
4152 // selection, the writing-mode value may not be correct for the index.
4153 // In that case, use FirstRectForCharacterRange to get a fresh value.
4154 // This does more work than strictly necessary (we don't need the rect here),
4155 // but should be a rare case.
4156 NS_WARNING("DrawsVerticallyForCharacterAtIndex not using cached writing mode");
4157 NSRange range = NSMakeRange(aCharIndex, 1);
4158 NSRange actualRange;
4159 FirstRectForCharacterRange(range, &actualRange);
4162 return mWritingMode.IsVertical();
4164 NS_OBJC_END_TRY_BLOCK_RETURN(false);
4167 NSRect IMEInputHandler::FirstRectForCharacterRange(NSRange& aRange, NSRange* aActualRange) {
4168 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4170 MOZ_LOG(gIMELog, LogLevel::Info,
4171 ("%p IMEInputHandler::FirstRectForCharacterRange, Destroyed()=%s, "
4172 "aRange={ location=%lu, length=%lu }, aActualRange=%p }",
4173 this, TrueOrFalse(Destroyed()), static_cast<unsigned long>(aRange.location),
4174 static_cast<unsigned long>(aRange.length), aActualRange));
4176 // XXX this returns first character rect or caret rect, it is limitation of
4177 // now. We need more work for returns first line rect. But current
4178 // implementation is enough for IMEs.
4180 NSRect rect = NSMakeRect(0.0, 0.0, 0.0, 0.0);
4181 NSRange actualRange = NSMakeRange(NSNotFound, 0);
4183 *aActualRange = actualRange;
4185 if (Destroyed() || aRange.location == NSNotFound) {
4189 RefPtr<IMEInputHandler> kungFuDeathGrip(this);
4191 LayoutDeviceIntRect r;
4192 bool useCaretRect = (aRange.length == 0);
4193 if (!useCaretRect) {
4194 WidgetQueryContentEvent queryTextRectEvent(true, eQueryTextRect, mWidget);
4195 WidgetQueryContentEvent::Options options;
4196 int64_t startOffset = aRange.location;
4197 if (IsIMEComposing()) {
4198 // The composition may be at different offset from the selection start
4199 // offset at dispatching compositionstart because start of composition
4200 // is fixed when composition string becomes non-empty in the editor.
4201 // Therefore, we need to use query event which is relative to insertion
4203 options.mRelativeToInsertionPoint = true;
4204 startOffset -= mIMECompositionStart;
4206 queryTextRectEvent.InitForQueryTextRect(startOffset, 1, options);
4207 DispatchEvent(queryTextRectEvent);
4208 if (queryTextRectEvent.Succeeded()) {
4209 r = queryTextRectEvent.mReply->mRect;
4210 actualRange = MakeNSRangeFrom(queryTextRectEvent.mReply->mOffsetAndData);
4211 mWritingMode = queryTextRectEvent.mReply->WritingModeRef();
4212 mRangeForWritingMode = actualRange;
4214 useCaretRect = true;
4219 WidgetQueryContentEvent queryCaretRectEvent(true, eQueryCaretRect, mWidget);
4220 WidgetQueryContentEvent::Options options;
4221 int64_t startOffset = aRange.location;
4222 if (IsIMEComposing()) {
4223 // The composition may be at different offset from the selection start
4224 // offset at dispatching compositionstart because start of composition
4225 // is fixed when composition string becomes non-empty in the editor.
4226 // Therefore, we need to use query event which is relative to insertion
4228 options.mRelativeToInsertionPoint = true;
4229 startOffset -= mIMECompositionStart;
4231 queryCaretRectEvent.InitForQueryCaretRect(startOffset, options);
4232 DispatchEvent(queryCaretRectEvent);
4233 if (queryCaretRectEvent.Failed()) {
4236 r = queryCaretRectEvent.mReply->mRect;
4238 actualRange.location = queryCaretRectEvent.mReply->StartOffset();
4239 actualRange.length = 0;
4242 nsIWidget* rootWidget = mWidget->GetTopLevelWidget();
4243 NSWindow* rootWindow = static_cast<NSWindow*>(rootWidget->GetNativeData(NS_NATIVE_WINDOW));
4244 NSView* rootView = static_cast<NSView*>(rootWidget->GetNativeData(NS_NATIVE_WIDGET));
4245 if (!rootWindow || !rootView) {
4248 rect = nsCocoaUtils::DevPixelsToCocoaPoints(r, mWidget->BackingScaleFactor());
4249 rect = [rootView convertRect:rect toView:nil];
4250 rect.origin = nsCocoaUtils::ConvertPointToScreen(rootWindow, rect.origin);
4253 *aActualRange = actualRange;
4256 MOZ_LOG(gIMELog, LogLevel::Info,
4257 ("%p IMEInputHandler::FirstRectForCharacterRange, "
4258 "useCaretRect=%s rect={ x=%f, y=%f, width=%f, height=%f }, "
4259 "actualRange={ location=%lu, length=%lu }",
4260 this, TrueOrFalse(useCaretRect), rect.origin.x, rect.origin.y, rect.size.width,
4261 rect.size.height, static_cast<unsigned long>(actualRange.location),
4262 static_cast<unsigned long>(actualRange.length)));
4266 NS_OBJC_END_TRY_BLOCK_RETURN(NSMakeRect(0.0, 0.0, 0.0, 0.0));
4269 NSUInteger IMEInputHandler::CharacterIndexForPoint(NSPoint& aPoint) {
4270 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4272 MOZ_LOG(gIMELog, LogLevel::Info,
4273 ("%p IMEInputHandler::CharacterIndexForPoint, aPoint={ x=%f, y=%f }", this, aPoint.x,
4276 NSWindow* mainWindow = [NSApp mainWindow];
4277 if (!mWidget || !mainWindow) {
4281 WidgetQueryContentEvent queryCharAtPointEvent(true, eQueryCharacterAtPoint, mWidget);
4282 NSPoint ptInWindow = nsCocoaUtils::ConvertPointFromScreen(mainWindow, aPoint);
4283 NSPoint ptInView = [mView convertPoint:ptInWindow fromView:nil];
4284 queryCharAtPointEvent.mRefPoint.x =
4285 static_cast<int32_t>(ptInView.x) * mWidget->BackingScaleFactor();
4286 queryCharAtPointEvent.mRefPoint.y =
4287 static_cast<int32_t>(ptInView.y) * mWidget->BackingScaleFactor();
4288 mWidget->DispatchWindowEvent(queryCharAtPointEvent);
4289 if (queryCharAtPointEvent.Failed() || queryCharAtPointEvent.DidNotFindChar() ||
4290 queryCharAtPointEvent.mReply->StartOffset() >= static_cast<uint32_t>(NSNotFound)) {
4294 return queryCharAtPointEvent.mReply->StartOffset();
4296 NS_OBJC_END_TRY_BLOCK_RETURN(NSNotFound);
4300 extern NSString* NSTextInputReplacementRangeAttributeName;
4303 NSArray* IMEInputHandler::GetValidAttributesForMarkedText() {
4304 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4306 MOZ_LOG(gIMELog, LogLevel::Info, ("%p IMEInputHandler::GetValidAttributesForMarkedText", this));
4308 // Return same attributes as Chromium (see render_widget_host_view_mac.mm)
4309 // because most IMEs must be tested with Safari (OS default) and Chrome
4310 // (having most market share). Therefore, we need to follow their behavior.
4311 // XXX It might be better to reuse an array instance for this result because
4312 // this may be called a lot. Note that Chromium does so.
4313 return [NSArray arrayWithObjects:NSUnderlineStyleAttributeName, NSUnderlineColorAttributeName,
4314 NSMarkedClauseSegmentAttributeName,
4315 NSTextInputReplacementRangeAttributeName, nil];
4317 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
4322 /******************************************************************************
4324 * IMEInputHandler implementation #2
4326 ******************************************************************************/
4328 IMEInputHandler::IMEInputHandler(nsChildView* aWidget, NSView<mozView>* aNativeView)
4329 : TextInputHandlerBase(aWidget, aNativeView),
4331 mIMECompositionString(nullptr),
4332 mIMECompositionStart(UINT32_MAX),
4333 mRangeForWritingMode(),
4334 mIsIMEComposing(false),
4335 mIsDeadKeyComposing(false),
4336 mIsIMEEnabled(true),
4337 mIsASCIICapableOnly(false),
4338 mIgnoreIMECommit(false),
4339 mIMEHasFocus(false) {
4340 InitStaticMembers();
4342 mMarkedRange.location = NSNotFound;
4343 mMarkedRange.length = 0;
4344 mSelectedRange.location = NSNotFound;
4345 mSelectedRange.length = 0;
4348 IMEInputHandler::~IMEInputHandler() {
4353 if (sFocusedIMEHandler == this) {
4354 sFocusedIMEHandler = nullptr;
4356 if (mIMECompositionString) {
4357 [mIMECompositionString release];
4358 mIMECompositionString = nullptr;
4362 void IMEInputHandler::OnFocusChangeInGecko(bool aFocus) {
4363 MOZ_LOG(gIMELog, LogLevel::Info,
4364 ("%p IMEInputHandler::OnFocusChangeInGecko, aFocus=%s, Destroyed()=%s, "
4365 "sFocusedIMEHandler=%p",
4366 this, TrueOrFalse(aFocus), TrueOrFalse(Destroyed()), sFocusedIMEHandler));
4368 mSelectedRange.location = NSNotFound; // Marking dirty
4369 mIMEHasFocus = aFocus;
4371 // This is called when the native focus is changed and when the native focus
4372 // isn't changed but the focus is changed in Gecko.
4374 if (sFocusedIMEHandler == this) sFocusedIMEHandler = nullptr;
4378 sFocusedIMEHandler = this;
4380 // We need to notify IME of focus change in Gecko as native focus change
4381 // because the window level of the focused element in Gecko may be changed.
4382 mPendingMethods |= kNotifyIMEOfFocusChangeInGecko;
4386 bool IMEInputHandler::OnDestroyWidget(nsChildView* aDestroyingWidget) {
4387 MOZ_LOG(gIMELog, LogLevel::Info,
4388 ("%p IMEInputHandler::OnDestroyWidget, aDestroyingWidget=%p, "
4389 "sFocusedIMEHandler=%p, IsIMEComposing()=%s",
4390 this, aDestroyingWidget, sFocusedIMEHandler, TrueOrFalse(IsIMEComposing())));
4392 // If we're not focused, the focused IMEInputHandler may have been
4393 // created by another widget/nsChildView.
4394 if (sFocusedIMEHandler && sFocusedIMEHandler != this) {
4395 sFocusedIMEHandler->OnDestroyWidget(aDestroyingWidget);
4398 if (!TextInputHandlerBase::OnDestroyWidget(aDestroyingWidget)) {
4402 if (IsIMEComposing()) {
4403 // If our view is in the composition, we should clean up it.
4404 CancelIMEComposition();
4407 mSelectedRange.location = NSNotFound; // Marking dirty
4408 mIMEHasFocus = false;
4413 void IMEInputHandler::SendCommittedText(NSString* aString) {
4414 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
4417 gIMELog, LogLevel::Info,
4418 ("%p IMEInputHandler::SendCommittedText, mView=%p, mWidget=%p, "
4419 "inputContext=%p, mIsIMEComposing=%s",
4420 this, mView, mWidget, mView ? [mView inputContext] : nullptr, TrueOrFalse(mIsIMEComposing)));
4422 NS_ENSURE_TRUE(mWidget, );
4423 // XXX We should send the string without mView.
4428 NSAttributedString* attrStr = [[NSAttributedString alloc] initWithString:aString];
4429 if ([mView conformsToProtocol:@protocol(NSTextInputClient)]) {
4430 NSObject<NSTextInputClient>* textInputClient = static_cast<NSObject<NSTextInputClient>*>(mView);
4431 [textInputClient insertText:attrStr replacementRange:NSMakeRange(NSNotFound, 0)];
4434 // Last resort. If we cannot retrieve NSTextInputProtocol from mView
4435 // or blocking to call our InsertText(), we should call InsertText()
4436 // directly to commit composition forcibly.
4437 if (mIsIMEComposing) {
4438 MOZ_LOG(gIMELog, LogLevel::Info,
4439 ("%p IMEInputHandler::SendCommittedText, trying to insert text directly "
4440 "due to IME not calling our InsertText()",
4442 static_cast<TextInputHandler*>(this)->InsertText(attrStr);
4443 MOZ_ASSERT(!mIsIMEComposing);
4448 NS_OBJC_END_TRY_IGNORE_BLOCK;
4451 void IMEInputHandler::KillIMEComposition() {
4452 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
4454 MOZ_LOG(gIMELog, LogLevel::Info,
4455 ("%p IMEInputHandler::KillIMEComposition, mView=%p, mWidget=%p, "
4456 "inputContext=%p, mIsIMEComposing=%s, "
4457 "Destroyed()=%s, IsFocused()=%s",
4458 this, mView, mWidget, mView ? [mView inputContext] : nullptr,
4459 TrueOrFalse(mIsIMEComposing), TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused())));
4461 if (Destroyed() || NS_WARN_IF(!mView)) {
4465 NSTextInputContext* inputContext = [mView inputContext];
4466 if (NS_WARN_IF(!inputContext)) {
4469 [inputContext discardMarkedText];
4471 NS_OBJC_END_TRY_IGNORE_BLOCK;
4474 void IMEInputHandler::CommitIMEComposition() {
4475 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
4477 MOZ_LOG(gIMELog, LogLevel::Info,
4478 ("%p IMEInputHandler::CommitIMEComposition, mIMECompositionString=%s", this,
4479 GetCharacters(mIMECompositionString)));
4481 // If this is called before dispatching eCompositionStart, IsIMEComposing()
4482 // returns false. Even in such case, we need to commit composition *in*
4483 // IME if this is called by preceding eKeyDown event of eCompositionStart.
4484 // So, we need to call KillIMEComposition() even when IsIMEComposing()
4486 KillIMEComposition();
4488 if (!IsIMEComposing()) return;
4490 // If the composition is still there, KillIMEComposition only kills the
4491 // composition in TSM. We also need to finish the our composition too.
4492 SendCommittedText(mIMECompositionString);
4494 NS_OBJC_END_TRY_IGNORE_BLOCK;
4497 void IMEInputHandler::CancelIMEComposition() {
4498 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
4500 if (!IsIMEComposing()) return;
4502 MOZ_LOG(gIMELog, LogLevel::Info,
4503 ("%p IMEInputHandler::CancelIMEComposition, mIMECompositionString=%s", this,
4504 GetCharacters(mIMECompositionString)));
4506 // For canceling the current composing, we need to ignore the param of
4507 // insertText. But this code is ugly...
4508 mIgnoreIMECommit = true;
4509 KillIMEComposition();
4510 mIgnoreIMECommit = false;
4512 if (!IsIMEComposing()) return;
4514 // If the composition is still there, KillIMEComposition only kills the
4515 // composition in TSM. We also need to kill the our composition too.
4516 SendCommittedText(@"");
4518 NS_OBJC_END_TRY_IGNORE_BLOCK;
4521 bool IMEInputHandler::IsFocused() {
4522 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
4524 NS_ENSURE_TRUE(!Destroyed(), false);
4525 NSWindow* window = [mView window];
4526 NS_ENSURE_TRUE(window, false);
4527 return [window firstResponder] == mView && [window isKeyWindow] &&
4528 [[NSApplication sharedApplication] isActive];
4530 NS_OBJC_END_TRY_BLOCK_RETURN(false);
4533 bool IMEInputHandler::IsIMEOpened() {
4534 TISInputSourceWrapper tis;
4535 tis.InitByCurrentInputSource();
4536 return tis.IsOpenedIMEMode();
4539 void IMEInputHandler::SetASCIICapableOnly(bool aASCIICapableOnly) {
4540 if (aASCIICapableOnly == mIsASCIICapableOnly) return;
4542 CommitIMEComposition();
4543 mIsASCIICapableOnly = aASCIICapableOnly;
4544 SyncASCIICapableOnly();
4547 void IMEInputHandler::EnableIME(bool aEnableIME) {
4548 if (aEnableIME == mIsIMEEnabled) return;
4550 CommitIMEComposition();
4551 mIsIMEEnabled = aEnableIME;
4554 void IMEInputHandler::SetIMEOpenState(bool aOpenIME) {
4555 if (!IsFocused() || IsIMEOpened() == aOpenIME) return;
4558 TISInputSourceWrapper tis;
4559 tis.InitByCurrentASCIICapableInputSource();
4564 // If we know the latest IME opened mode, we should select it.
4565 if (sLatestIMEOpenedModeInputSourceID) {
4566 TISInputSourceWrapper tis;
4567 tis.InitByInputSourceID(sLatestIMEOpenedModeInputSourceID);
4572 // XXX If the current input source is a mode of IME, we should turn on it,
4573 // but we haven't found such way...
4575 // Finally, we should refer the system locale but this is a little expensive,
4576 // we shouldn't retry this (if it was succeeded, we already set
4577 // sLatestIMEOpenedModeInputSourceID at that time).
4578 static bool sIsPrefferredIMESearched = false;
4579 if (sIsPrefferredIMESearched) return;
4580 sIsPrefferredIMESearched = true;
4581 OpenSystemPreferredLanguageIME();
4584 void IMEInputHandler::OpenSystemPreferredLanguageIME() {
4585 MOZ_LOG(gIMELog, LogLevel::Info, ("%p IMEInputHandler::OpenSystemPreferredLanguageIME", this));
4587 CFArrayRef langList = ::CFLocaleCopyPreferredLanguages();
4589 MOZ_LOG(gIMELog, LogLevel::Info,
4590 ("%p IMEInputHandler::OpenSystemPreferredLanguageIME, langList is NULL", this));
4593 CFIndex count = ::CFArrayGetCount(langList);
4594 for (CFIndex i = 0; i < count; i++) {
4595 CFLocaleRef locale = ::CFLocaleCreate(
4596 kCFAllocatorDefault, static_cast<CFStringRef>(::CFArrayGetValueAtIndex(langList, i)));
4601 bool changed = false;
4602 CFStringRef lang = static_cast<CFStringRef>(::CFLocaleGetValue(locale, kCFLocaleLanguageCode));
4603 NS_ASSERTION(lang, "lang is null");
4605 TISInputSourceWrapper tis;
4606 tis.InitByLanguage(lang);
4607 if (tis.IsOpenedIMEMode()) {
4608 if (MOZ_LOG_TEST(gIMELog, LogLevel::Info)) {
4609 CFStringRef foundTIS;
4610 tis.GetInputSourceID(foundTIS);
4611 MOZ_LOG(gIMELog, LogLevel::Info,
4612 ("%p IMEInputHandler::OpenSystemPreferredLanguageIME, "
4613 "foundTIS=%s, lang=%s",
4614 this, GetCharacters(foundTIS), GetCharacters(lang)));
4620 ::CFRelease(locale);
4625 ::CFRelease(langList);
4628 void IMEInputHandler::OnSelectionChange(const IMENotification& aIMENotification) {
4629 MOZ_ASSERT(aIMENotification.mSelectionChangeData.IsInitialized());
4630 MOZ_LOG(gIMELog, LogLevel::Info, ("%p IMEInputHandler::OnSelectionChange", this));
4632 if (!aIMENotification.mSelectionChangeData.HasRange()) {
4633 mSelectedRange.location = NSNotFound;
4634 mSelectedRange.length = 0;
4635 mRangeForWritingMode.location = NSNotFound;
4636 mRangeForWritingMode.length = 0;
4640 mWritingMode = aIMENotification.mSelectionChangeData.GetWritingMode();
4641 mRangeForWritingMode = NSMakeRange(aIMENotification.mSelectionChangeData.mOffset,
4642 aIMENotification.mSelectionChangeData.Length());
4644 mSelectedRange = mRangeForWritingMode;
4648 void IMEInputHandler::OnLayoutChange() {
4649 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
4654 NSTextInputContext* inputContext = [mView inputContext];
4655 [inputContext invalidateCharacterCoordinates];
4657 NS_OBJC_END_TRY_IGNORE_BLOCK;
4660 bool IMEInputHandler::OnHandleEvent(NSEvent* aEvent) {
4665 bool allowConsumeEvent = true;
4666 if (nsCocoaFeatures::OnCatalinaOrLater() && !IsIMEComposing()) {
4667 // Hack for bug of Korean IMEs on Catalina (10.15).
4668 // If we are inactivated during composition, active Korean IME keeps
4669 // consuming all mousedown events of any mouse buttons. So, we should
4670 // allow Korean IMEs to handle mousedown events only when there is
4671 // composition string.
4672 // List of ID of Korean IME:
4673 // * com.apple.inputmethod.Korean.2SetKorean
4674 // * com.apple.inputmethod.Korean.3SetKorean
4675 // * com.apple.inputmethod.Korean.390Sebulshik
4676 // * com.apple.inputmethod.Korean.GongjinCheongRomaja
4677 // * com.apple.inputmethod.Korean.HNCRomaja
4678 TISInputSourceWrapper tis;
4679 tis.InitByCurrentInputSource();
4680 nsAutoString inputSourceID;
4681 tis.GetInputSourceID(inputSourceID);
4682 allowConsumeEvent = !StringBeginsWith(inputSourceID, u"com.apple.inputmethod.Korean."_ns);
4684 NSTextInputContext* inputContext = [mView inputContext];
4685 return [inputContext handleEvent:aEvent] && allowConsumeEvent;
4690 /******************************************************************************
4692 * TextInputHandlerBase implementation
4694 ******************************************************************************/
4696 int32_t TextInputHandlerBase::sSecureEventInputCount = 0;
4698 NS_IMPL_ISUPPORTS(TextInputHandlerBase, TextEventDispatcherListener, nsISupportsWeakReference)
4700 TextInputHandlerBase::TextInputHandlerBase(nsChildView* aWidget, NSView<mozView>* aNativeView)
4701 : mWidget(aWidget), mDispatcher(aWidget->GetTextEventDispatcher()) {
4702 gHandlerInstanceCount++;
4703 mView = [aNativeView retain];
4706 TextInputHandlerBase::~TextInputHandlerBase() {
4708 if (--gHandlerInstanceCount == 0) {
4709 TISInputSourceWrapper::Shutdown();
4713 bool TextInputHandlerBase::OnDestroyWidget(nsChildView* aDestroyingWidget) {
4714 MOZ_LOG_KEY_OR_IME(LogLevel::Info, ("%p TextInputHandlerBase::OnDestroyWidget, "
4715 "aDestroyingWidget=%p, mWidget=%p",
4716 this, aDestroyingWidget, mWidget));
4718 if (aDestroyingWidget != mWidget) {
4723 mDispatcher = nullptr;
4727 bool TextInputHandlerBase::DispatchEvent(WidgetGUIEvent& aEvent) {
4728 return mWidget->DispatchWindowEvent(aEvent);
4731 void TextInputHandlerBase::InitKeyEvent(NSEvent* aNativeKeyEvent, WidgetKeyboardEvent& aKeyEvent,
4732 bool aIsProcessedByIME, const nsAString* aInsertString) {
4733 NS_ASSERTION(aNativeKeyEvent, "aNativeKeyEvent must not be NULL");
4735 if (mKeyboardOverride.mOverrideEnabled) {
4736 TISInputSourceWrapper tis;
4737 tis.InitByLayoutID(mKeyboardOverride.mKeyboardLayout, true);
4738 tis.InitKeyEvent(aNativeKeyEvent, aKeyEvent, aIsProcessedByIME, aInsertString);
4741 TISInputSourceWrapper::CurrentInputSource().InitKeyEvent(aNativeKeyEvent, aKeyEvent,
4742 aIsProcessedByIME, aInsertString);
4745 nsresult TextInputHandlerBase::SynthesizeNativeKeyEvent(int32_t aNativeKeyboardLayout,
4746 int32_t aNativeKeyCode,
4747 uint32_t aModifierFlags,
4748 const nsAString& aCharacters,
4749 const nsAString& aUnmodifiedCharacters) {
4750 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4752 uint32_t modifierFlags = nsCocoaUtils::ConvertWidgetModifiersToMacModifierFlags(
4753 static_cast<nsIWidget::Modifiers>(aModifierFlags));
4754 NSInteger windowNumber = [[mView window] windowNumber];
4755 bool sendFlagsChangedEvent = IsModifierKey(aNativeKeyCode);
4756 NSEventType eventType = sendFlagsChangedEvent ? NSEventTypeFlagsChanged : NSEventTypeKeyDown;
4757 NSEvent* downEvent = [NSEvent keyEventWithType:eventType
4758 location:NSMakePoint(0, 0)
4759 modifierFlags:modifierFlags
4761 windowNumber:windowNumber
4763 characters:nsCocoaUtils::ToNSString(aCharacters)
4764 charactersIgnoringModifiers:nsCocoaUtils::ToNSString(aUnmodifiedCharacters)
4766 keyCode:aNativeKeyCode];
4768 NSEvent* upEvent = sendFlagsChangedEvent
4770 : nsCocoaUtils::MakeNewCocoaEventWithType(NSEventTypeKeyUp, downEvent);
4772 if (downEvent && (sendFlagsChangedEvent || upEvent)) {
4773 KeyboardLayoutOverride currentLayout = mKeyboardOverride;
4774 mKeyboardOverride.mKeyboardLayout = aNativeKeyboardLayout;
4775 mKeyboardOverride.mOverrideEnabled = true;
4776 [NSApp sendEvent:downEvent];
4778 [NSApp sendEvent:upEvent];
4780 // processKeyDownEvent and keyUp block exceptions so we're sure to
4781 // reach here to restore mKeyboardOverride
4782 mKeyboardOverride = currentLayout;
4787 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
4790 NSInteger TextInputHandlerBase::GetWindowLevel() {
4791 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4793 MOZ_LOG_KEY_OR_IME(LogLevel::Info, ("%p TextInputHandlerBase::GetWindowLevel, Destryoed()=%s",
4794 this, TrueOrFalse(Destroyed())));
4797 return NSNormalWindowLevel;
4800 // When an <input> element on a XUL <panel> is focused, the actual focused view
4801 // is the panel's parent view (mView). But the editor is displayed on the
4802 // popped-up widget's view (editorView). We want the latter's window level.
4803 NSView<mozView>* editorView = mWidget->GetEditorView();
4804 NS_ENSURE_TRUE(editorView, NSNormalWindowLevel);
4805 NSInteger windowLevel = [[editorView window] level];
4807 MOZ_LOG_KEY_OR_IME(LogLevel::Info,
4808 ("%p TextInputHandlerBase::GetWindowLevel, windowLevel=%s (%lX)", this,
4809 GetWindowLevelName(windowLevel), static_cast<unsigned long>(windowLevel)));
4813 NS_OBJC_END_TRY_BLOCK_RETURN(NSNormalWindowLevel);
4817 TextInputHandlerBase::AttachNativeKeyEvent(WidgetKeyboardEvent& aKeyEvent) {
4818 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4820 // Don't try to replace a native event if one already exists.
4821 // OS X doesn't have an OS modifier, can't make a native event.
4822 if (aKeyEvent.mNativeKeyEvent || aKeyEvent.mModifiers & MODIFIER_OS) {
4826 MOZ_LOG_KEY_OR_IME(LogLevel::Info,
4827 ("%p TextInputHandlerBase::AttachNativeKeyEvent, key=0x%X, char=0x%X, "
4829 this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode, aKeyEvent.mModifiers));
4831 NSInteger windowNumber = [[mView window] windowNumber];
4832 NSGraphicsContext* context = [NSGraphicsContext currentContext];
4833 aKeyEvent.mNativeKeyEvent =
4834 nsCocoaUtils::MakeNewCococaEventFromWidgetEvent(aKeyEvent, windowNumber, context);
4838 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
4841 bool TextInputHandlerBase::SetSelection(NSRange& aRange) {
4842 MOZ_ASSERT(!Destroyed());
4844 RefPtr<TextInputHandlerBase> kungFuDeathGrip(this);
4845 WidgetSelectionEvent selectionEvent(true, eSetSelection, mWidget);
4846 selectionEvent.mOffset = aRange.location;
4847 selectionEvent.mLength = aRange.length;
4848 selectionEvent.mReversed = false;
4849 selectionEvent.mExpandToClusterBoundary = false;
4850 DispatchEvent(selectionEvent);
4851 NS_ENSURE_TRUE(selectionEvent.mSucceeded, false);
4852 return !Destroyed();
4855 /* static */ bool TextInputHandlerBase::IsPrintableChar(char16_t aChar) {
4856 return (aChar >= 0x20 && aChar <= 0x7E) || aChar >= 0xA0;
4859 /* static */ bool TextInputHandlerBase::IsSpecialGeckoKey(UInt32 aNativeKeyCode) {
4860 // this table is used to determine which keys are special and should not
4861 // generate a charCode
4862 switch (aNativeKeyCode) {
4863 // modifiers - we don't get separate events for these yet
4866 case kVK_RightShift:
4868 case kVK_RightCommand:
4871 case kVK_RightControl:
4873 case kVK_RightOption:
4874 case kVK_ANSI_KeypadClear:
4891 case kVK_PC_ScrollLock:
4892 case kVK_PC_PrintScreen:
4901 case kVK_PC_Backspace:
4902 case kVK_PC_ContextMenu:
4912 case kVK_RightArrow:
4916 case kVK_ANSI_KeypadEnter:
4917 case kVK_Powerbook_KeypadEnter:
4923 /* static */ bool TextInputHandlerBase::IsNormalCharInputtingEvent(NSEvent* aNativeEvent) {
4924 if ([aNativeEvent type] != NSEventTypeKeyDown && [aNativeEvent type] != NSEventTypeKeyUp) {
4927 nsAutoString nativeChars;
4928 nsCocoaUtils::GetStringForNSString([aNativeEvent characters], nativeChars);
4930 // this is not character inputting event, simply.
4931 if (nativeChars.IsEmpty() || ([aNativeEvent modifierFlags] & NSEventModifierFlagCommand)) {
4934 return !IsControlChar(nativeChars[0]);
4937 /* static */ bool TextInputHandlerBase::IsModifierKey(UInt32 aNativeKeyCode) {
4938 switch (aNativeKeyCode) {
4940 case kVK_RightCommand:
4945 case kVK_RightShift:
4946 case kVK_RightOption:
4947 case kVK_RightControl:
4954 /* static */ void TextInputHandlerBase::EnableSecureEventInput() {
4955 sSecureEventInputCount++;
4956 ::EnableSecureEventInput();
4959 /* static */ void TextInputHandlerBase::DisableSecureEventInput() {
4960 if (!sSecureEventInputCount) {
4963 sSecureEventInputCount--;
4964 ::DisableSecureEventInput();
4967 /* static */ bool TextInputHandlerBase::IsSecureEventInputEnabled() {
4968 // sSecureEventInputCount is our mechanism to track when Secure Event Input
4969 // is enabled. Non-zero indicates we have enabled Secure Input. But
4970 // zero does not mean that Secure Input is _disabled_ because another
4971 // application may have enabled it. If the OS reports Secure Event
4972 // Input is disabled though, a non-zero sSecureEventInputCount is an error.
4974 ::IsSecureEventInputEnabled() || 0 == sSecureEventInputCount,
4975 "sSecureEventInputCount is not zero when the OS thinks SecureEventInput is disabled.");
4976 return !!sSecureEventInputCount;
4979 /* static */ void TextInputHandlerBase::EnsureSecureEventInputDisabled() {
4980 while (sSecureEventInputCount) {
4981 TextInputHandlerBase::DisableSecureEventInput();
4987 /******************************************************************************
4989 * TextInputHandlerBase::KeyEventState implementation
4991 ******************************************************************************/
4993 void TextInputHandlerBase::KeyEventState::InitKeyEvent(TextInputHandlerBase* aHandler,
4994 WidgetKeyboardEvent& aKeyEvent,
4995 bool aIsProcessedByIME) {
4996 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
4998 MOZ_ASSERT(aHandler);
4999 MOZ_RELEASE_ASSERT(mKeyEvent);
5001 NSEvent* nativeEvent = mKeyEvent;
5002 if (!mInsertedString.IsEmpty()) {
5003 nsAutoString unhandledString;
5004 GetUnhandledString(unhandledString);
5005 NSString* unhandledNSString = nsCocoaUtils::ToNSString(unhandledString);
5006 // If the key event's some characters were already handled by
5007 // InsertString() calls, we need to create a dummy event which doesn't
5008 // include the handled characters.
5009 nativeEvent = [NSEvent keyEventWithType:[mKeyEvent type]
5010 location:[mKeyEvent locationInWindow]
5011 modifierFlags:[mKeyEvent modifierFlags]
5012 timestamp:[mKeyEvent timestamp]
5013 windowNumber:[mKeyEvent windowNumber]
5015 characters:unhandledNSString
5016 charactersIgnoringModifiers:[mKeyEvent charactersIgnoringModifiers]
5017 isARepeat:[mKeyEvent isARepeat]
5018 keyCode:[mKeyEvent keyCode]];
5021 aKeyEvent.mUniqueId = mUniqueId;
5022 aHandler->InitKeyEvent(nativeEvent, aKeyEvent, aIsProcessedByIME, mInsertString);
5024 NS_OBJC_END_TRY_IGNORE_BLOCK;
5027 void TextInputHandlerBase::KeyEventState::GetUnhandledString(nsAString& aUnhandledString) const {
5028 aUnhandledString.Truncate();
5029 if (NS_WARN_IF(!mKeyEvent)) {
5032 nsAutoString characters;
5033 nsCocoaUtils::GetStringForNSString([mKeyEvent characters], characters);
5034 if (characters.IsEmpty()) {
5037 if (mInsertedString.IsEmpty()) {
5038 aUnhandledString = characters;
5042 // The insertes string must match with the start of characters.
5043 MOZ_ASSERT(StringBeginsWith(characters, mInsertedString));
5045 aUnhandledString = nsDependentSubstring(characters, mInsertedString.Length());
5050 /******************************************************************************
5052 * TextInputHandlerBase::AutoInsertStringClearer implementation
5054 ******************************************************************************/
5056 TextInputHandlerBase::AutoInsertStringClearer::~AutoInsertStringClearer() {
5057 if (mState && mState->mInsertString) {
5058 // If inserting string is a part of characters of the event,
5059 // we should record it as inserted string.
5060 nsAutoString characters;
5061 nsCocoaUtils::GetStringForNSString([mState->mKeyEvent characters], characters);
5062 nsAutoString insertedString(mState->mInsertedString);
5063 insertedString += *mState->mInsertString;
5064 if (StringBeginsWith(characters, insertedString)) {
5065 mState->mInsertedString = insertedString;
5069 mState->mInsertString = nullptr;
5073 #undef MOZ_LOG_KEY_OR_IME