Bug 1755924 [wpt PR 32876] - Handle resumed blocks that get sliced by floats correctl...
[gecko.git] / widget / cocoa / TextInputHandler.mm
bloba6d9dd3c029c552fcd237d98a51a19156d6ae992
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 et tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "TextInputHandler.h"
9 #include "mozilla/Logging.h"
11 #include "mozilla/ArrayUtils.h"
12 #include "mozilla/AutoRestore.h"
13 #include "mozilla/MiscEvents.h"
14 #include "mozilla/MouseEvents.h"
15 #include "mozilla/StaticPrefs_intl.h"
16 #include "mozilla/Telemetry.h"
17 #include "mozilla/TextEventDispatcher.h"
18 #include "mozilla/TextEvents.h"
19 #include "mozilla/ToString.h"
21 #include "nsChildView.h"
22 #include "nsCocoaFeatures.h"
23 #include "nsObjCExceptions.h"
24 #include "nsBidiUtils.h"
25 #include "nsToolkit.h"
26 #include "nsCocoaUtils.h"
27 #include "WidgetUtils.h"
28 #include "nsPrintfCString.h"
30 using namespace mozilla;
31 using namespace mozilla::widget;
33 // For collecting other people's log, tell them `MOZ_LOG=IMEHandler:4,sync`
34 // rather than `MOZ_LOG=IMEHandler:5,sync` since using `5` may create too
35 // big file.
36 // Therefore you shouldn't use `LogLevel::Verbose` for logging usual behavior.
37 mozilla::LazyLogModule gIMELog("IMEHandler");
39 // For collecting other people's log, tell them `MOZ_LOG=KeyboardHandler:4,sync`
40 // rather than `MOZ_LOG=KeyboardHandler:5,sync` since using `5` may create too
41 // big file.
42 // Therefore you shouldn't use `LogLevel::Verbose` for logging usual behavior.
43 mozilla::LazyLogModule gKeyLog("KeyboardHandler");
45 // The behavior of `TextInputHandler` class is important both for logging
46 // keyboard handler and IME handler.  Therefore, the behavior is logged when
47 // either `IMEHandler` or `KeyboardHandler` is set to `MOZ_LOG`.  Therefore,
48 // you may not need to tell people `MOZ_LOG=IMEHandler:4,KeyboardHandler:4,sync`.
49 #define MOZ_LOG_KEY_OR_IME(aLogLevel, aArgs) \
50   MOZ_LOG(MOZ_LOG_TEST(gIMELog, aLogLevel) ? gIMELog : gKeyLog, aLogLevel, aArgs)
52 static const char* OnOrOff(bool aBool) { return aBool ? "ON" : "off"; }
54 static const char* TrueOrFalse(bool aBool) { return aBool ? "TRUE" : "FALSE"; }
56 static const char* GetKeyNameForNativeKeyCode(unsigned short aNativeKeyCode) {
57   switch (aNativeKeyCode) {
58     case kVK_Escape:
59       return "Escape";
60     case kVK_RightCommand:
61       return "Right-Command";
62     case kVK_Command:
63       return "Command";
64     case kVK_Shift:
65       return "Shift";
66     case kVK_CapsLock:
67       return "CapsLock";
68     case kVK_Option:
69       return "Option";
70     case kVK_Control:
71       return "Control";
72     case kVK_RightShift:
73       return "Right-Shift";
74     case kVK_RightOption:
75       return "Right-Option";
76     case kVK_RightControl:
77       return "Right-Control";
78     case kVK_ANSI_KeypadClear:
79       return "Clear";
81     case kVK_F1:
82       return "F1";
83     case kVK_F2:
84       return "F2";
85     case kVK_F3:
86       return "F3";
87     case kVK_F4:
88       return "F4";
89     case kVK_F5:
90       return "F5";
91     case kVK_F6:
92       return "F6";
93     case kVK_F7:
94       return "F7";
95     case kVK_F8:
96       return "F8";
97     case kVK_F9:
98       return "F9";
99     case kVK_F10:
100       return "F10";
101     case kVK_F11:
102       return "F11";
103     case kVK_F12:
104       return "F12";
105     case kVK_F13:
106       return "F13/PrintScreen";
107     case kVK_F14:
108       return "F14/ScrollLock";
109     case kVK_F15:
110       return "F15/Pause";
112     case kVK_ANSI_Keypad0:
113       return "NumPad-0";
114     case kVK_ANSI_Keypad1:
115       return "NumPad-1";
116     case kVK_ANSI_Keypad2:
117       return "NumPad-2";
118     case kVK_ANSI_Keypad3:
119       return "NumPad-3";
120     case kVK_ANSI_Keypad4:
121       return "NumPad-4";
122     case kVK_ANSI_Keypad5:
123       return "NumPad-5";
124     case kVK_ANSI_Keypad6:
125       return "NumPad-6";
126     case kVK_ANSI_Keypad7:
127       return "NumPad-7";
128     case kVK_ANSI_Keypad8:
129       return "NumPad-8";
130     case kVK_ANSI_Keypad9:
131       return "NumPad-9";
133     case kVK_ANSI_KeypadMultiply:
134       return "NumPad-*";
135     case kVK_ANSI_KeypadPlus:
136       return "NumPad-+";
137     case kVK_ANSI_KeypadMinus:
138       return "NumPad--";
139     case kVK_ANSI_KeypadDecimal:
140       return "NumPad-.";
141     case kVK_ANSI_KeypadDivide:
142       return "NumPad-/";
143     case kVK_ANSI_KeypadEquals:
144       return "NumPad-=";
145     case kVK_ANSI_KeypadEnter:
146       return "NumPad-Enter";
147     case kVK_Return:
148       return "Return";
149     case kVK_Powerbook_KeypadEnter:
150       return "NumPad-EnterOnPowerBook";
152     case kVK_PC_Insert:
153       return "Insert/Help";
154     case kVK_PC_Delete:
155       return "Delete";
156     case kVK_Tab:
157       return "Tab";
158     case kVK_PC_Backspace:
159       return "Backspace";
160     case kVK_Home:
161       return "Home";
162     case kVK_End:
163       return "End";
164     case kVK_PageUp:
165       return "PageUp";
166     case kVK_PageDown:
167       return "PageDown";
168     case kVK_LeftArrow:
169       return "LeftArrow";
170     case kVK_RightArrow:
171       return "RightArrow";
172     case kVK_UpArrow:
173       return "UpArrow";
174     case kVK_DownArrow:
175       return "DownArrow";
176     case kVK_PC_ContextMenu:
177       return "ContextMenu";
179     case kVK_Function:
180       return "Function";
181     case kVK_VolumeUp:
182       return "VolumeUp";
183     case kVK_VolumeDown:
184       return "VolumeDown";
185     case kVK_Mute:
186       return "Mute";
188     case kVK_ISO_Section:
189       return "ISO_Section";
191     case kVK_JIS_Yen:
192       return "JIS_Yen";
193     case kVK_JIS_Underscore:
194       return "JIS_Underscore";
195     case kVK_JIS_KeypadComma:
196       return "JIS_KeypadComma";
197     case kVK_JIS_Eisu:
198       return "JIS_Eisu";
199     case kVK_JIS_Kana:
200       return "JIS_Kana";
202     case kVK_ANSI_A:
203       return "A";
204     case kVK_ANSI_B:
205       return "B";
206     case kVK_ANSI_C:
207       return "C";
208     case kVK_ANSI_D:
209       return "D";
210     case kVK_ANSI_E:
211       return "E";
212     case kVK_ANSI_F:
213       return "F";
214     case kVK_ANSI_G:
215       return "G";
216     case kVK_ANSI_H:
217       return "H";
218     case kVK_ANSI_I:
219       return "I";
220     case kVK_ANSI_J:
221       return "J";
222     case kVK_ANSI_K:
223       return "K";
224     case kVK_ANSI_L:
225       return "L";
226     case kVK_ANSI_M:
227       return "M";
228     case kVK_ANSI_N:
229       return "N";
230     case kVK_ANSI_O:
231       return "O";
232     case kVK_ANSI_P:
233       return "P";
234     case kVK_ANSI_Q:
235       return "Q";
236     case kVK_ANSI_R:
237       return "R";
238     case kVK_ANSI_S:
239       return "S";
240     case kVK_ANSI_T:
241       return "T";
242     case kVK_ANSI_U:
243       return "U";
244     case kVK_ANSI_V:
245       return "V";
246     case kVK_ANSI_W:
247       return "W";
248     case kVK_ANSI_X:
249       return "X";
250     case kVK_ANSI_Y:
251       return "Y";
252     case kVK_ANSI_Z:
253       return "Z";
255     case kVK_ANSI_1:
256       return "1";
257     case kVK_ANSI_2:
258       return "2";
259     case kVK_ANSI_3:
260       return "3";
261     case kVK_ANSI_4:
262       return "4";
263     case kVK_ANSI_5:
264       return "5";
265     case kVK_ANSI_6:
266       return "6";
267     case kVK_ANSI_7:
268       return "7";
269     case kVK_ANSI_8:
270       return "8";
271     case kVK_ANSI_9:
272       return "9";
273     case kVK_ANSI_0:
274       return "0";
275     case kVK_ANSI_Equal:
276       return "Equal";
277     case kVK_ANSI_Minus:
278       return "Minus";
279     case kVK_ANSI_RightBracket:
280       return "RightBracket";
281     case kVK_ANSI_LeftBracket:
282       return "LeftBracket";
283     case kVK_ANSI_Quote:
284       return "Quote";
285     case kVK_ANSI_Semicolon:
286       return "Semicolon";
287     case kVK_ANSI_Backslash:
288       return "Backslash";
289     case kVK_ANSI_Comma:
290       return "Comma";
291     case kVK_ANSI_Slash:
292       return "Slash";
293     case kVK_ANSI_Period:
294       return "Period";
295     case kVK_ANSI_Grave:
296       return "Grave";
298     default:
299       return "undefined";
300   }
303 static const char* GetCharacters(const nsAString& aString) {
304   if (aString.IsEmpty()) {
305     return "";
306   }
307   nsAutoString escapedStr;
308   for (uint32_t i = 0; i < aString.Length(); i++) {
309     char16_t ch = aString.CharAt(i);
310     if (ch < 0x20) {
311       nsPrintfCString utf8str("(U+%04X)", ch);
312       escapedStr += NS_ConvertUTF8toUTF16(utf8str);
313     } else if (ch <= 0x7E) {
314       escapedStr += ch;
315     } else {
316       nsPrintfCString utf8str("(U+%04X)", ch);
317       escapedStr += ch;
318       escapedStr += NS_ConvertUTF8toUTF16(utf8str);
319     }
320   }
322   // the result will be freed automatically by cocoa.
323   NSString* result = nsCocoaUtils::ToNSString(escapedStr);
324   return [result UTF8String];
327 static const char* GetCharacters(const NSString* aString) {
328   nsAutoString str;
329   nsCocoaUtils::GetStringForNSString(aString, str);
330   return GetCharacters(str);
333 static const char* GetCharacters(const CFStringRef aString) {
334   const NSString* str = reinterpret_cast<const NSString*>(aString);
335   return GetCharacters(str);
338 static const char* GetNativeKeyEventType(NSEvent* aNativeEvent) {
339   switch ([aNativeEvent type]) {
340     case NSEventTypeKeyDown:
341       return "NSEventTypeKeyDown";
342     case NSEventTypeKeyUp:
343       return "NSEventTypeKeyUp";
344     case NSEventTypeFlagsChanged:
345       return "NSEventTypeFlagsChanged";
346     default:
347       return "not key event";
348   }
351 static const char* GetGeckoKeyEventType(const WidgetEvent& aEvent) {
352   switch (aEvent.mMessage) {
353     case eKeyDown:
354       return "eKeyDown";
355     case eKeyUp:
356       return "eKeyUp";
357     case eKeyPress:
358       return "eKeyPress";
359     default:
360       return "not key event";
361   }
364 static const char* GetWindowLevelName(NSInteger aWindowLevel) {
365   switch (aWindowLevel) {
366     case kCGBaseWindowLevelKey:
367       return "kCGBaseWindowLevelKey (NSNormalWindowLevel)";
368     case kCGMinimumWindowLevelKey:
369       return "kCGMinimumWindowLevelKey";
370     case kCGDesktopWindowLevelKey:
371       return "kCGDesktopWindowLevelKey";
372     case kCGBackstopMenuLevelKey:
373       return "kCGBackstopMenuLevelKey";
374     case kCGNormalWindowLevelKey:
375       return "kCGNormalWindowLevelKey";
376     case kCGFloatingWindowLevelKey:
377       return "kCGFloatingWindowLevelKey (NSFloatingWindowLevel)";
378     case kCGTornOffMenuWindowLevelKey:
379       return "kCGTornOffMenuWindowLevelKey (NSSubmenuWindowLevel, NSTornOffMenuWindowLevel)";
380     case kCGDockWindowLevelKey:
381       return "kCGDockWindowLevelKey (NSDockWindowLevel)";
382     case kCGMainMenuWindowLevelKey:
383       return "kCGMainMenuWindowLevelKey (NSMainMenuWindowLevel)";
384     case kCGStatusWindowLevelKey:
385       return "kCGStatusWindowLevelKey (NSStatusWindowLevel)";
386     case kCGModalPanelWindowLevelKey:
387       return "kCGModalPanelWindowLevelKey (NSModalPanelWindowLevel)";
388     case kCGPopUpMenuWindowLevelKey:
389       return "kCGPopUpMenuWindowLevelKey (NSPopUpMenuWindowLevel)";
390     case kCGDraggingWindowLevelKey:
391       return "kCGDraggingWindowLevelKey";
392     case kCGScreenSaverWindowLevelKey:
393       return "kCGScreenSaverWindowLevelKey (NSScreenSaverWindowLevel)";
394     case kCGMaximumWindowLevelKey:
395       return "kCGMaximumWindowLevelKey";
396     case kCGOverlayWindowLevelKey:
397       return "kCGOverlayWindowLevelKey";
398     case kCGHelpWindowLevelKey:
399       return "kCGHelpWindowLevelKey";
400     case kCGUtilityWindowLevelKey:
401       return "kCGUtilityWindowLevelKey";
402     case kCGDesktopIconWindowLevelKey:
403       return "kCGDesktopIconWindowLevelKey";
404     case kCGCursorWindowLevelKey:
405       return "kCGCursorWindowLevelKey";
406     case kCGNumberOfWindowLevelKeys:
407       return "kCGNumberOfWindowLevelKeys";
408     default:
409       return "unknown window level";
410   }
413 static bool IsControlChar(uint32_t aCharCode) { return aCharCode < ' ' || aCharCode == 0x7F; }
415 static uint32_t gHandlerInstanceCount = 0;
417 static void EnsureToLogAllKeyboardLayoutsAndIMEs() {
418   static bool sDone = false;
419   if (!sDone) {
420     sDone = true;
421     TextInputHandler::DebugPrintAllKeyboardLayouts();
422     IMEInputHandler::DebugPrintAllIMEModes();
423   }
426 inline NSRange MakeNSRangeFrom(const Maybe<OffsetAndData<uint32_t>>& aOffsetAndData) {
427   if (aOffsetAndData.isNothing()) {
428     return NSMakeRange(NSNotFound, 0);
429   }
430   return NSMakeRange(aOffsetAndData->StartOffset(), aOffsetAndData->Length());
433 #pragma mark -
435 /******************************************************************************
437  *  TISInputSourceWrapper implementation
439  ******************************************************************************/
441 TISInputSourceWrapper* TISInputSourceWrapper::sCurrentInputSource = nullptr;
443 // static
444 TISInputSourceWrapper& TISInputSourceWrapper::CurrentInputSource() {
445   if (!sCurrentInputSource) {
446     sCurrentInputSource = new TISInputSourceWrapper();
447   }
448   if (!sCurrentInputSource->IsInitializedByCurrentInputSource()) {
449     sCurrentInputSource->InitByCurrentInputSource();
450   }
451   return *sCurrentInputSource;
454 // static
455 void TISInputSourceWrapper::Shutdown() {
456   if (!sCurrentInputSource) {
457     return;
458   }
459   sCurrentInputSource->Clear();
460   delete sCurrentInputSource;
461   sCurrentInputSource = nullptr;
464 bool TISInputSourceWrapper::TranslateToString(UInt32 aKeyCode, UInt32 aModifiers, UInt32 aKbType,
465                                               nsAString& aStr) {
466   aStr.Truncate();
468   const UCKeyboardLayout* UCKey = GetUCKeyboardLayout();
470   MOZ_LOG(gKeyLog, LogLevel::Info,
471           ("%p TISInputSourceWrapper::TranslateToString, aKeyCode=0x%X, "
472            "aModifiers=0x%X, aKbType=0x%X UCKey=%p\n    "
473            "Shift: %s, Ctrl: %s, Opt: %s, Cmd: %s, CapsLock: %s, NumLock: %s",
474            this, static_cast<unsigned int>(aKeyCode), static_cast<unsigned int>(aModifiers),
475            static_cast<unsigned int>(aKbType), UCKey, OnOrOff(aModifiers & shiftKey),
476            OnOrOff(aModifiers & controlKey), OnOrOff(aModifiers & optionKey),
477            OnOrOff(aModifiers & cmdKey), OnOrOff(aModifiers & alphaLock),
478            OnOrOff(aModifiers & kEventKeyModifierNumLockMask)));
480   NS_ENSURE_TRUE(UCKey, false);
482   UInt32 deadKeyState = 0;
483   UniCharCount len;
484   UniChar chars[5];
485   OSStatus err = ::UCKeyTranslate(UCKey, aKeyCode, kUCKeyActionDown, aModifiers >> 8, aKbType,
486                                   kUCKeyTranslateNoDeadKeysMask, &deadKeyState, 5, &len, chars);
488   MOZ_LOG(gKeyLog, LogLevel::Info,
489           ("%p   TISInputSourceWrapper::TranslateToString, err=0x%X, len=%zu", this,
490            static_cast<int>(err), len));
492   NS_ENSURE_TRUE(err == noErr, false);
493   if (len == 0) {
494     return true;
495   }
496   if (!aStr.SetLength(len, fallible)) {
497     return false;
498   }
499   NS_ASSERTION(sizeof(char16_t) == sizeof(UniChar),
500                "size of char16_t and size of UniChar are different");
501   memcpy(aStr.BeginWriting(), chars, len * sizeof(char16_t));
503   MOZ_LOG(gKeyLog, LogLevel::Info,
504           ("%p   TISInputSourceWrapper::TranslateToString, aStr=\"%s\"", this,
505            NS_ConvertUTF16toUTF8(aStr).get()));
507   return true;
510 uint32_t TISInputSourceWrapper::TranslateToChar(UInt32 aKeyCode, UInt32 aModifiers,
511                                                 UInt32 aKbType) {
512   nsAutoString str;
513   if (!TranslateToString(aKeyCode, aModifiers, aKbType, str) || str.Length() != 1) {
514     return 0;
515   }
516   return static_cast<uint32_t>(str.CharAt(0));
519 bool TISInputSourceWrapper::IsDeadKey(NSEvent* aNativeKeyEvent) {
520   if ([[aNativeKeyEvent characters] length]) {
521     return false;
522   }
524   // Assmue that if control key or command key is pressed, it's not a dead key.
525   NSUInteger cocoaState = [aNativeKeyEvent modifierFlags];
526   if (cocoaState & (NSEventModifierFlagControl | NSEventModifierFlagCommand)) {
527     return false;
528   }
530   UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
531   switch (nativeKeyCode) {
532     case kVK_ANSI_A:
533     case kVK_ANSI_B:
534     case kVK_ANSI_C:
535     case kVK_ANSI_D:
536     case kVK_ANSI_E:
537     case kVK_ANSI_F:
538     case kVK_ANSI_G:
539     case kVK_ANSI_H:
540     case kVK_ANSI_I:
541     case kVK_ANSI_J:
542     case kVK_ANSI_K:
543     case kVK_ANSI_L:
544     case kVK_ANSI_M:
545     case kVK_ANSI_N:
546     case kVK_ANSI_O:
547     case kVK_ANSI_P:
548     case kVK_ANSI_Q:
549     case kVK_ANSI_R:
550     case kVK_ANSI_S:
551     case kVK_ANSI_T:
552     case kVK_ANSI_U:
553     case kVK_ANSI_V:
554     case kVK_ANSI_W:
555     case kVK_ANSI_X:
556     case kVK_ANSI_Y:
557     case kVK_ANSI_Z:
558     case kVK_ANSI_1:
559     case kVK_ANSI_2:
560     case kVK_ANSI_3:
561     case kVK_ANSI_4:
562     case kVK_ANSI_5:
563     case kVK_ANSI_6:
564     case kVK_ANSI_7:
565     case kVK_ANSI_8:
566     case kVK_ANSI_9:
567     case kVK_ANSI_0:
568     case kVK_ANSI_Equal:
569     case kVK_ANSI_Minus:
570     case kVK_ANSI_RightBracket:
571     case kVK_ANSI_LeftBracket:
572     case kVK_ANSI_Quote:
573     case kVK_ANSI_Semicolon:
574     case kVK_ANSI_Backslash:
575     case kVK_ANSI_Comma:
576     case kVK_ANSI_Slash:
577     case kVK_ANSI_Period:
578     case kVK_ANSI_Grave:
579     case kVK_JIS_Yen:
580     case kVK_JIS_Underscore:
581       break;
582     default:
583       // Let's assume that dead key can be only a printable key in standard
584       // position.
585       return false;
586   }
588   // If TranslateToChar() returns non-zero value, that means that
589   // the key may input a character with different dead key state.
590   UInt32 kbType = GetKbdType();
591   UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState);
592   return IsDeadKey(nativeKeyCode, carbonState, kbType);
595 bool TISInputSourceWrapper::IsDeadKey(UInt32 aKeyCode, UInt32 aModifiers, UInt32 aKbType) {
596   const UCKeyboardLayout* UCKey = GetUCKeyboardLayout();
598   MOZ_LOG(gKeyLog, LogLevel::Info,
599           ("%p TISInputSourceWrapper::IsDeadKey, aKeyCode=0x%X, "
600            "aModifiers=0x%X, aKbType=0x%X UCKey=%p\n    "
601            "Shift: %s, Ctrl: %s, Opt: %s, Cmd: %s, CapsLock: %s, NumLock: %s",
602            this, static_cast<unsigned int>(aKeyCode), static_cast<unsigned int>(aModifiers),
603            static_cast<unsigned int>(aKbType), UCKey, OnOrOff(aModifiers & shiftKey),
604            OnOrOff(aModifiers & controlKey), OnOrOff(aModifiers & optionKey),
605            OnOrOff(aModifiers & cmdKey), OnOrOff(aModifiers & alphaLock),
606            OnOrOff(aModifiers & kEventKeyModifierNumLockMask)));
608   if (NS_WARN_IF(!UCKey)) {
609     return false;
610   }
612   UInt32 deadKeyState = 0;
613   UniCharCount len;
614   UniChar chars[5];
615   OSStatus err = ::UCKeyTranslate(UCKey, aKeyCode, kUCKeyActionDown, aModifiers >> 8, aKbType, 0,
616                                   &deadKeyState, 5, &len, chars);
618   MOZ_LOG(gKeyLog, LogLevel::Info,
619           ("%p   TISInputSourceWrapper::IsDeadKey, err=0x%X, "
620            "len=%zu, deadKeyState=%u",
621            this, static_cast<int>(err), len, deadKeyState));
623   if (NS_WARN_IF(err != noErr)) {
624     return false;
625   }
627   return deadKeyState != 0;
630 void TISInputSourceWrapper::InitByInputSourceID(const char* aID) {
631   Clear();
632   if (!aID) return;
634   CFStringRef idstr = ::CFStringCreateWithCString(kCFAllocatorDefault, aID, kCFStringEncodingASCII);
635   InitByInputSourceID(idstr);
636   ::CFRelease(idstr);
639 void TISInputSourceWrapper::InitByInputSourceID(const nsString& aID) {
640   Clear();
641   if (aID.IsEmpty()) return;
642   CFStringRef idstr = ::CFStringCreateWithCharacters(
643       kCFAllocatorDefault, reinterpret_cast<const UniChar*>(aID.get()), aID.Length());
644   InitByInputSourceID(idstr);
645   ::CFRelease(idstr);
648 void TISInputSourceWrapper::InitByInputSourceID(const CFStringRef aID) {
649   Clear();
650   if (!aID) return;
651   const void* keys[] = {kTISPropertyInputSourceID};
652   const void* values[] = {aID};
653   CFDictionaryRef filter = ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
654   NS_ASSERTION(filter, "failed to create the filter");
655   mInputSourceList = ::TISCreateInputSourceList(filter, true);
656   ::CFRelease(filter);
657   if (::CFArrayGetCount(mInputSourceList) > 0) {
658     mInputSource = static_cast<TISInputSourceRef>(
659         const_cast<void*>(::CFArrayGetValueAtIndex(mInputSourceList, 0)));
660     if (IsKeyboardLayout()) {
661       mKeyboardLayout = mInputSource;
662     }
663   }
666 void TISInputSourceWrapper::InitByLayoutID(SInt32 aLayoutID, bool aOverrideKeyboard) {
667   // NOTE: Doument new layout IDs in TextInputHandler.h when you add ones.
668   switch (aLayoutID) {
669     case 0:
670       InitByInputSourceID("com.apple.keylayout.US");
671       break;
672     case 1:
673       InitByInputSourceID("com.apple.keylayout.Greek");
674       break;
675     case 2:
676       InitByInputSourceID("com.apple.keylayout.German");
677       break;
678     case 3:
679       InitByInputSourceID("com.apple.keylayout.Swedish-Pro");
680       break;
681     case 4:
682       InitByInputSourceID("com.apple.keylayout.DVORAK-QWERTYCMD");
683       break;
684     case 5:
685       InitByInputSourceID("com.apple.keylayout.Thai");
686       break;
687     case 6:
688       InitByInputSourceID("com.apple.keylayout.Arabic");
689       break;
690     case 7:
691       InitByInputSourceID("com.apple.keylayout.ArabicPC");
692       break;
693     case 8:
694       InitByInputSourceID("com.apple.keylayout.French");
695       break;
696     case 9:
697       InitByInputSourceID("com.apple.keylayout.Hebrew");
698       break;
699     case 10:
700       InitByInputSourceID("com.apple.keylayout.Lithuanian");
701       break;
702     case 11:
703       InitByInputSourceID("com.apple.keylayout.Norwegian");
704       break;
705     case 12:
706       InitByInputSourceID("com.apple.keylayout.Spanish");
707       break;
708     default:
709       Clear();
710       break;
711   }
712   mOverrideKeyboard = aOverrideKeyboard;
715 void TISInputSourceWrapper::InitByCurrentInputSource() {
716   Clear();
717   mInputSource = ::TISCopyCurrentKeyboardInputSource();
718   mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride();
719   if (!mKeyboardLayout) {
720     mKeyboardLayout = ::TISCopyCurrentKeyboardLayoutInputSource();
721   }
722   // If this causes composition, the current keyboard layout may input non-ASCII
723   // characters such as Japanese Kana characters or Hangul characters.
724   // However, we need to set ASCII characters to DOM key events for consistency
725   // with other platforms.
726   if (IsOpenedIMEMode()) {
727     TISInputSourceWrapper tis(mKeyboardLayout);
728     if (!tis.IsASCIICapable()) {
729       mKeyboardLayout = ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
730     }
731   }
734 void TISInputSourceWrapper::InitByCurrentKeyboardLayout() {
735   Clear();
736   mInputSource = ::TISCopyCurrentKeyboardLayoutInputSource();
737   mKeyboardLayout = mInputSource;
740 void TISInputSourceWrapper::InitByCurrentASCIICapableInputSource() {
741   Clear();
742   mInputSource = ::TISCopyCurrentASCIICapableKeyboardInputSource();
743   mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride();
744   if (mKeyboardLayout) {
745     TISInputSourceWrapper tis(mKeyboardLayout);
746     if (!tis.IsASCIICapable()) {
747       mKeyboardLayout = nullptr;
748     }
749   }
750   if (!mKeyboardLayout) {
751     mKeyboardLayout = ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
752   }
755 void TISInputSourceWrapper::InitByCurrentASCIICapableKeyboardLayout() {
756   Clear();
757   mInputSource = ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
758   mKeyboardLayout = mInputSource;
761 void TISInputSourceWrapper::InitByCurrentInputMethodKeyboardLayoutOverride() {
762   Clear();
763   mInputSource = ::TISCopyInputMethodKeyboardLayoutOverride();
764   mKeyboardLayout = mInputSource;
767 void TISInputSourceWrapper::InitByTISInputSourceRef(TISInputSourceRef aInputSource) {
768   Clear();
769   mInputSource = aInputSource;
770   if (IsKeyboardLayout()) {
771     mKeyboardLayout = mInputSource;
772   }
775 void TISInputSourceWrapper::InitByLanguage(CFStringRef aLanguage) {
776   Clear();
777   mInputSource = ::TISCopyInputSourceForLanguage(aLanguage);
778   if (IsKeyboardLayout()) {
779     mKeyboardLayout = mInputSource;
780   }
783 const UCKeyboardLayout* TISInputSourceWrapper::GetUCKeyboardLayout() {
784   NS_ENSURE_TRUE(mKeyboardLayout, nullptr);
785   if (mUCKeyboardLayout) {
786     return mUCKeyboardLayout;
787   }
788   CFDataRef uchr = static_cast<CFDataRef>(
789       ::TISGetInputSourceProperty(mKeyboardLayout, kTISPropertyUnicodeKeyLayoutData));
791   // We should be always able to get the layout here.
792   NS_ENSURE_TRUE(uchr, nullptr);
793   mUCKeyboardLayout = reinterpret_cast<const UCKeyboardLayout*>(CFDataGetBytePtr(uchr));
794   return mUCKeyboardLayout;
797 bool TISInputSourceWrapper::GetBoolProperty(const CFStringRef aKey) {
798   CFBooleanRef ret = static_cast<CFBooleanRef>(::TISGetInputSourceProperty(mInputSource, aKey));
799   return ::CFBooleanGetValue(ret);
802 bool TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey, CFStringRef& aStr) {
803   aStr = static_cast<CFStringRef>(::TISGetInputSourceProperty(mInputSource, aKey));
804   return aStr != nullptr;
807 bool TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey, nsAString& aStr) {
808   CFStringRef str;
809   GetStringProperty(aKey, str);
810   nsCocoaUtils::GetStringForNSString((const NSString*)str, aStr);
811   return !aStr.IsEmpty();
814 bool TISInputSourceWrapper::IsOpenedIMEMode() {
815   NS_ENSURE_TRUE(mInputSource, false);
816   if (!IsIMEMode()) return false;
817   return !IsASCIICapable();
820 bool TISInputSourceWrapper::IsIMEMode() {
821   NS_ENSURE_TRUE(mInputSource, false);
822   CFStringRef str;
823   GetInputSourceType(str);
824   NS_ENSURE_TRUE(str, false);
825   return ::CFStringCompare(kTISTypeKeyboardInputMode, str, 0) == kCFCompareEqualTo;
828 bool TISInputSourceWrapper::IsKeyboardLayout() {
829   NS_ENSURE_TRUE(mInputSource, false);
830   CFStringRef str;
831   GetInputSourceType(str);
832   NS_ENSURE_TRUE(str, false);
833   return ::CFStringCompare(kTISTypeKeyboardLayout, str, 0) == kCFCompareEqualTo;
836 bool TISInputSourceWrapper::GetLanguageList(CFArrayRef& aLanguageList) {
837   NS_ENSURE_TRUE(mInputSource, false);
838   aLanguageList = static_cast<CFArrayRef>(
839       ::TISGetInputSourceProperty(mInputSource, kTISPropertyInputSourceLanguages));
840   return aLanguageList != nullptr;
843 bool TISInputSourceWrapper::GetPrimaryLanguage(CFStringRef& aPrimaryLanguage) {
844   NS_ENSURE_TRUE(mInputSource, false);
845   CFArrayRef langList;
846   NS_ENSURE_TRUE(GetLanguageList(langList), false);
847   if (::CFArrayGetCount(langList) == 0) return false;
848   aPrimaryLanguage = static_cast<CFStringRef>(::CFArrayGetValueAtIndex(langList, 0));
849   return aPrimaryLanguage != nullptr;
852 bool TISInputSourceWrapper::GetPrimaryLanguage(nsAString& aPrimaryLanguage) {
853   NS_ENSURE_TRUE(mInputSource, false);
854   CFStringRef primaryLanguage;
855   NS_ENSURE_TRUE(GetPrimaryLanguage(primaryLanguage), false);
856   nsCocoaUtils::GetStringForNSString((const NSString*)primaryLanguage, aPrimaryLanguage);
857   return !aPrimaryLanguage.IsEmpty();
860 bool TISInputSourceWrapper::IsForRTLLanguage() {
861   if (mIsRTL < 0) {
862     // Get the input character of the 'A' key of ANSI keyboard layout.
863     nsAutoString str;
864     bool ret = TranslateToString(kVK_ANSI_A, 0, eKbdType_ANSI, str);
865     NS_ENSURE_TRUE(ret, ret);
866     char16_t ch = str.IsEmpty() ? char16_t(0) : str.CharAt(0);
867     mIsRTL = UTF16_CODE_UNIT_IS_BIDI(ch);
868   }
869   return mIsRTL != 0;
872 bool TISInputSourceWrapper::IsForJapaneseLanguage() {
873   nsAutoString lang;
874   GetPrimaryLanguage(lang);
875   return lang.EqualsLiteral("ja");
878 bool TISInputSourceWrapper::IsInitializedByCurrentInputSource() {
879   return mInputSource == ::TISCopyCurrentKeyboardInputSource();
882 void TISInputSourceWrapper::Select() {
883   if (!mInputSource) return;
884   ::TISSelectInputSource(mInputSource);
887 void TISInputSourceWrapper::Clear() {
888   // Clear() is always called when TISInputSourceWrappper is created.
889   EnsureToLogAllKeyboardLayoutsAndIMEs();
891   if (mInputSourceList) {
892     ::CFRelease(mInputSourceList);
893   }
894   mInputSourceList = nullptr;
895   mInputSource = nullptr;
896   mKeyboardLayout = nullptr;
897   mIsRTL = -1;
898   mUCKeyboardLayout = nullptr;
899   mOverrideKeyboard = false;
902 bool TISInputSourceWrapper::IsPrintableKeyEvent(NSEvent* aNativeKeyEvent) const {
903   UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
905   bool isPrintableKey = !TextInputHandler::IsSpecialGeckoKey(nativeKeyCode);
906   if (isPrintableKey && [aNativeKeyEvent type] != NSEventTypeKeyDown &&
907       [aNativeKeyEvent type] != NSEventTypeKeyUp) {
908     NS_WARNING("Why would a printable key not be an NSEventTypeKeyDown or NSEventTypeKeyUp event?");
909     isPrintableKey = false;
910   }
911   return isPrintableKey;
914 UInt32 TISInputSourceWrapper::GetKbdType() const {
915   // If a keyboard layout override is set, we also need to force the keyboard
916   // type to something ANSI to avoid test failures on machines with JIS
917   // keyboards (since the pair of keyboard layout and physical keyboard type
918   // form the actual key layout).  This assumes that the test setting the
919   // override was written assuming an ANSI keyboard.
920   return mOverrideKeyboard ? eKbdType_ANSI : ::LMGetKbdType();
923 void TISInputSourceWrapper::ComputeInsertStringForCharCode(NSEvent* aNativeKeyEvent,
924                                                            const WidgetKeyboardEvent& aKeyEvent,
925                                                            const nsAString* aInsertString,
926                                                            nsAString& aResult) {
927   if (aInsertString) {
928     // If the caller expects that the aInsertString will be input, we shouldn't
929     // change it.
930     aResult = *aInsertString;
931   } else if (IsPrintableKeyEvent(aNativeKeyEvent)) {
932     // If IME is open, [aNativeKeyEvent characters] may be a character
933     // which will be appended to the composition string.  However, especially,
934     // while IME is disabled, most users and developers expect the key event
935     // works as IME closed.  So, we should compute the aResult with
936     // the ASCII capable keyboard layout.
937     // NOTE: Such keyboard layouts typically change the layout to its ASCII
938     //       capable layout when Command key is pressed.  And we don't worry
939     //       when Control key is pressed too because it causes inputting
940     //       control characters.
941     // Additionally, if the key event doesn't input any text, the event may be
942     // dead key event.  In this case, the charCode value should be the dead
943     // character.
944     UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
945     if ((!aKeyEvent.IsMeta() && !aKeyEvent.IsControl() && IsOpenedIMEMode()) ||
946         ![[aNativeKeyEvent characters] length]) {
947       UInt32 state = nsCocoaUtils::ConvertToCarbonModifier([aNativeKeyEvent modifierFlags]);
948       uint32_t ch = TranslateToChar(nativeKeyCode, state, GetKbdType());
949       if (ch) {
950         aResult = ch;
951       }
952     } else {
953       // If the caller isn't sure what string will be input, let's use
954       // characters of NSEvent.
955       nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], aResult);
956     }
958     // If control key is pressed and the eventChars is a non-printable control
959     // character, we should convert it to ASCII alphabet.
960     if (aKeyEvent.IsControl() && !aResult.IsEmpty() && aResult[0] <= char16_t(26)) {
961       aResult = (aKeyEvent.IsShift() ^ aKeyEvent.IsCapsLocked())
962                     ? static_cast<char16_t>(aResult[0] + ('A' - 1))
963                     : static_cast<char16_t>(aResult[0] + ('a' - 1));
964     }
965     // If Meta key is pressed, it may cause to switch the keyboard layout like
966     // Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY.
967     else if (aKeyEvent.IsMeta() && !(aKeyEvent.IsControl() || aKeyEvent.IsAlt())) {
968       UInt32 kbType = GetKbdType();
969       UInt32 numLockState = aKeyEvent.IsNumLocked() ? kEventKeyModifierNumLockMask : 0;
970       UInt32 capsLockState = aKeyEvent.IsCapsLocked() ? alphaLock : 0;
971       UInt32 shiftState = aKeyEvent.IsShift() ? shiftKey : 0;
972       uint32_t uncmdedChar = TranslateToChar(nativeKeyCode, numLockState, kbType);
973       uint32_t cmdedChar = TranslateToChar(nativeKeyCode, cmdKey | numLockState, kbType);
974       // If we can make a good guess at the characters that the user would
975       // expect this key combination to produce (with and without Shift) then
976       // use those characters.  This also corrects for CapsLock.
977       uint32_t ch = 0;
978       if (uncmdedChar == cmdedChar) {
979         // The characters produced with Command seem similar to those without
980         // Command.
981         ch = TranslateToChar(nativeKeyCode, shiftState | capsLockState | numLockState, kbType);
982       } else {
983         TISInputSourceWrapper USLayout("com.apple.keylayout.US");
984         uint32_t uncmdedUSChar = USLayout.TranslateToChar(nativeKeyCode, numLockState, kbType);
985         // If it looks like characters from US keyboard layout when Command key
986         // is pressed, we should compute a character in the layout.
987         if (uncmdedUSChar == cmdedChar) {
988           ch = USLayout.TranslateToChar(nativeKeyCode, shiftState | capsLockState | numLockState,
989                                         kbType);
990         }
991       }
993       // If there is a more preferred character for the commanded key event,
994       // we should use it.
995       if (ch) {
996         aResult = ch;
997       }
998     }
999   }
1001   // Remove control characters which shouldn't be inputted on editor.
1002   // XXX Currently, we don't find any cases inserting control characters with
1003   //     printable character.  So, just checking first character is enough.
1004   if (!aResult.IsEmpty() && IsControlChar(aResult[0])) {
1005     aResult.Truncate();
1006   }
1009 void TISInputSourceWrapper::InitKeyEvent(NSEvent* aNativeKeyEvent, WidgetKeyboardEvent& aKeyEvent,
1010                                          bool aIsProcessedByIME, const nsAString* aInsertString) {
1011   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1013   MOZ_ASSERT(!aIsProcessedByIME || aKeyEvent.mMessage != eKeyPress,
1014              "eKeyPress event should not be marked as proccessed by IME");
1016   MOZ_LOG(gKeyLog, LogLevel::Info,
1017           ("%p TISInputSourceWrapper::InitKeyEvent, aNativeKeyEvent=%p, "
1018            "aKeyEvent.mMessage=%s, aProcessedByIME=%s, aInsertString=%p, "
1019            "IsOpenedIMEMode()=%s",
1020            this, aNativeKeyEvent, GetGeckoKeyEventType(aKeyEvent), TrueOrFalse(aIsProcessedByIME),
1021            aInsertString, TrueOrFalse(IsOpenedIMEMode())));
1023   if (NS_WARN_IF(!aNativeKeyEvent)) {
1024     return;
1025   }
1027   nsCocoaUtils::InitInputEvent(aKeyEvent, aNativeKeyEvent);
1029   // This is used only while dispatching the event (which is a synchronous
1030   // call), so there is no need to retain and release this data.
1031   aKeyEvent.mNativeKeyEvent = aNativeKeyEvent;
1033   aKeyEvent.mRefPoint = LayoutDeviceIntPoint(0, 0);
1035   UInt32 kbType = GetKbdType();
1036   UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
1038   // macOS handles dead key as IME.  If the key is first key press of dead
1039   // key, we should use KEY_NAME_INDEX_Dead for first (dead) key event.
1040   // So, if aIsProcessedByIME is true, it may be dead key.  Let's check
1041   // if current key event is a dead key's keydown event.
1042   bool isProcessedByIME =
1043       aIsProcessedByIME && !TISInputSourceWrapper::CurrentInputSource().IsDeadKey(aNativeKeyEvent);
1045   aKeyEvent.mKeyCode = isProcessedByIME
1046                            ? NS_VK_PROCESSKEY
1047                            : ComputeGeckoKeyCode(nativeKeyCode, kbType, aKeyEvent.IsMeta());
1049   switch (nativeKeyCode) {
1050     case kVK_Command:
1051     case kVK_Shift:
1052     case kVK_Option:
1053     case kVK_Control:
1054       aKeyEvent.mLocation = eKeyLocationLeft;
1055       break;
1057     case kVK_RightCommand:
1058     case kVK_RightShift:
1059     case kVK_RightOption:
1060     case kVK_RightControl:
1061       aKeyEvent.mLocation = eKeyLocationRight;
1062       break;
1064     case kVK_ANSI_Keypad0:
1065     case kVK_ANSI_Keypad1:
1066     case kVK_ANSI_Keypad2:
1067     case kVK_ANSI_Keypad3:
1068     case kVK_ANSI_Keypad4:
1069     case kVK_ANSI_Keypad5:
1070     case kVK_ANSI_Keypad6:
1071     case kVK_ANSI_Keypad7:
1072     case kVK_ANSI_Keypad8:
1073     case kVK_ANSI_Keypad9:
1074     case kVK_ANSI_KeypadMultiply:
1075     case kVK_ANSI_KeypadPlus:
1076     case kVK_ANSI_KeypadMinus:
1077     case kVK_ANSI_KeypadDecimal:
1078     case kVK_ANSI_KeypadDivide:
1079     case kVK_ANSI_KeypadEquals:
1080     case kVK_ANSI_KeypadEnter:
1081     case kVK_JIS_KeypadComma:
1082     case kVK_Powerbook_KeypadEnter:
1083       aKeyEvent.mLocation = eKeyLocationNumpad;
1084       break;
1086     default:
1087       aKeyEvent.mLocation = eKeyLocationStandard;
1088       break;
1089   }
1091   aKeyEvent.mIsRepeat =
1092       ([aNativeKeyEvent type] == NSEventTypeKeyDown) ? [aNativeKeyEvent isARepeat] : false;
1094   MOZ_LOG(gKeyLog, LogLevel::Info,
1095           ("%p   TISInputSourceWrapper::InitKeyEvent, "
1096            "shift=%s, ctrl=%s, alt=%s, meta=%s",
1097            this, OnOrOff(aKeyEvent.IsShift()), OnOrOff(aKeyEvent.IsControl()),
1098            OnOrOff(aKeyEvent.IsAlt()), OnOrOff(aKeyEvent.IsMeta())));
1100   if (isProcessedByIME) {
1101     aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_Process;
1102   } else if (IsPrintableKeyEvent(aNativeKeyEvent)) {
1103     aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
1104     // If insertText calls this method, let's use the string.
1105     if (aInsertString && !aInsertString->IsEmpty() && !IsControlChar((*aInsertString)[0])) {
1106       aKeyEvent.mKeyValue = *aInsertString;
1107     }
1108     // If meta key is pressed, the printable key layout may be switched from
1109     // non-ASCII capable layout to ASCII capable, or from Dvorak to QWERTY.
1110     // KeyboardEvent.key value should be the switched layout's character.
1111     else if (aKeyEvent.IsMeta()) {
1112       nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], aKeyEvent.mKeyValue);
1113     }
1114     // If control key is pressed, some keys may produce printable character via
1115     // [aNativeKeyEvent characters].  Otherwise, translate input character of
1116     // the key without control key.
1117     else if (aKeyEvent.IsControl()) {
1118       NSUInteger cocoaState = [aNativeKeyEvent modifierFlags] & ~NSEventModifierFlagControl;
1119       UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState);
1120       if (IsDeadKey(nativeKeyCode, carbonState, kbType)) {
1121         aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_Dead;
1122       } else {
1123         aKeyEvent.mKeyValue = TranslateToChar(nativeKeyCode, carbonState, kbType);
1124         if (!aKeyEvent.mKeyValue.IsEmpty() && IsControlChar(aKeyEvent.mKeyValue[0])) {
1125           // Don't expose control character to the web.
1126           aKeyEvent.mKeyValue.Truncate();
1127         }
1128       }
1129     }
1130     // Otherwise, KeyboardEvent.key expose
1131     // [aNativeKeyEvent characters] value.  However, if IME is open and the
1132     // keyboard layout isn't ASCII capable, exposing the non-ASCII character
1133     // doesn't match with other platform's behavior.  For the compatibility
1134     // with other platform's Gecko, we need to set a translated character.
1135     else if (IsOpenedIMEMode()) {
1136       UInt32 state = nsCocoaUtils::ConvertToCarbonModifier([aNativeKeyEvent modifierFlags]);
1137       aKeyEvent.mKeyValue = TranslateToChar(nativeKeyCode, state, kbType);
1138     } else {
1139       nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], aKeyEvent.mKeyValue);
1140       // If the key value is empty, the event may be a dead key event.
1141       // If TranslateToChar() returns non-zero value, that means that
1142       // the key may input a character with different dead key state.
1143       if (aKeyEvent.mKeyValue.IsEmpty()) {
1144         NSUInteger cocoaState = [aNativeKeyEvent modifierFlags];
1145         UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState);
1146         if (TranslateToChar(nativeKeyCode, carbonState, kbType)) {
1147           aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_Dead;
1148         }
1149       }
1150     }
1152     // Last resort.  If .key value becomes empty string, we should use
1153     // charactersIgnoringModifiers, if it's available.
1154     if (aKeyEvent.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING &&
1155         (aKeyEvent.mKeyValue.IsEmpty() || IsControlChar(aKeyEvent.mKeyValue[0]))) {
1156       nsCocoaUtils::GetStringForNSString([aNativeKeyEvent charactersIgnoringModifiers],
1157                                          aKeyEvent.mKeyValue);
1158       // But don't expose it if it's a control character.
1159       if (!aKeyEvent.mKeyValue.IsEmpty() && IsControlChar(aKeyEvent.mKeyValue[0])) {
1160         aKeyEvent.mKeyValue.Truncate();
1161       }
1162     }
1163   } else {
1164     // Compute the key for non-printable keys and some special printable keys.
1165     aKeyEvent.mKeyNameIndex = ComputeGeckoKeyNameIndex(nativeKeyCode);
1166   }
1168   aKeyEvent.mCodeNameIndex = ComputeGeckoCodeNameIndex(nativeKeyCode, kbType);
1169   MOZ_ASSERT(aKeyEvent.mCodeNameIndex != CODE_NAME_INDEX_USE_STRING);
1171   NS_OBJC_END_TRY_IGNORE_BLOCK
1174 void TISInputSourceWrapper::WillDispatchKeyboardEvent(NSEvent* aNativeKeyEvent,
1175                                                       const nsAString* aInsertString,
1176                                                       uint32_t aIndexOfKeypress,
1177                                                       WidgetKeyboardEvent& aKeyEvent) {
1178   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1180   // Nothing to do here if the native key event is neither NSEventTypeKeyDown nor
1181   // NSEventTypeKeyUp because accessing [aNativeKeyEvent characters] causes throwing
1182   // an exception.
1183   if ([aNativeKeyEvent type] != NSEventTypeKeyDown && [aNativeKeyEvent type] != NSEventTypeKeyUp) {
1184     return;
1185   }
1187   UInt32 kbType = GetKbdType();
1189   if (MOZ_LOG_TEST(gKeyLog, LogLevel::Info)) {
1190     nsAutoString chars;
1191     nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], chars);
1192     NS_ConvertUTF16toUTF8 utf8Chars(chars);
1193     char16_t uniChar = static_cast<char16_t>(aKeyEvent.mCharCode);
1194     MOZ_LOG(gKeyLog, LogLevel::Info,
1195             ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
1196              "aNativeKeyEvent=%p, aInsertString=%p (\"%s\"), "
1197              "aIndexOfKeypress=%u, [aNativeKeyEvent characters]=\"%s\", "
1198              "aKeyEvent={ mMessage=%s, mCharCode=0x%X(%s) }, kbType=0x%X, "
1199              "IsOpenedIMEMode()=%s",
1200              this, aNativeKeyEvent, aInsertString,
1201              aInsertString ? GetCharacters(*aInsertString) : "", aIndexOfKeypress,
1202              GetCharacters([aNativeKeyEvent characters]), GetGeckoKeyEventType(aKeyEvent),
1203              aKeyEvent.mCharCode, uniChar ? NS_ConvertUTF16toUTF8(&uniChar, 1).get() : "",
1204              static_cast<unsigned int>(kbType), TrueOrFalse(IsOpenedIMEMode())));
1205   }
1207   nsAutoString insertStringForCharCode;
1208   ComputeInsertStringForCharCode(aNativeKeyEvent, aKeyEvent, aInsertString,
1209                                  insertStringForCharCode);
1211   // The mCharCode was set from mKeyValue. However, for example, when Ctrl key
1212   // is pressed, its value should indicate an ASCII character for backward
1213   // compatibility rather than inputting character without the modifiers.
1214   // Therefore, we need to modify mCharCode value here.
1215   uint32_t charCode = 0;
1216   if (aIndexOfKeypress < insertStringForCharCode.Length()) {
1217     charCode = insertStringForCharCode[aIndexOfKeypress];
1218   }
1219   aKeyEvent.SetCharCode(charCode);
1221   MOZ_LOG(gKeyLog, LogLevel::Info,
1222           ("%p   TISInputSourceWrapper::WillDispatchKeyboardEvent, "
1223            "aKeyEvent.mKeyCode=0x%X, aKeyEvent.mCharCode=0x%X",
1224            this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode));
1226   // If aInsertString is not nullptr (it means InsertText() is called)
1227   // and it acutally inputs a character, we don't need to append alternative
1228   // charCode values since such keyboard event shouldn't be handled as
1229   // a shortcut key.
1230   if (aInsertString && charCode) {
1231     return;
1232   }
1234   TISInputSourceWrapper USLayout("com.apple.keylayout.US");
1235   bool isRomanKeyboardLayout = IsASCIICapable();
1237   UInt32 key = [aNativeKeyEvent keyCode];
1239   // Caps lock and num lock modifier state:
1240   UInt32 lockState = 0;
1241   if ([aNativeKeyEvent modifierFlags] & NSEventModifierFlagCapsLock) {
1242     lockState |= alphaLock;
1243   }
1244   if ([aNativeKeyEvent modifierFlags] & NSEventModifierFlagNumericPad) {
1245     lockState |= kEventKeyModifierNumLockMask;
1246   }
1248   MOZ_LOG(gKeyLog, LogLevel::Info,
1249           ("%p   TISInputSourceWrapper::WillDispatchKeyboardEvent, "
1250            "isRomanKeyboardLayout=%s, kbType=0x%X, key=0x%X",
1251            this, TrueOrFalse(isRomanKeyboardLayout), static_cast<unsigned int>(kbType),
1252            static_cast<unsigned int>(key)));
1254   nsString str;
1256   // normal chars
1257   uint32_t unshiftedChar = TranslateToChar(key, lockState, kbType);
1258   UInt32 shiftLockMod = shiftKey | lockState;
1259   uint32_t shiftedChar = TranslateToChar(key, shiftLockMod, kbType);
1261   // characters generated with Cmd key
1262   // XXX we should remove CapsLock state, which changes characters from
1263   //     Latin to Cyrillic with Russian layout on 10.4 only when Cmd key
1264   //     is pressed.
1265   UInt32 numState = (lockState & ~alphaLock);  // only num lock state
1266   uint32_t uncmdedChar = TranslateToChar(key, numState, kbType);
1267   UInt32 shiftNumMod = numState | shiftKey;
1268   uint32_t uncmdedShiftChar = TranslateToChar(key, shiftNumMod, kbType);
1269   uint32_t uncmdedUSChar = USLayout.TranslateToChar(key, numState, kbType);
1270   UInt32 cmdNumMod = cmdKey | numState;
1271   uint32_t cmdedChar = TranslateToChar(key, cmdNumMod, kbType);
1272   UInt32 cmdShiftNumMod = shiftKey | cmdNumMod;
1273   uint32_t cmdedShiftChar = TranslateToChar(key, cmdShiftNumMod, kbType);
1275   // Is the keyboard layout changed by Cmd key?
1276   // E.g., Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY.
1277   bool isCmdSwitchLayout = uncmdedChar != cmdedChar;
1278   // Is the keyboard layout for Latin, but Cmd key switches the layout?
1279   // I.e., Dvorak-QWERTY
1280   bool isDvorakQWERTY = isCmdSwitchLayout && isRomanKeyboardLayout;
1282   // If the current keyboard is not Dvorak-QWERTY or Cmd is not pressed,
1283   // we should append unshiftedChar and shiftedChar for handling the
1284   // normal characters.  These are the characters that the user is most
1285   // likely to associate with this key.
1286   if ((unshiftedChar || shiftedChar) && (!aKeyEvent.IsMeta() || !isDvorakQWERTY)) {
1287     AlternativeCharCode altCharCodes(unshiftedChar, shiftedChar);
1288     aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes);
1289   }
1290   MOZ_LOG(
1291       gKeyLog, LogLevel::Info,
1292       ("%p   TISInputSourceWrapper::WillDispatchKeyboardEvent, "
1293        "aKeyEvent.isMeta=%s, isDvorakQWERTY=%s, "
1294        "unshiftedChar=U+%X, shiftedChar=U+%X",
1295        this, OnOrOff(aKeyEvent.IsMeta()), TrueOrFalse(isDvorakQWERTY), unshiftedChar, shiftedChar));
1297   // Most keyboard layouts provide the same characters in the NSEvents
1298   // with Command+Shift as with Command.  However, with Command+Shift we
1299   // want the character on the second level.  e.g. With a US QWERTY
1300   // layout, we want "?" when the "/","?" key is pressed with
1301   // Command+Shift.
1303   // On a German layout, the OS gives us '/' with Cmd+Shift+SS(eszett)
1304   // even though Cmd+SS is 'SS' and Shift+'SS' is '?'.  This '/' seems
1305   // like a hack to make the Cmd+"?" event look the same as the Cmd+"?"
1306   // event on a US keyboard.  The user thinks they are typing Cmd+"?", so
1307   // we'll prefer the "?" character, replacing mCharCode with shiftedChar
1308   // when Shift is pressed.  However, in case there is a layout where the
1309   // character unique to Cmd+Shift is the character that the user expects,
1310   // we'll send it as an alternative char.
1311   bool hasCmdShiftOnlyChar = cmdedChar != cmdedShiftChar && uncmdedShiftChar != cmdedShiftChar;
1312   uint32_t originalCmdedShiftChar = cmdedShiftChar;
1314   // If we can make a good guess at the characters that the user would
1315   // expect this key combination to produce (with and without Shift) then
1316   // use those characters.  This also corrects for CapsLock, which was
1317   // ignored above.
1318   if (!isCmdSwitchLayout) {
1319     // The characters produced with Command seem similar to those without
1320     // Command.
1321     if (unshiftedChar) {
1322       cmdedChar = unshiftedChar;
1323     }
1324     if (shiftedChar) {
1325       cmdedShiftChar = shiftedChar;
1326     }
1327   } else if (uncmdedUSChar == cmdedChar) {
1328     // It looks like characters from a US layout are provided when Command
1329     // is down.
1330     uint32_t ch = USLayout.TranslateToChar(key, lockState, kbType);
1331     if (ch) {
1332       cmdedChar = ch;
1333     }
1334     ch = USLayout.TranslateToChar(key, shiftLockMod, kbType);
1335     if (ch) {
1336       cmdedShiftChar = ch;
1337     }
1338   }
1340   // If the current keyboard layout is switched by the Cmd key,
1341   // we should append cmdedChar and shiftedCmdChar that are
1342   // Latin char for the key.
1343   // If the keyboard layout is Dvorak-QWERTY, we should append them only when
1344   // command key is pressed because when command key isn't pressed, uncmded
1345   // chars have been appended already.
1346   if ((cmdedChar || cmdedShiftChar) && isCmdSwitchLayout &&
1347       (aKeyEvent.IsMeta() || !isDvorakQWERTY)) {
1348     AlternativeCharCode altCharCodes(cmdedChar, cmdedShiftChar);
1349     aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes);
1350   }
1351   MOZ_LOG(gKeyLog, LogLevel::Info,
1352           ("%p   TISInputSourceWrapper::WillDispatchKeyboardEvent, "
1353            "hasCmdShiftOnlyChar=%s, isCmdSwitchLayout=%s, isDvorakQWERTY=%s, "
1354            "cmdedChar=U+%X, cmdedShiftChar=U+%X",
1355            this, TrueOrFalse(hasCmdShiftOnlyChar), TrueOrFalse(isDvorakQWERTY),
1356            TrueOrFalse(isDvorakQWERTY), cmdedChar, cmdedShiftChar));
1357   // Special case for 'SS' key of German layout. See the comment of
1358   // hasCmdShiftOnlyChar definition for the detail.
1359   if (hasCmdShiftOnlyChar && originalCmdedShiftChar) {
1360     AlternativeCharCode altCharCodes(0, originalCmdedShiftChar);
1361     aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes);
1362   }
1363   MOZ_LOG(gKeyLog, LogLevel::Info,
1364           ("%p   TISInputSourceWrapper::WillDispatchKeyboardEvent, "
1365            "hasCmdShiftOnlyChar=%s, originalCmdedShiftChar=U+%X",
1366            this, TrueOrFalse(hasCmdShiftOnlyChar), originalCmdedShiftChar));
1368   NS_OBJC_END_TRY_IGNORE_BLOCK
1371 uint32_t TISInputSourceWrapper::ComputeGeckoKeyCode(UInt32 aNativeKeyCode, UInt32 aKbType,
1372                                                     bool aCmdIsPressed) {
1373   MOZ_LOG(
1374       gKeyLog, LogLevel::Info,
1375       ("%p TISInputSourceWrapper::ComputeGeckoKeyCode, aNativeKeyCode=0x%X, "
1376        "aKbType=0x%X, aCmdIsPressed=%s, IsOpenedIMEMode()=%s, "
1377        "IsASCIICapable()=%s",
1378        this, static_cast<unsigned int>(aNativeKeyCode), static_cast<unsigned int>(aKbType),
1379        TrueOrFalse(aCmdIsPressed), TrueOrFalse(IsOpenedIMEMode()), TrueOrFalse(IsASCIICapable())));
1381   switch (aNativeKeyCode) {
1382     case kVK_Space:
1383       return NS_VK_SPACE;
1384     case kVK_Escape:
1385       return NS_VK_ESCAPE;
1387     // modifiers
1388     case kVK_RightCommand:
1389     case kVK_Command:
1390       return NS_VK_META;
1391     case kVK_RightShift:
1392     case kVK_Shift:
1393       return NS_VK_SHIFT;
1394     case kVK_CapsLock:
1395       return NS_VK_CAPS_LOCK;
1396     case kVK_RightControl:
1397     case kVK_Control:
1398       return NS_VK_CONTROL;
1399     case kVK_RightOption:
1400     case kVK_Option:
1401       return NS_VK_ALT;
1403     case kVK_ANSI_KeypadClear:
1404       return NS_VK_CLEAR;
1406     // function keys
1407     case kVK_F1:
1408       return NS_VK_F1;
1409     case kVK_F2:
1410       return NS_VK_F2;
1411     case kVK_F3:
1412       return NS_VK_F3;
1413     case kVK_F4:
1414       return NS_VK_F4;
1415     case kVK_F5:
1416       return NS_VK_F5;
1417     case kVK_F6:
1418       return NS_VK_F6;
1419     case kVK_F7:
1420       return NS_VK_F7;
1421     case kVK_F8:
1422       return NS_VK_F8;
1423     case kVK_F9:
1424       return NS_VK_F9;
1425     case kVK_F10:
1426       return NS_VK_F10;
1427     case kVK_F11:
1428       return NS_VK_F11;
1429     case kVK_F12:
1430       return NS_VK_F12;
1431     // case kVK_F13:               return NS_VK_F13;  // clash with the 3 below
1432     // case kVK_F14:               return NS_VK_F14;
1433     // case kVK_F15:               return NS_VK_F15;
1434     case kVK_F16:
1435       return NS_VK_F16;
1436     case kVK_F17:
1437       return NS_VK_F17;
1438     case kVK_F18:
1439       return NS_VK_F18;
1440     case kVK_F19:
1441       return NS_VK_F19;
1443     case kVK_PC_Pause:
1444       return NS_VK_PAUSE;
1445     case kVK_PC_ScrollLock:
1446       return NS_VK_SCROLL_LOCK;
1447     case kVK_PC_PrintScreen:
1448       return NS_VK_PRINTSCREEN;
1450     // keypad
1451     case kVK_ANSI_Keypad0:
1452       return NS_VK_NUMPAD0;
1453     case kVK_ANSI_Keypad1:
1454       return NS_VK_NUMPAD1;
1455     case kVK_ANSI_Keypad2:
1456       return NS_VK_NUMPAD2;
1457     case kVK_ANSI_Keypad3:
1458       return NS_VK_NUMPAD3;
1459     case kVK_ANSI_Keypad4:
1460       return NS_VK_NUMPAD4;
1461     case kVK_ANSI_Keypad5:
1462       return NS_VK_NUMPAD5;
1463     case kVK_ANSI_Keypad6:
1464       return NS_VK_NUMPAD6;
1465     case kVK_ANSI_Keypad7:
1466       return NS_VK_NUMPAD7;
1467     case kVK_ANSI_Keypad8:
1468       return NS_VK_NUMPAD8;
1469     case kVK_ANSI_Keypad9:
1470       return NS_VK_NUMPAD9;
1472     case kVK_ANSI_KeypadMultiply:
1473       return NS_VK_MULTIPLY;
1474     case kVK_ANSI_KeypadPlus:
1475       return NS_VK_ADD;
1476     case kVK_ANSI_KeypadMinus:
1477       return NS_VK_SUBTRACT;
1478     case kVK_ANSI_KeypadDecimal:
1479       return NS_VK_DECIMAL;
1480     case kVK_ANSI_KeypadDivide:
1481       return NS_VK_DIVIDE;
1483     case kVK_JIS_KeypadComma:
1484       return NS_VK_SEPARATOR;
1486     // IME keys
1487     case kVK_JIS_Eisu:
1488       return NS_VK_EISU;
1489     case kVK_JIS_Kana:
1490       return NS_VK_KANA;
1492     // these may clash with forward delete and help
1493     case kVK_PC_Insert:
1494       return NS_VK_INSERT;
1495     case kVK_PC_Delete:
1496       return NS_VK_DELETE;
1498     case kVK_PC_Backspace:
1499       return NS_VK_BACK;
1500     case kVK_Tab:
1501       return NS_VK_TAB;
1503     case kVK_Home:
1504       return NS_VK_HOME;
1505     case kVK_End:
1506       return NS_VK_END;
1508     case kVK_PageUp:
1509       return NS_VK_PAGE_UP;
1510     case kVK_PageDown:
1511       return NS_VK_PAGE_DOWN;
1513     case kVK_LeftArrow:
1514       return NS_VK_LEFT;
1515     case kVK_RightArrow:
1516       return NS_VK_RIGHT;
1517     case kVK_UpArrow:
1518       return NS_VK_UP;
1519     case kVK_DownArrow:
1520       return NS_VK_DOWN;
1522     case kVK_PC_ContextMenu:
1523       return NS_VK_CONTEXT_MENU;
1525     case kVK_ANSI_1:
1526       return NS_VK_1;
1527     case kVK_ANSI_2:
1528       return NS_VK_2;
1529     case kVK_ANSI_3:
1530       return NS_VK_3;
1531     case kVK_ANSI_4:
1532       return NS_VK_4;
1533     case kVK_ANSI_5:
1534       return NS_VK_5;
1535     case kVK_ANSI_6:
1536       return NS_VK_6;
1537     case kVK_ANSI_7:
1538       return NS_VK_7;
1539     case kVK_ANSI_8:
1540       return NS_VK_8;
1541     case kVK_ANSI_9:
1542       return NS_VK_9;
1543     case kVK_ANSI_0:
1544       return NS_VK_0;
1546     case kVK_ANSI_KeypadEnter:
1547     case kVK_Return:
1548     case kVK_Powerbook_KeypadEnter:
1549       return NS_VK_RETURN;
1550   }
1552   // If Cmd key is pressed, that causes switching keyboard layout temporarily.
1553   // E.g., Dvorak-QWERTY.  Therefore, if Cmd key is pressed, we should honor it.
1554   UInt32 modifiers = aCmdIsPressed ? cmdKey : 0;
1556   uint32_t charCode = TranslateToChar(aNativeKeyCode, modifiers, aKbType);
1558   // Special case for Mac.  Mac inputs Yen sign (U+00A5) directly instead of
1559   // Back slash (U+005C).  We should return NS_VK_BACK_SLASH for compatibility
1560   // with other platforms.
1561   // XXX How about Won sign (U+20A9) which has same problem as Yen sign?
1562   if (charCode == 0x00A5) {
1563     return NS_VK_BACK_SLASH;
1564   }
1566   uint32_t keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode);
1567   if (keyCode) {
1568     return keyCode;
1569   }
1571   // If the unshifed char isn't an ASCII character, use shifted char.
1572   charCode = TranslateToChar(aNativeKeyCode, modifiers | shiftKey, aKbType);
1573   keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode);
1574   if (keyCode) {
1575     return keyCode;
1576   }
1578   if (!IsASCIICapable()) {
1579     // Retry with ASCII capable keyboard layout.
1580     TISInputSourceWrapper currentKeyboardLayout;
1581     currentKeyboardLayout.InitByCurrentASCIICapableKeyboardLayout();
1582     NS_ENSURE_TRUE(mInputSource != currentKeyboardLayout.mInputSource, 0);
1583     keyCode = currentKeyboardLayout.ComputeGeckoKeyCode(aNativeKeyCode, aKbType, aCmdIsPressed);
1584     // We've returned 0 for long time if keyCode isn't for an alphabet keys or
1585     // a numeric key even in alternative ASCII capable keyboard layout because
1586     // we decided that we should avoid setting same keyCode value to 2 or
1587     // more keys since active keyboard layout may have a key to input the
1588     // punctuation with different key.  However, setting keyCode to 0 makes
1589     // some web applications which are aware of neither KeyboardEvent.key nor
1590     // KeyboardEvent.code not work with Firefox when user selects non-ASCII
1591     // capable keyboard layout such as Russian and Thai.  So, if alternative
1592     // ASCII capable keyboard layout has keyCode value for the key, we should
1593     // use it.  In other words, this behavior does that non-ASCII capable
1594     // keyboard layout overrides some keys' keyCode value only if the key
1595     // produces ASCII character by itself or with Shift key.
1596     if (keyCode) {
1597       return keyCode;
1598     }
1599   }
1601   // Otherwise, let's decide keyCode value from the native virtual keycode
1602   // value on major keyboard layout.
1603   CodeNameIndex code = ComputeGeckoCodeNameIndex(aNativeKeyCode, aKbType);
1604   return WidgetKeyboardEvent::GetFallbackKeyCodeOfPunctuationKey(code);
1607 // static
1608 KeyNameIndex TISInputSourceWrapper::ComputeGeckoKeyNameIndex(UInt32 aNativeKeyCode) {
1609   // NOTE:
1610   //   When unsupported keys like Convert, Nonconvert of Japanese keyboard is
1611   //   pressed:
1612   //     on 10.6.x, 'A' key event is fired (and also actually 'a' is inserted).
1613   //     on 10.7.x, Nothing happens.
1614   //     on 10.8.x, Nothing happens.
1615   //     on 10.9.x, FlagsChanged event is fired with keyCode 0xFF.
1616   switch (aNativeKeyCode) {
1617 #define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
1618   case aNativeKey:                                                     \
1619     return aKeyNameIndex;
1621 #include "NativeKeyToDOMKeyName.h"
1623 #undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
1625     default:
1626       return KEY_NAME_INDEX_Unidentified;
1627   }
1630 // static
1631 CodeNameIndex TISInputSourceWrapper::ComputeGeckoCodeNameIndex(UInt32 aNativeKeyCode,
1632                                                                UInt32 aKbType) {
1633   // macOS swaps native key code between Backquote key and IntlBackslash key
1634   // only when the keyboard type is ISO.  Let's treat the key code after
1635   // swapping them here because Chromium does so only when computing .code
1636   // value.
1637   if (::KBGetLayoutType(aKbType) == kKeyboardISO) {
1638     if (aNativeKeyCode == kVK_ISO_Section) {
1639       aNativeKeyCode = kVK_ANSI_Grave;
1640     } else if (aNativeKeyCode == kVK_ANSI_Grave) {
1641       aNativeKeyCode = kVK_ISO_Section;
1642     }
1643   }
1645   switch (aNativeKeyCode) {
1646 #define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \
1647   case aNativeKey:                                                       \
1648     return aCodeNameIndex;
1650 #include "NativeKeyToDOMCodeName.h"
1652 #undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX
1654     default:
1655       return CODE_NAME_INDEX_UNKNOWN;
1656   }
1659 #pragma mark -
1661 /******************************************************************************
1663  *  TextInputHandler implementation (static methods)
1665  ******************************************************************************/
1667 NSUInteger TextInputHandler::sLastModifierState = 0;
1669 // static
1670 CFArrayRef TextInputHandler::CreateAllKeyboardLayoutList() {
1671   const void* keys[] = {kTISPropertyInputSourceType};
1672   const void* values[] = {kTISTypeKeyboardLayout};
1673   CFDictionaryRef filter = ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
1674   NS_ASSERTION(filter, "failed to create the filter");
1675   CFArrayRef list = ::TISCreateInputSourceList(filter, true);
1676   ::CFRelease(filter);
1677   return list;
1680 // static
1681 void TextInputHandler::DebugPrintAllKeyboardLayouts() {
1682   if (MOZ_LOG_TEST(gKeyLog, LogLevel::Info)) {
1683     CFArrayRef list = CreateAllKeyboardLayoutList();
1684     MOZ_LOG(gKeyLog, LogLevel::Info, ("Keyboard layout configuration:"));
1685     CFIndex idx = ::CFArrayGetCount(list);
1686     TISInputSourceWrapper tis;
1687     for (CFIndex i = 0; i < idx; ++i) {
1688       TISInputSourceRef inputSource =
1689           static_cast<TISInputSourceRef>(const_cast<void*>(::CFArrayGetValueAtIndex(list, i)));
1690       tis.InitByTISInputSourceRef(inputSource);
1691       nsAutoString name, isid;
1692       tis.GetLocalizedName(name);
1693       tis.GetInputSourceID(isid);
1694       MOZ_LOG(
1695           gKeyLog, LogLevel::Info,
1696           ("  %s\t<%s>%s%s\n", NS_ConvertUTF16toUTF8(name).get(), NS_ConvertUTF16toUTF8(isid).get(),
1697            tis.IsASCIICapable() ? "" : "\t(Isn't ASCII capable)",
1698            tis.IsKeyboardLayout() && tis.GetUCKeyboardLayout() ? "" : "\t(uchr is NOT AVAILABLE)"));
1699     }
1700     ::CFRelease(list);
1701   }
1704 #pragma mark -
1706 /******************************************************************************
1708  *  TextInputHandler implementation
1710  ******************************************************************************/
1712 TextInputHandler::TextInputHandler(nsChildView* aWidget, NSView<mozView>* aNativeView)
1713     : IMEInputHandler(aWidget, aNativeView) {
1714   EnsureToLogAllKeyboardLayoutsAndIMEs();
1715   [mView installTextInputHandler:this];
1718 TextInputHandler::~TextInputHandler() { [mView uninstallTextInputHandler]; }
1720 bool TextInputHandler::HandleKeyDownEvent(NSEvent* aNativeEvent, uint32_t aUniqueId) {
1721   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
1723   if (Destroyed()) {
1724     MOZ_LOG_KEY_OR_IME(LogLevel::Info, ("%p TextInputHandler::HandleKeyDownEvent, "
1725                                         "widget has been already destroyed",
1726                                         this));
1727     return false;
1728   }
1730   // Insert empty line to the log for easier to read.
1731   MOZ_LOG_KEY_OR_IME(LogLevel::Info, (""));
1732   MOZ_LOG_KEY_OR_IME(
1733       LogLevel::Info,
1734       ("%p TextInputHandler::HandleKeyDownEvent, aNativeEvent=%p, "
1735        "type=%s, keyCode=%u (0x%X), modifierFlags=0x%lX, characters=\"%s\", "
1736        "charactersIgnoringModifiers=\"%s\"",
1737        this, aNativeEvent, GetNativeKeyEventType(aNativeEvent), [aNativeEvent keyCode],
1738        [aNativeEvent keyCode], static_cast<unsigned long>([aNativeEvent modifierFlags]),
1739        GetCharacters([aNativeEvent characters]),
1740        GetCharacters([aNativeEvent charactersIgnoringModifiers])));
1742   // Except when Command key is pressed, we should hide mouse cursor until
1743   // next mousemove.  Handling here means that:
1744   // - Don't hide mouse cursor at pressing modifier key
1745   // - Hide mouse cursor even if the key event will be handled by IME (i.e.,
1746   //   even without dispatching eKeyPress events)
1747   // - Hide mouse cursor even when a plugin has focus
1748   if (!([aNativeEvent modifierFlags] & NSEventModifierFlagCommand)) {
1749     [NSCursor setHiddenUntilMouseMoves:YES];
1750   }
1752   RefPtr<nsChildView> widget(mWidget);
1754   KeyEventState* currentKeyEvent = PushKeyEvent(aNativeEvent, aUniqueId);
1755   AutoKeyEventStateCleaner remover(this);
1757   RefPtr<TextInputHandler> kungFuDeathGrip(this);
1759   // When we're already in a composition, we need always to mark the eKeyDown
1760   // event as "processed by IME".  So, let's dispatch eKeyDown event here in
1761   // such case.
1762   if (IsIMEComposing() && !MaybeDispatchCurrentKeydownEvent(true)) {
1763     MOZ_LOG_KEY_OR_IME(LogLevel::Info,
1764                        ("%p   TextInputHandler::HandleKeyDownEvent, eKeyDown caused focus move or "
1765                         "something and canceling the composition",
1766                         this));
1767     return false;
1768   }
1770   // Let Cocoa interpret the key events, caching IsIMEComposing first.
1771   bool wasComposing = IsIMEComposing();
1772   bool interpretKeyEventsCalled = false;
1773   // Don't call interpretKeyEvents when a plugin has focus.  If we call it,
1774   // for example, a character is inputted twice during a composition in e10s
1775   // mode.
1776   if (IsIMEEnabled() || IsASCIICapableOnly()) {
1777     MOZ_LOG_KEY_OR_IME(
1778         LogLevel::Info,
1779         ("%p   TextInputHandler::HandleKeyDownEvent, calling interpretKeyEvents", this));
1780     [mView interpretKeyEvents:[NSArray arrayWithObject:aNativeEvent]];
1781     interpretKeyEventsCalled = true;
1782     MOZ_LOG_KEY_OR_IME(
1783         LogLevel::Info,
1784         ("%p   TextInputHandler::HandleKeyDownEvent, called interpretKeyEvents", this));
1785   }
1787   if (Destroyed()) {
1788     MOZ_LOG_KEY_OR_IME(LogLevel::Info,
1789                        ("%p   TextInputHandler::HandleKeyDownEvent, widget was destroyed", this));
1790     return currentKeyEvent->IsDefaultPrevented();
1791   }
1793   MOZ_LOG_KEY_OR_IME(LogLevel::Info,
1794                      ("%p   TextInputHandler::HandleKeyDownEvent, wasComposing=%s, "
1795                       "IsIMEComposing()=%s",
1796                       this, TrueOrFalse(wasComposing), TrueOrFalse(IsIMEComposing())));
1798   if (currentKeyEvent->CanDispatchKeyDownEvent()) {
1799     // Dispatch eKeyDown event if nobody has dispatched it yet.
1800     // NOTE: Although reaching here means that the native keydown event may
1801     //       not be handled by IME.  However, we cannot know if it is.
1802     //       For example, Japanese IME of Apple shows candidate window for
1803     //       typing window.  They, you can switch the sort order with Tab key.
1804     //       However, when you choose "Symbol" of the sort order, there may
1805     //       be no candiate words.  In this case, IME handles the Tab key
1806     //       actually, but we cannot know it because composition string is
1807     //       not updated.  So, let's mark eKeyDown event as "processed by IME"
1808     //       when there is composition string.  This is same as Chrome.
1809     MOZ_LOG_KEY_OR_IME(LogLevel::Info,
1810                        ("%p   TextInputHandler::HandleKeyDownEvent, trying to dispatch eKeyDown "
1811                         "event since it's not yet dispatched",
1812                         this));
1813     if (!MaybeDispatchCurrentKeydownEvent(IsIMEComposing())) {
1814       return true;  // treat the eKeydDown event as consumed.
1815     }
1816     MOZ_LOG_KEY_OR_IME(LogLevel::Info,
1817                        ("%p   TextInputHandler::HandleKeyDownEvent, eKeyDown event has been "
1818                         "dispatched",
1819                         this));
1820   }
1822   if (currentKeyEvent->CanDispatchKeyPressEvent() && !wasComposing && !IsIMEComposing()) {
1823     nsresult rv = mDispatcher->BeginNativeInputTransaction();
1824     if (NS_WARN_IF(NS_FAILED(rv))) {
1825       MOZ_LOG_KEY_OR_IME(LogLevel::Error, ("%p   TextInputHandler::HandleKeyDownEvent, "
1826                                            "FAILED, due to BeginNativeInputTransaction() failure "
1827                                            "at dispatching keypress",
1828                                            this));
1829       return false;
1830     }
1832     WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget);
1833     currentKeyEvent->InitKeyEvent(this, keypressEvent, false);
1835     // If we called interpretKeyEvents and this isn't normal character input
1836     // then IME probably ate the event for some reason. We do not want to
1837     // send a key press event in that case.
1838     // TODO:
1839     // There are some other cases which IME eats the current event.
1840     // 1. If key events were nested during calling interpretKeyEvents, it means
1841     //    that IME did something.  Then, we should do nothing.
1842     // 2. If one or more commands are called like "deleteBackward", we should
1843     //    dispatch keypress event at that time.  Note that the command may have
1844     //    been a converted or generated action by IME.  Then, we shouldn't do
1845     //    our default action for this key.
1846     if (!(interpretKeyEventsCalled && IsNormalCharInputtingEvent(aNativeEvent))) {
1847       MOZ_LOG_KEY_OR_IME(LogLevel::Info,
1848                          ("%p   TextInputHandler::HandleKeyDownEvent, trying to dispatch "
1849                           "eKeyPress event since it's not yet dispatched",
1850                           this));
1851       nsEventStatus status = nsEventStatus_eIgnore;
1852       currentKeyEvent->mKeyPressDispatched =
1853           mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status, currentKeyEvent);
1854       currentKeyEvent->mKeyPressHandled = (status == nsEventStatus_eConsumeNoDefault);
1855       currentKeyEvent->mKeyPressDispatched = true;
1856       MOZ_LOG_KEY_OR_IME(LogLevel::Info,
1857                          ("%p TextInputHandler::HandleKeyDownEvent, eKeyPress event has been "
1858                           "dispatched",
1859                           this));
1860     }
1861   }
1863   // Note: mWidget might have become null here. Don't count on it from here on.
1865   MOZ_LOG_KEY_OR_IME(LogLevel::Info,
1866                      ("%p   TextInputHandler::HandleKeyDownEvent, "
1867                       "keydown handled=%s, keypress handled=%s, causedOtherKeyEvents=%s, "
1868                       "compositionDispatched=%s",
1869                       this, TrueOrFalse(currentKeyEvent->mKeyDownHandled),
1870                       TrueOrFalse(currentKeyEvent->mKeyPressHandled),
1871                       TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents),
1872                       TrueOrFalse(currentKeyEvent->mCompositionDispatched)));
1873   // Insert empty line to the log for easier to read.
1874   MOZ_LOG_KEY_OR_IME(LogLevel::Info, (""));
1875   return currentKeyEvent->IsDefaultPrevented();
1877   NS_OBJC_END_TRY_BLOCK_RETURN(false);
1880 void TextInputHandler::HandleKeyUpEvent(NSEvent* aNativeEvent) {
1881   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1883   MOZ_LOG_KEY_OR_IME(
1884       LogLevel::Info,
1885       ("%p   TextInputHandler::HandleKeyUpEvent, aNativeEvent=%p, "
1886        "type=%s, keyCode=%u (0x%X), modifierFlags=0x%lX, characters=\"%s\", "
1887        "charactersIgnoringModifiers=\"%s\", "
1888        "IsIMEComposing()=%s",
1889        this, aNativeEvent, GetNativeKeyEventType(aNativeEvent), [aNativeEvent keyCode],
1890        [aNativeEvent keyCode], static_cast<unsigned long>([aNativeEvent modifierFlags]),
1891        GetCharacters([aNativeEvent characters]),
1892        GetCharacters([aNativeEvent charactersIgnoringModifiers]), TrueOrFalse(IsIMEComposing())));
1894   if (Destroyed()) {
1895     MOZ_LOG_KEY_OR_IME(LogLevel::Info, ("%p TextInputHandler::HandleKeyUpEvent, "
1896                                         "widget has been already destroyed",
1897                                         this));
1898     return;
1899   }
1901   nsresult rv = mDispatcher->BeginNativeInputTransaction();
1902   if (NS_WARN_IF(NS_FAILED(rv))) {
1903     MOZ_LOG_KEY_OR_IME(LogLevel::Error, ("%p   TextInputHandler::HandleKeyUpEvent, "
1904                                          "FAILED, due to BeginNativeInputTransaction() failure",
1905                                          this));
1906     return;
1907   }
1909   // Neither Chrome for macOS nor Safari marks "keyup" event as "processed by
1910   // IME" even during composition.  So, let's follow this behavior.
1911   WidgetKeyboardEvent keyupEvent(true, eKeyUp, mWidget);
1912   InitKeyEvent(aNativeEvent, keyupEvent, false);
1914   KeyEventState currentKeyEvent(aNativeEvent);
1915   nsEventStatus status = nsEventStatus_eIgnore;
1916   mDispatcher->DispatchKeyboardEvent(eKeyUp, keyupEvent, status, &currentKeyEvent);
1918   NS_OBJC_END_TRY_IGNORE_BLOCK;
1921 void TextInputHandler::HandleFlagsChanged(NSEvent* aNativeEvent) {
1922   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1924   if (Destroyed()) {
1925     MOZ_LOG_KEY_OR_IME(LogLevel::Info, ("%p TextInputHandler::HandleFlagsChanged, "
1926                                         "widget has been already destroyed",
1927                                         this));
1928     return;
1929   }
1931   RefPtr<nsChildView> kungFuDeathGrip(mWidget);
1932   mozilla::Unused << kungFuDeathGrip;  // Not referenced within this function
1934   MOZ_LOG_KEY_OR_IME(
1935       LogLevel::Info,
1936       ("%p TextInputHandler::HandleFlagsChanged, aNativeEvent=%p, "
1937        "type=%s, keyCode=%s (0x%X), modifierFlags=0x%08lX, "
1938        "sLastModifierState=0x%08lX, IsIMEComposing()=%s",
1939        this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
1940        GetKeyNameForNativeKeyCode([aNativeEvent keyCode]), [aNativeEvent keyCode],
1941        static_cast<unsigned long>([aNativeEvent modifierFlags]),
1942        static_cast<unsigned long>(sLastModifierState), TrueOrFalse(IsIMEComposing())));
1944   MOZ_ASSERT([aNativeEvent type] == NSEventTypeFlagsChanged);
1946   NSUInteger diff = [aNativeEvent modifierFlags] ^ sLastModifierState;
1947   // Device dependent flags for left-control key, both shift keys, both command
1948   // keys and both option keys have been defined in Next's SDK.  But we
1949   // shouldn't use it directly as far as possible since Cocoa SDK doesn't
1950   // define them.  Fortunately, we need them only when we dispatch keyup
1951   // events.  So, we can usually know the actual relation between keyCode and
1952   // device dependent flags.  However, we need to remove following flags first
1953   // since the differences don't indicate modifier key state.
1954   // NX_STYLUSPROXIMITYMASK: Probably used for pen like device.
1955   // kCGEventFlagMaskNonCoalesced (= NX_NONCOALSESCEDMASK): See the document for
1956   // Quartz Event Services.
1957   diff &= ~(NX_STYLUSPROXIMITYMASK | kCGEventFlagMaskNonCoalesced);
1959   switch ([aNativeEvent keyCode]) {
1960     // CapsLock state and other modifier states are different:
1961     // CapsLock state does not revert when the CapsLock key goes up, as the
1962     // modifier state does for other modifier keys on key up.
1963     case kVK_CapsLock: {
1964       // Fire key down event for caps lock.
1965       DispatchKeyEventForFlagsChanged(aNativeEvent, true);
1966       // XXX should we fire keyup event too? The keyup event for CapsLock key
1967       // is never dispatched on Gecko.
1968       // XXX WebKit dispatches keydown event when CapsLock is locked, otherwise,
1969       // keyup event.  If we do so, we cannot keep the consistency with other
1970       // platform's behavior...
1971       break;
1972     }
1974     // If the event is caused by pressing or releasing a modifier key, just
1975     // dispatch the key's event.
1976     case kVK_Shift:
1977     case kVK_RightShift:
1978     case kVK_Command:
1979     case kVK_RightCommand:
1980     case kVK_Control:
1981     case kVK_RightControl:
1982     case kVK_Option:
1983     case kVK_RightOption:
1984     case kVK_Help: {
1985       // We assume that at most one modifier is changed per event if the event
1986       // is caused by pressing or releasing a modifier key.
1987       bool isKeyDown = ([aNativeEvent modifierFlags] & diff) != 0;
1988       DispatchKeyEventForFlagsChanged(aNativeEvent, isKeyDown);
1989       // XXX Some applications might send the event with incorrect device-
1990       //     dependent flags.
1991       if (isKeyDown && ((diff & ~NSEventModifierFlagDeviceIndependentFlagsMask) != 0)) {
1992         unsigned short keyCode = [aNativeEvent keyCode];
1993         const ModifierKey* modifierKey = GetModifierKeyForDeviceDependentFlags(diff);
1994         if (modifierKey && modifierKey->keyCode != keyCode) {
1995           // Although, we're not sure the actual cause of this case, the stored
1996           // modifier information and the latest key event information may be
1997           // mismatched. Then, let's reset the stored information.
1998           // NOTE: If this happens, it may fail to handle NSEventTypeFlagsChanged event
1999           // in the default case (below). However, it's the rare case handler
2000           // and this case occurs rarely. So, we can ignore the edge case bug.
2001           NS_WARNING("Resetting stored modifier key information");
2002           mModifierKeys.Clear();
2003           modifierKey = nullptr;
2004         }
2005         if (!modifierKey) {
2006           mModifierKeys.AppendElement(ModifierKey(diff, keyCode));
2007         }
2008       }
2009       break;
2010     }
2012     // Currently we don't support Fn key since other browsers don't dispatch
2013     // events for it and we don't have keyCode for this key.
2014     // It should be supported when we implement .key and .char.
2015     case kVK_Function:
2016       break;
2018     // If the event is caused by something else than pressing or releasing a
2019     // single modifier key (for example by the app having been deactivated
2020     // using command-tab), use the modifiers themselves to determine which
2021     // key's event to dispatch, and whether it's a keyup or keydown event.
2022     // In all cases we assume one or more modifiers are being deactivated
2023     // (never activated) -- otherwise we'd have received one or more events
2024     // corresponding to a single modifier key being pressed.
2025     default: {
2026       NSUInteger modifiers = sLastModifierState;
2027       AutoTArray<unsigned short, 10> dispatchedKeyCodes;
2028       for (int32_t bit = 0; bit < 32; ++bit) {
2029         NSUInteger flag = 1 << bit;
2030         if (!(diff & flag)) {
2031           continue;
2032         }
2034         // Given correct information from the application, a flag change here
2035         // will normally be a deactivation (except for some lockable modifiers
2036         // such as CapsLock).  But some applications (like VNC) can send an
2037         // activating event with a zero keyCode.  So we need to check for that
2038         // here.
2039         bool dispatchKeyDown = ((flag & [aNativeEvent modifierFlags]) != 0);
2041         unsigned short keyCode = 0;
2042         if (flag & NSEventModifierFlagDeviceIndependentFlagsMask) {
2043           switch (flag) {
2044             case NSEventModifierFlagCapsLock:
2045               keyCode = kVK_CapsLock;
2046               dispatchKeyDown = true;
2047               break;
2049             case NSEventModifierFlagNumericPad:
2050               // NSEventModifierFlagNumericPad is fired by VNC a lot. But not all of
2051               // these events can really be Clear key events, so we just ignore
2052               // them.
2053               continue;
2055             case NSEventModifierFlagHelp:
2056               keyCode = kVK_Help;
2057               break;
2059             case NSEventModifierFlagFunction:
2060               // An NSEventModifierFlagFunction change here will normally be a
2061               // deactivation.  But sometimes it will be an activation send (by
2062               // VNC for example) with a zero keyCode.
2063               continue;
2065             // These cases (NSEventModifierFlagShift, NSEventModifierFlagControl,
2066             // NSEventModifierFlagOption and NSEventModifierFlagCommand) should be handled by the
2067             // other branch of the if statement, below (which handles device dependent flags).
2068             // However, some applications (like VNC) can send key events without
2069             // any device dependent flags, so we handle them here instead.
2070             case NSEventModifierFlagShift:
2071               keyCode = (modifiers & 0x0004) ? kVK_RightShift : kVK_Shift;
2072               break;
2073             case NSEventModifierFlagControl:
2074               keyCode = (modifiers & 0x2000) ? kVK_RightControl : kVK_Control;
2075               break;
2076             case NSEventModifierFlagOption:
2077               keyCode = (modifiers & 0x0040) ? kVK_RightOption : kVK_Option;
2078               break;
2079             case NSEventModifierFlagCommand:
2080               keyCode = (modifiers & 0x0010) ? kVK_RightCommand : kVK_Command;
2081               break;
2083             default:
2084               continue;
2085           }
2086         } else {
2087           const ModifierKey* modifierKey = GetModifierKeyForDeviceDependentFlags(flag);
2088           if (!modifierKey) {
2089             // See the note above (in the other branch of the if statement)
2090             // about the NSEventModifierFlagShift, NSEventModifierFlagControl,
2091             // NSEventModifierFlagOption and NSEventModifierFlagCommand cases.
2092             continue;
2093           }
2094           keyCode = modifierKey->keyCode;
2095         }
2097         // Remove flags
2098         modifiers &= ~flag;
2099         switch (keyCode) {
2100           case kVK_Shift: {
2101             const ModifierKey* modifierKey = GetModifierKeyForNativeKeyCode(kVK_RightShift);
2102             if (!modifierKey || !(modifiers & modifierKey->GetDeviceDependentFlags())) {
2103               modifiers &= ~NSEventModifierFlagShift;
2104             }
2105             break;
2106           }
2107           case kVK_RightShift: {
2108             const ModifierKey* modifierKey = GetModifierKeyForNativeKeyCode(kVK_Shift);
2109             if (!modifierKey || !(modifiers & modifierKey->GetDeviceDependentFlags())) {
2110               modifiers &= ~NSEventModifierFlagShift;
2111             }
2112             break;
2113           }
2114           case kVK_Command: {
2115             const ModifierKey* modifierKey = GetModifierKeyForNativeKeyCode(kVK_RightCommand);
2116             if (!modifierKey || !(modifiers & modifierKey->GetDeviceDependentFlags())) {
2117               modifiers &= ~NSEventModifierFlagCommand;
2118             }
2119             break;
2120           }
2121           case kVK_RightCommand: {
2122             const ModifierKey* modifierKey = GetModifierKeyForNativeKeyCode(kVK_Command);
2123             if (!modifierKey || !(modifiers & modifierKey->GetDeviceDependentFlags())) {
2124               modifiers &= ~NSEventModifierFlagCommand;
2125             }
2126             break;
2127           }
2128           case kVK_Control: {
2129             const ModifierKey* modifierKey = GetModifierKeyForNativeKeyCode(kVK_RightControl);
2130             if (!modifierKey || !(modifiers & modifierKey->GetDeviceDependentFlags())) {
2131               modifiers &= ~NSEventModifierFlagControl;
2132             }
2133             break;
2134           }
2135           case kVK_RightControl: {
2136             const ModifierKey* modifierKey = GetModifierKeyForNativeKeyCode(kVK_Control);
2137             if (!modifierKey || !(modifiers & modifierKey->GetDeviceDependentFlags())) {
2138               modifiers &= ~NSEventModifierFlagControl;
2139             }
2140             break;
2141           }
2142           case kVK_Option: {
2143             const ModifierKey* modifierKey = GetModifierKeyForNativeKeyCode(kVK_RightOption);
2144             if (!modifierKey || !(modifiers & modifierKey->GetDeviceDependentFlags())) {
2145               modifiers &= ~NSEventModifierFlagOption;
2146             }
2147             break;
2148           }
2149           case kVK_RightOption: {
2150             const ModifierKey* modifierKey = GetModifierKeyForNativeKeyCode(kVK_Option);
2151             if (!modifierKey || !(modifiers & modifierKey->GetDeviceDependentFlags())) {
2152               modifiers &= ~NSEventModifierFlagOption;
2153             }
2154             break;
2155           }
2156           case kVK_Help:
2157             modifiers &= ~NSEventModifierFlagHelp;
2158             break;
2159           default:
2160             break;
2161         }
2163         // Avoid dispatching same keydown/keyup events twice or more.
2164         // We must be able to assume that there is no case to dispatch
2165         // both keydown and keyup events with same key code value here.
2166         if (dispatchedKeyCodes.Contains(keyCode)) {
2167           continue;
2168         }
2169         dispatchedKeyCodes.AppendElement(keyCode);
2171         NSEvent* event = [NSEvent keyEventWithType:NSEventTypeFlagsChanged
2172                                           location:[aNativeEvent locationInWindow]
2173                                      modifierFlags:modifiers
2174                                          timestamp:[aNativeEvent timestamp]
2175                                       windowNumber:[aNativeEvent windowNumber]
2176                                            context:nil
2177                                         characters:@""
2178                        charactersIgnoringModifiers:@""
2179                                          isARepeat:NO
2180                                            keyCode:keyCode];
2181         DispatchKeyEventForFlagsChanged(event, dispatchKeyDown);
2182         if (Destroyed()) {
2183           break;
2184         }
2186         // Stop if focus has changed.
2187         // Check to see if mView is still the first responder.
2188         if (![mView isFirstResponder]) {
2189           break;
2190         }
2191       }
2192       break;
2193     }
2194   }
2196   // Be aware, the widget may have been destroyed.
2197   sLastModifierState = [aNativeEvent modifierFlags];
2199   NS_OBJC_END_TRY_IGNORE_BLOCK;
2202 const TextInputHandler::ModifierKey* TextInputHandler::GetModifierKeyForNativeKeyCode(
2203     unsigned short aKeyCode) const {
2204   for (ModifierKeyArray::index_type i = 0; i < mModifierKeys.Length(); ++i) {
2205     if (mModifierKeys[i].keyCode == aKeyCode) {
2206       return &((ModifierKey&)mModifierKeys[i]);
2207     }
2208   }
2209   return nullptr;
2212 const TextInputHandler::ModifierKey* TextInputHandler::GetModifierKeyForDeviceDependentFlags(
2213     NSUInteger aFlags) const {
2214   for (ModifierKeyArray::index_type i = 0; i < mModifierKeys.Length(); ++i) {
2215     if (mModifierKeys[i].GetDeviceDependentFlags() ==
2216         (aFlags & ~NSEventModifierFlagDeviceIndependentFlagsMask)) {
2217       return &((ModifierKey&)mModifierKeys[i]);
2218     }
2219   }
2220   return nullptr;
2223 void TextInputHandler::DispatchKeyEventForFlagsChanged(NSEvent* aNativeEvent,
2224                                                        bool aDispatchKeyDown) {
2225   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2227   if (Destroyed()) {
2228     return;
2229   }
2231   MOZ_LOG_KEY_OR_IME(LogLevel::Info,
2232                      ("%p TextInputHandler::DispatchKeyEventForFlagsChanged, aNativeEvent=%p, "
2233                       "type=%s, keyCode=%s (0x%X), aDispatchKeyDown=%s, IsIMEComposing()=%s",
2234                       this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
2235                       GetKeyNameForNativeKeyCode([aNativeEvent keyCode]), [aNativeEvent keyCode],
2236                       TrueOrFalse(aDispatchKeyDown), TrueOrFalse(IsIMEComposing())));
2238   if ([aNativeEvent type] != NSEventTypeFlagsChanged) {
2239     return;
2240   }
2242   nsresult rv = mDispatcher->BeginNativeInputTransaction();
2243   if (NS_WARN_IF(NS_FAILED(rv))) {
2244     MOZ_LOG_KEY_OR_IME(LogLevel::Error, ("%p   TextInputHandler::DispatchKeyEventForFlagsChanged, "
2245                                          "FAILED, due to BeginNativeInputTransaction() failure",
2246                                          this));
2247     return;
2248   }
2250   EventMessage message = aDispatchKeyDown ? eKeyDown : eKeyUp;
2252   // Fire a key event for the modifier key.  Note that even if modifier key
2253   // is pressed during composition, we shouldn't mark the keyboard event as
2254   // "processed by IME" since neither Chrome for macOS nor Safari does it.
2255   WidgetKeyboardEvent keyEvent(true, message, mWidget);
2256   InitKeyEvent(aNativeEvent, keyEvent, false);
2258   KeyEventState currentKeyEvent(aNativeEvent);
2259   nsEventStatus status = nsEventStatus_eIgnore;
2260   mDispatcher->DispatchKeyboardEvent(message, keyEvent, status, &currentKeyEvent);
2262   NS_OBJC_END_TRY_IGNORE_BLOCK;
2265 void TextInputHandler::InsertText(NSAttributedString* aAttrString, NSRange* aReplacementRange) {
2266   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2268   if (Destroyed()) {
2269     return;
2270   }
2272   KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
2274   MOZ_LOG_KEY_OR_IME(
2275       LogLevel::Info,
2276       ("%p TextInputHandler::InsertText, aAttrString=\"%s\", "
2277        "aReplacementRange=%p { location=%lu, length=%lu }, "
2278        "IsIMEComposing()=%s, "
2279        "keyevent=%p, keydownDispatched=%s, "
2280        "keydownHandled=%s, keypressDispatched=%s, "
2281        "causedOtherKeyEvents=%s, compositionDispatched=%s",
2282        this, GetCharacters([aAttrString string]), aReplacementRange,
2283        static_cast<unsigned long>(aReplacementRange ? aReplacementRange->location : 0),
2284        static_cast<unsigned long>(aReplacementRange ? aReplacementRange->length : 0),
2285        TrueOrFalse(IsIMEComposing()), currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr,
2286        currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyDownDispatched) : "N/A",
2287        currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
2288        currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A",
2289        currentKeyEvent ? TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A",
2290        currentKeyEvent ? TrueOrFalse(currentKeyEvent->mCompositionDispatched) : "N/A"));
2292   InputContext context = mWidget->GetInputContext();
2293   bool isEditable = (context.mIMEState.mEnabled == IMEEnabled::Enabled ||
2294                      context.mIMEState.mEnabled == IMEEnabled::Password);
2295   NSRange selectedRange = SelectedRange();
2297   nsAutoString str;
2298   nsCocoaUtils::GetStringForNSString([aAttrString string], str);
2300   AutoInsertStringClearer clearer(currentKeyEvent);
2301   if (currentKeyEvent) {
2302     currentKeyEvent->mInsertString = &str;
2303   }
2305   if (!IsIMEComposing() && str.IsEmpty()) {
2306     // nothing to do if there is no content which can be removed.
2307     if (!isEditable) {
2308       return;
2309     }
2310     // If replacement range is specified, we need to remove the range.
2311     // Otherwise, we need to remove the selected range if it's not collapsed.
2312     if (aReplacementRange && aReplacementRange->location != NSNotFound) {
2313       // nothing to do since the range is collapsed.
2314       if (aReplacementRange->length == 0) {
2315         return;
2316       }
2317       // If the replacement range is different from current selected range,
2318       // select the range.
2319       if (!NSEqualRanges(selectedRange, *aReplacementRange)) {
2320         NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
2321       }
2322       selectedRange = SelectedRange();
2323     }
2324     NS_ENSURE_TRUE_VOID(selectedRange.location != NSNotFound);
2325     if (selectedRange.length == 0) {
2326       return;  // nothing to do
2327     }
2328     // If this is caused by a key input, the keypress event which will be
2329     // dispatched later should cause the delete.  Therefore, nothing to do here.
2330     // Although, we're not sure if such case is actually possible.
2331     if (!currentKeyEvent) {
2332       return;
2333     }
2335     // When current keydown event causes this empty text input, let's
2336     // dispatch eKeyDown event before any other events.  Note that if we're
2337     // in a composition, we've already dispatched eKeyDown event from
2338     // TextInputHandler::HandleKeyDownEvent().
2339     // XXX Should we mark this eKeyDown event as "processed by IME"?
2340     RefPtr<TextInputHandler> kungFuDeathGrip(this);
2341     if (!IsIMEComposing() && !MaybeDispatchCurrentKeydownEvent(false)) {
2342       MOZ_LOG_KEY_OR_IME(LogLevel::Info,
2343                          ("%p   TextInputHandler::InsertText, eKeyDown caused focus move or "
2344                           "something and canceling the composition",
2345                           this));
2346       return;
2347     }
2349     // Delete the selected range.
2350     WidgetContentCommandEvent deleteCommandEvent(true, eContentCommandDelete, mWidget);
2351     DispatchEvent(deleteCommandEvent);
2352     NS_ENSURE_TRUE_VOID(deleteCommandEvent.mSucceeded);
2353     // Be aware! The widget might be destroyed here.
2354     return;
2355   }
2357   bool isReplacingSpecifiedRange = isEditable && aReplacementRange &&
2358                                    aReplacementRange->location != NSNotFound &&
2359                                    !NSEqualRanges(selectedRange, *aReplacementRange);
2361   // If this is not caused by pressing a key, there is a composition or
2362   // replacing a range which is different from current selection, let's
2363   // insert the text as committing a composition.
2364   // If InsertText() is called two or more times, we should insert all
2365   // text with composition events.
2366   // XXX When InsertText() is called multiple times, Chromium dispatches
2367   //     only one composition event.  So, we need to store InsertText()
2368   //     calls and flush later.
2369   if (!currentKeyEvent || currentKeyEvent->mCompositionDispatched || IsIMEComposing() ||
2370       isReplacingSpecifiedRange) {
2371     InsertTextAsCommittingComposition(aAttrString, aReplacementRange);
2372     if (currentKeyEvent) {
2373       currentKeyEvent->mCompositionDispatched = true;
2374     }
2375     return;
2376   }
2378   // Don't let the same event be fired twice when hitting
2379   // enter/return for Bug 420502.  However, Korean IME (or some other
2380   // simple IME) may work without marked text.  For example, composing
2381   // character may be inserted as committed text and it's modified with
2382   // aReplacementRange.  When a keydown starts new composition with
2383   // committing previous character, InsertText() may be called twice,
2384   // one is for committing previous character and then, inserting new
2385   // composing character as committed character.  In the latter case,
2386   // |CanDispatchKeyPressEvent()| returns true but we need to dispatch
2387   // keypress event for the new character.  So, when IME tries to insert
2388   // printable characters, we should ignore current key event state even
2389   // after the keydown has already caused dispatching composition event.
2390   // XXX Anyway, we should sort out around this at fixing bug 1338460.
2391   if (currentKeyEvent && !currentKeyEvent->CanDispatchKeyPressEvent() &&
2392       (str.IsEmpty() || (str.Length() == 1 && !IsPrintableChar(str[0])))) {
2393     return;
2394   }
2396   // This is the normal path to input a character when you press a key.
2397   // Let's dispatch eKeyDown event now.
2398   RefPtr<TextInputHandler> kungFuDeathGrip(this);
2399   if (!MaybeDispatchCurrentKeydownEvent(false)) {
2400     MOZ_LOG_KEY_OR_IME(LogLevel::Info,
2401                        ("%p   TextInputHandler::InsertText, eKeyDown caused focus move or "
2402                         "something and canceling the composition",
2403                         this));
2404     return;
2405   }
2407   // XXX Shouldn't we hold mDispatcher instead of mWidget?
2408   RefPtr<nsChildView> widget(mWidget);
2409   nsresult rv = mDispatcher->BeginNativeInputTransaction();
2410   if (NS_WARN_IF(NS_FAILED(rv))) {
2411     MOZ_LOG_KEY_OR_IME(LogLevel::Error, ("%p   TextInputHandler::InsertText, "
2412                                          "FAILED, due to BeginNativeInputTransaction() failure",
2413                                          this));
2414     return;
2415   }
2417   MOZ_LOG_KEY_OR_IME(LogLevel::Info,
2418                      ("%p   TextInputHandler::InsertText, "
2419                       "maybe dispatches eKeyPress event without control, alt and meta modifiers",
2420                       this));
2422   // Dispatch keypress event with char instead of compositionchange event
2423   WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget);
2424   // XXX Why do we need to dispatch keypress event for not inputting any
2425   //     string?  If it wants to delete the specified range, should we
2426   //     dispatch an eContentCommandDelete event instead?  Because this
2427   //     must not be caused by a key operation, a part of IME's processing.
2429   // Don't set other modifiers from the current event, because here in
2430   // -insertText: they've already been taken into account in creating
2431   // the input string.
2433   if (currentKeyEvent) {
2434     currentKeyEvent->InitKeyEvent(this, keypressEvent, false);
2435   } else {
2436     nsCocoaUtils::InitInputEvent(keypressEvent, static_cast<NSEvent*>(nullptr));
2437     keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
2438     keypressEvent.mKeyValue = str;
2439     // FYI: TextEventDispatcher will set mKeyCode to 0 for printable key's
2440     //      keypress events even if they don't cause inputting non-empty string.
2441   }
2443   // TODO:
2444   // If mCurrentKeyEvent.mKeyEvent is null, the text should be inputted as
2445   // composition events.
2446   nsEventStatus status = nsEventStatus_eIgnore;
2447   bool keyPressDispatched =
2448       mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status, currentKeyEvent);
2449   bool keyPressHandled = (status == nsEventStatus_eConsumeNoDefault);
2451   // Note: mWidget might have become null here. Don't count on it from here on.
2453   if (currentKeyEvent) {
2454     currentKeyEvent->mKeyPressHandled = keyPressHandled;
2455     currentKeyEvent->mKeyPressDispatched = keyPressDispatched;
2456   }
2458   NS_OBJC_END_TRY_IGNORE_BLOCK;
2461 bool TextInputHandler::HandleCommand(Command aCommand) {
2462   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2464   if (Destroyed()) {
2465     return false;
2466   }
2468   KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
2470   MOZ_LOG_KEY_OR_IME(
2471       LogLevel::Info,
2472       ("%p TextInputHandler::HandleCommand, "
2473        "aCommand=%s, IsIMEComposing()=%s, "
2474        "keyevent=%p, keydownHandled=%s, keypressDispatched=%s, "
2475        "causedOtherKeyEvents=%s, compositionDispatched=%s",
2476        this, ToChar(aCommand), TrueOrFalse(IsIMEComposing()),
2477        currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr,
2478        currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
2479        currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A",
2480        currentKeyEvent ? TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A",
2481        currentKeyEvent ? TrueOrFalse(currentKeyEvent->mCompositionDispatched) : "N/A"));
2483   // The command shouldn't be handled, let's ignore it.
2484   if (currentKeyEvent && !currentKeyEvent->CanHandleCommand()) {
2485     return false;
2486   }
2488   // When current keydown event causes this command, let's dispatch
2489   // eKeyDown event before any other events.  Note that if we're in a
2490   // composition, we've already dispatched eKeyDown event from
2491   // TextInputHandler::HandleKeyDownEvent().
2492   RefPtr<TextInputHandler> kungFuDeathGrip(this);
2493   if (!IsIMEComposing() && !MaybeDispatchCurrentKeydownEvent(false)) {
2494     MOZ_LOG_KEY_OR_IME(LogLevel::Info,
2495                        ("%p   TextInputHandler::SetMarkedText, eKeyDown caused focus move or "
2496                         "something and canceling the composition",
2497                         this));
2498     return false;
2499   }
2501   // If it's in composition, we cannot dispatch keypress event.
2502   // Therefore, we should use different approach or give up to handle
2503   // the command.
2504   if (IsIMEComposing()) {
2505     switch (aCommand) {
2506       case Command::InsertLineBreak:
2507       case Command::InsertParagraph: {
2508         // Insert '\n' as committing composition.
2509         // Otherwise, we need to dispatch keypress event because HTMLEditor
2510         // doesn't treat "\n" in composition string as a line break unless
2511         // the whitespace is treated as pre (see bug 1350541).  In strictly
2512         // speaking, we should dispatch keypress event as-is if it's handling
2513         // NSEventTypeKeyDown event or should insert it with committing composition.
2514         NSAttributedString* lineBreaker = [[NSAttributedString alloc] initWithString:@"\n"];
2515         InsertTextAsCommittingComposition(lineBreaker, nullptr);
2516         if (currentKeyEvent) {
2517           currentKeyEvent->mCompositionDispatched = true;
2518         }
2519         [lineBreaker release];
2520         return true;
2521       }
2522       case Command::DeleteCharBackward:
2523       case Command::DeleteCharForward:
2524       case Command::DeleteToBeginningOfLine:
2525       case Command::DeleteWordBackward:
2526       case Command::DeleteWordForward:
2527         // Don't remove any contents during composition.
2528         return false;
2529       case Command::InsertTab:
2530       case Command::InsertBacktab:
2531         // Don't move focus during composition.
2532         return false;
2533       case Command::CharNext:
2534       case Command::SelectCharNext:
2535       case Command::WordNext:
2536       case Command::SelectWordNext:
2537       case Command::EndLine:
2538       case Command::SelectEndLine:
2539       case Command::CharPrevious:
2540       case Command::SelectCharPrevious:
2541       case Command::WordPrevious:
2542       case Command::SelectWordPrevious:
2543       case Command::BeginLine:
2544       case Command::SelectBeginLine:
2545       case Command::LinePrevious:
2546       case Command::SelectLinePrevious:
2547       case Command::MoveTop:
2548       case Command::LineNext:
2549       case Command::SelectLineNext:
2550       case Command::MoveBottom:
2551       case Command::SelectBottom:
2552       case Command::SelectPageUp:
2553       case Command::SelectPageDown:
2554       case Command::ScrollBottom:
2555       case Command::ScrollTop:
2556         // Don't move selection during composition.
2557         return false;
2558       case Command::CancelOperation:
2559       case Command::Complete:
2560         // Don't handle Escape key by ourselves during composition.
2561         return false;
2562       case Command::ScrollPageUp:
2563       case Command::ScrollPageDown:
2564         // Allow to scroll.
2565         break;
2566       default:
2567         break;
2568     }
2569   }
2571   RefPtr<nsChildView> widget(mWidget);
2572   nsresult rv = mDispatcher->BeginNativeInputTransaction();
2573   if (NS_WARN_IF(NS_FAILED(rv))) {
2574     MOZ_LOG_KEY_OR_IME(LogLevel::Error, ("%p,   TextInputHandler::HandleCommand, "
2575                                          "FAILED, due to BeginNativeInputTransaction() failure",
2576                                          this));
2577     return false;
2578   }
2580   // TODO: If it's not appropriate keypress but user customized the OS
2581   //       settings to do the command with other key, we should just set
2582   //       command to the keypress event and it should be handled as
2583   //       the key press in editor.
2585   // If it's handling actual key event and hasn't cause any composition
2586   // events nor other key events, we should expose actual modifier state.
2587   // Otherwise, we should adjust Control, Option and Command state since
2588   // editor may behave differently if some of them are active.
2589   bool dispatchFakeKeyPress = !(currentKeyEvent && currentKeyEvent->IsProperKeyEvent(aCommand));
2591   WidgetKeyboardEvent keydownEvent(true, eKeyDown, widget);
2592   WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget);
2593   if (!dispatchFakeKeyPress) {
2594     // If we're acutally handling a key press, we should dispatch
2595     // the keypress event as-is.
2596     currentKeyEvent->InitKeyEvent(this, keydownEvent, false);
2597     currentKeyEvent->InitKeyEvent(this, keypressEvent, false);
2598   } else {
2599     // Otherwise, we should dispatch "fake" keypress event.
2600     // However, for making it possible to compute edit commands, we need to
2601     // set current native key event to the fake keyboard event even if it's
2602     // not same as what we expect since the native keyboard event caused
2603     // this command.
2604     NSEvent* keyEvent = currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr;
2605     keydownEvent.mNativeKeyEvent = keypressEvent.mNativeKeyEvent = keyEvent;
2606     NS_WARNING_ASSERTION(keypressEvent.mNativeKeyEvent,
2607                          "Without native key event, NativeKeyBindings cannot compute aCommand");
2608     switch (aCommand) {
2609       case Command::InsertLineBreak:
2610       case Command::InsertParagraph: {
2611         // Although, Shift+Enter and Enter are work differently in HTML
2612         // editor, we should expose actual Shift state if it's caused by
2613         // Enter key for compatibility with Chromium.  Chromium breaks
2614         // line in HTML editor with default pargraph separator when Enter
2615         // is pressed, with <br> element when Shift+Enter.  Safari breaks
2616         // line in HTML editor with default paragraph separator when
2617         // Enter, Shift+Enter or Option+Enter.  So, we should not change
2618         // Shift+Enter meaning when there was composition string or not.
2619         nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
2620         keypressEvent.mKeyCode = NS_VK_RETURN;
2621         keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_Enter;
2622         keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
2623         if (aCommand == Command::InsertLineBreak) {
2624           // In default settings, Ctrl + Enter causes insertLineBreak command.
2625           // So, let's make Ctrl state active of the keypress event.
2626           keypressEvent.mModifiers |= MODIFIER_CONTROL;
2627         }
2628         break;
2629       }
2630       case Command::InsertTab:
2631       case Command::InsertBacktab:
2632         nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
2633         keypressEvent.mKeyCode = NS_VK_TAB;
2634         keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_Tab;
2635         keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
2636         if (aCommand == Command::InsertBacktab) {
2637           keypressEvent.mModifiers |= MODIFIER_SHIFT;
2638         }
2639         break;
2640       case Command::DeleteCharBackward:
2641       case Command::DeleteToBeginningOfLine:
2642       case Command::DeleteWordBackward: {
2643         nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
2644         keypressEvent.mKeyCode = NS_VK_BACK;
2645         keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_Backspace;
2646         keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
2647         if (aCommand == Command::DeleteToBeginningOfLine) {
2648           keypressEvent.mModifiers |= MODIFIER_META;
2649         } else if (aCommand == Command::DeleteWordBackward) {
2650           keypressEvent.mModifiers |= MODIFIER_ALT;
2651         }
2652         break;
2653       }
2654       case Command::DeleteCharForward:
2655       case Command::DeleteWordForward: {
2656         nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
2657         keypressEvent.mKeyCode = NS_VK_DELETE;
2658         keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_Delete;
2659         keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
2660         if (aCommand == Command::DeleteWordForward) {
2661           keypressEvent.mModifiers |= MODIFIER_ALT;
2662         }
2663         break;
2664       }
2665       case Command::CharNext:
2666       case Command::SelectCharNext:
2667       case Command::WordNext:
2668       case Command::SelectWordNext:
2669       case Command::EndLine:
2670       case Command::SelectEndLine: {
2671         nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
2672         keypressEvent.mKeyCode = NS_VK_RIGHT;
2673         keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_ArrowRight;
2674         keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
2675         if (aCommand == Command::SelectCharNext || aCommand == Command::SelectWordNext ||
2676             aCommand == Command::SelectEndLine) {
2677           keypressEvent.mModifiers |= MODIFIER_SHIFT;
2678         }
2679         if (aCommand == Command::WordNext || aCommand == Command::SelectWordNext) {
2680           keypressEvent.mModifiers |= MODIFIER_ALT;
2681         }
2682         if (aCommand == Command::EndLine || aCommand == Command::SelectEndLine) {
2683           keypressEvent.mModifiers |= MODIFIER_META;
2684         }
2685         break;
2686       }
2687       case Command::CharPrevious:
2688       case Command::SelectCharPrevious:
2689       case Command::WordPrevious:
2690       case Command::SelectWordPrevious:
2691       case Command::BeginLine:
2692       case Command::SelectBeginLine: {
2693         nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
2694         keypressEvent.mKeyCode = NS_VK_LEFT;
2695         keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_ArrowLeft;
2696         keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
2697         if (aCommand == Command::SelectCharPrevious || aCommand == Command::SelectWordPrevious ||
2698             aCommand == Command::SelectBeginLine) {
2699           keypressEvent.mModifiers |= MODIFIER_SHIFT;
2700         }
2701         if (aCommand == Command::WordPrevious || aCommand == Command::SelectWordPrevious) {
2702           keypressEvent.mModifiers |= MODIFIER_ALT;
2703         }
2704         if (aCommand == Command::BeginLine || aCommand == Command::SelectBeginLine) {
2705           keypressEvent.mModifiers |= MODIFIER_META;
2706         }
2707         break;
2708       }
2709       case Command::LinePrevious:
2710       case Command::SelectLinePrevious:
2711       case Command::MoveTop:
2712       case Command::SelectTop: {
2713         nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
2714         keypressEvent.mKeyCode = NS_VK_UP;
2715         keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_ArrowUp;
2716         keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
2717         if (aCommand == Command::SelectLinePrevious || aCommand == Command::SelectTop) {
2718           keypressEvent.mModifiers |= MODIFIER_SHIFT;
2719         }
2720         if (aCommand == Command::MoveTop || aCommand == Command::SelectTop) {
2721           keypressEvent.mModifiers |= MODIFIER_META;
2722         }
2723         break;
2724       }
2725       case Command::LineNext:
2726       case Command::SelectLineNext:
2727       case Command::MoveBottom:
2728       case Command::SelectBottom: {
2729         nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
2730         keypressEvent.mKeyCode = NS_VK_DOWN;
2731         keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_ArrowDown;
2732         keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
2733         if (aCommand == Command::SelectLineNext || aCommand == Command::SelectBottom) {
2734           keypressEvent.mModifiers |= MODIFIER_SHIFT;
2735         }
2736         if (aCommand == Command::MoveBottom || aCommand == Command::SelectBottom) {
2737           keypressEvent.mModifiers |= MODIFIER_META;
2738         }
2739         break;
2740       }
2741       case Command::ScrollPageUp:
2742       case Command::SelectPageUp: {
2743         nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
2744         keypressEvent.mKeyCode = NS_VK_PAGE_UP;
2745         keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_PageUp;
2746         keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
2747         if (aCommand == Command::SelectPageUp) {
2748           keypressEvent.mModifiers |= MODIFIER_SHIFT;
2749         }
2750         break;
2751       }
2752       case Command::ScrollPageDown:
2753       case Command::SelectPageDown: {
2754         nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
2755         keypressEvent.mKeyCode = NS_VK_PAGE_DOWN;
2756         keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_PageDown;
2757         keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
2758         if (aCommand == Command::SelectPageDown) {
2759           keypressEvent.mModifiers |= MODIFIER_SHIFT;
2760         }
2761         break;
2762       }
2763       case Command::ScrollBottom:
2764       case Command::ScrollTop: {
2765         nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
2766         if (aCommand == Command::ScrollBottom) {
2767           keypressEvent.mKeyCode = NS_VK_END;
2768           keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_End;
2769         } else {
2770           keypressEvent.mKeyCode = NS_VK_HOME;
2771           keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_Home;
2772         }
2773         keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
2774         break;
2775       }
2776       case Command::CancelOperation:
2777       case Command::Complete: {
2778         nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
2779         keypressEvent.mKeyCode = NS_VK_ESCAPE;
2780         keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_Escape;
2781         keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
2782         if (aCommand == Command::Complete) {
2783           keypressEvent.mModifiers |= MODIFIER_ALT;
2784         }
2785         break;
2786       }
2787       default:
2788         return false;
2789     }
2791     nsCocoaUtils::InitInputEvent(keydownEvent, keyEvent);
2792     keydownEvent.mKeyCode = keypressEvent.mKeyCode;
2793     keydownEvent.mKeyNameIndex = keypressEvent.mKeyNameIndex;
2794     keydownEvent.mModifiers = keypressEvent.mModifiers;
2795   }
2797   // We've stopped dispatching "keypress" events of non-printable keys on
2798   // the web.  Therefore, we need to dispatch eKeyDown event here for web
2799   // apps.  This is non-standard behavior if we've already dispatched a
2800   // "keydown" event.  However, Chrome also dispatches such fake "keydown"
2801   // (and "keypress") event for making same behavior as Safari.
2802   nsEventStatus status = nsEventStatus_eIgnore;
2803   if (mDispatcher->DispatchKeyboardEvent(eKeyDown, keydownEvent, status, nullptr)) {
2804     bool keydownHandled = status == nsEventStatus_eConsumeNoDefault;
2805     if (currentKeyEvent) {
2806       currentKeyEvent->mKeyDownDispatched = true;
2807       currentKeyEvent->mKeyDownHandled |= keydownHandled;
2808     }
2809     if (keydownHandled) {
2810       // Don't dispatch eKeyPress event if preceding eKeyDown event is
2811       // consumed for conforming to UI Events.
2812       // XXX Perhaps, we should ignore previous eKeyDown event result
2813       //     even if we've already dispatched because it may notify web apps
2814       //     of different key information, e.g., it's handled by IME, but
2815       //     web apps want to handle only this key.
2816       return true;
2817     }
2818   }
2820   bool keyPressDispatched =
2821       mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status, currentKeyEvent);
2822   bool keyPressHandled = (status == nsEventStatus_eConsumeNoDefault);
2824   // NOTE: mWidget might have become null here.
2826   if (keyPressDispatched) {
2827     // Record the keypress event state only when it dispatched actual Enter
2828     // keypress event because in other cases, the keypress event just a
2829     // messenger.  E.g., if it's caused by different key, keypress event for
2830     // the actual key should be dispatched.
2831     if (!dispatchFakeKeyPress && currentKeyEvent) {
2832       currentKeyEvent->mKeyPressHandled = keyPressHandled;
2833       currentKeyEvent->mKeyPressDispatched = keyPressDispatched;
2834     }
2835     return true;
2836   }
2838   // If keypress event isn't dispatched as expected, we should fallback to
2839   // using composition events.
2840   if (aCommand == Command::InsertLineBreak || aCommand == Command::InsertParagraph) {
2841     NSAttributedString* lineBreaker = [[NSAttributedString alloc] initWithString:@"\n"];
2842     InsertTextAsCommittingComposition(lineBreaker, nullptr);
2843     if (currentKeyEvent) {
2844       currentKeyEvent->mCompositionDispatched = true;
2845     }
2846     [lineBreaker release];
2847     return true;
2848   }
2850   return false;
2852   NS_OBJC_END_TRY_BLOCK_RETURN(false);
2855 bool TextInputHandler::DoCommandBySelector(const char* aSelector) {
2856   RefPtr<nsChildView> widget(mWidget);
2858   KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
2860   MOZ_LOG_KEY_OR_IME(
2861       LogLevel::Info,
2862       ("%p TextInputHandler::DoCommandBySelector, aSelector=\"%s\", "
2863        "Destroyed()=%s, keydownDispatched=%s, keydownHandled=%s, "
2864        "keypressDispatched=%s, keypressHandled=%s, causedOtherKeyEvents=%s",
2865        this, aSelector ? aSelector : "", TrueOrFalse(Destroyed()),
2866        currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyDownDispatched) : "N/A",
2867        currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
2868        currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A",
2869        currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyPressHandled) : "N/A",
2870        currentKeyEvent ? TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A"));
2872   // If the command isn't caused by key operation, the command should
2873   // be handled in the super class of the caller.
2874   if (!currentKeyEvent) {
2875     return Destroyed();
2876   }
2878   // When current keydown event causes this command, let's dispatch
2879   // eKeyDown event before any other events.  Note that if we're in a
2880   // composition, we've already dispatched eKeyDown event from
2881   // TextInputHandler::HandleKeyDownEvent().
2882   RefPtr<TextInputHandler> kungFuDeathGrip(this);
2883   if (!IsIMEComposing() && !MaybeDispatchCurrentKeydownEvent(false)) {
2884     MOZ_LOG_KEY_OR_IME(LogLevel::Info,
2885                        ("%p   TextInputHandler::SetMarkedText, eKeyDown caused focus move or "
2886                         "something and canceling the composition",
2887                         this));
2888     return true;
2889   }
2891   // If the key operation causes this command, should dispatch a keypress
2892   // event.
2893   // XXX This must be worng.  Even if this command is caused by the key
2894   //     operation, its our default action can be different from the
2895   //     command.  So, in this case, we should dispatch a keypress event
2896   //     which have the command and editor should handle it.
2897   if (currentKeyEvent->CanDispatchKeyPressEvent()) {
2898     nsresult rv = mDispatcher->BeginNativeInputTransaction();
2899     if (NS_WARN_IF(NS_FAILED(rv))) {
2900       MOZ_LOG_KEY_OR_IME(LogLevel::Error, ("%p   TextInputHandler::DoCommandBySelector, "
2901                                            "FAILED, due to BeginNativeInputTransaction() failure "
2902                                            "at dispatching keypress",
2903                                            this));
2904       return Destroyed();
2905     }
2907     WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget);
2908     currentKeyEvent->InitKeyEvent(this, keypressEvent, false);
2910     nsEventStatus status = nsEventStatus_eIgnore;
2911     currentKeyEvent->mKeyPressDispatched =
2912         mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status, currentKeyEvent);
2913     currentKeyEvent->mKeyPressHandled = (status == nsEventStatus_eConsumeNoDefault);
2914     MOZ_LOG_KEY_OR_IME(
2915         LogLevel::Info,
2916         ("%p   TextInputHandler::DoCommandBySelector, keypress event "
2917          "dispatched, Destroyed()=%s, keypressHandled=%s",
2918          this, TrueOrFalse(Destroyed()), TrueOrFalse(currentKeyEvent->mKeyPressHandled)));
2919     // This command is now dispatched with keypress event.
2920     // So, this shouldn't be handled by nobody anymore.
2921     return true;
2922   }
2924   // If the key operation didn't cause keypress event or caused keypress event
2925   // but not prevented its default, we need to honor the command.  For example,
2926   // Korean IME sends "insertNewline:" when committing existing composition
2927   // with Enter key press.  In such case, the key operation has been consumed
2928   // by the committing composition but we still need to handle the command.
2929   if (Destroyed() || !currentKeyEvent->CanHandleCommand()) {
2930     return true;
2931   }
2933   // cancelOperation: command is fired after Escape or Command + Period.
2934   // However, if ChildView implements cancelOperation:, calling
2935   // [[ChildView super] doCommandBySelector:aSelector] when Command + Period
2936   // causes only a call of [ChildView cancelOperation:sender].  I.e.,
2937   // [ChildView keyDown:theEvent] becomes to be never called.  For avoiding
2938   // this odd behavior, we need to handle the command before super class of
2939   // ChildView only when current key event is proper event to fire Escape
2940   // keypress event.
2941   if (!strcmp(aSelector, "cancelOperation:") && currentKeyEvent &&
2942       currentKeyEvent->IsProperKeyEvent(Command::CancelOperation)) {
2943     return HandleCommand(Command::CancelOperation);
2944   }
2946   // Otherwise, we've not handled the command yet.  Propagate the command
2947   // to the super class of ChildView.
2948   return false;
2951 #pragma mark -
2953 /******************************************************************************
2955  *  IMEInputHandler implementation (static methods)
2957  ******************************************************************************/
2959 bool IMEInputHandler::sStaticMembersInitialized = false;
2960 bool IMEInputHandler::sCachedIsForRTLLangage = false;
2961 CFStringRef IMEInputHandler::sLatestIMEOpenedModeInputSourceID = nullptr;
2962 IMEInputHandler* IMEInputHandler::sFocusedIMEHandler = nullptr;
2964 // static
2965 void IMEInputHandler::InitStaticMembers() {
2966   if (sStaticMembersInitialized) return;
2967   sStaticMembersInitialized = true;
2968   // We need to check the keyboard layout changes on all applications.
2969   CFNotificationCenterRef center = ::CFNotificationCenterGetDistributedCenter();
2970   // XXX Don't we need to remove the observer at shut down?
2971   // Mac Dev Center's document doesn't say how to remove the observer if
2972   // the second parameter is NULL.
2973   ::CFNotificationCenterAddObserver(center, NULL, OnCurrentTextInputSourceChange,
2974                                     kTISNotifySelectedKeyboardInputSourceChanged, NULL,
2975                                     CFNotificationSuspensionBehaviorDeliverImmediately);
2976   // Initiailize with the current keyboard layout
2977   OnCurrentTextInputSourceChange(NULL, NULL, kTISNotifySelectedKeyboardInputSourceChanged, NULL,
2978                                  NULL);
2981 // static
2982 void IMEInputHandler::OnCurrentTextInputSourceChange(CFNotificationCenterRef aCenter,
2983                                                      void* aObserver, CFStringRef aName,
2984                                                      const void* aObject,
2985                                                      CFDictionaryRef aUserInfo) {
2986   // Cache the latest IME opened mode to sLatestIMEOpenedModeInputSourceID.
2987   TISInputSourceWrapper tis;
2988   tis.InitByCurrentInputSource();
2989   if (tis.IsOpenedIMEMode()) {
2990     tis.GetInputSourceID(sLatestIMEOpenedModeInputSourceID);
2991     // Collect Input Source ID which includes input mode in most cases.
2992     // However, if it's Japanese IME, collecting input mode (e.g.,
2993     // "HiraganaKotei") does not make sense because in most languages,
2994     // input mode changes "how to input", but Japanese IME changes
2995     // "which type of characters to input".  I.e., only Japanese IME
2996     // users may use multiple input modes.  If we'd collect each type of
2997     // input mode of Japanese IMEs, it'd be difficult to count actual
2998     // users of each IME from the result.  So, only when active IME is
2999     // a Japanese IME, we should use Bundle ID which does not contain
3000     // input mode instead.
3001     nsAutoString key;
3002     if (tis.IsForJapaneseLanguage()) {
3003       tis.GetBundleID(key);
3004     } else {
3005       tis.GetInputSourceID(key);
3006     }
3007     // 72 is kMaximumKeyStringLength in TelemetryScalar.cpp
3008     if (key.Length() > 72) {
3009       if (NS_IS_SURROGATE_PAIR(key[72 - 2], key[72 - 1])) {
3010         key.Truncate(72 - 2);
3011       } else {
3012         key.Truncate(72 - 1);
3013       }
3014       // U+2026 is "..."
3015       key.Append(char16_t(0x2026));
3016     }
3017     Telemetry::ScalarSet(Telemetry::ScalarID::WIDGET_IME_NAME_ON_MAC, key, true);
3018   }
3020   if (MOZ_LOG_TEST(gIMELog, LogLevel::Info)) {
3021     static CFStringRef sLastTIS = nullptr;
3022     CFStringRef newTIS;
3023     tis.GetInputSourceID(newTIS);
3024     if (!sLastTIS || ::CFStringCompare(sLastTIS, newTIS, 0) != kCFCompareEqualTo) {
3025       TISInputSourceWrapper tis1, tis2, tis3, tis4, tis5;
3026       tis1.InitByCurrentKeyboardLayout();
3027       tis2.InitByCurrentASCIICapableInputSource();
3028       tis3.InitByCurrentASCIICapableKeyboardLayout();
3029       tis4.InitByCurrentInputMethodKeyboardLayoutOverride();
3030       tis5.InitByTISInputSourceRef(tis.GetKeyboardLayoutInputSource());
3031       CFStringRef is0 = nullptr, is1 = nullptr, is2 = nullptr, is3 = nullptr, is4 = nullptr,
3032                   is5 = nullptr, type0 = nullptr, lang0 = nullptr, bundleID0 = nullptr;
3033       tis.GetInputSourceID(is0);
3034       tis1.GetInputSourceID(is1);
3035       tis2.GetInputSourceID(is2);
3036       tis3.GetInputSourceID(is3);
3037       tis4.GetInputSourceID(is4);
3038       tis5.GetInputSourceID(is5);
3039       tis.GetInputSourceType(type0);
3040       tis.GetPrimaryLanguage(lang0);
3041       tis.GetBundleID(bundleID0);
3043       MOZ_LOG(gIMELog, LogLevel::Info,
3044               ("IMEInputHandler::OnCurrentTextInputSourceChange,\n"
3045                "  Current Input Source is changed to:\n"
3046                "    currentInputContext=%p\n"
3047                "    %s\n"
3048                "      type=%s %s\n"
3049                "      overridden keyboard layout=%s\n"
3050                "      used keyboard layout for translation=%s\n"
3051                "    primary language=%s\n"
3052                "    bundle ID=%s\n"
3053                "    current ASCII capable Input Source=%s\n"
3054                "    current Keyboard Layout=%s\n"
3055                "    current ASCII capable Keyboard Layout=%s",
3056                [NSTextInputContext currentInputContext], GetCharacters(is0), GetCharacters(type0),
3057                tis.IsASCIICapable() ? "- ASCII capable " : "", GetCharacters(is4),
3058                GetCharacters(is5), GetCharacters(lang0), GetCharacters(bundleID0),
3059                GetCharacters(is2), GetCharacters(is1), GetCharacters(is3)));
3060     }
3061     sLastTIS = newTIS;
3062   }
3064   /**
3065    * When the direction is changed, all the children are notified.
3066    * No need to treat the initial case separately because it is covered
3067    * by the general case (sCachedIsForRTLLangage is initially false)
3068    */
3069   if (sCachedIsForRTLLangage != tis.IsForRTLLanguage()) {
3070     WidgetUtils::SendBidiKeyboardInfoToContent();
3071     sCachedIsForRTLLangage = tis.IsForRTLLanguage();
3072   }
3075 // static
3076 void IMEInputHandler::FlushPendingMethods(nsITimer* aTimer, void* aClosure) {
3077   NS_ASSERTION(aClosure, "aClosure is null");
3078   static_cast<IMEInputHandler*>(aClosure)->ExecutePendingMethods();
3081 // static
3082 CFArrayRef IMEInputHandler::CreateAllIMEModeList() {
3083   const void* keys[] = {kTISPropertyInputSourceType};
3084   const void* values[] = {kTISTypeKeyboardInputMode};
3085   CFDictionaryRef filter = ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
3086   NS_ASSERTION(filter, "failed to create the filter");
3087   CFArrayRef list = ::TISCreateInputSourceList(filter, true);
3088   ::CFRelease(filter);
3089   return list;
3092 // static
3093 void IMEInputHandler::DebugPrintAllIMEModes() {
3094   if (MOZ_LOG_TEST(gIMELog, LogLevel::Info)) {
3095     CFArrayRef list = CreateAllIMEModeList();
3096     MOZ_LOG(gIMELog, LogLevel::Info, ("IME mode configuration:"));
3097     CFIndex idx = ::CFArrayGetCount(list);
3098     TISInputSourceWrapper tis;
3099     for (CFIndex i = 0; i < idx; ++i) {
3100       TISInputSourceRef inputSource =
3101           static_cast<TISInputSourceRef>(const_cast<void*>(::CFArrayGetValueAtIndex(list, i)));
3102       tis.InitByTISInputSourceRef(inputSource);
3103       nsAutoString name, isid, bundleID;
3104       tis.GetLocalizedName(name);
3105       tis.GetInputSourceID(isid);
3106       tis.GetBundleID(bundleID);
3107       MOZ_LOG(gIMELog, LogLevel::Info,
3108               ("  %s\t<%s>%s%s\n"
3109                "    bundled in <%s>\n",
3110                NS_ConvertUTF16toUTF8(name).get(), NS_ConvertUTF16toUTF8(isid).get(),
3111                tis.IsASCIICapable() ? "" : "\t(Isn't ASCII capable)",
3112                tis.IsEnabled() ? "" : "\t(Isn't Enabled)", NS_ConvertUTF16toUTF8(bundleID).get()));
3113     }
3114     ::CFRelease(list);
3115   }
3118 // static
3119 TSMDocumentID IMEInputHandler::GetCurrentTSMDocumentID() {
3120   // At least on Mac OS X 10.6.x and 10.7.x, ::TSMGetActiveDocument() has a bug.
3121   // The result of ::TSMGetActiveDocument() isn't modified for new active text
3122   // input context until [NSTextInputContext currentInputContext] is called.
3123   // Therefore, we need to call it here.
3124   [NSTextInputContext currentInputContext];
3125   return ::TSMGetActiveDocument();
3128 #pragma mark -
3130 /******************************************************************************
3132  *  IMEInputHandler implementation #1
3133  *    The methods are releated to the pending methods.  Some jobs should be
3134  *    run after the stack is finished, e.g, some methods cannot run the jobs
3135  *    during processing the focus event.  And also some other jobs should be
3136  *    run at the next focus event is processed.
3137  *    The pending methods are recorded in mPendingMethods.  They are executed
3138  *    by ExecutePendingMethods via FlushPendingMethods.
3140  ******************************************************************************/
3142 nsresult IMEInputHandler::NotifyIME(TextEventDispatcher* aTextEventDispatcher,
3143                                     const IMENotification& aNotification) {
3144   switch (aNotification.mMessage) {
3145     case REQUEST_TO_COMMIT_COMPOSITION:
3146       CommitIMEComposition();
3147       return NS_OK;
3148     case REQUEST_TO_CANCEL_COMPOSITION:
3149       CancelIMEComposition();
3150       return NS_OK;
3151     case NOTIFY_IME_OF_FOCUS:
3152       if (IsFocused()) {
3153         nsIWidget* widget = aTextEventDispatcher->GetWidget();
3154         if (widget && widget->GetInputContext().IsPasswordEditor()) {
3155           EnableSecureEventInput();
3156         } else {
3157           EnsureSecureEventInputDisabled();
3158         }
3159       }
3160       OnFocusChangeInGecko(true);
3161       return NS_OK;
3162     case NOTIFY_IME_OF_BLUR:
3163       OnFocusChangeInGecko(false);
3164       return NS_OK;
3165     case NOTIFY_IME_OF_SELECTION_CHANGE:
3166       OnSelectionChange(aNotification);
3167       return NS_OK;
3168     case NOTIFY_IME_OF_POSITION_CHANGE:
3169       OnLayoutChange();
3170       return NS_OK;
3171     default:
3172       return NS_ERROR_NOT_IMPLEMENTED;
3173   }
3176 NS_IMETHODIMP_(IMENotificationRequests)
3177 IMEInputHandler::GetIMENotificationRequests() {
3178   // XXX Shouldn't we move floating window which shows composition string
3179   //     when plugin has focus and its parent is scrolled or the window is
3180   //     moved?
3181   return IMENotificationRequests();
3184 NS_IMETHODIMP_(void)
3185 IMEInputHandler::OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) {
3186   // XXX When input transaction is being stolen by add-on, what should we do?
3189 NS_IMETHODIMP_(void)
3190 IMEInputHandler::WillDispatchKeyboardEvent(TextEventDispatcher* aTextEventDispatcher,
3191                                            WidgetKeyboardEvent& aKeyboardEvent,
3192                                            uint32_t aIndexOfKeypress, void* aData) {
3193   // If the keyboard event is not caused by a native key event, we can do
3194   // nothing here.
3195   if (!aData) {
3196     return;
3197   }
3199   KeyEventState* currentKeyEvent = static_cast<KeyEventState*>(aData);
3200   NSEvent* nativeEvent = currentKeyEvent->mKeyEvent;
3201   nsAString* insertString = currentKeyEvent->mInsertString;
3202   if (aKeyboardEvent.mMessage == eKeyPress && aIndexOfKeypress == 0 &&
3203       (!insertString || insertString->IsEmpty())) {
3204     // Inform the child process that this is an event that we want a reply
3205     // from.
3206     // XXX This should be called only when the target is a remote process.
3207     //     However, it's difficult to check it under widget/.
3208     //     So, let's do this here for now, then,
3209     //     EventStateManager::PreHandleEvent() will reset the flags if
3210     //     the event target isn't in remote process.
3211     aKeyboardEvent.MarkAsWaitingReplyFromRemoteProcess();
3212   }
3213   if (KeyboardLayoutOverrideRef().mOverrideEnabled) {
3214     TISInputSourceWrapper tis;
3215     tis.InitByLayoutID(KeyboardLayoutOverrideRef().mKeyboardLayout, true);
3216     tis.WillDispatchKeyboardEvent(nativeEvent, insertString, aIndexOfKeypress, aKeyboardEvent);
3217   } else {
3218     TISInputSourceWrapper::CurrentInputSource().WillDispatchKeyboardEvent(
3219         nativeEvent, insertString, aIndexOfKeypress, aKeyboardEvent);
3220   }
3222   // Remove basic modifiers from keypress event because if they are included
3223   // but this causes inputting text, since TextEditor won't handle eKeyPress
3224   // events whose ctrlKey, altKey or metaKey is true as text input.
3225   // Note that this hack should be used only when an editor has focus because
3226   // this is a hack for TextEditor and modifier key information may be
3227   // important for current web app.
3228   if (IsEditableContent() && insertString && aKeyboardEvent.mMessage == eKeyPress &&
3229       aKeyboardEvent.mCharCode) {
3230     aKeyboardEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
3231   }
3234 void IMEInputHandler::NotifyIMEOfFocusChangeInGecko() {
3235   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3237   MOZ_LOG(gIMELog, LogLevel::Info,
3238           ("%p IMEInputHandler::NotifyIMEOfFocusChangeInGecko, "
3239            "Destroyed()=%s, IsFocused()=%s, inputContext=%p",
3240            this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
3241            mView ? [mView inputContext] : nullptr));
3243   if (Destroyed()) {
3244     return;
3245   }
3247   if (!IsFocused()) {
3248     // retry at next focus event
3249     mPendingMethods |= kNotifyIMEOfFocusChangeInGecko;
3250     return;
3251   }
3253   MOZ_ASSERT(mView);
3254   NSTextInputContext* inputContext = [mView inputContext];
3255   NS_ENSURE_TRUE_VOID(inputContext);
3257   // When an <input> element on a XUL <panel> element gets focus from an <input>
3258   // element on the opener window of the <panel> element, the owner window
3259   // still has native focus.  Therefore, IMEs may store the opener window's
3260   // level at this time because they don't know the actual focus is moved to
3261   // different window.  If IMEs try to get the newest window level after the
3262   // focus change, we return the window level of the XUL <panel>'s widget.
3263   // Therefore, let's emulate the native focus change.  Then, IMEs can refresh
3264   // the stored window level.
3265   [inputContext deactivate];
3266   [inputContext activate];
3268   NS_OBJC_END_TRY_IGNORE_BLOCK;
3271 void IMEInputHandler::SyncASCIICapableOnly() {
3272   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3274   MOZ_LOG(gIMELog, LogLevel::Info,
3275           ("%p IMEInputHandler::SyncASCIICapableOnly, "
3276            "Destroyed()=%s, IsFocused()=%s, mIsASCIICapableOnly=%s, "
3277            "GetCurrentTSMDocumentID()=%p",
3278            this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
3279            TrueOrFalse(mIsASCIICapableOnly), GetCurrentTSMDocumentID()));
3281   if (Destroyed()) {
3282     return;
3283   }
3285   if (!IsFocused()) {
3286     // retry at next focus event
3287     mPendingMethods |= kSyncASCIICapableOnly;
3288     return;
3289   }
3291   TSMDocumentID doc = GetCurrentTSMDocumentID();
3292   if (!doc) {
3293     // retry
3294     mPendingMethods |= kSyncASCIICapableOnly;
3295     NS_WARNING("Application is active but there is no active document");
3296     ResetTimer();
3297     return;
3298   }
3300   if (mIsASCIICapableOnly) {
3301     CFArrayRef ASCIICapableTISList = ::TISCreateASCIICapableInputSourceList();
3302     ::TSMSetDocumentProperty(doc, kTSMDocumentEnabledInputSourcesPropertyTag, sizeof(CFArrayRef),
3303                              &ASCIICapableTISList);
3304     ::CFRelease(ASCIICapableTISList);
3305   } else {
3306     ::TSMRemoveDocumentProperty(doc, kTSMDocumentEnabledInputSourcesPropertyTag);
3307   }
3309   NS_OBJC_END_TRY_IGNORE_BLOCK;
3312 void IMEInputHandler::ResetTimer() {
3313   NS_ASSERTION(mPendingMethods != 0, "There are not pending methods, why this is called?");
3314   if (mTimer) {
3315     mTimer->Cancel();
3316   } else {
3317     mTimer = NS_NewTimer();
3318     NS_ENSURE_TRUE(mTimer, );
3319   }
3320   mTimer->InitWithNamedFuncCallback(FlushPendingMethods, this, 0, nsITimer::TYPE_ONE_SHOT,
3321                                     "IMEInputHandler::FlushPendingMethods");
3324 void IMEInputHandler::ExecutePendingMethods() {
3325   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3327   if (mTimer) {
3328     mTimer->Cancel();
3329     mTimer = nullptr;
3330   }
3332   if (![[NSApplication sharedApplication] isActive]) {
3333     // If we're not active, we should retry at focus event
3334     return;
3335   }
3337   uint32_t pendingMethods = mPendingMethods;
3338   // First, reset the pending method flags because if each methods cannot
3339   // run now, they can reentry to the pending flags by theirselves.
3340   mPendingMethods = 0;
3342   if (pendingMethods & kSyncASCIICapableOnly) SyncASCIICapableOnly();
3343   if (pendingMethods & kNotifyIMEOfFocusChangeInGecko) {
3344     NotifyIMEOfFocusChangeInGecko();
3345   }
3347   NS_OBJC_END_TRY_IGNORE_BLOCK;
3350 #pragma mark -
3352 /******************************************************************************
3354  * IMEInputHandler implementation (native event handlers)
3356  ******************************************************************************/
3358 TextRangeType IMEInputHandler::ConvertToTextRangeType(uint32_t aUnderlineStyle,
3359                                                       NSRange& aSelectedRange) {
3360   MOZ_LOG(gIMELog, LogLevel::Info,
3361           ("%p IMEInputHandler::ConvertToTextRangeType, "
3362            "aUnderlineStyle=%u, aSelectedRange.length=%lu,",
3363            this, aUnderlineStyle, static_cast<unsigned long>(aSelectedRange.length)));
3365   // We assume that aUnderlineStyle is NSUnderlineStyleSingle or
3366   // NSUnderlineStyleThick.  NSUnderlineStyleThick should indicate a selected
3367   // clause.  Otherwise, should indicate non-selected clause.
3369   if (aSelectedRange.length == 0) {
3370     switch (aUnderlineStyle) {
3371       case NSUnderlineStyleSingle:
3372         return TextRangeType::eRawClause;
3373       case NSUnderlineStyleThick:
3374         return TextRangeType::eSelectedRawClause;
3375       default:
3376         NS_WARNING("Unexpected line style");
3377         return TextRangeType::eSelectedRawClause;
3378     }
3379   }
3381   switch (aUnderlineStyle) {
3382     case NSUnderlineStyleSingle:
3383       return TextRangeType::eConvertedClause;
3384     case NSUnderlineStyleThick:
3385       return TextRangeType::eSelectedClause;
3386     default:
3387       NS_WARNING("Unexpected line style");
3388       return TextRangeType::eSelectedClause;
3389   }
3392 uint32_t IMEInputHandler::GetRangeCount(NSAttributedString* aAttrString) {
3393   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3395   // Iterate through aAttrString for the NSUnderlineStyleAttributeName and
3396   // count the different segments adjusting limitRange as we go.
3397   uint32_t count = 0;
3398   NSRange effectiveRange;
3399   NSRange limitRange = NSMakeRange(0, [aAttrString length]);
3400   while (limitRange.length > 0) {
3401     [aAttrString attribute:NSUnderlineStyleAttributeName
3402                       atIndex:limitRange.location
3403         longestEffectiveRange:&effectiveRange
3404                       inRange:limitRange];
3405     limitRange = NSMakeRange(NSMaxRange(effectiveRange),
3406                              NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
3407     count++;
3408   }
3410   MOZ_LOG(gIMELog, LogLevel::Info,
3411           ("%p IMEInputHandler::GetRangeCount, aAttrString=\"%s\", count=%u", this,
3412            GetCharacters([aAttrString string]), count));
3414   return count;
3416   NS_OBJC_END_TRY_BLOCK_RETURN(0);
3419 already_AddRefed<mozilla::TextRangeArray> IMEInputHandler::CreateTextRangeArray(
3420     NSAttributedString* aAttrString, NSRange& aSelectedRange) {
3421   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3423   RefPtr<mozilla::TextRangeArray> textRangeArray = new mozilla::TextRangeArray();
3425   // Note that we shouldn't append ranges when composition string
3426   // is empty because it may cause TextComposition confused.
3427   if (![aAttrString length]) {
3428     return textRangeArray.forget();
3429   }
3431   // Convert the Cocoa range into the TextRange Array used in Gecko.
3432   // Iterate through the attributed string and map the underline attribute to
3433   // Gecko IME textrange attributes.  We may need to change the code here if
3434   // we change the implementation of validAttributesForMarkedText.
3435   NSRange limitRange = NSMakeRange(0, [aAttrString length]);
3436   uint32_t rangeCount = GetRangeCount(aAttrString);
3437   for (uint32_t i = 0; i < rangeCount && limitRange.length > 0; i++) {
3438     NSRange effectiveRange;
3439     id attributeValue = [aAttrString attribute:NSUnderlineStyleAttributeName
3440                                        atIndex:limitRange.location
3441                          longestEffectiveRange:&effectiveRange
3442                                        inRange:limitRange];
3444     TextRange range;
3445     range.mStartOffset = effectiveRange.location;
3446     range.mEndOffset = NSMaxRange(effectiveRange);
3447     range.mRangeType = ConvertToTextRangeType([attributeValue intValue], aSelectedRange);
3448     textRangeArray->AppendElement(range);
3450     MOZ_LOG(gIMELog, LogLevel::Info,
3451             ("%p   IMEInputHandler::CreateTextRangeArray, "
3452              "range={ mStartOffset=%u, mEndOffset=%u, mRangeType=%s }",
3453              this, range.mStartOffset, range.mEndOffset, ToChar(range.mRangeType)));
3455     limitRange = NSMakeRange(NSMaxRange(effectiveRange),
3456                              NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
3457   }
3459   // Get current caret position.
3460   TextRange range;
3461   range.mStartOffset = aSelectedRange.location + aSelectedRange.length;
3462   range.mEndOffset = range.mStartOffset;
3463   range.mRangeType = TextRangeType::eCaret;
3464   textRangeArray->AppendElement(range);
3466   MOZ_LOG(gIMELog, LogLevel::Info,
3467           ("%p IMEInputHandler::CreateTextRangeArray, "
3468            "range={ mStartOffset=%u, mEndOffset=%u, mRangeType=%s }",
3469            this, range.mStartOffset, range.mEndOffset, ToChar(range.mRangeType)));
3471   return textRangeArray.forget();
3473   NS_OBJC_END_TRY_BLOCK_RETURN(nullptr);
3476 bool IMEInputHandler::DispatchCompositionStartEvent() {
3477   MOZ_LOG(gIMELog, LogLevel::Info,
3478           ("%p IMEInputHandler::DispatchCompositionStartEvent, "
3479            "mSelectedRange={ location=%lu, length=%lu }, Destroyed()=%s, "
3480            "mView=%p, mWidget=%p, inputContext=%p, mIsIMEComposing=%s",
3481            this, static_cast<unsigned long>(SelectedRange().location),
3482            static_cast<unsigned long>(mSelectedRange.length), TrueOrFalse(Destroyed()), mView,
3483            mWidget, mView ? [mView inputContext] : nullptr, TrueOrFalse(mIsIMEComposing)));
3485   RefPtr<IMEInputHandler> kungFuDeathGrip(this);
3487   nsresult rv = mDispatcher->BeginNativeInputTransaction();
3488   if (NS_WARN_IF(NS_FAILED(rv))) {
3489     MOZ_LOG(gIMELog, LogLevel::Error,
3490             ("%p   IMEInputHandler::DispatchCompositionStartEvent, "
3491              "FAILED, due to BeginNativeInputTransaction() failure",
3492              this));
3493     return false;
3494   }
3496   NS_ASSERTION(!mIsIMEComposing, "There is a composition already");
3497   mIsIMEComposing = true;
3498   KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
3499   mIsDeadKeyComposing =
3500       currentKeyEvent && currentKeyEvent->mKeyEvent &&
3501       TISInputSourceWrapper::CurrentInputSource().IsDeadKey(currentKeyEvent->mKeyEvent);
3503   nsEventStatus status;
3504   rv = mDispatcher->StartComposition(status);
3505   if (NS_WARN_IF(NS_FAILED(rv))) {
3506     MOZ_LOG(gIMELog, LogLevel::Error,
3507             ("%p   IMEInputHandler::DispatchCompositionStartEvent, "
3508              "FAILED, due to StartComposition() failure",
3509              this));
3510     return false;
3511   }
3513   if (Destroyed()) {
3514     MOZ_LOG(gIMELog, LogLevel::Info,
3515             ("%p   IMEInputHandler::DispatchCompositionStartEvent, "
3516              "destroyed by compositionstart event",
3517              this));
3518     return false;
3519   }
3521   // FYI: compositionstart may cause committing composition by the webapp.
3522   if (!mIsIMEComposing) {
3523     return false;
3524   }
3526   // FYI: The selection range might have been modified by a compositionstart
3527   //      event handler.
3528   mIMECompositionStart = SelectedRange().location;
3529   return true;
3532 bool IMEInputHandler::DispatchCompositionChangeEvent(const nsString& aText,
3533                                                      NSAttributedString* aAttrString,
3534                                                      NSRange& aSelectedRange) {
3535   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3537   MOZ_LOG(gIMELog, LogLevel::Info,
3538           ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
3539            "aText=\"%s\", aAttrString=\"%s\", "
3540            "aSelectedRange={ location=%lu, length=%lu }, Destroyed()=%s, mView=%p, "
3541            "mWidget=%p, inputContext=%p, mIsIMEComposing=%s",
3542            this, NS_ConvertUTF16toUTF8(aText).get(), GetCharacters([aAttrString string]),
3543            static_cast<unsigned long>(aSelectedRange.location),
3544            static_cast<unsigned long>(aSelectedRange.length), TrueOrFalse(Destroyed()), mView,
3545            mWidget, mView ? [mView inputContext] : nullptr, TrueOrFalse(mIsIMEComposing)));
3547   NS_ENSURE_TRUE(!Destroyed(), false);
3549   NS_ASSERTION(mIsIMEComposing, "We're not in composition");
3551   RefPtr<IMEInputHandler> kungFuDeathGrip(this);
3553   nsresult rv = mDispatcher->BeginNativeInputTransaction();
3554   if (NS_WARN_IF(NS_FAILED(rv))) {
3555     MOZ_LOG(gIMELog, LogLevel::Error,
3556             ("%p   IMEInputHandler::DispatchCompositionChangeEvent, "
3557              "FAILED, due to BeginNativeInputTransaction() failure",
3558              this));
3559     return false;
3560   }
3562   RefPtr<TextRangeArray> rangeArray = CreateTextRangeArray(aAttrString, aSelectedRange);
3564   rv = mDispatcher->SetPendingComposition(aText, rangeArray);
3565   if (NS_WARN_IF(NS_FAILED(rv))) {
3566     MOZ_LOG(gIMELog, LogLevel::Error,
3567             ("%p   IMEInputHandler::DispatchCompositionChangeEvent, "
3568              "FAILED, due to SetPendingComposition() failure",
3569              this));
3570     return false;
3571   }
3573   mSelectedRange.location = mIMECompositionStart + aSelectedRange.location;
3574   mSelectedRange.length = aSelectedRange.length;
3576   if (mIMECompositionString) {
3577     [mIMECompositionString release];
3578   }
3579   mIMECompositionString = [[aAttrString string] retain];
3581   nsEventStatus status;
3582   rv = mDispatcher->FlushPendingComposition(status);
3583   if (NS_WARN_IF(NS_FAILED(rv))) {
3584     MOZ_LOG(gIMELog, LogLevel::Error,
3585             ("%p   IMEInputHandler::DispatchCompositionChangeEvent, "
3586              "FAILED, due to FlushPendingComposition() failure",
3587              this));
3588     return false;
3589   }
3591   if (Destroyed()) {
3592     MOZ_LOG(gIMELog, LogLevel::Info,
3593             ("%p   IMEInputHandler::DispatchCompositionChangeEvent, "
3594              "destroyed by compositionchange event",
3595              this));
3596     return false;
3597   }
3599   // FYI: compositionstart may cause committing composition by the webapp.
3600   return mIsIMEComposing;
3602   NS_OBJC_END_TRY_BLOCK_RETURN(false);
3605 bool IMEInputHandler::DispatchCompositionCommitEvent(const nsAString* aCommitString) {
3606   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3608   MOZ_LOG(gIMELog, LogLevel::Info,
3609           ("%p IMEInputHandler::DispatchCompositionCommitEvent, "
3610            "aCommitString=0x%p (\"%s\"), Destroyed()=%s, mView=%p, mWidget=%p, "
3611            "inputContext=%p, mIsIMEComposing=%s",
3612            this, aCommitString, aCommitString ? NS_ConvertUTF16toUTF8(*aCommitString).get() : "",
3613            TrueOrFalse(Destroyed()), mView, mWidget, mView ? [mView inputContext] : nullptr,
3614            TrueOrFalse(mIsIMEComposing)));
3616   NS_ASSERTION(mIsIMEComposing, "We're not in composition");
3618   RefPtr<IMEInputHandler> kungFuDeathGrip(this);
3620   if (!Destroyed()) {
3621     // IME may query selection immediately after this, however, in e10s mode,
3622     // OnSelectionChange() will be called asynchronously.  Until then, we
3623     // should emulate expected selection range if the webapp does nothing.
3624     mSelectedRange.location = mIMECompositionStart;
3625     if (aCommitString) {
3626       mSelectedRange.location += aCommitString->Length();
3627     } else if (mIMECompositionString) {
3628       nsAutoString commitString;
3629       nsCocoaUtils::GetStringForNSString(mIMECompositionString, commitString);
3630       mSelectedRange.location += commitString.Length();
3631     }
3632     mSelectedRange.length = 0;
3634     nsresult rv = mDispatcher->BeginNativeInputTransaction();
3635     if (NS_WARN_IF(NS_FAILED(rv))) {
3636       MOZ_LOG(gIMELog, LogLevel::Error,
3637               ("%p   IMEInputHandler::DispatchCompositionCommitEvent, "
3638                "FAILED, due to BeginNativeInputTransaction() failure",
3639                this));
3640     } else {
3641       nsEventStatus status;
3642       rv = mDispatcher->CommitComposition(status, aCommitString);
3643       if (NS_WARN_IF(NS_FAILED(rv))) {
3644         MOZ_LOG(gIMELog, LogLevel::Error,
3645                 ("%p   IMEInputHandler::DispatchCompositionCommitEvent, "
3646                  "FAILED, due to BeginNativeInputTransaction() failure",
3647                  this));
3648       }
3649     }
3650   }
3652   mIsIMEComposing = mIsDeadKeyComposing = false;
3653   mIMECompositionStart = UINT32_MAX;
3654   if (mIMECompositionString) {
3655     [mIMECompositionString release];
3656     mIMECompositionString = nullptr;
3657   }
3659   if (Destroyed()) {
3660     MOZ_LOG(gIMELog, LogLevel::Info,
3661             ("%p   IMEInputHandler::DispatchCompositionCommitEvent, "
3662              "destroyed by compositioncommit event",
3663              this));
3664     return false;
3665   }
3667   return true;
3669   NS_OBJC_END_TRY_BLOCK_RETURN(false);
3672 bool IMEInputHandler::MaybeDispatchCurrentKeydownEvent(bool aIsProcessedByIME) {
3673   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3675   if (Destroyed()) {
3676     return false;
3677   }
3678   MOZ_ASSERT(mWidget);
3680   KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
3681   if (!currentKeyEvent || !currentKeyEvent->CanDispatchKeyDownEvent()) {
3682     return true;
3683   }
3685   NSEvent* nativeEvent = currentKeyEvent->mKeyEvent;
3686   if (NS_WARN_IF(!nativeEvent) || [nativeEvent type] != NSEventTypeKeyDown) {
3687     return true;
3688   }
3690   MOZ_LOG(gIMELog, LogLevel::Info,
3691           ("%p IMEInputHandler::MaybeDispatchKeydownEvent, aIsProcessedByIME=%s "
3692            "currentKeyEvent={ mKeyEvent(%p)={ type=%s, keyCode=%s (0x%X) } }, "
3693            "aIsProcessedBy=%s, IsDeadKeyComposing()=%s",
3694            this, TrueOrFalse(aIsProcessedByIME), nativeEvent, GetNativeKeyEventType(nativeEvent),
3695            GetKeyNameForNativeKeyCode([nativeEvent keyCode]), [nativeEvent keyCode],
3696            TrueOrFalse(IsIMEComposing()), TrueOrFalse(IsDeadKeyComposing())));
3698   RefPtr<IMEInputHandler> kungFuDeathGrip(this);
3699   RefPtr<TextEventDispatcher> dispatcher(mDispatcher);
3700   nsresult rv = dispatcher->BeginNativeInputTransaction();
3701   if (NS_WARN_IF(NS_FAILED(rv))) {
3702     MOZ_LOG(gIMELog, LogLevel::Error,
3703             ("%p   IMEInputHandler::DispatchKeyEventForFlagsChanged, "
3704              "FAILED, due to BeginNativeInputTransaction() failure",
3705              this));
3706     return false;
3707   }
3709   NSResponder* firstResponder = [[mView window] firstResponder];
3711   // Mark currentKeyEvent as "dispatched eKeyDown event" and actually do it.
3712   currentKeyEvent->mKeyDownDispatched = true;
3714   RefPtr<nsChildView> widget(mWidget);
3716   WidgetKeyboardEvent keydownEvent(true, eKeyDown, widget);
3717   // Don't mark the eKeyDown event as "processed by IME" if the composition
3718   // is started with dead key.
3719   currentKeyEvent->InitKeyEvent(this, keydownEvent, aIsProcessedByIME && !IsDeadKeyComposing());
3721   nsEventStatus status = nsEventStatus_eIgnore;
3722   dispatcher->DispatchKeyboardEvent(eKeyDown, keydownEvent, status, currentKeyEvent);
3723   currentKeyEvent->mKeyDownHandled = (status == nsEventStatus_eConsumeNoDefault);
3725   if (Destroyed()) {
3726     MOZ_LOG(gIMELog, LogLevel::Info,
3727             ("%p   IMEInputHandler::MaybeDispatchKeydownEvent, "
3728              "widget was destroyed by keydown event",
3729              this));
3730     return false;
3731   }
3733   // The key down event may have shifted the focus, in which case, we should
3734   // not continue to handle current key sequence and let's commit current
3735   // composition.
3736   if (firstResponder != [[mView window] firstResponder]) {
3737     MOZ_LOG(gIMELog, LogLevel::Info,
3738             ("%p   IMEInputHandler::MaybeDispatchKeydownEvent, "
3739              "view lost focus by keydown event",
3740              this));
3741     CommitIMEComposition();
3742     return false;
3743   }
3745   return true;
3747   NS_OBJC_END_TRY_BLOCK_RETURN(false);
3750 void IMEInputHandler::InsertTextAsCommittingComposition(NSAttributedString* aAttrString,
3751                                                         NSRange* aReplacementRange) {
3752   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3754   MOZ_LOG(gIMELog, LogLevel::Info,
3755           ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
3756            "aAttrString=\"%s\", aReplacementRange=%p { location=%lu, length=%lu }, "
3757            "Destroyed()=%s, IsIMEComposing()=%s, "
3758            "mMarkedRange={ location=%lu, length=%lu }",
3759            this, GetCharacters([aAttrString string]), aReplacementRange,
3760            static_cast<unsigned long>(aReplacementRange ? aReplacementRange->location : 0),
3761            static_cast<unsigned long>(aReplacementRange ? aReplacementRange->length : 0),
3762            TrueOrFalse(Destroyed()), TrueOrFalse(IsIMEComposing()),
3763            static_cast<unsigned long>(mMarkedRange.location),
3764            static_cast<unsigned long>(mMarkedRange.length)));
3766   if (IgnoreIMECommit()) {
3767     MOZ_CRASH("IMEInputHandler::InsertTextAsCommittingComposition() must not"
3768               "be called while canceling the composition");
3769   }
3771   if (Destroyed()) {
3772     return;
3773   }
3775   // When current keydown event causes this text input, let's dispatch
3776   // eKeyDown event before any other events.  Note that if we're in a
3777   // composition, we've already dispatched eKeyDown event from
3778   // TextInputHandler::HandleKeyDownEvent().
3779   // XXX Should we mark the eKeyDown event as "processed by IME"?
3780   //     However, if the key causes two or more Unicode characters as
3781   //     UTF-16 string, this is used.  So, perhaps, we need to improve
3782   //     HandleKeyDownEvent() before do that.
3783   RefPtr<IMEInputHandler> kungFuDeathGrip(this);
3784   if (!IsIMEComposing() && !MaybeDispatchCurrentKeydownEvent(false)) {
3785     MOZ_LOG(gIMELog, LogLevel::Info,
3786             ("%p   IMEInputHandler::InsertTextAsCommittingComposition, eKeyDown "
3787              "caused focus move or something and canceling the composition",
3788              this));
3789     return;
3790   }
3792   // First, commit current composition with the latest composition string if the
3793   // replacement range is different from marked range.
3794   if (IsIMEComposing() && aReplacementRange && aReplacementRange->location != NSNotFound &&
3795       !NSEqualRanges(MarkedRange(), *aReplacementRange)) {
3796     if (!DispatchCompositionCommitEvent()) {
3797       MOZ_LOG(gIMELog, LogLevel::Info,
3798               ("%p   IMEInputHandler::InsertTextAsCommittingComposition, "
3799                "destroyed by commiting composition for setting replacement range",
3800                this));
3801       return;
3802     }
3803   }
3805   nsString str;
3806   nsCocoaUtils::GetStringForNSString([aAttrString string], str);
3808   if (!IsIMEComposing()) {
3809     MOZ_DIAGNOSTIC_ASSERT(!str.IsEmpty());
3811     // If there is no selection and replacement range is specified, set the
3812     // range as selection.
3813     if (aReplacementRange && aReplacementRange->location != NSNotFound &&
3814         !NSEqualRanges(SelectedRange(), *aReplacementRange)) {
3815       NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
3816     }
3818     if (!StaticPrefs::intl_ime_use_composition_events_for_insert_text()) {
3819       // In the default settings, we should not use composition events for
3820       // inserting text without key press nor IME composition because the
3821       // other browsers do so.   This will cause only a cancelable `beforeinput`
3822       // event whose `inputType` is `insertText`.
3823       WidgetContentCommandEvent insertTextEvent(true, eContentCommandInsertText, mWidget);
3824       insertTextEvent.mString = Some(str);
3825       DispatchEvent(insertTextEvent);
3826       return;
3827     }
3829     // Otherise, emulate an IME composition.  This is our traditional behavior,
3830     // but `beforeinput` events are not cancelable since they should be so for
3831     // native IME limitation.  So, this is now seriously imcompatible with the
3832     // other browsers.
3833     if (!DispatchCompositionStartEvent()) {
3834       MOZ_LOG(gIMELog, LogLevel::Info,
3835               ("%p   IMEInputHandler::InsertTextAsCommittingComposition, "
3836                "cannot continue handling composition after compositionstart",
3837                this));
3838       return;
3839     }
3840   }
3842   if (!DispatchCompositionCommitEvent(&str)) {
3843     MOZ_LOG(gIMELog, LogLevel::Info,
3844             ("%p   IMEInputHandler::InsertTextAsCommittingComposition, "
3845              "destroyed by compositioncommit event",
3846              this));
3847     return;
3848   }
3850   mMarkedRange = NSMakeRange(NSNotFound, 0);
3852   NS_OBJC_END_TRY_IGNORE_BLOCK;
3855 void IMEInputHandler::SetMarkedText(NSAttributedString* aAttrString, NSRange& aSelectedRange,
3856                                     NSRange* aReplacementRange) {
3857   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3859   KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
3861   MOZ_LOG(gIMELog, LogLevel::Info,
3862           ("%p IMEInputHandler::SetMarkedText, "
3863            "aAttrString=\"%s\", aSelectedRange={ location=%lu, length=%lu }, "
3864            "aReplacementRange=%p { location=%lu, length=%lu }, "
3865            "Destroyed()=%s, IsIMEComposing()=%s, "
3866            "mMarkedRange={ location=%lu, length=%lu }, keyevent=%p, "
3867            "keydownDispatched=%s, keydownHandled=%s, "
3868            "keypressDispatched=%s, causedOtherKeyEvents=%s, "
3869            "compositionDispatched=%s",
3870            this, GetCharacters([aAttrString string]),
3871            static_cast<unsigned long>(aSelectedRange.location),
3872            static_cast<unsigned long>(aSelectedRange.length), aReplacementRange,
3873            static_cast<unsigned long>(aReplacementRange ? aReplacementRange->location : 0),
3874            static_cast<unsigned long>(aReplacementRange ? aReplacementRange->length : 0),
3875            TrueOrFalse(Destroyed()), TrueOrFalse(IsIMEComposing()),
3876            static_cast<unsigned long>(mMarkedRange.location),
3877            static_cast<unsigned long>(mMarkedRange.length),
3878            currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr,
3879            currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyDownDispatched) : "N/A",
3880            currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
3881            currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A",
3882            currentKeyEvent ? TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A",
3883            currentKeyEvent ? TrueOrFalse(currentKeyEvent->mCompositionDispatched) : "N/A"));
3885   RefPtr<IMEInputHandler> kungFuDeathGrip(this);
3887   // If SetMarkedText() is called during handling a key press, that means that
3888   // the key event caused this composition.  So, keypress event shouldn't
3889   // be dispatched later, let's mark the key event causing composition event.
3890   if (currentKeyEvent) {
3891     currentKeyEvent->mCompositionDispatched = true;
3893     // When current keydown event causes this text input, let's dispatch
3894     // eKeyDown event before any other events.  Note that if we're in a
3895     // composition, we've already dispatched eKeyDown event from
3896     // TextInputHandler::HandleKeyDownEvent().  On the other hand, if we're
3897     // not in composition, the key event starts new composition.  So, we
3898     // need to mark the eKeyDown event as "processed by IME".
3899     if (!IsIMEComposing() && !MaybeDispatchCurrentKeydownEvent(true)) {
3900       MOZ_LOG(gIMELog, LogLevel::Info,
3901               ("%p   IMEInputHandler::SetMarkedText, eKeyDown caused focus move or "
3902                "something and canceling the composition",
3903                this));
3904       return;
3905     }
3906   }
3908   if (Destroyed()) {
3909     return;
3910   }
3912   // First, commit current composition with the latest composition string if the
3913   // replacement range is different from marked range.
3914   if (IsIMEComposing() && aReplacementRange && aReplacementRange->location != NSNotFound &&
3915       !NSEqualRanges(MarkedRange(), *aReplacementRange)) {
3916     AutoRestore<bool> ignoreIMECommit(mIgnoreIMECommit);
3917     mIgnoreIMECommit = false;
3918     if (!DispatchCompositionCommitEvent()) {
3919       MOZ_LOG(gIMELog, LogLevel::Info,
3920               ("%p   IMEInputHandler::SetMarkedText, "
3921                "destroyed by commiting composition for setting replacement range",
3922                this));
3923       return;
3924     }
3925   }
3927   nsString str;
3928   nsCocoaUtils::GetStringForNSString([aAttrString string], str);
3930   mMarkedRange.length = str.Length();
3932   if (!IsIMEComposing() && !str.IsEmpty()) {
3933     // If there is no selection and replacement range is specified, set the
3934     // range as selection.
3935     if (aReplacementRange && aReplacementRange->location != NSNotFound &&
3936         !NSEqualRanges(SelectedRange(), *aReplacementRange)) {
3937       // Set temporary selection range since OnSelectionChange is async.
3938       mSelectedRange = *aReplacementRange;
3939       if (NS_WARN_IF(!SetSelection(*aReplacementRange))) {
3940         mSelectedRange.location = NSNotFound;  // Marking dirty
3941         return;
3942       }
3943     }
3945     mMarkedRange.location = SelectedRange().location;
3947     if (!DispatchCompositionStartEvent()) {
3948       MOZ_LOG(gIMELog, LogLevel::Info,
3949               ("%p   IMEInputHandler::SetMarkedText, cannot continue handling "
3950                "composition after dispatching compositionstart",
3951                this));
3952       return;
3953     }
3954   }
3956   if (!str.IsEmpty()) {
3957     if (!DispatchCompositionChangeEvent(str, aAttrString, aSelectedRange)) {
3958       MOZ_LOG(gIMELog, LogLevel::Info,
3959               ("%p   IMEInputHandler::SetMarkedText, cannot continue handling "
3960                "composition after dispatching compositionchange",
3961                this));
3962     }
3963     return;
3964   }
3966   // If the composition string becomes empty string, we should commit
3967   // current composition.
3968   if (!DispatchCompositionCommitEvent(&EmptyString())) {
3969     MOZ_LOG(gIMELog, LogLevel::Info,
3970             ("%p   IMEInputHandler::SetMarkedText, "
3971              "destroyed by compositioncommit event",
3972              this));
3973   }
3975   NS_OBJC_END_TRY_IGNORE_BLOCK;
3978 NSAttributedString* IMEInputHandler::GetAttributedSubstringFromRange(NSRange& aRange,
3979                                                                      NSRange* aActualRange) {
3980   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3982   MOZ_LOG(gIMELog, LogLevel::Info,
3983           ("%p   IMEInputHandler::GetAttributedSubstringFromRange, "
3984            "aRange={ location=%lu, length=%lu }, aActualRange=%p, Destroyed()=%s",
3985            this, static_cast<unsigned long>(aRange.location),
3986            static_cast<unsigned long>(aRange.length), aActualRange, TrueOrFalse(Destroyed())));
3988   if (aActualRange) {
3989     *aActualRange = NSMakeRange(NSNotFound, 0);
3990   }
3992   if (Destroyed() || aRange.location == NSNotFound || aRange.length == 0) {
3993     return nil;
3994   }
3996   RefPtr<IMEInputHandler> kungFuDeathGrip(this);
3998   // If we're in composing, the queried range may be in the composition string.
3999   // In such case, we should use mIMECompositionString since if the composition
4000   // string is handled by a remote process, the content cache may be out of
4001   // date.
4002   // XXX Should we set composition string attributes?  Although, Blink claims
4003   //     that some attributes of marked text are supported, but they return
4004   //     just marked string without any style.  So, let's keep current behavior
4005   //     at least for now.
4006   NSUInteger compositionLength = mIMECompositionString ? [mIMECompositionString length] : 0;
4007   if (mIMECompositionStart != UINT32_MAX && aRange.location >= mIMECompositionStart &&
4008       aRange.location + aRange.length <= mIMECompositionStart + compositionLength) {
4009     NSRange range = NSMakeRange(aRange.location - mIMECompositionStart, aRange.length);
4010     NSString* nsstr = [mIMECompositionString substringWithRange:range];
4011     NSMutableAttributedString* result =
4012         [[[NSMutableAttributedString alloc] initWithString:nsstr attributes:nil] autorelease];
4013     // XXX We cannot return font information in this case.  However, this
4014     //     case must occur only when IME tries to confirm if composing string
4015     //     is handled as expected.
4016     if (aActualRange) {
4017       *aActualRange = aRange;
4018     }
4020     if (MOZ_LOG_TEST(gIMELog, LogLevel::Info)) {
4021       nsAutoString str;
4022       nsCocoaUtils::GetStringForNSString(nsstr, str);
4023       MOZ_LOG(gIMELog, LogLevel::Info,
4024               ("%p   IMEInputHandler::GetAttributedSubstringFromRange, "
4025                "computed with mIMECompositionString (result string=\"%s\")",
4026                this, NS_ConvertUTF16toUTF8(str).get()));
4027     }
4028     return result;
4029   }
4031   nsAutoString str;
4032   WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent, mWidget);
4033   WidgetQueryContentEvent::Options options;
4034   int64_t startOffset = aRange.location;
4035   if (IsIMEComposing()) {
4036     // The composition may be at different offset from the selection start
4037     // offset at dispatching compositionstart because start of composition
4038     // is fixed when composition string becomes non-empty in the editor.
4039     // Therefore, we need to use query event which is relative to insertion
4040     // point.
4041     options.mRelativeToInsertionPoint = true;
4042     startOffset -= mIMECompositionStart;
4043   }
4044   queryTextContentEvent.InitForQueryTextContent(startOffset, aRange.length, options);
4045   queryTextContentEvent.RequestFontRanges();
4046   DispatchEvent(queryTextContentEvent);
4048   MOZ_LOG(gIMELog, LogLevel::Info,
4049           ("%p   IMEInputHandler::GetAttributedSubstringFromRange, "
4050            "queryTextContentEvent={ mReply=%s }",
4051            this, ToString(queryTextContentEvent.mReply).c_str()));
4053   if (queryTextContentEvent.Failed()) {
4054     return nil;
4055   }
4057   // We don't set vertical information at this point.  If required,
4058   // OS will calls drawsVerticallyForCharacterAtIndex.
4059   NSMutableAttributedString* result = nsCocoaUtils::GetNSMutableAttributedString(
4060       queryTextContentEvent.mReply->DataRef(), queryTextContentEvent.mReply->mFontRanges, false,
4061       mWidget->BackingScaleFactor());
4062   if (aActualRange) {
4063     *aActualRange = MakeNSRangeFrom(queryTextContentEvent.mReply->mOffsetAndData);
4064   }
4065   return result;
4067   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
4070 bool IMEInputHandler::HasMarkedText() {
4071   MOZ_LOG(gIMELog, LogLevel::Info,
4072           ("%p   IMEInputHandler::HasMarkedText, "
4073            "mMarkedRange={ location=%lu, length=%lu }",
4074            this, static_cast<unsigned long>(mMarkedRange.location),
4075            static_cast<unsigned long>(mMarkedRange.length)));
4077   return (mMarkedRange.location != NSNotFound) && (mMarkedRange.length != 0);
4080 NSRange IMEInputHandler::MarkedRange() {
4081   MOZ_LOG(gIMELog, LogLevel::Info,
4082           ("%p   IMEInputHandler::MarkedRange, "
4083            "mMarkedRange={ location=%lu, length=%lu }",
4084            this, static_cast<unsigned long>(mMarkedRange.location),
4085            static_cast<unsigned long>(mMarkedRange.length)));
4087   if (!HasMarkedText()) {
4088     return NSMakeRange(NSNotFound, 0);
4089   }
4090   return mMarkedRange;
4093 NSRange IMEInputHandler::SelectedRange() {
4094   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4096   MOZ_LOG(gIMELog, LogLevel::Info,
4097           ("%p   IMEInputHandler::SelectedRange, Destroyed()=%s, mSelectedRange={ "
4098            "location=%lu, length=%lu }",
4099            this, TrueOrFalse(Destroyed()), static_cast<unsigned long>(mSelectedRange.location),
4100            static_cast<unsigned long>(mSelectedRange.length)));
4102   if (Destroyed()) {
4103     return mSelectedRange;
4104   }
4106   if (mSelectedRange.location != NSNotFound) {
4107     MOZ_ASSERT(mIMEHasFocus);
4108     return mSelectedRange;
4109   }
4111   RefPtr<IMEInputHandler> kungFuDeathGrip(this);
4113   WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText, mWidget);
4114   DispatchEvent(querySelectedTextEvent);
4116   MOZ_LOG(gIMELog, LogLevel::Info,
4117           ("%p   IMEInputHandler::SelectedRange, querySelectedTextEvent={ mReply=%s }", this,
4118            ToString(querySelectedTextEvent.mReply).c_str()));
4120   if (querySelectedTextEvent.Failed()) {
4121     return mSelectedRange;
4122   }
4124   mWritingMode = querySelectedTextEvent.mReply->WritingModeRef();
4125   mRangeForWritingMode = MakeNSRangeFrom(querySelectedTextEvent.mReply->mOffsetAndData);
4127   if (mIMEHasFocus) {
4128     mSelectedRange = mRangeForWritingMode;
4129   }
4131   return mRangeForWritingMode;
4133   NS_OBJC_END_TRY_BLOCK_RETURN(mSelectedRange);
4136 bool IMEInputHandler::DrawsVerticallyForCharacterAtIndex(uint32_t aCharIndex) {
4137   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4139   if (Destroyed()) {
4140     return false;
4141   }
4143   if (mRangeForWritingMode.location == NSNotFound) {
4144     // Update cached writing-mode value for the current selection.
4145     SelectedRange();
4146   }
4148   if (aCharIndex < mRangeForWritingMode.location ||
4149       aCharIndex > mRangeForWritingMode.location + mRangeForWritingMode.length) {
4150     // It's not clear to me whether this ever happens in practice, but if an
4151     // IME ever wants to query writing mode at an offset outside the current
4152     // selection, the writing-mode value may not be correct for the index.
4153     // In that case, use FirstRectForCharacterRange to get a fresh value.
4154     // This does more work than strictly necessary (we don't need the rect here),
4155     // but should be a rare case.
4156     NS_WARNING("DrawsVerticallyForCharacterAtIndex not using cached writing mode");
4157     NSRange range = NSMakeRange(aCharIndex, 1);
4158     NSRange actualRange;
4159     FirstRectForCharacterRange(range, &actualRange);
4160   }
4162   return mWritingMode.IsVertical();
4164   NS_OBJC_END_TRY_BLOCK_RETURN(false);
4167 NSRect IMEInputHandler::FirstRectForCharacterRange(NSRange& aRange, NSRange* aActualRange) {
4168   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4170   MOZ_LOG(gIMELog, LogLevel::Info,
4171           ("%p IMEInputHandler::FirstRectForCharacterRange, Destroyed()=%s, "
4172            "aRange={ location=%lu, length=%lu }, aActualRange=%p }",
4173            this, TrueOrFalse(Destroyed()), static_cast<unsigned long>(aRange.location),
4174            static_cast<unsigned long>(aRange.length), aActualRange));
4176   // XXX this returns first character rect or caret rect, it is limitation of
4177   // now. We need more work for returns first line rect. But current
4178   // implementation is enough for IMEs.
4180   NSRect rect = NSMakeRect(0.0, 0.0, 0.0, 0.0);
4181   NSRange actualRange = NSMakeRange(NSNotFound, 0);
4182   if (aActualRange) {
4183     *aActualRange = actualRange;
4184   }
4185   if (Destroyed() || aRange.location == NSNotFound) {
4186     return rect;
4187   }
4189   RefPtr<IMEInputHandler> kungFuDeathGrip(this);
4191   LayoutDeviceIntRect r;
4192   bool useCaretRect = (aRange.length == 0);
4193   if (!useCaretRect) {
4194     WidgetQueryContentEvent queryTextRectEvent(true, eQueryTextRect, mWidget);
4195     WidgetQueryContentEvent::Options options;
4196     int64_t startOffset = aRange.location;
4197     if (IsIMEComposing()) {
4198       // The composition may be at different offset from the selection start
4199       // offset at dispatching compositionstart because start of composition
4200       // is fixed when composition string becomes non-empty in the editor.
4201       // Therefore, we need to use query event which is relative to insertion
4202       // point.
4203       options.mRelativeToInsertionPoint = true;
4204       startOffset -= mIMECompositionStart;
4205     }
4206     queryTextRectEvent.InitForQueryTextRect(startOffset, 1, options);
4207     DispatchEvent(queryTextRectEvent);
4208     if (queryTextRectEvent.Succeeded()) {
4209       r = queryTextRectEvent.mReply->mRect;
4210       actualRange = MakeNSRangeFrom(queryTextRectEvent.mReply->mOffsetAndData);
4211       mWritingMode = queryTextRectEvent.mReply->WritingModeRef();
4212       mRangeForWritingMode = actualRange;
4213     } else {
4214       useCaretRect = true;
4215     }
4216   }
4218   if (useCaretRect) {
4219     WidgetQueryContentEvent queryCaretRectEvent(true, eQueryCaretRect, mWidget);
4220     WidgetQueryContentEvent::Options options;
4221     int64_t startOffset = aRange.location;
4222     if (IsIMEComposing()) {
4223       // The composition may be at different offset from the selection start
4224       // offset at dispatching compositionstart because start of composition
4225       // is fixed when composition string becomes non-empty in the editor.
4226       // Therefore, we need to use query event which is relative to insertion
4227       // point.
4228       options.mRelativeToInsertionPoint = true;
4229       startOffset -= mIMECompositionStart;
4230     }
4231     queryCaretRectEvent.InitForQueryCaretRect(startOffset, options);
4232     DispatchEvent(queryCaretRectEvent);
4233     if (queryCaretRectEvent.Failed()) {
4234       return rect;
4235     }
4236     r = queryCaretRectEvent.mReply->mRect;
4237     r.width = 0;
4238     actualRange.location = queryCaretRectEvent.mReply->StartOffset();
4239     actualRange.length = 0;
4240   }
4242   nsIWidget* rootWidget = mWidget->GetTopLevelWidget();
4243   NSWindow* rootWindow = static_cast<NSWindow*>(rootWidget->GetNativeData(NS_NATIVE_WINDOW));
4244   NSView* rootView = static_cast<NSView*>(rootWidget->GetNativeData(NS_NATIVE_WIDGET));
4245   if (!rootWindow || !rootView) {
4246     return rect;
4247   }
4248   rect = nsCocoaUtils::DevPixelsToCocoaPoints(r, mWidget->BackingScaleFactor());
4249   rect = [rootView convertRect:rect toView:nil];
4250   rect.origin = nsCocoaUtils::ConvertPointToScreen(rootWindow, rect.origin);
4252   if (aActualRange) {
4253     *aActualRange = actualRange;
4254   }
4256   MOZ_LOG(gIMELog, LogLevel::Info,
4257           ("%p   IMEInputHandler::FirstRectForCharacterRange, "
4258            "useCaretRect=%s rect={ x=%f, y=%f, width=%f, height=%f }, "
4259            "actualRange={ location=%lu, length=%lu }",
4260            this, TrueOrFalse(useCaretRect), rect.origin.x, rect.origin.y, rect.size.width,
4261            rect.size.height, static_cast<unsigned long>(actualRange.location),
4262            static_cast<unsigned long>(actualRange.length)));
4264   return rect;
4266   NS_OBJC_END_TRY_BLOCK_RETURN(NSMakeRect(0.0, 0.0, 0.0, 0.0));
4269 NSUInteger IMEInputHandler::CharacterIndexForPoint(NSPoint& aPoint) {
4270   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4272   MOZ_LOG(gIMELog, LogLevel::Info,
4273           ("%p IMEInputHandler::CharacterIndexForPoint, aPoint={ x=%f, y=%f }", this, aPoint.x,
4274            aPoint.y));
4276   NSWindow* mainWindow = [NSApp mainWindow];
4277   if (!mWidget || !mainWindow) {
4278     return NSNotFound;
4279   }
4281   WidgetQueryContentEvent queryCharAtPointEvent(true, eQueryCharacterAtPoint, mWidget);
4282   NSPoint ptInWindow = nsCocoaUtils::ConvertPointFromScreen(mainWindow, aPoint);
4283   NSPoint ptInView = [mView convertPoint:ptInWindow fromView:nil];
4284   queryCharAtPointEvent.mRefPoint.x =
4285       static_cast<int32_t>(ptInView.x) * mWidget->BackingScaleFactor();
4286   queryCharAtPointEvent.mRefPoint.y =
4287       static_cast<int32_t>(ptInView.y) * mWidget->BackingScaleFactor();
4288   mWidget->DispatchWindowEvent(queryCharAtPointEvent);
4289   if (queryCharAtPointEvent.Failed() || queryCharAtPointEvent.DidNotFindChar() ||
4290       queryCharAtPointEvent.mReply->StartOffset() >= static_cast<uint32_t>(NSNotFound)) {
4291     return NSNotFound;
4292   }
4294   return queryCharAtPointEvent.mReply->StartOffset();
4296   NS_OBJC_END_TRY_BLOCK_RETURN(NSNotFound);
4299 extern "C" {
4300 extern NSString* NSTextInputReplacementRangeAttributeName;
4303 NSArray* IMEInputHandler::GetValidAttributesForMarkedText() {
4304   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4306   MOZ_LOG(gIMELog, LogLevel::Info, ("%p IMEInputHandler::GetValidAttributesForMarkedText", this));
4308   // Return same attributes as Chromium (see render_widget_host_view_mac.mm)
4309   // because most IMEs must be tested with Safari (OS default) and Chrome
4310   // (having most market share).  Therefore, we need to follow their behavior.
4311   // XXX It might be better to reuse an array instance for this result because
4312   //     this may be called a lot.  Note that Chromium does so.
4313   return [NSArray arrayWithObjects:NSUnderlineStyleAttributeName, NSUnderlineColorAttributeName,
4314                                    NSMarkedClauseSegmentAttributeName,
4315                                    NSTextInputReplacementRangeAttributeName, nil];
4317   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
4320 #pragma mark -
4322 /******************************************************************************
4324  *  IMEInputHandler implementation #2
4326  ******************************************************************************/
4328 IMEInputHandler::IMEInputHandler(nsChildView* aWidget, NSView<mozView>* aNativeView)
4329     : TextInputHandlerBase(aWidget, aNativeView),
4330       mPendingMethods(0),
4331       mIMECompositionString(nullptr),
4332       mIMECompositionStart(UINT32_MAX),
4333       mRangeForWritingMode(),
4334       mIsIMEComposing(false),
4335       mIsDeadKeyComposing(false),
4336       mIsIMEEnabled(true),
4337       mIsASCIICapableOnly(false),
4338       mIgnoreIMECommit(false),
4339       mIMEHasFocus(false) {
4340   InitStaticMembers();
4342   mMarkedRange.location = NSNotFound;
4343   mMarkedRange.length = 0;
4344   mSelectedRange.location = NSNotFound;
4345   mSelectedRange.length = 0;
4348 IMEInputHandler::~IMEInputHandler() {
4349   if (mTimer) {
4350     mTimer->Cancel();
4351     mTimer = nullptr;
4352   }
4353   if (sFocusedIMEHandler == this) {
4354     sFocusedIMEHandler = nullptr;
4355   }
4356   if (mIMECompositionString) {
4357     [mIMECompositionString release];
4358     mIMECompositionString = nullptr;
4359   }
4362 void IMEInputHandler::OnFocusChangeInGecko(bool aFocus) {
4363   MOZ_LOG(gIMELog, LogLevel::Info,
4364           ("%p IMEInputHandler::OnFocusChangeInGecko, aFocus=%s, Destroyed()=%s, "
4365            "sFocusedIMEHandler=%p",
4366            this, TrueOrFalse(aFocus), TrueOrFalse(Destroyed()), sFocusedIMEHandler));
4368   mSelectedRange.location = NSNotFound;  // Marking dirty
4369   mIMEHasFocus = aFocus;
4371   // This is called when the native focus is changed and when the native focus
4372   // isn't changed but the focus is changed in Gecko.
4373   if (!aFocus) {
4374     if (sFocusedIMEHandler == this) sFocusedIMEHandler = nullptr;
4375     return;
4376   }
4378   sFocusedIMEHandler = this;
4380   // We need to notify IME of focus change in Gecko as native focus change
4381   // because the window level of the focused element in Gecko may be changed.
4382   mPendingMethods |= kNotifyIMEOfFocusChangeInGecko;
4383   ResetTimer();
4386 bool IMEInputHandler::OnDestroyWidget(nsChildView* aDestroyingWidget) {
4387   MOZ_LOG(gIMELog, LogLevel::Info,
4388           ("%p IMEInputHandler::OnDestroyWidget, aDestroyingWidget=%p, "
4389            "sFocusedIMEHandler=%p, IsIMEComposing()=%s",
4390            this, aDestroyingWidget, sFocusedIMEHandler, TrueOrFalse(IsIMEComposing())));
4392   // If we're not focused, the focused IMEInputHandler may have been
4393   // created by another widget/nsChildView.
4394   if (sFocusedIMEHandler && sFocusedIMEHandler != this) {
4395     sFocusedIMEHandler->OnDestroyWidget(aDestroyingWidget);
4396   }
4398   if (!TextInputHandlerBase::OnDestroyWidget(aDestroyingWidget)) {
4399     return false;
4400   }
4402   if (IsIMEComposing()) {
4403     // If our view is in the composition, we should clean up it.
4404     CancelIMEComposition();
4405   }
4407   mSelectedRange.location = NSNotFound;  // Marking dirty
4408   mIMEHasFocus = false;
4410   return true;
4413 void IMEInputHandler::SendCommittedText(NSString* aString) {
4414   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
4416   MOZ_LOG(
4417       gIMELog, LogLevel::Info,
4418       ("%p IMEInputHandler::SendCommittedText, mView=%p, mWidget=%p, "
4419        "inputContext=%p, mIsIMEComposing=%s",
4420        this, mView, mWidget, mView ? [mView inputContext] : nullptr, TrueOrFalse(mIsIMEComposing)));
4422   NS_ENSURE_TRUE(mWidget, );
4423   // XXX We should send the string without mView.
4424   if (!mView) {
4425     return;
4426   }
4428   NSAttributedString* attrStr = [[NSAttributedString alloc] initWithString:aString];
4429   if ([mView conformsToProtocol:@protocol(NSTextInputClient)]) {
4430     NSObject<NSTextInputClient>* textInputClient = static_cast<NSObject<NSTextInputClient>*>(mView);
4431     [textInputClient insertText:attrStr replacementRange:NSMakeRange(NSNotFound, 0)];
4432   }
4434   // Last resort.  If we cannot retrieve NSTextInputProtocol from mView
4435   // or blocking to call our InsertText(), we should call InsertText()
4436   // directly to commit composition forcibly.
4437   if (mIsIMEComposing) {
4438     MOZ_LOG(gIMELog, LogLevel::Info,
4439             ("%p   IMEInputHandler::SendCommittedText, trying to insert text directly "
4440              "due to IME not calling our InsertText()",
4441              this));
4442     static_cast<TextInputHandler*>(this)->InsertText(attrStr);
4443     MOZ_ASSERT(!mIsIMEComposing);
4444   }
4446   [attrStr release];
4448   NS_OBJC_END_TRY_IGNORE_BLOCK;
4451 void IMEInputHandler::KillIMEComposition() {
4452   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
4454   MOZ_LOG(gIMELog, LogLevel::Info,
4455           ("%p IMEInputHandler::KillIMEComposition, mView=%p, mWidget=%p, "
4456            "inputContext=%p, mIsIMEComposing=%s, "
4457            "Destroyed()=%s, IsFocused()=%s",
4458            this, mView, mWidget, mView ? [mView inputContext] : nullptr,
4459            TrueOrFalse(mIsIMEComposing), TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused())));
4461   if (Destroyed() || NS_WARN_IF(!mView)) {
4462     return;
4463   }
4465   NSTextInputContext* inputContext = [mView inputContext];
4466   if (NS_WARN_IF(!inputContext)) {
4467     return;
4468   }
4469   [inputContext discardMarkedText];
4471   NS_OBJC_END_TRY_IGNORE_BLOCK;
4474 void IMEInputHandler::CommitIMEComposition() {
4475   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
4477   MOZ_LOG(gIMELog, LogLevel::Info,
4478           ("%p IMEInputHandler::CommitIMEComposition, mIMECompositionString=%s", this,
4479            GetCharacters(mIMECompositionString)));
4481   // If this is called before dispatching eCompositionStart, IsIMEComposing()
4482   // returns false.  Even in such case, we need to commit composition *in*
4483   // IME if this is called by preceding eKeyDown event of eCompositionStart.
4484   // So, we need to call KillIMEComposition() even when IsIMEComposing()
4485   // returns false.
4486   KillIMEComposition();
4488   if (!IsIMEComposing()) return;
4490   // If the composition is still there, KillIMEComposition only kills the
4491   // composition in TSM.  We also need to finish the our composition too.
4492   SendCommittedText(mIMECompositionString);
4494   NS_OBJC_END_TRY_IGNORE_BLOCK;
4497 void IMEInputHandler::CancelIMEComposition() {
4498   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
4500   if (!IsIMEComposing()) return;
4502   MOZ_LOG(gIMELog, LogLevel::Info,
4503           ("%p IMEInputHandler::CancelIMEComposition, mIMECompositionString=%s", this,
4504            GetCharacters(mIMECompositionString)));
4506   // For canceling the current composing, we need to ignore the param of
4507   // insertText.  But this code is ugly...
4508   mIgnoreIMECommit = true;
4509   KillIMEComposition();
4510   mIgnoreIMECommit = false;
4512   if (!IsIMEComposing()) return;
4514   // If the composition is still there, KillIMEComposition only kills the
4515   // composition in TSM.  We also need to kill the our composition too.
4516   SendCommittedText(@"");
4518   NS_OBJC_END_TRY_IGNORE_BLOCK;
4521 bool IMEInputHandler::IsFocused() {
4522   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
4524   NS_ENSURE_TRUE(!Destroyed(), false);
4525   NSWindow* window = [mView window];
4526   NS_ENSURE_TRUE(window, false);
4527   return [window firstResponder] == mView && [window isKeyWindow] &&
4528          [[NSApplication sharedApplication] isActive];
4530   NS_OBJC_END_TRY_BLOCK_RETURN(false);
4533 bool IMEInputHandler::IsIMEOpened() {
4534   TISInputSourceWrapper tis;
4535   tis.InitByCurrentInputSource();
4536   return tis.IsOpenedIMEMode();
4539 void IMEInputHandler::SetASCIICapableOnly(bool aASCIICapableOnly) {
4540   if (aASCIICapableOnly == mIsASCIICapableOnly) return;
4542   CommitIMEComposition();
4543   mIsASCIICapableOnly = aASCIICapableOnly;
4544   SyncASCIICapableOnly();
4547 void IMEInputHandler::EnableIME(bool aEnableIME) {
4548   if (aEnableIME == mIsIMEEnabled) return;
4550   CommitIMEComposition();
4551   mIsIMEEnabled = aEnableIME;
4554 void IMEInputHandler::SetIMEOpenState(bool aOpenIME) {
4555   if (!IsFocused() || IsIMEOpened() == aOpenIME) return;
4557   if (!aOpenIME) {
4558     TISInputSourceWrapper tis;
4559     tis.InitByCurrentASCIICapableInputSource();
4560     tis.Select();
4561     return;
4562   }
4564   // If we know the latest IME opened mode, we should select it.
4565   if (sLatestIMEOpenedModeInputSourceID) {
4566     TISInputSourceWrapper tis;
4567     tis.InitByInputSourceID(sLatestIMEOpenedModeInputSourceID);
4568     tis.Select();
4569     return;
4570   }
4572   // XXX If the current input source is a mode of IME, we should turn on it,
4573   // but we haven't found such way...
4575   // Finally, we should refer the system locale but this is a little expensive,
4576   // we shouldn't retry this (if it was succeeded, we already set
4577   // sLatestIMEOpenedModeInputSourceID at that time).
4578   static bool sIsPrefferredIMESearched = false;
4579   if (sIsPrefferredIMESearched) return;
4580   sIsPrefferredIMESearched = true;
4581   OpenSystemPreferredLanguageIME();
4584 void IMEInputHandler::OpenSystemPreferredLanguageIME() {
4585   MOZ_LOG(gIMELog, LogLevel::Info, ("%p IMEInputHandler::OpenSystemPreferredLanguageIME", this));
4587   CFArrayRef langList = ::CFLocaleCopyPreferredLanguages();
4588   if (!langList) {
4589     MOZ_LOG(gIMELog, LogLevel::Info,
4590             ("%p   IMEInputHandler::OpenSystemPreferredLanguageIME, langList is NULL", this));
4591     return;
4592   }
4593   CFIndex count = ::CFArrayGetCount(langList);
4594   for (CFIndex i = 0; i < count; i++) {
4595     CFLocaleRef locale = ::CFLocaleCreate(
4596         kCFAllocatorDefault, static_cast<CFStringRef>(::CFArrayGetValueAtIndex(langList, i)));
4597     if (!locale) {
4598       continue;
4599     }
4601     bool changed = false;
4602     CFStringRef lang = static_cast<CFStringRef>(::CFLocaleGetValue(locale, kCFLocaleLanguageCode));
4603     NS_ASSERTION(lang, "lang is null");
4604     if (lang) {
4605       TISInputSourceWrapper tis;
4606       tis.InitByLanguage(lang);
4607       if (tis.IsOpenedIMEMode()) {
4608         if (MOZ_LOG_TEST(gIMELog, LogLevel::Info)) {
4609           CFStringRef foundTIS;
4610           tis.GetInputSourceID(foundTIS);
4611           MOZ_LOG(gIMELog, LogLevel::Info,
4612                   ("%p   IMEInputHandler::OpenSystemPreferredLanguageIME, "
4613                    "foundTIS=%s, lang=%s",
4614                    this, GetCharacters(foundTIS), GetCharacters(lang)));
4615         }
4616         tis.Select();
4617         changed = true;
4618       }
4619     }
4620     ::CFRelease(locale);
4621     if (changed) {
4622       break;
4623     }
4624   }
4625   ::CFRelease(langList);
4628 void IMEInputHandler::OnSelectionChange(const IMENotification& aIMENotification) {
4629   MOZ_ASSERT(aIMENotification.mSelectionChangeData.IsInitialized());
4630   MOZ_LOG(gIMELog, LogLevel::Info, ("%p IMEInputHandler::OnSelectionChange", this));
4632   if (!aIMENotification.mSelectionChangeData.HasRange()) {
4633     mSelectedRange.location = NSNotFound;
4634     mSelectedRange.length = 0;
4635     mRangeForWritingMode.location = NSNotFound;
4636     mRangeForWritingMode.length = 0;
4637     return;
4638   }
4640   mWritingMode = aIMENotification.mSelectionChangeData.GetWritingMode();
4641   mRangeForWritingMode = NSMakeRange(aIMENotification.mSelectionChangeData.mOffset,
4642                                      aIMENotification.mSelectionChangeData.Length());
4643   if (mIMEHasFocus) {
4644     mSelectedRange = mRangeForWritingMode;
4645   }
4648 void IMEInputHandler::OnLayoutChange() {
4649   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
4651   if (!IsFocused()) {
4652     return;
4653   }
4654   NSTextInputContext* inputContext = [mView inputContext];
4655   [inputContext invalidateCharacterCoordinates];
4657   NS_OBJC_END_TRY_IGNORE_BLOCK;
4660 bool IMEInputHandler::OnHandleEvent(NSEvent* aEvent) {
4661   if (!IsFocused()) {
4662     return false;
4663   }
4665   bool allowConsumeEvent = true;
4666   if (nsCocoaFeatures::OnCatalinaOrLater() && !IsIMEComposing()) {
4667     // Hack for bug of Korean IMEs on Catalina (10.15).
4668     // If we are inactivated during composition, active Korean IME keeps
4669     // consuming all mousedown events of any mouse buttons.  So, we should
4670     // allow Korean IMEs to handle mousedown events only when there is
4671     // composition string.
4672     // List of ID of Korean IME:
4673     // * com.apple.inputmethod.Korean.2SetKorean
4674     // * com.apple.inputmethod.Korean.3SetKorean
4675     // * com.apple.inputmethod.Korean.390Sebulshik
4676     // * com.apple.inputmethod.Korean.GongjinCheongRomaja
4677     // * com.apple.inputmethod.Korean.HNCRomaja
4678     TISInputSourceWrapper tis;
4679     tis.InitByCurrentInputSource();
4680     nsAutoString inputSourceID;
4681     tis.GetInputSourceID(inputSourceID);
4682     allowConsumeEvent = !StringBeginsWith(inputSourceID, u"com.apple.inputmethod.Korean."_ns);
4683   }
4684   NSTextInputContext* inputContext = [mView inputContext];
4685   return [inputContext handleEvent:aEvent] && allowConsumeEvent;
4688 #pragma mark -
4690 /******************************************************************************
4692  *  TextInputHandlerBase implementation
4694  ******************************************************************************/
4696 int32_t TextInputHandlerBase::sSecureEventInputCount = 0;
4698 NS_IMPL_ISUPPORTS(TextInputHandlerBase, TextEventDispatcherListener, nsISupportsWeakReference)
4700 TextInputHandlerBase::TextInputHandlerBase(nsChildView* aWidget, NSView<mozView>* aNativeView)
4701     : mWidget(aWidget), mDispatcher(aWidget->GetTextEventDispatcher()) {
4702   gHandlerInstanceCount++;
4703   mView = [aNativeView retain];
4706 TextInputHandlerBase::~TextInputHandlerBase() {
4707   [mView release];
4708   if (--gHandlerInstanceCount == 0) {
4709     TISInputSourceWrapper::Shutdown();
4710   }
4713 bool TextInputHandlerBase::OnDestroyWidget(nsChildView* aDestroyingWidget) {
4714   MOZ_LOG_KEY_OR_IME(LogLevel::Info, ("%p TextInputHandlerBase::OnDestroyWidget, "
4715                                       "aDestroyingWidget=%p, mWidget=%p",
4716                                       this, aDestroyingWidget, mWidget));
4718   if (aDestroyingWidget != mWidget) {
4719     return false;
4720   }
4722   mWidget = nullptr;
4723   mDispatcher = nullptr;
4724   return true;
4727 bool TextInputHandlerBase::DispatchEvent(WidgetGUIEvent& aEvent) {
4728   return mWidget->DispatchWindowEvent(aEvent);
4731 void TextInputHandlerBase::InitKeyEvent(NSEvent* aNativeKeyEvent, WidgetKeyboardEvent& aKeyEvent,
4732                                         bool aIsProcessedByIME, const nsAString* aInsertString) {
4733   NS_ASSERTION(aNativeKeyEvent, "aNativeKeyEvent must not be NULL");
4735   if (mKeyboardOverride.mOverrideEnabled) {
4736     TISInputSourceWrapper tis;
4737     tis.InitByLayoutID(mKeyboardOverride.mKeyboardLayout, true);
4738     tis.InitKeyEvent(aNativeKeyEvent, aKeyEvent, aIsProcessedByIME, aInsertString);
4739     return;
4740   }
4741   TISInputSourceWrapper::CurrentInputSource().InitKeyEvent(aNativeKeyEvent, aKeyEvent,
4742                                                            aIsProcessedByIME, aInsertString);
4745 nsresult TextInputHandlerBase::SynthesizeNativeKeyEvent(int32_t aNativeKeyboardLayout,
4746                                                         int32_t aNativeKeyCode,
4747                                                         uint32_t aModifierFlags,
4748                                                         const nsAString& aCharacters,
4749                                                         const nsAString& aUnmodifiedCharacters) {
4750   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4752   uint32_t modifierFlags = nsCocoaUtils::ConvertWidgetModifiersToMacModifierFlags(
4753       static_cast<nsIWidget::Modifiers>(aModifierFlags));
4754   NSInteger windowNumber = [[mView window] windowNumber];
4755   bool sendFlagsChangedEvent = IsModifierKey(aNativeKeyCode);
4756   NSEventType eventType = sendFlagsChangedEvent ? NSEventTypeFlagsChanged : NSEventTypeKeyDown;
4757   NSEvent* downEvent = [NSEvent keyEventWithType:eventType
4758                                         location:NSMakePoint(0, 0)
4759                                    modifierFlags:modifierFlags
4760                                        timestamp:0
4761                                     windowNumber:windowNumber
4762                                          context:nil
4763                                       characters:nsCocoaUtils::ToNSString(aCharacters)
4764                      charactersIgnoringModifiers:nsCocoaUtils::ToNSString(aUnmodifiedCharacters)
4765                                        isARepeat:NO
4766                                          keyCode:aNativeKeyCode];
4768   NSEvent* upEvent = sendFlagsChangedEvent
4769                          ? nil
4770                          : nsCocoaUtils::MakeNewCocoaEventWithType(NSEventTypeKeyUp, downEvent);
4772   if (downEvent && (sendFlagsChangedEvent || upEvent)) {
4773     KeyboardLayoutOverride currentLayout = mKeyboardOverride;
4774     mKeyboardOverride.mKeyboardLayout = aNativeKeyboardLayout;
4775     mKeyboardOverride.mOverrideEnabled = true;
4776     [NSApp sendEvent:downEvent];
4777     if (upEvent) {
4778       [NSApp sendEvent:upEvent];
4779     }
4780     // processKeyDownEvent and keyUp block exceptions so we're sure to
4781     // reach here to restore mKeyboardOverride
4782     mKeyboardOverride = currentLayout;
4783   }
4785   return NS_OK;
4787   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
4790 NSInteger TextInputHandlerBase::GetWindowLevel() {
4791   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4793   MOZ_LOG_KEY_OR_IME(LogLevel::Info, ("%p TextInputHandlerBase::GetWindowLevel, Destryoed()=%s",
4794                                       this, TrueOrFalse(Destroyed())));
4796   if (Destroyed()) {
4797     return NSNormalWindowLevel;
4798   }
4800   // When an <input> element on a XUL <panel> is focused, the actual focused view
4801   // is the panel's parent view (mView). But the editor is displayed on the
4802   // popped-up widget's view (editorView).  We want the latter's window level.
4803   NSView<mozView>* editorView = mWidget->GetEditorView();
4804   NS_ENSURE_TRUE(editorView, NSNormalWindowLevel);
4805   NSInteger windowLevel = [[editorView window] level];
4807   MOZ_LOG_KEY_OR_IME(LogLevel::Info,
4808                      ("%p TextInputHandlerBase::GetWindowLevel, windowLevel=%s (%lX)", this,
4809                       GetWindowLevelName(windowLevel), static_cast<unsigned long>(windowLevel)));
4811   return windowLevel;
4813   NS_OBJC_END_TRY_BLOCK_RETURN(NSNormalWindowLevel);
4816 NS_IMETHODIMP
4817 TextInputHandlerBase::AttachNativeKeyEvent(WidgetKeyboardEvent& aKeyEvent) {
4818   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4820   // Don't try to replace a native event if one already exists.
4821   // OS X doesn't have an OS modifier, can't make a native event.
4822   if (aKeyEvent.mNativeKeyEvent || aKeyEvent.mModifiers & MODIFIER_OS) {
4823     return NS_OK;
4824   }
4826   MOZ_LOG_KEY_OR_IME(LogLevel::Info,
4827                      ("%p TextInputHandlerBase::AttachNativeKeyEvent, key=0x%X, char=0x%X, "
4828                       "mod=0x%X",
4829                       this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode, aKeyEvent.mModifiers));
4831   NSInteger windowNumber = [[mView window] windowNumber];
4832   NSGraphicsContext* context = [NSGraphicsContext currentContext];
4833   aKeyEvent.mNativeKeyEvent =
4834       nsCocoaUtils::MakeNewCococaEventFromWidgetEvent(aKeyEvent, windowNumber, context);
4836   return NS_OK;
4838   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
4841 bool TextInputHandlerBase::SetSelection(NSRange& aRange) {
4842   MOZ_ASSERT(!Destroyed());
4844   RefPtr<TextInputHandlerBase> kungFuDeathGrip(this);
4845   WidgetSelectionEvent selectionEvent(true, eSetSelection, mWidget);
4846   selectionEvent.mOffset = aRange.location;
4847   selectionEvent.mLength = aRange.length;
4848   selectionEvent.mReversed = false;
4849   selectionEvent.mExpandToClusterBoundary = false;
4850   DispatchEvent(selectionEvent);
4851   NS_ENSURE_TRUE(selectionEvent.mSucceeded, false);
4852   return !Destroyed();
4855 /* static */ bool TextInputHandlerBase::IsPrintableChar(char16_t aChar) {
4856   return (aChar >= 0x20 && aChar <= 0x7E) || aChar >= 0xA0;
4859 /* static */ bool TextInputHandlerBase::IsSpecialGeckoKey(UInt32 aNativeKeyCode) {
4860   // this table is used to determine which keys are special and should not
4861   // generate a charCode
4862   switch (aNativeKeyCode) {
4863     // modifiers - we don't get separate events for these yet
4864     case kVK_Escape:
4865     case kVK_Shift:
4866     case kVK_RightShift:
4867     case kVK_Command:
4868     case kVK_RightCommand:
4869     case kVK_CapsLock:
4870     case kVK_Control:
4871     case kVK_RightControl:
4872     case kVK_Option:
4873     case kVK_RightOption:
4874     case kVK_ANSI_KeypadClear:
4875     case kVK_Function:
4877     // function keys
4878     case kVK_F1:
4879     case kVK_F2:
4880     case kVK_F3:
4881     case kVK_F4:
4882     case kVK_F5:
4883     case kVK_F6:
4884     case kVK_F7:
4885     case kVK_F8:
4886     case kVK_F9:
4887     case kVK_F10:
4888     case kVK_F11:
4889     case kVK_F12:
4890     case kVK_PC_Pause:
4891     case kVK_PC_ScrollLock:
4892     case kVK_PC_PrintScreen:
4893     case kVK_F16:
4894     case kVK_F17:
4895     case kVK_F18:
4896     case kVK_F19:
4898     case kVK_PC_Insert:
4899     case kVK_PC_Delete:
4900     case kVK_Tab:
4901     case kVK_PC_Backspace:
4902     case kVK_PC_ContextMenu:
4904     case kVK_JIS_Eisu:
4905     case kVK_JIS_Kana:
4907     case kVK_Home:
4908     case kVK_End:
4909     case kVK_PageUp:
4910     case kVK_PageDown:
4911     case kVK_LeftArrow:
4912     case kVK_RightArrow:
4913     case kVK_UpArrow:
4914     case kVK_DownArrow:
4915     case kVK_Return:
4916     case kVK_ANSI_KeypadEnter:
4917     case kVK_Powerbook_KeypadEnter:
4918       return true;
4919   }
4920   return false;
4923 /* static */ bool TextInputHandlerBase::IsNormalCharInputtingEvent(NSEvent* aNativeEvent) {
4924   if ([aNativeEvent type] != NSEventTypeKeyDown && [aNativeEvent type] != NSEventTypeKeyUp) {
4925     return false;
4926   }
4927   nsAutoString nativeChars;
4928   nsCocoaUtils::GetStringForNSString([aNativeEvent characters], nativeChars);
4930   // this is not character inputting event, simply.
4931   if (nativeChars.IsEmpty() || ([aNativeEvent modifierFlags] & NSEventModifierFlagCommand)) {
4932     return false;
4933   }
4934   return !IsControlChar(nativeChars[0]);
4937 /* static */ bool TextInputHandlerBase::IsModifierKey(UInt32 aNativeKeyCode) {
4938   switch (aNativeKeyCode) {
4939     case kVK_CapsLock:
4940     case kVK_RightCommand:
4941     case kVK_Command:
4942     case kVK_Shift:
4943     case kVK_Option:
4944     case kVK_Control:
4945     case kVK_RightShift:
4946     case kVK_RightOption:
4947     case kVK_RightControl:
4948     case kVK_Function:
4949       return true;
4950   }
4951   return false;
4954 /* static */ void TextInputHandlerBase::EnableSecureEventInput() {
4955   sSecureEventInputCount++;
4956   ::EnableSecureEventInput();
4959 /* static */ void TextInputHandlerBase::DisableSecureEventInput() {
4960   if (!sSecureEventInputCount) {
4961     return;
4962   }
4963   sSecureEventInputCount--;
4964   ::DisableSecureEventInput();
4967 /* static */ bool TextInputHandlerBase::IsSecureEventInputEnabled() {
4968   // sSecureEventInputCount is our mechanism to track when Secure Event Input
4969   //   is enabled. Non-zero indicates we have enabled Secure Input. But
4970   //   zero does not mean that Secure Input is _disabled_ because another
4971   //   application may have enabled it.  If the OS reports Secure Event
4972   //   Input is disabled though, a non-zero sSecureEventInputCount is an error.
4973   NS_ASSERTION(
4974       ::IsSecureEventInputEnabled() || 0 == sSecureEventInputCount,
4975       "sSecureEventInputCount is not zero when the OS thinks SecureEventInput is disabled.");
4976   return !!sSecureEventInputCount;
4979 /* static */ void TextInputHandlerBase::EnsureSecureEventInputDisabled() {
4980   while (sSecureEventInputCount) {
4981     TextInputHandlerBase::DisableSecureEventInput();
4982   }
4985 #pragma mark -
4987 /******************************************************************************
4989  *  TextInputHandlerBase::KeyEventState implementation
4991  ******************************************************************************/
4993 void TextInputHandlerBase::KeyEventState::InitKeyEvent(TextInputHandlerBase* aHandler,
4994                                                        WidgetKeyboardEvent& aKeyEvent,
4995                                                        bool aIsProcessedByIME) {
4996   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
4998   MOZ_ASSERT(aHandler);
4999   MOZ_RELEASE_ASSERT(mKeyEvent);
5001   NSEvent* nativeEvent = mKeyEvent;
5002   if (!mInsertedString.IsEmpty()) {
5003     nsAutoString unhandledString;
5004     GetUnhandledString(unhandledString);
5005     NSString* unhandledNSString = nsCocoaUtils::ToNSString(unhandledString);
5006     // If the key event's some characters were already handled by
5007     // InsertString() calls, we need to create a dummy event which doesn't
5008     // include the handled characters.
5009     nativeEvent = [NSEvent keyEventWithType:[mKeyEvent type]
5010                                    location:[mKeyEvent locationInWindow]
5011                               modifierFlags:[mKeyEvent modifierFlags]
5012                                   timestamp:[mKeyEvent timestamp]
5013                                windowNumber:[mKeyEvent windowNumber]
5014                                     context:nil
5015                                  characters:unhandledNSString
5016                 charactersIgnoringModifiers:[mKeyEvent charactersIgnoringModifiers]
5017                                   isARepeat:[mKeyEvent isARepeat]
5018                                     keyCode:[mKeyEvent keyCode]];
5019   }
5021   aKeyEvent.mUniqueId = mUniqueId;
5022   aHandler->InitKeyEvent(nativeEvent, aKeyEvent, aIsProcessedByIME, mInsertString);
5024   NS_OBJC_END_TRY_IGNORE_BLOCK;
5027 void TextInputHandlerBase::KeyEventState::GetUnhandledString(nsAString& aUnhandledString) const {
5028   aUnhandledString.Truncate();
5029   if (NS_WARN_IF(!mKeyEvent)) {
5030     return;
5031   }
5032   nsAutoString characters;
5033   nsCocoaUtils::GetStringForNSString([mKeyEvent characters], characters);
5034   if (characters.IsEmpty()) {
5035     return;
5036   }
5037   if (mInsertedString.IsEmpty()) {
5038     aUnhandledString = characters;
5039     return;
5040   }
5042   // The insertes string must match with the start of characters.
5043   MOZ_ASSERT(StringBeginsWith(characters, mInsertedString));
5045   aUnhandledString = nsDependentSubstring(characters, mInsertedString.Length());
5048 #pragma mark -
5050 /******************************************************************************
5052  *  TextInputHandlerBase::AutoInsertStringClearer implementation
5054  ******************************************************************************/
5056 TextInputHandlerBase::AutoInsertStringClearer::~AutoInsertStringClearer() {
5057   if (mState && mState->mInsertString) {
5058     // If inserting string is a part of characters of the event,
5059     // we should record it as inserted string.
5060     nsAutoString characters;
5061     nsCocoaUtils::GetStringForNSString([mState->mKeyEvent characters], characters);
5062     nsAutoString insertedString(mState->mInsertedString);
5063     insertedString += *mState->mInsertString;
5064     if (StringBeginsWith(characters, insertedString)) {
5065       mState->mInsertedString = insertedString;
5066     }
5067   }
5068   if (mState) {
5069     mState->mInsertString = nullptr;
5070   }
5073 #undef MOZ_LOG_KEY_OR_IME