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/. */
6
7#include "TextInputHandler.h"
8
9#include "mozilla/Logging.h"
10
11#include "mozilla/ArrayUtils.h"
12#include "mozilla/AutoRestore.h"
13#include "mozilla/MiscEvents.h"
14#include "mozilla/MouseEvents.h"
15#include "mozilla/TextEventDispatcher.h"
16#include "mozilla/TextEvents.h"
17
18#include "nsChildView.h"
19#include "nsObjCExceptions.h"
20#include "nsBidiUtils.h"
21#include "nsToolkit.h"
22#include "nsCocoaUtils.h"
23#include "WidgetUtils.h"
24#include "nsPrintfCString.h"
25#include "ComplexTextInputPanel.h"
26
27using namespace mozilla;
28using namespace mozilla::widget;
29
30LazyLogModule gLog("TextInputHandlerWidgets");
31
32static const char*
33OnOrOff(bool aBool)
34{
35  return aBool ? "ON" : "off";
36}
37
38static const char*
39TrueOrFalse(bool aBool)
40{
41  return aBool ? "TRUE" : "FALSE";
42}
43
44static const char*
45GetKeyNameForNativeKeyCode(unsigned short aNativeKeyCode)
46{
47  switch (aNativeKeyCode) {
48    case kVK_Escape:              return "Escape";
49    case kVK_RightCommand:        return "Right-Command";
50    case kVK_Command:             return "Command";
51    case kVK_Shift:               return "Shift";
52    case kVK_CapsLock:            return "CapsLock";
53    case kVK_Option:              return "Option";
54    case kVK_Control:             return "Control";
55    case kVK_RightShift:          return "Right-Shift";
56    case kVK_RightOption:         return "Right-Option";
57    case kVK_RightControl:        return "Right-Control";
58    case kVK_ANSI_KeypadClear:    return "Clear";
59
60    case kVK_F1:                  return "F1";
61    case kVK_F2:                  return "F2";
62    case kVK_F3:                  return "F3";
63    case kVK_F4:                  return "F4";
64    case kVK_F5:                  return "F5";
65    case kVK_F6:                  return "F6";
66    case kVK_F7:                  return "F7";
67    case kVK_F8:                  return "F8";
68    case kVK_F9:                  return "F9";
69    case kVK_F10:                 return "F10";
70    case kVK_F11:                 return "F11";
71    case kVK_F12:                 return "F12";
72    case kVK_F13:                 return "F13/PrintScreen";
73    case kVK_F14:                 return "F14/ScrollLock";
74    case kVK_F15:                 return "F15/Pause";
75
76    case kVK_ANSI_Keypad0:        return "NumPad-0";
77    case kVK_ANSI_Keypad1:        return "NumPad-1";
78    case kVK_ANSI_Keypad2:        return "NumPad-2";
79    case kVK_ANSI_Keypad3:        return "NumPad-3";
80    case kVK_ANSI_Keypad4:        return "NumPad-4";
81    case kVK_ANSI_Keypad5:        return "NumPad-5";
82    case kVK_ANSI_Keypad6:        return "NumPad-6";
83    case kVK_ANSI_Keypad7:        return "NumPad-7";
84    case kVK_ANSI_Keypad8:        return "NumPad-8";
85    case kVK_ANSI_Keypad9:        return "NumPad-9";
86
87    case kVK_ANSI_KeypadMultiply: return "NumPad-*";
88    case kVK_ANSI_KeypadPlus:     return "NumPad-+";
89    case kVK_ANSI_KeypadMinus:    return "NumPad--";
90    case kVK_ANSI_KeypadDecimal:  return "NumPad-.";
91    case kVK_ANSI_KeypadDivide:   return "NumPad-/";
92    case kVK_ANSI_KeypadEquals:   return "NumPad-=";
93    case kVK_ANSI_KeypadEnter:    return "NumPad-Enter";
94    case kVK_Return:              return "Return";
95    case kVK_Powerbook_KeypadEnter: return "NumPad-EnterOnPowerBook";
96
97    case kVK_PC_Insert:           return "Insert/Help";
98    case kVK_PC_Delete:           return "Delete";
99    case kVK_Tab:                 return "Tab";
100    case kVK_PC_Backspace:        return "Backspace";
101    case kVK_Home:                return "Home";
102    case kVK_End:                 return "End";
103    case kVK_PageUp:              return "PageUp";
104    case kVK_PageDown:            return "PageDown";
105    case kVK_LeftArrow:           return "LeftArrow";
106    case kVK_RightArrow:          return "RightArrow";
107    case kVK_UpArrow:             return "UpArrow";
108    case kVK_DownArrow:           return "DownArrow";
109    case kVK_PC_ContextMenu:      return "ContextMenu";
110
111    case kVK_Function:            return "Function";
112    case kVK_VolumeUp:            return "VolumeUp";
113    case kVK_VolumeDown:          return "VolumeDown";
114    case kVK_Mute:                return "Mute";
115
116    case kVK_ISO_Section:         return "ISO_Section";
117
118    case kVK_JIS_Yen:             return "JIS_Yen";
119    case kVK_JIS_Underscore:      return "JIS_Underscore";
120    case kVK_JIS_KeypadComma:     return "JIS_KeypadComma";
121    case kVK_JIS_Eisu:            return "JIS_Eisu";
122    case kVK_JIS_Kana:            return "JIS_Kana";
123
124    case kVK_ANSI_A:              return "A";
125    case kVK_ANSI_B:              return "B";
126    case kVK_ANSI_C:              return "C";
127    case kVK_ANSI_D:              return "D";
128    case kVK_ANSI_E:              return "E";
129    case kVK_ANSI_F:              return "F";
130    case kVK_ANSI_G:              return "G";
131    case kVK_ANSI_H:              return "H";
132    case kVK_ANSI_I:              return "I";
133    case kVK_ANSI_J:              return "J";
134    case kVK_ANSI_K:              return "K";
135    case kVK_ANSI_L:              return "L";
136    case kVK_ANSI_M:              return "M";
137    case kVK_ANSI_N:              return "N";
138    case kVK_ANSI_O:              return "O";
139    case kVK_ANSI_P:              return "P";
140    case kVK_ANSI_Q:              return "Q";
141    case kVK_ANSI_R:              return "R";
142    case kVK_ANSI_S:              return "S";
143    case kVK_ANSI_T:              return "T";
144    case kVK_ANSI_U:              return "U";
145    case kVK_ANSI_V:              return "V";
146    case kVK_ANSI_W:              return "W";
147    case kVK_ANSI_X:              return "X";
148    case kVK_ANSI_Y:              return "Y";
149    case kVK_ANSI_Z:              return "Z";
150
151    case kVK_ANSI_1:              return "1";
152    case kVK_ANSI_2:              return "2";
153    case kVK_ANSI_3:              return "3";
154    case kVK_ANSI_4:              return "4";
155    case kVK_ANSI_5:              return "5";
156    case kVK_ANSI_6:              return "6";
157    case kVK_ANSI_7:              return "7";
158    case kVK_ANSI_8:              return "8";
159    case kVK_ANSI_9:              return "9";
160    case kVK_ANSI_0:              return "0";
161    case kVK_ANSI_Equal:          return "Equal";
162    case kVK_ANSI_Minus:          return "Minus";
163    case kVK_ANSI_RightBracket:   return "RightBracket";
164    case kVK_ANSI_LeftBracket:    return "LeftBracket";
165    case kVK_ANSI_Quote:          return "Quote";
166    case kVK_ANSI_Semicolon:      return "Semicolon";
167    case kVK_ANSI_Backslash:      return "Backslash";
168    case kVK_ANSI_Comma:          return "Comma";
169    case kVK_ANSI_Slash:          return "Slash";
170    case kVK_ANSI_Period:         return "Period";
171    case kVK_ANSI_Grave:          return "Grave";
172
173    default:                      return "undefined";
174  }
175}
176
177static const char*
178GetCharacters(const NSString* aString)
179{
180  nsAutoString str;
181  nsCocoaUtils::GetStringForNSString(aString, str);
182  if (str.IsEmpty()) {
183    return "";
184  }
185
186  nsAutoString escapedStr;
187  for (uint32_t i = 0; i < str.Length(); i++) {
188    char16_t ch = str[i];
189    if (ch < 0x20) {
190      nsPrintfCString utf8str("(U+%04X)", ch);
191      escapedStr += NS_ConvertUTF8toUTF16(utf8str);
192    } else if (ch <= 0x7E) {
193      escapedStr += ch;
194    } else {
195      nsPrintfCString utf8str("(U+%04X)", ch);
196      escapedStr += ch;
197      escapedStr += NS_ConvertUTF8toUTF16(utf8str);
198    }
199  }
200
201  // the result will be freed automatically by cocoa.
202  NSString* result = nsCocoaUtils::ToNSString(escapedStr);
203  return [result UTF8String];
204}
205
206static const char*
207GetCharacters(const CFStringRef aString)
208{
209  const NSString* str = reinterpret_cast<const NSString*>(aString);
210  return GetCharacters(str);
211}
212
213static const char*
214GetNativeKeyEventType(NSEvent* aNativeEvent)
215{
216  switch ([aNativeEvent type]) {
217    case NSKeyDown:      return "NSKeyDown";
218    case NSKeyUp:        return "NSKeyUp";
219    case NSFlagsChanged: return "NSFlagsChanged";
220    default:             return "not key event";
221  }
222}
223
224static const char*
225GetGeckoKeyEventType(const WidgetEvent& aEvent)
226{
227  switch (aEvent.mMessage) {
228    case eKeyDown:       return "eKeyDown";
229    case eKeyUp:         return "eKeyUp";
230    case eKeyPress:      return "eKeyPress";
231    default:             return "not key event";
232  }
233}
234
235static const char*
236GetWindowLevelName(NSInteger aWindowLevel)
237{
238  switch (aWindowLevel) {
239    case kCGBaseWindowLevelKey:
240      return "kCGBaseWindowLevelKey (NSNormalWindowLevel)";
241    case kCGMinimumWindowLevelKey:
242      return "kCGMinimumWindowLevelKey";
243    case kCGDesktopWindowLevelKey:
244      return "kCGDesktopWindowLevelKey";
245    case kCGBackstopMenuLevelKey:
246      return "kCGBackstopMenuLevelKey";
247    case kCGNormalWindowLevelKey:
248      return "kCGNormalWindowLevelKey";
249    case kCGFloatingWindowLevelKey:
250      return "kCGFloatingWindowLevelKey (NSFloatingWindowLevel)";
251    case kCGTornOffMenuWindowLevelKey:
252      return "kCGTornOffMenuWindowLevelKey (NSSubmenuWindowLevel, NSTornOffMenuWindowLevel)";
253    case kCGDockWindowLevelKey:
254      return "kCGDockWindowLevelKey (NSDockWindowLevel)";
255    case kCGMainMenuWindowLevelKey:
256      return "kCGMainMenuWindowLevelKey (NSMainMenuWindowLevel)";
257    case kCGStatusWindowLevelKey:
258      return "kCGStatusWindowLevelKey (NSStatusWindowLevel)";
259    case kCGModalPanelWindowLevelKey:
260      return "kCGModalPanelWindowLevelKey (NSModalPanelWindowLevel)";
261    case kCGPopUpMenuWindowLevelKey:
262      return "kCGPopUpMenuWindowLevelKey (NSPopUpMenuWindowLevel)";
263    case kCGDraggingWindowLevelKey:
264      return "kCGDraggingWindowLevelKey";
265    case kCGScreenSaverWindowLevelKey:
266      return "kCGScreenSaverWindowLevelKey (NSScreenSaverWindowLevel)";
267    case kCGMaximumWindowLevelKey:
268      return "kCGMaximumWindowLevelKey";
269    case kCGOverlayWindowLevelKey:
270      return "kCGOverlayWindowLevelKey";
271    case kCGHelpWindowLevelKey:
272      return "kCGHelpWindowLevelKey";
273    case kCGUtilityWindowLevelKey:
274      return "kCGUtilityWindowLevelKey";
275    case kCGDesktopIconWindowLevelKey:
276      return "kCGDesktopIconWindowLevelKey";
277    case kCGCursorWindowLevelKey:
278      return "kCGCursorWindowLevelKey";
279    case kCGNumberOfWindowLevelKeys:
280      return "kCGNumberOfWindowLevelKeys";
281    default:
282      return "unknown window level";
283  }
284}
285
286static bool
287IsControlChar(uint32_t aCharCode)
288{
289  return aCharCode < ' ' || aCharCode == 0x7F;
290}
291
292static uint32_t gHandlerInstanceCount = 0;
293
294static void
295EnsureToLogAllKeyboardLayoutsAndIMEs()
296{
297  static bool sDone = false;
298  if (!sDone) {
299    sDone = true;
300    TextInputHandler::DebugPrintAllKeyboardLayouts();
301    IMEInputHandler::DebugPrintAllIMEModes();
302  }
303}
304
305#pragma mark -
306
307
308/******************************************************************************
309 *
310 *  TISInputSourceWrapper implementation
311 *
312 ******************************************************************************/
313
314TISInputSourceWrapper* TISInputSourceWrapper::sCurrentInputSource = nullptr;
315
316// static
317TISInputSourceWrapper&
318TISInputSourceWrapper::CurrentInputSource()
319{
320  if (!sCurrentInputSource) {
321    sCurrentInputSource = new TISInputSourceWrapper();
322  }
323  if (!sCurrentInputSource->IsInitializedByCurrentInputSource()) {
324    sCurrentInputSource->InitByCurrentInputSource();
325  }
326  return *sCurrentInputSource;
327}
328
329// static
330void
331TISInputSourceWrapper::Shutdown()
332{
333  if (!sCurrentInputSource) {
334    return;
335  }
336  sCurrentInputSource->Clear();
337  delete sCurrentInputSource;
338  sCurrentInputSource = nullptr;
339}
340
341bool
342TISInputSourceWrapper::TranslateToString(UInt32 aKeyCode, UInt32 aModifiers,
343                                         UInt32 aKbType, nsAString &aStr)
344{
345  aStr.Truncate();
346
347  const UCKeyboardLayout* UCKey = GetUCKeyboardLayout();
348
349  MOZ_LOG(gLog, LogLevel::Info,
350    ("%p TISInputSourceWrapper::TranslateToString, aKeyCode=0x%X, "
351     "aModifiers=0x%X, aKbType=0x%X UCKey=%p\n    "
352     "Shift: %s, Ctrl: %s, Opt: %s, Cmd: %s, CapsLock: %s, NumLock: %s",
353     this, aKeyCode, aModifiers, aKbType, UCKey,
354     OnOrOff(aModifiers & shiftKey), OnOrOff(aModifiers & controlKey),
355     OnOrOff(aModifiers & optionKey), OnOrOff(aModifiers & cmdKey),
356     OnOrOff(aModifiers & alphaLock),
357     OnOrOff(aModifiers & kEventKeyModifierNumLockMask)));
358
359  NS_ENSURE_TRUE(UCKey, false);
360
361  UInt32 deadKeyState = 0;
362  UniCharCount len;
363  UniChar chars[5];
364  OSStatus err = ::UCKeyTranslate(UCKey, aKeyCode,
365                                  kUCKeyActionDown, aModifiers >> 8,
366                                  aKbType, kUCKeyTranslateNoDeadKeysMask,
367                                  &deadKeyState, 5, &len, chars);
368
369  MOZ_LOG(gLog, LogLevel::Info,
370    ("%p TISInputSourceWrapper::TranslateToString, err=0x%X, len=%llu",
371     this, err, len));
372
373  NS_ENSURE_TRUE(err == noErr, false);
374  if (len == 0) {
375    return true;
376  }
377  NS_ENSURE_TRUE(EnsureStringLength(aStr, len), false);
378  NS_ASSERTION(sizeof(char16_t) == sizeof(UniChar),
379               "size of char16_t and size of UniChar are different");
380  memcpy(aStr.BeginWriting(), chars, len * sizeof(char16_t));
381
382  MOZ_LOG(gLog, LogLevel::Info,
383    ("%p TISInputSourceWrapper::TranslateToString, aStr=\"%s\"",
384     this, NS_ConvertUTF16toUTF8(aStr).get()));
385
386  return true;
387}
388
389uint32_t
390TISInputSourceWrapper::TranslateToChar(UInt32 aKeyCode, UInt32 aModifiers,
391                                       UInt32 aKbType)
392{
393  nsAutoString str;
394  if (!TranslateToString(aKeyCode, aModifiers, aKbType, str) ||
395      str.Length() != 1) {
396    return 0;
397  }
398  return static_cast<uint32_t>(str.CharAt(0));
399}
400
401void
402TISInputSourceWrapper::InitByInputSourceID(const char* aID)
403{
404  Clear();
405  if (!aID)
406    return;
407
408  CFStringRef idstr = ::CFStringCreateWithCString(kCFAllocatorDefault, aID,
409                                                  kCFStringEncodingASCII);
410  InitByInputSourceID(idstr);
411  ::CFRelease(idstr);
412}
413
414void
415TISInputSourceWrapper::InitByInputSourceID(const nsAFlatString &aID)
416{
417  Clear();
418  if (aID.IsEmpty())
419    return;
420  CFStringRef idstr = ::CFStringCreateWithCharacters(kCFAllocatorDefault,
421                                                     reinterpret_cast<const UniChar*>(aID.get()),
422                                                     aID.Length());
423  InitByInputSourceID(idstr);
424  ::CFRelease(idstr);
425}
426
427void
428TISInputSourceWrapper::InitByInputSourceID(const CFStringRef aID)
429{
430  Clear();
431  if (!aID)
432    return;
433  const void* keys[] = { kTISPropertyInputSourceID };
434  const void* values[] = { aID };
435  CFDictionaryRef filter =
436  ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
437  NS_ASSERTION(filter, "failed to create the filter");
438  mInputSourceList = ::TISCreateInputSourceList(filter, true);
439  ::CFRelease(filter);
440  if (::CFArrayGetCount(mInputSourceList) > 0) {
441    mInputSource = static_cast<TISInputSourceRef>(
442      const_cast<void *>(::CFArrayGetValueAtIndex(mInputSourceList, 0)));
443    if (IsKeyboardLayout()) {
444      mKeyboardLayout = mInputSource;
445    }
446  }
447}
448
449void
450TISInputSourceWrapper::InitByLayoutID(SInt32 aLayoutID,
451                                      bool aOverrideKeyboard)
452{
453  // NOTE: Doument new layout IDs in TextInputHandler.h when you add ones.
454  switch (aLayoutID) {
455    case 0:
456      InitByInputSourceID("com.apple.keylayout.US");
457      break;
458    case 1:
459      InitByInputSourceID("com.apple.keylayout.Greek");
460      break;
461    case 2:
462      InitByInputSourceID("com.apple.keylayout.German");
463      break;
464    case 3:
465      InitByInputSourceID("com.apple.keylayout.Swedish-Pro");
466      break;
467    case 4:
468      InitByInputSourceID("com.apple.keylayout.DVORAK-QWERTYCMD");
469      break;
470    case 5:
471      InitByInputSourceID("com.apple.keylayout.Thai");
472      break;
473    case 6:
474      InitByInputSourceID("com.apple.keylayout.Arabic");
475      break;
476    case 7:
477      InitByInputSourceID("com.apple.keylayout.ArabicPC");
478      break;
479    case 8:
480      InitByInputSourceID("com.apple.keylayout.French");
481      break;
482    case 9:
483      InitByInputSourceID("com.apple.keylayout.Hebrew");
484      break;
485    case 10:
486      InitByInputSourceID("com.apple.keylayout.Lithuanian");
487      break;
488    case 11:
489      InitByInputSourceID("com.apple.keylayout.Norwegian");
490      break;
491    case 12:
492      InitByInputSourceID("com.apple.keylayout.Spanish");
493      break;
494    default:
495      Clear();
496      break;
497  }
498  mOverrideKeyboard = aOverrideKeyboard;
499}
500
501void
502TISInputSourceWrapper::InitByCurrentInputSource()
503{
504  Clear();
505  mInputSource = ::TISCopyCurrentKeyboardInputSource();
506  mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride();
507  if (!mKeyboardLayout) {
508    mKeyboardLayout = ::TISCopyCurrentKeyboardLayoutInputSource();
509  }
510  // If this causes composition, the current keyboard layout may input non-ASCII
511  // characters such as Japanese Kana characters or Hangul characters.
512  // However, we need to set ASCII characters to DOM key events for consistency
513  // with other platforms.
514  if (IsOpenedIMEMode()) {
515    TISInputSourceWrapper tis(mKeyboardLayout);
516    if (!tis.IsASCIICapable()) {
517      mKeyboardLayout =
518        ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
519    }
520  }
521}
522
523void
524TISInputSourceWrapper::InitByCurrentKeyboardLayout()
525{
526  Clear();
527  mInputSource = ::TISCopyCurrentKeyboardLayoutInputSource();
528  mKeyboardLayout = mInputSource;
529}
530
531void
532TISInputSourceWrapper::InitByCurrentASCIICapableInputSource()
533{
534  Clear();
535  mInputSource = ::TISCopyCurrentASCIICapableKeyboardInputSource();
536  mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride();
537  if (mKeyboardLayout) {
538    TISInputSourceWrapper tis(mKeyboardLayout);
539    if (!tis.IsASCIICapable()) {
540      mKeyboardLayout = nullptr;
541    }
542  }
543  if (!mKeyboardLayout) {
544    mKeyboardLayout =
545      ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
546  }
547}
548
549void
550TISInputSourceWrapper::InitByCurrentASCIICapableKeyboardLayout()
551{
552  Clear();
553  mInputSource = ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
554  mKeyboardLayout = mInputSource;
555}
556
557void
558TISInputSourceWrapper::InitByCurrentInputMethodKeyboardLayoutOverride()
559{
560  Clear();
561  mInputSource = ::TISCopyInputMethodKeyboardLayoutOverride();
562  mKeyboardLayout = mInputSource;
563}
564
565void
566TISInputSourceWrapper::InitByTISInputSourceRef(TISInputSourceRef aInputSource)
567{
568  Clear();
569  mInputSource = aInputSource;
570  if (IsKeyboardLayout()) {
571    mKeyboardLayout = mInputSource;
572  }
573}
574
575void
576TISInputSourceWrapper::InitByLanguage(CFStringRef aLanguage)
577{
578  Clear();
579  mInputSource = ::TISCopyInputSourceForLanguage(aLanguage);
580  if (IsKeyboardLayout()) {
581    mKeyboardLayout = mInputSource;
582  }
583}
584
585const UCKeyboardLayout*
586TISInputSourceWrapper::GetUCKeyboardLayout()
587{
588  NS_ENSURE_TRUE(mKeyboardLayout, nullptr);
589  if (mUCKeyboardLayout) {
590    return mUCKeyboardLayout;
591  }
592  CFDataRef uchr = static_cast<CFDataRef>(
593    ::TISGetInputSourceProperty(mKeyboardLayout,
594                                kTISPropertyUnicodeKeyLayoutData));
595
596  // We should be always able to get the layout here.
597  NS_ENSURE_TRUE(uchr, nullptr);
598  mUCKeyboardLayout =
599    reinterpret_cast<const UCKeyboardLayout*>(CFDataGetBytePtr(uchr));
600  return mUCKeyboardLayout;
601}
602
603bool
604TISInputSourceWrapper::GetBoolProperty(const CFStringRef aKey)
605{
606  CFBooleanRef ret = static_cast<CFBooleanRef>(
607    ::TISGetInputSourceProperty(mInputSource, aKey));
608  return ::CFBooleanGetValue(ret);
609}
610
611bool
612TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey,
613                                         CFStringRef &aStr)
614{
615  aStr = static_cast<CFStringRef>(
616    ::TISGetInputSourceProperty(mInputSource, aKey));
617  return aStr != nullptr;
618}
619
620bool
621TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey,
622                                         nsAString &aStr)
623{
624  CFStringRef str;
625  GetStringProperty(aKey, str);
626  nsCocoaUtils::GetStringForNSString((const NSString*)str, aStr);
627  return !aStr.IsEmpty();
628}
629
630bool
631TISInputSourceWrapper::IsOpenedIMEMode()
632{
633  NS_ENSURE_TRUE(mInputSource, false);
634  if (!IsIMEMode())
635    return false;
636  return !IsASCIICapable();
637}
638
639bool
640TISInputSourceWrapper::IsIMEMode()
641{
642  NS_ENSURE_TRUE(mInputSource, false);
643  CFStringRef str;
644  GetInputSourceType(str);
645  NS_ENSURE_TRUE(str, false);
646  return ::CFStringCompare(kTISTypeKeyboardInputMode,
647                           str, 0) == kCFCompareEqualTo;
648}
649
650bool
651TISInputSourceWrapper::IsKeyboardLayout()
652{
653  NS_ENSURE_TRUE(mInputSource, false);
654  CFStringRef str;
655  GetInputSourceType(str);
656  NS_ENSURE_TRUE(str, false);
657  return ::CFStringCompare(kTISTypeKeyboardLayout,
658                           str, 0) == kCFCompareEqualTo;
659}
660
661bool
662TISInputSourceWrapper::GetLanguageList(CFArrayRef &aLanguageList)
663{
664  NS_ENSURE_TRUE(mInputSource, false);
665  aLanguageList = static_cast<CFArrayRef>(
666    ::TISGetInputSourceProperty(mInputSource,
667                                kTISPropertyInputSourceLanguages));
668  return aLanguageList != nullptr;
669}
670
671bool
672TISInputSourceWrapper::GetPrimaryLanguage(CFStringRef &aPrimaryLanguage)
673{
674  NS_ENSURE_TRUE(mInputSource, false);
675  CFArrayRef langList;
676  NS_ENSURE_TRUE(GetLanguageList(langList), false);
677  if (::CFArrayGetCount(langList) == 0)
678    return false;
679  aPrimaryLanguage =
680    static_cast<CFStringRef>(::CFArrayGetValueAtIndex(langList, 0));
681  return aPrimaryLanguage != nullptr;
682}
683
684bool
685TISInputSourceWrapper::GetPrimaryLanguage(nsAString &aPrimaryLanguage)
686{
687  NS_ENSURE_TRUE(mInputSource, false);
688  CFStringRef primaryLanguage;
689  NS_ENSURE_TRUE(GetPrimaryLanguage(primaryLanguage), false);
690  nsCocoaUtils::GetStringForNSString((const NSString*)primaryLanguage,
691                                     aPrimaryLanguage);
692  return !aPrimaryLanguage.IsEmpty();
693}
694
695bool
696TISInputSourceWrapper::IsForRTLLanguage()
697{
698  if (mIsRTL < 0) {
699    // Get the input character of the 'A' key of ANSI keyboard layout.
700    nsAutoString str;
701    bool ret = TranslateToString(kVK_ANSI_A, 0, eKbdType_ANSI, str);
702    NS_ENSURE_TRUE(ret, ret);
703    char16_t ch = str.IsEmpty() ? char16_t(0) : str.CharAt(0);
704    mIsRTL = UCS2_CHAR_IS_BIDI(ch);
705  }
706  return mIsRTL != 0;
707}
708
709bool
710TISInputSourceWrapper::IsInitializedByCurrentInputSource()
711{
712  return mInputSource == ::TISCopyCurrentKeyboardInputSource();
713}
714
715void
716TISInputSourceWrapper::Select()
717{
718  if (!mInputSource)
719    return;
720  ::TISSelectInputSource(mInputSource);
721}
722
723void
724TISInputSourceWrapper::Clear()
725{
726  // Clear() is always called when TISInputSourceWrappper is created.
727  EnsureToLogAllKeyboardLayoutsAndIMEs();
728
729  if (mInputSourceList) {
730    ::CFRelease(mInputSourceList);
731  }
732  mInputSourceList = nullptr;
733  mInputSource = nullptr;
734  mKeyboardLayout = nullptr;
735  mIsRTL = -1;
736  mUCKeyboardLayout = nullptr;
737  mOverrideKeyboard = false;
738}
739
740bool
741TISInputSourceWrapper::IsPrintableKeyEvent(NSEvent* aNativeKeyEvent) const
742{
743  UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
744
745  bool isPrintableKey = !TextInputHandler::IsSpecialGeckoKey(nativeKeyCode);
746  if (isPrintableKey &&
747      [aNativeKeyEvent type] != NSKeyDown &&
748      [aNativeKeyEvent type] != NSKeyUp) {
749    NS_WARNING("Why the printable key doesn't cause NSKeyDown or NSKeyUp?");
750    isPrintableKey = false;
751  }
752  return isPrintableKey;
753}
754
755UInt32
756TISInputSourceWrapper::GetKbdType() const
757{
758  // If a keyboard layout override is set, we also need to force the keyboard
759  // type to something ANSI to avoid test failures on machines with JIS
760  // keyboards (since the pair of keyboard layout and physical keyboard type
761  // form the actual key layout).  This assumes that the test setting the
762  // override was written assuming an ANSI keyboard.
763  return mOverrideKeyboard ? eKbdType_ANSI : ::LMGetKbdType();
764}
765
766void
767TISInputSourceWrapper::ComputeInsertStringForCharCode(
768                         NSEvent* aNativeKeyEvent,
769                         const WidgetKeyboardEvent& aKeyEvent,
770                         const nsAString* aInsertString,
771                         nsAString& aResult)
772{
773  if (aInsertString) {
774    // If the caller expects that the aInsertString will be input, we shouldn't
775    // change it.
776    aResult = *aInsertString;
777  } else if (IsPrintableKeyEvent(aNativeKeyEvent)) {
778    // If IME is open, [aNativeKeyEvent characters] may be a character
779    // which will be appended to the composition string.  However, especially,
780    // while IME is disabled, most users and developers expect the key event
781    // works as IME closed.  So, we should compute the aResult with
782    // the ASCII capable keyboard layout.
783    // NOTE: Such keyboard layouts typically change the layout to its ASCII
784    //       capable layout when Command key is pressed.  And we don't worry
785    //       when Control key is pressed too because it causes inputting
786    //       control characters.
787    // Additionally, if the key event doesn't input any text, the event may be
788    // dead key event.  In this case, the charCode value should be the dead
789    // character.
790    UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
791    if ((!aKeyEvent.IsMeta() && !aKeyEvent.IsControl() && IsOpenedIMEMode()) ||
792        ![[aNativeKeyEvent characters] length]) {
793      UInt32 state =
794        nsCocoaUtils::ConvertToCarbonModifier([aNativeKeyEvent modifierFlags]);
795      uint32_t ch = TranslateToChar(nativeKeyCode, state, GetKbdType());
796      if (ch) {
797        aResult = ch;
798      }
799    } else {
800      // If the caller isn't sure what string will be input, let's use
801      // characters of NSEvent.
802      nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], aResult);
803    }
804
805    // If control key is pressed and the eventChars is a non-printable control
806    // character, we should convert it to ASCII alphabet.
807    if (aKeyEvent.IsControl() &&
808        !aResult.IsEmpty() && aResult[0] <= char16_t(26)) {
809      aResult = (aKeyEvent.IsShift() ^ aKeyEvent.IsCapsLocked()) ?
810        static_cast<char16_t>(aResult[0] + ('A' - 1)) :
811        static_cast<char16_t>(aResult[0] + ('a' - 1));
812    }
813    // If Meta key is pressed, it may cause to switch the keyboard layout like
814    // Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY.
815    else if (aKeyEvent.IsMeta() &&
816             !(aKeyEvent.IsControl() || aKeyEvent.IsAlt())) {
817      UInt32 kbType = GetKbdType();
818      UInt32 numLockState =
819        aKeyEvent.IsNumLocked() ? kEventKeyModifierNumLockMask : 0;
820      UInt32 capsLockState = aKeyEvent.IsCapsLocked() ? alphaLock : 0;
821      UInt32 shiftState = aKeyEvent.IsShift() ? shiftKey : 0;
822      uint32_t uncmdedChar =
823        TranslateToChar(nativeKeyCode, numLockState, kbType);
824      uint32_t cmdedChar =
825        TranslateToChar(nativeKeyCode, cmdKey | numLockState, kbType);
826      // If we can make a good guess at the characters that the user would
827      // expect this key combination to produce (with and without Shift) then
828      // use those characters.  This also corrects for CapsLock.
829      uint32_t ch = 0;
830      if (uncmdedChar == cmdedChar) {
831        // The characters produced with Command seem similar to those without
832        // Command.
833        ch = TranslateToChar(nativeKeyCode,
834                             shiftState | capsLockState | numLockState, kbType);
835      } else {
836        TISInputSourceWrapper USLayout("com.apple.keylayout.US");
837        uint32_t uncmdedUSChar =
838          USLayout.TranslateToChar(nativeKeyCode, numLockState, kbType);
839        // If it looks like characters from US keyboard layout when Command key
840        // is pressed, we should compute a character in the layout.
841        if (uncmdedUSChar == cmdedChar) {
842          ch = USLayout.TranslateToChar(nativeKeyCode,
843                          shiftState | capsLockState | numLockState, kbType);
844        }
845      }
846
847      // If there is a more preferred character for the commanded key event,
848      // we should use it.
849      if (ch) {
850        aResult = ch;
851      }
852    }
853  }
854
855  // Remove control characters which shouldn't be inputted on editor.
856  // XXX Currently, we don't find any cases inserting control characters with
857  //     printable character.  So, just checking first character is enough.
858  if (!aResult.IsEmpty() && IsControlChar(aResult[0])) {
859    aResult.Truncate();
860  }
861}
862
863void
864TISInputSourceWrapper::InitKeyEvent(NSEvent *aNativeKeyEvent,
865                                    WidgetKeyboardEvent& aKeyEvent,
866                                    const nsAString *aInsertString)
867{
868  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
869
870  MOZ_LOG(gLog, LogLevel::Info,
871    ("%p TISInputSourceWrapper::InitKeyEvent, aNativeKeyEvent=%p, "
872     "aKeyEvent.mMessage=%s, aInsertString=%p, IsOpenedIMEMode()=%s",
873     this, aNativeKeyEvent, GetGeckoKeyEventType(aKeyEvent), aInsertString,
874     TrueOrFalse(IsOpenedIMEMode())));
875
876  NS_ENSURE_TRUE(aNativeKeyEvent, );
877
878  nsCocoaUtils::InitInputEvent(aKeyEvent, aNativeKeyEvent);
879
880  // This is used only while dispatching the event (which is a synchronous
881  // call), so there is no need to retain and release this data.
882  aKeyEvent.mNativeKeyEvent = aNativeKeyEvent;
883
884  // Fill in fields used for Cocoa NPAPI plugins
885  if ([aNativeKeyEvent type] == NSKeyDown ||
886      [aNativeKeyEvent type] == NSKeyUp) {
887    aKeyEvent.mNativeKeyCode = [aNativeKeyEvent keyCode];
888    aKeyEvent.mNativeModifierFlags = [aNativeKeyEvent modifierFlags];
889    nsAutoString nativeChars;
890    nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], nativeChars);
891    aKeyEvent.mNativeCharacters.Assign(nativeChars);
892    nsAutoString nativeCharsIgnoringModifiers;
893    nsCocoaUtils::GetStringForNSString([aNativeKeyEvent charactersIgnoringModifiers], nativeCharsIgnoringModifiers);
894    aKeyEvent.mNativeCharactersIgnoringModifiers.Assign(nativeCharsIgnoringModifiers);
895  } else if ([aNativeKeyEvent type] == NSFlagsChanged) {
896    aKeyEvent.mNativeKeyCode = [aNativeKeyEvent keyCode];
897    aKeyEvent.mNativeModifierFlags = [aNativeKeyEvent modifierFlags];
898  }
899
900  aKeyEvent.mRefPoint = LayoutDeviceIntPoint(0, 0);
901  aKeyEvent.mIsChar = false; // XXX not used in XP level
902
903  UInt32 kbType = GetKbdType();
904  UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
905
906  aKeyEvent.mKeyCode =
907    ComputeGeckoKeyCode(nativeKeyCode, kbType, aKeyEvent.IsMeta());
908
909  switch (nativeKeyCode) {
910    case kVK_Command:
911    case kVK_Shift:
912    case kVK_Option:
913    case kVK_Control:
914      aKeyEvent.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT;
915      break;
916
917    case kVK_RightCommand:
918    case kVK_RightShift:
919    case kVK_RightOption:
920    case kVK_RightControl:
921      aKeyEvent.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT;
922      break;
923
924    case kVK_ANSI_Keypad0:
925    case kVK_ANSI_Keypad1:
926    case kVK_ANSI_Keypad2:
927    case kVK_ANSI_Keypad3:
928    case kVK_ANSI_Keypad4:
929    case kVK_ANSI_Keypad5:
930    case kVK_ANSI_Keypad6:
931    case kVK_ANSI_Keypad7:
932    case kVK_ANSI_Keypad8:
933    case kVK_ANSI_Keypad9:
934    case kVK_ANSI_KeypadMultiply:
935    case kVK_ANSI_KeypadPlus:
936    case kVK_ANSI_KeypadMinus:
937    case kVK_ANSI_KeypadDecimal:
938    case kVK_ANSI_KeypadDivide:
939    case kVK_ANSI_KeypadEquals:
940    case kVK_ANSI_KeypadEnter:
941    case kVK_JIS_KeypadComma:
942    case kVK_Powerbook_KeypadEnter:
943      aKeyEvent.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD;
944      break;
945
946    default:
947      aKeyEvent.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD;
948      break;
949  }
950
951  aKeyEvent.mIsRepeat =
952    ([aNativeKeyEvent type] == NSKeyDown) ? [aNativeKeyEvent isARepeat] : false;
953
954  MOZ_LOG(gLog, LogLevel::Info,
955    ("%p TISInputSourceWrapper::InitKeyEvent, "
956     "shift=%s, ctrl=%s, alt=%s, meta=%s",
957     this, OnOrOff(aKeyEvent.IsShift()), OnOrOff(aKeyEvent.IsControl()),
958     OnOrOff(aKeyEvent.IsAlt()), OnOrOff(aKeyEvent.IsMeta())));
959
960  if (IsPrintableKeyEvent(aNativeKeyEvent)) {
961    aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
962    // If insertText calls this method, let's use the string.
963    if (aInsertString && !aInsertString->IsEmpty() &&
964        !IsControlChar((*aInsertString)[0])) {
965      aKeyEvent.mKeyValue = *aInsertString;
966    }
967    // If meta key is pressed, the printable key layout may be switched from
968    // non-ASCII capable layout to ASCII capable, or from Dvorak to QWERTY.
969    // KeyboardEvent.key value should be the switched layout's character.
970    else if (aKeyEvent.IsMeta()) {
971      nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters],
972                                         aKeyEvent.mKeyValue);
973    }
974    // If control key is pressed, some keys may produce printable character via
975    // [aNativeKeyEvent characters].  Otherwise, translate input character of
976    // the key without control key.
977    else if (aKeyEvent.IsControl()) {
978      nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters],
979                                         aKeyEvent.mKeyValue);
980      if (aKeyEvent.mKeyValue.IsEmpty() ||
981          IsControlChar(aKeyEvent.mKeyValue[0])) {
982        NSUInteger cocoaState =
983          [aNativeKeyEvent modifierFlags] & ~NSControlKeyMask;
984        UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState);
985        aKeyEvent.mKeyValue =
986          TranslateToChar(nativeKeyCode, carbonState, kbType);
987      }
988    }
989    // Otherwise, KeyboardEvent.key expose
990    // [aNativeKeyEvent characters] value.  However, if IME is open and the
991    // keyboard layout isn't ASCII capable, exposing the non-ASCII character
992    // doesn't match with other platform's behavior.  For the compatibility
993    // with other platform's Gecko, we need to set a translated character.
994    else if (IsOpenedIMEMode()) {
995      UInt32 state =
996        nsCocoaUtils::ConvertToCarbonModifier([aNativeKeyEvent modifierFlags]);
997      aKeyEvent.mKeyValue = TranslateToChar(nativeKeyCode, state, kbType);
998    } else {
999      nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters],
1000                                         aKeyEvent.mKeyValue);
1001      // If the key value is empty, the event may be a dead key event.
1002      // If TranslateToChar() returns non-zero value, that means that
1003      // the key may input a character with different dead key state.
1004      if (aKeyEvent.mKeyValue.IsEmpty()) {
1005        NSUInteger cocoaState = [aNativeKeyEvent modifierFlags];
1006        UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState);
1007        if (TranslateToChar(nativeKeyCode, carbonState, kbType)) {
1008          aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_Dead;
1009        }
1010      }
1011    }
1012
1013    // Last resort.  If .key value becomes empty string, we should use
1014    // charactersIgnoringModifiers, if it's available.
1015    if (aKeyEvent.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING &&
1016        (aKeyEvent.mKeyValue.IsEmpty() ||
1017         IsControlChar(aKeyEvent.mKeyValue[0]))) {
1018      nsCocoaUtils::GetStringForNSString(
1019        [aNativeKeyEvent charactersIgnoringModifiers], aKeyEvent.mKeyValue);
1020      // But don't expose it if it's a control character.
1021      if (!aKeyEvent.mKeyValue.IsEmpty() &&
1022          IsControlChar(aKeyEvent.mKeyValue[0])) {
1023        aKeyEvent.mKeyValue.Truncate();
1024      }
1025    }
1026  } else {
1027    // Compute the key for non-printable keys and some special printable keys.
1028    aKeyEvent.mKeyNameIndex = ComputeGeckoKeyNameIndex(nativeKeyCode);
1029  }
1030
1031  aKeyEvent.mCodeNameIndex = ComputeGeckoCodeNameIndex(nativeKeyCode);
1032  MOZ_ASSERT(aKeyEvent.mCodeNameIndex != CODE_NAME_INDEX_USE_STRING);
1033
1034  NS_OBJC_END_TRY_ABORT_BLOCK
1035}
1036
1037void
1038TISInputSourceWrapper::WillDispatchKeyboardEvent(
1039                         NSEvent* aNativeKeyEvent,
1040                         const nsAString* aInsertString,
1041                         WidgetKeyboardEvent& aKeyEvent)
1042{
1043  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1044
1045  // Nothing to do here if the native key event is neither NSKeyDown nor
1046  // NSKeyUp because accessing [aNativeKeyEvent characters] causes throwing
1047  // an exception.
1048  if ([aNativeKeyEvent type] != NSKeyDown &&
1049      [aNativeKeyEvent type] != NSKeyUp) {
1050    return;
1051  }
1052
1053  UInt32 kbType = GetKbdType();
1054
1055  if (MOZ_LOG_TEST(gLog, LogLevel::Info)) {
1056    nsAutoString chars;
1057    nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], chars);
1058    NS_ConvertUTF16toUTF8 utf8Chars(chars);
1059    char16_t uniChar = static_cast<char16_t>(aKeyEvent.mCharCode);
1060    MOZ_LOG(gLog, LogLevel::Info,
1061      ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
1062       "aNativeKeyEvent=%p, [aNativeKeyEvent characters]=\"%s\", "
1063       "aKeyEvent={ mMessage=%s, mCharCode=0x%X(%s) }, kbType=0x%X, "
1064       "IsOpenedIMEMode()=%s",
1065       this, aNativeKeyEvent, utf8Chars.get(),
1066       GetGeckoKeyEventType(aKeyEvent), aKeyEvent.mCharCode,
1067       uniChar ? NS_ConvertUTF16toUTF8(&uniChar, 1).get() : "",
1068       kbType, TrueOrFalse(IsOpenedIMEMode())));
1069  }
1070
1071  nsAutoString insertStringForCharCode;
1072  ComputeInsertStringForCharCode(aNativeKeyEvent, aKeyEvent, aInsertString,
1073                                 insertStringForCharCode);
1074
1075  // The mCharCode was set from mKeyValue. However, for example, when Ctrl key
1076  // is pressed, its value should indicate an ASCII character for backward
1077  // compatibility rather than inputting character without the modifiers.
1078  // Therefore, we need to modify mCharCode value here.
1079  uint32_t charCode =
1080    insertStringForCharCode.IsEmpty() ? 0 : insertStringForCharCode[0];
1081  aKeyEvent.SetCharCode(charCode);
1082  // this is not a special key  XXX not used in XP
1083  aKeyEvent.mIsChar = (aKeyEvent.mMessage == eKeyPress);
1084
1085  MOZ_LOG(gLog, LogLevel::Info,
1086    ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
1087     "aKeyEvent.mKeyCode=0x%X, aKeyEvent.mCharCode=0x%X",
1088     this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode));
1089
1090  TISInputSourceWrapper USLayout("com.apple.keylayout.US");
1091  bool isRomanKeyboardLayout = IsASCIICapable();
1092
1093  UInt32 key = [aNativeKeyEvent keyCode];
1094
1095  // Caps lock and num lock modifier state:
1096  UInt32 lockState = 0;
1097  if ([aNativeKeyEvent modifierFlags] & NSAlphaShiftKeyMask) {
1098    lockState |= alphaLock;
1099  }
1100  if ([aNativeKeyEvent modifierFlags] & NSNumericPadKeyMask) {
1101    lockState |= kEventKeyModifierNumLockMask;
1102  }
1103
1104  MOZ_LOG(gLog, LogLevel::Info,
1105    ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
1106     "isRomanKeyboardLayout=%s, key=0x%X",
1107     this, TrueOrFalse(isRomanKeyboardLayout), kbType, key));
1108
1109  nsString str;
1110
1111  // normal chars
1112  uint32_t unshiftedChar = TranslateToChar(key, lockState, kbType);
1113  UInt32 shiftLockMod = shiftKey | lockState;
1114  uint32_t shiftedChar = TranslateToChar(key, shiftLockMod, kbType);
1115
1116  // characters generated with Cmd key
1117  // XXX we should remove CapsLock state, which changes characters from
1118  //     Latin to Cyrillic with Russian layout on 10.4 only when Cmd key
1119  //     is pressed.
1120  UInt32 numState = (lockState & ~alphaLock); // only num lock state
1121  uint32_t uncmdedChar = TranslateToChar(key, numState, kbType);
1122  UInt32 shiftNumMod = numState | shiftKey;
1123  uint32_t uncmdedShiftChar = TranslateToChar(key, shiftNumMod, kbType);
1124  uint32_t uncmdedUSChar = USLayout.TranslateToChar(key, numState, kbType);
1125  UInt32 cmdNumMod = cmdKey | numState;
1126  uint32_t cmdedChar = TranslateToChar(key, cmdNumMod, kbType);
1127  UInt32 cmdShiftNumMod = shiftKey | cmdNumMod;
1128  uint32_t cmdedShiftChar = TranslateToChar(key, cmdShiftNumMod, kbType);
1129
1130  // Is the keyboard layout changed by Cmd key?
1131  // E.g., Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY.
1132  bool isCmdSwitchLayout = uncmdedChar != cmdedChar;
1133  // Is the keyboard layout for Latin, but Cmd key switches the layout?
1134  // I.e., Dvorak-QWERTY
1135  bool isDvorakQWERTY = isCmdSwitchLayout && isRomanKeyboardLayout;
1136
1137  // If the current keyboard is not Dvorak-QWERTY or Cmd is not pressed,
1138  // we should append unshiftedChar and shiftedChar for handling the
1139  // normal characters.  These are the characters that the user is most
1140  // likely to associate with this key.
1141  if ((unshiftedChar || shiftedChar) &&
1142      (!aKeyEvent.IsMeta() || !isDvorakQWERTY)) {
1143    AlternativeCharCode altCharCodes(unshiftedChar, shiftedChar);
1144    aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes);
1145  }
1146  MOZ_LOG(gLog, LogLevel::Info,
1147    ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
1148     "aKeyEvent.isMeta=%s, isDvorakQWERTY=%s, "
1149     "unshiftedChar=U+%X, shiftedChar=U+%X",
1150     this, OnOrOff(aKeyEvent.IsMeta()), TrueOrFalse(isDvorakQWERTY),
1151     unshiftedChar, shiftedChar));
1152
1153  // Most keyboard layouts provide the same characters in the NSEvents
1154  // with Command+Shift as with Command.  However, with Command+Shift we
1155  // want the character on the second level.  e.g. With a US QWERTY
1156  // layout, we want "?" when the "/","?" key is pressed with
1157  // Command+Shift.
1158
1159  // On a German layout, the OS gives us '/' with Cmd+Shift+SS(eszett)
1160  // even though Cmd+SS is 'SS' and Shift+'SS' is '?'.  This '/' seems
1161  // like a hack to make the Cmd+"?" event look the same as the Cmd+"?"
1162  // event on a US keyboard.  The user thinks they are typing Cmd+"?", so
1163  // we'll prefer the "?" character, replacing mCharCode with shiftedChar
1164  // when Shift is pressed.  However, in case there is a layout where the
1165  // character unique to Cmd+Shift is the character that the user expects,
1166  // we'll send it as an alternative char.
1167  bool hasCmdShiftOnlyChar =
1168    cmdedChar != cmdedShiftChar && uncmdedShiftChar != cmdedShiftChar;
1169  uint32_t originalCmdedShiftChar = cmdedShiftChar;
1170
1171  // If we can make a good guess at the characters that the user would
1172  // expect this key combination to produce (with and without Shift) then
1173  // use those characters.  This also corrects for CapsLock, which was
1174  // ignored above.
1175  if (!isCmdSwitchLayout) {
1176    // The characters produced with Command seem similar to those without
1177    // Command.
1178    if (unshiftedChar) {
1179      cmdedChar = unshiftedChar;
1180    }
1181    if (shiftedChar) {
1182      cmdedShiftChar = shiftedChar;
1183    }
1184  } else if (uncmdedUSChar == cmdedChar) {
1185    // It looks like characters from a US layout are provided when Command
1186    // is down.
1187    uint32_t ch = USLayout.TranslateToChar(key, lockState, kbType);
1188    if (ch) {
1189      cmdedChar = ch;
1190    }
1191    ch = USLayout.TranslateToChar(key, shiftLockMod, kbType);
1192    if (ch) {
1193      cmdedShiftChar = ch;
1194    }
1195  }
1196
1197  // If the current keyboard layout is switched by the Cmd key,
1198  // we should append cmdedChar and shiftedCmdChar that are
1199  // Latin char for the key.
1200  // If the keyboard layout is Dvorak-QWERTY, we should append them only when
1201  // command key is pressed because when command key isn't pressed, uncmded
1202  // chars have been appended already.
1203  if ((cmdedChar || cmdedShiftChar) && isCmdSwitchLayout &&
1204      (aKeyEvent.IsMeta() || !isDvorakQWERTY)) {
1205    AlternativeCharCode altCharCodes(cmdedChar, cmdedShiftChar);
1206    aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes);
1207  }
1208  MOZ_LOG(gLog, LogLevel::Info,
1209    ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
1210     "hasCmdShiftOnlyChar=%s, isCmdSwitchLayout=%s, isDvorakQWERTY=%s, "
1211     "cmdedChar=U+%X, cmdedShiftChar=U+%X",
1212     this, TrueOrFalse(hasCmdShiftOnlyChar), TrueOrFalse(isDvorakQWERTY),
1213     TrueOrFalse(isDvorakQWERTY), cmdedChar, cmdedShiftChar));
1214  // Special case for 'SS' key of German layout. See the comment of
1215  // hasCmdShiftOnlyChar definition for the detail.
1216  if (hasCmdShiftOnlyChar && originalCmdedShiftChar) {
1217    AlternativeCharCode altCharCodes(0, originalCmdedShiftChar);
1218    aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes);
1219  }
1220  MOZ_LOG(gLog, LogLevel::Info,
1221    ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
1222     "hasCmdShiftOnlyChar=%s, originalCmdedShiftChar=U+%X",
1223     this, TrueOrFalse(hasCmdShiftOnlyChar), originalCmdedShiftChar));
1224
1225  NS_OBJC_END_TRY_ABORT_BLOCK
1226}
1227
1228uint32_t
1229TISInputSourceWrapper::ComputeGeckoKeyCode(UInt32 aNativeKeyCode,
1230                                           UInt32 aKbType,
1231                                           bool aCmdIsPressed)
1232{
1233  MOZ_LOG(gLog, LogLevel::Info,
1234    ("%p TISInputSourceWrapper::ComputeGeckoKeyCode, aNativeKeyCode=0x%X, "
1235     "aKbType=0x%X, aCmdIsPressed=%s, IsOpenedIMEMode()=%s, "
1236     "IsASCIICapable()=%s",
1237     this, aNativeKeyCode, aKbType, TrueOrFalse(aCmdIsPressed),
1238     TrueOrFalse(IsOpenedIMEMode()), TrueOrFalse(IsASCIICapable())));
1239
1240  switch (aNativeKeyCode) {
1241    case kVK_Space:             return NS_VK_SPACE;
1242    case kVK_Escape:            return NS_VK_ESCAPE;
1243
1244    // modifiers
1245    case kVK_RightCommand:
1246    case kVK_Command:           return NS_VK_META;
1247    case kVK_RightShift:
1248    case kVK_Shift:             return NS_VK_SHIFT;
1249    case kVK_CapsLock:          return NS_VK_CAPS_LOCK;
1250    case kVK_RightControl:
1251    case kVK_Control:           return NS_VK_CONTROL;
1252    case kVK_RightOption:
1253    case kVK_Option:            return NS_VK_ALT;
1254
1255    case kVK_ANSI_KeypadClear:  return NS_VK_CLEAR;
1256
1257    // function keys
1258    case kVK_F1:                return NS_VK_F1;
1259    case kVK_F2:                return NS_VK_F2;
1260    case kVK_F3:                return NS_VK_F3;
1261    case kVK_F4:                return NS_VK_F4;
1262    case kVK_F5:                return NS_VK_F5;
1263    case kVK_F6:                return NS_VK_F6;
1264    case kVK_F7:                return NS_VK_F7;
1265    case kVK_F8:                return NS_VK_F8;
1266    case kVK_F9:                return NS_VK_F9;
1267    case kVK_F10:               return NS_VK_F10;
1268    case kVK_F11:               return NS_VK_F11;
1269    case kVK_F12:               return NS_VK_F12;
1270    // case kVK_F13:               return NS_VK_F13;  // clash with the 3 below
1271    // case kVK_F14:               return NS_VK_F14;
1272    // case kVK_F15:               return NS_VK_F15;
1273    case kVK_F16:               return NS_VK_F16;
1274    case kVK_F17:               return NS_VK_F17;
1275    case kVK_F18:               return NS_VK_F18;
1276    case kVK_F19:               return NS_VK_F19;
1277
1278    case kVK_PC_Pause:          return NS_VK_PAUSE;
1279    case kVK_PC_ScrollLock:     return NS_VK_SCROLL_LOCK;
1280    case kVK_PC_PrintScreen:    return NS_VK_PRINTSCREEN;
1281
1282    // keypad
1283    case kVK_ANSI_Keypad0:      return NS_VK_NUMPAD0;
1284    case kVK_ANSI_Keypad1:      return NS_VK_NUMPAD1;
1285    case kVK_ANSI_Keypad2:      return NS_VK_NUMPAD2;
1286    case kVK_ANSI_Keypad3:      return NS_VK_NUMPAD3;
1287    case kVK_ANSI_Keypad4:      return NS_VK_NUMPAD4;
1288    case kVK_ANSI_Keypad5:      return NS_VK_NUMPAD5;
1289    case kVK_ANSI_Keypad6:      return NS_VK_NUMPAD6;
1290    case kVK_ANSI_Keypad7:      return NS_VK_NUMPAD7;
1291    case kVK_ANSI_Keypad8:      return NS_VK_NUMPAD8;
1292    case kVK_ANSI_Keypad9:      return NS_VK_NUMPAD9;
1293
1294    case kVK_ANSI_KeypadMultiply: return NS_VK_MULTIPLY;
1295    case kVK_ANSI_KeypadPlus:     return NS_VK_ADD;
1296    case kVK_ANSI_KeypadMinus:    return NS_VK_SUBTRACT;
1297    case kVK_ANSI_KeypadDecimal:  return NS_VK_DECIMAL;
1298    case kVK_ANSI_KeypadDivide:   return NS_VK_DIVIDE;
1299
1300    case kVK_JIS_KeypadComma:   return NS_VK_SEPARATOR;
1301
1302    // IME keys
1303    case kVK_JIS_Eisu:          return NS_VK_EISU;
1304    case kVK_JIS_Kana:          return NS_VK_KANA;
1305
1306    // these may clash with forward delete and help
1307    case kVK_PC_Insert:         return NS_VK_INSERT;
1308    case kVK_PC_Delete:         return NS_VK_DELETE;
1309
1310    case kVK_PC_Backspace:      return NS_VK_BACK;
1311    case kVK_Tab:               return NS_VK_TAB;
1312
1313    case kVK_Home:              return NS_VK_HOME;
1314    case kVK_End:               return NS_VK_END;
1315
1316    case kVK_PageUp:            return NS_VK_PAGE_UP;
1317    case kVK_PageDown:          return NS_VK_PAGE_DOWN;
1318
1319    case kVK_LeftArrow:         return NS_VK_LEFT;
1320    case kVK_RightArrow:        return NS_VK_RIGHT;
1321    case kVK_UpArrow:           return NS_VK_UP;
1322    case kVK_DownArrow:         return NS_VK_DOWN;
1323
1324    case kVK_PC_ContextMenu:    return NS_VK_CONTEXT_MENU;
1325
1326    case kVK_ANSI_1:            return NS_VK_1;
1327    case kVK_ANSI_2:            return NS_VK_2;
1328    case kVK_ANSI_3:            return NS_VK_3;
1329    case kVK_ANSI_4:            return NS_VK_4;
1330    case kVK_ANSI_5:            return NS_VK_5;
1331    case kVK_ANSI_6:            return NS_VK_6;
1332    case kVK_ANSI_7:            return NS_VK_7;
1333    case kVK_ANSI_8:            return NS_VK_8;
1334    case kVK_ANSI_9:            return NS_VK_9;
1335    case kVK_ANSI_0:            return NS_VK_0;
1336
1337    case kVK_ANSI_KeypadEnter:
1338    case kVK_Return:
1339    case kVK_Powerbook_KeypadEnter: return NS_VK_RETURN;
1340  }
1341
1342  // If Cmd key is pressed, that causes switching keyboard layout temporarily.
1343  // E.g., Dvorak-QWERTY.  Therefore, if Cmd key is pressed, we should honor it.
1344  UInt32 modifiers = aCmdIsPressed ? cmdKey : 0;
1345
1346  uint32_t charCode = TranslateToChar(aNativeKeyCode, modifiers, aKbType);
1347
1348  // Special case for Mac.  Mac inputs Yen sign (U+00A5) directly instead of
1349  // Back slash (U+005C).  We should return NS_VK_BACK_SLASH for compatibility
1350  // with other platforms.
1351  // XXX How about Won sign (U+20A9) which has same problem as Yen sign?
1352  if (charCode == 0x00A5) {
1353    return NS_VK_BACK_SLASH;
1354  }
1355
1356  uint32_t keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode);
1357  if (keyCode) {
1358    return keyCode;
1359  }
1360
1361  // If the unshifed char isn't an ASCII character, use shifted char.
1362  charCode = TranslateToChar(aNativeKeyCode, modifiers | shiftKey, aKbType);
1363  keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode);
1364  if (keyCode) {
1365    return keyCode;
1366  }
1367
1368  // If this is ASCII capable, give up to compute it.
1369  if (IsASCIICapable()) {
1370    return 0;
1371  }
1372
1373  // Retry with ASCII capable keyboard layout.
1374  TISInputSourceWrapper currentKeyboardLayout;
1375  currentKeyboardLayout.InitByCurrentASCIICapableKeyboardLayout();
1376  NS_ENSURE_TRUE(mInputSource != currentKeyboardLayout.mInputSource, 0);
1377  keyCode = currentKeyboardLayout.ComputeGeckoKeyCode(aNativeKeyCode, aKbType,
1378                                                      aCmdIsPressed);
1379
1380  // However, if keyCode isn't for an alphabet keys or a numeric key, we should
1381  // ignore it.  For example, comma key of Thai layout is same as close-square-
1382  // bracket key of US layout and an unicode character key of Thai layout is
1383  // same as comma key of US layout.  If we return NS_VK_COMMA for latter key,
1384  // web application developers cannot distinguish with the former key.
1385  return ((keyCode >= NS_VK_A && keyCode <= NS_VK_Z) ||
1386          (keyCode >= NS_VK_0 && keyCode <= NS_VK_9)) ? keyCode : 0;
1387}
1388
1389// static
1390KeyNameIndex
1391TISInputSourceWrapper::ComputeGeckoKeyNameIndex(UInt32 aNativeKeyCode)
1392{
1393  // NOTE:
1394  //   When unsupported keys like Convert, Nonconvert of Japanese keyboard is
1395  //   pressed:
1396  //     on 10.6.x, 'A' key event is fired (and also actually 'a' is inserted).
1397  //     on 10.7.x, Nothing happens.
1398  //     on 10.8.x, Nothing happens.
1399  //     on 10.9.x, FlagsChanged event is fired with keyCode 0xFF.
1400  switch (aNativeKeyCode) {
1401
1402#define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
1403    case aNativeKey: return aKeyNameIndex;
1404
1405#include "NativeKeyToDOMKeyName.h"
1406
1407#undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
1408
1409    default:
1410      return KEY_NAME_INDEX_Unidentified;
1411  }
1412}
1413
1414// static
1415CodeNameIndex
1416TISInputSourceWrapper::ComputeGeckoCodeNameIndex(UInt32 aNativeKeyCode)
1417{
1418  switch (aNativeKeyCode) {
1419
1420#define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \
1421    case aNativeKey: return aCodeNameIndex;
1422
1423#include "NativeKeyToDOMCodeName.h"
1424
1425#undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX
1426
1427    default:
1428      return CODE_NAME_INDEX_UNKNOWN;
1429  }
1430}
1431
1432
1433#pragma mark -
1434
1435
1436/******************************************************************************
1437 *
1438 *  TextInputHandler implementation (static methods)
1439 *
1440 ******************************************************************************/
1441
1442NSUInteger TextInputHandler::sLastModifierState = 0;
1443
1444// static
1445CFArrayRef
1446TextInputHandler::CreateAllKeyboardLayoutList()
1447{
1448  const void* keys[] = { kTISPropertyInputSourceType };
1449  const void* values[] = { kTISTypeKeyboardLayout };
1450  CFDictionaryRef filter =
1451    ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
1452  NS_ASSERTION(filter, "failed to create the filter");
1453  CFArrayRef list = ::TISCreateInputSourceList(filter, true);
1454  ::CFRelease(filter);
1455  return list;
1456}
1457
1458// static
1459void
1460TextInputHandler::DebugPrintAllKeyboardLayouts()
1461{
1462  if (MOZ_LOG_TEST(gLog, LogLevel::Info)) {
1463    CFArrayRef list = CreateAllKeyboardLayoutList();
1464    MOZ_LOG(gLog, LogLevel::Info, ("Keyboard layout configuration:"));
1465    CFIndex idx = ::CFArrayGetCount(list);
1466    TISInputSourceWrapper tis;
1467    for (CFIndex i = 0; i < idx; ++i) {
1468      TISInputSourceRef inputSource = static_cast<TISInputSourceRef>(
1469        const_cast<void *>(::CFArrayGetValueAtIndex(list, i)));
1470      tis.InitByTISInputSourceRef(inputSource);
1471      nsAutoString name, isid;
1472      tis.GetLocalizedName(name);
1473      tis.GetInputSourceID(isid);
1474      MOZ_LOG(gLog, LogLevel::Info,
1475        ("  %s\t<%s>%s%s\n",
1476         NS_ConvertUTF16toUTF8(name).get(),
1477         NS_ConvertUTF16toUTF8(isid).get(),
1478         tis.IsASCIICapable() ? "" : "\t(Isn't ASCII capable)",
1479         tis.IsKeyboardLayout() && tis.GetUCKeyboardLayout() ?
1480           "" : "\t(uchr is NOT AVAILABLE)"));
1481    }
1482    ::CFRelease(list);
1483  }
1484}
1485
1486
1487#pragma mark -
1488
1489
1490/******************************************************************************
1491 *
1492 *  TextInputHandler implementation
1493 *
1494 ******************************************************************************/
1495
1496TextInputHandler::TextInputHandler(nsChildView* aWidget,
1497                                   NSView<mozView> *aNativeView) :
1498  IMEInputHandler(aWidget, aNativeView)
1499{
1500  EnsureToLogAllKeyboardLayoutsAndIMEs();
1501  [mView installTextInputHandler:this];
1502}
1503
1504TextInputHandler::~TextInputHandler()
1505{
1506  [mView uninstallTextInputHandler];
1507}
1508
1509bool
1510TextInputHandler::HandleKeyDownEvent(NSEvent* aNativeEvent)
1511{
1512  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
1513
1514  if (Destroyed()) {
1515    MOZ_LOG(gLog, LogLevel::Info,
1516      ("%p TextInputHandler::HandleKeyDownEvent, "
1517       "widget has been already destroyed", this));
1518    return false;
1519  }
1520
1521  // Insert empty line to the log for easier to read.
1522  MOZ_LOG(gLog, LogLevel::Info, (""));
1523  MOZ_LOG(gLog, LogLevel::Info,
1524    ("%p TextInputHandler::HandleKeyDownEvent, aNativeEvent=%p, "
1525     "type=%s, keyCode=%lld (0x%X), modifierFlags=0x%X, characters=\"%s\", "
1526     "charactersIgnoringModifiers=\"%s\"",
1527     this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
1528     [aNativeEvent keyCode], [aNativeEvent keyCode],
1529     [aNativeEvent modifierFlags], GetCharacters([aNativeEvent characters]),
1530     GetCharacters([aNativeEvent charactersIgnoringModifiers])));
1531
1532  // Except when Command key is pressed, we should hide mouse cursor until
1533  // next mousemove.  Handling here means that:
1534  // - Don't hide mouse cursor at pressing modifier key
1535  // - Hide mouse cursor even if the key event will be handled by IME (i.e.,
1536  //   even without dispatching eKeyPress events)
1537  // - Hide mouse cursor even when a plugin has focus
1538  if (!([aNativeEvent modifierFlags] & NSCommandKeyMask)) {
1539    [NSCursor setHiddenUntilMouseMoves:YES];
1540  }
1541
1542  RefPtr<nsChildView> widget(mWidget);
1543
1544  KeyEventState* currentKeyEvent = PushKeyEvent(aNativeEvent);
1545  AutoKeyEventStateCleaner remover(this);
1546
1547  ComplexTextInputPanel* ctiPanel = ComplexTextInputPanel::GetSharedComplexTextInputPanel();
1548  if (ctiPanel && ctiPanel->IsInComposition()) {
1549    nsAutoString committed;
1550    ctiPanel->InterpretKeyEvent(aNativeEvent, committed);
1551    if (!committed.IsEmpty()) {
1552      nsresult rv = mDispatcher->BeginNativeInputTransaction();
1553      if (NS_WARN_IF(NS_FAILED(rv))) {
1554        MOZ_LOG(gLog, LogLevel::Error,
1555          ("%p IMEInputHandler::HandleKeyDownEvent, "
1556           "FAILED, due to BeginNativeInputTransaction() failure "
1557           "at dispatching keydown for ComplexTextInputPanel", this));
1558        return false;
1559      }
1560
1561      WidgetKeyboardEvent imeEvent(true, eKeyDown, widget);
1562      currentKeyEvent->InitKeyEvent(this, imeEvent);
1563      imeEvent.mPluginTextEventString.Assign(committed);
1564      nsEventStatus status = nsEventStatus_eIgnore;
1565      mDispatcher->DispatchKeyboardEvent(eKeyDown, imeEvent, status,
1566                                         currentKeyEvent);
1567    }
1568    return true;
1569  }
1570
1571  NSResponder* firstResponder = [[mView window] firstResponder];
1572
1573  nsresult rv = mDispatcher->BeginNativeInputTransaction();
1574  if (NS_WARN_IF(NS_FAILED(rv))) {
1575      MOZ_LOG(gLog, LogLevel::Error,
1576        ("%p IMEInputHandler::HandleKeyDownEvent, "
1577         "FAILED, due to BeginNativeInputTransaction() failure "
1578         "at dispatching keydown for ordinal cases", this));
1579    return false;
1580  }
1581
1582  WidgetKeyboardEvent keydownEvent(true, eKeyDown, widget);
1583  currentKeyEvent->InitKeyEvent(this, keydownEvent);
1584
1585  nsEventStatus status = nsEventStatus_eIgnore;
1586  mDispatcher->DispatchKeyboardEvent(eKeyDown, keydownEvent, status,
1587                                     currentKeyEvent);
1588  currentKeyEvent->mKeyDownHandled =
1589    (status == nsEventStatus_eConsumeNoDefault);
1590
1591  if (Destroyed()) {
1592    MOZ_LOG(gLog, LogLevel::Info,
1593      ("%p TextInputHandler::HandleKeyDownEvent, "
1594       "widget was destroyed by keydown event", this));
1595    return currentKeyEvent->IsDefaultPrevented();
1596  }
1597
1598  // The key down event may have shifted the focus, in which
1599  // case we should not fire the key press.
1600  // XXX This is a special code only on Cocoa widget, why is this needed?
1601  if (firstResponder != [[mView window] firstResponder]) {
1602    MOZ_LOG(gLog, LogLevel::Info,
1603      ("%p TextInputHandler::HandleKeyDownEvent, "
1604       "view lost focus by keydown event", this));
1605    return currentKeyEvent->IsDefaultPrevented();
1606  }
1607
1608  if (currentKeyEvent->IsDefaultPrevented()) {
1609    MOZ_LOG(gLog, LogLevel::Info,
1610      ("%p TextInputHandler::HandleKeyDownEvent, "
1611       "keydown event's default is prevented", this));
1612    return true;
1613  }
1614
1615  // Let Cocoa interpret the key events, caching IsIMEComposing first.
1616  bool wasComposing = IsIMEComposing();
1617  bool interpretKeyEventsCalled = false;
1618  // Don't call interpretKeyEvents when a plugin has focus.  If we call it,
1619  // for example, a character is inputted twice during a composition in e10s
1620  // mode.
1621  if (!widget->IsPluginFocused() && (IsIMEEnabled() || IsASCIICapableOnly())) {
1622    MOZ_LOG(gLog, LogLevel::Info,
1623      ("%p TextInputHandler::HandleKeyDownEvent, calling interpretKeyEvents",
1624       this));
1625    [mView interpretKeyEvents:[NSArray arrayWithObject:aNativeEvent]];
1626    interpretKeyEventsCalled = true;
1627    MOZ_LOG(gLog, LogLevel::Info,
1628      ("%p TextInputHandler::HandleKeyDownEvent, called interpretKeyEvents",
1629       this));
1630  }
1631
1632  if (Destroyed()) {
1633    MOZ_LOG(gLog, LogLevel::Info,
1634      ("%p TextInputHandler::HandleKeyDownEvent, widget was destroyed",
1635       this));
1636    return currentKeyEvent->IsDefaultPrevented();
1637  }
1638
1639  MOZ_LOG(gLog, LogLevel::Info,
1640    ("%p TextInputHandler::HandleKeyDownEvent, wasComposing=%s, "
1641     "IsIMEComposing()=%s",
1642     this, TrueOrFalse(wasComposing), TrueOrFalse(IsIMEComposing())));
1643
1644  if (currentKeyEvent->CanDispatchKeyPressEvent() &&
1645      !wasComposing && !IsIMEComposing()) {
1646    rv = mDispatcher->BeginNativeInputTransaction();
1647    if (NS_WARN_IF(NS_FAILED(rv))) {
1648        MOZ_LOG(gLog, LogLevel::Error,
1649          ("%p IMEInputHandler::HandleKeyDownEvent, "
1650           "FAILED, due to BeginNativeInputTransaction() failure "
1651           "at dispatching keypress", this));
1652      return false;
1653    }
1654
1655    WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget);
1656    currentKeyEvent->InitKeyEvent(this, keypressEvent);
1657
1658    // If we called interpretKeyEvents and this isn't normal character input
1659    // then IME probably ate the event for some reason. We do not want to
1660    // send a key press event in that case.
1661    // TODO:
1662    // There are some other cases which IME eats the current event.
1663    // 1. If key events were nested during calling interpretKeyEvents, it means
1664    //    that IME did something.  Then, we should do nothing.
1665    // 2. If one or more commands are called like "deleteBackward", we should
1666    //    dispatch keypress event at that time.  Note that the command may have
1667    //    been a converted or generated action by IME.  Then, we shouldn't do
1668    //    our default action for this key.
1669    if (!(interpretKeyEventsCalled &&
1670          IsNormalCharInputtingEvent(keypressEvent))) {
1671      currentKeyEvent->mKeyPressDispatched =
1672        mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status,
1673                                                 currentKeyEvent);
1674      currentKeyEvent->mKeyPressHandled =
1675        (status == nsEventStatus_eConsumeNoDefault);
1676      currentKeyEvent->mKeyPressDispatched = true;
1677      MOZ_LOG(gLog, LogLevel::Info,
1678        ("%p TextInputHandler::HandleKeyDownEvent, keypress event dispatched",
1679         this));
1680    }
1681  }
1682
1683  // Note: mWidget might have become null here. Don't count on it from here on.
1684
1685  MOZ_LOG(gLog, LogLevel::Info,
1686    ("%p TextInputHandler::HandleKeyDownEvent, "
1687     "keydown handled=%s, keypress handled=%s, causedOtherKeyEvents=%s, "
1688     "compositionDispatched=%s",
1689     this, TrueOrFalse(currentKeyEvent->mKeyDownHandled),
1690     TrueOrFalse(currentKeyEvent->mKeyPressHandled),
1691     TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents),
1692     TrueOrFalse(currentKeyEvent->mCompositionDispatched)));
1693  // Insert empty line to the log for easier to read.
1694  MOZ_LOG(gLog, LogLevel::Info, (""));
1695  return currentKeyEvent->IsDefaultPrevented();
1696
1697  NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
1698}
1699
1700void
1701TextInputHandler::HandleKeyUpEvent(NSEvent* aNativeEvent)
1702{
1703  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1704
1705  MOZ_LOG(gLog, LogLevel::Info,
1706    ("%p TextInputHandler::HandleKeyUpEvent, aNativeEvent=%p, "
1707     "type=%s, keyCode=%lld (0x%X), modifierFlags=0x%X, characters=\"%s\", "
1708     "charactersIgnoringModifiers=\"%s\", "
1709     "IsIMEComposing()=%s",
1710     this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
1711     [aNativeEvent keyCode], [aNativeEvent keyCode],
1712     [aNativeEvent modifierFlags], GetCharacters([aNativeEvent characters]),
1713     GetCharacters([aNativeEvent charactersIgnoringModifiers]),
1714     TrueOrFalse(IsIMEComposing())));
1715
1716  if (Destroyed()) {
1717    MOZ_LOG(gLog, LogLevel::Info,
1718      ("%p TextInputHandler::HandleKeyUpEvent, "
1719       "widget has been already destroyed", this));
1720    return;
1721  }
1722
1723  nsresult rv = mDispatcher->BeginNativeInputTransaction();
1724  if (NS_WARN_IF(NS_FAILED(rv))) {
1725      MOZ_LOG(gLog, LogLevel::Error,
1726        ("%p IMEInputHandler::HandleKeyUpEvent, "
1727         "FAILED, due to BeginNativeInputTransaction() failure", this));
1728    return;
1729  }
1730
1731  WidgetKeyboardEvent keyupEvent(true, eKeyUp, mWidget);
1732  InitKeyEvent(aNativeEvent, keyupEvent);
1733
1734  KeyEventState currentKeyEvent(aNativeEvent);
1735  nsEventStatus status = nsEventStatus_eIgnore;
1736  mDispatcher->DispatchKeyboardEvent(eKeyUp, keyupEvent, status,
1737                                     &currentKeyEvent);
1738
1739  NS_OBJC_END_TRY_ABORT_BLOCK;
1740}
1741
1742void
1743TextInputHandler::HandleFlagsChanged(NSEvent* aNativeEvent)
1744{
1745  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1746
1747  if (Destroyed()) {
1748    MOZ_LOG(gLog, LogLevel::Info,
1749      ("%p TextInputHandler::HandleFlagsChanged, "
1750       "widget has been already destroyed", this));
1751    return;
1752  }
1753
1754  RefPtr<nsChildView> kungFuDeathGrip(mWidget);
1755  mozilla::Unused << kungFuDeathGrip; // Not referenced within this function
1756
1757  MOZ_LOG(gLog, LogLevel::Info,
1758    ("%p TextInputHandler::HandleFlagsChanged, aNativeEvent=%p, "
1759     "type=%s, keyCode=%s (0x%X), modifierFlags=0x%08X, "
1760     "sLastModifierState=0x%08X, IsIMEComposing()=%s",
1761     this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
1762     GetKeyNameForNativeKeyCode([aNativeEvent keyCode]), [aNativeEvent keyCode],
1763     [aNativeEvent modifierFlags], sLastModifierState,
1764     TrueOrFalse(IsIMEComposing())));
1765
1766  MOZ_ASSERT([aNativeEvent type] == NSFlagsChanged);
1767
1768  NSUInteger diff = [aNativeEvent modifierFlags] ^ sLastModifierState;
1769  // Device dependent flags for left-control key, both shift keys, both command
1770  // keys and both option keys have been defined in Next's SDK.  But we
1771  // shouldn't use it directly as far as possible since Cocoa SDK doesn't
1772  // define them.  Fortunately, we need them only when we dispatch keyup
1773  // events.  So, we can usually know the actual relation between keyCode and
1774  // device dependent flags.  However, we need to remove following flags first
1775  // since the differences don't indicate modifier key state.
1776  // NX_STYLUSPROXIMITYMASK: Probably used for pen like device.
1777  // kCGEventFlagMaskNonCoalesced (= NX_NONCOALSESCEDMASK): See the document for
1778  // Quartz Event Services.
1779  diff &= ~(NX_STYLUSPROXIMITYMASK | kCGEventFlagMaskNonCoalesced);
1780
1781  switch ([aNativeEvent keyCode]) {
1782    // CapsLock state and other modifier states are different:
1783    // CapsLock state does not revert when the CapsLock key goes up, as the
1784    // modifier state does for other modifier keys on key up.
1785    case kVK_CapsLock: {
1786      // Fire key down event for caps lock.
1787      DispatchKeyEventForFlagsChanged(aNativeEvent, true);
1788      // XXX should we fire keyup event too? The keyup event for CapsLock key
1789      // is never dispatched on Gecko.
1790      // XXX WebKit dispatches keydown event when CapsLock is locked, otherwise,
1791      // keyup event.  If we do so, we cannot keep the consistency with other
1792      // platform's behavior...
1793      break;
1794    }
1795
1796    // If the event is caused by pressing or releasing a modifier key, just
1797    // dispatch the key's event.
1798    case kVK_Shift:
1799    case kVK_RightShift:
1800    case kVK_Command:
1801    case kVK_RightCommand:
1802    case kVK_Control:
1803    case kVK_RightControl:
1804    case kVK_Option:
1805    case kVK_RightOption:
1806    case kVK_Help: {
1807      // We assume that at most one modifier is changed per event if the event
1808      // is caused by pressing or releasing a modifier key.
1809      bool isKeyDown = ([aNativeEvent modifierFlags] & diff) != 0;
1810      DispatchKeyEventForFlagsChanged(aNativeEvent, isKeyDown);
1811      // XXX Some applications might send the event with incorrect device-
1812      //     dependent flags.
1813      if (isKeyDown && ((diff & ~NSDeviceIndependentModifierFlagsMask) != 0)) {
1814        unsigned short keyCode = [aNativeEvent keyCode];
1815        const ModifierKey* modifierKey =
1816          GetModifierKeyForDeviceDependentFlags(diff);
1817        if (modifierKey && modifierKey->keyCode != keyCode) {
1818          // Although, we're not sure the actual cause of this case, the stored
1819          // modifier information and the latest key event information may be
1820          // mismatched. Then, let's reset the stored information.
1821          // NOTE: If this happens, it may fail to handle NSFlagsChanged event
1822          // in the default case (below). However, it's the rare case handler
1823          // and this case occurs rarely. So, we can ignore the edge case bug.
1824          NS_WARNING("Resetting stored modifier key information");
1825          mModifierKeys.Clear();
1826          modifierKey = nullptr;
1827        }
1828        if (!modifierKey) {
1829          mModifierKeys.AppendElement(ModifierKey(diff, keyCode));
1830        }
1831      }
1832      break;
1833    }
1834
1835    // Currently we don't support Fn key since other browsers don't dispatch
1836    // events for it and we don't have keyCode for this key.
1837    // It should be supported when we implement .key and .char.
1838    case kVK_Function:
1839      break;
1840
1841    // If the event is caused by something else than pressing or releasing a
1842    // single modifier key (for example by the app having been deactivated
1843    // using command-tab), use the modifiers themselves to determine which
1844    // key's event to dispatch, and whether it's a keyup or keydown event.
1845    // In all cases we assume one or more modifiers are being deactivated
1846    // (never activated) -- otherwise we'd have received one or more events
1847    // corresponding to a single modifier key being pressed.
1848    default: {
1849      NSUInteger modifiers = sLastModifierState;
1850      for (int32_t bit = 0; bit < 32; ++bit) {
1851        NSUInteger flag = 1 << bit;
1852        if (!(diff & flag)) {
1853          continue;
1854        }
1855
1856        // Given correct information from the application, a flag change here
1857        // will normally be a deactivation (except for some lockable modifiers
1858        // such as CapsLock).  But some applications (like VNC) can send an
1859        // activating event with a zero keyCode.  So we need to check for that
1860        // here.
1861        bool dispatchKeyDown = ((flag & [aNativeEvent modifierFlags]) != 0);
1862
1863        unsigned short keyCode = 0;
1864        if (flag & NSDeviceIndependentModifierFlagsMask) {
1865          switch (flag) {
1866            case NSAlphaShiftKeyMask:
1867              keyCode = kVK_CapsLock;
1868              dispatchKeyDown = true;
1869              break;
1870
1871            case NSNumericPadKeyMask:
1872              // NSNumericPadKeyMask is fired by VNC a lot. But not all of
1873              // these events can really be Clear key events, so we just ignore
1874              // them.
1875              continue;
1876
1877            case NSHelpKeyMask:
1878              keyCode = kVK_Help;
1879              break;
1880
1881            case NSFunctionKeyMask:
1882              // An NSFunctionKeyMask change here will normally be a
1883              // deactivation.  But sometimes it will be an activation send (by
1884              // VNC for example) with a zero keyCode.
1885              continue;
1886
1887            // These cases (NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask
1888            // and NSCommandKeyMask) should be handled by the other branch of
1889            // the if statement, below (which handles device dependent flags).
1890            // However, some applications (like VNC) can send key events without
1891            // any device dependent flags, so we handle them here instead.
1892            case NSShiftKeyMask:
1893              keyCode = (modifiers & 0x0004) ? kVK_RightShift : kVK_Shift;
1894              break;
1895            case NSControlKeyMask:
1896              keyCode = (modifiers & 0x2000) ? kVK_RightControl : kVK_Control;
1897              break;
1898            case NSAlternateKeyMask:
1899              keyCode = (modifiers & 0x0040) ? kVK_RightOption : kVK_Option;
1900              break;
1901            case NSCommandKeyMask:
1902              keyCode = (modifiers & 0x0010) ? kVK_RightCommand : kVK_Command;
1903              break;
1904
1905            default:
1906              continue;
1907          }
1908        } else {
1909          const ModifierKey* modifierKey =
1910            GetModifierKeyForDeviceDependentFlags(flag);
1911          if (!modifierKey) {
1912            // See the note above (in the other branch of the if statement)
1913            // about the NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask
1914            // and NSCommandKeyMask cases.
1915            continue;
1916          }
1917          keyCode = modifierKey->keyCode;
1918        }
1919
1920        // Remove flags
1921        modifiers &= ~flag;
1922        switch (keyCode) {
1923          case kVK_Shift: {
1924            const ModifierKey* modifierKey =
1925              GetModifierKeyForNativeKeyCode(kVK_RightShift);
1926            if (!modifierKey ||
1927                !(modifiers & modifierKey->GetDeviceDependentFlags())) {
1928              modifiers &= ~NSShiftKeyMask;
1929            }
1930            break;
1931          }
1932          case kVK_RightShift: {
1933            const ModifierKey* modifierKey =
1934              GetModifierKeyForNativeKeyCode(kVK_Shift);
1935            if (!modifierKey ||
1936                !(modifiers & modifierKey->GetDeviceDependentFlags())) {
1937              modifiers &= ~NSShiftKeyMask;
1938            }
1939            break;
1940          }
1941          case kVK_Command: {
1942            const ModifierKey* modifierKey =
1943              GetModifierKeyForNativeKeyCode(kVK_RightCommand);
1944            if (!modifierKey ||
1945                !(modifiers & modifierKey->GetDeviceDependentFlags())) {
1946              modifiers &= ~NSCommandKeyMask;
1947            }
1948            break;
1949          }
1950          case kVK_RightCommand: {
1951            const ModifierKey* modifierKey =
1952              GetModifierKeyForNativeKeyCode(kVK_Command);
1953            if (!modifierKey ||
1954                !(modifiers & modifierKey->GetDeviceDependentFlags())) {
1955              modifiers &= ~NSCommandKeyMask;
1956            }
1957            break;
1958          }
1959          case kVK_Control: {
1960            const ModifierKey* modifierKey =
1961              GetModifierKeyForNativeKeyCode(kVK_RightControl);
1962            if (!modifierKey ||
1963                !(modifiers & modifierKey->GetDeviceDependentFlags())) {
1964              modifiers &= ~NSControlKeyMask;
1965            }
1966            break;
1967          }
1968          case kVK_RightControl: {
1969            const ModifierKey* modifierKey =
1970              GetModifierKeyForNativeKeyCode(kVK_Control);
1971            if (!modifierKey ||
1972                !(modifiers & modifierKey->GetDeviceDependentFlags())) {
1973              modifiers &= ~NSControlKeyMask;
1974            }
1975            break;
1976          }
1977          case kVK_Option: {
1978            const ModifierKey* modifierKey =
1979              GetModifierKeyForNativeKeyCode(kVK_RightOption);
1980            if (!modifierKey ||
1981                !(modifiers & modifierKey->GetDeviceDependentFlags())) {
1982              modifiers &= ~NSAlternateKeyMask;
1983            }
1984            break;
1985          }
1986          case kVK_RightOption: {
1987            const ModifierKey* modifierKey =
1988              GetModifierKeyForNativeKeyCode(kVK_Option);
1989            if (!modifierKey ||
1990                !(modifiers & modifierKey->GetDeviceDependentFlags())) {
1991              modifiers &= ~NSAlternateKeyMask;
1992            }
1993            break;
1994          }
1995          case kVK_Help:
1996            modifiers &= ~NSHelpKeyMask;
1997            break;
1998          default:
1999            break;
2000        }
2001
2002        NSEvent* event =
2003          [NSEvent keyEventWithType:NSFlagsChanged
2004                           location:[aNativeEvent locationInWindow]
2005                      modifierFlags:modifiers
2006                          timestamp:[aNativeEvent timestamp]
2007                       windowNumber:[aNativeEvent windowNumber]
2008                            context:[aNativeEvent context]
2009                         characters:@""
2010        charactersIgnoringModifiers:@""
2011                          isARepeat:NO
2012                            keyCode:keyCode];
2013        DispatchKeyEventForFlagsChanged(event, dispatchKeyDown);
2014        if (Destroyed()) {
2015          break;
2016        }
2017
2018        // Stop if focus has changed.
2019        // Check to see if mView is still the first responder.
2020        if (![mView isFirstResponder]) {
2021          break;
2022        }
2023
2024      }
2025      break;
2026    }
2027  }
2028
2029  // Be aware, the widget may have been destroyed.
2030  sLastModifierState = [aNativeEvent modifierFlags];
2031
2032  NS_OBJC_END_TRY_ABORT_BLOCK;
2033}
2034
2035const TextInputHandler::ModifierKey*
2036TextInputHandler::GetModifierKeyForNativeKeyCode(unsigned short aKeyCode) const
2037{
2038  for (ModifierKeyArray::index_type i = 0; i < mModifierKeys.Length(); ++i) {
2039    if (mModifierKeys[i].keyCode == aKeyCode) {
2040      return &((ModifierKey&)mModifierKeys[i]);
2041    }
2042  }
2043  return nullptr;
2044}
2045
2046const TextInputHandler::ModifierKey*
2047TextInputHandler::GetModifierKeyForDeviceDependentFlags(NSUInteger aFlags) const
2048{
2049  for (ModifierKeyArray::index_type i = 0; i < mModifierKeys.Length(); ++i) {
2050    if (mModifierKeys[i].GetDeviceDependentFlags() ==
2051          (aFlags & ~NSDeviceIndependentModifierFlagsMask)) {
2052      return &((ModifierKey&)mModifierKeys[i]);
2053    }
2054  }
2055  return nullptr;
2056}
2057
2058void
2059TextInputHandler::DispatchKeyEventForFlagsChanged(NSEvent* aNativeEvent,
2060                                                  bool aDispatchKeyDown)
2061{
2062  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2063
2064  if (Destroyed()) {
2065    return;
2066  }
2067
2068  MOZ_LOG(gLog, LogLevel::Info,
2069    ("%p TextInputHandler::DispatchKeyEventForFlagsChanged, aNativeEvent=%p, "
2070     "type=%s, keyCode=%s (0x%X), aDispatchKeyDown=%s, IsIMEComposing()=%s",
2071     this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
2072     GetKeyNameForNativeKeyCode([aNativeEvent keyCode]), [aNativeEvent keyCode],
2073     TrueOrFalse(aDispatchKeyDown), TrueOrFalse(IsIMEComposing())));
2074
2075  if ([aNativeEvent type] != NSFlagsChanged || IsIMEComposing()) {
2076    return;
2077  }
2078
2079  nsresult rv = mDispatcher->BeginNativeInputTransaction();
2080  if (NS_WARN_IF(NS_FAILED(rv))) {
2081      MOZ_LOG(gLog, LogLevel::Error,
2082        ("%p IMEInputHandler::DispatchKeyEventForFlagsChanged, "
2083         "FAILED, due to BeginNativeInputTransaction() failure", this));
2084    return;
2085  }
2086
2087  EventMessage message = aDispatchKeyDown ? eKeyDown : eKeyUp;
2088
2089  // Fire a key event.
2090  WidgetKeyboardEvent keyEvent(true, message, mWidget);
2091  InitKeyEvent(aNativeEvent, keyEvent);
2092
2093  // Attach a plugin event, in case keyEvent gets dispatched to a plugin.  Only
2094  // one field is needed -- the type.  The other fields can be constructed as
2095  // the need arises.  But Gecko doesn't have anything equivalent to the
2096  // NPCocoaEventFlagsChanged type, and this needs to be passed accurately to
2097  // any plugin to which this event is sent.
2098  NPCocoaEvent cocoaEvent;
2099  nsCocoaUtils::InitNPCocoaEvent(&cocoaEvent);
2100  cocoaEvent.type = NPCocoaEventFlagsChanged;
2101  keyEvent.mPluginEvent.Copy(cocoaEvent);
2102
2103  KeyEventState currentKeyEvent(aNativeEvent);
2104  nsEventStatus status = nsEventStatus_eIgnore;
2105  mDispatcher->DispatchKeyboardEvent(message, keyEvent, status,
2106                                     &currentKeyEvent);
2107
2108  NS_OBJC_END_TRY_ABORT_BLOCK;
2109}
2110
2111void
2112TextInputHandler::InsertText(NSAttributedString* aAttrString,
2113                             NSRange* aReplacementRange)
2114{
2115  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2116
2117  if (Destroyed()) {
2118    return;
2119  }
2120
2121  KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
2122
2123  MOZ_LOG(gLog, LogLevel::Info,
2124    ("%p TextInputHandler::InsertText, aAttrString=\"%s\", "
2125     "aReplacementRange=%p { location=%llu, length=%llu }, "
2126     "IsIMEComposing()=%s, IgnoreIMEComposition()=%s, "
2127     "keyevent=%p, keydownHandled=%s, keypressDispatched=%s, "
2128     "causedOtherKeyEvents=%s, compositionDispatched=%s",
2129     this, GetCharacters([aAttrString string]), aReplacementRange,
2130     aReplacementRange ? aReplacementRange->location : 0,
2131     aReplacementRange ? aReplacementRange->length : 0,
2132     TrueOrFalse(IsIMEComposing()), TrueOrFalse(IgnoreIMEComposition()),
2133     currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr,
2134     currentKeyEvent ?
2135       TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
2136     currentKeyEvent ?
2137       TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A",
2138     currentKeyEvent ?
2139       TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A",
2140     currentKeyEvent ?
2141       TrueOrFalse(currentKeyEvent->mCompositionDispatched) : "N/A"));
2142
2143  if (IgnoreIMEComposition()) {
2144    return;
2145  }
2146
2147  InputContext context = mWidget->GetInputContext();
2148  bool isEditable = (context.mIMEState.mEnabled == IMEState::ENABLED ||
2149                     context.mIMEState.mEnabled == IMEState::PASSWORD);
2150  NSRange selectedRange = SelectedRange();
2151
2152  nsAutoString str;
2153  nsCocoaUtils::GetStringForNSString([aAttrString string], str);
2154
2155  AutoInsertStringClearer clearer(currentKeyEvent);
2156  if (currentKeyEvent) {
2157    currentKeyEvent->mInsertString = &str;
2158  }
2159
2160  if (!IsIMEComposing() && str.IsEmpty()) {
2161    // nothing to do if there is no content which can be removed.
2162    if (!isEditable) {
2163      return;
2164    }
2165    // If replacement range is specified, we need to remove the range.
2166    // Otherwise, we need to remove the selected range if it's not collapsed.
2167    if (aReplacementRange && aReplacementRange->location != NSNotFound) {
2168      // nothing to do since the range is collapsed.
2169      if (aReplacementRange->length == 0) {
2170        return;
2171      }
2172      // If the replacement range is different from current selected range,
2173      // select the range.
2174      if (!NSEqualRanges(selectedRange, *aReplacementRange)) {
2175        NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
2176      }
2177      selectedRange = SelectedRange();
2178    }
2179    NS_ENSURE_TRUE_VOID(selectedRange.location != NSNotFound);
2180    if (selectedRange.length == 0) {
2181      return; // nothing to do
2182    }
2183    // If this is caused by a key input, the keypress event which will be
2184    // dispatched later should cause the delete.  Therefore, nothing to do here.
2185    // Although, we're not sure if such case is actually possible.
2186    if (!currentKeyEvent) {
2187      return;
2188    }
2189    // Delete the selected range.
2190    RefPtr<TextInputHandler> kungFuDeathGrip(this);
2191    WidgetContentCommandEvent deleteCommandEvent(true, eContentCommandDelete,
2192                                                 mWidget);
2193    DispatchEvent(deleteCommandEvent);
2194    NS_ENSURE_TRUE_VOID(deleteCommandEvent.mSucceeded);
2195    // Be aware! The widget might be destroyed here.
2196    return;
2197  }
2198
2199  bool isReplacingSpecifiedRange =
2200    isEditable && aReplacementRange &&
2201    aReplacementRange->location != NSNotFound &&
2202    !NSEqualRanges(selectedRange, *aReplacementRange);
2203
2204  // If this is not caused by pressing a key, there is a composition or
2205  // replacing a range which is different from current selection, let's
2206  // insert the text as committing a composition.
2207  // If InsertText() is called two or more times, we should insert all
2208  // text with composition events.
2209  // XXX When InsertText() is called multiple times, Chromium dispatches
2210  //     only one composition event.  So, we need to store InsertText()
2211  //     calls and flush later.
2212  if (!currentKeyEvent || currentKeyEvent->mCompositionDispatched ||
2213      IsIMEComposing() || isReplacingSpecifiedRange) {
2214    InsertTextAsCommittingComposition(aAttrString, aReplacementRange);
2215    if (currentKeyEvent) {
2216      currentKeyEvent->mCompositionDispatched = true;
2217    }
2218    return;
2219  }
2220
2221  // Don't let the same event be fired twice when hitting
2222  // enter/return for Bug 420502.  However, Korean IME (or some other
2223  // simple IME) may work without marked text.  For example, composing
2224  // character may be inserted as committed text and it's modified with
2225  // aReplacementRange.  When a keydown starts new composition with
2226  // committing previous character, InsertText() may be called twice,
2227  // one is for committing previous character and then, inserting new
2228  // composing character as committed character.  In the latter case,
2229  // |CanDispatchKeyPressEvent()| returns true but we need to dispatch
2230  // keypress event for the new character.  So, when IME tries to insert
2231  // printable characters, we should ignore current key event state even
2232  // after the keydown has already caused dispatching composition event.
2233  // XXX Anyway, we should sort out around this at fixing bug 1338460.
2234  if (currentKeyEvent && !currentKeyEvent->CanDispatchKeyPressEvent() &&
2235      (str.IsEmpty() || (str.Length() == 1 && !IsPrintableChar(str[0])))) {
2236    return;
2237  }
2238
2239  // XXX Shouldn't we hold mDispatcher instead of mWidget?
2240  RefPtr<nsChildView> widget(mWidget);
2241  nsresult rv = mDispatcher->BeginNativeInputTransaction();
2242  if (NS_WARN_IF(NS_FAILED(rv))) {
2243      MOZ_LOG(gLog, LogLevel::Error,
2244        ("%p IMEInputHandler::HandleKeyUpEvent, "
2245         "FAILED, due to BeginNativeInputTransaction() failure", this));
2246    return;
2247  }
2248
2249  // Dispatch keypress event with char instead of compositionchange event
2250  WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget);
2251  // XXX Why do we need to dispatch keypress event for not inputting any
2252  //     string?  If it wants to delete the specified range, should we
2253  //     dispatch an eContentCommandDelete event instead?  Because this
2254  //     must not be caused by a key operation, a part of IME's processing.
2255  keypressEvent.mIsChar = IsPrintableChar(str.CharAt(0));
2256
2257  // Don't set other modifiers from the current event, because here in
2258  // -insertText: they've already been taken into account in creating
2259  // the input string.
2260
2261  if (currentKeyEvent) {
2262    currentKeyEvent->InitKeyEvent(this, keypressEvent);
2263  } else {
2264    nsCocoaUtils::InitInputEvent(keypressEvent, static_cast<NSEvent*>(nullptr));
2265    keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
2266    keypressEvent.mKeyValue = str;
2267    // FYI: TextEventDispatcher will set mKeyCode to 0 for printable key's
2268    //      keypress events even if they don't cause inputting non-empty string.
2269  }
2270
2271  // Remove basic modifiers from keypress event because if they are included,
2272  // nsPlaintextEditor ignores the event.
2273  keypressEvent.mModifiers &= ~(MODIFIER_CONTROL |
2274                                MODIFIER_ALT |
2275                                MODIFIER_META);
2276
2277  // TODO:
2278  // If mCurrentKeyEvent.mKeyEvent is null, the text should be inputted as
2279  // composition events.
2280  nsEventStatus status = nsEventStatus_eIgnore;
2281  bool keyPressDispatched =
2282    mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status,
2283                                             currentKeyEvent);
2284  bool keyPressHandled = (status == nsEventStatus_eConsumeNoDefault);
2285
2286  // Note: mWidget might have become null here. Don't count on it from here on.
2287
2288  if (currentKeyEvent) {
2289    currentKeyEvent->mKeyPressHandled = keyPressHandled;
2290    currentKeyEvent->mKeyPressDispatched = keyPressDispatched;
2291  }
2292
2293  NS_OBJC_END_TRY_ABORT_BLOCK;
2294}
2295
2296bool
2297TextInputHandler::DoCommandBySelector(const char* aSelector)
2298{
2299  RefPtr<nsChildView> widget(mWidget);
2300
2301  KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
2302
2303  MOZ_LOG(gLog, LogLevel::Info,
2304    ("%p TextInputHandler::DoCommandBySelector, aSelector=\"%s\", "
2305     "Destroyed()=%s, keydownHandled=%s, keypressHandled=%s, "
2306     "causedOtherKeyEvents=%s",
2307     this, aSelector ? aSelector : "", TrueOrFalse(Destroyed()),
2308     currentKeyEvent ?
2309       TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
2310     currentKeyEvent ?
2311       TrueOrFalse(currentKeyEvent->mKeyPressHandled) : "N/A",
2312     currentKeyEvent ?
2313       TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A"));
2314
2315  if (currentKeyEvent && currentKeyEvent->CanDispatchKeyPressEvent()) {
2316    nsresult rv = mDispatcher->BeginNativeInputTransaction();
2317    if (NS_WARN_IF(NS_FAILED(rv))) {
2318        MOZ_LOG(gLog, LogLevel::Error,
2319          ("%p IMEInputHandler::DoCommandBySelector, "
2320           "FAILED, due to BeginNativeInputTransaction() failure "
2321           "at dispatching keypress", this));
2322      return false;
2323    }
2324
2325    WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget);
2326    currentKeyEvent->InitKeyEvent(this, keypressEvent);
2327
2328    nsEventStatus status = nsEventStatus_eIgnore;
2329    currentKeyEvent->mKeyPressDispatched =
2330      mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status,
2331                                               currentKeyEvent);
2332    currentKeyEvent->mKeyPressHandled =
2333      (status == nsEventStatus_eConsumeNoDefault);
2334    MOZ_LOG(gLog, LogLevel::Info,
2335      ("%p TextInputHandler::DoCommandBySelector, keypress event "
2336       "dispatched, Destroyed()=%s, keypressHandled=%s",
2337       this, TrueOrFalse(Destroyed()),
2338       TrueOrFalse(currentKeyEvent->mKeyPressHandled)));
2339  }
2340
2341  return (!Destroyed() && currentKeyEvent &&
2342          currentKeyEvent->IsDefaultPrevented());
2343}
2344
2345
2346#pragma mark -
2347
2348
2349/******************************************************************************
2350 *
2351 *  IMEInputHandler implementation (static methods)
2352 *
2353 ******************************************************************************/
2354
2355bool IMEInputHandler::sStaticMembersInitialized = false;
2356bool IMEInputHandler::sCachedIsForRTLLangage = false;
2357CFStringRef IMEInputHandler::sLatestIMEOpenedModeInputSourceID = nullptr;
2358IMEInputHandler* IMEInputHandler::sFocusedIMEHandler = nullptr;
2359
2360// static
2361void
2362IMEInputHandler::InitStaticMembers()
2363{
2364  if (sStaticMembersInitialized)
2365    return;
2366  sStaticMembersInitialized = true;
2367  // We need to check the keyboard layout changes on all applications.
2368  CFNotificationCenterRef center = ::CFNotificationCenterGetDistributedCenter();
2369  // XXX Don't we need to remove the observer at shut down?
2370  // Mac Dev Center's document doesn't say how to remove the observer if
2371  // the second parameter is NULL.
2372  ::CFNotificationCenterAddObserver(center, NULL,
2373      OnCurrentTextInputSourceChange,
2374      kTISNotifySelectedKeyboardInputSourceChanged, NULL,
2375      CFNotificationSuspensionBehaviorDeliverImmediately);
2376  // Initiailize with the current keyboard layout
2377  OnCurrentTextInputSourceChange(NULL, NULL,
2378                                 kTISNotifySelectedKeyboardInputSourceChanged,
2379                                 NULL, NULL);
2380}
2381
2382// static
2383void
2384IMEInputHandler::OnCurrentTextInputSourceChange(CFNotificationCenterRef aCenter,
2385                                                void* aObserver,
2386                                                CFStringRef aName,
2387                                                const void* aObject,
2388                                                CFDictionaryRef aUserInfo)
2389{
2390  // Cache the latest IME opened mode to sLatestIMEOpenedModeInputSourceID.
2391  TISInputSourceWrapper tis;
2392  tis.InitByCurrentInputSource();
2393  if (tis.IsOpenedIMEMode()) {
2394    tis.GetInputSourceID(sLatestIMEOpenedModeInputSourceID);
2395  }
2396
2397  if (MOZ_LOG_TEST(gLog, LogLevel::Info)) {
2398    static CFStringRef sLastTIS = nullptr;
2399    CFStringRef newTIS;
2400    tis.GetInputSourceID(newTIS);
2401    if (!sLastTIS ||
2402        ::CFStringCompare(sLastTIS, newTIS, 0) != kCFCompareEqualTo) {
2403      TISInputSourceWrapper tis1, tis2, tis3, tis4, tis5;
2404      tis1.InitByCurrentKeyboardLayout();
2405      tis2.InitByCurrentASCIICapableInputSource();
2406      tis3.InitByCurrentASCIICapableKeyboardLayout();
2407      tis4.InitByCurrentInputMethodKeyboardLayoutOverride();
2408      tis5.InitByTISInputSourceRef(tis.GetKeyboardLayoutInputSource());
2409      CFStringRef is0 = nullptr, is1 = nullptr, is2 = nullptr, is3 = nullptr,
2410                  is4 = nullptr, is5 = nullptr, type0 = nullptr,
2411                  lang0 = nullptr, bundleID0 = nullptr;
2412      tis.GetInputSourceID(is0);
2413      tis1.GetInputSourceID(is1);
2414      tis2.GetInputSourceID(is2);
2415      tis3.GetInputSourceID(is3);
2416      tis4.GetInputSourceID(is4);
2417      tis5.GetInputSourceID(is5);
2418      tis.GetInputSourceType(type0);
2419      tis.GetPrimaryLanguage(lang0);
2420      tis.GetBundleID(bundleID0);
2421
2422      MOZ_LOG(gLog, LogLevel::Info,
2423        ("IMEInputHandler::OnCurrentTextInputSourceChange,\n"
2424         "  Current Input Source is changed to:\n"
2425         "    currentInputContext=%p\n"
2426         "    %s\n"
2427         "      type=%s %s\n"
2428         "      overridden keyboard layout=%s\n"
2429         "      used keyboard layout for translation=%s\n"
2430         "    primary language=%s\n"
2431         "    bundle ID=%s\n"
2432         "    current ASCII capable Input Source=%s\n"
2433         "    current Keyboard Layout=%s\n"
2434         "    current ASCII capable Keyboard Layout=%s",
2435         [NSTextInputContext currentInputContext], GetCharacters(is0),
2436         GetCharacters(type0), tis.IsASCIICapable() ? "- ASCII capable " : "",
2437         GetCharacters(is4), GetCharacters(is5),
2438         GetCharacters(lang0), GetCharacters(bundleID0),
2439         GetCharacters(is2), GetCharacters(is1), GetCharacters(is3)));
2440    }
2441    sLastTIS = newTIS;
2442  }
2443
2444  /**
2445   * When the direction is changed, all the children are notified.
2446   * No need to treat the initial case separately because it is covered
2447   * by the general case (sCachedIsForRTLLangage is initially false)
2448   */
2449  if (sCachedIsForRTLLangage != tis.IsForRTLLanguage()) {
2450    WidgetUtils::SendBidiKeyboardInfoToContent();
2451    sCachedIsForRTLLangage = tis.IsForRTLLanguage();
2452  }
2453}
2454
2455// static
2456void
2457IMEInputHandler::FlushPendingMethods(nsITimer* aTimer, void* aClosure)
2458{
2459  NS_ASSERTION(aClosure, "aClosure is null");
2460  static_cast<IMEInputHandler*>(aClosure)->ExecutePendingMethods();
2461}
2462
2463// static
2464CFArrayRef
2465IMEInputHandler::CreateAllIMEModeList()
2466{
2467  const void* keys[] = { kTISPropertyInputSourceType };
2468  const void* values[] = { kTISTypeKeyboardInputMode };
2469  CFDictionaryRef filter =
2470    ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
2471  NS_ASSERTION(filter, "failed to create the filter");
2472  CFArrayRef list = ::TISCreateInputSourceList(filter, true);
2473  ::CFRelease(filter);
2474  return list;
2475}
2476
2477// static
2478void
2479IMEInputHandler::DebugPrintAllIMEModes()
2480{
2481  if (MOZ_LOG_TEST(gLog, LogLevel::Info)) {
2482    CFArrayRef list = CreateAllIMEModeList();
2483    MOZ_LOG(gLog, LogLevel::Info, ("IME mode configuration:"));
2484    CFIndex idx = ::CFArrayGetCount(list);
2485    TISInputSourceWrapper tis;
2486    for (CFIndex i = 0; i < idx; ++i) {
2487      TISInputSourceRef inputSource = static_cast<TISInputSourceRef>(
2488        const_cast<void *>(::CFArrayGetValueAtIndex(list, i)));
2489      tis.InitByTISInputSourceRef(inputSource);
2490      nsAutoString name, isid;
2491      tis.GetLocalizedName(name);
2492      tis.GetInputSourceID(isid);
2493      MOZ_LOG(gLog, LogLevel::Info,
2494        ("  %s\t<%s>%s%s\n",
2495         NS_ConvertUTF16toUTF8(name).get(),
2496         NS_ConvertUTF16toUTF8(isid).get(),
2497         tis.IsASCIICapable() ? "" : "\t(Isn't ASCII capable)",
2498         tis.IsEnabled() ? "" : "\t(Isn't Enabled)"));
2499    }
2500    ::CFRelease(list);
2501  }
2502}
2503
2504//static
2505TSMDocumentID
2506IMEInputHandler::GetCurrentTSMDocumentID()
2507{
2508  // At least on Mac OS X 10.6.x and 10.7.x, ::TSMGetActiveDocument() has a bug.
2509  // The result of ::TSMGetActiveDocument() isn't modified for new active text
2510  // input context until [NSTextInputContext currentInputContext] is called.
2511  // Therefore, we need to call it here.
2512  [NSTextInputContext currentInputContext];
2513  return ::TSMGetActiveDocument();
2514}
2515
2516
2517#pragma mark -
2518
2519
2520/******************************************************************************
2521 *
2522 *  IMEInputHandler implementation #1
2523 *    The methods are releated to the pending methods.  Some jobs should be
2524 *    run after the stack is finished, e.g, some methods cannot run the jobs
2525 *    during processing the focus event.  And also some other jobs should be
2526 *    run at the next focus event is processed.
2527 *    The pending methods are recorded in mPendingMethods.  They are executed
2528 *    by ExecutePendingMethods via FlushPendingMethods.
2529 *
2530 ******************************************************************************/
2531
2532NS_IMETHODIMP
2533IMEInputHandler::NotifyIME(TextEventDispatcher* aTextEventDispatcher,
2534                           const IMENotification& aNotification)
2535{
2536  switch (aNotification.mMessage) {
2537    case REQUEST_TO_COMMIT_COMPOSITION:
2538      CommitIMEComposition();
2539      return NS_OK;
2540    case REQUEST_TO_CANCEL_COMPOSITION:
2541      CancelIMEComposition();
2542      return NS_OK;
2543    case NOTIFY_IME_OF_FOCUS:
2544      if (IsFocused()) {
2545        nsIWidget* widget = aTextEventDispatcher->GetWidget();
2546        if (widget && widget->GetInputContext().IsPasswordEditor()) {
2547          EnableSecureEventInput();
2548        } else {
2549          EnsureSecureEventInputDisabled();
2550        }
2551      }
2552      OnFocusChangeInGecko(true);
2553      return NS_OK;
2554    case NOTIFY_IME_OF_BLUR:
2555      OnFocusChangeInGecko(false);
2556      return NS_OK;
2557    case NOTIFY_IME_OF_SELECTION_CHANGE:
2558      OnSelectionChange(aNotification);
2559      return NS_OK;
2560    default:
2561      return NS_ERROR_NOT_IMPLEMENTED;
2562  }
2563}
2564
2565NS_IMETHODIMP_(void)
2566IMEInputHandler::OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher)
2567{
2568  // XXX When input transaction is being stolen by add-on, what should we do?
2569}
2570
2571NS_IMETHODIMP_(void)
2572IMEInputHandler::WillDispatchKeyboardEvent(
2573                   TextEventDispatcher* aTextEventDispatcher,
2574                   WidgetKeyboardEvent& aKeyboardEvent,
2575                   uint32_t aIndexOfKeypress,
2576                   void* aData)
2577{
2578  // If the keyboard event is not caused by a native key event, we can do
2579  // nothing here.
2580  if (!aData) {
2581    return;
2582  }
2583
2584  KeyEventState* currentKeyEvent = static_cast<KeyEventState*>(aData);
2585  NSEvent* nativeEvent = currentKeyEvent->mKeyEvent;
2586  nsAString* insertString = currentKeyEvent->mInsertString;
2587  if (KeyboardLayoutOverrideRef().mOverrideEnabled) {
2588    TISInputSourceWrapper tis;
2589    tis.InitByLayoutID(KeyboardLayoutOverrideRef().mKeyboardLayout, true);
2590    tis.WillDispatchKeyboardEvent(nativeEvent, insertString, aKeyboardEvent);
2591    return;
2592  }
2593  TISInputSourceWrapper::CurrentInputSource().
2594    WillDispatchKeyboardEvent(nativeEvent, insertString, aKeyboardEvent);
2595}
2596
2597void
2598IMEInputHandler::NotifyIMEOfFocusChangeInGecko()
2599{
2600  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2601
2602  MOZ_LOG(gLog, LogLevel::Info,
2603    ("%p IMEInputHandler::NotifyIMEOfFocusChangeInGecko, "
2604     "Destroyed()=%s, IsFocused()=%s, inputContext=%p",
2605     this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
2606     mView ? [mView inputContext] : nullptr));
2607
2608  if (Destroyed()) {
2609    return;
2610  }
2611
2612  if (!IsFocused()) {
2613    // retry at next focus event
2614    mPendingMethods |= kNotifyIMEOfFocusChangeInGecko;
2615    return;
2616  }
2617
2618  MOZ_ASSERT(mView);
2619  NSTextInputContext* inputContext = [mView inputContext];
2620  NS_ENSURE_TRUE_VOID(inputContext);
2621
2622  // When an <input> element on a XUL <panel> element gets focus from an <input>
2623  // element on the opener window of the <panel> element, the owner window
2624  // still has native focus.  Therefore, IMEs may store the opener window's
2625  // level at this time because they don't know the actual focus is moved to
2626  // different window.  If IMEs try to get the newest window level after the
2627  // focus change, we return the window level of the XUL <panel>'s widget.
2628  // Therefore, let's emulate the native focus change.  Then, IMEs can refresh
2629  // the stored window level.
2630  [inputContext deactivate];
2631  [inputContext activate];
2632
2633  NS_OBJC_END_TRY_ABORT_BLOCK;
2634}
2635
2636void
2637IMEInputHandler::DiscardIMEComposition()
2638{
2639  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2640
2641  MOZ_LOG(gLog, LogLevel::Info,
2642    ("%p IMEInputHandler::DiscardIMEComposition, "
2643     "Destroyed()=%s, IsFocused()=%s, mView=%p, inputContext=%p",
2644     this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
2645     mView, mView ? [mView inputContext] : nullptr));
2646
2647  if (Destroyed()) {
2648    return;
2649  }
2650
2651  if (!IsFocused()) {
2652    // retry at next focus event
2653    mPendingMethods |= kDiscardIMEComposition;
2654    return;
2655  }
2656
2657  NS_ENSURE_TRUE_VOID(mView);
2658  NSTextInputContext* inputContext = [mView inputContext];
2659  NS_ENSURE_TRUE_VOID(inputContext);
2660  mIgnoreIMECommit = true;
2661  [inputContext discardMarkedText];
2662  mIgnoreIMECommit = false;
2663
2664  NS_OBJC_END_TRY_ABORT_BLOCK
2665}
2666
2667void
2668IMEInputHandler::SyncASCIICapableOnly()
2669{
2670  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2671
2672  MOZ_LOG(gLog, LogLevel::Info,
2673    ("%p IMEInputHandler::SyncASCIICapableOnly, "
2674     "Destroyed()=%s, IsFocused()=%s, mIsASCIICapableOnly=%s, "
2675     "GetCurrentTSMDocumentID()=%p",
2676     this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
2677     TrueOrFalse(mIsASCIICapableOnly), GetCurrentTSMDocumentID()));
2678
2679  if (Destroyed()) {
2680    return;
2681  }
2682
2683  if (!IsFocused()) {
2684    // retry at next focus event
2685    mPendingMethods |= kSyncASCIICapableOnly;
2686    return;
2687  }
2688
2689  TSMDocumentID doc = GetCurrentTSMDocumentID();
2690  if (!doc) {
2691    // retry
2692    mPendingMethods |= kSyncASCIICapableOnly;
2693    NS_WARNING("Application is active but there is no active document");
2694    ResetTimer();
2695    return;
2696  }
2697
2698  if (mIsASCIICapableOnly) {
2699    CFArrayRef ASCIICapableTISList = ::TISCreateASCIICapableInputSourceList();
2700    ::TSMSetDocumentProperty(doc,
2701                             kTSMDocumentEnabledInputSourcesPropertyTag,
2702                             sizeof(CFArrayRef),
2703                             &ASCIICapableTISList);
2704    ::CFRelease(ASCIICapableTISList);
2705  } else {
2706    ::TSMRemoveDocumentProperty(doc,
2707                                kTSMDocumentEnabledInputSourcesPropertyTag);
2708  }
2709
2710  NS_OBJC_END_TRY_ABORT_BLOCK;
2711}
2712
2713void
2714IMEInputHandler::ResetTimer()
2715{
2716  NS_ASSERTION(mPendingMethods != 0,
2717               "There are not pending methods, why this is called?");
2718  if (mTimer) {
2719    mTimer->Cancel();
2720  } else {
2721    mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
2722    NS_ENSURE_TRUE(mTimer, );
2723  }
2724  mTimer->InitWithFuncCallback(FlushPendingMethods, this, 0,
2725                               nsITimer::TYPE_ONE_SHOT);
2726}
2727
2728void
2729IMEInputHandler::ExecutePendingMethods()
2730{
2731  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2732
2733  if (mTimer) {
2734    mTimer->Cancel();
2735    mTimer = nullptr;
2736  }
2737
2738  if (![[NSApplication sharedApplication] isActive]) {
2739    mIsInFocusProcessing = false;
2740    // If we're not active, we should retry at focus event
2741    return;
2742  }
2743
2744  uint32_t pendingMethods = mPendingMethods;
2745  // First, reset the pending method flags because if each methods cannot
2746  // run now, they can reentry to the pending flags by theirselves.
2747  mPendingMethods = 0;
2748
2749  if (pendingMethods & kDiscardIMEComposition)
2750    DiscardIMEComposition();
2751  if (pendingMethods & kSyncASCIICapableOnly)
2752    SyncASCIICapableOnly();
2753  if (pendingMethods & kNotifyIMEOfFocusChangeInGecko) {
2754    NotifyIMEOfFocusChangeInGecko();
2755  }
2756
2757  mIsInFocusProcessing = false;
2758
2759  NS_OBJC_END_TRY_ABORT_BLOCK;
2760}
2761
2762#pragma mark -
2763
2764
2765/******************************************************************************
2766 *
2767 * IMEInputHandler implementation (native event handlers)
2768 *
2769 ******************************************************************************/
2770
2771TextRangeType
2772IMEInputHandler::ConvertToTextRangeType(uint32_t aUnderlineStyle,
2773                                        NSRange& aSelectedRange)
2774{
2775  MOZ_LOG(gLog, LogLevel::Info,
2776    ("%p IMEInputHandler::ConvertToTextRangeType, "
2777     "aUnderlineStyle=%llu, aSelectedRange.length=%llu,",
2778     this, aUnderlineStyle, aSelectedRange.length));
2779
2780  // We assume that aUnderlineStyle is NSUnderlineStyleSingle or
2781  // NSUnderlineStyleThick.  NSUnderlineStyleThick should indicate a selected
2782  // clause.  Otherwise, should indicate non-selected clause.
2783
2784  if (aSelectedRange.length == 0) {
2785    switch (aUnderlineStyle) {
2786      case NSUnderlineStyleSingle:
2787        return TextRangeType::eRawClause;
2788      case NSUnderlineStyleThick:
2789        return TextRangeType::eSelectedRawClause;
2790      default:
2791        NS_WARNING("Unexpected line style");
2792        return TextRangeType::eSelectedRawClause;
2793    }
2794  }
2795
2796  switch (aUnderlineStyle) {
2797    case NSUnderlineStyleSingle:
2798      return TextRangeType::eConvertedClause;
2799    case NSUnderlineStyleThick:
2800      return TextRangeType::eSelectedClause;
2801    default:
2802      NS_WARNING("Unexpected line style");
2803      return TextRangeType::eSelectedClause;
2804  }
2805}
2806
2807uint32_t
2808IMEInputHandler::GetRangeCount(NSAttributedString *aAttrString)
2809{
2810  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
2811
2812  // Iterate through aAttrString for the NSUnderlineStyleAttributeName and
2813  // count the different segments adjusting limitRange as we go.
2814  uint32_t count = 0;
2815  NSRange effectiveRange;
2816  NSRange limitRange = NSMakeRange(0, [aAttrString length]);
2817  while (limitRange.length > 0) {
2818    [aAttrString  attribute:NSUnderlineStyleAttributeName
2819                    atIndex:limitRange.location
2820      longestEffectiveRange:&effectiveRange
2821                    inRange:limitRange];
2822    limitRange =
2823      NSMakeRange(NSMaxRange(effectiveRange),
2824                  NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
2825    count++;
2826  }
2827
2828  MOZ_LOG(gLog, LogLevel::Info,
2829    ("%p IMEInputHandler::GetRangeCount, aAttrString=\"%s\", count=%llu",
2830     this, GetCharacters([aAttrString string]), count));
2831
2832  return count;
2833
2834  NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
2835}
2836
2837already_AddRefed<mozilla::TextRangeArray>
2838IMEInputHandler::CreateTextRangeArray(NSAttributedString *aAttrString,
2839                                      NSRange& aSelectedRange)
2840{
2841  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
2842
2843  RefPtr<mozilla::TextRangeArray> textRangeArray =
2844                                      new mozilla::TextRangeArray();
2845
2846  // Note that we shouldn't append ranges when composition string
2847  // is empty because it may cause TextComposition confused.
2848  if (![aAttrString length]) {
2849    return textRangeArray.forget();
2850  }
2851
2852  // Convert the Cocoa range into the TextRange Array used in Gecko.
2853  // Iterate through the attributed string and map the underline attribute to
2854  // Gecko IME textrange attributes.  We may need to change the code here if
2855  // we change the implementation of validAttributesForMarkedText.
2856  NSRange limitRange = NSMakeRange(0, [aAttrString length]);
2857  uint32_t rangeCount = GetRangeCount(aAttrString);
2858  for (uint32_t i = 0; i < rangeCount && limitRange.length > 0; i++) {
2859    NSRange effectiveRange;
2860    id attributeValue = [aAttrString attribute:NSUnderlineStyleAttributeName
2861                                       atIndex:limitRange.location
2862                         longestEffectiveRange:&effectiveRange
2863                                       inRange:limitRange];
2864
2865    TextRange range;
2866    range.mStartOffset = effectiveRange.location;
2867    range.mEndOffset = NSMaxRange(effectiveRange);
2868    range.mRangeType =
2869      ConvertToTextRangeType([attributeValue intValue], aSelectedRange);
2870    textRangeArray->AppendElement(range);
2871
2872    MOZ_LOG(gLog, LogLevel::Info,
2873      ("%p IMEInputHandler::CreateTextRangeArray, "
2874       "range={ mStartOffset=%llu, mEndOffset=%llu, mRangeType=%s }",
2875       this, range.mStartOffset, range.mEndOffset,
2876       ToChar(range.mRangeType)));
2877
2878    limitRange =
2879      NSMakeRange(NSMaxRange(effectiveRange),
2880                  NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
2881  }
2882
2883  // Get current caret position.
2884  TextRange range;
2885  range.mStartOffset = aSelectedRange.location + aSelectedRange.length;
2886  range.mEndOffset = range.mStartOffset;
2887  range.mRangeType = TextRangeType::eCaret;
2888  textRangeArray->AppendElement(range);
2889
2890  MOZ_LOG(gLog, LogLevel::Info,
2891    ("%p IMEInputHandler::CreateTextRangeArray, "
2892     "range={ mStartOffset=%llu, mEndOffset=%llu, mRangeType=%s }",
2893     this, range.mStartOffset, range.mEndOffset,
2894     ToChar(range.mRangeType)));
2895
2896  return textRangeArray.forget();
2897
2898  NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL;
2899}
2900
2901bool
2902IMEInputHandler::DispatchCompositionStartEvent()
2903{
2904  MOZ_LOG(gLog, LogLevel::Info,
2905    ("%p IMEInputHandler::DispatchCompositionStartEvent, "
2906     "mSelectedRange={ location=%llu, length=%llu }, Destroyed()=%s, "
2907     "mView=%p, mWidget=%p, inputContext=%p, mIsIMEComposing=%s",
2908     this,  SelectedRange().location, mSelectedRange.length,
2909     TrueOrFalse(Destroyed()), mView, mWidget,
2910     mView ? [mView inputContext] : nullptr, TrueOrFalse(mIsIMEComposing)));
2911
2912  RefPtr<IMEInputHandler> kungFuDeathGrip(this);
2913
2914  nsresult rv = mDispatcher->BeginNativeInputTransaction();
2915  if (NS_WARN_IF(NS_FAILED(rv))) {
2916    MOZ_LOG(gLog, LogLevel::Error,
2917      ("%p IMEInputHandler::DispatchCompositionStartEvent, "
2918       "FAILED, due to BeginNativeInputTransaction() failure", this));
2919    return false;
2920  }
2921
2922  NS_ASSERTION(!mIsIMEComposing, "There is a composition already");
2923  mIsIMEComposing = true;
2924
2925  nsEventStatus status;
2926  rv = mDispatcher->StartComposition(status);
2927  if (NS_WARN_IF(NS_FAILED(rv))) {
2928    MOZ_LOG(gLog, LogLevel::Error,
2929      ("%p IMEInputHandler::DispatchCompositionStartEvent, "
2930       "FAILED, due to StartComposition() failure", this));
2931    return false;
2932  }
2933
2934  if (Destroyed()) {
2935    MOZ_LOG(gLog, LogLevel::Info,
2936      ("%p IMEInputHandler::DispatchCompositionStartEvent, "
2937       "destroyed by compositionstart event", this));
2938    return false;
2939  }
2940
2941  // FYI: compositionstart may cause committing composition by the webapp.
2942  if (!mIsIMEComposing) {
2943    return false;
2944  }
2945
2946  // FYI: The selection range might have been modified by a compositionstart
2947  //      event handler.
2948  mIMECompositionStart = SelectedRange().location;
2949  return true;
2950}
2951
2952bool
2953IMEInputHandler::DispatchCompositionChangeEvent(const nsString& aText,
2954                                                NSAttributedString* aAttrString,
2955                                                NSRange& aSelectedRange)
2956{
2957  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
2958
2959  MOZ_LOG(gLog, LogLevel::Info,
2960    ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
2961     "aText=\"%s\", aAttrString=\"%s\", "
2962     "aSelectedRange={ location=%llu, length=%llu }, Destroyed()=%s, mView=%p, "
2963     "mWidget=%p, inputContext=%p, mIsIMEComposing=%s",
2964     this, NS_ConvertUTF16toUTF8(aText).get(),
2965     GetCharacters([aAttrString string]),
2966     aSelectedRange.location, aSelectedRange.length,
2967     TrueOrFalse(Destroyed()), mView, mWidget,
2968     mView ? [mView inputContext] : nullptr, TrueOrFalse(mIsIMEComposing)));
2969
2970  NS_ENSURE_TRUE(!Destroyed(), false);
2971
2972  NS_ASSERTION(mIsIMEComposing, "We're not in composition");
2973
2974  RefPtr<IMEInputHandler> kungFuDeathGrip(this);
2975
2976  nsresult rv = mDispatcher->BeginNativeInputTransaction();
2977  if (NS_WARN_IF(NS_FAILED(rv))) {
2978    MOZ_LOG(gLog, LogLevel::Error,
2979      ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
2980       "FAILED, due to BeginNativeInputTransaction() failure", this));
2981    return false;
2982  }
2983
2984  RefPtr<TextRangeArray> rangeArray =
2985    CreateTextRangeArray(aAttrString, aSelectedRange);
2986
2987  rv = mDispatcher->SetPendingComposition(aText, rangeArray);
2988  if (NS_WARN_IF(NS_FAILED(rv))) {
2989    MOZ_LOG(gLog, LogLevel::Error,
2990      ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
2991       "FAILED, due to SetPendingComposition() failure", this));
2992    return false;
2993  }
2994
2995  mSelectedRange.location = mIMECompositionStart + aSelectedRange.location;
2996  mSelectedRange.length = aSelectedRange.length;
2997
2998  if (mIMECompositionString) {
2999    [mIMECompositionString release];
3000  }
3001  mIMECompositionString = [[aAttrString string] retain];
3002
3003  nsEventStatus status;
3004  rv = mDispatcher->FlushPendingComposition(status);
3005  if (NS_WARN_IF(NS_FAILED(rv))) {
3006    MOZ_LOG(gLog, LogLevel::Error,
3007      ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
3008       "FAILED, due to FlushPendingComposition() failure", this));
3009    return false;
3010  }
3011
3012  if (Destroyed()) {
3013    MOZ_LOG(gLog, LogLevel::Info,
3014      ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
3015       "destroyed by compositionchange event", this));
3016    return false;
3017  }
3018
3019  // FYI: compositionstart may cause committing composition by the webapp.
3020  return mIsIMEComposing;
3021
3022  NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
3023}
3024
3025bool
3026IMEInputHandler::DispatchCompositionCommitEvent(const nsAString* aCommitString)
3027{
3028  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
3029
3030  MOZ_LOG(gLog, LogLevel::Info,
3031    ("%p IMEInputHandler::DispatchCompositionCommitEvent, "
3032     "aCommitString=0x%p (\"%s\"), Destroyed()=%s, mView=%p, mWidget=%p, "
3033     "inputContext=%p, mIsIMEComposing=%s",
3034     this, aCommitString,
3035     aCommitString ? NS_ConvertUTF16toUTF8(*aCommitString).get() : "",
3036     TrueOrFalse(Destroyed()), mView, mWidget,
3037     mView ? [mView inputContext] : nullptr, TrueOrFalse(mIsIMEComposing)));
3038
3039  NS_ASSERTION(mIsIMEComposing, "We're not in composition");
3040
3041  RefPtr<IMEInputHandler> kungFuDeathGrip(this);
3042
3043  if (!Destroyed()) {
3044    // IME may query selection immediately after this, however, in e10s mode,
3045    // OnSelectionChange() will be called asynchronously.  Until then, we
3046    // should emulate expected selection range if the webapp does nothing.
3047    mSelectedRange.location = mIMECompositionStart;
3048    if (aCommitString) {
3049      mSelectedRange.location += aCommitString->Length();
3050    } else if (mIMECompositionString) {
3051      nsAutoString commitString;
3052      nsCocoaUtils::GetStringForNSString(mIMECompositionString, commitString);
3053      mSelectedRange.location += commitString.Length();
3054    }
3055    mSelectedRange.length = 0;
3056
3057    nsresult rv = mDispatcher->BeginNativeInputTransaction();
3058    if (NS_WARN_IF(NS_FAILED(rv))) {
3059      MOZ_LOG(gLog, LogLevel::Error,
3060        ("%p IMEInputHandler::DispatchCompositionCommitEvent, "
3061         "FAILED, due to BeginNativeInputTransaction() failure", this));
3062    } else {
3063      nsEventStatus status;
3064      rv = mDispatcher->CommitComposition(status, aCommitString);
3065      if (NS_WARN_IF(NS_FAILED(rv))) {
3066        MOZ_LOG(gLog, LogLevel::Error,
3067          ("%p IMEInputHandler::DispatchCompositionCommitEvent, "
3068           "FAILED, due to BeginNativeInputTransaction() failure", this));
3069      }
3070    }
3071  }
3072
3073  mIsIMEComposing = false;
3074  mIMECompositionStart = UINT32_MAX;
3075  if (mIMECompositionString) {
3076    [mIMECompositionString release];
3077    mIMECompositionString = nullptr;
3078  }
3079
3080  if (Destroyed()) {
3081    MOZ_LOG(gLog, LogLevel::Info,
3082      ("%p IMEInputHandler::DispatchCompositionCommitEvent, "
3083       "destroyed by compositioncommit event", this));
3084    return false;
3085  }
3086
3087  return true;
3088
3089  NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
3090}
3091
3092void
3093IMEInputHandler::InsertTextAsCommittingComposition(
3094                   NSAttributedString* aAttrString,
3095                   NSRange* aReplacementRange)
3096{
3097  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3098
3099  MOZ_LOG(gLog, LogLevel::Info,
3100    ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
3101     "aAttrString=\"%s\", aReplacementRange=%p { location=%llu, length=%llu }, "
3102     "Destroyed()=%s, IsIMEComposing()=%s, "
3103     "mMarkedRange={ location=%llu, length=%llu }",
3104     this, GetCharacters([aAttrString string]), aReplacementRange,
3105     aReplacementRange ? aReplacementRange->location : 0,
3106     aReplacementRange ? aReplacementRange->length : 0,
3107     TrueOrFalse(Destroyed()), TrueOrFalse(IsIMEComposing()),
3108     mMarkedRange.location, mMarkedRange.length));
3109
3110  if (IgnoreIMECommit()) {
3111    MOZ_CRASH("IMEInputHandler::InsertTextAsCommittingComposition() must not"
3112              "be called while canceling the composition");
3113  }
3114
3115  if (Destroyed()) {
3116    return;
3117  }
3118
3119  // First, commit current composition with the latest composition string if the
3120  // replacement range is different from marked range.
3121  if (IsIMEComposing() && aReplacementRange &&
3122      aReplacementRange->location != NSNotFound &&
3123      !NSEqualRanges(MarkedRange(), *aReplacementRange)) {
3124    if (!DispatchCompositionCommitEvent()) {
3125      MOZ_LOG(gLog, LogLevel::Info,
3126        ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
3127         "destroyed by commiting composition for setting replacement range",
3128         this));
3129      return;
3130    }
3131  }
3132
3133  RefPtr<IMEInputHandler> kungFuDeathGrip(this);
3134
3135  nsString str;
3136  nsCocoaUtils::GetStringForNSString([aAttrString string], str);
3137
3138  if (!IsIMEComposing()) {
3139    // If there is no selection and replacement range is specified, set the
3140    // range as selection.
3141    if (aReplacementRange && aReplacementRange->location != NSNotFound &&
3142        !NSEqualRanges(SelectedRange(), *aReplacementRange)) {
3143      NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
3144    }
3145
3146    if (!DispatchCompositionStartEvent()) {
3147      MOZ_LOG(gLog, LogLevel::Info,
3148        ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
3149         "cannot continue handling composition after compositionstart", this));
3150      return;
3151    }
3152  }
3153
3154  if (!DispatchCompositionCommitEvent(&str)) {
3155    MOZ_LOG(gLog, LogLevel::Info,
3156      ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
3157       "destroyed by compositioncommit event", this));
3158    return;
3159  }
3160
3161  mMarkedRange = NSMakeRange(NSNotFound, 0);
3162
3163  NS_OBJC_END_TRY_ABORT_BLOCK;
3164}
3165
3166void
3167IMEInputHandler::SetMarkedText(NSAttributedString* aAttrString,
3168                               NSRange& aSelectedRange,
3169                               NSRange* aReplacementRange)
3170{
3171  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3172
3173  KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
3174
3175  MOZ_LOG(gLog, LogLevel::Info,
3176    ("%p IMEInputHandler::SetMarkedText, "
3177     "aAttrString=\"%s\", aSelectedRange={ location=%llu, length=%llu }, "
3178     "aReplacementRange=%p { location=%llu, length=%llu }, "
3179     "Destroyed()=%s, IgnoreIMEComposition()=%s, IsIMEComposing()=%s, "
3180     "mMarkedRange={ location=%llu, length=%llu }, keyevent=%p, "
3181     "keydownHandled=%s, keypressDispatched=%s, causedOtherKeyEvents=%s, "
3182     "compositionDispatched=%s",
3183     this, GetCharacters([aAttrString string]),
3184     aSelectedRange.location, aSelectedRange.length, aReplacementRange,
3185     aReplacementRange ? aReplacementRange->location : 0,
3186     aReplacementRange ? aReplacementRange->length : 0,
3187     TrueOrFalse(Destroyed()), TrueOrFalse(IgnoreIMEComposition()),
3188     TrueOrFalse(IsIMEComposing()),
3189     mMarkedRange.location, mMarkedRange.length,
3190     currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr,
3191     currentKeyEvent ?
3192       TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
3193     currentKeyEvent ?
3194       TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A",
3195     currentKeyEvent ?
3196       TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A",
3197     currentKeyEvent ?
3198       TrueOrFalse(currentKeyEvent->mCompositionDispatched) : "N/A"));
3199
3200  // If SetMarkedText() is called during handling a key press, that means that
3201  // the key event caused this composition.  So, keypress event shouldn't
3202  // be dispatched later, let's mark the key event causing composition event.
3203  if (currentKeyEvent) {
3204    currentKeyEvent->mCompositionDispatched = true;
3205  }
3206
3207  if (Destroyed() || IgnoreIMEComposition()) {
3208    return;
3209  }
3210
3211  RefPtr<IMEInputHandler> kungFuDeathGrip(this);
3212
3213  // First, commit current composition with the latest composition string if the
3214  // replacement range is different from marked range.
3215  if (IsIMEComposing() && aReplacementRange &&
3216      aReplacementRange->location != NSNotFound &&
3217      !NSEqualRanges(MarkedRange(), *aReplacementRange)) {
3218    AutoRestore<bool> ignoreIMECommit(mIgnoreIMECommit);
3219    mIgnoreIMECommit = false;
3220    if (!DispatchCompositionCommitEvent()) {
3221      MOZ_LOG(gLog, LogLevel::Info,
3222        ("%p IMEInputHandler::SetMarkedText, "
3223         "destroyed by commiting composition for setting replacement range",
3224         this));
3225      return;
3226    }
3227  }
3228
3229  nsString str;
3230  nsCocoaUtils::GetStringForNSString([aAttrString string], str);
3231
3232  mMarkedRange.length = str.Length();
3233
3234  if (!IsIMEComposing() && !str.IsEmpty()) {
3235    // If there is no selection and replacement range is specified, set the
3236    // range as selection.
3237    if (aReplacementRange && aReplacementRange->location != NSNotFound &&
3238        !NSEqualRanges(SelectedRange(), *aReplacementRange)) {
3239      NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
3240    }
3241
3242    mMarkedRange.location = SelectedRange().location;
3243
3244    if (!DispatchCompositionStartEvent()) {
3245      MOZ_LOG(gLog, LogLevel::Info,
3246        ("%p IMEInputHandler::SetMarkedText, cannot continue handling "
3247         "composition after dispatching compositionstart", this));
3248      return;
3249    }
3250  }
3251
3252  if (!str.IsEmpty()) {
3253    if (!DispatchCompositionChangeEvent(str, aAttrString, aSelectedRange)) {
3254      MOZ_LOG(gLog, LogLevel::Info,
3255        ("%p IMEInputHandler::SetMarkedText, cannot continue handling "
3256         "composition after dispatching compositionchange", this));
3257    }
3258    return;
3259  }
3260
3261  // If the composition string becomes empty string, we should commit
3262  // current composition.
3263  if (!DispatchCompositionCommitEvent(&EmptyString())) {
3264    MOZ_LOG(gLog, LogLevel::Info,
3265      ("%p IMEInputHandler::SetMarkedText, "
3266       "destroyed by compositioncommit event", this));
3267  }
3268
3269  NS_OBJC_END_TRY_ABORT_BLOCK;
3270}
3271
3272NSAttributedString*
3273IMEInputHandler::GetAttributedSubstringFromRange(NSRange& aRange,
3274                                                 NSRange* aActualRange)
3275{
3276  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
3277
3278  MOZ_LOG(gLog, LogLevel::Info,
3279    ("%p IMEInputHandler::GetAttributedSubstringFromRange, "
3280     "aRange={ location=%llu, length=%llu }, aActualRange=%p, Destroyed()=%s",
3281     this, aRange.location, aRange.length, aActualRange,
3282     TrueOrFalse(Destroyed())));
3283
3284  if (aActualRange) {
3285    *aActualRange = NSMakeRange(NSNotFound, 0);
3286  }
3287
3288  if (Destroyed() || aRange.location == NSNotFound || aRange.length == 0) {
3289    return nil;
3290  }
3291
3292  RefPtr<IMEInputHandler> kungFuDeathGrip(this);
3293
3294  // If we're in composing, the queried range may be in the composition string.
3295  // In such case, we should use mIMECompositionString since if the composition
3296  // string is handled by a remote process, the content cache may be out of
3297  // date.
3298  // XXX Should we set composition string attributes?  Although, Blink claims
3299  //     that some attributes of marked text are supported, but they return
3300  //     just marked string without any style.  So, let's keep current behavior
3301  //     at least for now.
3302  NSUInteger compositionLength =
3303    mIMECompositionString ? [mIMECompositionString length] : 0;
3304  if (mIMECompositionStart != UINT32_MAX &&
3305      mIMECompositionStart >= aRange.location &&
3306      mIMECompositionStart + compositionLength <=
3307        aRange.location + aRange.length) {
3308    NSRange range =
3309      NSMakeRange(aRange.location - mIMECompositionStart, aRange.length);
3310    NSString* nsstr = [mIMECompositionString substringWithRange:range];
3311    NSMutableAttributedString* result =
3312      [[[NSMutableAttributedString alloc] initWithString:nsstr
3313                                              attributes:nil] autorelease];
3314    // XXX We cannot return font information in this case.  However, this
3315    //     case must occur only when IME tries to confirm if composing string
3316    //     is handled as expected.
3317    if (aActualRange) {
3318      *aActualRange = aRange;
3319    }
3320
3321    if (MOZ_LOG_TEST(gLog, LogLevel::Info)) {
3322      nsAutoString str;
3323      nsCocoaUtils::GetStringForNSString(nsstr, str);
3324      MOZ_LOG(gLog, LogLevel::Info,
3325        ("%p IMEInputHandler::GetAttributedSubstringFromRange, "
3326         "computed with mIMECompositionString (result string=\"%s\")",
3327         this, NS_ConvertUTF16toUTF8(str).get()));
3328    }
3329    return result;
3330  }
3331
3332  nsAutoString str;
3333  WidgetQueryContentEvent textContent(true, eQueryTextContent, mWidget);
3334  WidgetQueryContentEvent::Options options;
3335  int64_t startOffset = aRange.location;
3336  if (IsIMEComposing()) {
3337    // The composition may be at different offset from the selection start
3338    // offset at dispatching compositionstart because start of composition
3339    // is fixed when composition string becomes non-empty in the editor.
3340    // Therefore, we need to use query event which is relative to insertion
3341    // point.
3342    options.mRelativeToInsertionPoint = true;
3343    startOffset -= mIMECompositionStart;
3344  }
3345  textContent.InitForQueryTextContent(startOffset, aRange.length, options);
3346  textContent.RequestFontRanges();
3347  DispatchEvent(textContent);
3348
3349  MOZ_LOG(gLog, LogLevel::Info,
3350    ("%p IMEInputHandler::GetAttributedSubstringFromRange, "
3351     "textContent={ mSucceeded=%s, mReply={ mString=\"%s\", mOffset=%u } }",
3352     this, TrueOrFalse(textContent.mSucceeded),
3353     NS_ConvertUTF16toUTF8(textContent.mReply.mString).get(),
3354     textContent.mReply.mOffset));
3355
3356  if (!textContent.mSucceeded) {
3357    return nil;
3358  }
3359
3360  // We don't set vertical information at this point.  If required,
3361  // OS will calls drawsVerticallyForCharacterAtIndex.
3362  NSMutableAttributedString* result =
3363    nsCocoaUtils::GetNSMutableAttributedString(textContent.mReply.mString,
3364                                               textContent.mReply.mFontRanges,
3365                                               false,
3366                                               mWidget->BackingScaleFactor());
3367  if (aActualRange) {
3368    aActualRange->location = textContent.mReply.mOffset;
3369    aActualRange->length = textContent.mReply.mString.Length();
3370  }
3371  return result;
3372
3373  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
3374}
3375
3376bool
3377IMEInputHandler::HasMarkedText()
3378{
3379  MOZ_LOG(gLog, LogLevel::Info,
3380    ("%p IMEInputHandler::HasMarkedText, "
3381     "mMarkedRange={ location=%llu, length=%llu }",
3382     this, mMarkedRange.location, mMarkedRange.length));
3383
3384  return (mMarkedRange.location != NSNotFound) && (mMarkedRange.length != 0);
3385}
3386
3387NSRange
3388IMEInputHandler::MarkedRange()
3389{
3390  MOZ_LOG(gLog, LogLevel::Info,
3391    ("%p IMEInputHandler::MarkedRange, "
3392     "mMarkedRange={ location=%llu, length=%llu }",
3393     this, mMarkedRange.location, mMarkedRange.length));
3394
3395  if (!HasMarkedText()) {
3396    return NSMakeRange(NSNotFound, 0);
3397  }
3398  return mMarkedRange;
3399}
3400
3401NSRange
3402IMEInputHandler::SelectedRange()
3403{
3404  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
3405
3406  MOZ_LOG(gLog, LogLevel::Info,
3407    ("%p IMEInputHandler::SelectedRange, Destroyed()=%s, mSelectedRange={ "
3408     "location=%llu, length=%llu }",
3409     this, TrueOrFalse(Destroyed()), mSelectedRange.location,
3410     mSelectedRange.length));
3411
3412  if (Destroyed()) {
3413    return mSelectedRange;
3414  }
3415
3416  if (mSelectedRange.location != NSNotFound) {
3417    MOZ_ASSERT(mIMEHasFocus);
3418    return mSelectedRange;
3419  }
3420
3421  RefPtr<IMEInputHandler> kungFuDeathGrip(this);
3422
3423  WidgetQueryContentEvent selection(true, eQuerySelectedText, mWidget);
3424  DispatchEvent(selection);
3425
3426  MOZ_LOG(gLog, LogLevel::Info,
3427    ("%p IMEInputHandler::SelectedRange, selection={ mSucceeded=%s, "
3428     "mReply={ mOffset=%u, mString.Length()=%u } }",
3429     this, TrueOrFalse(selection.mSucceeded), selection.mReply.mOffset,
3430     selection.mReply.mString.Length()));
3431
3432  if (!selection.mSucceeded) {
3433    return mSelectedRange;
3434  }
3435
3436  mWritingMode = selection.GetWritingMode();
3437  mRangeForWritingMode = NSMakeRange(selection.mReply.mOffset,
3438                                     selection.mReply.mString.Length());
3439
3440  if (mIMEHasFocus) {
3441    mSelectedRange = mRangeForWritingMode;
3442  }
3443
3444  return mRangeForWritingMode;
3445
3446  NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(mSelectedRange);
3447}
3448
3449bool
3450IMEInputHandler::DrawsVerticallyForCharacterAtIndex(uint32_t aCharIndex)
3451{
3452  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
3453
3454  if (Destroyed()) {
3455    return false;
3456  }
3457
3458  if (mRangeForWritingMode.location == NSNotFound) {
3459    // Update cached writing-mode value for the current selection.
3460    SelectedRange();
3461  }
3462
3463  if (aCharIndex < mRangeForWritingMode.location ||
3464      aCharIndex > mRangeForWritingMode.location + mRangeForWritingMode.length) {
3465    // It's not clear to me whether this ever happens in practice, but if an
3466    // IME ever wants to query writing mode at an offset outside the current
3467    // selection, the writing-mode value may not be correct for the index.
3468    // In that case, use FirstRectForCharacterRange to get a fresh value.
3469    // This does more work than strictly necessary (we don't need the rect here),
3470    // but should be a rare case.
3471    NS_WARNING("DrawsVerticallyForCharacterAtIndex not using cached writing mode");
3472    NSRange range = NSMakeRange(aCharIndex, 1);
3473    NSRange actualRange;
3474    FirstRectForCharacterRange(range, &actualRange);
3475  }
3476
3477  return mWritingMode.IsVertical();
3478
3479  NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
3480}
3481
3482NSRect
3483IMEInputHandler::FirstRectForCharacterRange(NSRange& aRange,
3484                                            NSRange* aActualRange)
3485{
3486  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
3487
3488  MOZ_LOG(gLog, LogLevel::Info,
3489    ("%p IMEInputHandler::FirstRectForCharacterRange, Destroyed()=%s, "
3490     "aRange={ location=%llu, length=%llu }, aActualRange=%p }",
3491     this, TrueOrFalse(Destroyed()), aRange.location, aRange.length,
3492     aActualRange));
3493
3494  // XXX this returns first character rect or caret rect, it is limitation of
3495  // now. We need more work for returns first line rect. But current
3496  // implementation is enough for IMEs.
3497
3498  NSRect rect = NSMakeRect(0.0, 0.0, 0.0, 0.0);
3499  NSRange actualRange = NSMakeRange(NSNotFound, 0);
3500  if (aActualRange) {
3501    *aActualRange = actualRange;
3502  }
3503  if (Destroyed() || aRange.location == NSNotFound) {
3504    return rect;
3505  }
3506
3507  RefPtr<IMEInputHandler> kungFuDeathGrip(this);
3508
3509  LayoutDeviceIntRect r;
3510  bool useCaretRect = (aRange.length == 0);
3511  if (!useCaretRect) {
3512    WidgetQueryContentEvent charRect(true, eQueryTextRect, mWidget);
3513    WidgetQueryContentEvent::Options options;
3514    int64_t startOffset = aRange.location;
3515    if (IsIMEComposing()) {
3516      // The composition may be at different offset from the selection start
3517      // offset at dispatching compositionstart because start of composition
3518      // is fixed when composition string becomes non-empty in the editor.
3519      // Therefore, we need to use query event which is relative to insertion
3520      // point.
3521      options.mRelativeToInsertionPoint = true;
3522      startOffset -= mIMECompositionStart;
3523    }
3524    charRect.InitForQueryTextRect(startOffset, 1, options);
3525    DispatchEvent(charRect);
3526    if (charRect.mSucceeded) {
3527      r = charRect.mReply.mRect;
3528      actualRange.location = charRect.mReply.mOffset;
3529      actualRange.length = charRect.mReply.mString.Length();
3530      mWritingMode = charRect.GetWritingMode();
3531      mRangeForWritingMode = actualRange;
3532    } else {
3533      useCaretRect = true;
3534    }
3535  }
3536
3537  if (useCaretRect) {
3538    WidgetQueryContentEvent caretRect(true, eQueryCaretRect, mWidget);
3539    WidgetQueryContentEvent::Options options;
3540    int64_t startOffset = aRange.location;
3541    if (IsIMEComposing()) {
3542      // The composition may be at different offset from the selection start
3543      // offset at dispatching compositionstart because start of composition
3544      // is fixed when composition string becomes non-empty in the editor.
3545      // Therefore, we need to use query event which is relative to insertion
3546      // point.
3547      options.mRelativeToInsertionPoint = true;
3548      startOffset -= mIMECompositionStart;
3549    }
3550    caretRect.InitForQueryCaretRect(startOffset, options);
3551    DispatchEvent(caretRect);
3552    if (!caretRect.mSucceeded) {
3553      return rect;
3554    }
3555    r = caretRect.mReply.mRect;
3556    r.width = 0;
3557    actualRange.location = caretRect.mReply.mOffset;
3558    actualRange.length = 0;
3559  }
3560
3561  nsIWidget* rootWidget = mWidget->GetTopLevelWidget();
3562  NSWindow* rootWindow =
3563    static_cast<NSWindow*>(rootWidget->GetNativeData(NS_NATIVE_WINDOW));
3564  NSView* rootView =
3565    static_cast<NSView*>(rootWidget->GetNativeData(NS_NATIVE_WIDGET));
3566  if (!rootWindow || !rootView) {
3567    return rect;
3568  }
3569  rect = nsCocoaUtils::DevPixelsToCocoaPoints(r, mWidget->BackingScaleFactor());
3570  rect = [rootView convertRect:rect toView:nil];
3571  rect.origin = nsCocoaUtils::ConvertPointToScreen(rootWindow, rect.origin);
3572
3573  if (aActualRange) {
3574    *aActualRange = actualRange;
3575  }
3576
3577  MOZ_LOG(gLog, LogLevel::Info,
3578    ("%p IMEInputHandler::FirstRectForCharacterRange, "
3579     "useCaretRect=%s rect={ x=%f, y=%f, width=%f, height=%f }, "
3580     "actualRange={ location=%llu, length=%llu }",
3581     this, TrueOrFalse(useCaretRect), rect.origin.x, rect.origin.y,
3582     rect.size.width, rect.size.height, actualRange.location,
3583     actualRange.length));
3584
3585  return rect;
3586
3587  NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakeRect(0.0, 0.0, 0.0, 0.0));
3588}
3589
3590NSUInteger
3591IMEInputHandler::CharacterIndexForPoint(NSPoint& aPoint)
3592{
3593  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
3594
3595  MOZ_LOG(gLog, LogLevel::Info,
3596    ("%p IMEInputHandler::CharacterIndexForPoint, aPoint={ x=%f, y=%f }",
3597     this, aPoint.x, aPoint.y));
3598
3599  NSWindow* mainWindow = [NSApp mainWindow];
3600  if (!mWidget || !mainWindow) {
3601    return NSNotFound;
3602  }
3603
3604  WidgetQueryContentEvent charAt(true, eQueryCharacterAtPoint, mWidget);
3605  NSPoint ptInWindow = nsCocoaUtils::ConvertPointFromScreen(mainWindow, aPoint);
3606  NSPoint ptInView = [mView convertPoint:ptInWindow fromView:nil];
3607  charAt.mRefPoint.x =
3608    static_cast<int32_t>(ptInView.x) * mWidget->BackingScaleFactor();
3609  charAt.mRefPoint.y =
3610    static_cast<int32_t>(ptInView.y) * mWidget->BackingScaleFactor();
3611  mWidget->DispatchWindowEvent(charAt);
3612  if (!charAt.mSucceeded ||
3613      charAt.mReply.mOffset == WidgetQueryContentEvent::NOT_FOUND ||
3614      charAt.mReply.mOffset >= static_cast<uint32_t>(NSNotFound)) {
3615    return NSNotFound;
3616  }
3617
3618  return charAt.mReply.mOffset;
3619
3620  NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSNotFound);
3621}
3622
3623extern "C" {
3624extern NSString *NSTextInputReplacementRangeAttributeName;
3625}
3626
3627NSArray*
3628IMEInputHandler::GetValidAttributesForMarkedText()
3629{
3630  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
3631
3632  MOZ_LOG(gLog, LogLevel::Info,
3633    ("%p IMEInputHandler::GetValidAttributesForMarkedText", this));
3634
3635  // Return same attributes as Chromium (see render_widget_host_view_mac.mm)
3636  // because most IMEs must be tested with Safari (OS default) and Chrome
3637  // (having most market share).  Therefore, we need to follow their behavior.
3638  // XXX It might be better to reuse an array instance for this result because
3639  //     this may be called a lot.  Note that Chromium does so.
3640  return [NSArray arrayWithObjects:NSUnderlineStyleAttributeName,
3641                                   NSUnderlineColorAttributeName,
3642                                   NSMarkedClauseSegmentAttributeName,
3643                                   NSTextInputReplacementRangeAttributeName,
3644                                   nil];
3645
3646  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
3647}
3648
3649
3650#pragma mark -
3651
3652
3653/******************************************************************************
3654 *
3655 *  IMEInputHandler implementation #2
3656 *
3657 ******************************************************************************/
3658
3659IMEInputHandler::IMEInputHandler(nsChildView* aWidget,
3660                                 NSView<mozView> *aNativeView)
3661  : TextInputHandlerBase(aWidget, aNativeView)
3662  , mPendingMethods(0)
3663  , mIMECompositionString(nullptr)
3664  , mIMECompositionStart(UINT32_MAX)
3665  , mIsIMEComposing(false)
3666  , mIsIMEEnabled(true)
3667  , mIsASCIICapableOnly(false)
3668  , mIgnoreIMECommit(false)
3669  , mIsInFocusProcessing(false)
3670  , mIMEHasFocus(false)
3671{
3672  InitStaticMembers();
3673
3674  mMarkedRange.location = NSNotFound;
3675  mMarkedRange.length = 0;
3676  mSelectedRange.location = NSNotFound;
3677  mSelectedRange.length = 0;
3678}
3679
3680IMEInputHandler::~IMEInputHandler()
3681{
3682  if (mTimer) {
3683    mTimer->Cancel();
3684    mTimer = nullptr;
3685  }
3686  if (sFocusedIMEHandler == this) {
3687    sFocusedIMEHandler = nullptr;
3688  }
3689  if (mIMECompositionString) {
3690    [mIMECompositionString release];
3691    mIMECompositionString = nullptr;
3692  }
3693}
3694
3695void
3696IMEInputHandler::OnFocusChangeInGecko(bool aFocus)
3697{
3698  MOZ_LOG(gLog, LogLevel::Info,
3699    ("%p IMEInputHandler::OnFocusChangeInGecko, aFocus=%s, Destroyed()=%s, "
3700     "sFocusedIMEHandler=%p",
3701     this, TrueOrFalse(aFocus), TrueOrFalse(Destroyed()), sFocusedIMEHandler));
3702
3703  mSelectedRange.location = NSNotFound; // Marking dirty
3704  mIMEHasFocus = aFocus;
3705
3706  // This is called when the native focus is changed and when the native focus
3707  // isn't changed but the focus is changed in Gecko.
3708  if (!aFocus) {
3709    if (sFocusedIMEHandler == this)
3710      sFocusedIMEHandler = nullptr;
3711    return;
3712  }
3713
3714  sFocusedIMEHandler = this;
3715  mIsInFocusProcessing = true;
3716
3717  // We need to notify IME of focus change in Gecko as native focus change
3718  // because the window level of the focused element in Gecko may be changed.
3719  mPendingMethods |= kNotifyIMEOfFocusChangeInGecko;
3720  ResetTimer();
3721}
3722
3723bool
3724IMEInputHandler::OnDestroyWidget(nsChildView* aDestroyingWidget)
3725{
3726  MOZ_LOG(gLog, LogLevel::Info,
3727    ("%p IMEInputHandler::OnDestroyWidget, aDestroyingWidget=%p, "
3728     "sFocusedIMEHandler=%p, IsIMEComposing()=%s",
3729     this, aDestroyingWidget, sFocusedIMEHandler,
3730     TrueOrFalse(IsIMEComposing())));
3731
3732  // If we're not focused, the focused IMEInputHandler may have been
3733  // created by another widget/nsChildView.
3734  if (sFocusedIMEHandler && sFocusedIMEHandler != this) {
3735    sFocusedIMEHandler->OnDestroyWidget(aDestroyingWidget);
3736  }
3737
3738  if (!TextInputHandlerBase::OnDestroyWidget(aDestroyingWidget)) {
3739    return false;
3740  }
3741
3742  if (IsIMEComposing()) {
3743    // If our view is in the composition, we should clean up it.
3744    CancelIMEComposition();
3745  }
3746
3747  mSelectedRange.location = NSNotFound; // Marking dirty
3748  mIMEHasFocus = false;
3749
3750  return true;
3751}
3752
3753void
3754IMEInputHandler::SendCommittedText(NSString *aString)
3755{
3756  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3757
3758  MOZ_LOG(gLog, LogLevel::Info,
3759    ("%p IMEInputHandler::SendCommittedText, mView=%p, mWidget=%p, "
3760     "inputContext=%p, mIsIMEComposing=%s",
3761     this, mView, mWidget, mView ? [mView inputContext] : nullptr,
3762     TrueOrFalse(mIsIMEComposing), mWidget));
3763
3764  NS_ENSURE_TRUE(mWidget, );
3765  // XXX We should send the string without mView.
3766  if (!mView) {
3767    return;
3768  }
3769
3770  NSAttributedString* attrStr =
3771    [[NSAttributedString alloc] initWithString:aString];
3772  if ([mView conformsToProtocol:@protocol(NSTextInputClient)]) {
3773    NSObject<NSTextInputClient>* textInputClient =
3774      static_cast<NSObject<NSTextInputClient>*>(mView);
3775    [textInputClient insertText:attrStr
3776               replacementRange:NSMakeRange(NSNotFound, 0)];
3777  }
3778
3779  // Last resort.  If we cannot retrieve NSTextInputProtocol from mView
3780  // or blocking to call our InsertText(), we should call InsertText()
3781  // directly to commit composition forcibly.
3782  if (mIsIMEComposing) {
3783    MOZ_LOG(gLog, LogLevel::Info,
3784      ("%p IMEInputHandler::SendCommittedText, trying to insert text directly "
3785       "due to IME not calling our InsertText()", this));
3786    static_cast<TextInputHandler*>(this)->InsertText(attrStr);
3787    MOZ_ASSERT(!mIsIMEComposing);
3788  }
3789
3790  [attrStr release];
3791
3792  NS_OBJC_END_TRY_ABORT_BLOCK;
3793}
3794
3795void
3796IMEInputHandler::KillIMEComposition()
3797{
3798  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3799
3800  MOZ_LOG(gLog, LogLevel::Info,
3801    ("%p IMEInputHandler::KillIMEComposition, mView=%p, mWidget=%p, "
3802     "inputContext=%p, mIsIMEComposing=%s, "
3803     "Destroyed()=%s, IsFocused()=%s",
3804     this, mView, mWidget, mView ? [mView inputContext] : nullptr,
3805     TrueOrFalse(mIsIMEComposing), TrueOrFalse(Destroyed()),
3806     TrueOrFalse(IsFocused())));
3807
3808  if (Destroyed()) {
3809    return;
3810  }
3811
3812  if (IsFocused()) {
3813    NS_ENSURE_TRUE_VOID(mView);
3814    NSTextInputContext* inputContext = [mView inputContext];
3815    NS_ENSURE_TRUE_VOID(inputContext);
3816    [inputContext discardMarkedText];
3817    return;
3818  }
3819
3820  MOZ_LOG(gLog, LogLevel::Info,
3821    ("%p IMEInputHandler::KillIMEComposition, Pending...", this));
3822
3823  // Commit the composition internally.
3824  SendCommittedText(mIMECompositionString);
3825  NS_ASSERTION(!mIsIMEComposing, "We're still in a composition");
3826  // The pending method will be fired by the next focus event.
3827  mPendingMethods |= kDiscardIMEComposition;
3828
3829  NS_OBJC_END_TRY_ABORT_BLOCK;
3830}
3831
3832void
3833IMEInputHandler::CommitIMEComposition()
3834{
3835  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3836
3837  if (!IsIMEComposing())
3838    return;
3839
3840  MOZ_LOG(gLog, LogLevel::Info,
3841    ("%p IMEInputHandler::CommitIMEComposition, mIMECompositionString=%s",
3842     this, GetCharacters(mIMECompositionString)));
3843
3844  KillIMEComposition();
3845
3846  if (!IsIMEComposing())
3847    return;
3848
3849  // If the composition is still there, KillIMEComposition only kills the
3850  // composition in TSM.  We also need to finish the our composition too.
3851  SendCommittedText(mIMECompositionString);
3852
3853  NS_OBJC_END_TRY_ABORT_BLOCK;
3854}
3855
3856void
3857IMEInputHandler::CancelIMEComposition()
3858{
3859  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3860
3861  if (!IsIMEComposing())
3862    return;
3863
3864  MOZ_LOG(gLog, LogLevel::Info,
3865    ("%p IMEInputHandler::CancelIMEComposition, mIMECompositionString=%s",
3866     this, GetCharacters(mIMECompositionString)));
3867
3868  // For canceling the current composing, we need to ignore the param of
3869  // insertText.  But this code is ugly...
3870  mIgnoreIMECommit = true;
3871  KillIMEComposition();
3872  mIgnoreIMECommit = false;
3873
3874  if (!IsIMEComposing())
3875    return;
3876
3877  // If the composition is still there, KillIMEComposition only kills the
3878  // composition in TSM.  We also need to kill the our composition too.
3879  SendCommittedText(@"");
3880
3881  NS_OBJC_END_TRY_ABORT_BLOCK;
3882}
3883
3884bool
3885IMEInputHandler::IsFocused()
3886{
3887  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3888
3889  NS_ENSURE_TRUE(!Destroyed(), false);
3890  NSWindow* window = [mView window];
3891  NS_ENSURE_TRUE(window, false);
3892  return [window firstResponder] == mView &&
3893         [window isKeyWindow] &&
3894         [[NSApplication sharedApplication] isActive];
3895
3896  NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
3897}
3898
3899bool
3900IMEInputHandler::IsIMEOpened()
3901{
3902  TISInputSourceWrapper tis;
3903  tis.InitByCurrentInputSource();
3904  return tis.IsOpenedIMEMode();
3905}
3906
3907void
3908IMEInputHandler::SetASCIICapableOnly(bool aASCIICapableOnly)
3909{
3910  if (aASCIICapableOnly == mIsASCIICapableOnly)
3911    return;
3912
3913  CommitIMEComposition();
3914  mIsASCIICapableOnly = aASCIICapableOnly;
3915  SyncASCIICapableOnly();
3916}
3917
3918void
3919IMEInputHandler::EnableIME(bool aEnableIME)
3920{
3921  if (aEnableIME == mIsIMEEnabled)
3922    return;
3923
3924  CommitIMEComposition();
3925  mIsIMEEnabled = aEnableIME;
3926}
3927
3928void
3929IMEInputHandler::SetIMEOpenState(bool aOpenIME)
3930{
3931  if (!IsFocused() || IsIMEOpened() == aOpenIME)
3932    return;
3933
3934  if (!aOpenIME) {
3935    TISInputSourceWrapper tis;
3936    tis.InitByCurrentASCIICapableInputSource();
3937    tis.Select();
3938    return;
3939  }
3940
3941  // If we know the latest IME opened mode, we should select it.
3942  if (sLatestIMEOpenedModeInputSourceID) {
3943    TISInputSourceWrapper tis;
3944    tis.InitByInputSourceID(sLatestIMEOpenedModeInputSourceID);
3945    tis.Select();
3946    return;
3947  }
3948
3949  // XXX If the current input source is a mode of IME, we should turn on it,
3950  // but we haven't found such way...
3951
3952  // Finally, we should refer the system locale but this is a little expensive,
3953  // we shouldn't retry this (if it was succeeded, we already set
3954  // sLatestIMEOpenedModeInputSourceID at that time).
3955  static bool sIsPrefferredIMESearched = false;
3956  if (sIsPrefferredIMESearched)
3957    return;
3958  sIsPrefferredIMESearched = true;
3959  OpenSystemPreferredLanguageIME();
3960}
3961
3962void
3963IMEInputHandler::OpenSystemPreferredLanguageIME()
3964{
3965  MOZ_LOG(gLog, LogLevel::Info,
3966    ("%p IMEInputHandler::OpenSystemPreferredLanguageIME", this));
3967
3968  CFArrayRef langList = ::CFLocaleCopyPreferredLanguages();
3969  if (!langList) {
3970    MOZ_LOG(gLog, LogLevel::Info,
3971      ("%p IMEInputHandler::OpenSystemPreferredLanguageIME, langList is NULL",
3972       this));
3973    return;
3974  }
3975  CFIndex count = ::CFArrayGetCount(langList);
3976  for (CFIndex i = 0; i < count; i++) {
3977    CFLocaleRef locale =
3978      ::CFLocaleCreate(kCFAllocatorDefault,
3979          static_cast<CFStringRef>(::CFArrayGetValueAtIndex(langList, i)));
3980    if (!locale) {
3981      continue;
3982    }
3983
3984    bool changed = false;
3985    CFStringRef lang = static_cast<CFStringRef>(
3986      ::CFLocaleGetValue(locale, kCFLocaleLanguageCode));
3987    NS_ASSERTION(lang, "lang is null");
3988    if (lang) {
3989      TISInputSourceWrapper tis;
3990      tis.InitByLanguage(lang);
3991      if (tis.IsOpenedIMEMode()) {
3992        if (MOZ_LOG_TEST(gLog, LogLevel::Info)) {
3993          CFStringRef foundTIS;
3994          tis.GetInputSourceID(foundTIS);
3995          MOZ_LOG(gLog, LogLevel::Info,
3996            ("%p IMEInputHandler::OpenSystemPreferredLanguageIME, "
3997             "foundTIS=%s, lang=%s",
3998             this, GetCharacters(foundTIS), GetCharacters(lang)));
3999        }
4000        tis.Select();
4001        changed = true;
4002      }
4003    }
4004    ::CFRelease(locale);
4005    if (changed) {
4006      break;
4007    }
4008  }
4009  ::CFRelease(langList);
4010}
4011
4012void
4013IMEInputHandler::OnSelectionChange(const IMENotification& aIMENotification)
4014{
4015  MOZ_LOG(gLog, LogLevel::Info,
4016    ("%p IMEInputHandler::OnSelectionChange", this));
4017
4018  if (aIMENotification.mSelectionChangeData.mOffset == UINT32_MAX) {
4019    mSelectedRange.location = NSNotFound;
4020    mSelectedRange.length = 0;
4021    mRangeForWritingMode.location = NSNotFound;
4022    mRangeForWritingMode.length = 0;
4023    return;
4024  }
4025
4026  mWritingMode = aIMENotification.mSelectionChangeData.GetWritingMode();
4027  mRangeForWritingMode =
4028    NSMakeRange(aIMENotification.mSelectionChangeData.mOffset,
4029                aIMENotification.mSelectionChangeData.Length());
4030  if (mIMEHasFocus) {
4031    mSelectedRange = mRangeForWritingMode;
4032  }
4033}
4034
4035bool
4036IMEInputHandler::OnHandleEvent(NSEvent* aEvent)
4037{
4038  if (!IsFocused()) {
4039    return false;
4040  }
4041  NSTextInputContext* inputContext = [mView inputContext];
4042  return [inputContext handleEvent:aEvent];
4043}
4044
4045#pragma mark -
4046
4047
4048/******************************************************************************
4049 *
4050 *  TextInputHandlerBase implementation
4051 *
4052 ******************************************************************************/
4053
4054int32_t TextInputHandlerBase::sSecureEventInputCount = 0;
4055
4056NS_IMPL_ISUPPORTS(TextInputHandlerBase,
4057                  TextEventDispatcherListener,
4058                  nsISupportsWeakReference)
4059
4060TextInputHandlerBase::TextInputHandlerBase(nsChildView* aWidget,
4061                                           NSView<mozView> *aNativeView)
4062  : mWidget(aWidget)
4063  , mDispatcher(aWidget->GetTextEventDispatcher())
4064{
4065  gHandlerInstanceCount++;
4066  mView = [aNativeView retain];
4067}
4068
4069TextInputHandlerBase::~TextInputHandlerBase()
4070{
4071  [mView release];
4072  if (--gHandlerInstanceCount == 0) {
4073    TISInputSourceWrapper::Shutdown();
4074  }
4075}
4076
4077bool
4078TextInputHandlerBase::OnDestroyWidget(nsChildView* aDestroyingWidget)
4079{
4080  MOZ_LOG(gLog, LogLevel::Info,
4081    ("%p TextInputHandlerBase::OnDestroyWidget, "
4082     "aDestroyingWidget=%p, mWidget=%p",
4083     this, aDestroyingWidget, mWidget));
4084
4085  if (aDestroyingWidget != mWidget) {
4086    return false;
4087  }
4088
4089  mWidget = nullptr;
4090  mDispatcher = nullptr;
4091  return true;
4092}
4093
4094bool
4095TextInputHandlerBase::DispatchEvent(WidgetGUIEvent& aEvent)
4096{
4097  return mWidget->DispatchWindowEvent(aEvent);
4098}
4099
4100void
4101TextInputHandlerBase::InitKeyEvent(NSEvent *aNativeKeyEvent,
4102                                   WidgetKeyboardEvent& aKeyEvent,
4103                                   const nsAString* aInsertString)
4104{
4105  NS_ASSERTION(aNativeKeyEvent, "aNativeKeyEvent must not be NULL");
4106
4107  if (mKeyboardOverride.mOverrideEnabled) {
4108    TISInputSourceWrapper tis;
4109    tis.InitByLayoutID(mKeyboardOverride.mKeyboardLayout, true);
4110    tis.InitKeyEvent(aNativeKeyEvent, aKeyEvent, aInsertString);
4111    return;
4112  }
4113  TISInputSourceWrapper::CurrentInputSource().
4114    InitKeyEvent(aNativeKeyEvent, aKeyEvent, aInsertString);
4115}
4116
4117nsresult
4118TextInputHandlerBase::SynthesizeNativeKeyEvent(
4119                        int32_t aNativeKeyboardLayout,
4120                        int32_t aNativeKeyCode,
4121                        uint32_t aModifierFlags,
4122                        const nsAString& aCharacters,
4123                        const nsAString& aUnmodifiedCharacters)
4124{
4125  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
4126
4127  static const uint32_t sModifierFlagMap[][2] = {
4128    { nsIWidget::CAPS_LOCK,       NSAlphaShiftKeyMask },
4129    { nsIWidget::SHIFT_L,         NSShiftKeyMask      | 0x0002 },
4130    { nsIWidget::SHIFT_R,         NSShiftKeyMask      | 0x0004 },
4131    { nsIWidget::CTRL_L,          NSControlKeyMask    | 0x0001 },
4132    { nsIWidget::CTRL_R,          NSControlKeyMask    | 0x2000 },
4133    { nsIWidget::ALT_L,           NSAlternateKeyMask  | 0x0020 },
4134    { nsIWidget::ALT_R,           NSAlternateKeyMask  | 0x0040 },
4135    { nsIWidget::COMMAND_L,       NSCommandKeyMask    | 0x0008 },
4136    { nsIWidget::COMMAND_R,       NSCommandKeyMask    | 0x0010 },
4137    { nsIWidget::NUMERIC_KEY_PAD, NSNumericPadKeyMask },
4138    { nsIWidget::HELP,            NSHelpKeyMask },
4139    { nsIWidget::FUNCTION,        NSFunctionKeyMask }
4140  };
4141
4142  uint32_t modifierFlags = 0;
4143  for (uint32_t i = 0; i < ArrayLength(sModifierFlagMap); ++i) {
4144    if (aModifierFlags & sModifierFlagMap[i][0]) {
4145      modifierFlags |= sModifierFlagMap[i][1];
4146    }
4147  }
4148
4149  NSInteger windowNumber = [[mView window] windowNumber];
4150  bool sendFlagsChangedEvent = IsModifierKey(aNativeKeyCode);
4151  NSEventType eventType = sendFlagsChangedEvent ? NSFlagsChanged : NSKeyDown;
4152  NSEvent* downEvent =
4153    [NSEvent     keyEventWithType:eventType
4154                         location:NSMakePoint(0,0)
4155                    modifierFlags:modifierFlags
4156                        timestamp:0
4157                     windowNumber:windowNumber
4158                          context:[NSGraphicsContext currentContext]
4159                       characters:nsCocoaUtils::ToNSString(aCharacters)
4160      charactersIgnoringModifiers:nsCocoaUtils::ToNSString(aUnmodifiedCharacters)
4161                        isARepeat:NO
4162                          keyCode:aNativeKeyCode];
4163
4164  NSEvent* upEvent = sendFlagsChangedEvent ?
4165    nil : nsCocoaUtils::MakeNewCocoaEventWithType(NSKeyUp, downEvent);
4166
4167  if (downEvent && (sendFlagsChangedEvent || upEvent)) {
4168    KeyboardLayoutOverride currentLayout = mKeyboardOverride;
4169    mKeyboardOverride.mKeyboardLayout = aNativeKeyboardLayout;
4170    mKeyboardOverride.mOverrideEnabled = true;
4171    [NSApp sendEvent:downEvent];
4172    if (upEvent) {
4173      [NSApp sendEvent:upEvent];
4174    }
4175    // processKeyDownEvent and keyUp block exceptions so we're sure to
4176    // reach here to restore mKeyboardOverride
4177    mKeyboardOverride = currentLayout;
4178  }
4179
4180  return NS_OK;
4181
4182  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
4183}
4184
4185NSInteger
4186TextInputHandlerBase::GetWindowLevel()
4187{
4188  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
4189
4190  MOZ_LOG(gLog, LogLevel::Info,
4191    ("%p TextInputHandlerBase::GetWindowLevel, Destryoed()=%s",
4192     this, TrueOrFalse(Destroyed())));
4193
4194  if (Destroyed()) {
4195    return NSNormalWindowLevel;
4196  }
4197
4198  // When an <input> element on a XUL <panel> is focused, the actual focused view
4199  // is the panel's parent view (mView). But the editor is displayed on the
4200  // popped-up widget's view (editorView).  We want the latter's window level.
4201  NSView<mozView>* editorView = mWidget->GetEditorView();
4202  NS_ENSURE_TRUE(editorView, NSNormalWindowLevel);
4203  NSInteger windowLevel = [[editorView window] level];
4204
4205  MOZ_LOG(gLog, LogLevel::Info,
4206    ("%p TextInputHandlerBase::GetWindowLevel, windowLevel=%s (%X)",
4207     this, GetWindowLevelName(windowLevel), windowLevel));
4208
4209  return windowLevel;
4210
4211  NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSNormalWindowLevel);
4212}
4213
4214NS_IMETHODIMP
4215TextInputHandlerBase::AttachNativeKeyEvent(WidgetKeyboardEvent& aKeyEvent)
4216{
4217  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
4218
4219  // Don't try to replace a native event if one already exists.
4220  // OS X doesn't have an OS modifier, can't make a native event.
4221  if (aKeyEvent.mNativeKeyEvent || aKeyEvent.mModifiers & MODIFIER_OS) {
4222    return NS_OK;
4223  }
4224
4225  MOZ_LOG(gLog, LogLevel::Info,
4226    ("%p TextInputHandlerBase::AttachNativeKeyEvent, key=0x%X, char=0x%X, "
4227     "mod=0x%X", this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode,
4228     aKeyEvent.mModifiers));
4229
4230  NSEventType eventType;
4231  if (aKeyEvent.mMessage == eKeyUp) {
4232    eventType = NSKeyUp;
4233  } else {
4234    eventType = NSKeyDown;
4235  }
4236
4237  static const uint32_t sModifierFlagMap[][2] = {
4238    { MODIFIER_SHIFT,    NSShiftKeyMask },
4239    { MODIFIER_CONTROL,  NSControlKeyMask },
4240    { MODIFIER_ALT,      NSAlternateKeyMask },
4241    { MODIFIER_ALTGRAPH, NSAlternateKeyMask },
4242    { MODIFIER_META,     NSCommandKeyMask },
4243    { MODIFIER_CAPSLOCK, NSAlphaShiftKeyMask },
4244    { MODIFIER_NUMLOCK,  NSNumericPadKeyMask }
4245  };
4246
4247  NSUInteger modifierFlags = 0;
4248  for (uint32_t i = 0; i < ArrayLength(sModifierFlagMap); ++i) {
4249    if (aKeyEvent.mModifiers & sModifierFlagMap[i][0]) {
4250      modifierFlags |= sModifierFlagMap[i][1];
4251    }
4252  }
4253
4254  NSInteger windowNumber = [[mView window] windowNumber];
4255
4256  NSString* characters;
4257  if (aKeyEvent.mCharCode) {
4258    characters = [NSString stringWithCharacters:
4259      reinterpret_cast<const unichar*>(&(aKeyEvent.mCharCode)) length:1];
4260  } else {
4261    uint32_t cocoaCharCode =
4262      nsCocoaUtils::ConvertGeckoKeyCodeToMacCharCode(aKeyEvent.mKeyCode);
4263    characters = [NSString stringWithCharacters:
4264      reinterpret_cast<const unichar*>(&cocoaCharCode) length:1];
4265  }
4266
4267  aKeyEvent.mNativeKeyEvent =
4268    [NSEvent     keyEventWithType:eventType
4269                         location:NSMakePoint(0,0)
4270                    modifierFlags:modifierFlags
4271                        timestamp:0
4272                     windowNumber:windowNumber
4273                          context:[NSGraphicsContext currentContext]
4274                       characters:characters
4275      charactersIgnoringModifiers:characters
4276                        isARepeat:NO
4277                          keyCode:0]; // Native key code not currently needed
4278
4279  return NS_OK;
4280
4281  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
4282}
4283
4284bool
4285TextInputHandlerBase::SetSelection(NSRange& aRange)
4286{
4287  MOZ_ASSERT(!Destroyed());
4288
4289  RefPtr<TextInputHandlerBase> kungFuDeathGrip(this);
4290  WidgetSelectionEvent selectionEvent(true, eSetSelection, mWidget);
4291  selectionEvent.mOffset = aRange.location;
4292  selectionEvent.mLength = aRange.length;
4293  selectionEvent.mReversed = false;
4294  selectionEvent.mExpandToClusterBoundary = false;
4295  DispatchEvent(selectionEvent);
4296  NS_ENSURE_TRUE(selectionEvent.mSucceeded, false);
4297  return !Destroyed();
4298}
4299
4300/* static */ bool
4301TextInputHandlerBase::IsPrintableChar(char16_t aChar)
4302{
4303  return (aChar >= 0x20 && aChar <= 0x7E) || aChar >= 0xA0;
4304}
4305
4306
4307/* static */ bool
4308TextInputHandlerBase::IsSpecialGeckoKey(UInt32 aNativeKeyCode)
4309{
4310  // this table is used to determine which keys are special and should not
4311  // generate a charCode
4312  switch (aNativeKeyCode) {
4313    // modifiers - we don't get separate events for these yet
4314    case kVK_Escape:
4315    case kVK_Shift:
4316    case kVK_RightShift:
4317    case kVK_Command:
4318    case kVK_RightCommand:
4319    case kVK_CapsLock:
4320    case kVK_Control:
4321    case kVK_RightControl:
4322    case kVK_Option:
4323    case kVK_RightOption:
4324    case kVK_ANSI_KeypadClear:
4325    case kVK_Function:
4326
4327    // function keys
4328    case kVK_F1:
4329    case kVK_F2:
4330    case kVK_F3:
4331    case kVK_F4:
4332    case kVK_F5:
4333    case kVK_F6:
4334    case kVK_F7:
4335    case kVK_F8:
4336    case kVK_F9:
4337    case kVK_F10:
4338    case kVK_F11:
4339    case kVK_F12:
4340    case kVK_PC_Pause:
4341    case kVK_PC_ScrollLock:
4342    case kVK_PC_PrintScreen:
4343    case kVK_F16:
4344    case kVK_F17:
4345    case kVK_F18:
4346    case kVK_F19:
4347
4348    case kVK_PC_Insert:
4349    case kVK_PC_Delete:
4350    case kVK_Tab:
4351    case kVK_PC_Backspace:
4352    case kVK_PC_ContextMenu:
4353
4354    case kVK_JIS_Eisu:
4355    case kVK_JIS_Kana:
4356
4357    case kVK_Home:
4358    case kVK_End:
4359    case kVK_PageUp:
4360    case kVK_PageDown:
4361    case kVK_LeftArrow:
4362    case kVK_RightArrow:
4363    case kVK_UpArrow:
4364    case kVK_DownArrow:
4365    case kVK_Return:
4366    case kVK_ANSI_KeypadEnter:
4367    case kVK_Powerbook_KeypadEnter:
4368      return true;
4369  }
4370  return false;
4371}
4372
4373/* static */ bool
4374TextInputHandlerBase::IsNormalCharInputtingEvent(
4375                        const WidgetKeyboardEvent& aKeyEvent)
4376{
4377  // this is not character inputting event, simply.
4378  if (aKeyEvent.mNativeCharacters.IsEmpty() ||
4379      aKeyEvent.IsMeta()) {
4380    return false;
4381  }
4382  return !IsControlChar(aKeyEvent.mNativeCharacters[0]);
4383}
4384
4385/* static */ bool
4386TextInputHandlerBase::IsModifierKey(UInt32 aNativeKeyCode)
4387{
4388  switch (aNativeKeyCode) {
4389    case kVK_CapsLock:
4390    case kVK_RightCommand:
4391    case kVK_Command:
4392    case kVK_Shift:
4393    case kVK_Option:
4394    case kVK_Control:
4395    case kVK_RightShift:
4396    case kVK_RightOption:
4397    case kVK_RightControl:
4398    case kVK_Function:
4399      return true;
4400  }
4401  return false;
4402}
4403
4404/* static */ void
4405TextInputHandlerBase::EnableSecureEventInput()
4406{
4407  sSecureEventInputCount++;
4408  ::EnableSecureEventInput();
4409}
4410
4411/* static */ void
4412TextInputHandlerBase::DisableSecureEventInput()
4413{
4414  if (!sSecureEventInputCount) {
4415    return;
4416  }
4417  sSecureEventInputCount--;
4418  ::DisableSecureEventInput();
4419}
4420
4421/* static */ bool
4422TextInputHandlerBase::IsSecureEventInputEnabled()
4423{
4424  NS_ASSERTION(!!sSecureEventInputCount == !!::IsSecureEventInputEnabled(),
4425               "Some other process has enabled secure event input");
4426  return !!sSecureEventInputCount;
4427}
4428
4429/* static */ void
4430TextInputHandlerBase::EnsureSecureEventInputDisabled()
4431{
4432  while (sSecureEventInputCount) {
4433    TextInputHandlerBase::DisableSecureEventInput();
4434  }
4435}
4436
4437#pragma mark -
4438
4439
4440/******************************************************************************
4441 *
4442 *  TextInputHandlerBase::KeyEventState implementation
4443 *
4444 ******************************************************************************/
4445
4446void
4447TextInputHandlerBase::KeyEventState::InitKeyEvent(
4448                                       TextInputHandlerBase* aHandler,
4449                                       WidgetKeyboardEvent& aKeyEvent)
4450{
4451  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
4452
4453  MOZ_ASSERT(aHandler);
4454  MOZ_RELEASE_ASSERT(mKeyEvent);
4455
4456  NSEvent* nativeEvent = mKeyEvent;
4457  if (!mInsertedString.IsEmpty()) {
4458    nsAutoString unhandledString;
4459    GetUnhandledString(unhandledString);
4460    NSString* unhandledNSString =
4461      nsCocoaUtils::ToNSString(unhandledString);
4462    // If the key event's some characters were already handled by
4463    // InsertString() calls, we need to create a dummy event which doesn't
4464    // include the handled characters.
4465    nativeEvent =
4466      [NSEvent keyEventWithType:[mKeyEvent type]
4467                       location:[mKeyEvent locationInWindow]
4468                  modifierFlags:[mKeyEvent modifierFlags]
4469                      timestamp:[mKeyEvent timestamp]
4470                   windowNumber:[mKeyEvent windowNumber]
4471                        context:[mKeyEvent context]
4472                     characters:unhandledNSString
4473    charactersIgnoringModifiers:[mKeyEvent charactersIgnoringModifiers]
4474                      isARepeat:[mKeyEvent isARepeat]
4475                        keyCode:[mKeyEvent keyCode]];
4476  }
4477
4478  aHandler->InitKeyEvent(nativeEvent, aKeyEvent, mInsertString);
4479
4480  NS_OBJC_END_TRY_ABORT_BLOCK;
4481}
4482
4483void
4484TextInputHandlerBase::KeyEventState::GetUnhandledString(
4485                                       nsAString& aUnhandledString) const
4486{
4487  aUnhandledString.Truncate();
4488  if (NS_WARN_IF(!mKeyEvent)) {
4489    return;
4490  }
4491  nsAutoString characters;
4492  nsCocoaUtils::GetStringForNSString([mKeyEvent characters],
4493                                     characters);
4494  if (characters.IsEmpty()) {
4495    return;
4496  }
4497  if (mInsertedString.IsEmpty()) {
4498    aUnhandledString = characters;
4499    return;
4500  }
4501
4502  // The insertes string must match with the start of characters.
4503  MOZ_ASSERT(StringBeginsWith(characters, mInsertedString));
4504
4505  aUnhandledString = nsDependentSubstring(characters, mInsertedString.Length());
4506}
4507
4508#pragma mark -
4509
4510
4511/******************************************************************************
4512 *
4513 *  TextInputHandlerBase::AutoInsertStringClearer implementation
4514 *
4515 ******************************************************************************/
4516
4517TextInputHandlerBase::AutoInsertStringClearer::~AutoInsertStringClearer()
4518{
4519  if (mState && mState->mInsertString) {
4520    // If inserting string is a part of characters of the event,
4521    // we should record it as inserted string.
4522    nsAutoString characters;
4523    nsCocoaUtils::GetStringForNSString([mState->mKeyEvent characters],
4524                                       characters);
4525    nsAutoString insertedString(mState->mInsertedString);
4526    insertedString += *mState->mInsertString;
4527    if (StringBeginsWith(characters, insertedString)) {
4528      mState->mInsertedString = insertedString;
4529    }
4530  }
4531  if (mState) {
4532    mState->mInsertString = nullptr;
4533  }
4534}
4535