Bumping manifests a=b2g-bump
[gecko.git] / widget / cocoa / TextInputHandler.mm
blobab0a3c64826435b72a70d86ff30fe60cf9ae358d
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 "mozilla/ArrayUtils.h"
9 #include "TextInputHandler.h"
11 #include "prlog.h"
13 #include "mozilla/MiscEvents.h"
14 #include "mozilla/MouseEvents.h"
15 #include "mozilla/TextEvents.h"
17 #include "nsChildView.h"
18 #include "nsObjCExceptions.h"
19 #include "nsBidiUtils.h"
20 #include "nsToolkit.h"
21 #include "nsCocoaUtils.h"
22 #include "WidgetUtils.h"
23 #include "nsPrintfCString.h"
24 #include "ComplexTextInputPanel.h"
26 using namespace mozilla;
27 using namespace mozilla::widget;
29 #ifdef PR_LOGGING
31 PRLogModuleInfo* gLog = nullptr;
33 static const char*
34 OnOrOff(bool aBool)
36   return aBool ? "ON" : "off";
39 static const char*
40 TrueOrFalse(bool aBool)
42   return aBool ? "TRUE" : "FALSE";
45 static const char*
46 GetKeyNameForNativeKeyCode(unsigned short aNativeKeyCode)
48   switch (aNativeKeyCode) {
49     case kVK_Escape:              return "Escape";
50     case kVK_RightCommand:        return "Right-Command";
51     case kVK_Command:             return "Command";
52     case kVK_Shift:               return "Shift";
53     case kVK_CapsLock:            return "CapsLock";
54     case kVK_Option:              return "Option";
55     case kVK_Control:             return "Control";
56     case kVK_RightShift:          return "Right-Shift";
57     case kVK_RightOption:         return "Right-Option";
58     case kVK_RightControl:        return "Right-Control";
59     case kVK_ANSI_KeypadClear:    return "Clear";
61     case kVK_F1:                  return "F1";
62     case kVK_F2:                  return "F2";
63     case kVK_F3:                  return "F3";
64     case kVK_F4:                  return "F4";
65     case kVK_F5:                  return "F5";
66     case kVK_F6:                  return "F6";
67     case kVK_F7:                  return "F7";
68     case kVK_F8:                  return "F8";
69     case kVK_F9:                  return "F9";
70     case kVK_F10:                 return "F10";
71     case kVK_F11:                 return "F11";
72     case kVK_F12:                 return "F12";
73     case kVK_F13:                 return "F13/PrintScreen";
74     case kVK_F14:                 return "F14/ScrollLock";
75     case kVK_F15:                 return "F15/Pause";
77     case kVK_ANSI_Keypad0:        return "NumPad-0";
78     case kVK_ANSI_Keypad1:        return "NumPad-1";
79     case kVK_ANSI_Keypad2:        return "NumPad-2";
80     case kVK_ANSI_Keypad3:        return "NumPad-3";
81     case kVK_ANSI_Keypad4:        return "NumPad-4";
82     case kVK_ANSI_Keypad5:        return "NumPad-5";
83     case kVK_ANSI_Keypad6:        return "NumPad-6";
84     case kVK_ANSI_Keypad7:        return "NumPad-7";
85     case kVK_ANSI_Keypad8:        return "NumPad-8";
86     case kVK_ANSI_Keypad9:        return "NumPad-9";
88     case kVK_ANSI_KeypadMultiply: return "NumPad-*";
89     case kVK_ANSI_KeypadPlus:     return "NumPad-+";
90     case kVK_ANSI_KeypadMinus:    return "NumPad--";
91     case kVK_ANSI_KeypadDecimal:  return "NumPad-.";
92     case kVK_ANSI_KeypadDivide:   return "NumPad-/";
93     case kVK_ANSI_KeypadEquals:   return "NumPad-=";
94     case kVK_ANSI_KeypadEnter:    return "NumPad-Enter";
95     case kVK_Return:              return "Return";
96     case kVK_Powerbook_KeypadEnter: return "NumPad-EnterOnPowerBook";
98     case kVK_PC_Insert:           return "Insert/Help";
99     case kVK_PC_Delete:           return "Delete";
100     case kVK_Tab:                 return "Tab";
101     case kVK_PC_Backspace:        return "Backspace";
102     case kVK_Home:                return "Home";
103     case kVK_End:                 return "End";
104     case kVK_PageUp:              return "PageUp";
105     case kVK_PageDown:            return "PageDown";
106     case kVK_LeftArrow:           return "LeftArrow";
107     case kVK_RightArrow:          return "RightArrow";
108     case kVK_UpArrow:             return "UpArrow";
109     case kVK_DownArrow:           return "DownArrow";
110     case kVK_PC_ContextMenu:      return "ContextMenu";
112     case kVK_Function:            return "Function";
113     case kVK_VolumeUp:            return "VolumeUp";
114     case kVK_VolumeDown:          return "VolumeDown";
115     case kVK_Mute:                return "Mute";
117     case kVK_ISO_Section:         return "ISO_Section";
119     case kVK_JIS_Yen:             return "JIS_Yen";
120     case kVK_JIS_Underscore:      return "JIS_Underscore";
121     case kVK_JIS_KeypadComma:     return "JIS_KeypadComma";
122     case kVK_JIS_Eisu:            return "JIS_Eisu";
123     case kVK_JIS_Kana:            return "JIS_Kana";
125     case kVK_ANSI_A:              return "A";
126     case kVK_ANSI_B:              return "B";
127     case kVK_ANSI_C:              return "C";
128     case kVK_ANSI_D:              return "D";
129     case kVK_ANSI_E:              return "E";
130     case kVK_ANSI_F:              return "F";
131     case kVK_ANSI_G:              return "G";
132     case kVK_ANSI_H:              return "H";
133     case kVK_ANSI_I:              return "I";
134     case kVK_ANSI_J:              return "J";
135     case kVK_ANSI_K:              return "K";
136     case kVK_ANSI_L:              return "L";
137     case kVK_ANSI_M:              return "M";
138     case kVK_ANSI_N:              return "N";
139     case kVK_ANSI_O:              return "O";
140     case kVK_ANSI_P:              return "P";
141     case kVK_ANSI_Q:              return "Q";
142     case kVK_ANSI_R:              return "R";
143     case kVK_ANSI_S:              return "S";
144     case kVK_ANSI_T:              return "T";
145     case kVK_ANSI_U:              return "U";
146     case kVK_ANSI_V:              return "V";
147     case kVK_ANSI_W:              return "W";
148     case kVK_ANSI_X:              return "X";
149     case kVK_ANSI_Y:              return "Y";
150     case kVK_ANSI_Z:              return "Z";
152     case kVK_ANSI_1:              return "1";
153     case kVK_ANSI_2:              return "2";
154     case kVK_ANSI_3:              return "3";
155     case kVK_ANSI_4:              return "4";
156     case kVK_ANSI_5:              return "5";
157     case kVK_ANSI_6:              return "6";
158     case kVK_ANSI_7:              return "7";
159     case kVK_ANSI_8:              return "8";
160     case kVK_ANSI_9:              return "9";
161     case kVK_ANSI_0:              return "0";
162     case kVK_ANSI_Equal:          return "Equal";
163     case kVK_ANSI_Minus:          return "Minus";
164     case kVK_ANSI_RightBracket:   return "RightBracket";
165     case kVK_ANSI_LeftBracket:    return "LeftBracket";
166     case kVK_ANSI_Quote:          return "Quote";
167     case kVK_ANSI_Semicolon:      return "Semicolon";
168     case kVK_ANSI_Backslash:      return "Backslash";
169     case kVK_ANSI_Comma:          return "Comma";
170     case kVK_ANSI_Slash:          return "Slash";
171     case kVK_ANSI_Period:         return "Period";
172     case kVK_ANSI_Grave:          return "Grave";
174     default:                      return "undefined";
175   }
178 static const char*
179 GetCharacters(const NSString* aString)
181   nsAutoString str;
182   nsCocoaUtils::GetStringForNSString(aString, str);
183   if (str.IsEmpty()) {
184     return "";
185   }
187   nsAutoString escapedStr;
188   for (uint32_t i = 0; i < str.Length(); i++) {
189     char16_t ch = str[i];
190     if (ch < 0x20) {
191       nsPrintfCString utf8str("(U+%04X)", ch);
192       escapedStr += NS_ConvertUTF8toUTF16(utf8str);
193     } else if (ch <= 0x7E) {
194       escapedStr += ch;
195     } else {
196       nsPrintfCString utf8str("(U+%04X)", ch);
197       escapedStr += ch;
198       escapedStr += NS_ConvertUTF8toUTF16(utf8str);
199     }
200   }
202   // the result will be freed automatically by cocoa.
203   NSString* result = nsCocoaUtils::ToNSString(escapedStr);
204   return [result UTF8String];
207 static const char*
208 GetCharacters(const CFStringRef aString)
210   const NSString* str = reinterpret_cast<const NSString*>(aString);
211   return GetCharacters(str);
214 static const char*
215 GetNativeKeyEventType(NSEvent* aNativeEvent)
217   switch ([aNativeEvent type]) {
218     case NSKeyDown:      return "NSKeyDown";
219     case NSKeyUp:        return "NSKeyUp";
220     case NSFlagsChanged: return "NSFlagsChanged";
221     default:             return "not key event";
222   }
225 static const char*
226 GetGeckoKeyEventType(const WidgetEvent& aEvent)
228   switch (aEvent.message) {
229     case NS_KEY_DOWN:    return "NS_KEY_DOWN";
230     case NS_KEY_UP:      return "NS_KEY_UP";
231     case NS_KEY_PRESS:   return "NS_KEY_PRESS";
232     default:             return "not key event";
233   }
236 static const char*
237 GetRangeTypeName(uint32_t aRangeType)
239   switch (aRangeType) {
240     case NS_TEXTRANGE_RAWINPUT:
241       return "NS_TEXTRANGE_RAWINPUT";
242     case NS_TEXTRANGE_CONVERTEDTEXT:
243       return "NS_TEXTRANGE_CONVERTEDTEXT";
244     case NS_TEXTRANGE_SELECTEDRAWTEXT:
245       return "NS_TEXTRANGE_SELECTEDRAWTEXT";
246     case NS_TEXTRANGE_SELECTEDCONVERTEDTEXT:
247       return "NS_TEXTRANGE_SELECTEDCONVERTEDTEXT";
248     case NS_TEXTRANGE_CARETPOSITION:
249       return "NS_TEXTRANGE_CARETPOSITION";
250     default:
251       return "invalid range type";
252   }
255 static const char*
256 GetWindowLevelName(NSInteger aWindowLevel)
258   switch (aWindowLevel) {
259     case kCGBaseWindowLevelKey:
260       return "kCGBaseWindowLevelKey (NSNormalWindowLevel)";
261     case kCGMinimumWindowLevelKey:
262       return "kCGMinimumWindowLevelKey";
263     case kCGDesktopWindowLevelKey:
264       return "kCGDesktopWindowLevelKey";
265     case kCGBackstopMenuLevelKey:
266       return "kCGBackstopMenuLevelKey";
267     case kCGNormalWindowLevelKey:
268       return "kCGNormalWindowLevelKey";
269     case kCGFloatingWindowLevelKey:
270       return "kCGFloatingWindowLevelKey (NSFloatingWindowLevel)";
271     case kCGTornOffMenuWindowLevelKey:
272       return "kCGTornOffMenuWindowLevelKey (NSSubmenuWindowLevel, NSTornOffMenuWindowLevel)";
273     case kCGDockWindowLevelKey:
274       return "kCGDockWindowLevelKey (NSDockWindowLevel)";
275     case kCGMainMenuWindowLevelKey:
276       return "kCGMainMenuWindowLevelKey (NSMainMenuWindowLevel)";
277     case kCGStatusWindowLevelKey:
278       return "kCGStatusWindowLevelKey (NSStatusWindowLevel)";
279     case kCGModalPanelWindowLevelKey:
280       return "kCGModalPanelWindowLevelKey (NSModalPanelWindowLevel)";
281     case kCGPopUpMenuWindowLevelKey:
282       return "kCGPopUpMenuWindowLevelKey (NSPopUpMenuWindowLevel)";
283     case kCGDraggingWindowLevelKey:
284       return "kCGDraggingWindowLevelKey";
285     case kCGScreenSaverWindowLevelKey:
286       return "kCGScreenSaverWindowLevelKey (NSScreenSaverWindowLevel)";
287     case kCGMaximumWindowLevelKey:
288       return "kCGMaximumWindowLevelKey";
289     case kCGOverlayWindowLevelKey:
290       return "kCGOverlayWindowLevelKey";
291     case kCGHelpWindowLevelKey:
292       return "kCGHelpWindowLevelKey";
293     case kCGUtilityWindowLevelKey:
294       return "kCGUtilityWindowLevelKey";
295     case kCGDesktopIconWindowLevelKey:
296       return "kCGDesktopIconWindowLevelKey";
297     case kCGCursorWindowLevelKey:
298       return "kCGCursorWindowLevelKey";
299     case kCGNumberOfWindowLevelKeys:
300       return "kCGNumberOfWindowLevelKeys";
301     default:
302       return "unknown window level";
303   }
306 #endif // #ifdef PR_LOGGING
308 static bool
309 IsControlChar(uint32_t aCharCode)
311   return aCharCode < ' ' || aCharCode == 0x7F;
314 static uint32_t gHandlerInstanceCount = 0;
315 static TISInputSourceWrapper gCurrentInputSource;
317 static void
318 InitLogModule()
320 #ifdef PR_LOGGING
321   // Clear() is always called when TISInputSourceWrappper is created.
322   if (!gLog) {
323     gLog = PR_NewLogModule("TextInputHandlerWidgets");
324     TextInputHandler::DebugPrintAllKeyboardLayouts();
325     IMEInputHandler::DebugPrintAllIMEModes();
326   }
327 #endif
330 static void
331 InitCurrentInputSource()
333   if (gHandlerInstanceCount > 0 &&
334       !gCurrentInputSource.IsInitializedByCurrentInputSource()) {
335     gCurrentInputSource.InitByCurrentInputSource();
336   }
339 static void
340 FinalizeCurrentInputSource()
342   gCurrentInputSource.Clear();
346 #pragma mark -
349 /******************************************************************************
351  *  TISInputSourceWrapper implementation
353  ******************************************************************************/
355 // static
356 TISInputSourceWrapper&
357 TISInputSourceWrapper::CurrentInputSource()
359   InitCurrentInputSource();
360   return gCurrentInputSource;
363 bool
364 TISInputSourceWrapper::TranslateToString(UInt32 aKeyCode, UInt32 aModifiers,
365                                          UInt32 aKbType, nsAString &aStr)
367   aStr.Truncate();
369   const UCKeyboardLayout* UCKey = GetUCKeyboardLayout();
371   PR_LOG(gLog, PR_LOG_ALWAYS,
372     ("%p TISInputSourceWrapper::TranslateToString, aKeyCode=0x%X, "
373      "aModifiers=0x%X, aKbType=0x%X UCKey=%p\n    "
374      "Shift: %s, Ctrl: %s, Opt: %s, Cmd: %s, CapsLock: %s, NumLock: %s",
375      this, aKeyCode, aModifiers, aKbType, UCKey,
376      OnOrOff(aModifiers & shiftKey), OnOrOff(aModifiers & controlKey),
377      OnOrOff(aModifiers & optionKey), OnOrOff(aModifiers & cmdKey),
378      OnOrOff(aModifiers & alphaLock),
379      OnOrOff(aModifiers & kEventKeyModifierNumLockMask)));
381   NS_ENSURE_TRUE(UCKey, false);
383   UInt32 deadKeyState = 0;
384   UniCharCount len;
385   UniChar chars[5];
386   OSStatus err = ::UCKeyTranslate(UCKey, aKeyCode,
387                                   kUCKeyActionDown, aModifiers >> 8,
388                                   aKbType, kUCKeyTranslateNoDeadKeysMask,
389                                   &deadKeyState, 5, &len, chars);
391   PR_LOG(gLog, PR_LOG_ALWAYS,
392     ("%p TISInputSourceWrapper::TranslateToString, err=0x%X, len=%llu",
393      this, err, len));
395   NS_ENSURE_TRUE(err == noErr, false);
396   if (len == 0) {
397     return true;
398   }
399   NS_ENSURE_TRUE(EnsureStringLength(aStr, len), false);
400   NS_ASSERTION(sizeof(char16_t) == sizeof(UniChar),
401                "size of char16_t and size of UniChar are different");
402   memcpy(aStr.BeginWriting(), chars, len * sizeof(char16_t));
404   PR_LOG(gLog, PR_LOG_ALWAYS,
405     ("%p TISInputSourceWrapper::TranslateToString, aStr=\"%s\"",
406      this, NS_ConvertUTF16toUTF8(aStr).get()));
408   return true;
411 uint32_t
412 TISInputSourceWrapper::TranslateToChar(UInt32 aKeyCode, UInt32 aModifiers,
413                                        UInt32 aKbType)
415   nsAutoString str;
416   if (!TranslateToString(aKeyCode, aModifiers, aKbType, str) ||
417       str.Length() != 1) {
418     return 0;
419   }
420   return static_cast<uint32_t>(str.CharAt(0));
423 void
424 TISInputSourceWrapper::InitByInputSourceID(const char* aID)
426   Clear();
427   if (!aID)
428     return;
430   CFStringRef idstr = ::CFStringCreateWithCString(kCFAllocatorDefault, aID,
431                                                   kCFStringEncodingASCII);
432   InitByInputSourceID(idstr);
433   ::CFRelease(idstr);
436 void
437 TISInputSourceWrapper::InitByInputSourceID(const nsAFlatString &aID)
439   Clear();
440   if (aID.IsEmpty())
441     return;
442   CFStringRef idstr = ::CFStringCreateWithCharacters(kCFAllocatorDefault,
443                                                      reinterpret_cast<const UniChar*>(aID.get()),
444                                                      aID.Length());
445   InitByInputSourceID(idstr);
446   ::CFRelease(idstr);
449 void
450 TISInputSourceWrapper::InitByInputSourceID(const CFStringRef aID)
452   Clear();
453   if (!aID)
454     return;
455   const void* keys[] = { kTISPropertyInputSourceID };
456   const void* values[] = { aID };
457   CFDictionaryRef filter =
458   ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
459   NS_ASSERTION(filter, "failed to create the filter");
460   mInputSourceList = ::TISCreateInputSourceList(filter, true);
461   ::CFRelease(filter);
462   if (::CFArrayGetCount(mInputSourceList) > 0) {
463     mInputSource = static_cast<TISInputSourceRef>(
464       const_cast<void *>(::CFArrayGetValueAtIndex(mInputSourceList, 0)));
465     if (IsKeyboardLayout()) {
466       mKeyboardLayout = mInputSource;
467     }
468   }
471 void
472 TISInputSourceWrapper::InitByLayoutID(SInt32 aLayoutID,
473                                       bool aOverrideKeyboard)
475   // NOTE: Doument new layout IDs in TextInputHandler.h when you add ones.
476   switch (aLayoutID) {
477     case 0:
478       InitByInputSourceID("com.apple.keylayout.US");
479       break;
480     case 1:
481       InitByInputSourceID("com.apple.keylayout.Greek");
482       break;
483     case 2:
484       InitByInputSourceID("com.apple.keylayout.German");
485       break;
486     case 3:
487       InitByInputSourceID("com.apple.keylayout.Swedish-Pro");
488       break;
489     case 4:
490       InitByInputSourceID("com.apple.keylayout.DVORAK-QWERTYCMD");
491       break;
492     case 5:
493       InitByInputSourceID("com.apple.keylayout.Thai");
494       break;
495     case 6:
496       InitByInputSourceID("com.apple.keylayout.Arabic");
497       break;
498     case 7:
499       InitByInputSourceID("com.apple.keylayout.French");
500       break;
501     case 8:
502       InitByInputSourceID("com.apple.keylayout.Hebrew");
503       break;
504     case 9:
505       InitByInputSourceID("com.apple.keylayout.Lithuanian");
506       break;
507     case 10:
508       InitByInputSourceID("com.apple.keylayout.Norwegian");
509       break;
510     case 11:
511       InitByInputSourceID("com.apple.keylayout.Spanish");
512       break;
513     default:
514       Clear();
515       break;
516   }
517   mOverrideKeyboard = aOverrideKeyboard;
520 void
521 TISInputSourceWrapper::InitByCurrentInputSource()
523   Clear();
524   mInputSource = ::TISCopyCurrentKeyboardInputSource();
525   mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride();
526   if (!mKeyboardLayout) {
527     mKeyboardLayout = ::TISCopyCurrentKeyboardLayoutInputSource();
528   }
529   // If this causes composition, the current keyboard layout may input non-ASCII
530   // characters such as Japanese Kana characters or Hangul characters.
531   // However, we need to set ASCII characters to DOM key events for consistency
532   // with other platforms.
533   if (IsOpenedIMEMode()) {
534     TISInputSourceWrapper tis(mKeyboardLayout);
535     if (!tis.IsASCIICapable()) {
536       mKeyboardLayout =
537         ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
538     }
539   }
542 void
543 TISInputSourceWrapper::InitByCurrentKeyboardLayout()
545   Clear();
546   mInputSource = ::TISCopyCurrentKeyboardLayoutInputSource();
547   mKeyboardLayout = mInputSource;
550 void
551 TISInputSourceWrapper::InitByCurrentASCIICapableInputSource()
553   Clear();
554   mInputSource = ::TISCopyCurrentASCIICapableKeyboardInputSource();
555   mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride();
556   if (mKeyboardLayout) {
557     TISInputSourceWrapper tis(mKeyboardLayout);
558     if (!tis.IsASCIICapable()) {
559       mKeyboardLayout = nullptr;
560     }
561   }
562   if (!mKeyboardLayout) {
563     mKeyboardLayout =
564       ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
565   }
568 void
569 TISInputSourceWrapper::InitByCurrentASCIICapableKeyboardLayout()
571   Clear();
572   mInputSource = ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
573   mKeyboardLayout = mInputSource;
576 void
577 TISInputSourceWrapper::InitByCurrentInputMethodKeyboardLayoutOverride()
579   Clear();
580   mInputSource = ::TISCopyInputMethodKeyboardLayoutOverride();
581   mKeyboardLayout = mInputSource;
584 void
585 TISInputSourceWrapper::InitByTISInputSourceRef(TISInputSourceRef aInputSource)
587   Clear();
588   mInputSource = aInputSource;
589   if (IsKeyboardLayout()) {
590     mKeyboardLayout = mInputSource;
591   }
594 void
595 TISInputSourceWrapper::InitByLanguage(CFStringRef aLanguage)
597   Clear();
598   mInputSource = ::TISCopyInputSourceForLanguage(aLanguage);
599   if (IsKeyboardLayout()) {
600     mKeyboardLayout = mInputSource;
601   }
604 const UCKeyboardLayout*
605 TISInputSourceWrapper::GetUCKeyboardLayout()
607   NS_ENSURE_TRUE(mKeyboardLayout, nullptr);
608   if (mUCKeyboardLayout) {
609     return mUCKeyboardLayout;
610   }
611   CFDataRef uchr = static_cast<CFDataRef>(
612     ::TISGetInputSourceProperty(mKeyboardLayout,
613                                 kTISPropertyUnicodeKeyLayoutData));
615   // We should be always able to get the layout here.
616   NS_ENSURE_TRUE(uchr, nullptr);
617   mUCKeyboardLayout =
618     reinterpret_cast<const UCKeyboardLayout*>(CFDataGetBytePtr(uchr));
619   return mUCKeyboardLayout;
622 bool
623 TISInputSourceWrapper::GetBoolProperty(const CFStringRef aKey)
625   CFBooleanRef ret = static_cast<CFBooleanRef>(
626     ::TISGetInputSourceProperty(mInputSource, aKey));
627   return ::CFBooleanGetValue(ret);
630 bool
631 TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey,
632                                          CFStringRef &aStr)
634   aStr = static_cast<CFStringRef>(
635     ::TISGetInputSourceProperty(mInputSource, aKey));
636   return aStr != nullptr;
639 bool
640 TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey,
641                                          nsAString &aStr)
643   CFStringRef str;
644   GetStringProperty(aKey, str);
645   nsCocoaUtils::GetStringForNSString((const NSString*)str, aStr);
646   return !aStr.IsEmpty();
649 bool
650 TISInputSourceWrapper::IsOpenedIMEMode()
652   NS_ENSURE_TRUE(mInputSource, false);
653   if (!IsIMEMode())
654     return false;
655   return !IsASCIICapable();
658 bool
659 TISInputSourceWrapper::IsIMEMode()
661   NS_ENSURE_TRUE(mInputSource, false);
662   CFStringRef str;
663   GetInputSourceType(str);
664   NS_ENSURE_TRUE(str, false);
665   return ::CFStringCompare(kTISTypeKeyboardInputMode,
666                            str, 0) == kCFCompareEqualTo;
669 bool
670 TISInputSourceWrapper::IsKeyboardLayout()
672   NS_ENSURE_TRUE(mInputSource, false);
673   CFStringRef str;
674   GetInputSourceType(str);
675   NS_ENSURE_TRUE(str, false);
676   return ::CFStringCompare(kTISTypeKeyboardLayout,
677                            str, 0) == kCFCompareEqualTo;
680 bool
681 TISInputSourceWrapper::GetLanguageList(CFArrayRef &aLanguageList)
683   NS_ENSURE_TRUE(mInputSource, false);
684   aLanguageList = static_cast<CFArrayRef>(
685     ::TISGetInputSourceProperty(mInputSource,
686                                 kTISPropertyInputSourceLanguages));
687   return aLanguageList != nullptr;
690 bool
691 TISInputSourceWrapper::GetPrimaryLanguage(CFStringRef &aPrimaryLanguage)
693   NS_ENSURE_TRUE(mInputSource, false);
694   CFArrayRef langList;
695   NS_ENSURE_TRUE(GetLanguageList(langList), false);
696   if (::CFArrayGetCount(langList) == 0)
697     return false;
698   aPrimaryLanguage =
699     static_cast<CFStringRef>(::CFArrayGetValueAtIndex(langList, 0));
700   return aPrimaryLanguage != nullptr;
703 bool
704 TISInputSourceWrapper::GetPrimaryLanguage(nsAString &aPrimaryLanguage)
706   NS_ENSURE_TRUE(mInputSource, false);
707   CFStringRef primaryLanguage;
708   NS_ENSURE_TRUE(GetPrimaryLanguage(primaryLanguage), false);
709   nsCocoaUtils::GetStringForNSString((const NSString*)primaryLanguage,
710                                      aPrimaryLanguage);
711   return !aPrimaryLanguage.IsEmpty();
714 bool
715 TISInputSourceWrapper::IsForRTLLanguage()
717   if (mIsRTL < 0) {
718     // Get the input character of the 'A' key of ANSI keyboard layout.
719     nsAutoString str;
720     bool ret = TranslateToString(kVK_ANSI_A, 0, eKbdType_ANSI, str);
721     NS_ENSURE_TRUE(ret, ret);
722     char16_t ch = str.IsEmpty() ? char16_t(0) : str.CharAt(0);
723     mIsRTL = UCS2_CHAR_IS_BIDI(ch) || ch == 0xD802 || ch == 0xD803;
724   }
725   return mIsRTL != 0;
728 bool
729 TISInputSourceWrapper::IsInitializedByCurrentInputSource()
731   return mInputSource == ::TISCopyCurrentKeyboardInputSource();
734 void
735 TISInputSourceWrapper::Select()
737   if (!mInputSource)
738     return;
739   ::TISSelectInputSource(mInputSource);
742 void
743 TISInputSourceWrapper::Clear()
745   // Clear() is always called when TISInputSourceWrappper is created.
746   InitLogModule();
748   if (mInputSourceList) {
749     ::CFRelease(mInputSourceList);
750   }
751   mInputSourceList = nullptr;
752   mInputSource = nullptr;
753   mKeyboardLayout = nullptr;
754   mIsRTL = -1;
755   mUCKeyboardLayout = nullptr;
756   mOverrideKeyboard = false;
759 void
760 TISInputSourceWrapper::InitKeyEvent(NSEvent *aNativeKeyEvent,
761                                     WidgetKeyboardEvent& aKeyEvent,
762                                     const nsAString *aInsertString)
764   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
766   PR_LOG(gLog, PR_LOG_ALWAYS,
767     ("%p TISInputSourceWrapper::InitKeyEvent, aNativeKeyEvent=%p, "
768      "aKeyEvent.message=%s, aInsertString=%p, IsOpenedIMEMode()=%s",
769      this, aNativeKeyEvent, GetGeckoKeyEventType(aKeyEvent), aInsertString,
770      TrueOrFalse(IsOpenedIMEMode())));
772   NS_ENSURE_TRUE(aNativeKeyEvent, );
774   nsCocoaUtils::InitInputEvent(aKeyEvent, aNativeKeyEvent);
776   // This is used only while dispatching the event (which is a synchronous
777   // call), so there is no need to retain and release this data.
778   aKeyEvent.mNativeKeyEvent = aNativeKeyEvent;
780   // Fill in fields used for Cocoa NPAPI plugins
781   if ([aNativeKeyEvent type] == NSKeyDown ||
782       [aNativeKeyEvent type] == NSKeyUp) {
783     aKeyEvent.mNativeKeyCode = [aNativeKeyEvent keyCode];
784     aKeyEvent.mNativeModifierFlags = [aNativeKeyEvent modifierFlags];
785     nsAutoString nativeChars;
786     nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], nativeChars);
787     aKeyEvent.mNativeCharacters.Assign(nativeChars);
788     nsAutoString nativeCharsIgnoringModifiers;
789     nsCocoaUtils::GetStringForNSString([aNativeKeyEvent charactersIgnoringModifiers], nativeCharsIgnoringModifiers);
790     aKeyEvent.mNativeCharactersIgnoringModifiers.Assign(nativeCharsIgnoringModifiers);
791   } else if ([aNativeKeyEvent type] == NSFlagsChanged) {
792     aKeyEvent.mNativeKeyCode = [aNativeKeyEvent keyCode];
793     aKeyEvent.mNativeModifierFlags = [aNativeKeyEvent modifierFlags];
794   }
796   aKeyEvent.refPoint = LayoutDeviceIntPoint(0, 0);
798   // If a keyboard layout override is set, we also need to force the keyboard
799   // type to something ANSI to avoid test failures on machines with JIS
800   // keyboards (since the pair of keyboard layout and physical keyboard type
801   // form the actual key layout).  This assumes that the test setting the
802   // override was written assuming an ANSI keyboard.
803   UInt32 kbType = mOverrideKeyboard ? eKbdType_ANSI : ::LMGetKbdType();
805   UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
807   bool isPrintableKey = !TextInputHandler::IsSpecialGeckoKey(nativeKeyCode);
808   if (isPrintableKey &&
809       [aNativeKeyEvent type] != NSKeyDown &&
810       [aNativeKeyEvent type] != NSKeyUp) {
811     NS_WARNING("Why the printable key doesn't cause NSKeyDown or NSKeyUp?");
812     isPrintableKey = false;
813   }
815   // Decide what string will be input.
816   nsAutoString insertString;
817   if (aInsertString) {
818     // If the caller expects that the aInsertString will be input, we shouldn't
819     // change it.
820     insertString = *aInsertString;
821   } else if (isPrintableKey) {
822     // If IME is open, [aNativeKeyEvent characters] may be a character
823     // which will be appended to the composition string.  However, especially,
824     // while IME is disabled, most users and developers expect the key event
825     // works as IME closed.  So, we should compute the insertString with
826     // the ASCII capable keyboard layout.
827     // NOTE: Such keyboard layouts typically change the layout to its ASCII
828     //       capable layout when Command key is pressed.  And we don't worry
829     //       when Control key is pressed too because it causes inputting
830     //       control characters.
831     if (!aKeyEvent.IsMeta() && !aKeyEvent.IsControl() && IsOpenedIMEMode()) {
832       UInt32 state =
833         nsCocoaUtils::ConvertToCarbonModifier([aNativeKeyEvent modifierFlags]);
834       uint32_t ch = TranslateToChar(nativeKeyCode, state, kbType);
835       if (ch) {
836         insertString = ch;
837       }
838     } else {
839       // If the caller isn't sure what string will be input, let's use
840       // characters of NSEvent.
841       nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters],
842                                          insertString);
843     }
845     // If control key is pressed and the eventChars is a non-printable control
846     // character, we should convert it to ASCII alphabet.
847     if (aKeyEvent.IsControl() &&
848         !insertString.IsEmpty() && insertString[0] <= char16_t(26)) {
849       insertString = (aKeyEvent.IsShift() ^ aKeyEvent.IsCapsLocked()) ?
850         static_cast<char16_t>(insertString[0] + ('A' - 1)) :
851         static_cast<char16_t>(insertString[0] + ('a' - 1));
852     }
853     // If Meta key is pressed, it may cause to switch the keyboard layout like
854     // Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY.
855     else if (aKeyEvent.IsMeta() &&
856              !(aKeyEvent.IsControl() || aKeyEvent.IsAlt())) {
857       UInt32 numLockState =
858         aKeyEvent.IsNumLocked() ? kEventKeyModifierNumLockMask : 0;
859       UInt32 capsLockState = aKeyEvent.IsCapsLocked() ? alphaLock : 0;
860       UInt32 shiftState = aKeyEvent.IsShift() ? shiftKey : 0;
861       uint32_t uncmdedChar =
862         TranslateToChar(nativeKeyCode, numLockState, kbType);
863       uint32_t cmdedChar =
864         TranslateToChar(nativeKeyCode, cmdKey | numLockState, kbType);
865       // If we can make a good guess at the characters that the user would
866       // expect this key combination to produce (with and without Shift) then
867       // use those characters.  This also corrects for CapsLock.
868       uint32_t ch = 0;
869       if (uncmdedChar == cmdedChar) {
870         // The characters produced with Command seem similar to those without
871         // Command.
872         ch = TranslateToChar(nativeKeyCode,
873                              shiftState | capsLockState | numLockState, kbType);
874       } else {
875         TISInputSourceWrapper USLayout("com.apple.keylayout.US");
876         uint32_t uncmdedUSChar =
877           USLayout.TranslateToChar(nativeKeyCode, numLockState, kbType);
878         // If it looks like characters from US keyboard layout when Command key
879         // is pressed, we should compute a character in the layout.
880         if (uncmdedUSChar == cmdedChar) {
881           ch = USLayout.TranslateToChar(nativeKeyCode,
882                           shiftState | capsLockState | numLockState, kbType);
883         }
884       }
886       // If there is a more preferred character for the commanded key event,
887       // we should use it.
888       if (ch) {
889         insertString = ch;
890       }
891     }
892   }
894   // Remove control characters which shouldn't be inputted on editor.
895   // XXX Currently, we don't find any cases inserting control characters with
896   //     printable character.  So, just checking first character is enough.
897   if (!insertString.IsEmpty() && IsControlChar(insertString[0])) {
898     insertString.Truncate();
899   }
901   aKeyEvent.keyCode =
902     ComputeGeckoKeyCode(nativeKeyCode, kbType, aKeyEvent.IsMeta());
904   switch (nativeKeyCode) {
905     case kVK_Command:
906     case kVK_Shift:
907     case kVK_Option:
908     case kVK_Control:
909       aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT;
910       break;
912     case kVK_RightCommand:
913     case kVK_RightShift:
914     case kVK_RightOption:
915     case kVK_RightControl:
916       aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT;
917       break;
919     case kVK_ANSI_Keypad0:
920     case kVK_ANSI_Keypad1:
921     case kVK_ANSI_Keypad2:
922     case kVK_ANSI_Keypad3:
923     case kVK_ANSI_Keypad4:
924     case kVK_ANSI_Keypad5:
925     case kVK_ANSI_Keypad6:
926     case kVK_ANSI_Keypad7:
927     case kVK_ANSI_Keypad8:
928     case kVK_ANSI_Keypad9:
929     case kVK_ANSI_KeypadMultiply:
930     case kVK_ANSI_KeypadPlus:
931     case kVK_ANSI_KeypadMinus:
932     case kVK_ANSI_KeypadDecimal:
933     case kVK_ANSI_KeypadDivide:
934     case kVK_ANSI_KeypadEquals:
935     case kVK_ANSI_KeypadEnter:
936     case kVK_JIS_KeypadComma:
937     case kVK_Powerbook_KeypadEnter:
938       aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD;
939       break;
941     default:
942       aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD;
943       break;
944   }
946   aKeyEvent.mIsRepeat =
947     ([aNativeKeyEvent type] == NSKeyDown) ? [aNativeKeyEvent isARepeat] : false;
949   PR_LOG(gLog, PR_LOG_ALWAYS,
950     ("%p TISInputSourceWrapper::InitKeyEvent, "
951      "shift=%s, ctrl=%s, alt=%s, meta=%s",
952      this, OnOrOff(aKeyEvent.IsShift()), OnOrOff(aKeyEvent.IsControl()),
953      OnOrOff(aKeyEvent.IsAlt()), OnOrOff(aKeyEvent.IsMeta())));
955   if (aKeyEvent.message == NS_KEY_PRESS &&
956       (isPrintableKey || !insertString.IsEmpty())) {
957     InitKeyPressEvent(aNativeKeyEvent,
958                       insertString.IsEmpty() ? 0 : insertString[0],
959                       aKeyEvent, kbType);
960     MOZ_ASSERT(!aKeyEvent.charCode || !IsControlChar(aKeyEvent.charCode),
961                "charCode must not be a control character");
962   } else {
963     aKeyEvent.charCode = 0;
964     aKeyEvent.isChar = false; // XXX not used in XP level
966     PR_LOG(gLog, PR_LOG_ALWAYS,
967       ("%p TISInputSourceWrapper::InitKeyEvent, keyCode=0x%X charCode=0x0",
968        this, aKeyEvent.keyCode));
969   }
971   if (isPrintableKey) {
972     aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
973     // If insertText calls this method, let's use the string.
974     if (aInsertString && !aInsertString->IsEmpty() &&
975         !IsControlChar((*aInsertString)[0])) {
976       aKeyEvent.mKeyValue = *aInsertString;
977     }
978     // If meta key is pressed, the printable key layout may be switched from
979     // non-ASCII capable layout to ASCII capable, or from Dvorak to QWERTY.
980     // KeyboardEvent.key value should be the switched layout's character.
981     else if (aKeyEvent.IsMeta()) {
982       nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters],
983                                          aKeyEvent.mKeyValue);
984     }
985     // If control key is pressed, some keys may produce printable character via
986     // [aNativeKeyEvent characters].  Otherwise, translate input character of
987     // the key without control key.
988     else if (aKeyEvent.IsControl()) {
989       nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters],
990                                          aKeyEvent.mKeyValue);
991       if (aKeyEvent.mKeyValue.IsEmpty() ||
992           IsControlChar(aKeyEvent.mKeyValue[0])) {
993         NSUInteger cocoaState =
994           [aNativeKeyEvent modifierFlags] & ~NSControlKeyMask;
995         UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState);
996         aKeyEvent.mKeyValue =
997           TranslateToChar(nativeKeyCode, carbonState, kbType);
998       }
999     }
1000     // Otherwise, KeyboardEvent.key expose
1001     // [aNativeKeyEvent characters] value.  However, if IME is open and the
1002     // keyboard layout isn't ASCII capable, exposing the non-ASCII character
1003     // doesn't match with other platform's behavior.  For the compatibility
1004     // with other platform's Gecko, we need to set a translated character.
1005     else if (IsOpenedIMEMode()) {
1006       UInt32 state =
1007         nsCocoaUtils::ConvertToCarbonModifier([aNativeKeyEvent modifierFlags]);
1008       aKeyEvent.mKeyValue = TranslateToChar(nativeKeyCode, state, kbType);
1009     } else {
1010       nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters],
1011                                          aKeyEvent.mKeyValue);
1012     }
1014     // Last resort.  If .key value becomes empty string, we should use
1015     // charactersIgnoringModifiers, if it's available.
1016     if (aKeyEvent.mKeyValue.IsEmpty() ||
1017         IsControlChar(aKeyEvent.mKeyValue[0])) {
1018       nsCocoaUtils::GetStringForNSString(
1019         [aNativeKeyEvent charactersIgnoringModifiers], aKeyEvent.mKeyValue);
1020       // But don't expose it if it's a control character.
1021       if (!aKeyEvent.mKeyValue.IsEmpty() &&
1022           IsControlChar(aKeyEvent.mKeyValue[0])) {
1023         aKeyEvent.mKeyValue.Truncate();
1024       }
1025     }
1026   } else {
1027     // Compute the key for non-printable keys and some special printable keys.
1028     aKeyEvent.mKeyNameIndex = ComputeGeckoKeyNameIndex(nativeKeyCode);
1029   }
1031   aKeyEvent.mCodeNameIndex = ComputeGeckoCodeNameIndex(nativeKeyCode);
1032   MOZ_ASSERT(aKeyEvent.mCodeNameIndex != CODE_NAME_INDEX_USE_STRING);
1034   NS_OBJC_END_TRY_ABORT_BLOCK
1037 void
1038 TISInputSourceWrapper::InitKeyPressEvent(NSEvent *aNativeKeyEvent,
1039                                          char16_t aInsertChar,
1040                                          WidgetKeyboardEvent& aKeyEvent,
1041                                          UInt32 aKbType)
1043   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1045   NS_ASSERTION(aKeyEvent.message == NS_KEY_PRESS,
1046                "aKeyEvent must be NS_KEY_PRESS event");
1048 #ifdef PR_LOGGING
1049   if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) {
1050     nsAutoString chars;
1051     nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], chars);
1052     NS_ConvertUTF16toUTF8 utf8Chars(chars);
1053     char16_t expectedChar = static_cast<char16_t>(aInsertChar);
1054     NS_ConvertUTF16toUTF8 utf8ExpectedChar(&expectedChar, 1);
1055     PR_LOG(gLog, PR_LOG_ALWAYS,
1056       ("%p TISInputSourceWrapper::InitKeyPressEvent, aNativeKeyEvent=%p, "
1057        "[aNativeKeyEvent characters]=\"%s\", aInsertChar=0x%X(%s), "
1058        "aKeyEvent.message=%s, aKbType=0x%X, IsOpenedIMEMode()=%s",
1059        this, aNativeKeyEvent, utf8Chars.get(), aInsertChar,
1060        utf8ExpectedChar.get(), GetGeckoKeyEventType(aKeyEvent), aKbType,
1061        TrueOrFalse(IsOpenedIMEMode())));
1062   }
1063 #endif // #ifdef PR_LOGGING
1065   aKeyEvent.isChar = true; // this is not a special key  XXX not used in XP
1066   aKeyEvent.charCode = aInsertChar;
1067   if (aKeyEvent.charCode != 0) {
1068     aKeyEvent.keyCode = 0;
1069   }
1071   PR_LOG(gLog, PR_LOG_ALWAYS,
1072     ("%p TISInputSourceWrapper::InitKeyPressEvent, "
1073      "aKeyEvent.keyCode=0x%X, aKeyEvent.charCode=0x%X",
1074      this, aKeyEvent.keyCode, aKeyEvent.charCode));
1076   if (!aKeyEvent.IsControl() && !aKeyEvent.IsMeta() && !aKeyEvent.IsAlt()) {
1077     return;
1078   }
1080   TISInputSourceWrapper USLayout("com.apple.keylayout.US");
1081   bool isRomanKeyboardLayout = IsASCIICapable();
1083   UInt32 key = [aNativeKeyEvent keyCode];
1085   // Caps lock and num lock modifier state:
1086   UInt32 lockState = 0;
1087   if ([aNativeKeyEvent modifierFlags] & NSAlphaShiftKeyMask) {
1088     lockState |= alphaLock;
1089   }
1090   if ([aNativeKeyEvent modifierFlags] & NSNumericPadKeyMask) {
1091     lockState |= kEventKeyModifierNumLockMask;
1092   }
1094   PR_LOG(gLog, PR_LOG_ALWAYS,
1095     ("%p TISInputSourceWrapper::InitKeyPressEvent, "
1096      "isRomanKeyboardLayout=%s, key=0x%X",
1097      this, TrueOrFalse(isRomanKeyboardLayout), aKbType, key));
1099   nsString str;
1101   // normal chars
1102   uint32_t unshiftedChar = TranslateToChar(key, lockState, aKbType);
1103   UInt32 shiftLockMod = shiftKey | lockState;
1104   uint32_t shiftedChar = TranslateToChar(key, shiftLockMod, aKbType);
1106   // characters generated with Cmd key
1107   // XXX we should remove CapsLock state, which changes characters from
1108   //     Latin to Cyrillic with Russian layout on 10.4 only when Cmd key
1109   //     is pressed.
1110   UInt32 numState = (lockState & ~alphaLock); // only num lock state
1111   uint32_t uncmdedChar = TranslateToChar(key, numState, aKbType);
1112   UInt32 shiftNumMod = numState | shiftKey;
1113   uint32_t uncmdedShiftChar = TranslateToChar(key, shiftNumMod, aKbType);
1114   uint32_t uncmdedUSChar = USLayout.TranslateToChar(key, numState, aKbType);
1115   UInt32 cmdNumMod = cmdKey | numState;
1116   uint32_t cmdedChar = TranslateToChar(key, cmdNumMod, aKbType);
1117   UInt32 cmdShiftNumMod = shiftKey | cmdNumMod;
1118   uint32_t cmdedShiftChar = TranslateToChar(key, cmdShiftNumMod, aKbType);
1120   // Is the keyboard layout changed by Cmd key?
1121   // E.g., Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY.
1122   bool isCmdSwitchLayout = uncmdedChar != cmdedChar;
1123   // Is the keyboard layout for Latin, but Cmd key switches the layout?
1124   // I.e., Dvorak-QWERTY
1125   bool isDvorakQWERTY = isCmdSwitchLayout && isRomanKeyboardLayout;
1127   // If the current keyboard is not Dvorak-QWERTY or Cmd is not pressed,
1128   // we should append unshiftedChar and shiftedChar for handling the
1129   // normal characters.  These are the characters that the user is most
1130   // likely to associate with this key.
1131   if ((unshiftedChar || shiftedChar) &&
1132       (!aKeyEvent.IsMeta() || !isDvorakQWERTY)) {
1133     AlternativeCharCode altCharCodes(unshiftedChar, shiftedChar);
1134     aKeyEvent.alternativeCharCodes.AppendElement(altCharCodes);
1135   }
1136   PR_LOG(gLog, PR_LOG_ALWAYS,
1137     ("%p TISInputSourceWrapper::InitKeyPressEvent, "
1138      "aKeyEvent.isMeta=%s, isDvorakQWERTY=%s, "
1139      "unshiftedChar=U+%X, shiftedChar=U+%X",
1140      this, OnOrOff(aKeyEvent.IsMeta()), TrueOrFalse(isDvorakQWERTY),
1141      unshiftedChar, shiftedChar));
1143   // Most keyboard layouts provide the same characters in the NSEvents
1144   // with Command+Shift as with Command.  However, with Command+Shift we
1145   // want the character on the second level.  e.g. With a US QWERTY
1146   // layout, we want "?" when the "/","?" key is pressed with
1147   // Command+Shift.
1149   // On a German layout, the OS gives us '/' with Cmd+Shift+SS(eszett)
1150   // even though Cmd+SS is 'SS' and Shift+'SS' is '?'.  This '/' seems
1151   // like a hack to make the Cmd+"?" event look the same as the Cmd+"?"
1152   // event on a US keyboard.  The user thinks they are typing Cmd+"?", so
1153   // we'll prefer the "?" character, replacing charCode with shiftedChar
1154   // when Shift is pressed.  However, in case there is a layout where the
1155   // character unique to Cmd+Shift is the character that the user expects,
1156   // we'll send it as an alternative char.
1157   bool hasCmdShiftOnlyChar =
1158     cmdedChar != cmdedShiftChar && uncmdedShiftChar != cmdedShiftChar;
1159   uint32_t originalCmdedShiftChar = cmdedShiftChar;
1161   // If we can make a good guess at the characters that the user would
1162   // expect this key combination to produce (with and without Shift) then
1163   // use those characters.  This also corrects for CapsLock, which was
1164   // ignored above.
1165   if (!isCmdSwitchLayout) {
1166     // The characters produced with Command seem similar to those without
1167     // Command.
1168     if (unshiftedChar) {
1169       cmdedChar = unshiftedChar;
1170     }
1171     if (shiftedChar) {
1172       cmdedShiftChar = shiftedChar;
1173     }
1174   } else if (uncmdedUSChar == cmdedChar) {
1175     // It looks like characters from a US layout are provided when Command
1176     // is down.
1177     uint32_t ch = USLayout.TranslateToChar(key, lockState, aKbType);
1178     if (ch) {
1179       cmdedChar = ch;
1180     }
1181     ch = USLayout.TranslateToChar(key, shiftLockMod, aKbType);
1182     if (ch) {
1183       cmdedShiftChar = ch;
1184     }
1185   }
1187   // If the current keyboard layout is switched by the Cmd key,
1188   // we should append cmdedChar and shiftedCmdChar that are
1189   // Latin char for the key.
1190   // If the keyboard layout is Dvorak-QWERTY, we should append them only when
1191   // command key is pressed because when command key isn't pressed, uncmded
1192   // chars have been appended already.
1193   if ((cmdedChar || cmdedShiftChar) && isCmdSwitchLayout &&
1194       (aKeyEvent.IsMeta() || !isDvorakQWERTY)) {
1195     AlternativeCharCode altCharCodes(cmdedChar, cmdedShiftChar);
1196     aKeyEvent.alternativeCharCodes.AppendElement(altCharCodes);
1197   }
1198   PR_LOG(gLog, PR_LOG_ALWAYS,
1199     ("%p TISInputSourceWrapper::InitKeyPressEvent, "
1200      "hasCmdShiftOnlyChar=%s, isCmdSwitchLayout=%s, isDvorakQWERTY=%s, "
1201      "cmdedChar=U+%X, cmdedShiftChar=U+%X",
1202      this, TrueOrFalse(hasCmdShiftOnlyChar), TrueOrFalse(isDvorakQWERTY),
1203      TrueOrFalse(isDvorakQWERTY), cmdedChar, cmdedShiftChar));
1204   // Special case for 'SS' key of German layout. See the comment of
1205   // hasCmdShiftOnlyChar definition for the detail.
1206   if (hasCmdShiftOnlyChar && originalCmdedShiftChar) {
1207     AlternativeCharCode altCharCodes(0, originalCmdedShiftChar);
1208     aKeyEvent.alternativeCharCodes.AppendElement(altCharCodes);
1209   }
1210   PR_LOG(gLog, PR_LOG_ALWAYS,
1211     ("%p TISInputSourceWrapper::InitKeyPressEvent, "
1212      "hasCmdShiftOnlyChar=%s, originalCmdedShiftChar=U+%X",
1213      this, TrueOrFalse(hasCmdShiftOnlyChar), originalCmdedShiftChar));
1215   NS_OBJC_END_TRY_ABORT_BLOCK
1218 uint32_t
1219 TISInputSourceWrapper::ComputeGeckoKeyCode(UInt32 aNativeKeyCode,
1220                                            UInt32 aKbType,
1221                                            bool aCmdIsPressed)
1223   PR_LOG(gLog, PR_LOG_ALWAYS,
1224     ("%p TISInputSourceWrapper::ComputeGeckoKeyCode, aNativeKeyCode=0x%X, "
1225      "aKbType=0x%X, aCmdIsPressed=%s, IsOpenedIMEMode()=%s, "
1226      "IsASCIICapable()=%s",
1227      this, aNativeKeyCode, aKbType, TrueOrFalse(aCmdIsPressed),
1228      TrueOrFalse(IsOpenedIMEMode()), TrueOrFalse(IsASCIICapable())));
1230   switch (aNativeKeyCode) {
1231     case kVK_Space:             return NS_VK_SPACE;
1232     case kVK_Escape:            return NS_VK_ESCAPE;
1234     // modifiers
1235     case kVK_RightCommand:
1236     case kVK_Command:           return NS_VK_META;
1237     case kVK_RightShift:
1238     case kVK_Shift:             return NS_VK_SHIFT;
1239     case kVK_CapsLock:          return NS_VK_CAPS_LOCK;
1240     case kVK_RightControl:
1241     case kVK_Control:           return NS_VK_CONTROL;
1242     case kVK_RightOption:
1243     case kVK_Option:            return NS_VK_ALT;
1245     case kVK_ANSI_KeypadClear:  return NS_VK_CLEAR;
1247     // function keys
1248     case kVK_F1:                return NS_VK_F1;
1249     case kVK_F2:                return NS_VK_F2;
1250     case kVK_F3:                return NS_VK_F3;
1251     case kVK_F4:                return NS_VK_F4;
1252     case kVK_F5:                return NS_VK_F5;
1253     case kVK_F6:                return NS_VK_F6;
1254     case kVK_F7:                return NS_VK_F7;
1255     case kVK_F8:                return NS_VK_F8;
1256     case kVK_F9:                return NS_VK_F9;
1257     case kVK_F10:               return NS_VK_F10;
1258     case kVK_F11:               return NS_VK_F11;
1259     case kVK_F12:               return NS_VK_F12;
1260     // case kVK_F13:               return NS_VK_F13;  // clash with the 3 below
1261     // case kVK_F14:               return NS_VK_F14;
1262     // case kVK_F15:               return NS_VK_F15;
1263     case kVK_F16:               return NS_VK_F16;
1264     case kVK_F17:               return NS_VK_F17;
1265     case kVK_F18:               return NS_VK_F18;
1266     case kVK_F19:               return NS_VK_F19;
1268     case kVK_PC_Pause:          return NS_VK_PAUSE;
1269     case kVK_PC_ScrollLock:     return NS_VK_SCROLL_LOCK;
1270     case kVK_PC_PrintScreen:    return NS_VK_PRINTSCREEN;
1272     // keypad
1273     case kVK_ANSI_Keypad0:      return NS_VK_NUMPAD0;
1274     case kVK_ANSI_Keypad1:      return NS_VK_NUMPAD1;
1275     case kVK_ANSI_Keypad2:      return NS_VK_NUMPAD2;
1276     case kVK_ANSI_Keypad3:      return NS_VK_NUMPAD3;
1277     case kVK_ANSI_Keypad4:      return NS_VK_NUMPAD4;
1278     case kVK_ANSI_Keypad5:      return NS_VK_NUMPAD5;
1279     case kVK_ANSI_Keypad6:      return NS_VK_NUMPAD6;
1280     case kVK_ANSI_Keypad7:      return NS_VK_NUMPAD7;
1281     case kVK_ANSI_Keypad8:      return NS_VK_NUMPAD8;
1282     case kVK_ANSI_Keypad9:      return NS_VK_NUMPAD9;
1284     case kVK_ANSI_KeypadMultiply: return NS_VK_MULTIPLY;
1285     case kVK_ANSI_KeypadPlus:     return NS_VK_ADD;
1286     case kVK_ANSI_KeypadMinus:    return NS_VK_SUBTRACT;
1287     case kVK_ANSI_KeypadDecimal:  return NS_VK_DECIMAL;
1288     case kVK_ANSI_KeypadDivide:   return NS_VK_DIVIDE;
1290     case kVK_JIS_KeypadComma:   return NS_VK_SEPARATOR;
1292     // IME keys
1293     case kVK_JIS_Eisu:          return NS_VK_EISU;
1294     case kVK_JIS_Kana:          return NS_VK_KANA;
1296     // these may clash with forward delete and help
1297     case kVK_PC_Insert:         return NS_VK_INSERT;
1298     case kVK_PC_Delete:         return NS_VK_DELETE;
1300     case kVK_PC_Backspace:      return NS_VK_BACK;
1301     case kVK_Tab:               return NS_VK_TAB;
1303     case kVK_Home:              return NS_VK_HOME;
1304     case kVK_End:               return NS_VK_END;
1306     case kVK_PageUp:            return NS_VK_PAGE_UP;
1307     case kVK_PageDown:          return NS_VK_PAGE_DOWN;
1309     case kVK_LeftArrow:         return NS_VK_LEFT;
1310     case kVK_RightArrow:        return NS_VK_RIGHT;
1311     case kVK_UpArrow:           return NS_VK_UP;
1312     case kVK_DownArrow:         return NS_VK_DOWN;
1314     case kVK_PC_ContextMenu:    return NS_VK_CONTEXT_MENU;
1316     case kVK_ANSI_1:            return NS_VK_1;
1317     case kVK_ANSI_2:            return NS_VK_2;
1318     case kVK_ANSI_3:            return NS_VK_3;
1319     case kVK_ANSI_4:            return NS_VK_4;
1320     case kVK_ANSI_5:            return NS_VK_5;
1321     case kVK_ANSI_6:            return NS_VK_6;
1322     case kVK_ANSI_7:            return NS_VK_7;
1323     case kVK_ANSI_8:            return NS_VK_8;
1324     case kVK_ANSI_9:            return NS_VK_9;
1325     case kVK_ANSI_0:            return NS_VK_0;
1327     case kVK_ANSI_KeypadEnter:
1328     case kVK_Return:
1329     case kVK_Powerbook_KeypadEnter: return NS_VK_RETURN;
1330   }
1332   // If Cmd key is pressed, that causes switching keyboard layout temporarily.
1333   // E.g., Dvorak-QWERTY.  Therefore, if Cmd key is pressed, we should honor it.
1334   UInt32 modifiers = aCmdIsPressed ? cmdKey : 0;
1336   uint32_t charCode = TranslateToChar(aNativeKeyCode, modifiers, aKbType);
1338   // Special case for Mac.  Mac inputs Yen sign (U+00A5) directly instead of
1339   // Back slash (U+005C).  We should return NS_VK_BACK_SLASH for compatibility
1340   // with other platforms.
1341   // XXX How about Won sign (U+20A9) which has same problem as Yen sign?
1342   if (charCode == 0x00A5) {
1343     return NS_VK_BACK_SLASH;
1344   }
1346   uint32_t keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode);
1347   if (keyCode) {
1348     return keyCode;
1349   }
1351   // If the unshifed char isn't an ASCII character, use shifted char.
1352   charCode = TranslateToChar(aNativeKeyCode, modifiers | shiftKey, aKbType);
1353   keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode);
1354   if (keyCode) {
1355     return keyCode;
1356   }
1358   // If this is ASCII capable, give up to compute it.
1359   if (IsASCIICapable()) {
1360     return 0;
1361   }
1363   // Retry with ASCII capable keyboard layout.
1364   TISInputSourceWrapper currentKeyboardLayout;
1365   currentKeyboardLayout.InitByCurrentASCIICapableKeyboardLayout();
1366   NS_ENSURE_TRUE(mInputSource != currentKeyboardLayout.mInputSource, 0);
1367   keyCode = currentKeyboardLayout.ComputeGeckoKeyCode(aNativeKeyCode, aKbType,
1368                                                       aCmdIsPressed);
1370   // However, if keyCode isn't for an alphabet keys or a numeric key, we should
1371   // ignore it.  For example, comma key of Thai layout is same as close-square-
1372   // bracket key of US layout and an unicode character key of Thai layout is
1373   // same as comma key of US layout.  If we return NS_VK_COMMA for latter key,
1374   // web application developers cannot distinguish with the former key.
1375   return ((keyCode >= NS_VK_A && keyCode <= NS_VK_Z) ||
1376           (keyCode >= NS_VK_0 && keyCode <= NS_VK_9)) ? keyCode : 0;
1379 // static
1380 KeyNameIndex
1381 TISInputSourceWrapper::ComputeGeckoKeyNameIndex(UInt32 aNativeKeyCode)
1383   // NOTE:
1384   //   When unsupported keys like Convert, Nonconvert of Japanese keyboard is
1385   //   pressed:
1386   //     on 10.6.x, 'A' key event is fired (and also actually 'a' is inserted).
1387   //     on 10.7.x, Nothing happens.
1388   //     on 10.8.x, Nothing happens.
1389   //     on 10.9.x, FlagsChanged event is fired with keyCode 0xFF.
1390   switch (aNativeKeyCode) {
1392 #define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
1393     case aNativeKey: return aKeyNameIndex;
1395 #include "NativeKeyToDOMKeyName.h"
1397 #undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
1399     default:
1400       return KEY_NAME_INDEX_Unidentified;
1401   }
1404 // static
1405 CodeNameIndex
1406 TISInputSourceWrapper::ComputeGeckoCodeNameIndex(UInt32 aNativeKeyCode)
1408   switch (aNativeKeyCode) {
1410 #define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \
1411     case aNativeKey: return aCodeNameIndex;
1413 #include "NativeKeyToDOMCodeName.h"
1415 #undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX
1417     default:
1418       return CODE_NAME_INDEX_UNKNOWN;
1419   }
1423 #pragma mark -
1426 /******************************************************************************
1428  *  TextInputHandler implementation (static methods)
1430  ******************************************************************************/
1432 NSUInteger TextInputHandler::sLastModifierState = 0;
1434 // static
1435 CFArrayRef
1436 TextInputHandler::CreateAllKeyboardLayoutList()
1438   const void* keys[] = { kTISPropertyInputSourceType };
1439   const void* values[] = { kTISTypeKeyboardLayout };
1440   CFDictionaryRef filter =
1441     ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
1442   NS_ASSERTION(filter, "failed to create the filter");
1443   CFArrayRef list = ::TISCreateInputSourceList(filter, true);
1444   ::CFRelease(filter);
1445   return list;
1448 // static
1449 void
1450 TextInputHandler::DebugPrintAllKeyboardLayouts()
1452 #ifdef PR_LOGGING
1453   if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) {
1454     CFArrayRef list = CreateAllKeyboardLayoutList();
1455     PR_LOG(gLog, PR_LOG_ALWAYS, ("Keyboard layout configuration:"));
1456     CFIndex idx = ::CFArrayGetCount(list);
1457     TISInputSourceWrapper tis;
1458     for (CFIndex i = 0; i < idx; ++i) {
1459       TISInputSourceRef inputSource = static_cast<TISInputSourceRef>(
1460         const_cast<void *>(::CFArrayGetValueAtIndex(list, i)));
1461       tis.InitByTISInputSourceRef(inputSource);
1462       nsAutoString name, isid;
1463       tis.GetLocalizedName(name);
1464       tis.GetInputSourceID(isid);
1465       PR_LOG(gLog, PR_LOG_ALWAYS,
1466              ("  %s\t<%s>%s%s\n",
1467               NS_ConvertUTF16toUTF8(name).get(),
1468               NS_ConvertUTF16toUTF8(isid).get(),
1469               tis.IsASCIICapable() ? "" : "\t(Isn't ASCII capable)",
1470               tis.IsKeyboardLayout() && tis.GetUCKeyboardLayout() ?
1471                 "" : "\t(uchr is NOT AVAILABLE)"));
1472     }
1473     ::CFRelease(list);
1474   }
1475 #endif // #ifdef PR_LOGGING
1479 #pragma mark -
1482 /******************************************************************************
1484  *  TextInputHandler implementation
1486  ******************************************************************************/
1488 TextInputHandler::TextInputHandler(nsChildView* aWidget,
1489                                    NSView<mozView> *aNativeView) :
1490   IMEInputHandler(aWidget, aNativeView)
1492   InitLogModule();
1493   [mView installTextInputHandler:this];
1496 TextInputHandler::~TextInputHandler()
1498   [mView uninstallTextInputHandler];
1501 bool
1502 TextInputHandler::HandleKeyDownEvent(NSEvent* aNativeEvent)
1504   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
1506   if (Destroyed()) {
1507     PR_LOG(gLog, PR_LOG_ALWAYS,
1508       ("%p TextInputHandler::HandleKeyDownEvent, "
1509        "widget has been already destroyed", this));
1510     return false;
1511   }
1513   PR_LOG(gLog, PR_LOG_ALWAYS,
1514     ("%p TextInputHandler::HandleKeyDownEvent, aNativeEvent=%p, "
1515      "type=%s, keyCode=%lld (0x%X), modifierFlags=0x%X, characters=\"%s\", "
1516      "charactersIgnoringModifiers=\"%s\"",
1517      this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
1518      [aNativeEvent keyCode], [aNativeEvent keyCode],
1519      [aNativeEvent modifierFlags], GetCharacters([aNativeEvent characters]),
1520      GetCharacters([aNativeEvent charactersIgnoringModifiers])));
1522   nsRefPtr<nsChildView> kungFuDeathGrip(mWidget);
1524   KeyEventState* currentKeyEvent = PushKeyEvent(aNativeEvent);
1525   AutoKeyEventStateCleaner remover(this);
1527   ComplexTextInputPanel* ctiPanel = ComplexTextInputPanel::GetSharedComplexTextInputPanel();
1528   if (ctiPanel && ctiPanel->IsInComposition()) {
1529     nsAutoString committed;
1530     ctiPanel->InterpretKeyEvent(aNativeEvent, committed);
1531     if (!committed.IsEmpty()) {
1532       WidgetKeyboardEvent imeEvent(true, NS_KEY_DOWN, mWidget);
1533       InitKeyEvent(aNativeEvent, imeEvent);
1534       imeEvent.mPluginTextEventString.Assign(committed);
1535       DispatchEvent(imeEvent);
1536     }
1537     return true;
1538   }
1540   if (mWidget->IsPluginFocused() || !IsIMEComposing()) {
1541     NSResponder* firstResponder = [[mView window] firstResponder];
1543     WidgetKeyboardEvent keydownEvent(true, NS_KEY_DOWN, mWidget);
1544     InitKeyEvent(aNativeEvent, keydownEvent);
1546     currentKeyEvent->mKeyDownHandled = DispatchEvent(keydownEvent);
1547     if (Destroyed()) {
1548       PR_LOG(gLog, PR_LOG_ALWAYS,
1549         ("%p TextInputHandler::HandleKeyDownEvent, "
1550          "widget was destroyed by keydown event", this));
1551       return currentKeyEvent->IsDefaultPrevented();
1552     }
1554     // The key down event may have shifted the focus, in which
1555     // case we should not fire the key press.
1556     // XXX This is a special code only on Cocoa widget, why is this needed?
1557     if (firstResponder != [[mView window] firstResponder]) {
1558       PR_LOG(gLog, PR_LOG_ALWAYS,
1559         ("%p TextInputHandler::HandleKeyDownEvent, "
1560          "view lost focus by keydown event", this));
1561       return currentKeyEvent->IsDefaultPrevented();
1562     }
1564     if (currentKeyEvent->IsDefaultPrevented()) {
1565       PR_LOG(gLog, PR_LOG_ALWAYS,
1566         ("%p TextInputHandler::HandleKeyDownEvent, "
1567          "keydown event's default is prevented", this));
1568       return true;
1569     }
1570   }
1572   // None of what follows is needed for plugin keyboard input.  In fact it
1573   // may cause trouble -- for example the call to [mView interpretKeyEvents:]
1574   // can, in e10s mode, cause each key typed to appear twice in an IME
1575   // composition.
1576   if (mWidget->IsPluginFocused()) {
1577     return true;
1578   }
1580   // Let Cocoa interpret the key events, caching IsIMEComposing first.
1581   bool wasComposing = IsIMEComposing();
1582   bool interpretKeyEventsCalled = false;
1583   if (IsIMEEnabled() || IsASCIICapableOnly()) {
1584     PR_LOG(gLog, PR_LOG_ALWAYS,
1585       ("%p TextInputHandler::HandleKeyDownEvent, calling interpretKeyEvents",
1586        this));
1587     [mView interpretKeyEvents:[NSArray arrayWithObject:aNativeEvent]];
1588     interpretKeyEventsCalled = true;
1589     PR_LOG(gLog, PR_LOG_ALWAYS,
1590       ("%p TextInputHandler::HandleKeyDownEvent, called interpretKeyEvents",
1591        this));
1592   }
1594   if (Destroyed()) {
1595     PR_LOG(gLog, PR_LOG_ALWAYS,
1596       ("%p TextInputHandler::HandleKeyDownEvent, widget was destroyed",
1597        this));
1598     return currentKeyEvent->IsDefaultPrevented();
1599   }
1601   PR_LOG(gLog, PR_LOG_ALWAYS,
1602     ("%p TextInputHandler::HandleKeyDownEvent, wasComposing=%s, "
1603      "IsIMEComposing()=%s",
1604      this, TrueOrFalse(wasComposing), TrueOrFalse(IsIMEComposing())));
1606   if (currentKeyEvent->CanDispatchKeyPressEvent() &&
1607       !wasComposing && !IsIMEComposing()) {
1608     WidgetKeyboardEvent keypressEvent(true, NS_KEY_PRESS, mWidget);
1609     InitKeyEvent(aNativeEvent, keypressEvent);
1611     // If we called interpretKeyEvents and this isn't normal character input
1612     // then IME probably ate the event for some reason. We do not want to
1613     // send a key press event in that case.
1614     // TODO:
1615     // There are some other cases which IME eats the current event.
1616     // 1. If key events were nested during calling interpretKeyEvents, it means
1617     //    that IME did something.  Then, we should do nothing.
1618     // 2. If one or more commands are called like "deleteBackward", we should
1619     //    dispatch keypress event at that time.  Note that the command may have
1620     //    been a converted or generated action by IME.  Then, we shouldn't do
1621     //    our default action for this key.
1622     if (!(interpretKeyEventsCalled &&
1623           IsNormalCharInputtingEvent(keypressEvent))) {
1624       currentKeyEvent->mKeyPressHandled = DispatchEvent(keypressEvent);
1625       currentKeyEvent->mKeyPressDispatched = true;
1626       PR_LOG(gLog, PR_LOG_ALWAYS,
1627         ("%p TextInputHandler::HandleKeyDownEvent, keypress event dispatched",
1628          this));
1629     }
1630   }
1632   // Note: mWidget might have become null here. Don't count on it from here on.
1634   PR_LOG(gLog, PR_LOG_ALWAYS,
1635     ("%p TextInputHandler::HandleKeyDownEvent, "
1636      "keydown handled=%s, keypress handled=%s, causedOtherKeyEvents=%s",
1637      this, TrueOrFalse(currentKeyEvent->mKeyDownHandled),
1638      TrueOrFalse(currentKeyEvent->mKeyPressHandled),
1639      TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents)));
1640   return currentKeyEvent->IsDefaultPrevented();
1642   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
1645 void
1646 TextInputHandler::HandleKeyUpEvent(NSEvent* aNativeEvent)
1648   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1650   PR_LOG(gLog, PR_LOG_ALWAYS,
1651     ("%p TextInputHandler::HandleKeyUpEvent, aNativeEvent=%p, "
1652      "type=%s, keyCode=%lld (0x%X), modifierFlags=0x%X, characters=\"%s\", "
1653      "charactersIgnoringModifiers=\"%s\", "
1654      "IsIMEComposing()=%s",
1655      this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
1656      [aNativeEvent keyCode], [aNativeEvent keyCode],
1657      [aNativeEvent modifierFlags], GetCharacters([aNativeEvent characters]),
1658      GetCharacters([aNativeEvent charactersIgnoringModifiers]),
1659      TrueOrFalse(IsIMEComposing())));
1661   if (Destroyed()) {
1662     PR_LOG(gLog, PR_LOG_ALWAYS,
1663       ("%p TextInputHandler::HandleKeyUpEvent, "
1664        "widget has been already destroyed", this));
1665     return;
1666   }
1668   // if we don't have any characters we can't generate a keyUp event
1669   if (IsIMEComposing()) {
1670     return;
1671   }
1673   WidgetKeyboardEvent keyupEvent(true, NS_KEY_UP, mWidget);
1674   InitKeyEvent(aNativeEvent, keyupEvent);
1676   DispatchEvent(keyupEvent);
1678   NS_OBJC_END_TRY_ABORT_BLOCK;
1681 void
1682 TextInputHandler::HandleFlagsChanged(NSEvent* aNativeEvent)
1684   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1686   if (Destroyed()) {
1687     PR_LOG(gLog, PR_LOG_ALWAYS,
1688       ("%p TextInputHandler::HandleFlagsChanged, "
1689        "widget has been already destroyed", this));
1690     return;
1691   }
1693   nsRefPtr<nsChildView> kungFuDeathGrip(mWidget);
1695   PR_LOG(gLog, PR_LOG_ALWAYS,
1696     ("%p TextInputHandler::HandleFlagsChanged, aNativeEvent=%p, "
1697      "type=%s, keyCode=%s (0x%X), modifierFlags=0x%08X, "
1698      "sLastModifierState=0x%08X, IsIMEComposing()=%s",
1699      this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
1700      GetKeyNameForNativeKeyCode([aNativeEvent keyCode]), [aNativeEvent keyCode],
1701      [aNativeEvent modifierFlags], sLastModifierState,
1702      TrueOrFalse(IsIMEComposing())));
1704   MOZ_ASSERT([aNativeEvent type] == NSFlagsChanged);
1706   NSUInteger diff = [aNativeEvent modifierFlags] ^ sLastModifierState;
1707   // Device dependent flags for left-control key, both shift keys, both command
1708   // keys and both option keys have been defined in Next's SDK.  But we
1709   // shouldn't use it directly as far as possible since Cocoa SDK doesn't
1710   // define them.  Fortunately, we need them only when we dispatch keyup
1711   // events.  So, we can usually know the actual relation between keyCode and
1712   // device dependent flags.  However, we need to remove following flags first
1713   // since the differences don't indicate modifier key state.
1714   // NX_STYLUSPROXIMITYMASK: Probably used for pen like device.
1715   // kCGEventFlagMaskNonCoalesced (= NX_NONCOALSESCEDMASK): See the document for
1716   // Quartz Event Services.
1717   diff &= ~(NX_STYLUSPROXIMITYMASK | kCGEventFlagMaskNonCoalesced);
1719   switch ([aNativeEvent keyCode]) {
1720     // CapsLock state and other modifier states are different:
1721     // CapsLock state does not revert when the CapsLock key goes up, as the
1722     // modifier state does for other modifier keys on key up.
1723     case kVK_CapsLock: {
1724       // Fire key down event for caps lock.
1725       DispatchKeyEventForFlagsChanged(aNativeEvent, true);
1726       // XXX should we fire keyup event too? The keyup event for CapsLock key
1727       // is never dispatched on Gecko.
1728       // XXX WebKit dispatches keydown event when CapsLock is locked, otherwise,
1729       // keyup event.  If we do so, we cannot keep the consistency with other
1730       // platform's behavior...
1731       break;
1732     }
1734     // If the event is caused by pressing or releasing a modifier key, just
1735     // dispatch the key's event.
1736     case kVK_Shift:
1737     case kVK_RightShift:
1738     case kVK_Command:
1739     case kVK_RightCommand:
1740     case kVK_Control:
1741     case kVK_RightControl:
1742     case kVK_Option:
1743     case kVK_RightOption:
1744     case kVK_Help: {
1745       // We assume that at most one modifier is changed per event if the event
1746       // is caused by pressing or releasing a modifier key.
1747       bool isKeyDown = ([aNativeEvent modifierFlags] & diff) != 0;
1748       DispatchKeyEventForFlagsChanged(aNativeEvent, isKeyDown);
1749       // XXX Some applications might send the event with incorrect device-
1750       //     dependent flags.
1751       if (isKeyDown && ((diff & ~NSDeviceIndependentModifierFlagsMask) != 0)) {
1752         unsigned short keyCode = [aNativeEvent keyCode];
1753         const ModifierKey* modifierKey =
1754           GetModifierKeyForDeviceDependentFlags(diff);
1755         if (modifierKey && modifierKey->keyCode != keyCode) {
1756           // Although, we're not sure the actual cause of this case, the stored
1757           // modifier information and the latest key event information may be
1758           // mismatched. Then, let's reset the stored information.
1759           // NOTE: If this happens, it may fail to handle NSFlagsChanged event
1760           // in the default case (below). However, it's the rare case handler
1761           // and this case occurs rarely. So, we can ignore the edge case bug.
1762           NS_WARNING("Resetting stored modifier key information");
1763           mModifierKeys.Clear();
1764           modifierKey = nullptr;
1765         }
1766         if (!modifierKey) {
1767           mModifierKeys.AppendElement(ModifierKey(diff, keyCode));
1768         }
1769       }
1770       break;
1771     }
1773     // Currently we don't support Fn key since other browsers don't dispatch
1774     // events for it and we don't have keyCode for this key.
1775     // It should be supported when we implement .key and .char.
1776     case kVK_Function:
1777       break;
1779     // If the event is caused by something else than pressing or releasing a
1780     // single modifier key (for example by the app having been deactivated
1781     // using command-tab), use the modifiers themselves to determine which
1782     // key's event to dispatch, and whether it's a keyup or keydown event.
1783     // In all cases we assume one or more modifiers are being deactivated
1784     // (never activated) -- otherwise we'd have received one or more events
1785     // corresponding to a single modifier key being pressed.
1786     default: {
1787       NSUInteger modifiers = sLastModifierState;
1788       for (int32_t bit = 0; bit < 32; ++bit) {
1789         NSUInteger flag = 1 << bit;
1790         if (!(diff & flag)) {
1791           continue;
1792         }
1794         // Given correct information from the application, a flag change here
1795         // will normally be a deactivation (except for some lockable modifiers
1796         // such as CapsLock).  But some applications (like VNC) can send an
1797         // activating event with a zero keyCode.  So we need to check for that
1798         // here.
1799         bool dispatchKeyDown = ((flag & [aNativeEvent modifierFlags]) != 0);
1801         unsigned short keyCode = 0;
1802         if (flag & NSDeviceIndependentModifierFlagsMask) {
1803           switch (flag) {
1804             case NSAlphaShiftKeyMask:
1805               keyCode = kVK_CapsLock;
1806               dispatchKeyDown = true;
1807               break;
1809             case NSNumericPadKeyMask:
1810               // NSNumericPadKeyMask is fired by VNC a lot. But not all of
1811               // these events can really be Clear key events, so we just ignore
1812               // them.
1813               continue;
1815             case NSHelpKeyMask:
1816               keyCode = kVK_Help;
1817               break;
1819             case NSFunctionKeyMask:
1820               // An NSFunctionKeyMask change here will normally be a
1821               // deactivation.  But sometimes it will be an activation send (by
1822               // VNC for example) with a zero keyCode.
1823               continue;
1825             // These cases (NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask
1826             // and NSCommandKeyMask) should be handled by the other branch of
1827             // the if statement, below (which handles device dependent flags).
1828             // However, some applications (like VNC) can send key events without
1829             // any device dependent flags, so we handle them here instead.
1830             case NSShiftKeyMask:
1831               keyCode = (modifiers & 0x0004) ? kVK_RightShift : kVK_Shift;
1832               break;
1833             case NSControlKeyMask:
1834               keyCode = (modifiers & 0x2000) ? kVK_RightControl : kVK_Control;
1835               break;
1836             case NSAlternateKeyMask:
1837               keyCode = (modifiers & 0x0040) ? kVK_RightOption : kVK_Option;
1838               break;
1839             case NSCommandKeyMask:
1840               keyCode = (modifiers & 0x0010) ? kVK_RightCommand : kVK_Command;
1841               break;
1843             default:
1844               continue;
1845           }
1846         } else {
1847           const ModifierKey* modifierKey =
1848             GetModifierKeyForDeviceDependentFlags(flag);
1849           if (!modifierKey) {
1850             // See the note above (in the other branch of the if statement)
1851             // about the NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask
1852             // and NSCommandKeyMask cases.
1853             continue;
1854           }
1855           keyCode = modifierKey->keyCode;
1856         }
1858         // Remove flags
1859         modifiers &= ~flag;
1860         switch (keyCode) {
1861           case kVK_Shift: {
1862             const ModifierKey* modifierKey =
1863               GetModifierKeyForNativeKeyCode(kVK_RightShift);
1864             if (!modifierKey ||
1865                 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
1866               modifiers &= ~NSShiftKeyMask;
1867             }
1868             break;
1869           }
1870           case kVK_RightShift: {
1871             const ModifierKey* modifierKey =
1872               GetModifierKeyForNativeKeyCode(kVK_Shift);
1873             if (!modifierKey ||
1874                 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
1875               modifiers &= ~NSShiftKeyMask;
1876             }
1877             break;
1878           }
1879           case kVK_Command: {
1880             const ModifierKey* modifierKey =
1881               GetModifierKeyForNativeKeyCode(kVK_RightCommand);
1882             if (!modifierKey ||
1883                 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
1884               modifiers &= ~NSCommandKeyMask;
1885             }
1886             break;
1887           }
1888           case kVK_RightCommand: {
1889             const ModifierKey* modifierKey =
1890               GetModifierKeyForNativeKeyCode(kVK_Command);
1891             if (!modifierKey ||
1892                 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
1893               modifiers &= ~NSCommandKeyMask;
1894             }
1895             break;
1896           }
1897           case kVK_Control: {
1898             const ModifierKey* modifierKey =
1899               GetModifierKeyForNativeKeyCode(kVK_RightControl);
1900             if (!modifierKey ||
1901                 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
1902               modifiers &= ~NSControlKeyMask;
1903             }
1904             break;
1905           }
1906           case kVK_RightControl: {
1907             const ModifierKey* modifierKey =
1908               GetModifierKeyForNativeKeyCode(kVK_Control);
1909             if (!modifierKey ||
1910                 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
1911               modifiers &= ~NSControlKeyMask;
1912             }
1913             break;
1914           }
1915           case kVK_Option: {
1916             const ModifierKey* modifierKey =
1917               GetModifierKeyForNativeKeyCode(kVK_RightOption);
1918             if (!modifierKey ||
1919                 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
1920               modifiers &= ~NSAlternateKeyMask;
1921             }
1922             break;
1923           }
1924           case kVK_RightOption: {
1925             const ModifierKey* modifierKey =
1926               GetModifierKeyForNativeKeyCode(kVK_Option);
1927             if (!modifierKey ||
1928                 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
1929               modifiers &= ~NSAlternateKeyMask;
1930             }
1931             break;
1932           }
1933           case kVK_Help:
1934             modifiers &= ~NSHelpKeyMask;
1935             break;
1936           default:
1937             break;
1938         }
1940         NSEvent* event =
1941           [NSEvent keyEventWithType:NSFlagsChanged
1942                            location:[aNativeEvent locationInWindow]
1943                       modifierFlags:modifiers
1944                           timestamp:[aNativeEvent timestamp]
1945                        windowNumber:[aNativeEvent windowNumber]
1946                             context:[aNativeEvent context]
1947                          characters:nil
1948         charactersIgnoringModifiers:nil
1949                           isARepeat:NO
1950                             keyCode:keyCode];
1951         DispatchKeyEventForFlagsChanged(event, dispatchKeyDown);
1952         if (Destroyed()) {
1953           break;
1954         }
1956         // Stop if focus has changed.
1957         // Check to see if mView is still the first responder.
1958         if (![mView isFirstResponder]) {
1959           break;
1960         }
1962       }
1963       break;
1964     }
1965   }
1967   // Be aware, the widget may have been destroyed.
1968   sLastModifierState = [aNativeEvent modifierFlags];
1970   NS_OBJC_END_TRY_ABORT_BLOCK;
1973 const TextInputHandler::ModifierKey*
1974 TextInputHandler::GetModifierKeyForNativeKeyCode(unsigned short aKeyCode) const
1976   for (ModifierKeyArray::index_type i = 0; i < mModifierKeys.Length(); ++i) {
1977     if (mModifierKeys[i].keyCode == aKeyCode) {
1978       return &((ModifierKey&)mModifierKeys[i]);
1979     }
1980   }
1981   return nullptr;
1984 const TextInputHandler::ModifierKey*
1985 TextInputHandler::GetModifierKeyForDeviceDependentFlags(NSUInteger aFlags) const
1987   for (ModifierKeyArray::index_type i = 0; i < mModifierKeys.Length(); ++i) {
1988     if (mModifierKeys[i].GetDeviceDependentFlags() ==
1989           (aFlags & ~NSDeviceIndependentModifierFlagsMask)) {
1990       return &((ModifierKey&)mModifierKeys[i]);
1991     }
1992   }
1993   return nullptr;
1996 void
1997 TextInputHandler::DispatchKeyEventForFlagsChanged(NSEvent* aNativeEvent,
1998                                                   bool aDispatchKeyDown)
2000   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2002   if (Destroyed()) {
2003     return;
2004   }
2006   PR_LOG(gLog, PR_LOG_ALWAYS,
2007     ("%p TextInputHandler::DispatchKeyEventForFlagsChanged, aNativeEvent=%p, "
2008      "type=%s, keyCode=%s (0x%X), aDispatchKeyDown=%s, IsIMEComposing()=%s",
2009      this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
2010      GetKeyNameForNativeKeyCode([aNativeEvent keyCode]), [aNativeEvent keyCode],
2011      TrueOrFalse(aDispatchKeyDown), TrueOrFalse(IsIMEComposing())));
2013   if ([aNativeEvent type] != NSFlagsChanged || IsIMEComposing()) {
2014     return;
2015   }
2017   uint32_t message = aDispatchKeyDown ? NS_KEY_DOWN : NS_KEY_UP;
2019   // Fire a key event.
2020   WidgetKeyboardEvent keyEvent(true, message, mWidget);
2021   InitKeyEvent(aNativeEvent, keyEvent);
2023   // Attach a plugin event, in case keyEvent gets dispatched to a plugin.  Only
2024   // one field is needed -- the type.  The other fields can be constructed as
2025   // the need arises.  But Gecko doesn't have anything equivalent to the
2026   // NPCocoaEventFlagsChanged type, and this needs to be passed accurately to
2027   // any plugin to which this event is sent.
2028   NPCocoaEvent cocoaEvent;
2029   nsCocoaUtils::InitNPCocoaEvent(&cocoaEvent);
2030   cocoaEvent.type = NPCocoaEventFlagsChanged;
2031   keyEvent.mPluginEvent.Copy(cocoaEvent);
2033   DispatchEvent(keyEvent);
2035   NS_OBJC_END_TRY_ABORT_BLOCK;
2038 void
2039 TextInputHandler::InsertText(NSAttributedString* aAttrString,
2040                              NSRange* aReplacementRange)
2042   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2044   if (Destroyed()) {
2045     return;
2046   }
2048   KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
2050   PR_LOG(gLog, PR_LOG_ALWAYS,
2051     ("%p TextInputHandler::InsertText, aAttrString=\"%s\", "
2052      "aReplacementRange=%p { location=%llu, length=%llu }, "
2053      "IsIMEComposing()=%s, IgnoreIMEComposition()=%s, "
2054      "keyevent=%p, keydownHandled=%s, keypressDispatched=%s, "
2055      "causedOtherKeyEvents=%s",
2056      this, GetCharacters([aAttrString string]), aReplacementRange,
2057      aReplacementRange ? aReplacementRange->location : 0,
2058      aReplacementRange ? aReplacementRange->length : 0,
2059      TrueOrFalse(IsIMEComposing()), TrueOrFalse(IgnoreIMEComposition()),
2060      currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr,
2061      currentKeyEvent ?
2062        TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
2063      currentKeyEvent ?
2064        TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A",
2065      currentKeyEvent ?
2066        TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A"));
2068   if (IgnoreIMEComposition()) {
2069     return;
2070   }
2072   InputContext context = mWidget->GetInputContext();
2073   bool isEditable = (context.mIMEState.mEnabled == IMEState::ENABLED ||
2074                      context.mIMEState.mEnabled == IMEState::PASSWORD);
2075   NSRange selectedRange = SelectedRange();
2077   nsAutoString str;
2078   nsCocoaUtils::GetStringForNSString([aAttrString string], str);
2079   if (!IsIMEComposing() && str.IsEmpty()) {
2080     // nothing to do if there is no content which can be removed.
2081     if (!isEditable) {
2082       return;
2083     }
2084     // If replacement range is specified, we need to remove the range.
2085     // Otherwise, we need to remove the selected range if it's not collapsed.
2086     if (aReplacementRange && aReplacementRange->location != NSNotFound) {
2087       // nothing to do since the range is collapsed.
2088       if (aReplacementRange->length == 0) {
2089         return;
2090       }
2091       // If the replacement range is different from current selected range,
2092       // select the range.
2093       if (!NSEqualRanges(selectedRange, *aReplacementRange)) {
2094         NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
2095       }
2096       selectedRange = SelectedRange();
2097     }
2098     NS_ENSURE_TRUE_VOID(selectedRange.location != NSNotFound);
2099     if (selectedRange.length == 0) {
2100       return; // nothing to do
2101     }
2102     // If this is caused by a key input, the keypress event which will be
2103     // dispatched later should cause the delete.  Therefore, nothing to do here.
2104     // Although, we're not sure if such case is actually possible.
2105     if (!currentKeyEvent) {
2106       return;
2107     }
2108     // Delete the selected range.
2109     nsRefPtr<TextInputHandler> kungFuDeathGrip(this);
2110     WidgetContentCommandEvent deleteCommandEvent(true,
2111                                                  NS_CONTENT_COMMAND_DELETE,
2112                                                  mWidget);
2113     DispatchEvent(deleteCommandEvent);
2114     NS_ENSURE_TRUE_VOID(deleteCommandEvent.mSucceeded);
2115     // Be aware! The widget might be destroyed here.
2116     return;
2117   }
2119   if (str.Length() != 1 || IsIMEComposing()) {
2120     InsertTextAsCommittingComposition(aAttrString, aReplacementRange);
2121     return;
2122   }
2124   // Don't let the same event be fired twice when hitting
2125   // enter/return! (Bug 420502)
2126   if (currentKeyEvent && !currentKeyEvent->CanDispatchKeyPressEvent()) {
2127     return;
2128   }
2130   nsRefPtr<nsChildView> kungFuDeathGrip(mWidget);
2132   // If the replacement range is specified, select the range.  Then, the
2133   // selection will be replaced by the later keypress event.
2134   if (isEditable &&
2135       aReplacementRange && aReplacementRange->location != NSNotFound &&
2136       !NSEqualRanges(selectedRange, *aReplacementRange)) {
2137     NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
2138   }
2140   // Dispatch keypress event with char instead of compositionchange event
2141   WidgetKeyboardEvent keypressEvent(true, NS_KEY_PRESS, mWidget);
2142   keypressEvent.isChar = IsPrintableChar(str.CharAt(0));
2144   // Don't set other modifiers from the current event, because here in
2145   // -insertText: they've already been taken into account in creating
2146   // the input string.
2148   if (currentKeyEvent) {
2149     NSEvent* keyEvent = currentKeyEvent->mKeyEvent;
2150     InitKeyEvent(keyEvent, keypressEvent, &str);
2151   } else {
2152     nsCocoaUtils::InitInputEvent(keypressEvent, static_cast<NSEvent*>(nullptr));
2153     if (keypressEvent.isChar) {
2154       keypressEvent.charCode = str.CharAt(0);
2155     }
2156     // Note that insertText is not called only at key pressing.
2157     if (!keypressEvent.charCode) {
2158       keypressEvent.keyCode =
2159         WidgetUtils::ComputeKeyCodeFromChar(keypressEvent.charCode);
2160     }
2161   }
2163   // Remove basic modifiers from keypress event because if they are included,
2164   // nsPlaintextEditor ignores the event.
2165   keypressEvent.modifiers &= ~(MODIFIER_CONTROL |
2166                                MODIFIER_ALT |
2167                                MODIFIER_META);
2169   // TODO:
2170   // If mCurrentKeyEvent.mKeyEvent is null and when we implement textInput
2171   // event of DOM3 Events, we should dispatch it instead of keypress event.
2172   bool keyPressHandled = DispatchEvent(keypressEvent);
2174   // Note: mWidget might have become null here. Don't count on it from here on.
2176   if (currentKeyEvent) {
2177     currentKeyEvent->mKeyPressHandled = keyPressHandled;
2178     currentKeyEvent->mKeyPressDispatched = true;
2179   }
2181   NS_OBJC_END_TRY_ABORT_BLOCK;
2184 bool
2185 TextInputHandler::DoCommandBySelector(const char* aSelector)
2187   nsRefPtr<nsChildView> kungFuDeathGrip(mWidget);
2189   KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
2191   PR_LOG(gLog, PR_LOG_ALWAYS,
2192     ("%p TextInputHandler::DoCommandBySelector, aSelector=\"%s\", "
2193      "Destroyed()=%s, keydownHandled=%s, keypressHandled=%s, "
2194      "causedOtherKeyEvents=%s",
2195      this, aSelector ? aSelector : "", TrueOrFalse(Destroyed()),
2196      currentKeyEvent ?
2197        TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
2198      currentKeyEvent ?
2199        TrueOrFalse(currentKeyEvent->mKeyPressHandled) : "N/A",
2200      currentKeyEvent ?
2201        TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A"));
2203   if (currentKeyEvent && currentKeyEvent->CanDispatchKeyPressEvent()) {
2204     WidgetKeyboardEvent keypressEvent(true, NS_KEY_PRESS, mWidget);
2205     InitKeyEvent(currentKeyEvent->mKeyEvent, keypressEvent);
2206     currentKeyEvent->mKeyPressHandled = DispatchEvent(keypressEvent);
2207     currentKeyEvent->mKeyPressDispatched = true;
2208     PR_LOG(gLog, PR_LOG_ALWAYS,
2209       ("%p TextInputHandler::DoCommandBySelector, keypress event "
2210        "dispatched, Destroyed()=%s, keypressHandled=%s",
2211        this, TrueOrFalse(Destroyed()),
2212        TrueOrFalse(currentKeyEvent->mKeyPressHandled)));
2213   }
2215   return (!Destroyed() && currentKeyEvent &&
2216           currentKeyEvent->IsDefaultPrevented());
2220 #pragma mark -
2223 /******************************************************************************
2225  *  IMEInputHandler implementation (static methods)
2227  ******************************************************************************/
2229 bool IMEInputHandler::sStaticMembersInitialized = false;
2230 CFStringRef IMEInputHandler::sLatestIMEOpenedModeInputSourceID = nullptr;
2231 IMEInputHandler* IMEInputHandler::sFocusedIMEHandler = nullptr;
2233 // static
2234 void
2235 IMEInputHandler::InitStaticMembers()
2237   if (sStaticMembersInitialized)
2238     return;
2239   sStaticMembersInitialized = true;
2240   // We need to check the keyboard layout changes on all applications.
2241   CFNotificationCenterRef center = ::CFNotificationCenterGetDistributedCenter();
2242   // XXX Don't we need to remove the observer at shut down?
2243   // Mac Dev Center's document doesn't say how to remove the observer if
2244   // the second parameter is NULL.
2245   ::CFNotificationCenterAddObserver(center, NULL,
2246       OnCurrentTextInputSourceChange,
2247       kTISNotifySelectedKeyboardInputSourceChanged, NULL,
2248       CFNotificationSuspensionBehaviorDeliverImmediately);
2249   // Initiailize with the current keyboard layout
2250   OnCurrentTextInputSourceChange(NULL, NULL,
2251                                  kTISNotifySelectedKeyboardInputSourceChanged,
2252                                  NULL, NULL);
2255 // static
2256 void
2257 IMEInputHandler::OnCurrentTextInputSourceChange(CFNotificationCenterRef aCenter,
2258                                                 void* aObserver,
2259                                                 CFStringRef aName,
2260                                                 const void* aObject,
2261                                                 CFDictionaryRef aUserInfo)
2263   // Cache the latest IME opened mode to sLatestIMEOpenedModeInputSourceID.
2264   TISInputSourceWrapper tis;
2265   tis.InitByCurrentInputSource();
2266   if (tis.IsOpenedIMEMode()) {
2267     tis.GetInputSourceID(sLatestIMEOpenedModeInputSourceID);
2268   }
2270 #ifdef PR_LOGGING
2271   if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) {
2272     static CFStringRef sLastTIS = nullptr;
2273     CFStringRef newTIS;
2274     tis.GetInputSourceID(newTIS);
2275     if (!sLastTIS ||
2276         ::CFStringCompare(sLastTIS, newTIS, 0) != kCFCompareEqualTo) {
2277       TISInputSourceWrapper tis1, tis2, tis3, tis4, tis5;
2278       tis1.InitByCurrentKeyboardLayout();
2279       tis2.InitByCurrentASCIICapableInputSource();
2280       tis3.InitByCurrentASCIICapableKeyboardLayout();
2281       tis4.InitByCurrentInputMethodKeyboardLayoutOverride();
2282       tis5.InitByTISInputSourceRef(tis.GetKeyboardLayoutInputSource());
2283       CFStringRef is0 = nullptr, is1 = nullptr, is2 = nullptr, is3 = nullptr,
2284                   is4 = nullptr, is5 = nullptr, type0 = nullptr,
2285                   lang0 = nullptr, bundleID0 = nullptr;
2286       tis.GetInputSourceID(is0);
2287       tis1.GetInputSourceID(is1);
2288       tis2.GetInputSourceID(is2);
2289       tis3.GetInputSourceID(is3);
2290       tis4.GetInputSourceID(is4);
2291       tis5.GetInputSourceID(is5);
2292       tis.GetInputSourceType(type0);
2293       tis.GetPrimaryLanguage(lang0);
2294       tis.GetBundleID(bundleID0);
2296       PR_LOG(gLog, PR_LOG_ALWAYS,
2297         ("IMEInputHandler::OnCurrentTextInputSourceChange,\n"
2298          "  Current Input Source is changed to:\n"
2299          "    currentInputContext=%p\n"
2300          "    %s\n"
2301          "      type=%s %s\n"
2302          "      overridden keyboard layout=%s\n"
2303          "      used keyboard layout for translation=%s\n"
2304          "    primary language=%s\n"
2305          "    bundle ID=%s\n"
2306          "    current ASCII capable Input Source=%s\n"
2307          "    current Keyboard Layout=%s\n"
2308          "    current ASCII capable Keyboard Layout=%s",
2309          [NSTextInputContext currentInputContext], GetCharacters(is0),
2310          GetCharacters(type0), tis.IsASCIICapable() ? "- ASCII capable " : "",
2311          GetCharacters(is4), GetCharacters(is5),
2312          GetCharacters(lang0), GetCharacters(bundleID0),
2313          GetCharacters(is2), GetCharacters(is1), GetCharacters(is3)));
2314     }
2315     sLastTIS = newTIS;
2316   }
2317 #endif // #ifdef PR_LOGGING
2320 // static
2321 void
2322 IMEInputHandler::FlushPendingMethods(nsITimer* aTimer, void* aClosure)
2324   NS_ASSERTION(aClosure, "aClosure is null");
2325   static_cast<IMEInputHandler*>(aClosure)->ExecutePendingMethods();
2328 // static
2329 CFArrayRef
2330 IMEInputHandler::CreateAllIMEModeList()
2332   const void* keys[] = { kTISPropertyInputSourceType };
2333   const void* values[] = { kTISTypeKeyboardInputMode };
2334   CFDictionaryRef filter =
2335     ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
2336   NS_ASSERTION(filter, "failed to create the filter");
2337   CFArrayRef list = ::TISCreateInputSourceList(filter, true);
2338   ::CFRelease(filter);
2339   return list;
2342 // static
2343 void
2344 IMEInputHandler::DebugPrintAllIMEModes()
2346 #ifdef PR_LOGGING
2347   if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) {
2348     CFArrayRef list = CreateAllIMEModeList();
2349     PR_LOG(gLog, PR_LOG_ALWAYS, ("IME mode configuration:"));
2350     CFIndex idx = ::CFArrayGetCount(list);
2351     TISInputSourceWrapper tis;
2352     for (CFIndex i = 0; i < idx; ++i) {
2353       TISInputSourceRef inputSource = static_cast<TISInputSourceRef>(
2354         const_cast<void *>(::CFArrayGetValueAtIndex(list, i)));
2355       tis.InitByTISInputSourceRef(inputSource);
2356       nsAutoString name, isid;
2357       tis.GetLocalizedName(name);
2358       tis.GetInputSourceID(isid);
2359       PR_LOG(gLog, PR_LOG_ALWAYS,
2360              ("  %s\t<%s>%s%s\n",
2361               NS_ConvertUTF16toUTF8(name).get(),
2362               NS_ConvertUTF16toUTF8(isid).get(),
2363               tis.IsASCIICapable() ? "" : "\t(Isn't ASCII capable)",
2364               tis.IsEnabled() ? "" : "\t(Isn't Enabled)"));
2365     }
2366     ::CFRelease(list);
2367   }
2368 #endif // #ifdef PR_LOGGING
2371 //static
2372 TSMDocumentID
2373 IMEInputHandler::GetCurrentTSMDocumentID()
2375   // At least on Mac OS X 10.6.x and 10.7.x, ::TSMGetActiveDocument() has a bug.
2376   // The result of ::TSMGetActiveDocument() isn't modified for new active text
2377   // input context until [NSTextInputContext currentInputContext] is called.
2378   // Therefore, we need to call it here.
2379   [NSTextInputContext currentInputContext];
2380   return ::TSMGetActiveDocument();
2384 #pragma mark -
2387 /******************************************************************************
2389  *  IMEInputHandler implementation #1
2390  *    The methods are releated to the pending methods.  Some jobs should be
2391  *    run after the stack is finished, e.g, some methods cannot run the jobs
2392  *    during processing the focus event.  And also some other jobs should be
2393  *    run at the next focus event is processed.
2394  *    The pending methods are recorded in mPendingMethods.  They are executed
2395  *    by ExecutePendingMethods via FlushPendingMethods.
2397  ******************************************************************************/
2399 void
2400 IMEInputHandler::NotifyIMEOfFocusChangeInGecko()
2402   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2404   PR_LOG(gLog, PR_LOG_ALWAYS,
2405     ("%p IMEInputHandler::NotifyIMEOfFocusChangeInGecko, "
2406      "Destroyed()=%s, IsFocused()=%s, inputContext=%p",
2407      this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
2408      mView ? [mView inputContext] : nullptr));
2410   if (Destroyed()) {
2411     return;
2412   }
2414   if (!IsFocused()) {
2415     // retry at next focus event
2416     mPendingMethods |= kNotifyIMEOfFocusChangeInGecko;
2417     return;
2418   }
2420   MOZ_ASSERT(mView);
2421   NSTextInputContext* inputContext = [mView inputContext];
2422   NS_ENSURE_TRUE_VOID(inputContext);
2424   // When an <input> element on a XUL <panel> element gets focus from an <input>
2425   // element on the opener window of the <panel> element, the owner window
2426   // still has native focus.  Therefore, IMEs may store the opener window's
2427   // level at this time because they don't know the actual focus is moved to
2428   // different window.  If IMEs try to get the newest window level after the
2429   // focus change, we return the window level of the XUL <panel>'s widget.
2430   // Therefore, let's emulate the native focus change.  Then, IMEs can refresh
2431   // the stored window level.
2432   [inputContext deactivate];
2433   [inputContext activate];
2435   NS_OBJC_END_TRY_ABORT_BLOCK;
2438 void
2439 IMEInputHandler::DiscardIMEComposition()
2441   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2443   PR_LOG(gLog, PR_LOG_ALWAYS,
2444     ("%p IMEInputHandler::DiscardIMEComposition, "
2445      "Destroyed()=%s, IsFocused()=%s, mView=%p, inputContext=%p",
2446      this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
2447      mView, mView ? [mView inputContext] : nullptr));
2449   if (Destroyed()) {
2450     return;
2451   }
2453   if (!IsFocused()) {
2454     // retry at next focus event
2455     mPendingMethods |= kDiscardIMEComposition;
2456     return;
2457   }
2459   NS_ENSURE_TRUE_VOID(mView);
2460   NSTextInputContext* inputContext = [mView inputContext];
2461   NS_ENSURE_TRUE_VOID(inputContext);
2462   mIgnoreIMECommit = true;
2463   [inputContext discardMarkedText];
2464   mIgnoreIMECommit = false;
2466   NS_OBJC_END_TRY_ABORT_BLOCK
2469 void
2470 IMEInputHandler::SyncASCIICapableOnly()
2472   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2474   PR_LOG(gLog, PR_LOG_ALWAYS,
2475     ("%p IMEInputHandler::SyncASCIICapableOnly, "
2476      "Destroyed()=%s, IsFocused()=%s, mIsASCIICapableOnly=%s, "
2477      "GetCurrentTSMDocumentID()=%p",
2478      this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
2479      TrueOrFalse(mIsASCIICapableOnly), GetCurrentTSMDocumentID()));
2481   if (Destroyed()) {
2482     return;
2483   }
2485   if (!IsFocused()) {
2486     // retry at next focus event
2487     mPendingMethods |= kSyncASCIICapableOnly;
2488     return;
2489   }
2491   TSMDocumentID doc = GetCurrentTSMDocumentID();
2492   if (!doc) {
2493     // retry
2494     mPendingMethods |= kSyncASCIICapableOnly;
2495     NS_WARNING("Application is active but there is no active document");
2496     ResetTimer();
2497     return;
2498   }
2500   if (mIsASCIICapableOnly) {
2501     CFArrayRef ASCIICapableTISList = ::TISCreateASCIICapableInputSourceList();
2502     ::TSMSetDocumentProperty(doc,
2503                              kTSMDocumentEnabledInputSourcesPropertyTag,
2504                              sizeof(CFArrayRef),
2505                              &ASCIICapableTISList);
2506     ::CFRelease(ASCIICapableTISList);
2507   } else {
2508     ::TSMRemoveDocumentProperty(doc,
2509                                 kTSMDocumentEnabledInputSourcesPropertyTag);
2510   }
2512   NS_OBJC_END_TRY_ABORT_BLOCK;
2515 void
2516 IMEInputHandler::ResetTimer()
2518   NS_ASSERTION(mPendingMethods != 0,
2519                "There are not pending methods, why this is called?");
2520   if (mTimer) {
2521     mTimer->Cancel();
2522   } else {
2523     mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
2524     NS_ENSURE_TRUE(mTimer, );
2525   }
2526   mTimer->InitWithFuncCallback(FlushPendingMethods, this, 0,
2527                                nsITimer::TYPE_ONE_SHOT);
2530 void
2531 IMEInputHandler::ExecutePendingMethods()
2533   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2535   if (mTimer) {
2536     mTimer->Cancel();
2537     mTimer = nullptr;
2538   }
2540   if (![[NSApplication sharedApplication] isActive]) {
2541     mIsInFocusProcessing = false;
2542     // If we're not active, we should retry at focus event
2543     return;
2544   }
2546   uint32_t pendingMethods = mPendingMethods;
2547   // First, reset the pending method flags because if each methods cannot
2548   // run now, they can reentry to the pending flags by theirselves.
2549   mPendingMethods = 0;
2551   if (pendingMethods & kDiscardIMEComposition)
2552     DiscardIMEComposition();
2553   if (pendingMethods & kSyncASCIICapableOnly)
2554     SyncASCIICapableOnly();
2555   if (pendingMethods & kNotifyIMEOfFocusChangeInGecko) {
2556     NotifyIMEOfFocusChangeInGecko();
2557   }
2559   mIsInFocusProcessing = false;
2561   NS_OBJC_END_TRY_ABORT_BLOCK;
2564 #pragma mark -
2567 /******************************************************************************
2569  * IMEInputHandler implementation (native event handlers)
2571  ******************************************************************************/
2573 uint32_t
2574 IMEInputHandler::ConvertToTextRangeType(uint32_t aUnderlineStyle,
2575                                         NSRange& aSelectedRange)
2577   PR_LOG(gLog, PR_LOG_ALWAYS,
2578     ("%p IMEInputHandler::ConvertToTextRangeType, "
2579      "aUnderlineStyle=%llu, aSelectedRange.length=%llu,",
2580      this, aUnderlineStyle, aSelectedRange.length));
2582   // We assume that aUnderlineStyle is NSUnderlineStyleSingle or
2583   // NSUnderlineStyleThick.  NSUnderlineStyleThick should indicate a selected
2584   // clause.  Otherwise, should indicate non-selected clause.
2586   if (aSelectedRange.length == 0) {
2587     switch (aUnderlineStyle) {
2588       case NSUnderlineStyleSingle:
2589         return NS_TEXTRANGE_RAWINPUT;
2590       case NSUnderlineStyleThick:
2591         return NS_TEXTRANGE_SELECTEDRAWTEXT;
2592       default:
2593         NS_WARNING("Unexpected line style");
2594         return NS_TEXTRANGE_SELECTEDRAWTEXT;
2595     }
2596   }
2598   switch (aUnderlineStyle) {
2599     case NSUnderlineStyleSingle:
2600       return NS_TEXTRANGE_CONVERTEDTEXT;
2601     case NSUnderlineStyleThick:
2602       return NS_TEXTRANGE_SELECTEDCONVERTEDTEXT;
2603     default:
2604       NS_WARNING("Unexpected line style");
2605       return NS_TEXTRANGE_SELECTEDCONVERTEDTEXT;
2606   }
2609 uint32_t
2610 IMEInputHandler::GetRangeCount(NSAttributedString *aAttrString)
2612   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
2614   // Iterate through aAttrString for the NSUnderlineStyleAttributeName and
2615   // count the different segments adjusting limitRange as we go.
2616   uint32_t count = 0;
2617   NSRange effectiveRange;
2618   NSRange limitRange = NSMakeRange(0, [aAttrString length]);
2619   while (limitRange.length > 0) {
2620     [aAttrString  attribute:NSUnderlineStyleAttributeName 
2621                     atIndex:limitRange.location 
2622       longestEffectiveRange:&effectiveRange
2623                     inRange:limitRange];
2624     limitRange =
2625       NSMakeRange(NSMaxRange(effectiveRange), 
2626                   NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
2627     count++;
2628   }
2630   PR_LOG(gLog, PR_LOG_ALWAYS,
2631     ("%p IMEInputHandler::GetRangeCount, aAttrString=\"%s\", count=%llu",
2632      this, GetCharacters([aAttrString string]), count));
2634   return count;
2636   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
2639 already_AddRefed<mozilla::TextRangeArray>
2640 IMEInputHandler::CreateTextRangeArray(NSAttributedString *aAttrString,
2641                                       NSRange& aSelectedRange)
2643   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
2645   // Convert the Cocoa range into the TextRange Array used in Gecko.
2646   // Iterate through the attributed string and map the underline attribute to
2647   // Gecko IME textrange attributes.  We may need to change the code here if
2648   // we change the implementation of validAttributesForMarkedText.
2649   NSRange limitRange = NSMakeRange(0, [aAttrString length]);
2650   uint32_t rangeCount = GetRangeCount(aAttrString);
2651   nsRefPtr<mozilla::TextRangeArray> textRangeArray =
2652                                       new mozilla::TextRangeArray();
2653   for (uint32_t i = 0; i < rangeCount && limitRange.length > 0; i++) {
2654     NSRange effectiveRange;
2655     id attributeValue = [aAttrString attribute:NSUnderlineStyleAttributeName
2656                                        atIndex:limitRange.location
2657                          longestEffectiveRange:&effectiveRange
2658                                        inRange:limitRange];
2660     TextRange range;
2661     range.mStartOffset = effectiveRange.location;
2662     range.mEndOffset = NSMaxRange(effectiveRange);
2663     range.mRangeType =
2664       ConvertToTextRangeType([attributeValue intValue], aSelectedRange);
2665     textRangeArray->AppendElement(range);
2667     PR_LOG(gLog, PR_LOG_ALWAYS,
2668       ("%p IMEInputHandler::CreateTextRangeArray, "
2669        "range={ mStartOffset=%llu, mEndOffset=%llu, mRangeType=%s }",
2670        this, range.mStartOffset, range.mEndOffset,
2671        GetRangeTypeName(range.mRangeType)));
2673     limitRange =
2674       NSMakeRange(NSMaxRange(effectiveRange), 
2675                   NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
2676   }
2678   // Get current caret position.
2679   TextRange range;
2680   range.mStartOffset = aSelectedRange.location + aSelectedRange.length;
2681   range.mEndOffset = range.mStartOffset;
2682   range.mRangeType = NS_TEXTRANGE_CARETPOSITION;
2683   textRangeArray->AppendElement(range);
2685   PR_LOG(gLog, PR_LOG_ALWAYS,
2686     ("%p IMEInputHandler::CreateTextRangeArray, "
2687      "range={ mStartOffset=%llu, mEndOffset=%llu, mRangeType=%s }",
2688      this, range.mStartOffset, range.mEndOffset,
2689      GetRangeTypeName(range.mRangeType)));
2691   return textRangeArray.forget();
2693   NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL;
2696 bool
2697 IMEInputHandler::DispatchCompositionChangeEvent(const nsString& aText,
2698                                                 NSAttributedString* aAttrString,
2699                                                 NSRange& aSelectedRange)
2701   PR_LOG(gLog, PR_LOG_ALWAYS,
2702     ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
2703      "aText=\"%s\", aAttrString=\"%s\", "
2704      "aSelectedRange={ location=%llu, length=%llu }, "
2705      "Destroyed()=%s",
2706      this, NS_ConvertUTF16toUTF8(aText).get(),
2707      GetCharacters([aAttrString string]),
2708      aSelectedRange.location, aSelectedRange.length,
2709      TrueOrFalse(Destroyed())));
2711   NS_ENSURE_TRUE(!Destroyed(), false);
2713   nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
2715   WidgetCompositionEvent compositionChangeEvent(true, NS_COMPOSITION_CHANGE,
2716                                                 mWidget);
2717   compositionChangeEvent.time = PR_IntervalNow();
2718   compositionChangeEvent.mData = aText;
2719   compositionChangeEvent.mRanges =
2720     CreateTextRangeArray(aAttrString, aSelectedRange);
2721   return DispatchEvent(compositionChangeEvent);
2724 bool
2725 IMEInputHandler::DispatchCompositionCommitEvent(const nsAString* aCommitString)
2727   PR_LOG(gLog, PR_LOG_ALWAYS,
2728     ("%p IMEInputHandler::DispatchCompositionCommitEvent, "
2729      "aCommitString=0x%p (\"%s\"), Destroyed()=%s",
2730      this, aCommitString,
2731      aCommitString ? NS_ConvertUTF16toUTF8(*aCommitString).get() : "",
2732      TrueOrFalse(Destroyed())));
2734   if (NS_WARN_IF(Destroyed())) {
2735     return false;
2736   }
2738   nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
2740   uint32_t message =
2741     aCommitString ? NS_COMPOSITION_COMMIT : NS_COMPOSITION_COMMIT_AS_IS;
2742   WidgetCompositionEvent compositionCommitEvent(true, message, mWidget);
2743   compositionCommitEvent.time = PR_IntervalNow();
2744   if (aCommitString) {
2745     compositionCommitEvent.mData = *aCommitString;
2746   }
2747   return DispatchEvent(compositionCommitEvent);
2750 void
2751 IMEInputHandler::InitCompositionEvent(WidgetCompositionEvent& aCompositionEvent)
2753   aCompositionEvent.time = PR_IntervalNow();
2756 void
2757 IMEInputHandler::InsertTextAsCommittingComposition(
2758                    NSAttributedString* aAttrString,
2759                    NSRange* aReplacementRange)
2761   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2763   PR_LOG(gLog, PR_LOG_ALWAYS,
2764     ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
2765      "aAttrString=\"%s\", aReplacementRange=%p { location=%llu, length=%llu }, "
2766      "Destroyed()=%s, IsIMEComposing()=%s, "
2767      "mMarkedRange={ location=%llu, length=%llu }",
2768      this, GetCharacters([aAttrString string]), aReplacementRange,
2769      aReplacementRange ? aReplacementRange->location : 0,
2770      aReplacementRange ? aReplacementRange->length : 0,
2771      TrueOrFalse(Destroyed()), TrueOrFalse(IsIMEComposing()),
2772      mMarkedRange.location, mMarkedRange.length));
2774   if (IgnoreIMECommit()) {
2775     MOZ_CRASH("IMEInputHandler::InsertTextAsCommittingComposition() must not"
2776               "be called while canceling the composition");
2777   }
2779   if (Destroyed()) {
2780     return;
2781   }
2783   // First, commit current composition with the latest composition string if the
2784   // replacement range is different from marked range.
2785   if (IsIMEComposing() && aReplacementRange &&
2786       aReplacementRange->location != NSNotFound &&
2787       !NSEqualRanges(MarkedRange(), *aReplacementRange)) {
2788     DispatchCompositionCommitEvent();
2789     if (Destroyed()) {
2790       PR_LOG(gLog, PR_LOG_ALWAYS,
2791         ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
2792          "destroyed by commiting composition for setting replacement range",
2793          this));
2794       return;
2795     }
2796     OnEndIMEComposition();
2797   }
2799   nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
2801   nsString str;
2802   nsCocoaUtils::GetStringForNSString([aAttrString string], str);
2804   if (!IsIMEComposing()) {
2805     // If there is no selection and replacement range is specified, set the
2806     // range as selection.
2807     if (aReplacementRange && aReplacementRange->location != NSNotFound &&
2808         !NSEqualRanges(SelectedRange(), *aReplacementRange)) {
2809       NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
2810     }
2812     // XXXmnakano Probably, we shouldn't emulate composition in this case.
2813     // I think that we should just fire DOM3 textInput event if we implement it.
2814     WidgetCompositionEvent compStart(true, NS_COMPOSITION_START, mWidget);
2815     InitCompositionEvent(compStart);
2817     DispatchEvent(compStart);
2818     if (Destroyed()) {
2819       PR_LOG(gLog, PR_LOG_ALWAYS,
2820         ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
2821          "destroyed by compositionstart event", this));
2822       return;
2823     }
2825     OnStartIMEComposition();
2826   }
2828   DispatchCompositionCommitEvent(&str);
2829   if (Destroyed()) {
2830     PR_LOG(gLog, PR_LOG_ALWAYS,
2831       ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
2832        "destroyed by compositioncommit event", this));
2833     return;
2834   }
2836   OnEndIMEComposition();
2838   mMarkedRange = NSMakeRange(NSNotFound, 0);
2840   NS_OBJC_END_TRY_ABORT_BLOCK;
2843 void
2844 IMEInputHandler::SetMarkedText(NSAttributedString* aAttrString,
2845                                NSRange& aSelectedRange,
2846                                NSRange* aReplacementRange)
2848   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2850   PR_LOG(gLog, PR_LOG_ALWAYS,
2851     ("%p IMEInputHandler::SetMarkedText, "
2852      "aAttrString=\"%s\", aSelectedRange={ location=%llu, length=%llu }, "
2853      "aReplacementRange=%p { location=%llu, length=%llu }, "
2854      "Destroyed()=%s, IgnoreIMEComposition()=%s, IsIMEComposing()=%s, "
2855      "mMarkedRange={ location=%llu, length=%llu }",
2856      this, GetCharacters([aAttrString string]),
2857      aSelectedRange.location, aSelectedRange.length, aReplacementRange,
2858      aReplacementRange ? aReplacementRange->location : 0,
2859      aReplacementRange ? aReplacementRange->length : 0,
2860      TrueOrFalse(Destroyed()), TrueOrFalse(IgnoreIMEComposition()),
2861      TrueOrFalse(IsIMEComposing()),
2862      mMarkedRange.location, mMarkedRange.length));
2864   if (Destroyed() || IgnoreIMEComposition()) {
2865     return;
2866   }
2868   nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
2870   // First, commit current composition with the latest composition string if the
2871   // replacement range is different from marked range.
2872   if (IsIMEComposing() && aReplacementRange &&
2873       aReplacementRange->location != NSNotFound &&
2874       !NSEqualRanges(MarkedRange(), *aReplacementRange)) {
2875     bool ignoreIMECommit = mIgnoreIMECommit;
2876     mIgnoreIMECommit = false;
2877     DispatchCompositionCommitEvent();
2878     mIgnoreIMECommit = ignoreIMECommit;
2879     if (Destroyed()) {
2880       PR_LOG(gLog, PR_LOG_ALWAYS,
2881         ("%p IMEInputHandler::SetMarkedText, "
2882          "destroyed by commiting composition for setting replacement range",
2883          this));
2884       return;
2885     }
2886     OnEndIMEComposition();
2887   }
2889   nsString str;
2890   nsCocoaUtils::GetStringForNSString([aAttrString string], str);
2892   mMarkedRange.length = str.Length();
2894   if (!IsIMEComposing() && !str.IsEmpty()) {
2895     // If there is no selection and replacement range is specified, set the
2896     // range as selection.
2897     if (aReplacementRange && aReplacementRange->location != NSNotFound &&
2898         !NSEqualRanges(SelectedRange(), *aReplacementRange)) {
2899       NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
2900     }
2902     mMarkedRange.location = SelectedRange().location;
2904     WidgetCompositionEvent compStart(true, NS_COMPOSITION_START, mWidget);
2905     InitCompositionEvent(compStart);
2907     DispatchEvent(compStart);
2908     if (Destroyed()) {
2909       PR_LOG(gLog, PR_LOG_ALWAYS,
2910         ("%p IMEInputHandler::SetMarkedText, "
2911          "destroyed by compositionstart event", this));
2912       return;
2913     }
2915     OnStartIMEComposition();
2916   }
2918   if (!IsIMEComposing()) {
2919     return;
2920   }
2922   if (!str.IsEmpty()) {
2923     OnUpdateIMEComposition([aAttrString string]);
2925     DispatchCompositionChangeEvent(str, aAttrString, aSelectedRange);
2926     if (Destroyed()) {
2927       PR_LOG(gLog, PR_LOG_ALWAYS,
2928         ("%p IMEInputHandler::SetMarkedText, "
2929          "destroyed by compositionchange event", this));
2930     }
2931     return;
2932   }
2934   // If the composition string becomes empty string, we should commit
2935   // current composition.
2936   DispatchCompositionCommitEvent(&EmptyString());
2937   if (Destroyed()) {
2938     PR_LOG(gLog, PR_LOG_ALWAYS,
2939       ("%p IMEInputHandler::SetMarkedText, "
2940        "destroyed by compositioncommit event", this));
2941     return;
2942   }
2943   OnEndIMEComposition();
2945   NS_OBJC_END_TRY_ABORT_BLOCK;
2948 NSInteger
2949 IMEInputHandler::ConversationIdentifier()
2951   PR_LOG(gLog, PR_LOG_ALWAYS,
2952     ("%p IMEInputHandler::ConversationIdentifier, Destroyed()=%s",
2953      this, TrueOrFalse(Destroyed())));
2955   if (Destroyed()) {
2956     return reinterpret_cast<NSInteger>(mView);
2957   }
2959   nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
2961   // NOTE: The size of NSInteger is same as pointer size.
2962   WidgetQueryContentEvent textContent(true, NS_QUERY_TEXT_CONTENT, mWidget);
2963   textContent.InitForQueryTextContent(0, 0);
2964   DispatchEvent(textContent);
2965   if (!textContent.mSucceeded) {
2966     PR_LOG(gLog, PR_LOG_ALWAYS,
2967       ("%p IMEInputHandler::ConversationIdentifier, Failed", this));
2968     return reinterpret_cast<NSInteger>(mView);
2969   }
2970   // XXX This might return same ID as a previously existing editor if the
2971   //     deleted editor was created at the same address.  Is there a better way?
2972   return reinterpret_cast<NSInteger>(textContent.mReply.mContentsRoot);
2975 NSAttributedString*
2976 IMEInputHandler::GetAttributedSubstringFromRange(NSRange& aRange,
2977                                                  NSRange* aActualRange)
2979   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
2981   PR_LOG(gLog, PR_LOG_ALWAYS,
2982     ("%p IMEInputHandler::GetAttributedSubstringFromRange, "
2983      "aRange={ location=%llu, length=%llu }, aActualRange=%p, Destroyed()=%s",
2984      this, aRange.location, aRange.length, aActualRange,
2985      TrueOrFalse(Destroyed())));
2987   if (aActualRange) {
2988     *aActualRange = NSMakeRange(NSNotFound, 0);
2989   }
2991   if (Destroyed() || aRange.location == NSNotFound || aRange.length == 0) {
2992     return nil;
2993   }
2995   nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
2997   nsAutoString str;
2998   WidgetQueryContentEvent textContent(true, NS_QUERY_TEXT_CONTENT, mWidget);
2999   textContent.InitForQueryTextContent(aRange.location, aRange.length);
3000   DispatchEvent(textContent);
3002   PR_LOG(gLog, PR_LOG_ALWAYS,
3003     ("%p IMEInputHandler::GetAttributedSubstringFromRange, "
3004      "textContent={ mSucceeded=%s, mReply={ mString=\"%s\", mOffset=%llu } }",
3005      this, TrueOrFalse(textContent.mSucceeded),
3006      NS_ConvertUTF16toUTF8(textContent.mReply.mString).get(),
3007      textContent.mReply.mOffset));
3009   if (!textContent.mSucceeded) {
3010     return nil;
3011   }
3013   NSString* nsstr = nsCocoaUtils::ToNSString(textContent.mReply.mString);
3014   NSAttributedString* result =
3015     [[[NSAttributedString alloc] initWithString:nsstr
3016                                      attributes:nil] autorelease];
3017   if (aActualRange) {
3018     aActualRange->location = textContent.mReply.mOffset;
3019     aActualRange->length = textContent.mReply.mString.Length();
3020   }
3021   return result;
3023   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
3026 bool
3027 IMEInputHandler::HasMarkedText()
3029   PR_LOG(gLog, PR_LOG_ALWAYS,
3030     ("%p IMEInputHandler::HasMarkedText, "
3031      "mMarkedRange={ location=%llu, length=%llu }",
3032      this, mMarkedRange.location, mMarkedRange.length));
3034   return (mMarkedRange.location != NSNotFound) && (mMarkedRange.length != 0);
3037 NSRange
3038 IMEInputHandler::MarkedRange()
3040   PR_LOG(gLog, PR_LOG_ALWAYS,
3041     ("%p IMEInputHandler::MarkedRange, "
3042      "mMarkedRange={ location=%llu, length=%llu }",
3043      this, mMarkedRange.location, mMarkedRange.length));
3045   if (!HasMarkedText()) {
3046     return NSMakeRange(NSNotFound, 0);
3047   }
3048   return mMarkedRange;
3051 NSRange
3052 IMEInputHandler::SelectedRange()
3054   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
3056   PR_LOG(gLog, PR_LOG_ALWAYS,
3057     ("%p IMEInputHandler::SelectedRange, Destroyed()=%s, mSelectedRange={ "
3058      "location=%llu, length=%llu }",
3059      this, TrueOrFalse(Destroyed()), mSelectedRange.location,
3060      mSelectedRange.length));
3062   if (Destroyed()) {
3063     return mSelectedRange;
3064   }
3066   if (mSelectedRange.location != NSNotFound) {
3067     MOZ_ASSERT(mIMEHasFocus);
3068     return mSelectedRange;
3069   }
3071   nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
3073   WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, mWidget);
3074   DispatchEvent(selection);
3076   PR_LOG(gLog, PR_LOG_ALWAYS,
3077     ("%p IMEInputHandler::SelectedRange, selection={ mSucceeded=%s, "
3078      "mReply={ mOffset=%llu, mString.Length()=%llu } }",
3079      this, TrueOrFalse(selection.mSucceeded), selection.mReply.mOffset,
3080      selection.mReply.mString.Length()));
3082   if (!selection.mSucceeded) {
3083     return mSelectedRange;
3084   }
3086   if (mIMEHasFocus) {
3087     mSelectedRange.location = selection.mReply.mOffset;
3088     mSelectedRange.length = selection.mReply.mString.Length();
3089     return mSelectedRange;
3090   }
3092   return NSMakeRange(selection.mReply.mOffset,
3093                      selection.mReply.mString.Length());
3095   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(mSelectedRange);
3098 NSRect
3099 IMEInputHandler::FirstRectForCharacterRange(NSRange& aRange,
3100                                             NSRange* aActualRange)
3102   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
3104   PR_LOG(gLog, PR_LOG_ALWAYS,
3105     ("%p IMEInputHandler::FirstRectForCharacterRange, Destroyed()=%s, "
3106      "aRange={ location=%llu, length=%llu }, aActualRange=%p }",
3107      this, TrueOrFalse(Destroyed()), aRange.location, aRange.length,
3108      aActualRange));
3110   // XXX this returns first character rect or caret rect, it is limitation of
3111   // now. We need more work for returns first line rect. But current
3112   // implementation is enough for IMEs.
3114   NSRect rect = NSMakeRect(0.0, 0.0, 0.0, 0.0);
3115   NSRange actualRange = NSMakeRange(NSNotFound, 0);
3116   if (aActualRange) {
3117     *aActualRange = actualRange;
3118   }
3119   if (Destroyed() || aRange.location == NSNotFound) {
3120     return rect;
3121   }
3123   nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
3125   nsIntRect r;
3126   bool useCaretRect = (aRange.length == 0);
3127   if (!useCaretRect) {
3128     WidgetQueryContentEvent charRect(true, NS_QUERY_TEXT_RECT, mWidget);
3129     charRect.InitForQueryTextRect(aRange.location, 1);
3130     DispatchEvent(charRect);
3131     if (charRect.mSucceeded) {
3132       r = charRect.mReply.mRect;
3133       actualRange.location = charRect.mReply.mOffset;
3134       actualRange.length = charRect.mReply.mString.Length();
3135     } else {
3136       useCaretRect = true;
3137     }
3138   }
3140   if (useCaretRect) {
3141     WidgetQueryContentEvent caretRect(true, NS_QUERY_CARET_RECT, mWidget);
3142     caretRect.InitForQueryCaretRect(aRange.location);
3143     DispatchEvent(caretRect);
3144     if (!caretRect.mSucceeded) {
3145       return rect;
3146     }
3147     r = caretRect.mReply.mRect;
3148     r.width = 0;
3149     actualRange.location = caretRect.mReply.mOffset;
3150     actualRange.length = 0;
3151   }
3153   nsIWidget* rootWidget = mWidget->GetTopLevelWidget();
3154   NSWindow* rootWindow =
3155     static_cast<NSWindow*>(rootWidget->GetNativeData(NS_NATIVE_WINDOW));
3156   NSView* rootView =
3157     static_cast<NSView*>(rootWidget->GetNativeData(NS_NATIVE_WIDGET));
3158   if (!rootWindow || !rootView) {
3159     return rect;
3160   }
3161   rect = nsCocoaUtils::DevPixelsToCocoaPoints(r, mWidget->BackingScaleFactor());
3162   rect = [rootView convertRect:rect toView:nil];
3163   rect.origin = [rootWindow convertBaseToScreen:rect.origin];
3165   if (aActualRange) {
3166     *aActualRange = actualRange;
3167   }
3169   PR_LOG(gLog, PR_LOG_ALWAYS,
3170     ("%p IMEInputHandler::FirstRectForCharacterRange, "
3171      "useCaretRect=%s rect={ x=%f, y=%f, width=%f, height=%f }, "
3172      "actualRange={ location=%llu, length=%llu }",
3173      this, TrueOrFalse(useCaretRect), rect.origin.x, rect.origin.y,
3174      rect.size.width, rect.size.height, actualRange.location,
3175      actualRange.length));
3177   return rect;
3179   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakeRect(0.0, 0.0, 0.0, 0.0));
3182 NSUInteger
3183 IMEInputHandler::CharacterIndexForPoint(NSPoint& aPoint)
3185   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
3187   PR_LOG(gLog, PR_LOG_ALWAYS,
3188     ("%p IMEInputHandler::CharacterIndexForPoint, aPoint={ x=%f, y=%f }",
3189      this, aPoint.x, aPoint.y));
3191   NSWindow* mainWindow = [NSApp mainWindow];
3192   if (!mWidget || !mainWindow) {
3193     return NSNotFound;
3194   }
3196   WidgetQueryContentEvent charAt(true, NS_QUERY_CHARACTER_AT_POINT, mWidget);
3197   NSPoint ptInWindow = [mainWindow convertScreenToBase:aPoint];
3198   NSPoint ptInView = [mView convertPoint:ptInWindow fromView:nil];
3199   charAt.refPoint.x =
3200     static_cast<int32_t>(ptInView.x) * mWidget->BackingScaleFactor();
3201   charAt.refPoint.y =
3202     static_cast<int32_t>(ptInView.y) * mWidget->BackingScaleFactor();
3203   mWidget->DispatchWindowEvent(charAt);
3204   if (!charAt.mSucceeded ||
3205       charAt.mReply.mOffset == WidgetQueryContentEvent::NOT_FOUND ||
3206       charAt.mReply.mOffset >= static_cast<uint32_t>(NSNotFound)) {
3207     return NSNotFound;
3208   }
3210   return charAt.mReply.mOffset;
3212   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSNotFound);
3215 NSArray*
3216 IMEInputHandler::GetValidAttributesForMarkedText()
3218   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
3220   PR_LOG(gLog, PR_LOG_ALWAYS,
3221     ("%p IMEInputHandler::GetValidAttributesForMarkedText", this));
3223   //nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
3225   //return [NSArray arrayWithObjects:NSUnderlineStyleAttributeName,
3226   //                                 NSMarkedClauseSegmentAttributeName,
3227   //                                 NSTextInputReplacementRangeAttributeName,
3228   //                                 nil];
3229   // empty array; we don't support any attributes right now
3230   return [NSArray array];
3232   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
3236 #pragma mark -
3239 /******************************************************************************
3241  *  IMEInputHandler implementation #2
3243  ******************************************************************************/
3245 IMEInputHandler::IMEInputHandler(nsChildView* aWidget,
3246                                  NSView<mozView> *aNativeView) :
3247   TextInputHandlerBase(aWidget, aNativeView),
3248   mPendingMethods(0), mIMECompositionString(nullptr),
3249   mIsIMEComposing(false), mIsIMEEnabled(true),
3250   mIsASCIICapableOnly(false), mIgnoreIMECommit(false),
3251   mIsInFocusProcessing(false), mIMEHasFocus(false)
3253   InitStaticMembers();
3255   mMarkedRange.location = NSNotFound;
3256   mMarkedRange.length = 0;
3257   mSelectedRange.location = NSNotFound;
3258   mSelectedRange.length = 0;
3261 IMEInputHandler::~IMEInputHandler()
3263   if (mTimer) {
3264     mTimer->Cancel();
3265     mTimer = nullptr;
3266   }
3267   if (sFocusedIMEHandler == this) {
3268     sFocusedIMEHandler = nullptr;
3269   }
3272 void
3273 IMEInputHandler::OnFocusChangeInGecko(bool aFocus)
3275   PR_LOG(gLog, PR_LOG_ALWAYS,
3276     ("%p IMEInputHandler::OnFocusChangeInGecko, aFocus=%s, Destroyed()=%s, "
3277      "sFocusedIMEHandler=%p",
3278      this, TrueOrFalse(aFocus), TrueOrFalse(Destroyed()), sFocusedIMEHandler));
3280   mSelectedRange.location = NSNotFound; // Marking dirty
3281   mIMEHasFocus = aFocus;
3283   // This is called when the native focus is changed and when the native focus
3284   // isn't changed but the focus is changed in Gecko.
3285   if (!aFocus) {
3286     if (sFocusedIMEHandler == this)
3287       sFocusedIMEHandler = nullptr;
3288     return;
3289   }
3291   sFocusedIMEHandler = this;
3292   mIsInFocusProcessing = true;
3294   // We need to notify IME of focus change in Gecko as native focus change
3295   // because the window level of the focused element in Gecko may be changed.
3296   mPendingMethods |= kNotifyIMEOfFocusChangeInGecko;
3297   ResetTimer();
3300 bool
3301 IMEInputHandler::OnDestroyWidget(nsChildView* aDestroyingWidget)
3303   PR_LOG(gLog, PR_LOG_ALWAYS,
3304     ("%p IMEInputHandler::OnDestroyWidget, aDestroyingWidget=%p, "
3305      "sFocusedIMEHandler=%p, IsIMEComposing()=%s",
3306      this, aDestroyingWidget, sFocusedIMEHandler,
3307      TrueOrFalse(IsIMEComposing())));
3309   // If we're not focused, the focused IMEInputHandler may have been
3310   // created by another widget/nsChildView.
3311   if (sFocusedIMEHandler && sFocusedIMEHandler != this) {
3312     sFocusedIMEHandler->OnDestroyWidget(aDestroyingWidget);
3313   }
3315   if (!TextInputHandlerBase::OnDestroyWidget(aDestroyingWidget)) {
3316     return false;
3317   }
3319   if (IsIMEComposing()) {
3320     // If our view is in the composition, we should clean up it.
3321     CancelIMEComposition();
3322     OnEndIMEComposition();
3323   }
3325   mSelectedRange.location = NSNotFound; // Marking dirty
3326   mIMEHasFocus = false;
3328   return true;
3331 void
3332 IMEInputHandler::OnStartIMEComposition()
3334   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3336   PR_LOG(gLog, PR_LOG_ALWAYS,
3337     ("%p IMEInputHandler::OnStartIMEComposition, mView=%p, mWidget=%p"
3338      "inputContext=%p, mIsIMEComposing=%s",
3339      this, mView, mWidget, mView ? [mView inputContext] : nullptr,
3340      TrueOrFalse(mIsIMEComposing)));
3342   NS_ASSERTION(!mIsIMEComposing, "There is a composition already");
3343   mIsIMEComposing = true;
3345   NS_OBJC_END_TRY_ABORT_BLOCK;
3348 void
3349 IMEInputHandler::OnUpdateIMEComposition(NSString* aIMECompositionString)
3351   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3353   PR_LOG(gLog, PR_LOG_ALWAYS,
3354     ("%p IMEInputHandler::OnUpdateIMEComposition, mView=%p, mWidget=%p, "
3355      "inputContext=%p, mIsIMEComposing=%s, aIMECompositionString=\"%s\"",
3356      this, mView, mWidget, mView ? [mView inputContext] : nullptr,
3357      TrueOrFalse(mIsIMEComposing), GetCharacters(aIMECompositionString)));
3359   NS_ASSERTION(mIsIMEComposing, "We're not in composition");
3361   if (mIMECompositionString)
3362     [mIMECompositionString release];
3363   mIMECompositionString = [aIMECompositionString retain];
3365   NS_OBJC_END_TRY_ABORT_BLOCK;
3368 void
3369 IMEInputHandler::OnEndIMEComposition()
3371   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3373   PR_LOG(gLog, PR_LOG_ALWAYS,
3374     ("%p IMEInputHandler::OnEndIMEComposition, mView=%p, mWidget=%p, "
3375      "inputContext=%p, mIsIMEComposing=%s",
3376      this, mView, mWidget, mView ? [mView inputContext] : nullptr,
3377      TrueOrFalse(mIsIMEComposing)));
3379   NS_ASSERTION(mIsIMEComposing, "We're not in composition");
3381   mIsIMEComposing = false;
3383   if (mIMECompositionString) {
3384     [mIMECompositionString release];
3385     mIMECompositionString = nullptr;
3386   }
3388   NS_OBJC_END_TRY_ABORT_BLOCK;
3391 void
3392 IMEInputHandler::SendCommittedText(NSString *aString)
3394   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3396   PR_LOG(gLog, PR_LOG_ALWAYS,
3397     ("%p IMEInputHandler::SendCommittedText, mView=%p, mWidget=%p, "
3398      "inputContext=%p, mIsIMEComposing=%s",
3399      this, mView, mWidget, mView ? [mView inputContext] : nullptr,
3400      TrueOrFalse(mIsIMEComposing), mWidget));
3402   NS_ENSURE_TRUE(mWidget, );
3403   // XXX We should send the string without mView.
3404   if (!mView) {
3405     return;
3406   }
3408   NSAttributedString* attrStr =
3409     [[NSAttributedString alloc] initWithString:aString];
3410   [mView insertText:attrStr];
3411   [attrStr release];
3413   NS_OBJC_END_TRY_ABORT_BLOCK;
3416 void
3417 IMEInputHandler::KillIMEComposition()
3419   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3421   PR_LOG(gLog, PR_LOG_ALWAYS,
3422     ("%p IMEInputHandler::KillIMEComposition, mView=%p, mWidget=%p, "
3423      "inputContext=%p, mIsIMEComposing=%s, "
3424      "Destroyed()=%s, IsFocused()=%s",
3425      this, mView, mWidget, mView ? [mView inputContext] : nullptr,
3426      TrueOrFalse(mIsIMEComposing), TrueOrFalse(Destroyed()),
3427      TrueOrFalse(IsFocused())));
3429   if (Destroyed()) {
3430     return;
3431   }
3433   if (IsFocused()) {
3434     NS_ENSURE_TRUE_VOID(mView);
3435     NSTextInputContext* inputContext = [mView inputContext];
3436     NS_ENSURE_TRUE_VOID(inputContext);
3437     [inputContext discardMarkedText];
3438     return;
3439   }
3441   PR_LOG(gLog, PR_LOG_ALWAYS,
3442     ("%p IMEInputHandler::KillIMEComposition, Pending...", this));
3444   // Commit the composition internally.
3445   SendCommittedText(mIMECompositionString);
3446   NS_ASSERTION(!mIsIMEComposing, "We're still in a composition");
3447   // The pending method will be fired by the next focus event.
3448   mPendingMethods |= kDiscardIMEComposition;
3450   NS_OBJC_END_TRY_ABORT_BLOCK;
3453 void
3454 IMEInputHandler::CommitIMEComposition()
3456   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3458   if (!IsIMEComposing())
3459     return;
3461   PR_LOG(gLog, PR_LOG_ALWAYS,
3462     ("%p IMEInputHandler::CommitIMEComposition, mIMECompositionString=%s",
3463      this, GetCharacters(mIMECompositionString)));
3465   KillIMEComposition();
3467   if (!IsIMEComposing())
3468     return;
3470   // If the composition is still there, KillIMEComposition only kills the
3471   // composition in TSM.  We also need to finish the our composition too.
3472   SendCommittedText(mIMECompositionString);
3474   NS_OBJC_END_TRY_ABORT_BLOCK;
3477 void
3478 IMEInputHandler::CancelIMEComposition()
3480   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3482   if (!IsIMEComposing())
3483     return;
3485   PR_LOG(gLog, PR_LOG_ALWAYS,
3486     ("%p IMEInputHandler::CancelIMEComposition, mIMECompositionString=%s",
3487      this, GetCharacters(mIMECompositionString)));
3489   // For canceling the current composing, we need to ignore the param of
3490   // insertText.  But this code is ugly...
3491   mIgnoreIMECommit = true;
3492   KillIMEComposition();
3493   mIgnoreIMECommit = false;
3495   if (!IsIMEComposing())
3496     return;
3498   // If the composition is still there, KillIMEComposition only kills the
3499   // composition in TSM.  We also need to kill the our composition too.
3500   SendCommittedText(@"");
3502   NS_OBJC_END_TRY_ABORT_BLOCK;
3505 bool
3506 IMEInputHandler::IsFocused()
3508   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3510   NS_ENSURE_TRUE(!Destroyed(), false);
3511   NSWindow* window = [mView window];
3512   NS_ENSURE_TRUE(window, false);
3513   return [window firstResponder] == mView &&
3514          [window isKeyWindow] &&
3515          [[NSApplication sharedApplication] isActive];
3517   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
3520 bool
3521 IMEInputHandler::IsIMEOpened()
3523   TISInputSourceWrapper tis;
3524   tis.InitByCurrentInputSource();
3525   return tis.IsOpenedIMEMode();
3528 void
3529 IMEInputHandler::SetASCIICapableOnly(bool aASCIICapableOnly)
3531   if (aASCIICapableOnly == mIsASCIICapableOnly)
3532     return;
3534   CommitIMEComposition();
3535   mIsASCIICapableOnly = aASCIICapableOnly;
3536   SyncASCIICapableOnly();
3539 void
3540 IMEInputHandler::EnableIME(bool aEnableIME)
3542   if (aEnableIME == mIsIMEEnabled)
3543     return;
3545   CommitIMEComposition();
3546   mIsIMEEnabled = aEnableIME;
3549 void
3550 IMEInputHandler::SetIMEOpenState(bool aOpenIME)
3552   if (!IsFocused() || IsIMEOpened() == aOpenIME)
3553     return;
3555   if (!aOpenIME) {
3556     TISInputSourceWrapper tis;
3557     tis.InitByCurrentASCIICapableInputSource();
3558     tis.Select();
3559     return;
3560   }
3562   // If we know the latest IME opened mode, we should select it.
3563   if (sLatestIMEOpenedModeInputSourceID) {
3564     TISInputSourceWrapper tis;
3565     tis.InitByInputSourceID(sLatestIMEOpenedModeInputSourceID);
3566     tis.Select();
3567     return;
3568   }
3570   // XXX If the current input source is a mode of IME, we should turn on it,
3571   // but we haven't found such way...
3573   // Finally, we should refer the system locale but this is a little expensive,
3574   // we shouldn't retry this (if it was succeeded, we already set
3575   // sLatestIMEOpenedModeInputSourceID at that time).
3576   static bool sIsPrefferredIMESearched = false;
3577   if (sIsPrefferredIMESearched)
3578     return;
3579   sIsPrefferredIMESearched = true;
3580   OpenSystemPreferredLanguageIME();
3583 void
3584 IMEInputHandler::OpenSystemPreferredLanguageIME()
3586   PR_LOG(gLog, PR_LOG_ALWAYS,
3587     ("%p IMEInputHandler::OpenSystemPreferredLanguageIME", this));
3589   CFArrayRef langList = ::CFLocaleCopyPreferredLanguages();
3590   if (!langList) {
3591     PR_LOG(gLog, PR_LOG_ALWAYS,
3592       ("%p IMEInputHandler::OpenSystemPreferredLanguageIME, langList is NULL",
3593        this));
3594     return;
3595   }
3596   CFIndex count = ::CFArrayGetCount(langList);
3597   for (CFIndex i = 0; i < count; i++) {
3598     CFLocaleRef locale =
3599       ::CFLocaleCreate(kCFAllocatorDefault,
3600           static_cast<CFStringRef>(::CFArrayGetValueAtIndex(langList, i)));
3601     if (!locale) {
3602       continue;
3603     }
3605     bool changed = false;
3606     CFStringRef lang = static_cast<CFStringRef>(
3607       ::CFLocaleGetValue(locale, kCFLocaleLanguageCode));
3608     NS_ASSERTION(lang, "lang is null");
3609     if (lang) {
3610       TISInputSourceWrapper tis;
3611       tis.InitByLanguage(lang);
3612       if (tis.IsOpenedIMEMode()) {
3613 #ifdef PR_LOGGING
3614         if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) {
3615           CFStringRef foundTIS;
3616           tis.GetInputSourceID(foundTIS);
3617           PR_LOG(gLog, PR_LOG_ALWAYS,
3618             ("%p IMEInputHandler::OpenSystemPreferredLanguageIME, "
3619              "foundTIS=%s, lang=%s",
3620              this, GetCharacters(foundTIS), GetCharacters(lang)));
3621         }
3622 #endif // #ifdef PR_LOGGING
3623         tis.Select();
3624         changed = true;
3625       }
3626     }
3627     ::CFRelease(locale);
3628     if (changed) {
3629       break;
3630     }
3631   }
3632   ::CFRelease(langList);
3636 #pragma mark -
3639 /******************************************************************************
3641  *  TextInputHandlerBase implementation
3643  ******************************************************************************/
3645 int32_t TextInputHandlerBase::sSecureEventInputCount = 0;
3647 TextInputHandlerBase::TextInputHandlerBase(nsChildView* aWidget,
3648                                            NSView<mozView> *aNativeView) :
3649   mWidget(aWidget)
3651   gHandlerInstanceCount++;
3652   mView = [aNativeView retain];
3655 TextInputHandlerBase::~TextInputHandlerBase()
3657   [mView release];
3658   if (--gHandlerInstanceCount == 0) {
3659     FinalizeCurrentInputSource();
3660   }
3663 bool
3664 TextInputHandlerBase::OnDestroyWidget(nsChildView* aDestroyingWidget)
3666   PR_LOG(gLog, PR_LOG_ALWAYS,
3667     ("%p TextInputHandlerBase::OnDestroyWidget, "
3668      "aDestroyingWidget=%p, mWidget=%p",
3669      this, aDestroyingWidget, mWidget));
3671   if (aDestroyingWidget != mWidget) {
3672     return false;
3673   }
3675   mWidget = nullptr;
3676   return true;
3679 bool
3680 TextInputHandlerBase::DispatchEvent(WidgetGUIEvent& aEvent)
3682   if (aEvent.message == NS_KEY_PRESS) {
3683     WidgetInputEvent& inputEvent = *aEvent.AsInputEvent();
3684     if (!inputEvent.IsMeta()) {
3685       PR_LOG(gLog, PR_LOG_ALWAYS,
3686         ("%p TextInputHandlerBase::DispatchEvent, hiding mouse cursor", this));
3687       [NSCursor setHiddenUntilMouseMoves:YES];
3688     }
3689   }
3690   return mWidget->DispatchWindowEvent(aEvent);
3693 void
3694 TextInputHandlerBase::InitKeyEvent(NSEvent *aNativeKeyEvent,
3695                                    WidgetKeyboardEvent& aKeyEvent,
3696                                    const nsAString* aInsertString)
3698   NS_ASSERTION(aNativeKeyEvent, "aNativeKeyEvent must not be NULL");
3700   if (mKeyboardOverride.mOverrideEnabled) {
3701     TISInputSourceWrapper tis;
3702     tis.InitByLayoutID(mKeyboardOverride.mKeyboardLayout, true);
3703     tis.InitKeyEvent(aNativeKeyEvent, aKeyEvent, aInsertString);
3704     return;
3705   }
3706   TISInputSourceWrapper::CurrentInputSource().
3707     InitKeyEvent(aNativeKeyEvent, aKeyEvent, aInsertString);
3710 nsresult
3711 TextInputHandlerBase::SynthesizeNativeKeyEvent(
3712                         int32_t aNativeKeyboardLayout,
3713                         int32_t aNativeKeyCode,
3714                         uint32_t aModifierFlags,
3715                         const nsAString& aCharacters,
3716                         const nsAString& aUnmodifiedCharacters)
3718   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
3720   static const uint32_t sModifierFlagMap[][2] = {
3721     { nsIWidget::CAPS_LOCK,       NSAlphaShiftKeyMask },
3722     { nsIWidget::SHIFT_L,         NSShiftKeyMask      | 0x0002 },
3723     { nsIWidget::SHIFT_R,         NSShiftKeyMask      | 0x0004 },
3724     { nsIWidget::CTRL_L,          NSControlKeyMask    | 0x0001 },
3725     { nsIWidget::CTRL_R,          NSControlKeyMask    | 0x2000 },
3726     { nsIWidget::ALT_L,           NSAlternateKeyMask  | 0x0020 },
3727     { nsIWidget::ALT_R,           NSAlternateKeyMask  | 0x0040 },
3728     { nsIWidget::COMMAND_L,       NSCommandKeyMask    | 0x0008 },
3729     { nsIWidget::COMMAND_R,       NSCommandKeyMask    | 0x0010 },
3730     { nsIWidget::NUMERIC_KEY_PAD, NSNumericPadKeyMask },
3731     { nsIWidget::HELP,            NSHelpKeyMask },
3732     { nsIWidget::FUNCTION,        NSFunctionKeyMask }
3733   };
3735   uint32_t modifierFlags = 0;
3736   for (uint32_t i = 0; i < ArrayLength(sModifierFlagMap); ++i) {
3737     if (aModifierFlags & sModifierFlagMap[i][0]) {
3738       modifierFlags |= sModifierFlagMap[i][1];
3739     }
3740   }
3742   NSInteger windowNumber = [[mView window] windowNumber];
3743   bool sendFlagsChangedEvent = IsModifierKey(aNativeKeyCode);
3744   NSEventType eventType = sendFlagsChangedEvent ? NSFlagsChanged : NSKeyDown;
3745   NSEvent* downEvent =
3746     [NSEvent     keyEventWithType:eventType
3747                          location:NSMakePoint(0,0)
3748                     modifierFlags:modifierFlags
3749                         timestamp:0
3750                      windowNumber:windowNumber
3751                           context:[NSGraphicsContext currentContext]
3752                        characters:nsCocoaUtils::ToNSString(aCharacters)
3753       charactersIgnoringModifiers:nsCocoaUtils::ToNSString(aUnmodifiedCharacters)
3754                         isARepeat:NO
3755                           keyCode:aNativeKeyCode];
3757   NSEvent* upEvent = sendFlagsChangedEvent ?
3758     nil : nsCocoaUtils::MakeNewCocoaEventWithType(NSKeyUp, downEvent);
3760   if (downEvent && (sendFlagsChangedEvent || upEvent)) {
3761     KeyboardLayoutOverride currentLayout = mKeyboardOverride;
3762     mKeyboardOverride.mKeyboardLayout = aNativeKeyboardLayout;
3763     mKeyboardOverride.mOverrideEnabled = true;
3764     [NSApp sendEvent:downEvent];
3765     if (upEvent) {
3766       [NSApp sendEvent:upEvent];
3767     }
3768     // processKeyDownEvent and keyUp block exceptions so we're sure to
3769     // reach here to restore mKeyboardOverride
3770     mKeyboardOverride = currentLayout;
3771   }
3773   return NS_OK;
3775   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
3778 NSInteger
3779 TextInputHandlerBase::GetWindowLevel()
3781   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
3783   PR_LOG(gLog, PR_LOG_ALWAYS,
3784     ("%p TextInputHandlerBase::GetWindowLevel, Destryoed()=%s",
3785      this, TrueOrFalse(Destroyed())));
3787   if (Destroyed()) {
3788     return NSNormalWindowLevel;
3789   }
3791   // When an <input> element on a XUL <panel> is focused, the actual focused view
3792   // is the panel's parent view (mView). But the editor is displayed on the
3793   // popped-up widget's view (editorView).  We want the latter's window level.
3794   NSView<mozView>* editorView = mWidget->GetEditorView();
3795   NS_ENSURE_TRUE(editorView, NSNormalWindowLevel);
3796   NSInteger windowLevel = [[editorView window] level];
3798   PR_LOG(gLog, PR_LOG_ALWAYS,
3799     ("%p TextInputHandlerBase::GetWindowLevel, windowLevel=%s (%X)",
3800      this, GetWindowLevelName(windowLevel), windowLevel));
3802   return windowLevel;
3804   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSNormalWindowLevel);
3807 NS_IMETHODIMP
3808 TextInputHandlerBase::AttachNativeKeyEvent(WidgetKeyboardEvent& aKeyEvent)
3810   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
3812   // Don't try to replace a native event if one already exists.
3813   // OS X doesn't have an OS modifier, can't make a native event.
3814   if (aKeyEvent.mNativeKeyEvent || aKeyEvent.modifiers & MODIFIER_OS) {
3815     return NS_OK;
3816   }
3818   PR_LOG(gLog, PR_LOG_ALWAYS,
3819     ("%p TextInputHandlerBase::AttachNativeKeyEvent, key=0x%X, char=0x%X, "
3820      "mod=0x%X", this, aKeyEvent.keyCode, aKeyEvent.charCode,
3821      aKeyEvent.modifiers));
3823   NSEventType eventType;
3824   if (aKeyEvent.message == NS_KEY_UP) {
3825     eventType = NSKeyUp;
3826   } else {
3827     eventType = NSKeyDown;
3828   }
3830   static const uint32_t sModifierFlagMap[][2] = {
3831     { MODIFIER_SHIFT,    NSShiftKeyMask },
3832     { MODIFIER_CONTROL,  NSControlKeyMask },
3833     { MODIFIER_ALT,      NSAlternateKeyMask },
3834     { MODIFIER_ALTGRAPH, NSAlternateKeyMask },
3835     { MODIFIER_META,     NSCommandKeyMask },
3836     { MODIFIER_CAPSLOCK, NSAlphaShiftKeyMask },
3837     { MODIFIER_NUMLOCK,  NSNumericPadKeyMask }
3838   };
3840   NSUInteger modifierFlags = 0;
3841   for (uint32_t i = 0; i < ArrayLength(sModifierFlagMap); ++i) {
3842     if (aKeyEvent.modifiers & sModifierFlagMap[i][0]) {
3843       modifierFlags |= sModifierFlagMap[i][1];
3844     }
3845   }
3847   NSInteger windowNumber = [[mView window] windowNumber];
3849   NSString* characters;
3850   if (aKeyEvent.charCode) {
3851     characters = [NSString stringWithCharacters:
3852       reinterpret_cast<const unichar*>(&(aKeyEvent.charCode)) length:1];
3853   } else {
3854     uint32_t cocoaCharCode =
3855       nsCocoaUtils::ConvertGeckoKeyCodeToMacCharCode(aKeyEvent.keyCode);
3856     characters = [NSString stringWithCharacters:
3857       reinterpret_cast<const unichar*>(&cocoaCharCode) length:1];
3858   }
3860   aKeyEvent.mNativeKeyEvent =
3861     [NSEvent     keyEventWithType:eventType
3862                          location:NSMakePoint(0,0)
3863                     modifierFlags:modifierFlags
3864                         timestamp:0
3865                      windowNumber:windowNumber
3866                           context:[NSGraphicsContext currentContext]
3867                        characters:characters
3868       charactersIgnoringModifiers:characters
3869                         isARepeat:NO
3870                           keyCode:0]; // Native key code not currently needed
3872   return NS_OK;
3874   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
3877 bool
3878 TextInputHandlerBase::SetSelection(NSRange& aRange)
3880   MOZ_ASSERT(!Destroyed());
3882   nsRefPtr<TextInputHandlerBase> kungFuDeathGrip(this);
3883   WidgetSelectionEvent selectionEvent(true, NS_SELECTION_SET, mWidget);
3884   selectionEvent.mOffset = aRange.location;
3885   selectionEvent.mLength = aRange.length;
3886   selectionEvent.mReversed = false;
3887   selectionEvent.mExpandToClusterBoundary = false;
3888   DispatchEvent(selectionEvent);
3889   NS_ENSURE_TRUE(selectionEvent.mSucceeded, false);
3890   return !Destroyed();
3893 /* static */ bool
3894 TextInputHandlerBase::IsPrintableChar(char16_t aChar)
3896   return (aChar >= 0x20 && aChar <= 0x7E) || aChar >= 0xA0;
3900 /* static */ bool
3901 TextInputHandlerBase::IsSpecialGeckoKey(UInt32 aNativeKeyCode)
3903   // this table is used to determine which keys are special and should not
3904   // generate a charCode
3905   switch (aNativeKeyCode) {
3906     // modifiers - we don't get separate events for these yet
3907     case kVK_Escape:
3908     case kVK_Shift:
3909     case kVK_RightShift:
3910     case kVK_Command:
3911     case kVK_RightCommand:
3912     case kVK_CapsLock:
3913     case kVK_Control:
3914     case kVK_RightControl:
3915     case kVK_Option:
3916     case kVK_RightOption:
3917     case kVK_ANSI_KeypadClear:
3918     case kVK_Function:
3920     // function keys
3921     case kVK_F1:
3922     case kVK_F2:
3923     case kVK_F3:
3924     case kVK_F4:
3925     case kVK_F5:
3926     case kVK_F6:
3927     case kVK_F7:
3928     case kVK_F8:
3929     case kVK_F9:
3930     case kVK_F10:
3931     case kVK_F11:
3932     case kVK_F12:
3933     case kVK_PC_Pause:
3934     case kVK_PC_ScrollLock:
3935     case kVK_PC_PrintScreen:
3936     case kVK_F16:
3937     case kVK_F17:
3938     case kVK_F18:
3939     case kVK_F19:
3941     case kVK_PC_Insert:
3942     case kVK_PC_Delete:
3943     case kVK_Tab:
3944     case kVK_PC_Backspace:
3945     case kVK_PC_ContextMenu:
3947     case kVK_JIS_Eisu:
3948     case kVK_JIS_Kana:
3950     case kVK_Home:
3951     case kVK_End:
3952     case kVK_PageUp:
3953     case kVK_PageDown:
3954     case kVK_LeftArrow:
3955     case kVK_RightArrow:
3956     case kVK_UpArrow:
3957     case kVK_DownArrow:
3958     case kVK_Return:
3959     case kVK_ANSI_KeypadEnter:
3960     case kVK_Powerbook_KeypadEnter:
3961       return true;
3962   }
3963   return false;
3966 /* static */ bool
3967 TextInputHandlerBase::IsNormalCharInputtingEvent(
3968                         const WidgetKeyboardEvent& aKeyEvent)
3970   // this is not character inputting event, simply.
3971   if (!aKeyEvent.isChar || !aKeyEvent.charCode || aKeyEvent.IsMeta()) {
3972     return false;
3973   }
3974   // if this is unicode char inputting event, we don't need to check
3975   // ctrl/alt/command keys
3976   if (aKeyEvent.charCode > 0x7F) {
3977     return true;
3978   }
3979   // ASCII chars should be inputted without ctrl/alt/command keys
3980   return !aKeyEvent.IsControl() && !aKeyEvent.IsAlt();
3983 /* static */ bool
3984 TextInputHandlerBase::IsModifierKey(UInt32 aNativeKeyCode)
3986   switch (aNativeKeyCode) {
3987     case kVK_CapsLock:
3988     case kVK_RightCommand:
3989     case kVK_Command:
3990     case kVK_Shift:
3991     case kVK_Option:
3992     case kVK_Control:
3993     case kVK_RightShift:
3994     case kVK_RightOption:
3995     case kVK_RightControl:
3996     case kVK_Function:
3997       return true;
3998   }
3999   return false;
4002 /* static */ void
4003 TextInputHandlerBase::EnableSecureEventInput()
4005   sSecureEventInputCount++;
4006   ::EnableSecureEventInput();
4009 /* static */ void
4010 TextInputHandlerBase::DisableSecureEventInput()
4012   if (!sSecureEventInputCount) {
4013     return;
4014   }
4015   sSecureEventInputCount--;
4016   ::DisableSecureEventInput();
4019 /* static */ bool
4020 TextInputHandlerBase::IsSecureEventInputEnabled()
4022   NS_ASSERTION(!!sSecureEventInputCount == !!::IsSecureEventInputEnabled(),
4023                "Some other process has enabled secure event input");
4024   return !!sSecureEventInputCount;
4027 /* static */ void
4028 TextInputHandlerBase::EnsureSecureEventInputDisabled()
4030   while (sSecureEventInputCount) {
4031     TextInputHandlerBase::DisableSecureEventInput();
4032   }