widget/cocoa/TextInputHandler.mm

Thu, 15 Jan 2015 15:59:08 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:59:08 +0100
branch
TOR_BUG_9701
changeset 10
ac0c01689b40
permissions
-rw-r--r--

Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     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 #ifdef MOZ_LOGGING
    12 #define FORCE_PR_LOG /* Allow logging in the release build */
    13 #endif // MOZ_LOGGING
    14 #include "prlog.h"
    16 #include "mozilla/MiscEvents.h"
    17 #include "mozilla/MouseEvents.h"
    18 #include "mozilla/TextEvents.h"
    20 #include "nsChildView.h"
    21 #include "nsObjCExceptions.h"
    22 #include "nsBidiUtils.h"
    23 #include "nsToolkit.h"
    24 #include "nsCocoaUtils.h"
    25 #include "WidgetUtils.h"
    26 #include "nsPrintfCString.h"
    28 #ifdef __LP64__
    29 #include "ComplexTextInputPanel.h"
    30 #include <objc/runtime.h>
    31 #endif // __LP64__
    33 #ifdef MOZ_LOGGING
    34 #define FORCE_PR_LOG
    35 #endif
    36 #include "prlog.h"
    38 #ifndef __LP64__
    39 enum {
    40   // Currently focused ChildView (while this TSM document is active).
    41   // Transient (only set while TSMProcessRawKeyEvent() is processing a key
    42   // event), and the ChildView will be retained and released around the call
    43   // to TSMProcessRawKeyEvent() -- so it can be weak.
    44   kFocusedChildViewTSMDocPropertyTag  = 'GKFV', // type ChildView* [WEAK]
    45 };
    47 // Undocumented HIToolbox function used by WebKit to allow Carbon-based IME
    48 // to work in a Cocoa-based browser (like Safari or Cocoa-widgets Firefox).
    49 // (Recent WebKit versions actually use a thin wrapper around this function
    50 // called WKSendKeyEventToTSM().)
    51 //
    52 // Calling TSMProcessRawKeyEvent() from ChildView's keyDown: and keyUp:
    53 // methods (when the ChildView is a plugin view) bypasses Cocoa's IME
    54 // infrastructure and (instead) causes Carbon TSM events to be sent on each
    55 // NSKeyDown event.  We install a Carbon event handler
    56 // (PluginKeyEventsHandler()) to catch these events and pass them to Gecko
    57 // (which in turn passes them to the plugin).
    58 extern "C" long TSMProcessRawKeyEvent(EventRef carbonEvent);
    59 #endif // __LP64__
    61 using namespace mozilla;
    62 using namespace mozilla::widget;
    64 #ifdef PR_LOGGING
    66 PRLogModuleInfo* gLog = nullptr;
    68 static const char*
    69 OnOrOff(bool aBool)
    70 {
    71   return aBool ? "ON" : "off";
    72 }
    74 static const char*
    75 TrueOrFalse(bool aBool)
    76 {
    77   return aBool ? "TRUE" : "FALSE";
    78 }
    80 static const char*
    81 GetKeyNameForNativeKeyCode(unsigned short aNativeKeyCode)
    82 {
    83   switch (aNativeKeyCode) {
    84     case kVK_Escape:              return "Escape";
    85     case kVK_RightCommand:        return "Right-Command";
    86     case kVK_Command:             return "Command";
    87     case kVK_Shift:               return "Shift";
    88     case kVK_CapsLock:            return "CapsLock";
    89     case kVK_Option:              return "Option";
    90     case kVK_Control:             return "Control";
    91     case kVK_RightShift:          return "Right-Shift";
    92     case kVK_RightOption:         return "Right-Option";
    93     case kVK_RightControl:        return "Right-Control";
    94     case kVK_ANSI_KeypadClear:    return "Clear";
    96     case kVK_F1:                  return "F1";
    97     case kVK_F2:                  return "F2";
    98     case kVK_F3:                  return "F3";
    99     case kVK_F4:                  return "F4";
   100     case kVK_F5:                  return "F5";
   101     case kVK_F6:                  return "F6";
   102     case kVK_F7:                  return "F7";
   103     case kVK_F8:                  return "F8";
   104     case kVK_F9:                  return "F9";
   105     case kVK_F10:                 return "F10";
   106     case kVK_F11:                 return "F11";
   107     case kVK_F12:                 return "F12";
   108     case kVK_F13:                 return "F13/PrintScreen";
   109     case kVK_F14:                 return "F14/ScrollLock";
   110     case kVK_F15:                 return "F15/Pause";
   112     case kVK_ANSI_Keypad0:        return "NumPad-0";
   113     case kVK_ANSI_Keypad1:        return "NumPad-1";
   114     case kVK_ANSI_Keypad2:        return "NumPad-2";
   115     case kVK_ANSI_Keypad3:        return "NumPad-3";
   116     case kVK_ANSI_Keypad4:        return "NumPad-4";
   117     case kVK_ANSI_Keypad5:        return "NumPad-5";
   118     case kVK_ANSI_Keypad6:        return "NumPad-6";
   119     case kVK_ANSI_Keypad7:        return "NumPad-7";
   120     case kVK_ANSI_Keypad8:        return "NumPad-8";
   121     case kVK_ANSI_Keypad9:        return "NumPad-9";
   123     case kVK_ANSI_KeypadMultiply: return "NumPad-*";
   124     case kVK_ANSI_KeypadPlus:     return "NumPad-+";
   125     case kVK_ANSI_KeypadMinus:    return "NumPad--";
   126     case kVK_ANSI_KeypadDecimal:  return "NumPad-.";
   127     case kVK_ANSI_KeypadDivide:   return "NumPad-/";
   128     case kVK_ANSI_KeypadEquals:   return "NumPad-=";
   129     case kVK_ANSI_KeypadEnter:    return "NumPad-Enter";
   130     case kVK_Return:              return "Return";
   131     case kVK_Powerbook_KeypadEnter: return "NumPad-EnterOnPowerBook";
   133     case kVK_PC_Insert:           return "Insert/Help";
   134     case kVK_PC_Delete:           return "Delete";
   135     case kVK_Tab:                 return "Tab";
   136     case kVK_PC_Backspace:        return "Backspace";
   137     case kVK_Home:                return "Home";
   138     case kVK_End:                 return "End";
   139     case kVK_PageUp:              return "PageUp";
   140     case kVK_PageDown:            return "PageDown";
   141     case kVK_LeftArrow:           return "LeftArrow";
   142     case kVK_RightArrow:          return "RightArrow";
   143     case kVK_UpArrow:             return "UpArrow";
   144     case kVK_DownArrow:           return "DownArrow";
   145     case kVK_PC_ContextMenu:      return "ContextMenu";
   147     case kVK_Function:            return "Function";
   148     case kVK_VolumeUp:            return "VolumeUp";
   149     case kVK_VolumeDown:          return "VolumeDown";
   150     case kVK_Mute:                return "Mute";
   152     case kVK_ISO_Section:         return "ISO_Section";
   154     case kVK_JIS_Yen:             return "JIS_Yen";
   155     case kVK_JIS_Underscore:      return "JIS_Underscore";
   156     case kVK_JIS_KeypadComma:     return "JIS_KeypadComma";
   157     case kVK_JIS_Eisu:            return "JIS_Eisu";
   158     case kVK_JIS_Kana:            return "JIS_Kana";
   160     case kVK_ANSI_A:              return "A";
   161     case kVK_ANSI_B:              return "B";
   162     case kVK_ANSI_C:              return "C";
   163     case kVK_ANSI_D:              return "D";
   164     case kVK_ANSI_E:              return "E";
   165     case kVK_ANSI_F:              return "F";
   166     case kVK_ANSI_G:              return "G";
   167     case kVK_ANSI_H:              return "H";
   168     case kVK_ANSI_I:              return "I";
   169     case kVK_ANSI_J:              return "J";
   170     case kVK_ANSI_K:              return "K";
   171     case kVK_ANSI_L:              return "L";
   172     case kVK_ANSI_M:              return "M";
   173     case kVK_ANSI_N:              return "N";
   174     case kVK_ANSI_O:              return "O";
   175     case kVK_ANSI_P:              return "P";
   176     case kVK_ANSI_Q:              return "Q";
   177     case kVK_ANSI_R:              return "R";
   178     case kVK_ANSI_S:              return "S";
   179     case kVK_ANSI_T:              return "T";
   180     case kVK_ANSI_U:              return "U";
   181     case kVK_ANSI_V:              return "V";
   182     case kVK_ANSI_W:              return "W";
   183     case kVK_ANSI_X:              return "X";
   184     case kVK_ANSI_Y:              return "Y";
   185     case kVK_ANSI_Z:              return "Z";
   187     case kVK_ANSI_1:              return "1";
   188     case kVK_ANSI_2:              return "2";
   189     case kVK_ANSI_3:              return "3";
   190     case kVK_ANSI_4:              return "4";
   191     case kVK_ANSI_5:              return "5";
   192     case kVK_ANSI_6:              return "6";
   193     case kVK_ANSI_7:              return "7";
   194     case kVK_ANSI_8:              return "8";
   195     case kVK_ANSI_9:              return "9";
   196     case kVK_ANSI_0:              return "0";
   197     case kVK_ANSI_Equal:          return "Equal";
   198     case kVK_ANSI_Minus:          return "Minus";
   199     case kVK_ANSI_RightBracket:   return "RightBracket";
   200     case kVK_ANSI_LeftBracket:    return "LeftBracket";
   201     case kVK_ANSI_Quote:          return "Quote";
   202     case kVK_ANSI_Semicolon:      return "Semicolon";
   203     case kVK_ANSI_Backslash:      return "Backslash";
   204     case kVK_ANSI_Comma:          return "Comma";
   205     case kVK_ANSI_Slash:          return "Slash";
   206     case kVK_ANSI_Period:         return "Period";
   207     case kVK_ANSI_Grave:          return "Grave";
   209     default:                      return "undefined";
   210   }
   211 }
   213 static const char*
   214 GetCharacters(const NSString* aString)
   215 {
   216   nsAutoString str;
   217   nsCocoaUtils::GetStringForNSString(aString, str);
   218   if (str.IsEmpty()) {
   219     return "";
   220   }
   222   nsAutoString escapedStr;
   223   for (uint32_t i = 0; i < str.Length(); i++) {
   224     char16_t ch = str[i];
   225     if (ch < 0x20) {
   226       nsPrintfCString utf8str("(U+%04X)", ch);
   227       escapedStr += NS_ConvertUTF8toUTF16(utf8str);
   228     } else if (ch <= 0x7E) {
   229       escapedStr += ch;
   230     } else {
   231       nsPrintfCString utf8str("(U+%04X)", ch);
   232       escapedStr += ch;
   233       escapedStr += NS_ConvertUTF8toUTF16(utf8str);
   234     }
   235   }
   237   // the result will be freed automatically by cocoa.
   238   NSString* result = nsCocoaUtils::ToNSString(escapedStr);
   239   return [result UTF8String];
   240 }
   242 static const char*
   243 GetCharacters(const CFStringRef aString)
   244 {
   245   const NSString* str = reinterpret_cast<const NSString*>(aString);
   246   return GetCharacters(str);
   247 }
   249 static const char*
   250 GetNativeKeyEventType(NSEvent* aNativeEvent)
   251 {
   252   switch ([aNativeEvent type]) {
   253     case NSKeyDown:      return "NSKeyDown";
   254     case NSKeyUp:        return "NSKeyUp";
   255     case NSFlagsChanged: return "NSFlagsChanged";
   256     default:             return "not key event";
   257   }
   258 }
   260 static const char*
   261 GetGeckoKeyEventType(const WidgetEvent& aEvent)
   262 {
   263   switch (aEvent.message) {
   264     case NS_KEY_DOWN:    return "NS_KEY_DOWN";
   265     case NS_KEY_UP:      return "NS_KEY_UP";
   266     case NS_KEY_PRESS:   return "NS_KEY_PRESS";
   267     default:             return "not key event";
   268   }
   269 }
   271 static const char*
   272 GetRangeTypeName(uint32_t aRangeType)
   273 {
   274   switch (aRangeType) {
   275     case NS_TEXTRANGE_RAWINPUT:
   276       return "NS_TEXTRANGE_RAWINPUT";
   277     case NS_TEXTRANGE_CONVERTEDTEXT:
   278       return "NS_TEXTRANGE_CONVERTEDTEXT";
   279     case NS_TEXTRANGE_SELECTEDRAWTEXT:
   280       return "NS_TEXTRANGE_SELECTEDRAWTEXT";
   281     case NS_TEXTRANGE_SELECTEDCONVERTEDTEXT:
   282       return "NS_TEXTRANGE_SELECTEDCONVERTEDTEXT";
   283     case NS_TEXTRANGE_CARETPOSITION:
   284       return "NS_TEXTRANGE_CARETPOSITION";
   285     default:
   286       return "invalid range type";
   287   }
   288 }
   290 static const char*
   291 GetWindowLevelName(NSInteger aWindowLevel)
   292 {
   293   switch (aWindowLevel) {
   294     case kCGBaseWindowLevelKey:
   295       return "kCGBaseWindowLevelKey (NSNormalWindowLevel)";
   296     case kCGMinimumWindowLevelKey:
   297       return "kCGMinimumWindowLevelKey";
   298     case kCGDesktopWindowLevelKey:
   299       return "kCGDesktopWindowLevelKey";
   300     case kCGBackstopMenuLevelKey:
   301       return "kCGBackstopMenuLevelKey";
   302     case kCGNormalWindowLevelKey:
   303       return "kCGNormalWindowLevelKey";
   304     case kCGFloatingWindowLevelKey:
   305       return "kCGFloatingWindowLevelKey (NSFloatingWindowLevel)";
   306     case kCGTornOffMenuWindowLevelKey:
   307       return "kCGTornOffMenuWindowLevelKey (NSSubmenuWindowLevel, NSTornOffMenuWindowLevel)";
   308     case kCGDockWindowLevelKey:
   309       return "kCGDockWindowLevelKey (NSDockWindowLevel)";
   310     case kCGMainMenuWindowLevelKey:
   311       return "kCGMainMenuWindowLevelKey (NSMainMenuWindowLevel)";
   312     case kCGStatusWindowLevelKey:
   313       return "kCGStatusWindowLevelKey (NSStatusWindowLevel)";
   314     case kCGModalPanelWindowLevelKey:
   315       return "kCGModalPanelWindowLevelKey (NSModalPanelWindowLevel)";
   316     case kCGPopUpMenuWindowLevelKey:
   317       return "kCGPopUpMenuWindowLevelKey (NSPopUpMenuWindowLevel)";
   318     case kCGDraggingWindowLevelKey:
   319       return "kCGDraggingWindowLevelKey";
   320     case kCGScreenSaverWindowLevelKey:
   321       return "kCGScreenSaverWindowLevelKey (NSScreenSaverWindowLevel)";
   322     case kCGMaximumWindowLevelKey:
   323       return "kCGMaximumWindowLevelKey";
   324     case kCGOverlayWindowLevelKey:
   325       return "kCGOverlayWindowLevelKey";
   326     case kCGHelpWindowLevelKey:
   327       return "kCGHelpWindowLevelKey";
   328     case kCGUtilityWindowLevelKey:
   329       return "kCGUtilityWindowLevelKey";
   330     case kCGDesktopIconWindowLevelKey:
   331       return "kCGDesktopIconWindowLevelKey";
   332     case kCGCursorWindowLevelKey:
   333       return "kCGCursorWindowLevelKey";
   334     case kCGNumberOfWindowLevelKeys:
   335       return "kCGNumberOfWindowLevelKeys";
   336     default:
   337       return "unknown window level";
   338   }
   339 }
   341 #endif // #ifdef PR_LOGGING
   343 static bool
   344 IsControlChar(uint32_t aCharCode)
   345 {
   346   return aCharCode < ' ' || aCharCode == 0x7F;
   347 }
   349 static uint32_t gHandlerInstanceCount = 0;
   350 static TISInputSourceWrapper gCurrentInputSource;
   352 static void
   353 InitLogModule()
   354 {
   355 #ifdef PR_LOGGING
   356   // Clear() is always called when TISInputSourceWrappper is created.
   357   if (!gLog) {
   358     gLog = PR_NewLogModule("TextInputHandlerWidgets");
   359     TextInputHandler::DebugPrintAllKeyboardLayouts();
   360     IMEInputHandler::DebugPrintAllIMEModes();
   361   }
   362 #endif
   363 }
   365 static void
   366 InitCurrentInputSource()
   367 {
   368   if (gHandlerInstanceCount > 0 &&
   369       !gCurrentInputSource.IsInitializedByCurrentInputSource()) {
   370     gCurrentInputSource.InitByCurrentInputSource();
   371   }
   372 }
   374 static void
   375 FinalizeCurrentInputSource()
   376 {
   377   gCurrentInputSource.Clear();
   378 }
   381 #pragma mark -
   384 /******************************************************************************
   385  *
   386  *  TISInputSourceWrapper implementation
   387  *
   388  ******************************************************************************/
   390 // static
   391 TISInputSourceWrapper&
   392 TISInputSourceWrapper::CurrentInputSource()
   393 {
   394   InitCurrentInputSource();
   395   return gCurrentInputSource;
   396 }
   398 bool
   399 TISInputSourceWrapper::TranslateToString(UInt32 aKeyCode, UInt32 aModifiers,
   400                                          UInt32 aKbType, nsAString &aStr)
   401 {
   402   aStr.Truncate();
   404   const UCKeyboardLayout* UCKey = GetUCKeyboardLayout();
   406   PR_LOG(gLog, PR_LOG_ALWAYS,
   407     ("%p TISInputSourceWrapper::TranslateToString, aKeyCode=0x%X, "
   408      "aModifiers=0x%X, aKbType=0x%X UCKey=%p\n    "
   409      "Shift: %s, Ctrl: %s, Opt: %s, Cmd: %s, CapsLock: %s, NumLock: %s",
   410      this, aKeyCode, aModifiers, aKbType, UCKey,
   411      OnOrOff(aModifiers & shiftKey), OnOrOff(aModifiers & controlKey),
   412      OnOrOff(aModifiers & optionKey), OnOrOff(aModifiers & cmdKey),
   413      OnOrOff(aModifiers & alphaLock),
   414      OnOrOff(aModifiers & kEventKeyModifierNumLockMask)));
   416   NS_ENSURE_TRUE(UCKey, false);
   418   UInt32 deadKeyState = 0;
   419   UniCharCount len;
   420   UniChar chars[5];
   421   OSStatus err = ::UCKeyTranslate(UCKey, aKeyCode,
   422                                   kUCKeyActionDown, aModifiers >> 8,
   423                                   aKbType, kUCKeyTranslateNoDeadKeysMask,
   424                                   &deadKeyState, 5, &len, chars);
   426   PR_LOG(gLog, PR_LOG_ALWAYS,
   427     ("%p TISInputSourceWrapper::TranslateToString, err=0x%X, len=%llu",
   428      this, err, len));
   430   NS_ENSURE_TRUE(err == noErr, false);
   431   if (len == 0) {
   432     return true;
   433   }
   434   NS_ENSURE_TRUE(EnsureStringLength(aStr, len), false);
   435   NS_ASSERTION(sizeof(char16_t) == sizeof(UniChar),
   436                "size of char16_t and size of UniChar are different");
   437   memcpy(aStr.BeginWriting(), chars, len * sizeof(char16_t));
   439   PR_LOG(gLog, PR_LOG_ALWAYS,
   440     ("%p TISInputSourceWrapper::TranslateToString, aStr=\"%s\"",
   441      this, NS_ConvertUTF16toUTF8(aStr).get()));
   443   return true;
   444 }
   446 uint32_t
   447 TISInputSourceWrapper::TranslateToChar(UInt32 aKeyCode, UInt32 aModifiers,
   448                                        UInt32 aKbType)
   449 {
   450   nsAutoString str;
   451   if (!TranslateToString(aKeyCode, aModifiers, aKbType, str) ||
   452       str.Length() != 1) {
   453     return 0;
   454   }
   455   return static_cast<uint32_t>(str.CharAt(0));
   456 }
   458 void
   459 TISInputSourceWrapper::InitByInputSourceID(const char* aID)
   460 {
   461   Clear();
   462   if (!aID)
   463     return;
   465   CFStringRef idstr = ::CFStringCreateWithCString(kCFAllocatorDefault, aID,
   466                                                   kCFStringEncodingASCII);
   467   InitByInputSourceID(idstr);
   468   ::CFRelease(idstr);
   469 }
   471 void
   472 TISInputSourceWrapper::InitByInputSourceID(const nsAFlatString &aID)
   473 {
   474   Clear();
   475   if (aID.IsEmpty())
   476     return;
   477   CFStringRef idstr = ::CFStringCreateWithCharacters(kCFAllocatorDefault,
   478                                                      reinterpret_cast<const UniChar*>(aID.get()),
   479                                                      aID.Length());
   480   InitByInputSourceID(idstr);
   481   ::CFRelease(idstr);
   482 }
   484 void
   485 TISInputSourceWrapper::InitByInputSourceID(const CFStringRef aID)
   486 {
   487   Clear();
   488   if (!aID)
   489     return;
   490   const void* keys[] = { kTISPropertyInputSourceID };
   491   const void* values[] = { aID };
   492   CFDictionaryRef filter =
   493   ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
   494   NS_ASSERTION(filter, "failed to create the filter");
   495   mInputSourceList = ::TISCreateInputSourceList(filter, true);
   496   ::CFRelease(filter);
   497   if (::CFArrayGetCount(mInputSourceList) > 0) {
   498     mInputSource = static_cast<TISInputSourceRef>(
   499       const_cast<void *>(::CFArrayGetValueAtIndex(mInputSourceList, 0)));
   500     if (IsKeyboardLayout()) {
   501       mKeyboardLayout = mInputSource;
   502     }
   503   }
   504 }
   506 void
   507 TISInputSourceWrapper::InitByLayoutID(SInt32 aLayoutID,
   508                                       bool aOverrideKeyboard)
   509 {
   510   // NOTE: Doument new layout IDs in TextInputHandler.h when you add ones.
   511   switch (aLayoutID) {
   512     case 0:
   513       InitByInputSourceID("com.apple.keylayout.US");
   514       break;
   515     case 1:
   516       InitByInputSourceID("com.apple.keylayout.Greek");
   517       break;
   518     case 2:
   519       InitByInputSourceID("com.apple.keylayout.German");
   520       break;
   521     case 3:
   522       InitByInputSourceID("com.apple.keylayout.Swedish-Pro");
   523       break;
   524     case 4:
   525       InitByInputSourceID("com.apple.keylayout.DVORAK-QWERTYCMD");
   526       break;
   527     case 5:
   528       InitByInputSourceID("com.apple.keylayout.Thai");
   529       break;
   530     case 6:
   531       InitByInputSourceID("com.apple.keylayout.Arabic");
   532       break;
   533     case 7:
   534       InitByInputSourceID("com.apple.keylayout.French");
   535       break;
   536     case 8:
   537       InitByInputSourceID("com.apple.keylayout.Hebrew");
   538       break;
   539     case 9:
   540       InitByInputSourceID("com.apple.keylayout.Lithuanian");
   541       break;
   542     case 10:
   543       InitByInputSourceID("com.apple.keylayout.Norwegian");
   544       break;
   545     case 11:
   546       InitByInputSourceID("com.apple.keylayout.Spanish");
   547       break;
   548     default:
   549       Clear();
   550       break;
   551   }
   552   mOverrideKeyboard = aOverrideKeyboard;
   553 }
   555 void
   556 TISInputSourceWrapper::InitByCurrentInputSource()
   557 {
   558   Clear();
   559   mInputSource = ::TISCopyCurrentKeyboardInputSource();
   560   mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride();
   561   if (!mKeyboardLayout) {
   562     mKeyboardLayout = ::TISCopyCurrentKeyboardLayoutInputSource();
   563   }
   564   // If this causes composition, the current keyboard layout may input non-ASCII
   565   // characters such as Japanese Kana characters or Hangul characters.
   566   // However, we need to set ASCII characters to DOM key events for consistency
   567   // with other platforms.
   568   if (IsOpenedIMEMode()) {
   569     TISInputSourceWrapper tis(mKeyboardLayout);
   570     if (!tis.IsASCIICapable()) {
   571       mKeyboardLayout =
   572         ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
   573     }
   574   }
   575 }
   577 void
   578 TISInputSourceWrapper::InitByCurrentKeyboardLayout()
   579 {
   580   Clear();
   581   mInputSource = ::TISCopyCurrentKeyboardLayoutInputSource();
   582   mKeyboardLayout = mInputSource;
   583 }
   585 void
   586 TISInputSourceWrapper::InitByCurrentASCIICapableInputSource()
   587 {
   588   Clear();
   589   mInputSource = ::TISCopyCurrentASCIICapableKeyboardInputSource();
   590   mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride();
   591   if (mKeyboardLayout) {
   592     TISInputSourceWrapper tis(mKeyboardLayout);
   593     if (!tis.IsASCIICapable()) {
   594       mKeyboardLayout = nullptr;
   595     }
   596   }
   597   if (!mKeyboardLayout) {
   598     mKeyboardLayout =
   599       ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
   600   }
   601 }
   603 void
   604 TISInputSourceWrapper::InitByCurrentASCIICapableKeyboardLayout()
   605 {
   606   Clear();
   607   mInputSource = ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
   608   mKeyboardLayout = mInputSource;
   609 }
   611 void
   612 TISInputSourceWrapper::InitByCurrentInputMethodKeyboardLayoutOverride()
   613 {
   614   Clear();
   615   mInputSource = ::TISCopyInputMethodKeyboardLayoutOverride();
   616   mKeyboardLayout = mInputSource;
   617 }
   619 void
   620 TISInputSourceWrapper::InitByTISInputSourceRef(TISInputSourceRef aInputSource)
   621 {
   622   Clear();
   623   mInputSource = aInputSource;
   624   if (IsKeyboardLayout()) {
   625     mKeyboardLayout = mInputSource;
   626   }
   627 }
   629 void
   630 TISInputSourceWrapper::InitByLanguage(CFStringRef aLanguage)
   631 {
   632   Clear();
   633   mInputSource = ::TISCopyInputSourceForLanguage(aLanguage);
   634   if (IsKeyboardLayout()) {
   635     mKeyboardLayout = mInputSource;
   636   }
   637 }
   639 const UCKeyboardLayout*
   640 TISInputSourceWrapper::GetUCKeyboardLayout()
   641 {
   642   NS_ENSURE_TRUE(mKeyboardLayout, nullptr);
   643   if (mUCKeyboardLayout) {
   644     return mUCKeyboardLayout;
   645   }
   646   CFDataRef uchr = static_cast<CFDataRef>(
   647     ::TISGetInputSourceProperty(mKeyboardLayout,
   648                                 kTISPropertyUnicodeKeyLayoutData));
   650   // We should be always able to get the layout here.
   651   NS_ENSURE_TRUE(uchr, nullptr);
   652   mUCKeyboardLayout =
   653     reinterpret_cast<const UCKeyboardLayout*>(CFDataGetBytePtr(uchr));
   654   return mUCKeyboardLayout;
   655 }
   657 bool
   658 TISInputSourceWrapper::GetBoolProperty(const CFStringRef aKey)
   659 {
   660   CFBooleanRef ret = static_cast<CFBooleanRef>(
   661     ::TISGetInputSourceProperty(mInputSource, aKey));
   662   return ::CFBooleanGetValue(ret);
   663 }
   665 bool
   666 TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey,
   667                                          CFStringRef &aStr)
   668 {
   669   aStr = static_cast<CFStringRef>(
   670     ::TISGetInputSourceProperty(mInputSource, aKey));
   671   return aStr != nullptr;
   672 }
   674 bool
   675 TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey,
   676                                          nsAString &aStr)
   677 {
   678   CFStringRef str;
   679   GetStringProperty(aKey, str);
   680   nsCocoaUtils::GetStringForNSString((const NSString*)str, aStr);
   681   return !aStr.IsEmpty();
   682 }
   684 bool
   685 TISInputSourceWrapper::IsOpenedIMEMode()
   686 {
   687   NS_ENSURE_TRUE(mInputSource, false);
   688   if (!IsIMEMode())
   689     return false;
   690   return !IsASCIICapable();
   691 }
   693 bool
   694 TISInputSourceWrapper::IsIMEMode()
   695 {
   696   NS_ENSURE_TRUE(mInputSource, false);
   697   CFStringRef str;
   698   GetInputSourceType(str);
   699   NS_ENSURE_TRUE(str, false);
   700   return ::CFStringCompare(kTISTypeKeyboardInputMode,
   701                            str, 0) == kCFCompareEqualTo;
   702 }
   704 bool
   705 TISInputSourceWrapper::IsKeyboardLayout()
   706 {
   707   NS_ENSURE_TRUE(mInputSource, false);
   708   CFStringRef str;
   709   GetInputSourceType(str);
   710   NS_ENSURE_TRUE(str, false);
   711   return ::CFStringCompare(kTISTypeKeyboardLayout,
   712                            str, 0) == kCFCompareEqualTo;
   713 }
   715 bool
   716 TISInputSourceWrapper::GetLanguageList(CFArrayRef &aLanguageList)
   717 {
   718   NS_ENSURE_TRUE(mInputSource, false);
   719   aLanguageList = static_cast<CFArrayRef>(
   720     ::TISGetInputSourceProperty(mInputSource,
   721                                 kTISPropertyInputSourceLanguages));
   722   return aLanguageList != nullptr;
   723 }
   725 bool
   726 TISInputSourceWrapper::GetPrimaryLanguage(CFStringRef &aPrimaryLanguage)
   727 {
   728   NS_ENSURE_TRUE(mInputSource, false);
   729   CFArrayRef langList;
   730   NS_ENSURE_TRUE(GetLanguageList(langList), false);
   731   if (::CFArrayGetCount(langList) == 0)
   732     return false;
   733   aPrimaryLanguage =
   734     static_cast<CFStringRef>(::CFArrayGetValueAtIndex(langList, 0));
   735   return aPrimaryLanguage != nullptr;
   736 }
   738 bool
   739 TISInputSourceWrapper::GetPrimaryLanguage(nsAString &aPrimaryLanguage)
   740 {
   741   NS_ENSURE_TRUE(mInputSource, false);
   742   CFStringRef primaryLanguage;
   743   NS_ENSURE_TRUE(GetPrimaryLanguage(primaryLanguage), false);
   744   nsCocoaUtils::GetStringForNSString((const NSString*)primaryLanguage,
   745                                      aPrimaryLanguage);
   746   return !aPrimaryLanguage.IsEmpty();
   747 }
   749 bool
   750 TISInputSourceWrapper::IsForRTLLanguage()
   751 {
   752   if (mIsRTL < 0) {
   753     // Get the input character of the 'A' key of ANSI keyboard layout.
   754     nsAutoString str;
   755     bool ret = TranslateToString(kVK_ANSI_A, 0, eKbdType_ANSI, str);
   756     NS_ENSURE_TRUE(ret, ret);
   757     char16_t ch = str.IsEmpty() ? char16_t(0) : str.CharAt(0);
   758     mIsRTL = UCS2_CHAR_IS_BIDI(ch) || ch == 0xD802 || ch == 0xD803;
   759   }
   760   return mIsRTL != 0;
   761 }
   763 bool
   764 TISInputSourceWrapper::IsInitializedByCurrentInputSource()
   765 {
   766   return mInputSource == ::TISCopyCurrentKeyboardInputSource();
   767 }
   769 void
   770 TISInputSourceWrapper::Select()
   771 {
   772   if (!mInputSource)
   773     return;
   774   ::TISSelectInputSource(mInputSource);
   775 }
   777 void
   778 TISInputSourceWrapper::Clear()
   779 {
   780   // Clear() is always called when TISInputSourceWrappper is created.
   781   InitLogModule();
   783   if (mInputSourceList) {
   784     ::CFRelease(mInputSourceList);
   785   }
   786   mInputSourceList = nullptr;
   787   mInputSource = nullptr;
   788   mKeyboardLayout = nullptr;
   789   mIsRTL = -1;
   790   mUCKeyboardLayout = nullptr;
   791   mOverrideKeyboard = false;
   792 }
   794 void
   795 TISInputSourceWrapper::InitKeyEvent(NSEvent *aNativeKeyEvent,
   796                                     WidgetKeyboardEvent& aKeyEvent,
   797                                     const nsAString *aInsertString)
   798 {
   799   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   801   PR_LOG(gLog, PR_LOG_ALWAYS,
   802     ("%p TISInputSourceWrapper::InitKeyEvent, aNativeKeyEvent=%p, "
   803      "aKeyEvent.message=%s, aInsertString=%p, IsOpenedIMEMode()=%s",
   804      this, aNativeKeyEvent, GetGeckoKeyEventType(aKeyEvent), aInsertString,
   805      TrueOrFalse(IsOpenedIMEMode())));
   807   NS_ENSURE_TRUE(aNativeKeyEvent, );
   809   nsCocoaUtils::InitInputEvent(aKeyEvent, aNativeKeyEvent);
   811   // This is used only while dispatching the event (which is a synchronous
   812   // call), so there is no need to retain and release this data.
   813   aKeyEvent.mNativeKeyEvent = aNativeKeyEvent;
   815   aKeyEvent.refPoint = LayoutDeviceIntPoint(0, 0);
   817   // If a keyboard layout override is set, we also need to force the keyboard
   818   // type to something ANSI to avoid test failures on machines with JIS
   819   // keyboards (since the pair of keyboard layout and physical keyboard type
   820   // form the actual key layout).  This assumes that the test setting the
   821   // override was written assuming an ANSI keyboard.
   822   UInt32 kbType = mOverrideKeyboard ? eKbdType_ANSI : ::LMGetKbdType();
   824   UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
   826   bool isPrintableKey = !TextInputHandler::IsSpecialGeckoKey(nativeKeyCode);
   827   if (isPrintableKey &&
   828       [aNativeKeyEvent type] != NSKeyDown &&
   829       [aNativeKeyEvent type] != NSKeyUp) {
   830     NS_WARNING("Why the printable key doesn't cause NSKeyDown or NSKeyUp?");
   831     isPrintableKey = false;
   832   }
   834   // Decide what string will be input.
   835   nsAutoString insertString;
   836   if (aInsertString) {
   837     // If the caller expects that the aInsertString will be input, we shouldn't
   838     // change it.
   839     insertString = *aInsertString;
   840   } else if (isPrintableKey) {
   841     // If IME is open, [aNativeKeyEvent characters] may be a character
   842     // which will be appended to the composition string.  However, especially,
   843     // while IME is disabled, most users and developers expect the key event
   844     // works as IME closed.  So, we should compute the insertString with
   845     // the ASCII capable keyboard layout.
   846     // NOTE: Such keyboard layouts typically change the layout to its ASCII
   847     //       capable layout when Command key is pressed.  And we don't worry
   848     //       when Control key is pressed too because it causes inputting
   849     //       control characters.
   850     if (!aKeyEvent.IsMeta() && !aKeyEvent.IsControl() && IsOpenedIMEMode()) {
   851       UInt32 state =
   852         nsCocoaUtils::ConvertToCarbonModifier([aNativeKeyEvent modifierFlags]);
   853       uint32_t ch = TranslateToChar(nativeKeyCode, state, kbType);
   854       if (ch) {
   855         insertString = ch;
   856       }
   857     } else {
   858       // If the caller isn't sure what string will be input, let's use
   859       // characters of NSEvent.
   860       nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters],
   861                                          insertString);
   862     }
   864     // If control key is pressed and the eventChars is a non-printable control
   865     // character, we should convert it to ASCII alphabet.
   866     if (aKeyEvent.IsControl() &&
   867         !insertString.IsEmpty() && insertString[0] <= char16_t(26)) {
   868       insertString = (aKeyEvent.IsShift() ^ aKeyEvent.IsCapsLocked()) ?
   869         static_cast<char16_t>(insertString[0] + ('A' - 1)) :
   870         static_cast<char16_t>(insertString[0] + ('a' - 1));
   871     }
   872     // If Meta key is pressed, it may cause to switch the keyboard layout like
   873     // Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY.
   874     else if (aKeyEvent.IsMeta() &&
   875              !(aKeyEvent.IsControl() || aKeyEvent.IsAlt())) {
   876       UInt32 numLockState =
   877         aKeyEvent.IsNumLocked() ? kEventKeyModifierNumLockMask : 0;
   878       UInt32 capsLockState = aKeyEvent.IsCapsLocked() ? alphaLock : 0;
   879       UInt32 shiftState = aKeyEvent.IsShift() ? shiftKey : 0;
   880       uint32_t uncmdedChar =
   881         TranslateToChar(nativeKeyCode, numLockState, kbType);
   882       uint32_t cmdedChar =
   883         TranslateToChar(nativeKeyCode, cmdKey | numLockState, kbType);
   884       // If we can make a good guess at the characters that the user would
   885       // expect this key combination to produce (with and without Shift) then
   886       // use those characters.  This also corrects for CapsLock.
   887       uint32_t ch = 0;
   888       if (uncmdedChar == cmdedChar) {
   889         // The characters produced with Command seem similar to those without
   890         // Command.
   891         ch = TranslateToChar(nativeKeyCode,
   892                              shiftState | capsLockState | numLockState, kbType);
   893       } else {
   894         TISInputSourceWrapper USLayout("com.apple.keylayout.US");
   895         uint32_t uncmdedUSChar =
   896           USLayout.TranslateToChar(nativeKeyCode, numLockState, kbType);
   897         // If it looks like characters from US keyboard layout when Command key
   898         // is pressed, we should compute a character in the layout.
   899         if (uncmdedUSChar == cmdedChar) {
   900           ch = USLayout.TranslateToChar(nativeKeyCode,
   901                           shiftState | capsLockState | numLockState, kbType);
   902         }
   903       }
   905       // If there is a more preferred character for the commanded key event,
   906       // we should use it.
   907       if (ch) {
   908         insertString = ch;
   909       }
   910     }
   911   }
   913   // Remove control characters which shouldn't be inputted on editor.
   914   // XXX Currently, we don't find any cases inserting control characters with
   915   //     printable character.  So, just checking first character is enough.
   916   if (!insertString.IsEmpty() && IsControlChar(insertString[0])) {
   917     insertString.Truncate();
   918   }
   920   aKeyEvent.keyCode =
   921     ComputeGeckoKeyCode(nativeKeyCode, kbType, aKeyEvent.IsMeta());
   923   switch (nativeKeyCode) {
   924     case kVK_Command:
   925     case kVK_Shift:
   926     case kVK_Option:
   927     case kVK_Control:
   928       aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT;
   929       break;
   931     case kVK_RightCommand:
   932     case kVK_RightShift:
   933     case kVK_RightOption:
   934     case kVK_RightControl:
   935       aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT;
   936       break;
   938     case kVK_ANSI_Keypad0:
   939     case kVK_ANSI_Keypad1:
   940     case kVK_ANSI_Keypad2:
   941     case kVK_ANSI_Keypad3:
   942     case kVK_ANSI_Keypad4:
   943     case kVK_ANSI_Keypad5:
   944     case kVK_ANSI_Keypad6:
   945     case kVK_ANSI_Keypad7:
   946     case kVK_ANSI_Keypad8:
   947     case kVK_ANSI_Keypad9:
   948     case kVK_ANSI_KeypadMultiply:
   949     case kVK_ANSI_KeypadPlus:
   950     case kVK_ANSI_KeypadMinus:
   951     case kVK_ANSI_KeypadDecimal:
   952     case kVK_ANSI_KeypadDivide:
   953     case kVK_ANSI_KeypadEquals:
   954     case kVK_ANSI_KeypadEnter:
   955     case kVK_JIS_KeypadComma:
   956     case kVK_Powerbook_KeypadEnter:
   957       aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD;
   958       break;
   960     default:
   961       aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD;
   962       break;
   963   }
   965   aKeyEvent.mIsRepeat =
   966     ([aNativeKeyEvent type] == NSKeyDown) ? [aNativeKeyEvent isARepeat] : false;
   968   PR_LOG(gLog, PR_LOG_ALWAYS,
   969     ("%p TISInputSourceWrapper::InitKeyEvent, "
   970      "shift=%s, ctrl=%s, alt=%s, meta=%s",
   971      this, OnOrOff(aKeyEvent.IsShift()), OnOrOff(aKeyEvent.IsControl()),
   972      OnOrOff(aKeyEvent.IsAlt()), OnOrOff(aKeyEvent.IsMeta())));
   974   if (aKeyEvent.message == NS_KEY_PRESS &&
   975       (isPrintableKey || !insertString.IsEmpty())) {
   976     InitKeyPressEvent(aNativeKeyEvent,
   977                       insertString.IsEmpty() ? 0 : insertString[0],
   978                       aKeyEvent, kbType);
   979     MOZ_ASSERT(!aKeyEvent.charCode || !IsControlChar(aKeyEvent.charCode),
   980                "charCode must not be a control character");
   981   } else {
   982     aKeyEvent.charCode = 0;
   983     aKeyEvent.isChar = false; // XXX not used in XP level
   985     PR_LOG(gLog, PR_LOG_ALWAYS,
   986       ("%p TISInputSourceWrapper::InitKeyEvent, keyCode=0x%X charCode=0x0",
   987        this, aKeyEvent.keyCode));
   988   }
   990   if (isPrintableKey) {
   991     aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
   992     // If insertText calls this method, let's use the string.
   993     if (aInsertString && !aInsertString->IsEmpty() &&
   994         !IsControlChar((*aInsertString)[0])) {
   995       aKeyEvent.mKeyValue = *aInsertString;
   996     }
   997     // If meta key is pressed, the printable key layout may be switched from
   998     // non-ASCII capable layout to ASCII capable, or from Dvorak to QWERTY.
   999     // KeyboardEvent.key value should be the switched layout's character.
  1000     else if (aKeyEvent.IsMeta()) {
  1001       nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters],
  1002                                          aKeyEvent.mKeyValue);
  1004     // If control key is pressed, some keys may produce printable character via
  1005     // [aNativeKeyEvent characters].  Otherwise, translate input character of
  1006     // the key without control key.
  1007     else if (aKeyEvent.IsControl()) {
  1008       nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters],
  1009                                          aKeyEvent.mKeyValue);
  1010       if (aKeyEvent.mKeyValue.IsEmpty() ||
  1011           IsControlChar(aKeyEvent.mKeyValue[0])) {
  1012         NSUInteger cocoaState =
  1013           [aNativeKeyEvent modifierFlags] & ~NSControlKeyMask;
  1014         UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState);
  1015         aKeyEvent.mKeyValue =
  1016           TranslateToChar(nativeKeyCode, carbonState, kbType);
  1019     // Otherwise, KeyboardEvent.key expose
  1020     // [aNativeKeyEvent characters] value.  However, if IME is open and the
  1021     // keyboard layout isn't ASCII capable, exposing the non-ASCII character
  1022     // doesn't match with other platform's behavior.  For the compatibility
  1023     // with other platform's Gecko, we need to set a translated character.
  1024     else if (IsOpenedIMEMode()) {
  1025       UInt32 state =
  1026         nsCocoaUtils::ConvertToCarbonModifier([aNativeKeyEvent modifierFlags]);
  1027       aKeyEvent.mKeyValue = TranslateToChar(nativeKeyCode, state, kbType);
  1028     } else {
  1029       nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters],
  1030                                          aKeyEvent.mKeyValue);
  1033     // Last resort.  If .key value becomes empty string, we should use
  1034     // charactersIgnoringModifiers, if it's available.
  1035     if (aKeyEvent.mKeyValue.IsEmpty() ||
  1036         IsControlChar(aKeyEvent.mKeyValue[0])) {
  1037       nsCocoaUtils::GetStringForNSString(
  1038         [aNativeKeyEvent charactersIgnoringModifiers], aKeyEvent.mKeyValue);
  1039       // But don't expose it if it's a control character.
  1040       if (!aKeyEvent.mKeyValue.IsEmpty() &&
  1041           IsControlChar(aKeyEvent.mKeyValue[0])) {
  1042         aKeyEvent.mKeyValue.Truncate();
  1045   } else {
  1046     // Compute the key for non-printable keys and some special printable keys.
  1047     aKeyEvent.mKeyNameIndex = ComputeGeckoKeyNameIndex(nativeKeyCode);
  1050   NS_OBJC_END_TRY_ABORT_BLOCK
  1053 void
  1054 TISInputSourceWrapper::InitKeyPressEvent(NSEvent *aNativeKeyEvent,
  1055                                          char16_t aInsertChar,
  1056                                          WidgetKeyboardEvent& aKeyEvent,
  1057                                          UInt32 aKbType)
  1059   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
  1061   NS_ASSERTION(aKeyEvent.message == NS_KEY_PRESS,
  1062                "aKeyEvent must be NS_KEY_PRESS event");
  1064 #ifdef PR_LOGGING
  1065   if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) {
  1066     nsAutoString chars;
  1067     nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], chars);
  1068     NS_ConvertUTF16toUTF8 utf8Chars(chars);
  1069     char16_t expectedChar = static_cast<char16_t>(aInsertChar);
  1070     NS_ConvertUTF16toUTF8 utf8ExpectedChar(&expectedChar, 1);
  1071     PR_LOG(gLog, PR_LOG_ALWAYS,
  1072       ("%p TISInputSourceWrapper::InitKeyPressEvent, aNativeKeyEvent=%p, "
  1073        "[aNativeKeyEvent characters]=\"%s\", aInsertChar=0x%X(%s), "
  1074        "aKeyEvent.message=%s, aKbType=0x%X, IsOpenedIMEMode()=%s",
  1075        this, aNativeKeyEvent, utf8Chars.get(), aInsertChar,
  1076        utf8ExpectedChar.get(), GetGeckoKeyEventType(aKeyEvent), aKbType,
  1077        TrueOrFalse(IsOpenedIMEMode())));
  1079 #endif // #ifdef PR_LOGGING
  1081   aKeyEvent.isChar = true; // this is not a special key  XXX not used in XP
  1082   aKeyEvent.charCode = aInsertChar;
  1083   if (aKeyEvent.charCode != 0) {
  1084     aKeyEvent.keyCode = 0;
  1087   PR_LOG(gLog, PR_LOG_ALWAYS,
  1088     ("%p TISInputSourceWrapper::InitKeyPressEvent, "
  1089      "aKeyEvent.keyCode=0x%X, aKeyEvent.charCode=0x%X",
  1090      this, aKeyEvent.keyCode, aKeyEvent.charCode));
  1092   if (!aKeyEvent.IsControl() && !aKeyEvent.IsMeta() && !aKeyEvent.IsAlt()) {
  1093     return;
  1096   TISInputSourceWrapper USLayout("com.apple.keylayout.US");
  1097   bool isRomanKeyboardLayout = IsASCIICapable();
  1099   UInt32 key = [aNativeKeyEvent keyCode];
  1101   // Caps lock and num lock modifier state:
  1102   UInt32 lockState = 0;
  1103   if ([aNativeKeyEvent modifierFlags] & NSAlphaShiftKeyMask) {
  1104     lockState |= alphaLock;
  1106   if ([aNativeKeyEvent modifierFlags] & NSNumericPadKeyMask) {
  1107     lockState |= kEventKeyModifierNumLockMask;
  1110   PR_LOG(gLog, PR_LOG_ALWAYS,
  1111     ("%p TISInputSourceWrapper::InitKeyPressEvent, "
  1112      "isRomanKeyboardLayout=%s, key=0x%X",
  1113      this, TrueOrFalse(isRomanKeyboardLayout), aKbType, key));
  1115   nsString str;
  1117   // normal chars
  1118   uint32_t unshiftedChar = TranslateToChar(key, lockState, aKbType);
  1119   UInt32 shiftLockMod = shiftKey | lockState;
  1120   uint32_t shiftedChar = TranslateToChar(key, shiftLockMod, aKbType);
  1122   // characters generated with Cmd key
  1123   // XXX we should remove CapsLock state, which changes characters from
  1124   //     Latin to Cyrillic with Russian layout on 10.4 only when Cmd key
  1125   //     is pressed.
  1126   UInt32 numState = (lockState & ~alphaLock); // only num lock state
  1127   uint32_t uncmdedChar = TranslateToChar(key, numState, aKbType);
  1128   UInt32 shiftNumMod = numState | shiftKey;
  1129   uint32_t uncmdedShiftChar = TranslateToChar(key, shiftNumMod, aKbType);
  1130   uint32_t uncmdedUSChar = USLayout.TranslateToChar(key, numState, aKbType);
  1131   UInt32 cmdNumMod = cmdKey | numState;
  1132   uint32_t cmdedChar = TranslateToChar(key, cmdNumMod, aKbType);
  1133   UInt32 cmdShiftNumMod = shiftKey | cmdNumMod;
  1134   uint32_t cmdedShiftChar = TranslateToChar(key, cmdShiftNumMod, aKbType);
  1136   // Is the keyboard layout changed by Cmd key?
  1137   // E.g., Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY.
  1138   bool isCmdSwitchLayout = uncmdedChar != cmdedChar;
  1139   // Is the keyboard layout for Latin, but Cmd key switches the layout?
  1140   // I.e., Dvorak-QWERTY
  1141   bool isDvorakQWERTY = isCmdSwitchLayout && isRomanKeyboardLayout;
  1143   // If the current keyboard is not Dvorak-QWERTY or Cmd is not pressed,
  1144   // we should append unshiftedChar and shiftedChar for handling the
  1145   // normal characters.  These are the characters that the user is most
  1146   // likely to associate with this key.
  1147   if ((unshiftedChar || shiftedChar) &&
  1148       (!aKeyEvent.IsMeta() || !isDvorakQWERTY)) {
  1149     AlternativeCharCode altCharCodes(unshiftedChar, shiftedChar);
  1150     aKeyEvent.alternativeCharCodes.AppendElement(altCharCodes);
  1152   PR_LOG(gLog, PR_LOG_ALWAYS,
  1153     ("%p TISInputSourceWrapper::InitKeyPressEvent, "
  1154      "aKeyEvent.isMeta=%s, isDvorakQWERTY=%s, "
  1155      "unshiftedChar=U+%X, shiftedChar=U+%X",
  1156      this, OnOrOff(aKeyEvent.IsMeta()), TrueOrFalse(isDvorakQWERTY),
  1157      unshiftedChar, shiftedChar));
  1159   // Most keyboard layouts provide the same characters in the NSEvents
  1160   // with Command+Shift as with Command.  However, with Command+Shift we
  1161   // want the character on the second level.  e.g. With a US QWERTY
  1162   // layout, we want "?" when the "/","?" key is pressed with
  1163   // Command+Shift.
  1165   // On a German layout, the OS gives us '/' with Cmd+Shift+SS(eszett)
  1166   // even though Cmd+SS is 'SS' and Shift+'SS' is '?'.  This '/' seems
  1167   // like a hack to make the Cmd+"?" event look the same as the Cmd+"?"
  1168   // event on a US keyboard.  The user thinks they are typing Cmd+"?", so
  1169   // we'll prefer the "?" character, replacing charCode with shiftedChar
  1170   // when Shift is pressed.  However, in case there is a layout where the
  1171   // character unique to Cmd+Shift is the character that the user expects,
  1172   // we'll send it as an alternative char.
  1173   bool hasCmdShiftOnlyChar =
  1174     cmdedChar != cmdedShiftChar && uncmdedShiftChar != cmdedShiftChar;
  1175   uint32_t originalCmdedShiftChar = cmdedShiftChar;
  1177   // If we can make a good guess at the characters that the user would
  1178   // expect this key combination to produce (with and without Shift) then
  1179   // use those characters.  This also corrects for CapsLock, which was
  1180   // ignored above.
  1181   if (!isCmdSwitchLayout) {
  1182     // The characters produced with Command seem similar to those without
  1183     // Command.
  1184     if (unshiftedChar) {
  1185       cmdedChar = unshiftedChar;
  1187     if (shiftedChar) {
  1188       cmdedShiftChar = shiftedChar;
  1190   } else if (uncmdedUSChar == cmdedChar) {
  1191     // It looks like characters from a US layout are provided when Command
  1192     // is down.
  1193     uint32_t ch = USLayout.TranslateToChar(key, lockState, aKbType);
  1194     if (ch) {
  1195       cmdedChar = ch;
  1197     ch = USLayout.TranslateToChar(key, shiftLockMod, aKbType);
  1198     if (ch) {
  1199       cmdedShiftChar = ch;
  1203   // If the current keyboard layout is switched by the Cmd key,
  1204   // we should append cmdedChar and shiftedCmdChar that are
  1205   // Latin char for the key.
  1206   // If the keyboard layout is Dvorak-QWERTY, we should append them only when
  1207   // command key is pressed because when command key isn't pressed, uncmded
  1208   // chars have been appended already.
  1209   if ((cmdedChar || cmdedShiftChar) && isCmdSwitchLayout &&
  1210       (aKeyEvent.IsMeta() || !isDvorakQWERTY)) {
  1211     AlternativeCharCode altCharCodes(cmdedChar, cmdedShiftChar);
  1212     aKeyEvent.alternativeCharCodes.AppendElement(altCharCodes);
  1214   PR_LOG(gLog, PR_LOG_ALWAYS,
  1215     ("%p TISInputSourceWrapper::InitKeyPressEvent, "
  1216      "hasCmdShiftOnlyChar=%s, isCmdSwitchLayout=%s, isDvorakQWERTY=%s, "
  1217      "cmdedChar=U+%X, cmdedShiftChar=U+%X",
  1218      this, TrueOrFalse(hasCmdShiftOnlyChar), TrueOrFalse(isDvorakQWERTY),
  1219      TrueOrFalse(isDvorakQWERTY), cmdedChar, cmdedShiftChar));
  1220   // Special case for 'SS' key of German layout. See the comment of
  1221   // hasCmdShiftOnlyChar definition for the detail.
  1222   if (hasCmdShiftOnlyChar && originalCmdedShiftChar) {
  1223     AlternativeCharCode altCharCodes(0, originalCmdedShiftChar);
  1224     aKeyEvent.alternativeCharCodes.AppendElement(altCharCodes);
  1226   PR_LOG(gLog, PR_LOG_ALWAYS,
  1227     ("%p TISInputSourceWrapper::InitKeyPressEvent, "
  1228      "hasCmdShiftOnlyChar=%s, originalCmdedShiftChar=U+%X",
  1229      this, TrueOrFalse(hasCmdShiftOnlyChar), originalCmdedShiftChar));
  1231   NS_OBJC_END_TRY_ABORT_BLOCK
  1234 uint32_t
  1235 TISInputSourceWrapper::ComputeGeckoKeyCode(UInt32 aNativeKeyCode,
  1236                                            UInt32 aKbType,
  1237                                            bool aCmdIsPressed)
  1239   PR_LOG(gLog, PR_LOG_ALWAYS,
  1240     ("%p TISInputSourceWrapper::ComputeGeckoKeyCode, aNativeKeyCode=0x%X, "
  1241      "aKbType=0x%X, aCmdIsPressed=%s, IsOpenedIMEMode()=%s, "
  1242      "IsASCIICapable()=%s",
  1243      this, aNativeKeyCode, aKbType, TrueOrFalse(aCmdIsPressed),
  1244      TrueOrFalse(IsOpenedIMEMode()), TrueOrFalse(IsASCIICapable())));
  1246   switch (aNativeKeyCode) {
  1247     case kVK_Space:             return NS_VK_SPACE;
  1248     case kVK_Escape:            return NS_VK_ESCAPE;
  1250     // modifiers
  1251     case kVK_RightCommand:
  1252     case kVK_Command:           return NS_VK_META;
  1253     case kVK_RightShift:
  1254     case kVK_Shift:             return NS_VK_SHIFT;
  1255     case kVK_CapsLock:          return NS_VK_CAPS_LOCK;
  1256     case kVK_RightControl:
  1257     case kVK_Control:           return NS_VK_CONTROL;
  1258     case kVK_RightOption:
  1259     case kVK_Option:            return NS_VK_ALT;
  1261     case kVK_ANSI_KeypadClear:  return NS_VK_CLEAR;
  1263     // function keys
  1264     case kVK_F1:                return NS_VK_F1;
  1265     case kVK_F2:                return NS_VK_F2;
  1266     case kVK_F3:                return NS_VK_F3;
  1267     case kVK_F4:                return NS_VK_F4;
  1268     case kVK_F5:                return NS_VK_F5;
  1269     case kVK_F6:                return NS_VK_F6;
  1270     case kVK_F7:                return NS_VK_F7;
  1271     case kVK_F8:                return NS_VK_F8;
  1272     case kVK_F9:                return NS_VK_F9;
  1273     case kVK_F10:               return NS_VK_F10;
  1274     case kVK_F11:               return NS_VK_F11;
  1275     case kVK_F12:               return NS_VK_F12;
  1276     // case kVK_F13:               return NS_VK_F13;  // clash with the 3 below
  1277     // case kVK_F14:               return NS_VK_F14;
  1278     // case kVK_F15:               return NS_VK_F15;
  1279     case kVK_F16:               return NS_VK_F16;
  1280     case kVK_F17:               return NS_VK_F17;
  1281     case kVK_F18:               return NS_VK_F18;
  1282     case kVK_F19:               return NS_VK_F19;
  1284     case kVK_PC_Pause:          return NS_VK_PAUSE;
  1285     case kVK_PC_ScrollLock:     return NS_VK_SCROLL_LOCK;
  1286     case kVK_PC_PrintScreen:    return NS_VK_PRINTSCREEN;
  1288     // keypad
  1289     case kVK_ANSI_Keypad0:      return NS_VK_NUMPAD0;
  1290     case kVK_ANSI_Keypad1:      return NS_VK_NUMPAD1;
  1291     case kVK_ANSI_Keypad2:      return NS_VK_NUMPAD2;
  1292     case kVK_ANSI_Keypad3:      return NS_VK_NUMPAD3;
  1293     case kVK_ANSI_Keypad4:      return NS_VK_NUMPAD4;
  1294     case kVK_ANSI_Keypad5:      return NS_VK_NUMPAD5;
  1295     case kVK_ANSI_Keypad6:      return NS_VK_NUMPAD6;
  1296     case kVK_ANSI_Keypad7:      return NS_VK_NUMPAD7;
  1297     case kVK_ANSI_Keypad8:      return NS_VK_NUMPAD8;
  1298     case kVK_ANSI_Keypad9:      return NS_VK_NUMPAD9;
  1300     case kVK_ANSI_KeypadMultiply: return NS_VK_MULTIPLY;
  1301     case kVK_ANSI_KeypadPlus:     return NS_VK_ADD;
  1302     case kVK_ANSI_KeypadMinus:    return NS_VK_SUBTRACT;
  1303     case kVK_ANSI_KeypadDecimal:  return NS_VK_DECIMAL;
  1304     case kVK_ANSI_KeypadDivide:   return NS_VK_DIVIDE;
  1306     case kVK_JIS_KeypadComma:   return NS_VK_SEPARATOR;
  1308     // IME keys
  1309     case kVK_JIS_Eisu:          return NS_VK_EISU;
  1310     case kVK_JIS_Kana:          return NS_VK_KANA;
  1312     // these may clash with forward delete and help
  1313     case kVK_PC_Insert:         return NS_VK_INSERT;
  1314     case kVK_PC_Delete:         return NS_VK_DELETE;
  1316     case kVK_PC_Backspace:      return NS_VK_BACK;
  1317     case kVK_Tab:               return NS_VK_TAB;
  1319     case kVK_Home:              return NS_VK_HOME;
  1320     case kVK_End:               return NS_VK_END;
  1322     case kVK_PageUp:            return NS_VK_PAGE_UP;
  1323     case kVK_PageDown:          return NS_VK_PAGE_DOWN;
  1325     case kVK_LeftArrow:         return NS_VK_LEFT;
  1326     case kVK_RightArrow:        return NS_VK_RIGHT;
  1327     case kVK_UpArrow:           return NS_VK_UP;
  1328     case kVK_DownArrow:         return NS_VK_DOWN;
  1330     case kVK_PC_ContextMenu:    return NS_VK_CONTEXT_MENU;
  1332     case kVK_ANSI_1:            return NS_VK_1;
  1333     case kVK_ANSI_2:            return NS_VK_2;
  1334     case kVK_ANSI_3:            return NS_VK_3;
  1335     case kVK_ANSI_4:            return NS_VK_4;
  1336     case kVK_ANSI_5:            return NS_VK_5;
  1337     case kVK_ANSI_6:            return NS_VK_6;
  1338     case kVK_ANSI_7:            return NS_VK_7;
  1339     case kVK_ANSI_8:            return NS_VK_8;
  1340     case kVK_ANSI_9:            return NS_VK_9;
  1341     case kVK_ANSI_0:            return NS_VK_0;
  1343     case kVK_ANSI_KeypadEnter:
  1344     case kVK_Return:
  1345     case kVK_Powerbook_KeypadEnter: return NS_VK_RETURN;
  1348   // If Cmd key is pressed, that causes switching keyboard layout temporarily.
  1349   // E.g., Dvorak-QWERTY.  Therefore, if Cmd key is pressed, we should honor it.
  1350   UInt32 modifiers = aCmdIsPressed ? cmdKey : 0;
  1352   uint32_t charCode = TranslateToChar(aNativeKeyCode, modifiers, aKbType);
  1354   // Special case for Mac.  Mac inputs Yen sign (U+00A5) directly instead of
  1355   // Back slash (U+005C).  We should return NS_VK_BACK_SLASH for compatibility
  1356   // with other platforms.
  1357   // XXX How about Won sign (U+20A9) which has same problem as Yen sign?
  1358   if (charCode == 0x00A5) {
  1359     return NS_VK_BACK_SLASH;
  1362   uint32_t keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode);
  1363   if (keyCode) {
  1364     return keyCode;
  1367   // If the unshifed char isn't an ASCII character, use shifted char.
  1368   charCode = TranslateToChar(aNativeKeyCode, modifiers | shiftKey, aKbType);
  1369   keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode);
  1370   if (keyCode) {
  1371     return keyCode;
  1374   // If this is ASCII capable, give up to compute it.
  1375   if (IsASCIICapable()) {
  1376     return 0;
  1379   // Retry with ASCII capable keyboard layout.
  1380   TISInputSourceWrapper currentKeyboardLayout;
  1381   currentKeyboardLayout.InitByCurrentASCIICapableKeyboardLayout();
  1382   NS_ENSURE_TRUE(mInputSource != currentKeyboardLayout.mInputSource, 0);
  1383   keyCode = currentKeyboardLayout.ComputeGeckoKeyCode(aNativeKeyCode, aKbType,
  1384                                                       aCmdIsPressed);
  1386   // However, if keyCode isn't for an alphabet keys or a numeric key, we should
  1387   // ignore it.  For example, comma key of Thai layout is same as close-square-
  1388   // bracket key of US layout and an unicode character key of Thai layout is
  1389   // same as comma key of US layout.  If we return NS_VK_COMMA for latter key,
  1390   // web application developers cannot distinguish with the former key.
  1391   return ((keyCode >= NS_VK_A && keyCode <= NS_VK_Z) ||
  1392           (keyCode >= NS_VK_0 && keyCode <= NS_VK_9)) ? keyCode : 0;
  1395 // static
  1396 KeyNameIndex
  1397 TISInputSourceWrapper::ComputeGeckoKeyNameIndex(UInt32 aNativeKeyCode)
  1399   switch (aNativeKeyCode) {
  1401 #define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
  1402     case aNativeKey: return aKeyNameIndex;
  1404 #include "NativeKeyToDOMKeyName.h"
  1406 #undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
  1408     default:
  1409       return KEY_NAME_INDEX_Unidentified;
  1414 #pragma mark -
  1417 /******************************************************************************
  1419  *  TextInputHandler implementation (static methods)
  1421  ******************************************************************************/
  1423 NSUInteger TextInputHandler::sLastModifierState = 0;
  1425 // static
  1426 CFArrayRef
  1427 TextInputHandler::CreateAllKeyboardLayoutList()
  1429   const void* keys[] = { kTISPropertyInputSourceType };
  1430   const void* values[] = { kTISTypeKeyboardLayout };
  1431   CFDictionaryRef filter =
  1432     ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
  1433   NS_ASSERTION(filter, "failed to create the filter");
  1434   CFArrayRef list = ::TISCreateInputSourceList(filter, true);
  1435   ::CFRelease(filter);
  1436   return list;
  1439 // static
  1440 void
  1441 TextInputHandler::DebugPrintAllKeyboardLayouts()
  1443 #ifdef PR_LOGGING
  1444   if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) {
  1445     CFArrayRef list = CreateAllKeyboardLayoutList();
  1446     PR_LOG(gLog, PR_LOG_ALWAYS, ("Keyboard layout configuration:"));
  1447     CFIndex idx = ::CFArrayGetCount(list);
  1448     TISInputSourceWrapper tis;
  1449     for (CFIndex i = 0; i < idx; ++i) {
  1450       TISInputSourceRef inputSource = static_cast<TISInputSourceRef>(
  1451         const_cast<void *>(::CFArrayGetValueAtIndex(list, i)));
  1452       tis.InitByTISInputSourceRef(inputSource);
  1453       nsAutoString name, isid;
  1454       tis.GetLocalizedName(name);
  1455       tis.GetInputSourceID(isid);
  1456       PR_LOG(gLog, PR_LOG_ALWAYS,
  1457              ("  %s\t<%s>%s%s\n",
  1458               NS_ConvertUTF16toUTF8(name).get(),
  1459               NS_ConvertUTF16toUTF8(isid).get(),
  1460               tis.IsASCIICapable() ? "" : "\t(Isn't ASCII capable)",
  1461               tis.IsKeyboardLayout() && tis.GetUCKeyboardLayout() ?
  1462                 "" : "\t(uchr is NOT AVAILABLE)"));
  1464     ::CFRelease(list);
  1466 #endif // #ifdef PR_LOGGING
  1470 #pragma mark -
  1473 /******************************************************************************
  1475  *  TextInputHandler implementation
  1477  ******************************************************************************/
  1479 TextInputHandler::TextInputHandler(nsChildView* aWidget,
  1480                                    NSView<mozView> *aNativeView) :
  1481   IMEInputHandler(aWidget, aNativeView)
  1483   InitLogModule();
  1484   [mView installTextInputHandler:this];
  1487 TextInputHandler::~TextInputHandler()
  1489   [mView uninstallTextInputHandler];
  1492 bool
  1493 TextInputHandler::HandleKeyDownEvent(NSEvent* aNativeEvent)
  1495   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
  1497   if (Destroyed()) {
  1498     PR_LOG(gLog, PR_LOG_ALWAYS,
  1499       ("%p TextInputHandler::HandleKeyDownEvent, "
  1500        "widget has been already destroyed", this));
  1501     return false;
  1504   PR_LOG(gLog, PR_LOG_ALWAYS,
  1505     ("%p TextInputHandler::HandleKeyDownEvent, aNativeEvent=%p, "
  1506      "type=%s, keyCode=%lld (0x%X), modifierFlags=0x%X, characters=\"%s\", "
  1507      "charactersIgnoringModifiers=\"%s\"",
  1508      this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
  1509      [aNativeEvent keyCode], [aNativeEvent keyCode],
  1510      [aNativeEvent modifierFlags], GetCharacters([aNativeEvent characters]),
  1511      GetCharacters([aNativeEvent charactersIgnoringModifiers])));
  1513   nsRefPtr<nsChildView> kungFuDeathGrip(mWidget);
  1515   KeyEventState* currentKeyEvent = PushKeyEvent(aNativeEvent);
  1516   AutoKeyEventStateCleaner remover(this);
  1518   if (!IsIMEComposing()) {
  1519     NSResponder* firstResponder = [[mView window] firstResponder];
  1521     WidgetKeyboardEvent keydownEvent(true, NS_KEY_DOWN, mWidget);
  1522     InitKeyEvent(aNativeEvent, keydownEvent);
  1524     currentKeyEvent->mKeyDownHandled = DispatchEvent(keydownEvent);
  1525     if (Destroyed()) {
  1526       PR_LOG(gLog, PR_LOG_ALWAYS,
  1527         ("%p TextInputHandler::HandleKeyDownEvent, "
  1528          "widget was destroyed by keydown event", this));
  1529       return currentKeyEvent->IsDefaultPrevented();
  1532     // The key down event may have shifted the focus, in which
  1533     // case we should not fire the key press.
  1534     // XXX This is a special code only on Cocoa widget, why is this needed?
  1535     if (firstResponder != [[mView window] firstResponder]) {
  1536       PR_LOG(gLog, PR_LOG_ALWAYS,
  1537         ("%p TextInputHandler::HandleKeyDownEvent, "
  1538          "view lost focus by keydown event", this));
  1539       return currentKeyEvent->IsDefaultPrevented();
  1542     if (currentKeyEvent->IsDefaultPrevented()) {
  1543       PR_LOG(gLog, PR_LOG_ALWAYS,
  1544         ("%p TextInputHandler::HandleKeyDownEvent, "
  1545          "keydown event's default is prevented", this));
  1546       return true;
  1550   // Let Cocoa interpret the key events, caching IsIMEComposing first.
  1551   bool wasComposing = IsIMEComposing();
  1552   bool interpretKeyEventsCalled = false;
  1553   if (IsIMEEnabled() || IsASCIICapableOnly()) {
  1554     PR_LOG(gLog, PR_LOG_ALWAYS,
  1555       ("%p TextInputHandler::HandleKeyDownEvent, calling interpretKeyEvents",
  1556        this));
  1557     [mView interpretKeyEvents:[NSArray arrayWithObject:aNativeEvent]];
  1558     interpretKeyEventsCalled = true;
  1559     PR_LOG(gLog, PR_LOG_ALWAYS,
  1560       ("%p TextInputHandler::HandleKeyDownEvent, called interpretKeyEvents",
  1561        this));
  1564   if (Destroyed()) {
  1565     PR_LOG(gLog, PR_LOG_ALWAYS,
  1566       ("%p TextInputHandler::HandleKeyDownEvent, widget was destroyed",
  1567        this));
  1568     return currentKeyEvent->IsDefaultPrevented();
  1571   PR_LOG(gLog, PR_LOG_ALWAYS,
  1572     ("%p TextInputHandler::HandleKeyDownEvent, wasComposing=%s, "
  1573      "IsIMEComposing()=%s",
  1574      this, TrueOrFalse(wasComposing), TrueOrFalse(IsIMEComposing())));
  1576   if (currentKeyEvent->CanDispatchKeyPressEvent() &&
  1577       !wasComposing && !IsIMEComposing()) {
  1578     WidgetKeyboardEvent keypressEvent(true, NS_KEY_PRESS, mWidget);
  1579     InitKeyEvent(aNativeEvent, keypressEvent);
  1581     // If we called interpretKeyEvents and this isn't normal character input
  1582     // then IME probably ate the event for some reason. We do not want to
  1583     // send a key press event in that case.
  1584     // TODO:
  1585     // There are some other cases which IME eats the current event.
  1586     // 1. If key events were nested during calling interpretKeyEvents, it means
  1587     //    that IME did something.  Then, we should do nothing.
  1588     // 2. If one or more commands are called like "deleteBackward", we should
  1589     //    dispatch keypress event at that time.  Note that the command may have
  1590     //    been a converted or generated action by IME.  Then, we shouldn't do
  1591     //    our default action for this key.
  1592     if (!(interpretKeyEventsCalled &&
  1593           IsNormalCharInputtingEvent(keypressEvent))) {
  1594       currentKeyEvent->mKeyPressHandled = DispatchEvent(keypressEvent);
  1595       currentKeyEvent->mKeyPressDispatched = true;
  1596       PR_LOG(gLog, PR_LOG_ALWAYS,
  1597         ("%p TextInputHandler::HandleKeyDownEvent, keypress event dispatched",
  1598          this));
  1602   // Note: mWidget might have become null here. Don't count on it from here on.
  1604   PR_LOG(gLog, PR_LOG_ALWAYS,
  1605     ("%p TextInputHandler::HandleKeyDownEvent, "
  1606      "keydown handled=%s, keypress handled=%s, causedOtherKeyEvents=%s",
  1607      this, TrueOrFalse(currentKeyEvent->mKeyDownHandled),
  1608      TrueOrFalse(currentKeyEvent->mKeyPressHandled),
  1609      TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents)));
  1610   return currentKeyEvent->IsDefaultPrevented();
  1612   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
  1615 void
  1616 TextInputHandler::HandleKeyUpEvent(NSEvent* aNativeEvent)
  1618   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
  1620   PR_LOG(gLog, PR_LOG_ALWAYS,
  1621     ("%p TextInputHandler::HandleKeyUpEvent, aNativeEvent=%p, "
  1622      "type=%s, keyCode=%lld (0x%X), modifierFlags=0x%X, characters=\"%s\", "
  1623      "charactersIgnoringModifiers=\"%s\", "
  1624      "mIgnoreNextKeyUpEvent=%s, IsIMEComposing()=%s",
  1625      this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
  1626      [aNativeEvent keyCode], [aNativeEvent keyCode],
  1627      [aNativeEvent modifierFlags], GetCharacters([aNativeEvent characters]),
  1628      GetCharacters([aNativeEvent charactersIgnoringModifiers]),
  1629      TrueOrFalse(mIgnoreNextKeyUpEvent), TrueOrFalse(IsIMEComposing())));
  1631   if (mIgnoreNextKeyUpEvent) {
  1632     mIgnoreNextKeyUpEvent = false;
  1633     return;
  1636   if (Destroyed()) {
  1637     PR_LOG(gLog, PR_LOG_ALWAYS,
  1638       ("%p TextInputHandler::HandleKeyUpEvent, "
  1639        "widget has been already destroyed", this));
  1640     return;
  1643   // if we don't have any characters we can't generate a keyUp event
  1644   if (IsIMEComposing()) {
  1645     return;
  1648   WidgetKeyboardEvent keyupEvent(true, NS_KEY_UP, mWidget);
  1649   InitKeyEvent(aNativeEvent, keyupEvent);
  1651   DispatchEvent(keyupEvent);
  1653   NS_OBJC_END_TRY_ABORT_BLOCK;
  1656 void
  1657 TextInputHandler::HandleFlagsChanged(NSEvent* aNativeEvent)
  1659   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
  1661   if (Destroyed()) {
  1662     PR_LOG(gLog, PR_LOG_ALWAYS,
  1663       ("%p TextInputHandler::HandleFlagsChanged, "
  1664        "widget has been already destroyed", this));
  1665     return;
  1668   nsRefPtr<nsChildView> kungFuDeathGrip(mWidget);
  1670   PR_LOG(gLog, PR_LOG_ALWAYS,
  1671     ("%p TextInputHandler::HandleFlagsChanged, aNativeEvent=%p, "
  1672      "type=%s, keyCode=%s (0x%X), modifierFlags=0x%08X, "
  1673      "sLastModifierState=0x%08X, IsIMEComposing()=%s",
  1674      this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
  1675      GetKeyNameForNativeKeyCode([aNativeEvent keyCode]), [aNativeEvent keyCode],
  1676      [aNativeEvent modifierFlags], sLastModifierState,
  1677      TrueOrFalse(IsIMEComposing())));
  1679   MOZ_ASSERT([aNativeEvent type] == NSFlagsChanged);
  1681   NSUInteger diff = [aNativeEvent modifierFlags] ^ sLastModifierState;
  1682   // Device dependent flags for left-control key, both shift keys, both command
  1683   // keys and both option keys have been defined in Next's SDK.  But we
  1684   // shouldn't use it directly as far as possible since Cocoa SDK doesn't
  1685   // define them.  Fortunately, we need them only when we dispatch keyup
  1686   // events.  So, we can usually know the actual relation between keyCode and
  1687   // device dependent flags.  However, we need to remove following flags first
  1688   // since the differences don't indicate modifier key state.
  1689   // NX_STYLUSPROXIMITYMASK: Probably used for pen like device.
  1690   // kCGEventFlagMaskNonCoalesced (= NX_NONCOALSESCEDMASK): See the document for
  1691   // Quartz Event Services.
  1692   diff &= ~(NX_STYLUSPROXIMITYMASK | kCGEventFlagMaskNonCoalesced);
  1694   switch ([aNativeEvent keyCode]) {
  1695     // CapsLock state and other modifier states are different:
  1696     // CapsLock state does not revert when the CapsLock key goes up, as the
  1697     // modifier state does for other modifier keys on key up.
  1698     case kVK_CapsLock: {
  1699       // Fire key down event for caps lock.
  1700       DispatchKeyEventForFlagsChanged(aNativeEvent, true);
  1701       // XXX should we fire keyup event too? The keyup event for CapsLock key
  1702       // is never dispatched on Gecko.
  1703       // XXX WebKit dispatches keydown event when CapsLock is locked, otherwise,
  1704       // keyup event.  If we do so, we cannot keep the consistency with other
  1705       // platform's behavior...
  1706       break;
  1709     // If the event is caused by pressing or releasing a modifier key, just
  1710     // dispatch the key's event.
  1711     case kVK_Shift:
  1712     case kVK_RightShift:
  1713     case kVK_Command:
  1714     case kVK_RightCommand:
  1715     case kVK_Control:
  1716     case kVK_RightControl:
  1717     case kVK_Option:
  1718     case kVK_RightOption:
  1719     case kVK_Help: {
  1720       // We assume that at most one modifier is changed per event if the event
  1721       // is caused by pressing or releasing a modifier key.
  1722       bool isKeyDown = ([aNativeEvent modifierFlags] & diff) != 0;
  1723       DispatchKeyEventForFlagsChanged(aNativeEvent, isKeyDown);
  1724       // XXX Some applications might send the event with incorrect device-
  1725       //     dependent flags.
  1726       if (isKeyDown && ((diff & ~NSDeviceIndependentModifierFlagsMask) != 0)) {
  1727         unsigned short keyCode = [aNativeEvent keyCode];
  1728         const ModifierKey* modifierKey =
  1729           GetModifierKeyForDeviceDependentFlags(diff);
  1730         if (modifierKey && modifierKey->keyCode != keyCode) {
  1731           // Although, we're not sure the actual cause of this case, the stored
  1732           // modifier information and the latest key event information may be
  1733           // mismatched. Then, let's reset the stored information.
  1734           // NOTE: If this happens, it may fail to handle NSFlagsChanged event
  1735           // in the default case (below). However, it's the rare case handler
  1736           // and this case occurs rarely. So, we can ignore the edge case bug.
  1737           NS_WARNING("Resetting stored modifier key information");
  1738           mModifierKeys.Clear();
  1739           modifierKey = nullptr;
  1741         if (!modifierKey) {
  1742           mModifierKeys.AppendElement(ModifierKey(diff, keyCode));
  1745       break;
  1748     // Currently we don't support Fn key since other browsers don't dispatch
  1749     // events for it and we don't have keyCode for this key.
  1750     // It should be supported when we implement .key and .char.
  1751     case kVK_Function:
  1752       break;
  1754     // If the event is caused by something else than pressing or releasing a
  1755     // single modifier key (for example by the app having been deactivated
  1756     // using command-tab), use the modifiers themselves to determine which
  1757     // key's event to dispatch, and whether it's a keyup or keydown event.
  1758     // In all cases we assume one or more modifiers are being deactivated
  1759     // (never activated) -- otherwise we'd have received one or more events
  1760     // corresponding to a single modifier key being pressed.
  1761     default: {
  1762       NSUInteger modifiers = sLastModifierState;
  1763       for (int32_t bit = 0; bit < 32; ++bit) {
  1764         NSUInteger flag = 1 << bit;
  1765         if (!(diff & flag)) {
  1766           continue;
  1769         // Given correct information from the application, a flag change here
  1770         // will normally be a deactivation (except for some lockable modifiers
  1771         // such as CapsLock).  But some applications (like VNC) can send an
  1772         // activating event with a zero keyCode.  So we need to check for that
  1773         // here.
  1774         bool dispatchKeyDown = ((flag & [aNativeEvent modifierFlags]) != 0);
  1776         unsigned short keyCode = 0;
  1777         if (flag & NSDeviceIndependentModifierFlagsMask) {
  1778           switch (flag) {
  1779             case NSAlphaShiftKeyMask:
  1780               keyCode = kVK_CapsLock;
  1781               dispatchKeyDown = true;
  1782               break;
  1784             case NSNumericPadKeyMask:
  1785               // NSNumericPadKeyMask is fired by VNC a lot. But not all of
  1786               // these events can really be Clear key events, so we just ignore
  1787               // them.
  1788               continue;
  1790             case NSHelpKeyMask:
  1791               keyCode = kVK_Help;
  1792               break;
  1794             case NSFunctionKeyMask:
  1795               // An NSFunctionKeyMask change here will normally be a
  1796               // deactivation.  But sometimes it will be an activation send (by
  1797               // VNC for example) with a zero keyCode.
  1798               continue;
  1800             // These cases (NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask
  1801             // and NSCommandKeyMask) should be handled by the other branch of
  1802             // the if statement, below (which handles device dependent flags).
  1803             // However, some applications (like VNC) can send key events without
  1804             // any device dependent flags, so we handle them here instead.
  1805             case NSShiftKeyMask:
  1806               keyCode = (modifiers & 0x0004) ? kVK_RightShift : kVK_Shift;
  1807               break;
  1808             case NSControlKeyMask:
  1809               keyCode = (modifiers & 0x2000) ? kVK_RightControl : kVK_Control;
  1810               break;
  1811             case NSAlternateKeyMask:
  1812               keyCode = (modifiers & 0x0040) ? kVK_RightOption : kVK_Option;
  1813               break;
  1814             case NSCommandKeyMask:
  1815               keyCode = (modifiers & 0x0010) ? kVK_RightCommand : kVK_Command;
  1816               break;
  1818             default:
  1819               continue;
  1821         } else {
  1822           const ModifierKey* modifierKey =
  1823             GetModifierKeyForDeviceDependentFlags(flag);
  1824           if (!modifierKey) {
  1825             // See the note above (in the other branch of the if statement)
  1826             // about the NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask
  1827             // and NSCommandKeyMask cases.
  1828             continue;
  1830           keyCode = modifierKey->keyCode;
  1833         // Remove flags
  1834         modifiers &= ~flag;
  1835         switch (keyCode) {
  1836           case kVK_Shift: {
  1837             const ModifierKey* modifierKey =
  1838               GetModifierKeyForNativeKeyCode(kVK_RightShift);
  1839             if (!modifierKey ||
  1840                 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
  1841               modifiers &= ~NSShiftKeyMask;
  1843             break;
  1845           case kVK_RightShift: {
  1846             const ModifierKey* modifierKey =
  1847               GetModifierKeyForNativeKeyCode(kVK_Shift);
  1848             if (!modifierKey ||
  1849                 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
  1850               modifiers &= ~NSShiftKeyMask;
  1852             break;
  1854           case kVK_Command: {
  1855             const ModifierKey* modifierKey =
  1856               GetModifierKeyForNativeKeyCode(kVK_RightCommand);
  1857             if (!modifierKey ||
  1858                 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
  1859               modifiers &= ~NSCommandKeyMask;
  1861             break;
  1863           case kVK_RightCommand: {
  1864             const ModifierKey* modifierKey =
  1865               GetModifierKeyForNativeKeyCode(kVK_Command);
  1866             if (!modifierKey ||
  1867                 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
  1868               modifiers &= ~NSCommandKeyMask;
  1870             break;
  1872           case kVK_Control: {
  1873             const ModifierKey* modifierKey =
  1874               GetModifierKeyForNativeKeyCode(kVK_RightControl);
  1875             if (!modifierKey ||
  1876                 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
  1877               modifiers &= ~NSControlKeyMask;
  1879             break;
  1881           case kVK_RightControl: {
  1882             const ModifierKey* modifierKey =
  1883               GetModifierKeyForNativeKeyCode(kVK_Control);
  1884             if (!modifierKey ||
  1885                 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
  1886               modifiers &= ~NSControlKeyMask;
  1888             break;
  1890           case kVK_Option: {
  1891             const ModifierKey* modifierKey =
  1892               GetModifierKeyForNativeKeyCode(kVK_RightOption);
  1893             if (!modifierKey ||
  1894                 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
  1895               modifiers &= ~NSAlternateKeyMask;
  1897             break;
  1899           case kVK_RightOption: {
  1900             const ModifierKey* modifierKey =
  1901               GetModifierKeyForNativeKeyCode(kVK_Option);
  1902             if (!modifierKey ||
  1903                 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
  1904               modifiers &= ~NSAlternateKeyMask;
  1906             break;
  1908           case kVK_Help:
  1909             modifiers &= ~NSHelpKeyMask;
  1910             break;
  1911           default:
  1912             break;
  1915         NSEvent* event =
  1916           [NSEvent keyEventWithType:NSFlagsChanged
  1917                            location:[aNativeEvent locationInWindow]
  1918                       modifierFlags:modifiers
  1919                           timestamp:[aNativeEvent timestamp]
  1920                        windowNumber:[aNativeEvent windowNumber]
  1921                             context:[aNativeEvent context]
  1922                          characters:nil
  1923         charactersIgnoringModifiers:nil
  1924                           isARepeat:NO
  1925                             keyCode:keyCode];
  1926         DispatchKeyEventForFlagsChanged(event, dispatchKeyDown);
  1927         if (Destroyed()) {
  1928           break;
  1931         // Stop if focus has changed.
  1932         // Check to see if mView is still the first responder.
  1933         if (![mView isFirstResponder]) {
  1934           break;
  1938       break;
  1942   // Be aware, the widget may have been destroyed.
  1943   sLastModifierState = [aNativeEvent modifierFlags];
  1945   NS_OBJC_END_TRY_ABORT_BLOCK;
  1948 const TextInputHandler::ModifierKey*
  1949 TextInputHandler::GetModifierKeyForNativeKeyCode(unsigned short aKeyCode) const
  1951   for (ModifierKeyArray::index_type i = 0; i < mModifierKeys.Length(); ++i) {
  1952     if (mModifierKeys[i].keyCode == aKeyCode) {
  1953       return &((ModifierKey&)mModifierKeys[i]);
  1956   return nullptr;
  1959 const TextInputHandler::ModifierKey*
  1960 TextInputHandler::GetModifierKeyForDeviceDependentFlags(NSUInteger aFlags) const
  1962   for (ModifierKeyArray::index_type i = 0; i < mModifierKeys.Length(); ++i) {
  1963     if (mModifierKeys[i].GetDeviceDependentFlags() ==
  1964           (aFlags & ~NSDeviceIndependentModifierFlagsMask)) {
  1965       return &((ModifierKey&)mModifierKeys[i]);
  1968   return nullptr;
  1971 void
  1972 TextInputHandler::DispatchKeyEventForFlagsChanged(NSEvent* aNativeEvent,
  1973                                                   bool aDispatchKeyDown)
  1975   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
  1977   if (Destroyed()) {
  1978     return;
  1981   PR_LOG(gLog, PR_LOG_ALWAYS,
  1982     ("%p TextInputHandler::DispatchKeyEventForFlagsChanged, aNativeEvent=%p, "
  1983      "type=%s, keyCode=%s (0x%X), aDispatchKeyDown=%s, IsIMEComposing()=%s",
  1984      this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
  1985      GetKeyNameForNativeKeyCode([aNativeEvent keyCode]), [aNativeEvent keyCode],
  1986      TrueOrFalse(aDispatchKeyDown), TrueOrFalse(IsIMEComposing())));
  1988   if ([aNativeEvent type] != NSFlagsChanged || IsIMEComposing()) {
  1989     return;
  1992   uint32_t message = aDispatchKeyDown ? NS_KEY_DOWN : NS_KEY_UP;
  1994   NPCocoaEvent cocoaEvent;
  1996   // Fire a key event.
  1997   WidgetKeyboardEvent keyEvent(true, message, mWidget);
  1998   InitKeyEvent(aNativeEvent, keyEvent);
  2000   // create event for use by plugins
  2001   if ([mView isPluginView]) {
  2002     if ([mView pluginEventModel] == NPEventModelCocoa) {
  2003       ConvertCocoaKeyEventToNPCocoaEvent(aNativeEvent, cocoaEvent);
  2004       keyEvent.pluginEvent = &cocoaEvent;
  2008   DispatchEvent(keyEvent);
  2010   NS_OBJC_END_TRY_ABORT_BLOCK;
  2013 void
  2014 TextInputHandler::InsertText(NSAttributedString* aAttrString,
  2015                              NSRange* aReplacementRange)
  2017   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
  2019   if (Destroyed()) {
  2020     return;
  2023   KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
  2025   PR_LOG(gLog, PR_LOG_ALWAYS,
  2026     ("%p TextInputHandler::InsertText, aAttrString=\"%s\", "
  2027      "aReplacementRange=%p { location=%llu, length=%llu }, "
  2028      "IsIMEComposing()=%s, IgnoreIMEComposition()=%s, "
  2029      "keyevent=%p, keydownHandled=%s, keypressDispatched=%s, "
  2030      "causedOtherKeyEvents=%s",
  2031      this, GetCharacters([aAttrString string]), aReplacementRange,
  2032      aReplacementRange ? aReplacementRange->location : 0,
  2033      aReplacementRange ? aReplacementRange->length : 0,
  2034      TrueOrFalse(IsIMEComposing()), TrueOrFalse(IgnoreIMEComposition()),
  2035      currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr,
  2036      currentKeyEvent ?
  2037        TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
  2038      currentKeyEvent ?
  2039        TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A",
  2040      currentKeyEvent ?
  2041        TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A"));
  2043   if (IgnoreIMEComposition()) {
  2044     return;
  2047   InputContext context = mWidget->GetInputContext();
  2048   bool isEditable = (context.mIMEState.mEnabled == IMEState::ENABLED ||
  2049                      context.mIMEState.mEnabled == IMEState::PASSWORD);
  2050   NSRange selectedRange = SelectedRange();
  2052   nsAutoString str;
  2053   nsCocoaUtils::GetStringForNSString([aAttrString string], str);
  2054   if (!IsIMEComposing() && str.IsEmpty()) {
  2055     // nothing to do if there is no content which can be removed.
  2056     if (!isEditable) {
  2057       return;
  2059     // If replacement range is specified, we need to remove the range.
  2060     // Otherwise, we need to remove the selected range if it's not collapsed.
  2061     if (aReplacementRange && aReplacementRange->location != NSNotFound) {
  2062       // nothing to do since the range is collapsed.
  2063       if (aReplacementRange->length == 0) {
  2064         return;
  2066       // If the replacement range is different from current selected range,
  2067       // select the range.
  2068       if (!NSEqualRanges(selectedRange, *aReplacementRange)) {
  2069         NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
  2071       selectedRange = SelectedRange();
  2073     NS_ENSURE_TRUE_VOID(selectedRange.location != NSNotFound);
  2074     if (selectedRange.length == 0) {
  2075       return; // nothing to do
  2077     // If this is caused by a key input, the keypress event which will be
  2078     // dispatched later should cause the delete.  Therefore, nothing to do here.
  2079     // Although, we're not sure if such case is actually possible.
  2080     if (!currentKeyEvent) {
  2081       return;
  2083     // Delete the selected range.
  2084     nsRefPtr<TextInputHandler> kungFuDeathGrip(this);
  2085     WidgetContentCommandEvent deleteCommandEvent(true,
  2086                                                  NS_CONTENT_COMMAND_DELETE,
  2087                                                  mWidget);
  2088     DispatchEvent(deleteCommandEvent);
  2089     NS_ENSURE_TRUE_VOID(deleteCommandEvent.mSucceeded);
  2090     // Be aware! The widget might be destroyed here.
  2091     return;
  2094   if (str.Length() != 1 || IsIMEComposing()) {
  2095     InsertTextAsCommittingComposition(aAttrString, aReplacementRange);
  2096     return;
  2099   // Don't let the same event be fired twice when hitting
  2100   // enter/return! (Bug 420502)
  2101   if (currentKeyEvent && !currentKeyEvent->CanDispatchKeyPressEvent()) {
  2102     return;
  2105   nsRefPtr<nsChildView> kungFuDeathGrip(mWidget);
  2107   // If the replacement range is specified, select the range.  Then, the
  2108   // selection will be replaced by the later keypress event.
  2109   if (isEditable &&
  2110       aReplacementRange && aReplacementRange->location != NSNotFound &&
  2111       !NSEqualRanges(selectedRange, *aReplacementRange)) {
  2112     NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
  2115   // Dispatch keypress event with char instead of textEvent
  2116   WidgetKeyboardEvent keypressEvent(true, NS_KEY_PRESS, mWidget);
  2117   keypressEvent.isChar = IsPrintableChar(str.CharAt(0));
  2119   // Don't set other modifiers from the current event, because here in
  2120   // -insertText: they've already been taken into account in creating
  2121   // the input string.
  2123   if (currentKeyEvent) {
  2124     NSEvent* keyEvent = currentKeyEvent->mKeyEvent;
  2125     InitKeyEvent(keyEvent, keypressEvent, &str);
  2126   } else {
  2127     nsCocoaUtils::InitInputEvent(keypressEvent, static_cast<NSEvent*>(nullptr));
  2128     if (keypressEvent.isChar) {
  2129       keypressEvent.charCode = str.CharAt(0);
  2131     // Note that insertText is not called only at key pressing.
  2132     if (!keypressEvent.charCode) {
  2133       keypressEvent.keyCode =
  2134         WidgetUtils::ComputeKeyCodeFromChar(keypressEvent.charCode);
  2138   // Remove basic modifiers from keypress event because if they are included,
  2139   // nsPlaintextEditor ignores the event.
  2140   keypressEvent.modifiers &= ~(MODIFIER_CONTROL |
  2141                                MODIFIER_ALT |
  2142                                MODIFIER_META);
  2144   // TODO:
  2145   // If mCurrentKeyEvent.mKeyEvent is null and when we implement textInput
  2146   // event of DOM3 Events, we should dispatch it instead of keypress event.
  2147   bool keyPressHandled = DispatchEvent(keypressEvent);
  2149   // Note: mWidget might have become null here. Don't count on it from here on.
  2151   if (currentKeyEvent) {
  2152     currentKeyEvent->mKeyPressHandled = keyPressHandled;
  2153     currentKeyEvent->mKeyPressDispatched = true;
  2156   NS_OBJC_END_TRY_ABORT_BLOCK;
  2159 bool
  2160 TextInputHandler::DoCommandBySelector(const char* aSelector)
  2162   nsRefPtr<nsChildView> kungFuDeathGrip(mWidget);
  2164   KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
  2166   PR_LOG(gLog, PR_LOG_ALWAYS,
  2167     ("%p TextInputHandler::DoCommandBySelector, aSelector=\"%s\", "
  2168      "Destroyed()=%s, keydownHandled=%s, keypressHandled=%s, "
  2169      "causedOtherKeyEvents=%s",
  2170      this, aSelector ? aSelector : "", TrueOrFalse(Destroyed()),
  2171      currentKeyEvent ?
  2172        TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
  2173      currentKeyEvent ?
  2174        TrueOrFalse(currentKeyEvent->mKeyPressHandled) : "N/A",
  2175      currentKeyEvent ?
  2176        TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A"));
  2178   if (currentKeyEvent && currentKeyEvent->CanDispatchKeyPressEvent()) {
  2179     WidgetKeyboardEvent keypressEvent(true, NS_KEY_PRESS, mWidget);
  2180     InitKeyEvent(currentKeyEvent->mKeyEvent, keypressEvent);
  2181     currentKeyEvent->mKeyPressHandled = DispatchEvent(keypressEvent);
  2182     currentKeyEvent->mKeyPressDispatched = true;
  2183     PR_LOG(gLog, PR_LOG_ALWAYS,
  2184       ("%p TextInputHandler::DoCommandBySelector, keypress event "
  2185        "dispatched, Destroyed()=%s, keypressHandled=%s",
  2186        this, TrueOrFalse(Destroyed()),
  2187        TrueOrFalse(currentKeyEvent->mKeyPressHandled)));
  2190   return (!Destroyed() && currentKeyEvent &&
  2191           currentKeyEvent->IsDefaultPrevented());
  2195 #pragma mark -
  2198 /******************************************************************************
  2200  *  IMEInputHandler implementation (static methods)
  2202  ******************************************************************************/
  2204 bool IMEInputHandler::sStaticMembersInitialized = false;
  2205 CFStringRef IMEInputHandler::sLatestIMEOpenedModeInputSourceID = nullptr;
  2206 IMEInputHandler* IMEInputHandler::sFocusedIMEHandler = nullptr;
  2208 // static
  2209 void
  2210 IMEInputHandler::InitStaticMembers()
  2212   if (sStaticMembersInitialized)
  2213     return;
  2214   sStaticMembersInitialized = true;
  2215   // We need to check the keyboard layout changes on all applications.
  2216   CFNotificationCenterRef center = ::CFNotificationCenterGetDistributedCenter();
  2217   // XXX Don't we need to remove the observer at shut down?
  2218   // Mac Dev Center's document doesn't say how to remove the observer if
  2219   // the second parameter is NULL.
  2220   ::CFNotificationCenterAddObserver(center, NULL,
  2221       OnCurrentTextInputSourceChange,
  2222       kTISNotifySelectedKeyboardInputSourceChanged, NULL,
  2223       CFNotificationSuspensionBehaviorDeliverImmediately);
  2224   // Initiailize with the current keyboard layout
  2225   OnCurrentTextInputSourceChange(NULL, NULL,
  2226                                  kTISNotifySelectedKeyboardInputSourceChanged,
  2227                                  NULL, NULL);
  2230 // static
  2231 void
  2232 IMEInputHandler::OnCurrentTextInputSourceChange(CFNotificationCenterRef aCenter,
  2233                                                 void* aObserver,
  2234                                                 CFStringRef aName,
  2235                                                 const void* aObject,
  2236                                                 CFDictionaryRef aUserInfo)
  2238   // Cache the latest IME opened mode to sLatestIMEOpenedModeInputSourceID.
  2239   TISInputSourceWrapper tis;
  2240   tis.InitByCurrentInputSource();
  2241   if (tis.IsOpenedIMEMode()) {
  2242     tis.GetInputSourceID(sLatestIMEOpenedModeInputSourceID);
  2245 #ifdef PR_LOGGING
  2246   if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) {
  2247     static CFStringRef sLastTIS = nullptr;
  2248     CFStringRef newTIS;
  2249     tis.GetInputSourceID(newTIS);
  2250     if (!sLastTIS ||
  2251         ::CFStringCompare(sLastTIS, newTIS, 0) != kCFCompareEqualTo) {
  2252       TISInputSourceWrapper tis1, tis2, tis3, tis4, tis5;
  2253       tis1.InitByCurrentKeyboardLayout();
  2254       tis2.InitByCurrentASCIICapableInputSource();
  2255       tis3.InitByCurrentASCIICapableKeyboardLayout();
  2256       tis4.InitByCurrentInputMethodKeyboardLayoutOverride();
  2257       tis5.InitByTISInputSourceRef(tis.GetKeyboardLayoutInputSource());
  2258       CFStringRef is0 = nullptr, is1 = nullptr, is2 = nullptr, is3 = nullptr,
  2259                   is4 = nullptr, is5 = nullptr, type0 = nullptr,
  2260                   lang0 = nullptr, bundleID0 = nullptr;
  2261       tis.GetInputSourceID(is0);
  2262       tis1.GetInputSourceID(is1);
  2263       tis2.GetInputSourceID(is2);
  2264       tis3.GetInputSourceID(is3);
  2265       tis4.GetInputSourceID(is4);
  2266       tis5.GetInputSourceID(is5);
  2267       tis.GetInputSourceType(type0);
  2268       tis.GetPrimaryLanguage(lang0);
  2269       tis.GetBundleID(bundleID0);
  2271       PR_LOG(gLog, PR_LOG_ALWAYS,
  2272         ("IMEInputHandler::OnCurrentTextInputSourceChange,\n"
  2273          "  Current Input Source is changed to:\n"
  2274          "    currentInputContext=%p\n"
  2275          "    %s\n"
  2276          "      type=%s %s\n"
  2277          "      overridden keyboard layout=%s\n"
  2278          "      used keyboard layout for translation=%s\n"
  2279          "    primary language=%s\n"
  2280          "    bundle ID=%s\n"
  2281          "    current ASCII capable Input Source=%s\n"
  2282          "    current Keyboard Layout=%s\n"
  2283          "    current ASCII capable Keyboard Layout=%s",
  2284          [NSTextInputContext currentInputContext], GetCharacters(is0),
  2285          GetCharacters(type0), tis.IsASCIICapable() ? "- ASCII capable " : "",
  2286          GetCharacters(is4), GetCharacters(is5),
  2287          GetCharacters(lang0), GetCharacters(bundleID0),
  2288          GetCharacters(is2), GetCharacters(is1), GetCharacters(is3)));
  2290     sLastTIS = newTIS;
  2292 #endif // #ifdef PR_LOGGING
  2295 // static
  2296 void
  2297 IMEInputHandler::FlushPendingMethods(nsITimer* aTimer, void* aClosure)
  2299   NS_ASSERTION(aClosure, "aClosure is null");
  2300   static_cast<IMEInputHandler*>(aClosure)->ExecutePendingMethods();
  2303 // static
  2304 CFArrayRef
  2305 IMEInputHandler::CreateAllIMEModeList()
  2307   const void* keys[] = { kTISPropertyInputSourceType };
  2308   const void* values[] = { kTISTypeKeyboardInputMode };
  2309   CFDictionaryRef filter =
  2310     ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
  2311   NS_ASSERTION(filter, "failed to create the filter");
  2312   CFArrayRef list = ::TISCreateInputSourceList(filter, true);
  2313   ::CFRelease(filter);
  2314   return list;
  2317 // static
  2318 void
  2319 IMEInputHandler::DebugPrintAllIMEModes()
  2321 #ifdef PR_LOGGING
  2322   if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) {
  2323     CFArrayRef list = CreateAllIMEModeList();
  2324     PR_LOG(gLog, PR_LOG_ALWAYS, ("IME mode configuration:"));
  2325     CFIndex idx = ::CFArrayGetCount(list);
  2326     TISInputSourceWrapper tis;
  2327     for (CFIndex i = 0; i < idx; ++i) {
  2328       TISInputSourceRef inputSource = static_cast<TISInputSourceRef>(
  2329         const_cast<void *>(::CFArrayGetValueAtIndex(list, i)));
  2330       tis.InitByTISInputSourceRef(inputSource);
  2331       nsAutoString name, isid;
  2332       tis.GetLocalizedName(name);
  2333       tis.GetInputSourceID(isid);
  2334       PR_LOG(gLog, PR_LOG_ALWAYS,
  2335              ("  %s\t<%s>%s%s\n",
  2336               NS_ConvertUTF16toUTF8(name).get(),
  2337               NS_ConvertUTF16toUTF8(isid).get(),
  2338               tis.IsASCIICapable() ? "" : "\t(Isn't ASCII capable)",
  2339               tis.IsEnabled() ? "" : "\t(Isn't Enabled)"));
  2341     ::CFRelease(list);
  2343 #endif // #ifdef PR_LOGGING
  2346 //static
  2347 TSMDocumentID
  2348 IMEInputHandler::GetCurrentTSMDocumentID()
  2350   // At least on Mac OS X 10.6.x and 10.7.x, ::TSMGetActiveDocument() has a bug.
  2351   // The result of ::TSMGetActiveDocument() isn't modified for new active text
  2352   // input context until [NSTextInputContext currentInputContext] is called.
  2353   // Therefore, we need to call it here.
  2354   [NSTextInputContext currentInputContext];
  2355   return ::TSMGetActiveDocument();
  2359 #pragma mark -
  2362 /******************************************************************************
  2364  *  IMEInputHandler implementation #1
  2365  *    The methods are releated to the pending methods.  Some jobs should be
  2366  *    run after the stack is finished, e.g, some methods cannot run the jobs
  2367  *    during processing the focus event.  And also some other jobs should be
  2368  *    run at the next focus event is processed.
  2369  *    The pending methods are recorded in mPendingMethods.  They are executed
  2370  *    by ExecutePendingMethods via FlushPendingMethods.
  2372  ******************************************************************************/
  2374 void
  2375 IMEInputHandler::NotifyIMEOfFocusChangeInGecko()
  2377   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
  2379   PR_LOG(gLog, PR_LOG_ALWAYS,
  2380     ("%p IMEInputHandler::NotifyIMEOfFocusChangeInGecko, "
  2381      "Destroyed()=%s, IsFocused()=%s, inputContext=%p",
  2382      this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
  2383      mView ? [mView inputContext] : nullptr));
  2385   if (Destroyed()) {
  2386     return;
  2389   if (!IsFocused()) {
  2390     // retry at next focus event
  2391     mPendingMethods |= kNotifyIMEOfFocusChangeInGecko;
  2392     return;
  2395   MOZ_ASSERT(mView);
  2396   NSTextInputContext* inputContext = [mView inputContext];
  2397   NS_ENSURE_TRUE_VOID(inputContext);
  2399   // When an <input> element on a XUL <panel> element gets focus from an <input>
  2400   // element on the opener window of the <panel> element, the owner window
  2401   // still has native focus.  Therefore, IMEs may store the opener window's
  2402   // level at this time because they don't know the actual focus is moved to
  2403   // different window.  If IMEs try to get the newest window level after the
  2404   // focus change, we return the window level of the XUL <panel>'s widget.
  2405   // Therefore, let's emulate the native focus change.  Then, IMEs can refresh
  2406   // the stored window level.
  2407   [inputContext deactivate];
  2408   [inputContext activate];
  2410   NS_OBJC_END_TRY_ABORT_BLOCK;
  2413 void
  2414 IMEInputHandler::DiscardIMEComposition()
  2416   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
  2418   PR_LOG(gLog, PR_LOG_ALWAYS,
  2419     ("%p IMEInputHandler::DiscardIMEComposition, "
  2420      "Destroyed()=%s, IsFocused()=%s, mView=%p, inputContext=%p",
  2421      this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
  2422      mView, mView ? [mView inputContext] : nullptr));
  2424   if (Destroyed()) {
  2425     return;
  2428   if (!IsFocused()) {
  2429     // retry at next focus event
  2430     mPendingMethods |= kDiscardIMEComposition;
  2431     return;
  2434   NS_ENSURE_TRUE_VOID(mView);
  2435   NSTextInputContext* inputContext = [mView inputContext];
  2436   NS_ENSURE_TRUE_VOID(inputContext);
  2437   mIgnoreIMECommit = true;
  2438   [inputContext discardMarkedText];
  2439   mIgnoreIMECommit = false;
  2441   NS_OBJC_END_TRY_ABORT_BLOCK
  2444 void
  2445 IMEInputHandler::SyncASCIICapableOnly()
  2447   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
  2449   PR_LOG(gLog, PR_LOG_ALWAYS,
  2450     ("%p IMEInputHandler::SyncASCIICapableOnly, "
  2451      "Destroyed()=%s, IsFocused()=%s, mIsASCIICapableOnly=%s, "
  2452      "GetCurrentTSMDocumentID()=%p",
  2453      this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
  2454      TrueOrFalse(mIsASCIICapableOnly), GetCurrentTSMDocumentID()));
  2456   if (Destroyed()) {
  2457     return;
  2460   if (!IsFocused()) {
  2461     // retry at next focus event
  2462     mPendingMethods |= kSyncASCIICapableOnly;
  2463     return;
  2466   TSMDocumentID doc = GetCurrentTSMDocumentID();
  2467   if (!doc) {
  2468     // retry
  2469     mPendingMethods |= kSyncASCIICapableOnly;
  2470     NS_WARNING("Application is active but there is no active document");
  2471     ResetTimer();
  2472     return;
  2475   if (mIsASCIICapableOnly) {
  2476     CFArrayRef ASCIICapableTISList = ::TISCreateASCIICapableInputSourceList();
  2477     ::TSMSetDocumentProperty(doc,
  2478                              kTSMDocumentEnabledInputSourcesPropertyTag,
  2479                              sizeof(CFArrayRef),
  2480                              &ASCIICapableTISList);
  2481     ::CFRelease(ASCIICapableTISList);
  2482   } else {
  2483     ::TSMRemoveDocumentProperty(doc,
  2484                                 kTSMDocumentEnabledInputSourcesPropertyTag);
  2487   NS_OBJC_END_TRY_ABORT_BLOCK;
  2490 void
  2491 IMEInputHandler::ResetTimer()
  2493   NS_ASSERTION(mPendingMethods != 0,
  2494                "There are not pending methods, why this is called?");
  2495   if (mTimer) {
  2496     mTimer->Cancel();
  2497   } else {
  2498     mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
  2499     NS_ENSURE_TRUE(mTimer, );
  2501   mTimer->InitWithFuncCallback(FlushPendingMethods, this, 0,
  2502                                nsITimer::TYPE_ONE_SHOT);
  2505 void
  2506 IMEInputHandler::ExecutePendingMethods()
  2508   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
  2510   if (mTimer) {
  2511     mTimer->Cancel();
  2512     mTimer = nullptr;
  2515   if (![[NSApplication sharedApplication] isActive]) {
  2516     mIsInFocusProcessing = false;
  2517     // If we're not active, we should retry at focus event
  2518     return;
  2521   uint32_t pendingMethods = mPendingMethods;
  2522   // First, reset the pending method flags because if each methods cannot
  2523   // run now, they can reentry to the pending flags by theirselves.
  2524   mPendingMethods = 0;
  2526   if (pendingMethods & kDiscardIMEComposition)
  2527     DiscardIMEComposition();
  2528   if (pendingMethods & kSyncASCIICapableOnly)
  2529     SyncASCIICapableOnly();
  2530   if (pendingMethods & kNotifyIMEOfFocusChangeInGecko) {
  2531     NotifyIMEOfFocusChangeInGecko();
  2534   mIsInFocusProcessing = false;
  2536   NS_OBJC_END_TRY_ABORT_BLOCK;
  2539 #pragma mark -
  2542 /******************************************************************************
  2544  * IMEInputHandler implementation (native event handlers)
  2546  ******************************************************************************/
  2548 uint32_t
  2549 IMEInputHandler::ConvertToTextRangeType(uint32_t aUnderlineStyle,
  2550                                         NSRange& aSelectedRange)
  2552   PR_LOG(gLog, PR_LOG_ALWAYS,
  2553     ("%p IMEInputHandler::ConvertToTextRangeType, "
  2554      "aUnderlineStyle=%llu, aSelectedRange.length=%llu,",
  2555      this, aUnderlineStyle, aSelectedRange.length));
  2557   // We assume that aUnderlineStyle is NSUnderlineStyleSingle or
  2558   // NSUnderlineStyleThick.  NSUnderlineStyleThick should indicate a selected
  2559   // clause.  Otherwise, should indicate non-selected clause.
  2561   if (aSelectedRange.length == 0) {
  2562     switch (aUnderlineStyle) {
  2563       case NSUnderlineStyleSingle:
  2564         return NS_TEXTRANGE_RAWINPUT;
  2565       case NSUnderlineStyleThick:
  2566         return NS_TEXTRANGE_SELECTEDRAWTEXT;
  2567       default:
  2568         NS_WARNING("Unexpected line style");
  2569         return NS_TEXTRANGE_SELECTEDRAWTEXT;
  2573   switch (aUnderlineStyle) {
  2574     case NSUnderlineStyleSingle:
  2575       return NS_TEXTRANGE_CONVERTEDTEXT;
  2576     case NSUnderlineStyleThick:
  2577       return NS_TEXTRANGE_SELECTEDCONVERTEDTEXT;
  2578     default:
  2579       NS_WARNING("Unexpected line style");
  2580       return NS_TEXTRANGE_SELECTEDCONVERTEDTEXT;
  2584 uint32_t
  2585 IMEInputHandler::GetRangeCount(NSAttributedString *aAttrString)
  2587   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
  2589   // Iterate through aAttrString for the NSUnderlineStyleAttributeName and
  2590   // count the different segments adjusting limitRange as we go.
  2591   uint32_t count = 0;
  2592   NSRange effectiveRange;
  2593   NSRange limitRange = NSMakeRange(0, [aAttrString length]);
  2594   while (limitRange.length > 0) {
  2595     [aAttrString  attribute:NSUnderlineStyleAttributeName 
  2596                     atIndex:limitRange.location 
  2597       longestEffectiveRange:&effectiveRange
  2598                     inRange:limitRange];
  2599     limitRange =
  2600       NSMakeRange(NSMaxRange(effectiveRange), 
  2601                   NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
  2602     count++;
  2605   PR_LOG(gLog, PR_LOG_ALWAYS,
  2606     ("%p IMEInputHandler::GetRangeCount, aAttrString=\"%s\", count=%llu",
  2607      this, GetCharacters([aAttrString string]), count));
  2609   return count;
  2611   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
  2614 already_AddRefed<mozilla::TextRangeArray>
  2615 IMEInputHandler::CreateTextRangeArray(NSAttributedString *aAttrString,
  2616                                       NSRange& aSelectedRange)
  2618   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
  2620   // Convert the Cocoa range into the TextRange Array used in Gecko.
  2621   // Iterate through the attributed string and map the underline attribute to
  2622   // Gecko IME textrange attributes.  We may need to change the code here if
  2623   // we change the implementation of validAttributesForMarkedText.
  2624   NSRange limitRange = NSMakeRange(0, [aAttrString length]);
  2625   uint32_t rangeCount = GetRangeCount(aAttrString);
  2626   nsRefPtr<mozilla::TextRangeArray> textRangeArray =
  2627                                       new mozilla::TextRangeArray();
  2628   for (uint32_t i = 0; i < rangeCount && limitRange.length > 0; i++) {
  2629     NSRange effectiveRange;
  2630     id attributeValue = [aAttrString attribute:NSUnderlineStyleAttributeName
  2631                                        atIndex:limitRange.location
  2632                          longestEffectiveRange:&effectiveRange
  2633                                        inRange:limitRange];
  2635     TextRange range;
  2636     range.mStartOffset = effectiveRange.location;
  2637     range.mEndOffset = NSMaxRange(effectiveRange);
  2638     range.mRangeType =
  2639       ConvertToTextRangeType([attributeValue intValue], aSelectedRange);
  2640     textRangeArray->AppendElement(range);
  2642     PR_LOG(gLog, PR_LOG_ALWAYS,
  2643       ("%p IMEInputHandler::CreateTextRangeArray, "
  2644        "range={ mStartOffset=%llu, mEndOffset=%llu, mRangeType=%s }",
  2645        this, range.mStartOffset, range.mEndOffset,
  2646        GetRangeTypeName(range.mRangeType)));
  2648     limitRange =
  2649       NSMakeRange(NSMaxRange(effectiveRange), 
  2650                   NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
  2653   // Get current caret position.
  2654   TextRange range;
  2655   range.mStartOffset = aSelectedRange.location + aSelectedRange.length;
  2656   range.mEndOffset = range.mStartOffset;
  2657   range.mRangeType = NS_TEXTRANGE_CARETPOSITION;
  2658   textRangeArray->AppendElement(range);
  2660   PR_LOG(gLog, PR_LOG_ALWAYS,
  2661     ("%p IMEInputHandler::CreateTextRangeArray, "
  2662      "range={ mStartOffset=%llu, mEndOffset=%llu, mRangeType=%s }",
  2663      this, range.mStartOffset, range.mEndOffset,
  2664      GetRangeTypeName(range.mRangeType)));
  2666   return textRangeArray.forget();
  2668   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
  2671 bool
  2672 IMEInputHandler::DispatchTextEvent(const nsString& aText,
  2673                                    NSAttributedString* aAttrString,
  2674                                    NSRange& aSelectedRange,
  2675                                    bool aDoCommit)
  2677   PR_LOG(gLog, PR_LOG_ALWAYS,
  2678     ("%p IMEInputHandler::DispatchTextEvent, "
  2679      "aText=\"%s\", aAttrString=\"%s\", "
  2680      "aSelectedRange={ location=%llu, length=%llu }, "
  2681      "aDoCommit=%s, Destroyed()=%s",
  2682      this, NS_ConvertUTF16toUTF8(aText).get(),
  2683      GetCharacters([aAttrString string]),
  2684      aSelectedRange.location, aSelectedRange.length,
  2685      TrueOrFalse(aDoCommit), TrueOrFalse(Destroyed())));
  2687   NS_ENSURE_TRUE(!Destroyed(), false);
  2689   nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
  2691   WidgetTextEvent textEvent(true, NS_TEXT_TEXT, mWidget);
  2692   textEvent.time = PR_IntervalNow();
  2693   textEvent.theText = aText;
  2694   if (!aDoCommit) {
  2695     textEvent.mRanges = CreateTextRangeArray(aAttrString, aSelectedRange);
  2698   if (textEvent.theText != mLastDispatchedCompositionString) {
  2699     WidgetCompositionEvent compositionUpdate(true, NS_COMPOSITION_UPDATE,
  2700                                              mWidget);
  2701     compositionUpdate.time = textEvent.time;
  2702     compositionUpdate.data = textEvent.theText;
  2703     mLastDispatchedCompositionString = textEvent.theText;
  2704     DispatchEvent(compositionUpdate);
  2705     if (mIsInFocusProcessing || Destroyed()) {
  2706       PR_LOG(gLog, PR_LOG_ALWAYS,
  2707         ("%p IMEInputHandler::DispatchTextEvent, compositionupdate causes "
  2708          "aborting the composition, mIsInFocusProcessing=%s, Destryoed()=%s",
  2709          this, TrueOrFalse(mIsInFocusProcessing), TrueOrFalse(Destroyed())));
  2710       if (Destroyed()) {
  2711         return true;
  2716   return DispatchEvent(textEvent);
  2719 void
  2720 IMEInputHandler::InitCompositionEvent(WidgetCompositionEvent& aCompositionEvent)
  2722   aCompositionEvent.time = PR_IntervalNow();
  2725 void
  2726 IMEInputHandler::InsertTextAsCommittingComposition(
  2727                    NSAttributedString* aAttrString,
  2728                    NSRange* aReplacementRange)
  2730   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
  2732   PR_LOG(gLog, PR_LOG_ALWAYS,
  2733     ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
  2734      "aAttrString=\"%s\", aReplacementRange=%p { location=%llu, length=%llu }, "
  2735      "Destroyed()=%s, IsIMEComposing()=%s, "
  2736      "mMarkedRange={ location=%llu, length=%llu }",
  2737      this, GetCharacters([aAttrString string]), aReplacementRange,
  2738      aReplacementRange ? aReplacementRange->location : 0,
  2739      aReplacementRange ? aReplacementRange->length : 0,
  2740      TrueOrFalse(Destroyed()), TrueOrFalse(IsIMEComposing()),
  2741      mMarkedRange.location, mMarkedRange.length));
  2743   if (IgnoreIMECommit()) {
  2744     MOZ_CRASH("IMEInputHandler::InsertTextAsCommittingComposition() must not"
  2745               "be called while canceling the composition");
  2748   if (Destroyed()) {
  2749     return;
  2752   // First, commit current composition with the latest composition string if the
  2753   // replacement range is different from marked range.
  2754   if (IsIMEComposing() && aReplacementRange &&
  2755       aReplacementRange->location != NSNotFound &&
  2756       !NSEqualRanges(MarkedRange(), *aReplacementRange)) {
  2757     NSString* latestStr =
  2758       nsCocoaUtils::ToNSString(mLastDispatchedCompositionString);
  2759     NSAttributedString* attrLatestStr =
  2760       [[[NSAttributedString alloc] initWithString:latestStr] autorelease];
  2761     InsertTextAsCommittingComposition(attrLatestStr, nullptr);
  2762     if (Destroyed()) {
  2763       PR_LOG(gLog, PR_LOG_ALWAYS,
  2764         ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
  2765          "destroyed by commiting composition for setting replacement range",
  2766          this));
  2767       return;
  2771   nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
  2773   nsString str;
  2774   nsCocoaUtils::GetStringForNSString([aAttrString string], str);
  2776   if (!IsIMEComposing()) {
  2777     // If there is no selection and replacement range is specified, set the
  2778     // range as selection.
  2779     if (aReplacementRange && aReplacementRange->location != NSNotFound &&
  2780         !NSEqualRanges(SelectedRange(), *aReplacementRange)) {
  2781       NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
  2784     // XXXmnakano Probably, we shouldn't emulate composition in this case.
  2785     // I think that we should just fire DOM3 textInput event if we implement it.
  2786     WidgetCompositionEvent compStart(true, NS_COMPOSITION_START, mWidget);
  2787     InitCompositionEvent(compStart);
  2789     DispatchEvent(compStart);
  2790     if (Destroyed()) {
  2791       PR_LOG(gLog, PR_LOG_ALWAYS,
  2792         ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
  2793          "destroyed by compositionstart event", this));
  2794       return;
  2797     OnStartIMEComposition();
  2800   NSRange range = NSMakeRange(0, str.Length());
  2801   DispatchTextEvent(str, aAttrString, range, true);
  2802   if (Destroyed()) {
  2803     PR_LOG(gLog, PR_LOG_ALWAYS,
  2804       ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
  2805        "destroyed by text event", this));
  2806     return;
  2809   OnUpdateIMEComposition([aAttrString string]);
  2811   WidgetCompositionEvent compEnd(true, NS_COMPOSITION_END, mWidget);
  2812   InitCompositionEvent(compEnd);
  2813   compEnd.data = mLastDispatchedCompositionString;
  2814   DispatchEvent(compEnd);
  2815   if (Destroyed()) {
  2816     PR_LOG(gLog, PR_LOG_ALWAYS,
  2817       ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
  2818        "destroyed by compositionend event", this));
  2819     return;
  2822   OnEndIMEComposition();
  2824   mMarkedRange = NSMakeRange(NSNotFound, 0);
  2826   NS_OBJC_END_TRY_ABORT_BLOCK;
  2829 void
  2830 IMEInputHandler::SetMarkedText(NSAttributedString* aAttrString,
  2831                                NSRange& aSelectedRange,
  2832                                NSRange* aReplacementRange)
  2834   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
  2836   PR_LOG(gLog, PR_LOG_ALWAYS,
  2837     ("%p IMEInputHandler::SetMarkedText, "
  2838      "aAttrString=\"%s\", aSelectedRange={ location=%llu, length=%llu }, "
  2839      "aReplacementRange=%p { location=%llu, length=%llu }, "
  2840      "Destroyed()=%s, IgnoreIMEComposition()=%s, IsIMEComposing()=%s, "
  2841      "mMarkedRange={ location=%llu, length=%llu }",
  2842      this, GetCharacters([aAttrString string]),
  2843      aSelectedRange.location, aSelectedRange.length, aReplacementRange,
  2844      aReplacementRange ? aReplacementRange->location : 0,
  2845      aReplacementRange ? aReplacementRange->length : 0,
  2846      TrueOrFalse(Destroyed()), TrueOrFalse(IgnoreIMEComposition()),
  2847      TrueOrFalse(IsIMEComposing()),
  2848      mMarkedRange.location, mMarkedRange.length));
  2850   if (Destroyed() || IgnoreIMEComposition()) {
  2851     return;
  2854   nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
  2856   // First, commit current composition with the latest composition string if the
  2857   // replacement range is different from marked range.
  2858   if (IsIMEComposing() && aReplacementRange &&
  2859       aReplacementRange->location != NSNotFound &&
  2860       !NSEqualRanges(MarkedRange(), *aReplacementRange)) {
  2861     NSString* latestStr =
  2862       nsCocoaUtils::ToNSString(mLastDispatchedCompositionString);
  2863     NSAttributedString* attrLatestStr =
  2864       [[[NSAttributedString alloc] initWithString:latestStr] autorelease];
  2865     bool ignoreIMECommit = mIgnoreIMECommit;
  2866     mIgnoreIMECommit = false;
  2867     InsertTextAsCommittingComposition(attrLatestStr, nullptr);
  2868     mIgnoreIMECommit = ignoreIMECommit;
  2869     if (Destroyed()) {
  2870       PR_LOG(gLog, PR_LOG_ALWAYS,
  2871         ("%p IMEInputHandler::SetMarkedText, "
  2872          "destroyed by commiting composition for setting replacement range",
  2873          this));
  2874       return;
  2878   nsString str;
  2879   nsCocoaUtils::GetStringForNSString([aAttrString string], str);
  2881   mMarkedRange.length = str.Length();
  2883   if (!IsIMEComposing() && !str.IsEmpty()) {
  2884     // If there is no selection and replacement range is specified, set the
  2885     // range as selection.
  2886     if (aReplacementRange && aReplacementRange->location != NSNotFound &&
  2887         !NSEqualRanges(SelectedRange(), *aReplacementRange)) {
  2888       NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
  2891     mMarkedRange.location = SelectedRange().location;
  2893     WidgetCompositionEvent compStart(true, NS_COMPOSITION_START, mWidget);
  2894     InitCompositionEvent(compStart);
  2896     DispatchEvent(compStart);
  2897     if (Destroyed()) {
  2898       PR_LOG(gLog, PR_LOG_ALWAYS,
  2899         ("%p IMEInputHandler::SetMarkedText, "
  2900          "destroyed by compositionstart event", this));
  2901       return;
  2904     OnStartIMEComposition();
  2907   if (IsIMEComposing()) {
  2908     OnUpdateIMEComposition([aAttrString string]);
  2910     bool doCommit = str.IsEmpty();
  2911     DispatchTextEvent(str, aAttrString, aSelectedRange, doCommit);
  2912     if (Destroyed()) {
  2913       PR_LOG(gLog, PR_LOG_ALWAYS,
  2914         ("%p IMEInputHandler::SetMarkedText, "
  2915          "destroyed by text event", this));
  2916       return;
  2919     if (doCommit) {
  2920       WidgetCompositionEvent compEnd(true, NS_COMPOSITION_END, mWidget);
  2921       InitCompositionEvent(compEnd);
  2922       compEnd.data = mLastDispatchedCompositionString;
  2923       DispatchEvent(compEnd);
  2924       if (Destroyed()) {
  2925         PR_LOG(gLog, PR_LOG_ALWAYS,
  2926           ("%p IMEInputHandler::SetMarkedText, "
  2927            "destroyed by compositionend event", this));
  2928         return;
  2930       OnEndIMEComposition();
  2934   NS_OBJC_END_TRY_ABORT_BLOCK;
  2937 NSInteger
  2938 IMEInputHandler::ConversationIdentifier()
  2940   PR_LOG(gLog, PR_LOG_ALWAYS,
  2941     ("%p IMEInputHandler::ConversationIdentifier, Destroyed()=%s",
  2942      this, TrueOrFalse(Destroyed())));
  2944   if (Destroyed()) {
  2945     return reinterpret_cast<NSInteger>(mView);
  2948   nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
  2950   // NOTE: The size of NSInteger is same as pointer size.
  2951   WidgetQueryContentEvent textContent(true, NS_QUERY_TEXT_CONTENT, mWidget);
  2952   textContent.InitForQueryTextContent(0, 0);
  2953   DispatchEvent(textContent);
  2954   if (!textContent.mSucceeded) {
  2955     PR_LOG(gLog, PR_LOG_ALWAYS,
  2956       ("%p IMEInputHandler::ConversationIdentifier, Failed", this));
  2957     return reinterpret_cast<NSInteger>(mView);
  2959   // XXX This might return same ID as a previously existing editor if the
  2960   //     deleted editor was created at the same address.  Is there a better way?
  2961   return reinterpret_cast<NSInteger>(textContent.mReply.mContentsRoot);
  2964 NSAttributedString*
  2965 IMEInputHandler::GetAttributedSubstringFromRange(NSRange& aRange,
  2966                                                  NSRange* aActualRange)
  2968   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
  2970   PR_LOG(gLog, PR_LOG_ALWAYS,
  2971     ("%p IMEInputHandler::GetAttributedSubstringFromRange, "
  2972      "aRange={ location=%llu, length=%llu }, aActualRange=%p, Destroyed()=%s",
  2973      this, aRange.location, aRange.length, aActualRange,
  2974      TrueOrFalse(Destroyed())));
  2976   if (aActualRange) {
  2977     *aActualRange = NSMakeRange(NSNotFound, 0);
  2980   if (Destroyed() || aRange.location == NSNotFound || aRange.length == 0) {
  2981     return nil;
  2984   nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
  2986   nsAutoString str;
  2987   WidgetQueryContentEvent textContent(true, NS_QUERY_TEXT_CONTENT, mWidget);
  2988   textContent.InitForQueryTextContent(aRange.location, aRange.length);
  2989   DispatchEvent(textContent);
  2991   PR_LOG(gLog, PR_LOG_ALWAYS,
  2992     ("%p IMEInputHandler::GetAttributedSubstringFromRange, "
  2993      "textContent={ mSucceeded=%s, mReply={ mString=\"%s\", mOffset=%llu } }",
  2994      this, TrueOrFalse(textContent.mSucceeded),
  2995      NS_ConvertUTF16toUTF8(textContent.mReply.mString).get(),
  2996      textContent.mReply.mOffset));
  2998   if (!textContent.mSucceeded) {
  2999     return nil;
  3002   NSString* nsstr = nsCocoaUtils::ToNSString(textContent.mReply.mString);
  3003   NSAttributedString* result =
  3004     [[[NSAttributedString alloc] initWithString:nsstr
  3005                                      attributes:nil] autorelease];
  3006   if (aActualRange) {
  3007     aActualRange->location = textContent.mReply.mOffset;
  3008     aActualRange->length = textContent.mReply.mString.Length();
  3010   return result;
  3012   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
  3015 bool
  3016 IMEInputHandler::HasMarkedText()
  3018   PR_LOG(gLog, PR_LOG_ALWAYS,
  3019     ("%p IMEInputHandler::HasMarkedText, "
  3020      "mMarkedRange={ location=%llu, length=%llu }",
  3021      this, mMarkedRange.location, mMarkedRange.length));
  3023   return (mMarkedRange.location != NSNotFound) && (mMarkedRange.length != 0);
  3026 NSRange
  3027 IMEInputHandler::MarkedRange()
  3029   PR_LOG(gLog, PR_LOG_ALWAYS,
  3030     ("%p IMEInputHandler::MarkedRange, "
  3031      "mMarkedRange={ location=%llu, length=%llu }",
  3032      this, mMarkedRange.location, mMarkedRange.length));
  3034   if (!HasMarkedText()) {
  3035     return NSMakeRange(NSNotFound, 0);
  3037   return mMarkedRange;
  3040 NSRange
  3041 IMEInputHandler::SelectedRange()
  3043   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
  3045   PR_LOG(gLog, PR_LOG_ALWAYS,
  3046     ("%p IMEInputHandler::SelectedRange, Destroyed()=%s, mSelectedRange={ "
  3047      "location=%llu, length=%llu }",
  3048      this, TrueOrFalse(Destroyed()), mSelectedRange.location,
  3049      mSelectedRange.length));
  3051   if (Destroyed()) {
  3052     return mSelectedRange;
  3055   if (mSelectedRange.location != NSNotFound) {
  3056     MOZ_ASSERT(mIMEHasFocus);
  3057     return mSelectedRange;
  3060   nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
  3062   WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, mWidget);
  3063   DispatchEvent(selection);
  3065   PR_LOG(gLog, PR_LOG_ALWAYS,
  3066     ("%p IMEInputHandler::SelectedRange, selection={ mSucceeded=%s, "
  3067      "mReply={ mOffset=%llu, mString.Length()=%llu } }",
  3068      this, TrueOrFalse(selection.mSucceeded), selection.mReply.mOffset,
  3069      selection.mReply.mString.Length()));
  3071   if (!selection.mSucceeded) {
  3072     return mSelectedRange;
  3075   if (mIMEHasFocus) {
  3076     mSelectedRange.location = selection.mReply.mOffset;
  3077     mSelectedRange.length = selection.mReply.mString.Length();
  3078     return mSelectedRange;
  3081   return NSMakeRange(selection.mReply.mOffset,
  3082                      selection.mReply.mString.Length());
  3084   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(mSelectedRange);
  3087 NSRect
  3088 IMEInputHandler::FirstRectForCharacterRange(NSRange& aRange,
  3089                                             NSRange* aActualRange)
  3091   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
  3093   PR_LOG(gLog, PR_LOG_ALWAYS,
  3094     ("%p IMEInputHandler::FirstRectForCharacterRange, Destroyed()=%s, "
  3095      "aRange={ location=%llu, length=%llu }, aActualRange=%p }",
  3096      this, TrueOrFalse(Destroyed()), aRange.location, aRange.length,
  3097      aActualRange));
  3099   // XXX this returns first character rect or caret rect, it is limitation of
  3100   // now. We need more work for returns first line rect. But current
  3101   // implementation is enough for IMEs.
  3103   NSRect rect = NSMakeRect(0.0, 0.0, 0.0, 0.0);
  3104   NSRange actualRange = NSMakeRange(NSNotFound, 0);
  3105   if (aActualRange) {
  3106     *aActualRange = actualRange;
  3108   if (Destroyed() || aRange.location == NSNotFound) {
  3109     return rect;
  3112   nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
  3114   nsIntRect r;
  3115   bool useCaretRect = (aRange.length == 0);
  3116   if (!useCaretRect) {
  3117     WidgetQueryContentEvent charRect(true, NS_QUERY_TEXT_RECT, mWidget);
  3118     charRect.InitForQueryTextRect(aRange.location, 1);
  3119     DispatchEvent(charRect);
  3120     if (charRect.mSucceeded) {
  3121       r = charRect.mReply.mRect;
  3122       actualRange.location = charRect.mReply.mOffset;
  3123       actualRange.length = charRect.mReply.mString.Length();
  3124     } else {
  3125       useCaretRect = true;
  3129   if (useCaretRect) {
  3130     WidgetQueryContentEvent caretRect(true, NS_QUERY_CARET_RECT, mWidget);
  3131     caretRect.InitForQueryCaretRect(aRange.location);
  3132     DispatchEvent(caretRect);
  3133     if (!caretRect.mSucceeded) {
  3134       return rect;
  3136     r = caretRect.mReply.mRect;
  3137     r.width = 0;
  3138     actualRange.location = caretRect.mReply.mOffset;
  3139     actualRange.length = 0;
  3142   nsIWidget* rootWidget = mWidget->GetTopLevelWidget();
  3143   NSWindow* rootWindow =
  3144     static_cast<NSWindow*>(rootWidget->GetNativeData(NS_NATIVE_WINDOW));
  3145   NSView* rootView =
  3146     static_cast<NSView*>(rootWidget->GetNativeData(NS_NATIVE_WIDGET));
  3147   if (!rootWindow || !rootView) {
  3148     return rect;
  3150   rect = nsCocoaUtils::DevPixelsToCocoaPoints(r, mWidget->BackingScaleFactor());
  3151   rect = [rootView convertRect:rect toView:nil];
  3152   rect.origin = [rootWindow convertBaseToScreen:rect.origin];
  3154   if (aActualRange) {
  3155     *aActualRange = actualRange;
  3158   PR_LOG(gLog, PR_LOG_ALWAYS,
  3159     ("%p IMEInputHandler::FirstRectForCharacterRange, "
  3160      "useCaretRect=%s rect={ x=%f, y=%f, width=%f, height=%f }, "
  3161      "actualRange={ location=%llu, length=%llu }",
  3162      this, TrueOrFalse(useCaretRect), rect.origin.x, rect.origin.y,
  3163      rect.size.width, rect.size.height, actualRange.location,
  3164      actualRange.length));
  3166   return rect;
  3168   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakeRect(0.0, 0.0, 0.0, 0.0));
  3171 NSUInteger
  3172 IMEInputHandler::CharacterIndexForPoint(NSPoint& aPoint)
  3174   PR_LOG(gLog, PR_LOG_ALWAYS,
  3175     ("%p IMEInputHandler::CharacterIndexForPoint, aPoint={ x=%f, y=%f }",
  3176      this, aPoint.x, aPoint.y));
  3178   //nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
  3180   // To implement this, we'd have to grovel in text frames looking at text
  3181   // offsets.
  3182   return 0;
  3185 NSArray*
  3186 IMEInputHandler::GetValidAttributesForMarkedText()
  3188   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
  3190   PR_LOG(gLog, PR_LOG_ALWAYS,
  3191     ("%p IMEInputHandler::GetValidAttributesForMarkedText", this));
  3193   //nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
  3195   //return [NSArray arrayWithObjects:NSUnderlineStyleAttributeName,
  3196   //                                 NSMarkedClauseSegmentAttributeName,
  3197   //                                 NSTextInputReplacementRangeAttributeName,
  3198   //                                 nil];
  3199   // empty array; we don't support any attributes right now
  3200   return [NSArray array];
  3202   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
  3206 #pragma mark -
  3209 /******************************************************************************
  3211  *  IMEInputHandler implementation #2
  3213  ******************************************************************************/
  3215 IMEInputHandler::IMEInputHandler(nsChildView* aWidget,
  3216                                  NSView<mozView> *aNativeView) :
  3217   PluginTextInputHandler(aWidget, aNativeView),
  3218   mPendingMethods(0), mIMECompositionString(nullptr),
  3219   mIsIMEComposing(false), mIsIMEEnabled(true),
  3220   mIsASCIICapableOnly(false), mIgnoreIMECommit(false),
  3221   mIsInFocusProcessing(false), mIMEHasFocus(false)
  3223   InitStaticMembers();
  3225   mMarkedRange.location = NSNotFound;
  3226   mMarkedRange.length = 0;
  3227   mSelectedRange.location = NSNotFound;
  3228   mSelectedRange.length = 0;
  3231 IMEInputHandler::~IMEInputHandler()
  3233   if (mTimer) {
  3234     mTimer->Cancel();
  3235     mTimer = nullptr;
  3237   if (sFocusedIMEHandler == this) {
  3238     sFocusedIMEHandler = nullptr;
  3242 void
  3243 IMEInputHandler::OnFocusChangeInGecko(bool aFocus)
  3245   PR_LOG(gLog, PR_LOG_ALWAYS,
  3246     ("%p IMEInputHandler::OnFocusChangeInGecko, aFocus=%s, Destroyed()=%s, "
  3247      "sFocusedIMEHandler=%p",
  3248      this, TrueOrFalse(aFocus), TrueOrFalse(Destroyed()), sFocusedIMEHandler));
  3250   mSelectedRange.location = NSNotFound; // Marking dirty
  3251   mIMEHasFocus = aFocus;
  3253   // This is called when the native focus is changed and when the native focus
  3254   // isn't changed but the focus is changed in Gecko.
  3255   if (!aFocus) {
  3256     if (sFocusedIMEHandler == this)
  3257       sFocusedIMEHandler = nullptr;
  3258     return;
  3261   sFocusedIMEHandler = this;
  3262   mIsInFocusProcessing = true;
  3264   // We need to notify IME of focus change in Gecko as native focus change
  3265   // because the window level of the focused element in Gecko may be changed.
  3266   mPendingMethods |= kNotifyIMEOfFocusChangeInGecko;
  3267   ResetTimer();
  3270 bool
  3271 IMEInputHandler::OnDestroyWidget(nsChildView* aDestroyingWidget)
  3273   PR_LOG(gLog, PR_LOG_ALWAYS,
  3274     ("%p IMEInputHandler::OnDestroyWidget, aDestroyingWidget=%p, "
  3275      "sFocusedIMEHandler=%p, IsIMEComposing()=%s",
  3276      this, aDestroyingWidget, sFocusedIMEHandler,
  3277      TrueOrFalse(IsIMEComposing())));
  3279   // If we're not focused, the focused IMEInputHandler may have been
  3280   // created by another widget/nsChildView.
  3281   if (sFocusedIMEHandler && sFocusedIMEHandler != this) {
  3282     sFocusedIMEHandler->OnDestroyWidget(aDestroyingWidget);
  3285   if (!PluginTextInputHandler::OnDestroyWidget(aDestroyingWidget)) {
  3286     return false;
  3289   if (IsIMEComposing()) {
  3290     // If our view is in the composition, we should clean up it.
  3291     CancelIMEComposition();
  3292     OnEndIMEComposition();
  3295   mSelectedRange.location = NSNotFound; // Marking dirty
  3296   mIMEHasFocus = false;
  3298   return true;
  3301 void
  3302 IMEInputHandler::OnStartIMEComposition()
  3304   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
  3306   PR_LOG(gLog, PR_LOG_ALWAYS,
  3307     ("%p IMEInputHandler::OnStartIMEComposition, mView=%p, mWidget=%p"
  3308      "inputContext=%p, mIsIMEComposing=%s",
  3309      this, mView, mWidget, mView ? [mView inputContext] : nullptr,
  3310      TrueOrFalse(mIsIMEComposing)));
  3312   NS_ASSERTION(!mIsIMEComposing, "There is a composition already");
  3313   mIsIMEComposing = true;
  3315   mLastDispatchedCompositionString.Truncate();
  3317   NS_OBJC_END_TRY_ABORT_BLOCK;
  3320 void
  3321 IMEInputHandler::OnUpdateIMEComposition(NSString* aIMECompositionString)
  3323   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
  3325   PR_LOG(gLog, PR_LOG_ALWAYS,
  3326     ("%p IMEInputHandler::OnUpdateIMEComposition, mView=%p, mWidget=%p, "
  3327      "inputContext=%p, mIsIMEComposing=%s, aIMECompositionString=\"%s\"",
  3328      this, mView, mWidget, mView ? [mView inputContext] : nullptr,
  3329      TrueOrFalse(mIsIMEComposing), GetCharacters(aIMECompositionString)));
  3331   NS_ASSERTION(mIsIMEComposing, "We're not in composition");
  3333   if (mIMECompositionString)
  3334     [mIMECompositionString release];
  3335   mIMECompositionString = [aIMECompositionString retain];
  3337   NS_OBJC_END_TRY_ABORT_BLOCK;
  3340 void
  3341 IMEInputHandler::OnEndIMEComposition()
  3343   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
  3345   PR_LOG(gLog, PR_LOG_ALWAYS,
  3346     ("%p IMEInputHandler::OnEndIMEComposition, mView=%p, mWidget=%p, "
  3347      "inputContext=%p, mIsIMEComposing=%s",
  3348      this, mView, mWidget, mView ? [mView inputContext] : nullptr,
  3349      TrueOrFalse(mIsIMEComposing)));
  3351   NS_ASSERTION(mIsIMEComposing, "We're not in composition");
  3353   mIsIMEComposing = false;
  3355   if (mIMECompositionString) {
  3356     [mIMECompositionString release];
  3357     mIMECompositionString = nullptr;
  3360   mLastDispatchedCompositionString.Truncate();
  3362   NS_OBJC_END_TRY_ABORT_BLOCK;
  3365 void
  3366 IMEInputHandler::SendCommittedText(NSString *aString)
  3368   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
  3370   PR_LOG(gLog, PR_LOG_ALWAYS,
  3371     ("%p IMEInputHandler::SendCommittedText, mView=%p, mWidget=%p, "
  3372      "inputContext=%p, mIsIMEComposing=%s",
  3373      this, mView, mWidget, mView ? [mView inputContext] : nullptr,
  3374      TrueOrFalse(mIsIMEComposing), mWidget));
  3376   NS_ENSURE_TRUE(mWidget, );
  3377   // XXX We should send the string without mView.
  3378   if (!mView) {
  3379     return;
  3382   NSAttributedString* attrStr =
  3383     [[NSAttributedString alloc] initWithString:aString];
  3384   [mView insertText:attrStr];
  3385   [attrStr release];
  3387   NS_OBJC_END_TRY_ABORT_BLOCK;
  3390 void
  3391 IMEInputHandler::KillIMEComposition()
  3393   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
  3395   PR_LOG(gLog, PR_LOG_ALWAYS,
  3396     ("%p IMEInputHandler::KillIMEComposition, mView=%p, mWidget=%p, "
  3397      "inputContext=%p, mIsIMEComposing=%s, "
  3398      "Destroyed()=%s, IsFocused()=%s",
  3399      this, mView, mWidget, mView ? [mView inputContext] : nullptr,
  3400      TrueOrFalse(mIsIMEComposing), TrueOrFalse(Destroyed()),
  3401      TrueOrFalse(IsFocused())));
  3403   if (Destroyed()) {
  3404     return;
  3407   if (IsFocused()) {
  3408     NS_ENSURE_TRUE_VOID(mView);
  3409     NSTextInputContext* inputContext = [mView inputContext];
  3410     NS_ENSURE_TRUE_VOID(inputContext);
  3411     [inputContext discardMarkedText];
  3412     return;
  3415   PR_LOG(gLog, PR_LOG_ALWAYS,
  3416     ("%p IMEInputHandler::KillIMEComposition, Pending...", this));
  3418   // Commit the composition internally.
  3419   SendCommittedText(mIMECompositionString);
  3420   NS_ASSERTION(!mIsIMEComposing, "We're still in a composition");
  3421   // The pending method will be fired by the next focus event.
  3422   mPendingMethods |= kDiscardIMEComposition;
  3424   NS_OBJC_END_TRY_ABORT_BLOCK;
  3427 void
  3428 IMEInputHandler::CommitIMEComposition()
  3430   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
  3432   if (!IsIMEComposing())
  3433     return;
  3435   PR_LOG(gLog, PR_LOG_ALWAYS,
  3436     ("%p IMEInputHandler::CommitIMEComposition, mIMECompositionString=%s",
  3437      this, GetCharacters(mIMECompositionString)));
  3439   KillIMEComposition();
  3441   if (!IsIMEComposing())
  3442     return;
  3444   // If the composition is still there, KillIMEComposition only kills the
  3445   // composition in TSM.  We also need to finish the our composition too.
  3446   SendCommittedText(mIMECompositionString);
  3448   NS_OBJC_END_TRY_ABORT_BLOCK;
  3451 void
  3452 IMEInputHandler::CancelIMEComposition()
  3454   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
  3456   if (!IsIMEComposing())
  3457     return;
  3459   PR_LOG(gLog, PR_LOG_ALWAYS,
  3460     ("%p IMEInputHandler::CancelIMEComposition, mIMECompositionString=%s",
  3461      this, GetCharacters(mIMECompositionString)));
  3463   // For canceling the current composing, we need to ignore the param of
  3464   // insertText.  But this code is ugly...
  3465   mIgnoreIMECommit = true;
  3466   KillIMEComposition();
  3467   mIgnoreIMECommit = false;
  3469   if (!IsIMEComposing())
  3470     return;
  3472   // If the composition is still there, KillIMEComposition only kills the
  3473   // composition in TSM.  We also need to kill the our composition too.
  3474   SendCommittedText(@"");
  3476   NS_OBJC_END_TRY_ABORT_BLOCK;
  3479 bool
  3480 IMEInputHandler::IsFocused()
  3482   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
  3484   NS_ENSURE_TRUE(!Destroyed(), false);
  3485   NSWindow* window = [mView window];
  3486   NS_ENSURE_TRUE(window, false);
  3487   return [window firstResponder] == mView &&
  3488          [window isKeyWindow] &&
  3489          [[NSApplication sharedApplication] isActive];
  3491   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
  3494 bool
  3495 IMEInputHandler::IsIMEOpened()
  3497   TISInputSourceWrapper tis;
  3498   tis.InitByCurrentInputSource();
  3499   return tis.IsOpenedIMEMode();
  3502 void
  3503 IMEInputHandler::SetASCIICapableOnly(bool aASCIICapableOnly)
  3505   if (aASCIICapableOnly == mIsASCIICapableOnly)
  3506     return;
  3508   CommitIMEComposition();
  3509   mIsASCIICapableOnly = aASCIICapableOnly;
  3510   SyncASCIICapableOnly();
  3513 void
  3514 IMEInputHandler::EnableIME(bool aEnableIME)
  3516   if (aEnableIME == mIsIMEEnabled)
  3517     return;
  3519   CommitIMEComposition();
  3520   mIsIMEEnabled = aEnableIME;
  3523 void
  3524 IMEInputHandler::SetIMEOpenState(bool aOpenIME)
  3526   if (!IsFocused() || IsIMEOpened() == aOpenIME)
  3527     return;
  3529   if (!aOpenIME) {
  3530     TISInputSourceWrapper tis;
  3531     tis.InitByCurrentASCIICapableInputSource();
  3532     tis.Select();
  3533     return;
  3536   // If we know the latest IME opened mode, we should select it.
  3537   if (sLatestIMEOpenedModeInputSourceID) {
  3538     TISInputSourceWrapper tis;
  3539     tis.InitByInputSourceID(sLatestIMEOpenedModeInputSourceID);
  3540     tis.Select();
  3541     return;
  3544   // XXX If the current input source is a mode of IME, we should turn on it,
  3545   // but we haven't found such way...
  3547   // Finally, we should refer the system locale but this is a little expensive,
  3548   // we shouldn't retry this (if it was succeeded, we already set
  3549   // sLatestIMEOpenedModeInputSourceID at that time).
  3550   static bool sIsPrefferredIMESearched = false;
  3551   if (sIsPrefferredIMESearched)
  3552     return;
  3553   sIsPrefferredIMESearched = true;
  3554   OpenSystemPreferredLanguageIME();
  3557 void
  3558 IMEInputHandler::OpenSystemPreferredLanguageIME()
  3560   PR_LOG(gLog, PR_LOG_ALWAYS,
  3561     ("%p IMEInputHandler::OpenSystemPreferredLanguageIME", this));
  3563   CFArrayRef langList = ::CFLocaleCopyPreferredLanguages();
  3564   if (!langList) {
  3565     PR_LOG(gLog, PR_LOG_ALWAYS,
  3566       ("%p IMEInputHandler::OpenSystemPreferredLanguageIME, langList is NULL",
  3567        this));
  3568     return;
  3570   CFIndex count = ::CFArrayGetCount(langList);
  3571   for (CFIndex i = 0; i < count; i++) {
  3572     CFLocaleRef locale =
  3573       ::CFLocaleCreate(kCFAllocatorDefault,
  3574           static_cast<CFStringRef>(::CFArrayGetValueAtIndex(langList, i)));
  3575     if (!locale) {
  3576       continue;
  3579     bool changed = false;
  3580     CFStringRef lang = static_cast<CFStringRef>(
  3581       ::CFLocaleGetValue(locale, kCFLocaleLanguageCode));
  3582     NS_ASSERTION(lang, "lang is null");
  3583     if (lang) {
  3584       TISInputSourceWrapper tis;
  3585       tis.InitByLanguage(lang);
  3586       if (tis.IsOpenedIMEMode()) {
  3587 #ifdef PR_LOGGING
  3588         if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) {
  3589           CFStringRef foundTIS;
  3590           tis.GetInputSourceID(foundTIS);
  3591           PR_LOG(gLog, PR_LOG_ALWAYS,
  3592             ("%p IMEInputHandler::OpenSystemPreferredLanguageIME, "
  3593              "foundTIS=%s, lang=%s",
  3594              this, GetCharacters(foundTIS), GetCharacters(lang)));
  3596 #endif // #ifdef PR_LOGGING
  3597         tis.Select();
  3598         changed = true;
  3601     ::CFRelease(locale);
  3602     if (changed) {
  3603       break;
  3606   ::CFRelease(langList);
  3610 #pragma mark -
  3613 /******************************************************************************
  3615  *  PluginTextInputHandler implementation
  3617  ******************************************************************************/
  3619 PluginTextInputHandler::PluginTextInputHandler(nsChildView* aWidget,
  3620                                                NSView<mozView> *aNativeView) :
  3621   TextInputHandlerBase(aWidget, aNativeView),
  3622   mIgnoreNextKeyUpEvent(false),
  3623 #ifndef __LP64__
  3624   mPluginTSMDoc(0), mPluginTSMInComposition(false),
  3625 #endif // #ifndef __LP64__
  3626   mPluginComplexTextInputRequested(false)
  3630 PluginTextInputHandler::~PluginTextInputHandler()
  3632 #ifndef __LP64__
  3633   if (mPluginTSMDoc) {
  3634     ::DeleteTSMDocument(mPluginTSMDoc);
  3636 #endif // #ifndef __LP64__
  3639 /* static */ void
  3640 PluginTextInputHandler::ConvertCocoaKeyEventToNPCocoaEvent(
  3641                           NSEvent* aCocoaEvent,
  3642                           NPCocoaEvent& aPluginEvent)
  3644   nsCocoaUtils::InitNPCocoaEvent(&aPluginEvent);
  3645   NSEventType nativeType = [aCocoaEvent type];
  3646   switch (nativeType) {
  3647     case NSKeyDown:
  3648       aPluginEvent.type = NPCocoaEventKeyDown;
  3649       break;
  3650     case NSKeyUp:
  3651       aPluginEvent.type = NPCocoaEventKeyUp;
  3652       break;
  3653     case NSFlagsChanged:
  3654       aPluginEvent.type = NPCocoaEventFlagsChanged;
  3655       break;
  3656     default:
  3657       NS_WARNING("Asked to convert key event of unknown type to Cocoa plugin event!");
  3659   aPluginEvent.data.key.modifierFlags = [aCocoaEvent modifierFlags];
  3660   aPluginEvent.data.key.keyCode = [aCocoaEvent keyCode];
  3661   // don't try to access character data for flags changed events,
  3662   // it will raise an exception
  3663   if (nativeType != NSFlagsChanged) {
  3664     aPluginEvent.data.key.characters = (NPNSString*)[aCocoaEvent characters];
  3665     aPluginEvent.data.key.charactersIgnoringModifiers =
  3666       (NPNSString*)[aCocoaEvent charactersIgnoringModifiers];
  3667     aPluginEvent.data.key.isARepeat = [aCocoaEvent isARepeat];
  3671 #ifndef __LP64__
  3673 EventHandlerRef PluginTextInputHandler::sPluginKeyEventsHandler = NULL;
  3675 /* static */ void
  3676 PluginTextInputHandler::InstallPluginKeyEventsHandler()
  3678   if (sPluginKeyEventsHandler) {
  3679     return;
  3681   static const EventTypeSpec sTSMEvents[] =
  3682     { { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent } };
  3683   ::InstallEventHandler(::GetEventDispatcherTarget(),
  3684                         ::NewEventHandlerUPP(PluginKeyEventsHandler),
  3685                         GetEventTypeCount(sTSMEvents),
  3686                         sTSMEvents,
  3687                         NULL,
  3688                         &sPluginKeyEventsHandler);
  3691 /* static */ void
  3692 PluginTextInputHandler::RemovePluginKeyEventsHandler()
  3694   if (!sPluginKeyEventsHandler) {
  3695     return;
  3697   ::RemoveEventHandler(sPluginKeyEventsHandler);
  3698   sPluginKeyEventsHandler = NULL;
  3701 /* static */ void
  3702 PluginTextInputHandler::SwizzleMethods()
  3704   Class IMKInputSessionClass = ::NSClassFromString(@"IMKInputSession");
  3705   nsToolkit::SwizzleMethods(IMKInputSessionClass, @selector(handleEvent:),
  3706     @selector(PluginTextInputHandler_IMKInputSession_handleEvent:));
  3707   nsToolkit::SwizzleMethods(IMKInputSessionClass, @selector(commitComposition),
  3708     @selector(PluginTextInputHandler_IMKInputSession_commitComposition));
  3709   nsToolkit::SwizzleMethods(IMKInputSessionClass, @selector(finishSession),
  3710     @selector(PluginTextInputHandler_IMKInputSession_finishSession));
  3713 /* static */ OSStatus
  3714 PluginTextInputHandler::PluginKeyEventsHandler(EventHandlerCallRef aHandlerRef,
  3715                                                EventRef aEvent,
  3716                                                void *aUserData)
  3718   nsAutoreleasePool localPool;
  3720   TSMDocumentID activeDoc = ::TSMGetActiveDocument();
  3721   NS_ENSURE_TRUE(activeDoc, eventNotHandledErr);
  3723   ChildView *target = nil;
  3724   OSStatus status = ::TSMGetDocumentProperty(activeDoc,
  3725                                              kFocusedChildViewTSMDocPropertyTag,
  3726                                              sizeof(ChildView *), nil, &target);
  3727   NS_ENSURE_TRUE(status == noErr, eventNotHandledErr);
  3728   NS_ENSURE_TRUE(target, eventNotHandledErr);
  3730   EventRef keyEvent = NULL;
  3731   status = ::GetEventParameter(aEvent, kEventParamTextInputSendKeyboardEvent,
  3732                                typeEventRef, NULL, sizeof(EventRef), NULL,
  3733                                &keyEvent);
  3734   NS_ENSURE_TRUE(status == noErr, eventNotHandledErr);
  3735   NS_ENSURE_TRUE(keyEvent, eventNotHandledErr);
  3737   nsIWidget* widget = [target widget];
  3738   NS_ENSURE_TRUE(widget, eventNotHandledErr);
  3739   TextInputHandler*  handler =
  3740     static_cast<nsChildView*>(widget)->GetTextInputHandler();
  3741   NS_ENSURE_TRUE(handler, eventNotHandledErr);
  3742   handler->HandleCarbonPluginKeyEvent(keyEvent);
  3744   return noErr;
  3747 // Called from PluginKeyEventsHandler() (a handler for Carbon TSM events) to
  3748 // process a Carbon key event for the currently focused plugin.  Both Unicode
  3749 // characters and "Mac encoding characters" (in the MBCS or "multibyte
  3750 // character system") are (or should be) available from aKeyEvent, but here we
  3751 // use the MCBS characters.  This is how the WebKit does things, and seems to
  3752 // be what plugins expect.
  3753 void
  3754 PluginTextInputHandler::HandleCarbonPluginKeyEvent(EventRef aKeyEvent)
  3756   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
  3758   if (Destroyed()) {
  3759     return;
  3762   NS_ASSERTION(mView, "mView must not be NULL");
  3764   nsRefPtr<nsChildView> kungFuDeathGrip(mWidget);
  3766   if ([mView pluginEventModel] == NPEventModelCocoa) {
  3767     UInt32 size;
  3768     OSStatus status =
  3769       ::GetEventParameter(aKeyEvent, kEventParamKeyUnicodes, typeUnicodeText,
  3770                           NULL, 0, &size, NULL);
  3771     NS_ENSURE_TRUE(status == noErr, );
  3773     UniChar* chars = (UniChar*)malloc(size);
  3774     NS_ENSURE_TRUE(chars, );
  3776     status = ::GetEventParameter(aKeyEvent, kEventParamKeyUnicodes,
  3777                                  typeUnicodeText, NULL, size, NULL, chars);
  3778     if (status != noErr) {
  3779       free(chars);
  3780       return;
  3783     CFStringRef text =
  3784       ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, chars,
  3785                                            (size / sizeof(UniChar)),
  3786                                            kCFAllocatorNull);
  3787     if (!text) {
  3788       free(chars);
  3789       return;
  3792     NPCocoaEvent cocoaTextEvent;
  3793     nsCocoaUtils::InitNPCocoaEvent(&cocoaTextEvent);
  3794     cocoaTextEvent.type = NPCocoaEventTextInput;
  3795     cocoaTextEvent.data.text.text = (NPNSString*)text;
  3797     WidgetPluginEvent pluginEvent(true, NS_PLUGIN_INPUT_EVENT, mWidget);
  3798     nsCocoaUtils::InitPluginEvent(pluginEvent, cocoaTextEvent);
  3799     DispatchEvent(pluginEvent);
  3801     ::CFRelease(text);
  3802     free(chars);
  3804     return;
  3807   UInt32 numCharCodes;
  3808   OSStatus status = ::GetEventParameter(aKeyEvent, kEventParamKeyMacCharCodes,
  3809                                         typeChar, NULL, 0, &numCharCodes, NULL);
  3810   NS_ENSURE_TRUE(status == noErr, );
  3812   nsAutoTArray<unsigned char, 3> charCodes;
  3813   charCodes.SetLength(numCharCodes);
  3814   status = ::GetEventParameter(aKeyEvent, kEventParamKeyMacCharCodes,
  3815                                typeChar, NULL, numCharCodes, NULL,
  3816                                charCodes.Elements());
  3817   NS_ENSURE_TRUE(status == noErr, );
  3819   UInt32 modifiers;
  3820   status = ::GetEventParameter(aKeyEvent, kEventParamKeyModifiers,
  3821                                typeUInt32, NULL, sizeof(modifiers), NULL,
  3822                                &modifiers);
  3823   NS_ENSURE_TRUE(status == noErr, );
  3825   NSUInteger cocoaModifiers = 0;
  3826   if (modifiers & shiftKey) {
  3827     cocoaModifiers |= NSShiftKeyMask;
  3829   if (modifiers & controlKey) {
  3830     cocoaModifiers |= NSControlKeyMask;
  3832   if (modifiers & optionKey) {
  3833     cocoaModifiers |= NSAlternateKeyMask;
  3835   if (modifiers & cmdKey) { // Should never happen
  3836     cocoaModifiers |= NSCommandKeyMask;
  3839   UInt32 macKeyCode;
  3840   status = ::GetEventParameter(aKeyEvent, kEventParamKeyCode,
  3841                                typeUInt32, NULL, sizeof(macKeyCode), NULL,
  3842                                &macKeyCode);
  3843   NS_ENSURE_TRUE(status == noErr, );
  3845   TISInputSourceWrapper& currentInputSource =
  3846     TISInputSourceWrapper::CurrentInputSource();
  3848   EventRef cloneEvent = ::CopyEvent(aKeyEvent);
  3849   for (uint32_t i = 0; i < numCharCodes; ++i) {
  3850     status = ::SetEventParameter(cloneEvent, kEventParamKeyMacCharCodes,
  3851                                  typeChar, 1, charCodes.Elements() + i);
  3852     NS_ENSURE_TRUE(status == noErr, );
  3854     EventRecord eventRec;
  3855     if (::ConvertEventRefToEventRecord(cloneEvent, &eventRec)) {
  3856       WidgetKeyboardEvent keydownEvent(true, NS_KEY_DOWN, mWidget);
  3857       nsCocoaUtils::InitInputEvent(keydownEvent, cocoaModifiers);
  3859       uint32_t keyCode =
  3860         currentInputSource.ComputeGeckoKeyCode(macKeyCode, ::LMGetKbdType(),
  3861                                                keydownEvent.IsMeta());
  3862       uint32_t charCode(charCodes.ElementAt(i));
  3864       keydownEvent.time = PR_IntervalNow();
  3865       keydownEvent.pluginEvent = &eventRec;
  3866       if (IsSpecialGeckoKey(macKeyCode)) {
  3867         keydownEvent.keyCode = keyCode;
  3868       } else {
  3869         // XXX This is wrong. charCode must be 0 for keydown event.
  3870         keydownEvent.charCode = charCode;
  3871         keydownEvent.isChar   = true;
  3873       DispatchEvent(keydownEvent);
  3874       if (Destroyed()) {
  3875         break;
  3880   ::ReleaseEvent(cloneEvent);
  3882   NS_OBJC_END_TRY_ABORT_BLOCK;
  3885 void
  3886 PluginTextInputHandler::ActivatePluginTSMDocument()
  3888   if (!mPluginTSMDoc) {
  3889     // Create a TSM document that supports both non-Unicode and Unicode input.
  3890     // Though ProcessPluginKeyEvent() only sends Mac char codes to
  3891     // the plugin, this makes the input window behave better when it contains
  3892     // more than one kind of input (say Hiragana and Romaji).  This is what
  3893     // the OS does when it creates a TSM document for use by an
  3894     // NSTSMInputContext class.
  3895     InterfaceTypeList supportedServices;
  3896     supportedServices[0] = kTextServiceDocumentInterfaceType;
  3897     supportedServices[1] = kUnicodeDocumentInterfaceType;
  3898     ::NewTSMDocument(2, supportedServices, &mPluginTSMDoc, 0);
  3899     // We'll need to use the "input window".
  3900     ::UseInputWindow(mPluginTSMDoc, YES);
  3901     ::ActivateTSMDocument(mPluginTSMDoc);
  3902   } else if (::TSMGetActiveDocument() != mPluginTSMDoc) {
  3903     ::ActivateTSMDocument(mPluginTSMDoc);
  3907 #endif // #ifndef __LP64__
  3909 void
  3910 PluginTextInputHandler::HandleKeyDownEventForPlugin(NSEvent* aNativeKeyEvent)
  3912   if (Destroyed()) {
  3913     return;
  3916   NS_ASSERTION(mView, "mView must not be NULL");
  3918 #ifdef __LP64__
  3920   if ([mView pluginEventModel] != NPEventModelCocoa) {
  3921     return;
  3924   ComplexTextInputPanel* ctiPanel =
  3925     [ComplexTextInputPanel sharedComplexTextInputPanel];
  3926   [ctiPanel adjustTo:mView];
  3928   // If a composition is in progress then simply let the input panel continue
  3929   // it.
  3930   if (IsInPluginComposition()) {
  3931     // Don't send key up events for key downs associated with compositions.
  3932     mIgnoreNextKeyUpEvent = true;
  3934     NSString* textString = nil;
  3935     [ctiPanel interpretKeyEvent:aNativeKeyEvent string:&textString];
  3936     if (textString) {
  3937       DispatchCocoaNPAPITextEvent(textString);
  3940     return;
  3943   // Reset complex text input request flag.
  3944   mPluginComplexTextInputRequested = false;
  3946   // Send key down event to the plugin.
  3947   WidgetPluginEvent pluginEvent(true, NS_PLUGIN_INPUT_EVENT, mWidget);
  3948   NPCocoaEvent cocoaEvent;
  3949   ConvertCocoaKeyEventToNPCocoaEvent(aNativeKeyEvent, cocoaEvent);
  3950   nsCocoaUtils::InitPluginEvent(pluginEvent, cocoaEvent);
  3951   DispatchEvent(pluginEvent);
  3952   if (Destroyed()) {
  3953     return;
  3956   // Start complex text composition if requested.
  3957   if (mPluginComplexTextInputRequested) {
  3958     // Don't send key up events for key downs associated with compositions.
  3959     mIgnoreNextKeyUpEvent = true;
  3961     NSString* textString = nil;
  3962     [ctiPanel interpretKeyEvent:aNativeKeyEvent string:&textString];
  3963     if (textString) {
  3964       DispatchCocoaNPAPITextEvent(textString);
  3968 #else // #ifdef __LP64__
  3970   bool wasInComposition = false;
  3971   if ([mView pluginEventModel] == NPEventModelCocoa) {
  3972     if (IsInPluginComposition()) {
  3973       wasInComposition = true;
  3975       // Don't send key up events for key downs associated with compositions.
  3976       mIgnoreNextKeyUpEvent = true;
  3977     } else {
  3978       // Reset complex text input request flag.
  3979       mPluginComplexTextInputRequested = false;
  3981       // Send key down event to the plugin.
  3982       WidgetPluginEvent pluginEvent(true, NS_PLUGIN_INPUT_EVENT, mWidget);
  3983       NPCocoaEvent cocoaEvent;
  3984       ConvertCocoaKeyEventToNPCocoaEvent(aNativeKeyEvent, cocoaEvent);
  3985       nsCocoaUtils::InitPluginEvent(pluginEvent, cocoaEvent);
  3986       DispatchEvent(pluginEvent);
  3987       if (Destroyed()) {
  3988         return;
  3991       // Only continue if plugin wants complex text input.
  3992       if (!mPluginComplexTextInputRequested) {
  3993         return;
  3996       // Don't send key up events for key downs associated with compositions.
  3997       mIgnoreNextKeyUpEvent = true;
  4000     // Don't send complex text input to a plugin in Cocoa event mode if
  4001     // either the Control key or the Command key is pressed -- even if the
  4002     // plugin has requested it, or we are already in IME composition.  This
  4003     // conforms to our behavior in 64-bit mode and fixes bug 619217.
  4004     NSUInteger modifierFlags = [aNativeKeyEvent modifierFlags];
  4005     if ((modifierFlags & NSControlKeyMask) ||
  4006         (modifierFlags & NSCommandKeyMask)) {
  4007       return;
  4011   // This will take care of all Carbon plugin events and also send Cocoa plugin
  4012   // text events when NSInputContext is not available (ifndef NP_NO_CARBON).
  4013   ActivatePluginTSMDocument();
  4015   // We use the active TSM document to pass a pointer to ourselves (the
  4016   // currently focused ChildView) to PluginKeyEventsHandler().  Because this
  4017   // pointer is weak, we should retain and release ourselves around the call
  4018   // to TSMProcessRawKeyEvent().
  4019   nsAutoRetainCocoaObject kungFuDeathGrip(mView);
  4020   ::TSMSetDocumentProperty(mPluginTSMDoc,
  4021                            kFocusedChildViewTSMDocPropertyTag,
  4022                            sizeof(ChildView *), &mView);
  4023   ::TSMProcessRawKeyEvent([aNativeKeyEvent _eventRef]);
  4024   ::TSMRemoveDocumentProperty(mPluginTSMDoc,
  4025                               kFocusedChildViewTSMDocPropertyTag);
  4027 #endif // #ifdef __LP64__ else
  4030 void
  4031 PluginTextInputHandler::HandleKeyUpEventForPlugin(NSEvent* aNativeKeyEvent)
  4033   if (mIgnoreNextKeyUpEvent) {
  4034     mIgnoreNextKeyUpEvent = false;
  4035     return;
  4038   if (Destroyed()) {
  4039     return;
  4042   NS_ASSERTION(mView, "mView must not be NULL");
  4044   NPEventModel eventModel = [mView pluginEventModel];
  4045   if (eventModel == NPEventModelCocoa) {
  4046     // Don't send key up events to Cocoa plugins during composition.
  4047     if (IsInPluginComposition()) {
  4048       return;
  4051     WidgetKeyboardEvent keyupEvent(true, NS_KEY_UP, mWidget);
  4052     InitKeyEvent(aNativeKeyEvent, keyupEvent);
  4053     NPCocoaEvent pluginEvent;
  4054     ConvertCocoaKeyEventToNPCocoaEvent(aNativeKeyEvent, pluginEvent);
  4055     keyupEvent.pluginEvent = &pluginEvent;
  4056     DispatchEvent(keyupEvent);
  4057     return;
  4061 bool
  4062 PluginTextInputHandler::IsInPluginComposition()
  4064   return
  4065 #ifdef __LP64__
  4066     [[ComplexTextInputPanel sharedComplexTextInputPanel] inComposition] != NO;
  4067 #else // #ifdef __LP64__
  4068     mPluginTSMInComposition;
  4069 #endif // #ifdef __LP64__ else
  4072 bool
  4073 PluginTextInputHandler::DispatchCocoaNPAPITextEvent(NSString* aString)
  4075   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
  4077   NPCocoaEvent cocoaTextEvent;
  4078   nsCocoaUtils::InitNPCocoaEvent(&cocoaTextEvent);
  4079   cocoaTextEvent.type = NPCocoaEventTextInput;
  4080   cocoaTextEvent.data.text.text = (NPNSString*)aString;
  4082   WidgetPluginEvent pluginEvent(true, NS_PLUGIN_INPUT_EVENT, mWidget);
  4083   nsCocoaUtils::InitPluginEvent(pluginEvent, cocoaTextEvent);
  4084   return DispatchEvent(pluginEvent);
  4086   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
  4090 #pragma mark -
  4093 #ifndef __LP64__
  4095 /******************************************************************************
  4097  *  PluginTextInputHandler_IMKInputSession_* implementation
  4099  ******************************************************************************/
  4101 // IMKInputSession is an undocumented class in the HIToolbox framework.  It's
  4102 // present on both Leopard and SnowLeopard, and is used at a low level to
  4103 // process IME input regardless of which high-level API is used (Text Services
  4104 // Manager or Cocoa).  It works the same way in both 32-bit and 64-bit code.
  4105 @interface NSObject (IMKInputSessionMethodSwizzling)
  4106 - (BOOL)PluginTextInputHandler_IMKInputSession_handleEvent:(EventRef)theEvent;
  4107 - (void)PluginTextInputHandler_IMKInputSession_commitComposition;
  4108 - (void)PluginTextInputHandler_IMKInputSession_finishSession;
  4109 @end
  4111 @implementation NSObject (IMKInputSessionMethodSwizzling)
  4113 - (BOOL)PluginTextInputHandler_IMKInputSession_handleEvent:(EventRef)theEvent
  4115   [self retain];
  4116   BOOL retval =
  4117     [self PluginTextInputHandler_IMKInputSession_handleEvent:theEvent];
  4118   NSUInteger retainCount = [self retainCount];
  4119   [self release];
  4120   // Return without doing anything if we've been deleted.
  4121   if (retainCount == 1) {
  4122     return retval;
  4125   NSWindow *mainWindow = [NSApp mainWindow];
  4126   NSResponder *firstResponder = [mainWindow firstResponder];
  4127   if (![firstResponder isKindOfClass:[ChildView class]]) {
  4128     return retval;
  4131   // 'charactersEntered' is the length (in bytes) of currently "marked text"
  4132   // -- text that's been entered in IME but not yet committed.  If it's
  4133   // non-zero we're composing text in an IME session; if it's zero we're
  4134   // not in an IME session.
  4135   NSInteger entered = 0;
  4136   object_getInstanceVariable(self, "charactersEntered",
  4137                              (void **) &entered);
  4138   nsIWidget* widget = [(ChildView*)firstResponder widget];
  4139   NS_ENSURE_TRUE(widget, retval);
  4140   TextInputHandler* handler =
  4141     static_cast<nsChildView*>(widget)->GetTextInputHandler();
  4142   NS_ENSURE_TRUE(handler, retval);
  4143   handler->SetPluginTSMInComposition(entered != 0);
  4145   return retval;
  4148 // This method is called whenever IME input is committed as a result of an
  4149 // "abnormal" termination -- for example when changing the keyboard focus from
  4150 // one input field to another.
  4151 - (void)PluginTextInputHandler_IMKInputSession_commitComposition
  4153   NSWindow *mainWindow = [NSApp mainWindow];
  4154   NSResponder *firstResponder = [mainWindow firstResponder];
  4155   if ([firstResponder isKindOfClass:[ChildView class]]) {
  4156     nsIWidget* widget = [(ChildView*)firstResponder widget];
  4157     if (widget) {
  4158       TextInputHandler* handler =
  4159         static_cast<nsChildView*>(widget)->GetTextInputHandler();
  4160       if (handler) {
  4161         handler->SetPluginTSMInComposition(false);
  4165   [self PluginTextInputHandler_IMKInputSession_commitComposition];
  4168 // This method is called just before we're deallocated.
  4169 - (void)PluginTextInputHandler_IMKInputSession_finishSession
  4171   NSWindow *mainWindow = [NSApp mainWindow];
  4172   NSResponder *firstResponder = [mainWindow firstResponder];
  4173   if ([firstResponder isKindOfClass:[ChildView class]]) {
  4174     nsIWidget* widget = [(ChildView*)firstResponder widget];
  4175     if (widget) {
  4176       TextInputHandler* handler =
  4177         static_cast<nsChildView*>(widget)->GetTextInputHandler();
  4178       if (handler) {
  4179         handler->SetPluginTSMInComposition(false);
  4183   [self PluginTextInputHandler_IMKInputSession_finishSession];
  4186 @end
  4188 #endif // #ifndef __LP64__
  4191 #pragma mark -
  4194 /******************************************************************************
  4196  *  TextInputHandlerBase implementation
  4198  ******************************************************************************/
  4200 int32_t TextInputHandlerBase::sSecureEventInputCount = 0;
  4202 TextInputHandlerBase::TextInputHandlerBase(nsChildView* aWidget,
  4203                                            NSView<mozView> *aNativeView) :
  4204   mWidget(aWidget)
  4206   gHandlerInstanceCount++;
  4207   mView = [aNativeView retain];
  4210 TextInputHandlerBase::~TextInputHandlerBase()
  4212   [mView release];
  4213   if (--gHandlerInstanceCount == 0) {
  4214     FinalizeCurrentInputSource();
  4218 bool
  4219 TextInputHandlerBase::OnDestroyWidget(nsChildView* aDestroyingWidget)
  4221   PR_LOG(gLog, PR_LOG_ALWAYS,
  4222     ("%p TextInputHandlerBase::OnDestroyWidget, "
  4223      "aDestroyingWidget=%p, mWidget=%p",
  4224      this, aDestroyingWidget, mWidget));
  4226   if (aDestroyingWidget != mWidget) {
  4227     return false;
  4230   mWidget = nullptr;
  4231   return true;
  4234 bool
  4235 TextInputHandlerBase::DispatchEvent(WidgetGUIEvent& aEvent)
  4237   if (aEvent.message == NS_KEY_PRESS) {
  4238     WidgetInputEvent& inputEvent = *aEvent.AsInputEvent();
  4239     if (!inputEvent.IsMeta()) {
  4240       PR_LOG(gLog, PR_LOG_ALWAYS,
  4241         ("%p TextInputHandlerBase::DispatchEvent, hiding mouse cursor", this));
  4242       [NSCursor setHiddenUntilMouseMoves:YES];
  4245   return mWidget->DispatchWindowEvent(aEvent);
  4248 void
  4249 TextInputHandlerBase::InitKeyEvent(NSEvent *aNativeKeyEvent,
  4250                                    WidgetKeyboardEvent& aKeyEvent,
  4251                                    const nsAString* aInsertString)
  4253   NS_ASSERTION(aNativeKeyEvent, "aNativeKeyEvent must not be NULL");
  4255   if (mKeyboardOverride.mOverrideEnabled) {
  4256     TISInputSourceWrapper tis;
  4257     tis.InitByLayoutID(mKeyboardOverride.mKeyboardLayout, true);
  4258     tis.InitKeyEvent(aNativeKeyEvent, aKeyEvent, aInsertString);
  4259     return;
  4261   TISInputSourceWrapper::CurrentInputSource().
  4262     InitKeyEvent(aNativeKeyEvent, aKeyEvent, aInsertString);
  4265 nsresult
  4266 TextInputHandlerBase::SynthesizeNativeKeyEvent(
  4267                         int32_t aNativeKeyboardLayout,
  4268                         int32_t aNativeKeyCode,
  4269                         uint32_t aModifierFlags,
  4270                         const nsAString& aCharacters,
  4271                         const nsAString& aUnmodifiedCharacters)
  4273   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
  4275   static const uint32_t sModifierFlagMap[][2] = {
  4276     { nsIWidget::CAPS_LOCK,       NSAlphaShiftKeyMask },
  4277     { nsIWidget::SHIFT_L,         NSShiftKeyMask      | 0x0002 },
  4278     { nsIWidget::SHIFT_R,         NSShiftKeyMask      | 0x0004 },
  4279     { nsIWidget::CTRL_L,          NSControlKeyMask    | 0x0001 },
  4280     { nsIWidget::CTRL_R,          NSControlKeyMask    | 0x2000 },
  4281     { nsIWidget::ALT_L,           NSAlternateKeyMask  | 0x0020 },
  4282     { nsIWidget::ALT_R,           NSAlternateKeyMask  | 0x0040 },
  4283     { nsIWidget::COMMAND_L,       NSCommandKeyMask    | 0x0008 },
  4284     { nsIWidget::COMMAND_R,       NSCommandKeyMask    | 0x0010 },
  4285     { nsIWidget::NUMERIC_KEY_PAD, NSNumericPadKeyMask },
  4286     { nsIWidget::HELP,            NSHelpKeyMask },
  4287     { nsIWidget::FUNCTION,        NSFunctionKeyMask }
  4288   };
  4290   uint32_t modifierFlags = 0;
  4291   for (uint32_t i = 0; i < ArrayLength(sModifierFlagMap); ++i) {
  4292     if (aModifierFlags & sModifierFlagMap[i][0]) {
  4293       modifierFlags |= sModifierFlagMap[i][1];
  4297   NSInteger windowNumber = [[mView window] windowNumber];
  4298   bool sendFlagsChangedEvent = IsModifierKey(aNativeKeyCode);
  4299   NSEventType eventType = sendFlagsChangedEvent ? NSFlagsChanged : NSKeyDown;
  4300   NSEvent* downEvent =
  4301     [NSEvent     keyEventWithType:eventType
  4302                          location:NSMakePoint(0,0)
  4303                     modifierFlags:modifierFlags
  4304                         timestamp:0
  4305                      windowNumber:windowNumber
  4306                           context:[NSGraphicsContext currentContext]
  4307                        characters:nsCocoaUtils::ToNSString(aCharacters)
  4308       charactersIgnoringModifiers:nsCocoaUtils::ToNSString(aUnmodifiedCharacters)
  4309                         isARepeat:NO
  4310                           keyCode:aNativeKeyCode];
  4312   NSEvent* upEvent = sendFlagsChangedEvent ?
  4313     nil : nsCocoaUtils::MakeNewCocoaEventWithType(NSKeyUp, downEvent);
  4315   if (downEvent && (sendFlagsChangedEvent || upEvent)) {
  4316     KeyboardLayoutOverride currentLayout = mKeyboardOverride;
  4317     mKeyboardOverride.mKeyboardLayout = aNativeKeyboardLayout;
  4318     mKeyboardOverride.mOverrideEnabled = true;
  4319     [NSApp sendEvent:downEvent];
  4320     if (upEvent) {
  4321       [NSApp sendEvent:upEvent];
  4323     // processKeyDownEvent and keyUp block exceptions so we're sure to
  4324     // reach here to restore mKeyboardOverride
  4325     mKeyboardOverride = currentLayout;
  4328   return NS_OK;
  4330   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
  4333 NSInteger
  4334 TextInputHandlerBase::GetWindowLevel()
  4336   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
  4338   PR_LOG(gLog, PR_LOG_ALWAYS,
  4339     ("%p TextInputHandlerBase::GetWindowLevel, Destryoed()=%s",
  4340      this, TrueOrFalse(Destroyed())));
  4342   if (Destroyed()) {
  4343     return NSNormalWindowLevel;
  4346   // When an <input> element on a XUL <panel> is focused, the actual focused view
  4347   // is the panel's parent view (mView). But the editor is displayed on the
  4348   // popped-up widget's view (editorView).  We want the latter's window level.
  4349   NSView<mozView>* editorView = mWidget->GetEditorView();
  4350   NS_ENSURE_TRUE(editorView, NSNormalWindowLevel);
  4351   NSInteger windowLevel = [[editorView window] level];
  4353   PR_LOG(gLog, PR_LOG_ALWAYS,
  4354     ("%p TextInputHandlerBase::GetWindowLevel, windowLevel=%s (%X)",
  4355      this, GetWindowLevelName(windowLevel), windowLevel));
  4357   return windowLevel;
  4359   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSNormalWindowLevel);
  4362 NS_IMETHODIMP
  4363 TextInputHandlerBase::AttachNativeKeyEvent(WidgetKeyboardEvent& aKeyEvent)
  4365   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
  4367   // Don't try to replace a native event if one already exists.
  4368   // OS X doesn't have an OS modifier, can't make a native event.
  4369   if (aKeyEvent.mNativeKeyEvent || aKeyEvent.modifiers & MODIFIER_OS) {
  4370     return NS_OK;
  4373   PR_LOG(gLog, PR_LOG_ALWAYS,
  4374     ("%p TextInputHandlerBase::AttachNativeKeyEvent, key=0x%X, char=0x%X, "
  4375      "mod=0x%X", this, aKeyEvent.keyCode, aKeyEvent.charCode,
  4376      aKeyEvent.modifiers));
  4378   NSEventType eventType;
  4379   if (aKeyEvent.message == NS_KEY_UP) {
  4380     eventType = NSKeyUp;
  4381   } else {
  4382     eventType = NSKeyDown;
  4385   static const uint32_t sModifierFlagMap[][2] = {
  4386     { MODIFIER_SHIFT,    NSShiftKeyMask },
  4387     { MODIFIER_CONTROL,  NSControlKeyMask },
  4388     { MODIFIER_ALT,      NSAlternateKeyMask },
  4389     { MODIFIER_ALTGRAPH, NSAlternateKeyMask },
  4390     { MODIFIER_META,     NSCommandKeyMask },
  4391     { MODIFIER_CAPSLOCK, NSAlphaShiftKeyMask },
  4392     { MODIFIER_NUMLOCK,  NSNumericPadKeyMask }
  4393   };
  4395   NSUInteger modifierFlags = 0;
  4396   for (uint32_t i = 0; i < ArrayLength(sModifierFlagMap); ++i) {
  4397     if (aKeyEvent.modifiers & sModifierFlagMap[i][0]) {
  4398       modifierFlags |= sModifierFlagMap[i][1];
  4402   NSInteger windowNumber = [[mView window] windowNumber];
  4404   NSString* characters;
  4405   if (aKeyEvent.charCode) {
  4406     characters = [NSString stringWithCharacters:
  4407       reinterpret_cast<const unichar*>(&(aKeyEvent.charCode)) length:1];
  4408   } else {
  4409     uint32_t cocoaCharCode =
  4410       nsCocoaUtils::ConvertGeckoKeyCodeToMacCharCode(aKeyEvent.keyCode);
  4411     characters = [NSString stringWithCharacters:
  4412       reinterpret_cast<const unichar*>(&cocoaCharCode) length:1];
  4415   aKeyEvent.mNativeKeyEvent =
  4416     [NSEvent     keyEventWithType:eventType
  4417                          location:NSMakePoint(0,0)
  4418                     modifierFlags:modifierFlags
  4419                         timestamp:0
  4420                      windowNumber:windowNumber
  4421                           context:[NSGraphicsContext currentContext]
  4422                        characters:characters
  4423       charactersIgnoringModifiers:characters
  4424                         isARepeat:NO
  4425                           keyCode:0]; // Native key code not currently needed
  4427   return NS_OK;
  4429   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
  4432 bool
  4433 TextInputHandlerBase::SetSelection(NSRange& aRange)
  4435   MOZ_ASSERT(!Destroyed());
  4437   nsRefPtr<TextInputHandlerBase> kungFuDeathGrip(this);
  4438   WidgetSelectionEvent selectionEvent(true, NS_SELECTION_SET, mWidget);
  4439   selectionEvent.mOffset = aRange.location;
  4440   selectionEvent.mLength = aRange.length;
  4441   selectionEvent.mReversed = false;
  4442   selectionEvent.mExpandToClusterBoundary = false;
  4443   DispatchEvent(selectionEvent);
  4444   NS_ENSURE_TRUE(selectionEvent.mSucceeded, false);
  4445   return !Destroyed();
  4448 /* static */ bool
  4449 TextInputHandlerBase::IsPrintableChar(char16_t aChar)
  4451   return (aChar >= 0x20 && aChar <= 0x7E) || aChar >= 0xA0;
  4455 /* static */ bool
  4456 TextInputHandlerBase::IsSpecialGeckoKey(UInt32 aNativeKeyCode)
  4458   // this table is used to determine which keys are special and should not
  4459   // generate a charCode
  4460   switch (aNativeKeyCode) {
  4461     // modifiers - we don't get separate events for these yet
  4462     case kVK_Escape:
  4463     case kVK_Shift:
  4464     case kVK_RightShift:
  4465     case kVK_Command:
  4466     case kVK_RightCommand:
  4467     case kVK_CapsLock:
  4468     case kVK_Control:
  4469     case kVK_RightControl:
  4470     case kVK_Option:
  4471     case kVK_RightOption:
  4472     case kVK_ANSI_KeypadClear:
  4473     case kVK_Function:
  4475     // function keys
  4476     case kVK_F1:
  4477     case kVK_F2:
  4478     case kVK_F3:
  4479     case kVK_F4:
  4480     case kVK_F5:
  4481     case kVK_F6:
  4482     case kVK_F7:
  4483     case kVK_F8:
  4484     case kVK_F9:
  4485     case kVK_F10:
  4486     case kVK_F11:
  4487     case kVK_F12:
  4488     case kVK_PC_Pause:
  4489     case kVK_PC_ScrollLock:
  4490     case kVK_PC_PrintScreen:
  4491     case kVK_F16:
  4492     case kVK_F17:
  4493     case kVK_F18:
  4494     case kVK_F19:
  4496     case kVK_PC_Insert:
  4497     case kVK_PC_Delete:
  4498     case kVK_Tab:
  4499     case kVK_PC_Backspace:
  4500     case kVK_PC_ContextMenu:
  4502     case kVK_JIS_Eisu:
  4503     case kVK_JIS_Kana:
  4505     case kVK_Home:
  4506     case kVK_End:
  4507     case kVK_PageUp:
  4508     case kVK_PageDown:
  4509     case kVK_LeftArrow:
  4510     case kVK_RightArrow:
  4511     case kVK_UpArrow:
  4512     case kVK_DownArrow:
  4513     case kVK_Return:
  4514     case kVK_ANSI_KeypadEnter:
  4515     case kVK_Powerbook_KeypadEnter:
  4516       return true;
  4518   return false;
  4521 /* static */ bool
  4522 TextInputHandlerBase::IsNormalCharInputtingEvent(
  4523                         const WidgetKeyboardEvent& aKeyEvent)
  4525   // this is not character inputting event, simply.
  4526   if (!aKeyEvent.isChar || !aKeyEvent.charCode || aKeyEvent.IsMeta()) {
  4527     return false;
  4529   // if this is unicode char inputting event, we don't need to check
  4530   // ctrl/alt/command keys
  4531   if (aKeyEvent.charCode > 0x7F) {
  4532     return true;
  4534   // ASCII chars should be inputted without ctrl/alt/command keys
  4535   return !aKeyEvent.IsControl() && !aKeyEvent.IsAlt();
  4538 /* static */ bool
  4539 TextInputHandlerBase::IsModifierKey(UInt32 aNativeKeyCode)
  4541   switch (aNativeKeyCode) {
  4542     case kVK_CapsLock:
  4543     case kVK_RightCommand:
  4544     case kVK_Command:
  4545     case kVK_Shift:
  4546     case kVK_Option:
  4547     case kVK_Control:
  4548     case kVK_RightShift:
  4549     case kVK_RightOption:
  4550     case kVK_RightControl:
  4551     case kVK_Function:
  4552       return true;
  4554   return false;
  4557 /* static */ void
  4558 TextInputHandlerBase::EnableSecureEventInput()
  4560   sSecureEventInputCount++;
  4561   ::EnableSecureEventInput();
  4564 /* static */ void
  4565 TextInputHandlerBase::DisableSecureEventInput()
  4567   if (!sSecureEventInputCount) {
  4568     return;
  4570   sSecureEventInputCount--;
  4571   ::DisableSecureEventInput();
  4574 /* static */ bool
  4575 TextInputHandlerBase::IsSecureEventInputEnabled()
  4577   NS_ASSERTION(!!sSecureEventInputCount == !!::IsSecureEventInputEnabled(),
  4578                "Some other process has enabled secure event input");
  4579   return !!sSecureEventInputCount;
  4582 /* static */ void
  4583 TextInputHandlerBase::EnsureSecureEventInputDisabled()
  4585   while (sSecureEventInputCount) {
  4586     TextInputHandlerBase::DisableSecureEventInput();

mercurial