widget/cocoa/TextInputHandler.mm

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

mercurial