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