diff options
author | Moonchild <moonchild@palemoon.org> | 2021-05-08 21:00:49 +0000 |
---|---|---|
committer | Moonchild <moonchild@palemoon.org> | 2021-05-08 21:00:49 +0000 |
commit | 08da125d9cc6eea0bc514023e3a75efd64587259 (patch) | |
tree | 1fd18aee3b81c3b0da3c38b15af100db0be776af /widget/cocoa/TextInputHandler.mm | |
parent | ca35efb84ebae522f9ab7803d8e017f721e03207 (diff) | |
download | uxp-08da125d9cc6eea0bc514023e3a75efd64587259.tar.gz |
Issue #1751 -- Remove cocoa and uikit widget support code
Diffstat (limited to 'widget/cocoa/TextInputHandler.mm')
-rw-r--r-- | widget/cocoa/TextInputHandler.mm | 4533 |
1 files changed, 0 insertions, 4533 deletions
diff --git a/widget/cocoa/TextInputHandler.mm b/widget/cocoa/TextInputHandler.mm deleted file mode 100644 index 348d99ab69..0000000000 --- a/widget/cocoa/TextInputHandler.mm +++ /dev/null @@ -1,4533 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "TextInputHandler.h" - -#include "mozilla/Logging.h" - -#include "mozilla/ArrayUtils.h" -#include "mozilla/AutoRestore.h" -#include "mozilla/MiscEvents.h" -#include "mozilla/MouseEvents.h" -#include "mozilla/TextEventDispatcher.h" -#include "mozilla/TextEvents.h" - -#include "nsChildView.h" -#include "nsObjCExceptions.h" -#include "nsBidiUtils.h" -#include "nsToolkit.h" -#include "nsCocoaUtils.h" -#include "WidgetUtils.h" -#include "nsPrintfCString.h" -#include "ComplexTextInputPanel.h" - -using namespace mozilla; -using namespace mozilla::widget; - -LazyLogModule gLog("TextInputHandlerWidgets"); - -static const char* -OnOrOff(bool aBool) -{ - return aBool ? "ON" : "off"; -} - -static const char* -TrueOrFalse(bool aBool) -{ - return aBool ? "TRUE" : "FALSE"; -} - -static const char* -GetKeyNameForNativeKeyCode(unsigned short aNativeKeyCode) -{ - switch (aNativeKeyCode) { - case kVK_Escape: return "Escape"; - case kVK_RightCommand: return "Right-Command"; - case kVK_Command: return "Command"; - case kVK_Shift: return "Shift"; - case kVK_CapsLock: return "CapsLock"; - case kVK_Option: return "Option"; - case kVK_Control: return "Control"; - case kVK_RightShift: return "Right-Shift"; - case kVK_RightOption: return "Right-Option"; - case kVK_RightControl: return "Right-Control"; - case kVK_ANSI_KeypadClear: return "Clear"; - - case kVK_F1: return "F1"; - case kVK_F2: return "F2"; - case kVK_F3: return "F3"; - case kVK_F4: return "F4"; - case kVK_F5: return "F5"; - case kVK_F6: return "F6"; - case kVK_F7: return "F7"; - case kVK_F8: return "F8"; - case kVK_F9: return "F9"; - case kVK_F10: return "F10"; - case kVK_F11: return "F11"; - case kVK_F12: return "F12"; - case kVK_F13: return "F13/PrintScreen"; - case kVK_F14: return "F14/ScrollLock"; - case kVK_F15: return "F15/Pause"; - - case kVK_ANSI_Keypad0: return "NumPad-0"; - case kVK_ANSI_Keypad1: return "NumPad-1"; - case kVK_ANSI_Keypad2: return "NumPad-2"; - case kVK_ANSI_Keypad3: return "NumPad-3"; - case kVK_ANSI_Keypad4: return "NumPad-4"; - case kVK_ANSI_Keypad5: return "NumPad-5"; - case kVK_ANSI_Keypad6: return "NumPad-6"; - case kVK_ANSI_Keypad7: return "NumPad-7"; - case kVK_ANSI_Keypad8: return "NumPad-8"; - case kVK_ANSI_Keypad9: return "NumPad-9"; - - case kVK_ANSI_KeypadMultiply: return "NumPad-*"; - case kVK_ANSI_KeypadPlus: return "NumPad-+"; - case kVK_ANSI_KeypadMinus: return "NumPad--"; - case kVK_ANSI_KeypadDecimal: return "NumPad-."; - case kVK_ANSI_KeypadDivide: return "NumPad-/"; - case kVK_ANSI_KeypadEquals: return "NumPad-="; - case kVK_ANSI_KeypadEnter: return "NumPad-Enter"; - case kVK_Return: return "Return"; - case kVK_Powerbook_KeypadEnter: return "NumPad-EnterOnPowerBook"; - - case kVK_PC_Insert: return "Insert/Help"; - case kVK_PC_Delete: return "Delete"; - case kVK_Tab: return "Tab"; - case kVK_PC_Backspace: return "Backspace"; - case kVK_Home: return "Home"; - case kVK_End: return "End"; - case kVK_PageUp: return "PageUp"; - case kVK_PageDown: return "PageDown"; - case kVK_LeftArrow: return "LeftArrow"; - case kVK_RightArrow: return "RightArrow"; - case kVK_UpArrow: return "UpArrow"; - case kVK_DownArrow: return "DownArrow"; - case kVK_PC_ContextMenu: return "ContextMenu"; - - case kVK_Function: return "Function"; - case kVK_VolumeUp: return "VolumeUp"; - case kVK_VolumeDown: return "VolumeDown"; - case kVK_Mute: return "Mute"; - - case kVK_ISO_Section: return "ISO_Section"; - - case kVK_JIS_Yen: return "JIS_Yen"; - case kVK_JIS_Underscore: return "JIS_Underscore"; - case kVK_JIS_KeypadComma: return "JIS_KeypadComma"; - case kVK_JIS_Eisu: return "JIS_Eisu"; - case kVK_JIS_Kana: return "JIS_Kana"; - - case kVK_ANSI_A: return "A"; - case kVK_ANSI_B: return "B"; - case kVK_ANSI_C: return "C"; - case kVK_ANSI_D: return "D"; - case kVK_ANSI_E: return "E"; - case kVK_ANSI_F: return "F"; - case kVK_ANSI_G: return "G"; - case kVK_ANSI_H: return "H"; - case kVK_ANSI_I: return "I"; - case kVK_ANSI_J: return "J"; - case kVK_ANSI_K: return "K"; - case kVK_ANSI_L: return "L"; - case kVK_ANSI_M: return "M"; - case kVK_ANSI_N: return "N"; - case kVK_ANSI_O: return "O"; - case kVK_ANSI_P: return "P"; - case kVK_ANSI_Q: return "Q"; - case kVK_ANSI_R: return "R"; - case kVK_ANSI_S: return "S"; - case kVK_ANSI_T: return "T"; - case kVK_ANSI_U: return "U"; - case kVK_ANSI_V: return "V"; - case kVK_ANSI_W: return "W"; - case kVK_ANSI_X: return "X"; - case kVK_ANSI_Y: return "Y"; - case kVK_ANSI_Z: return "Z"; - - case kVK_ANSI_1: return "1"; - case kVK_ANSI_2: return "2"; - case kVK_ANSI_3: return "3"; - case kVK_ANSI_4: return "4"; - case kVK_ANSI_5: return "5"; - case kVK_ANSI_6: return "6"; - case kVK_ANSI_7: return "7"; - case kVK_ANSI_8: return "8"; - case kVK_ANSI_9: return "9"; - case kVK_ANSI_0: return "0"; - case kVK_ANSI_Equal: return "Equal"; - case kVK_ANSI_Minus: return "Minus"; - case kVK_ANSI_RightBracket: return "RightBracket"; - case kVK_ANSI_LeftBracket: return "LeftBracket"; - case kVK_ANSI_Quote: return "Quote"; - case kVK_ANSI_Semicolon: return "Semicolon"; - case kVK_ANSI_Backslash: return "Backslash"; - case kVK_ANSI_Comma: return "Comma"; - case kVK_ANSI_Slash: return "Slash"; - case kVK_ANSI_Period: return "Period"; - case kVK_ANSI_Grave: return "Grave"; - - default: return "undefined"; - } -} - -static const char* -GetCharacters(const NSString* aString) -{ - nsAutoString str; - nsCocoaUtils::GetStringForNSString(aString, str); - if (str.IsEmpty()) { - return ""; - } - - nsAutoString escapedStr; - for (uint32_t i = 0; i < str.Length(); i++) { - char16_t ch = str[i]; - if (ch < 0x20) { - nsPrintfCString utf8str("(U+%04X)", ch); - escapedStr += NS_ConvertUTF8toUTF16(utf8str); - } else if (ch <= 0x7E) { - escapedStr += ch; - } else { - nsPrintfCString utf8str("(U+%04X)", ch); - escapedStr += ch; - escapedStr += NS_ConvertUTF8toUTF16(utf8str); - } - } - - // the result will be freed automatically by cocoa. - NSString* result = nsCocoaUtils::ToNSString(escapedStr); - return [result UTF8String]; -} - -static const char* -GetCharacters(const CFStringRef aString) -{ - const NSString* str = reinterpret_cast<const NSString*>(aString); - return GetCharacters(str); -} - -static const char* -GetNativeKeyEventType(NSEvent* aNativeEvent) -{ - switch ([aNativeEvent type]) { - case NSKeyDown: return "NSKeyDown"; - case NSKeyUp: return "NSKeyUp"; - case NSFlagsChanged: return "NSFlagsChanged"; - default: return "not key event"; - } -} - -static const char* -GetGeckoKeyEventType(const WidgetEvent& aEvent) -{ - switch (aEvent.mMessage) { - case eKeyDown: return "eKeyDown"; - case eKeyUp: return "eKeyUp"; - case eKeyPress: return "eKeyPress"; - default: return "not key event"; - } -} - -static const char* -GetWindowLevelName(NSInteger aWindowLevel) -{ - switch (aWindowLevel) { - case kCGBaseWindowLevelKey: - return "kCGBaseWindowLevelKey (NSNormalWindowLevel)"; - case kCGMinimumWindowLevelKey: - return "kCGMinimumWindowLevelKey"; - case kCGDesktopWindowLevelKey: - return "kCGDesktopWindowLevelKey"; - case kCGBackstopMenuLevelKey: - return "kCGBackstopMenuLevelKey"; - case kCGNormalWindowLevelKey: - return "kCGNormalWindowLevelKey"; - case kCGFloatingWindowLevelKey: - return "kCGFloatingWindowLevelKey (NSFloatingWindowLevel)"; - case kCGTornOffMenuWindowLevelKey: - return "kCGTornOffMenuWindowLevelKey (NSSubmenuWindowLevel, NSTornOffMenuWindowLevel)"; - case kCGDockWindowLevelKey: - return "kCGDockWindowLevelKey (NSDockWindowLevel)"; - case kCGMainMenuWindowLevelKey: - return "kCGMainMenuWindowLevelKey (NSMainMenuWindowLevel)"; - case kCGStatusWindowLevelKey: - return "kCGStatusWindowLevelKey (NSStatusWindowLevel)"; - case kCGModalPanelWindowLevelKey: - return "kCGModalPanelWindowLevelKey (NSModalPanelWindowLevel)"; - case kCGPopUpMenuWindowLevelKey: - return "kCGPopUpMenuWindowLevelKey (NSPopUpMenuWindowLevel)"; - case kCGDraggingWindowLevelKey: - return "kCGDraggingWindowLevelKey"; - case kCGScreenSaverWindowLevelKey: - return "kCGScreenSaverWindowLevelKey (NSScreenSaverWindowLevel)"; - case kCGMaximumWindowLevelKey: - return "kCGMaximumWindowLevelKey"; - case kCGOverlayWindowLevelKey: - return "kCGOverlayWindowLevelKey"; - case kCGHelpWindowLevelKey: - return "kCGHelpWindowLevelKey"; - case kCGUtilityWindowLevelKey: - return "kCGUtilityWindowLevelKey"; - case kCGDesktopIconWindowLevelKey: - return "kCGDesktopIconWindowLevelKey"; - case kCGCursorWindowLevelKey: - return "kCGCursorWindowLevelKey"; - case kCGNumberOfWindowLevelKeys: - return "kCGNumberOfWindowLevelKeys"; - default: - return "unknown window level"; - } -} - -static bool -IsControlChar(uint32_t aCharCode) -{ - return aCharCode < ' ' || aCharCode == 0x7F; -} - -static uint32_t gHandlerInstanceCount = 0; - -static void -EnsureToLogAllKeyboardLayoutsAndIMEs() -{ - static bool sDone = false; - if (!sDone) { - sDone = true; - TextInputHandler::DebugPrintAllKeyboardLayouts(); - IMEInputHandler::DebugPrintAllIMEModes(); - } -} - -#pragma mark - - - -/****************************************************************************** - * - * TISInputSourceWrapper implementation - * - ******************************************************************************/ - -TISInputSourceWrapper* TISInputSourceWrapper::sCurrentInputSource = nullptr; - -// static -TISInputSourceWrapper& -TISInputSourceWrapper::CurrentInputSource() -{ - if (!sCurrentInputSource) { - sCurrentInputSource = new TISInputSourceWrapper(); - } - if (!sCurrentInputSource->IsInitializedByCurrentInputSource()) { - sCurrentInputSource->InitByCurrentInputSource(); - } - return *sCurrentInputSource; -} - -// static -void -TISInputSourceWrapper::Shutdown() -{ - if (!sCurrentInputSource) { - return; - } - sCurrentInputSource->Clear(); - delete sCurrentInputSource; - sCurrentInputSource = nullptr; -} - -bool -TISInputSourceWrapper::TranslateToString(UInt32 aKeyCode, UInt32 aModifiers, - UInt32 aKbType, nsAString &aStr) -{ - aStr.Truncate(); - - const UCKeyboardLayout* UCKey = GetUCKeyboardLayout(); - - MOZ_LOG(gLog, LogLevel::Info, - ("%p TISInputSourceWrapper::TranslateToString, aKeyCode=0x%X, " - "aModifiers=0x%X, aKbType=0x%X UCKey=%p\n " - "Shift: %s, Ctrl: %s, Opt: %s, Cmd: %s, CapsLock: %s, NumLock: %s", - this, aKeyCode, aModifiers, aKbType, UCKey, - OnOrOff(aModifiers & shiftKey), OnOrOff(aModifiers & controlKey), - OnOrOff(aModifiers & optionKey), OnOrOff(aModifiers & cmdKey), - OnOrOff(aModifiers & alphaLock), - OnOrOff(aModifiers & kEventKeyModifierNumLockMask))); - - NS_ENSURE_TRUE(UCKey, false); - - UInt32 deadKeyState = 0; - UniCharCount len; - UniChar chars[5]; - OSStatus err = ::UCKeyTranslate(UCKey, aKeyCode, - kUCKeyActionDown, aModifiers >> 8, - aKbType, kUCKeyTranslateNoDeadKeysMask, - &deadKeyState, 5, &len, chars); - - MOZ_LOG(gLog, LogLevel::Info, - ("%p TISInputSourceWrapper::TranslateToString, err=0x%X, len=%llu", - this, err, len)); - - NS_ENSURE_TRUE(err == noErr, false); - if (len == 0) { - return true; - } - NS_ENSURE_TRUE(EnsureStringLength(aStr, len), false); - NS_ASSERTION(sizeof(char16_t) == sizeof(UniChar), - "size of char16_t and size of UniChar are different"); - memcpy(aStr.BeginWriting(), chars, len * sizeof(char16_t)); - - MOZ_LOG(gLog, LogLevel::Info, - ("%p TISInputSourceWrapper::TranslateToString, aStr=\"%s\"", - this, NS_ConvertUTF16toUTF8(aStr).get())); - - return true; -} - -uint32_t -TISInputSourceWrapper::TranslateToChar(UInt32 aKeyCode, UInt32 aModifiers, - UInt32 aKbType) -{ - nsAutoString str; - if (!TranslateToString(aKeyCode, aModifiers, aKbType, str) || - str.Length() != 1) { - return 0; - } - return static_cast<uint32_t>(str.CharAt(0)); -} - -void -TISInputSourceWrapper::InitByInputSourceID(const char* aID) -{ - Clear(); - if (!aID) - return; - - CFStringRef idstr = ::CFStringCreateWithCString(kCFAllocatorDefault, aID, - kCFStringEncodingASCII); - InitByInputSourceID(idstr); - ::CFRelease(idstr); -} - -void -TISInputSourceWrapper::InitByInputSourceID(const nsAFlatString &aID) -{ - Clear(); - if (aID.IsEmpty()) - return; - CFStringRef idstr = ::CFStringCreateWithCharacters(kCFAllocatorDefault, - reinterpret_cast<const UniChar*>(aID.get()), - aID.Length()); - InitByInputSourceID(idstr); - ::CFRelease(idstr); -} - -void -TISInputSourceWrapper::InitByInputSourceID(const CFStringRef aID) -{ - Clear(); - if (!aID) - return; - const void* keys[] = { kTISPropertyInputSourceID }; - const void* values[] = { aID }; - CFDictionaryRef filter = - ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL); - NS_ASSERTION(filter, "failed to create the filter"); - mInputSourceList = ::TISCreateInputSourceList(filter, true); - ::CFRelease(filter); - if (::CFArrayGetCount(mInputSourceList) > 0) { - mInputSource = static_cast<TISInputSourceRef>( - const_cast<void *>(::CFArrayGetValueAtIndex(mInputSourceList, 0))); - if (IsKeyboardLayout()) { - mKeyboardLayout = mInputSource; - } - } -} - -void -TISInputSourceWrapper::InitByLayoutID(SInt32 aLayoutID, - bool aOverrideKeyboard) -{ - // NOTE: Doument new layout IDs in TextInputHandler.h when you add ones. - switch (aLayoutID) { - case 0: - InitByInputSourceID("com.apple.keylayout.US"); - break; - case 1: - InitByInputSourceID("com.apple.keylayout.Greek"); - break; - case 2: - InitByInputSourceID("com.apple.keylayout.German"); - break; - case 3: - InitByInputSourceID("com.apple.keylayout.Swedish-Pro"); - break; - case 4: - InitByInputSourceID("com.apple.keylayout.DVORAK-QWERTYCMD"); - break; - case 5: - InitByInputSourceID("com.apple.keylayout.Thai"); - break; - case 6: - InitByInputSourceID("com.apple.keylayout.Arabic"); - break; - case 7: - InitByInputSourceID("com.apple.keylayout.ArabicPC"); - break; - case 8: - InitByInputSourceID("com.apple.keylayout.French"); - break; - case 9: - InitByInputSourceID("com.apple.keylayout.Hebrew"); - break; - case 10: - InitByInputSourceID("com.apple.keylayout.Lithuanian"); - break; - case 11: - InitByInputSourceID("com.apple.keylayout.Norwegian"); - break; - case 12: - InitByInputSourceID("com.apple.keylayout.Spanish"); - break; - default: - Clear(); - break; - } - mOverrideKeyboard = aOverrideKeyboard; -} - -void -TISInputSourceWrapper::InitByCurrentInputSource() -{ - Clear(); - mInputSource = ::TISCopyCurrentKeyboardInputSource(); - mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride(); - if (!mKeyboardLayout) { - mKeyboardLayout = ::TISCopyCurrentKeyboardLayoutInputSource(); - } - // If this causes composition, the current keyboard layout may input non-ASCII - // characters such as Japanese Kana characters or Hangul characters. - // However, we need to set ASCII characters to DOM key events for consistency - // with other platforms. - if (IsOpenedIMEMode()) { - TISInputSourceWrapper tis(mKeyboardLayout); - if (!tis.IsASCIICapable()) { - mKeyboardLayout = - ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource(); - } - } -} - -void -TISInputSourceWrapper::InitByCurrentKeyboardLayout() -{ - Clear(); - mInputSource = ::TISCopyCurrentKeyboardLayoutInputSource(); - mKeyboardLayout = mInputSource; -} - -void -TISInputSourceWrapper::InitByCurrentASCIICapableInputSource() -{ - Clear(); - mInputSource = ::TISCopyCurrentASCIICapableKeyboardInputSource(); - mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride(); - if (mKeyboardLayout) { - TISInputSourceWrapper tis(mKeyboardLayout); - if (!tis.IsASCIICapable()) { - mKeyboardLayout = nullptr; - } - } - if (!mKeyboardLayout) { - mKeyboardLayout = - ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource(); - } -} - -void -TISInputSourceWrapper::InitByCurrentASCIICapableKeyboardLayout() -{ - Clear(); - mInputSource = ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource(); - mKeyboardLayout = mInputSource; -} - -void -TISInputSourceWrapper::InitByCurrentInputMethodKeyboardLayoutOverride() -{ - Clear(); - mInputSource = ::TISCopyInputMethodKeyboardLayoutOverride(); - mKeyboardLayout = mInputSource; -} - -void -TISInputSourceWrapper::InitByTISInputSourceRef(TISInputSourceRef aInputSource) -{ - Clear(); - mInputSource = aInputSource; - if (IsKeyboardLayout()) { - mKeyboardLayout = mInputSource; - } -} - -void -TISInputSourceWrapper::InitByLanguage(CFStringRef aLanguage) -{ - Clear(); - mInputSource = ::TISCopyInputSourceForLanguage(aLanguage); - if (IsKeyboardLayout()) { - mKeyboardLayout = mInputSource; - } -} - -const UCKeyboardLayout* -TISInputSourceWrapper::GetUCKeyboardLayout() -{ - NS_ENSURE_TRUE(mKeyboardLayout, nullptr); - if (mUCKeyboardLayout) { - return mUCKeyboardLayout; - } - CFDataRef uchr = static_cast<CFDataRef>( - ::TISGetInputSourceProperty(mKeyboardLayout, - kTISPropertyUnicodeKeyLayoutData)); - - // We should be always able to get the layout here. - NS_ENSURE_TRUE(uchr, nullptr); - mUCKeyboardLayout = - reinterpret_cast<const UCKeyboardLayout*>(CFDataGetBytePtr(uchr)); - return mUCKeyboardLayout; -} - -bool -TISInputSourceWrapper::GetBoolProperty(const CFStringRef aKey) -{ - CFBooleanRef ret = static_cast<CFBooleanRef>( - ::TISGetInputSourceProperty(mInputSource, aKey)); - return ::CFBooleanGetValue(ret); -} - -bool -TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey, - CFStringRef &aStr) -{ - aStr = static_cast<CFStringRef>( - ::TISGetInputSourceProperty(mInputSource, aKey)); - return aStr != nullptr; -} - -bool -TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey, - nsAString &aStr) -{ - CFStringRef str; - GetStringProperty(aKey, str); - nsCocoaUtils::GetStringForNSString((const NSString*)str, aStr); - return !aStr.IsEmpty(); -} - -bool -TISInputSourceWrapper::IsOpenedIMEMode() -{ - NS_ENSURE_TRUE(mInputSource, false); - if (!IsIMEMode()) - return false; - return !IsASCIICapable(); -} - -bool -TISInputSourceWrapper::IsIMEMode() -{ - NS_ENSURE_TRUE(mInputSource, false); - CFStringRef str; - GetInputSourceType(str); - NS_ENSURE_TRUE(str, false); - return ::CFStringCompare(kTISTypeKeyboardInputMode, - str, 0) == kCFCompareEqualTo; -} - -bool -TISInputSourceWrapper::IsKeyboardLayout() -{ - NS_ENSURE_TRUE(mInputSource, false); - CFStringRef str; - GetInputSourceType(str); - NS_ENSURE_TRUE(str, false); - return ::CFStringCompare(kTISTypeKeyboardLayout, - str, 0) == kCFCompareEqualTo; -} - -bool -TISInputSourceWrapper::GetLanguageList(CFArrayRef &aLanguageList) -{ - NS_ENSURE_TRUE(mInputSource, false); - aLanguageList = static_cast<CFArrayRef>( - ::TISGetInputSourceProperty(mInputSource, - kTISPropertyInputSourceLanguages)); - return aLanguageList != nullptr; -} - -bool -TISInputSourceWrapper::GetPrimaryLanguage(CFStringRef &aPrimaryLanguage) -{ - NS_ENSURE_TRUE(mInputSource, false); - CFArrayRef langList; - NS_ENSURE_TRUE(GetLanguageList(langList), false); - if (::CFArrayGetCount(langList) == 0) - return false; - aPrimaryLanguage = - static_cast<CFStringRef>(::CFArrayGetValueAtIndex(langList, 0)); - return aPrimaryLanguage != nullptr; -} - -bool -TISInputSourceWrapper::GetPrimaryLanguage(nsAString &aPrimaryLanguage) -{ - NS_ENSURE_TRUE(mInputSource, false); - CFStringRef primaryLanguage; - NS_ENSURE_TRUE(GetPrimaryLanguage(primaryLanguage), false); - nsCocoaUtils::GetStringForNSString((const NSString*)primaryLanguage, - aPrimaryLanguage); - return !aPrimaryLanguage.IsEmpty(); -} - -bool -TISInputSourceWrapper::IsForRTLLanguage() -{ - if (mIsRTL < 0) { - // Get the input character of the 'A' key of ANSI keyboard layout. - nsAutoString str; - bool ret = TranslateToString(kVK_ANSI_A, 0, eKbdType_ANSI, str); - NS_ENSURE_TRUE(ret, ret); - char16_t ch = str.IsEmpty() ? char16_t(0) : str.CharAt(0); - mIsRTL = UCS2_CHAR_IS_BIDI(ch); - } - return mIsRTL != 0; -} - -bool -TISInputSourceWrapper::IsInitializedByCurrentInputSource() -{ - return mInputSource == ::TISCopyCurrentKeyboardInputSource(); -} - -void -TISInputSourceWrapper::Select() -{ - if (!mInputSource) - return; - ::TISSelectInputSource(mInputSource); -} - -void -TISInputSourceWrapper::Clear() -{ - // Clear() is always called when TISInputSourceWrappper is created. - EnsureToLogAllKeyboardLayoutsAndIMEs(); - - if (mInputSourceList) { - ::CFRelease(mInputSourceList); - } - mInputSourceList = nullptr; - mInputSource = nullptr; - mKeyboardLayout = nullptr; - mIsRTL = -1; - mUCKeyboardLayout = nullptr; - mOverrideKeyboard = false; -} - -bool -TISInputSourceWrapper::IsPrintableKeyEvent(NSEvent* aNativeKeyEvent) const -{ - UInt32 nativeKeyCode = [aNativeKeyEvent keyCode]; - - bool isPrintableKey = !TextInputHandler::IsSpecialGeckoKey(nativeKeyCode); - if (isPrintableKey && - [aNativeKeyEvent type] != NSKeyDown && - [aNativeKeyEvent type] != NSKeyUp) { - NS_WARNING("Why the printable key doesn't cause NSKeyDown or NSKeyUp?"); - isPrintableKey = false; - } - return isPrintableKey; -} - -UInt32 -TISInputSourceWrapper::GetKbdType() const -{ - // If a keyboard layout override is set, we also need to force the keyboard - // type to something ANSI to avoid test failures on machines with JIS - // keyboards (since the pair of keyboard layout and physical keyboard type - // form the actual key layout). This assumes that the test setting the - // override was written assuming an ANSI keyboard. - return mOverrideKeyboard ? eKbdType_ANSI : ::LMGetKbdType(); -} - -void -TISInputSourceWrapper::ComputeInsertStringForCharCode( - NSEvent* aNativeKeyEvent, - const WidgetKeyboardEvent& aKeyEvent, - const nsAString* aInsertString, - nsAString& aResult) -{ - if (aInsertString) { - // If the caller expects that the aInsertString will be input, we shouldn't - // change it. - aResult = *aInsertString; - } else if (IsPrintableKeyEvent(aNativeKeyEvent)) { - // If IME is open, [aNativeKeyEvent characters] may be a character - // which will be appended to the composition string. However, especially, - // while IME is disabled, most users and developers expect the key event - // works as IME closed. So, we should compute the aResult with - // the ASCII capable keyboard layout. - // NOTE: Such keyboard layouts typically change the layout to its ASCII - // capable layout when Command key is pressed. And we don't worry - // when Control key is pressed too because it causes inputting - // control characters. - // Additionally, if the key event doesn't input any text, the event may be - // dead key event. In this case, the charCode value should be the dead - // character. - UInt32 nativeKeyCode = [aNativeKeyEvent keyCode]; - if ((!aKeyEvent.IsMeta() && !aKeyEvent.IsControl() && IsOpenedIMEMode()) || - ![[aNativeKeyEvent characters] length]) { - UInt32 state = - nsCocoaUtils::ConvertToCarbonModifier([aNativeKeyEvent modifierFlags]); - uint32_t ch = TranslateToChar(nativeKeyCode, state, GetKbdType()); - if (ch) { - aResult = ch; - } - } else { - // If the caller isn't sure what string will be input, let's use - // characters of NSEvent. - nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], aResult); - } - - // If control key is pressed and the eventChars is a non-printable control - // character, we should convert it to ASCII alphabet. - if (aKeyEvent.IsControl() && - !aResult.IsEmpty() && aResult[0] <= char16_t(26)) { - aResult = (aKeyEvent.IsShift() ^ aKeyEvent.IsCapsLocked()) ? - static_cast<char16_t>(aResult[0] + ('A' - 1)) : - static_cast<char16_t>(aResult[0] + ('a' - 1)); - } - // If Meta key is pressed, it may cause to switch the keyboard layout like - // Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY. - else if (aKeyEvent.IsMeta() && - !(aKeyEvent.IsControl() || aKeyEvent.IsAlt())) { - UInt32 kbType = GetKbdType(); - UInt32 numLockState = - aKeyEvent.IsNumLocked() ? kEventKeyModifierNumLockMask : 0; - UInt32 capsLockState = aKeyEvent.IsCapsLocked() ? alphaLock : 0; - UInt32 shiftState = aKeyEvent.IsShift() ? shiftKey : 0; - uint32_t uncmdedChar = - TranslateToChar(nativeKeyCode, numLockState, kbType); - uint32_t cmdedChar = - TranslateToChar(nativeKeyCode, cmdKey | numLockState, kbType); - // If we can make a good guess at the characters that the user would - // expect this key combination to produce (with and without Shift) then - // use those characters. This also corrects for CapsLock. - uint32_t ch = 0; - if (uncmdedChar == cmdedChar) { - // The characters produced with Command seem similar to those without - // Command. - ch = TranslateToChar(nativeKeyCode, - shiftState | capsLockState | numLockState, kbType); - } else { - TISInputSourceWrapper USLayout("com.apple.keylayout.US"); - uint32_t uncmdedUSChar = - USLayout.TranslateToChar(nativeKeyCode, numLockState, kbType); - // If it looks like characters from US keyboard layout when Command key - // is pressed, we should compute a character in the layout. - if (uncmdedUSChar == cmdedChar) { - ch = USLayout.TranslateToChar(nativeKeyCode, - shiftState | capsLockState | numLockState, kbType); - } - } - - // If there is a more preferred character for the commanded key event, - // we should use it. - if (ch) { - aResult = ch; - } - } - } - - // Remove control characters which shouldn't be inputted on editor. - // XXX Currently, we don't find any cases inserting control characters with - // printable character. So, just checking first character is enough. - if (!aResult.IsEmpty() && IsControlChar(aResult[0])) { - aResult.Truncate(); - } -} - -void -TISInputSourceWrapper::InitKeyEvent(NSEvent *aNativeKeyEvent, - WidgetKeyboardEvent& aKeyEvent, - const nsAString *aInsertString) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - - MOZ_LOG(gLog, LogLevel::Info, - ("%p TISInputSourceWrapper::InitKeyEvent, aNativeKeyEvent=%p, " - "aKeyEvent.mMessage=%s, aInsertString=%p, IsOpenedIMEMode()=%s", - this, aNativeKeyEvent, GetGeckoKeyEventType(aKeyEvent), aInsertString, - TrueOrFalse(IsOpenedIMEMode()))); - - NS_ENSURE_TRUE(aNativeKeyEvent, ); - - nsCocoaUtils::InitInputEvent(aKeyEvent, aNativeKeyEvent); - - // This is used only while dispatching the event (which is a synchronous - // call), so there is no need to retain and release this data. - aKeyEvent.mNativeKeyEvent = aNativeKeyEvent; - - // Fill in fields used for Cocoa NPAPI plugins - if ([aNativeKeyEvent type] == NSKeyDown || - [aNativeKeyEvent type] == NSKeyUp) { - aKeyEvent.mNativeKeyCode = [aNativeKeyEvent keyCode]; - aKeyEvent.mNativeModifierFlags = [aNativeKeyEvent modifierFlags]; - nsAutoString nativeChars; - nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], nativeChars); - aKeyEvent.mNativeCharacters.Assign(nativeChars); - nsAutoString nativeCharsIgnoringModifiers; - nsCocoaUtils::GetStringForNSString([aNativeKeyEvent charactersIgnoringModifiers], nativeCharsIgnoringModifiers); - aKeyEvent.mNativeCharactersIgnoringModifiers.Assign(nativeCharsIgnoringModifiers); - } else if ([aNativeKeyEvent type] == NSFlagsChanged) { - aKeyEvent.mNativeKeyCode = [aNativeKeyEvent keyCode]; - aKeyEvent.mNativeModifierFlags = [aNativeKeyEvent modifierFlags]; - } - - aKeyEvent.mRefPoint = LayoutDeviceIntPoint(0, 0); - aKeyEvent.mIsChar = false; // XXX not used in XP level - - UInt32 kbType = GetKbdType(); - UInt32 nativeKeyCode = [aNativeKeyEvent keyCode]; - - aKeyEvent.mKeyCode = - ComputeGeckoKeyCode(nativeKeyCode, kbType, aKeyEvent.IsMeta()); - - switch (nativeKeyCode) { - case kVK_Command: - case kVK_Shift: - case kVK_Option: - case kVK_Control: - aKeyEvent.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT; - break; - - case kVK_RightCommand: - case kVK_RightShift: - case kVK_RightOption: - case kVK_RightControl: - aKeyEvent.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT; - break; - - case kVK_ANSI_Keypad0: - case kVK_ANSI_Keypad1: - case kVK_ANSI_Keypad2: - case kVK_ANSI_Keypad3: - case kVK_ANSI_Keypad4: - case kVK_ANSI_Keypad5: - case kVK_ANSI_Keypad6: - case kVK_ANSI_Keypad7: - case kVK_ANSI_Keypad8: - case kVK_ANSI_Keypad9: - case kVK_ANSI_KeypadMultiply: - case kVK_ANSI_KeypadPlus: - case kVK_ANSI_KeypadMinus: - case kVK_ANSI_KeypadDecimal: - case kVK_ANSI_KeypadDivide: - case kVK_ANSI_KeypadEquals: - case kVK_ANSI_KeypadEnter: - case kVK_JIS_KeypadComma: - case kVK_Powerbook_KeypadEnter: - aKeyEvent.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD; - break; - - default: - aKeyEvent.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD; - break; - } - - aKeyEvent.mIsRepeat = - ([aNativeKeyEvent type] == NSKeyDown) ? [aNativeKeyEvent isARepeat] : false; - - MOZ_LOG(gLog, LogLevel::Info, - ("%p TISInputSourceWrapper::InitKeyEvent, " - "shift=%s, ctrl=%s, alt=%s, meta=%s", - this, OnOrOff(aKeyEvent.IsShift()), OnOrOff(aKeyEvent.IsControl()), - OnOrOff(aKeyEvent.IsAlt()), OnOrOff(aKeyEvent.IsMeta()))); - - if (IsPrintableKeyEvent(aNativeKeyEvent)) { - aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING; - // If insertText calls this method, let's use the string. - if (aInsertString && !aInsertString->IsEmpty() && - !IsControlChar((*aInsertString)[0])) { - aKeyEvent.mKeyValue = *aInsertString; - } - // If meta key is pressed, the printable key layout may be switched from - // non-ASCII capable layout to ASCII capable, or from Dvorak to QWERTY. - // KeyboardEvent.key value should be the switched layout's character. - else if (aKeyEvent.IsMeta()) { - nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], - aKeyEvent.mKeyValue); - } - // If control key is pressed, some keys may produce printable character via - // [aNativeKeyEvent characters]. Otherwise, translate input character of - // the key without control key. - else if (aKeyEvent.IsControl()) { - nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], - aKeyEvent.mKeyValue); - if (aKeyEvent.mKeyValue.IsEmpty() || - IsControlChar(aKeyEvent.mKeyValue[0])) { - NSUInteger cocoaState = - [aNativeKeyEvent modifierFlags] & ~NSControlKeyMask; - UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState); - aKeyEvent.mKeyValue = - TranslateToChar(nativeKeyCode, carbonState, kbType); - } - } - // Otherwise, KeyboardEvent.key expose - // [aNativeKeyEvent characters] value. However, if IME is open and the - // keyboard layout isn't ASCII capable, exposing the non-ASCII character - // doesn't match with other platform's behavior. For the compatibility - // with other platform's Gecko, we need to set a translated character. - else if (IsOpenedIMEMode()) { - UInt32 state = - nsCocoaUtils::ConvertToCarbonModifier([aNativeKeyEvent modifierFlags]); - aKeyEvent.mKeyValue = TranslateToChar(nativeKeyCode, state, kbType); - } else { - nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], - aKeyEvent.mKeyValue); - // If the key value is empty, the event may be a dead key event. - // If TranslateToChar() returns non-zero value, that means that - // the key may input a character with different dead key state. - if (aKeyEvent.mKeyValue.IsEmpty()) { - NSUInteger cocoaState = [aNativeKeyEvent modifierFlags]; - UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState); - if (TranslateToChar(nativeKeyCode, carbonState, kbType)) { - aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_Dead; - } - } - } - - // Last resort. If .key value becomes empty string, we should use - // charactersIgnoringModifiers, if it's available. - if (aKeyEvent.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING && - (aKeyEvent.mKeyValue.IsEmpty() || - IsControlChar(aKeyEvent.mKeyValue[0]))) { - nsCocoaUtils::GetStringForNSString( - [aNativeKeyEvent charactersIgnoringModifiers], aKeyEvent.mKeyValue); - // But don't expose it if it's a control character. - if (!aKeyEvent.mKeyValue.IsEmpty() && - IsControlChar(aKeyEvent.mKeyValue[0])) { - aKeyEvent.mKeyValue.Truncate(); - } - } - } else { - // Compute the key for non-printable keys and some special printable keys. - aKeyEvent.mKeyNameIndex = ComputeGeckoKeyNameIndex(nativeKeyCode); - } - - aKeyEvent.mCodeNameIndex = ComputeGeckoCodeNameIndex(nativeKeyCode); - MOZ_ASSERT(aKeyEvent.mCodeNameIndex != CODE_NAME_INDEX_USE_STRING); - - NS_OBJC_END_TRY_ABORT_BLOCK -} - -void -TISInputSourceWrapper::WillDispatchKeyboardEvent( - NSEvent* aNativeKeyEvent, - const nsAString* aInsertString, - WidgetKeyboardEvent& aKeyEvent) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - - // Nothing to do here if the native key event is neither NSKeyDown nor - // NSKeyUp because accessing [aNativeKeyEvent characters] causes throwing - // an exception. - if ([aNativeKeyEvent type] != NSKeyDown && - [aNativeKeyEvent type] != NSKeyUp) { - return; - } - - UInt32 kbType = GetKbdType(); - - if (MOZ_LOG_TEST(gLog, LogLevel::Info)) { - nsAutoString chars; - nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], chars); - NS_ConvertUTF16toUTF8 utf8Chars(chars); - char16_t uniChar = static_cast<char16_t>(aKeyEvent.mCharCode); - MOZ_LOG(gLog, LogLevel::Info, - ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, " - "aNativeKeyEvent=%p, [aNativeKeyEvent characters]=\"%s\", " - "aKeyEvent={ mMessage=%s, mCharCode=0x%X(%s) }, kbType=0x%X, " - "IsOpenedIMEMode()=%s", - this, aNativeKeyEvent, utf8Chars.get(), - GetGeckoKeyEventType(aKeyEvent), aKeyEvent.mCharCode, - uniChar ? NS_ConvertUTF16toUTF8(&uniChar, 1).get() : "", - kbType, TrueOrFalse(IsOpenedIMEMode()))); - } - - nsAutoString insertStringForCharCode; - ComputeInsertStringForCharCode(aNativeKeyEvent, aKeyEvent, aInsertString, - insertStringForCharCode); - - // The mCharCode was set from mKeyValue. However, for example, when Ctrl key - // is pressed, its value should indicate an ASCII character for backward - // compatibility rather than inputting character without the modifiers. - // Therefore, we need to modify mCharCode value here. - uint32_t charCode = - insertStringForCharCode.IsEmpty() ? 0 : insertStringForCharCode[0]; - aKeyEvent.SetCharCode(charCode); - // this is not a special key XXX not used in XP - aKeyEvent.mIsChar = (aKeyEvent.mMessage == eKeyPress); - - MOZ_LOG(gLog, LogLevel::Info, - ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, " - "aKeyEvent.mKeyCode=0x%X, aKeyEvent.mCharCode=0x%X", - this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode)); - - TISInputSourceWrapper USLayout("com.apple.keylayout.US"); - bool isRomanKeyboardLayout = IsASCIICapable(); - - UInt32 key = [aNativeKeyEvent keyCode]; - - // Caps lock and num lock modifier state: - UInt32 lockState = 0; - if ([aNativeKeyEvent modifierFlags] & NSAlphaShiftKeyMask) { - lockState |= alphaLock; - } - if ([aNativeKeyEvent modifierFlags] & NSNumericPadKeyMask) { - lockState |= kEventKeyModifierNumLockMask; - } - - MOZ_LOG(gLog, LogLevel::Info, - ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, " - "isRomanKeyboardLayout=%s, key=0x%X", - this, TrueOrFalse(isRomanKeyboardLayout), kbType, key)); - - nsString str; - - // normal chars - uint32_t unshiftedChar = TranslateToChar(key, lockState, kbType); - UInt32 shiftLockMod = shiftKey | lockState; - uint32_t shiftedChar = TranslateToChar(key, shiftLockMod, kbType); - - // characters generated with Cmd key - // XXX we should remove CapsLock state, which changes characters from - // Latin to Cyrillic with Russian layout on 10.4 only when Cmd key - // is pressed. - UInt32 numState = (lockState & ~alphaLock); // only num lock state - uint32_t uncmdedChar = TranslateToChar(key, numState, kbType); - UInt32 shiftNumMod = numState | shiftKey; - uint32_t uncmdedShiftChar = TranslateToChar(key, shiftNumMod, kbType); - uint32_t uncmdedUSChar = USLayout.TranslateToChar(key, numState, kbType); - UInt32 cmdNumMod = cmdKey | numState; - uint32_t cmdedChar = TranslateToChar(key, cmdNumMod, kbType); - UInt32 cmdShiftNumMod = shiftKey | cmdNumMod; - uint32_t cmdedShiftChar = TranslateToChar(key, cmdShiftNumMod, kbType); - - // Is the keyboard layout changed by Cmd key? - // E.g., Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY. - bool isCmdSwitchLayout = uncmdedChar != cmdedChar; - // Is the keyboard layout for Latin, but Cmd key switches the layout? - // I.e., Dvorak-QWERTY - bool isDvorakQWERTY = isCmdSwitchLayout && isRomanKeyboardLayout; - - // If the current keyboard is not Dvorak-QWERTY or Cmd is not pressed, - // we should append unshiftedChar and shiftedChar for handling the - // normal characters. These are the characters that the user is most - // likely to associate with this key. - if ((unshiftedChar || shiftedChar) && - (!aKeyEvent.IsMeta() || !isDvorakQWERTY)) { - AlternativeCharCode altCharCodes(unshiftedChar, shiftedChar); - aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes); - } - MOZ_LOG(gLog, LogLevel::Info, - ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, " - "aKeyEvent.isMeta=%s, isDvorakQWERTY=%s, " - "unshiftedChar=U+%X, shiftedChar=U+%X", - this, OnOrOff(aKeyEvent.IsMeta()), TrueOrFalse(isDvorakQWERTY), - unshiftedChar, shiftedChar)); - - // Most keyboard layouts provide the same characters in the NSEvents - // with Command+Shift as with Command. However, with Command+Shift we - // want the character on the second level. e.g. With a US QWERTY - // layout, we want "?" when the "/","?" key is pressed with - // Command+Shift. - - // On a German layout, the OS gives us '/' with Cmd+Shift+SS(eszett) - // even though Cmd+SS is 'SS' and Shift+'SS' is '?'. This '/' seems - // like a hack to make the Cmd+"?" event look the same as the Cmd+"?" - // event on a US keyboard. The user thinks they are typing Cmd+"?", so - // we'll prefer the "?" character, replacing mCharCode with shiftedChar - // when Shift is pressed. However, in case there is a layout where the - // character unique to Cmd+Shift is the character that the user expects, - // we'll send it as an alternative char. - bool hasCmdShiftOnlyChar = - cmdedChar != cmdedShiftChar && uncmdedShiftChar != cmdedShiftChar; - uint32_t originalCmdedShiftChar = cmdedShiftChar; - - // If we can make a good guess at the characters that the user would - // expect this key combination to produce (with and without Shift) then - // use those characters. This also corrects for CapsLock, which was - // ignored above. - if (!isCmdSwitchLayout) { - // The characters produced with Command seem similar to those without - // Command. - if (unshiftedChar) { - cmdedChar = unshiftedChar; - } - if (shiftedChar) { - cmdedShiftChar = shiftedChar; - } - } else if (uncmdedUSChar == cmdedChar) { - // It looks like characters from a US layout are provided when Command - // is down. - uint32_t ch = USLayout.TranslateToChar(key, lockState, kbType); - if (ch) { - cmdedChar = ch; - } - ch = USLayout.TranslateToChar(key, shiftLockMod, kbType); - if (ch) { - cmdedShiftChar = ch; - } - } - - // If the current keyboard layout is switched by the Cmd key, - // we should append cmdedChar and shiftedCmdChar that are - // Latin char for the key. - // If the keyboard layout is Dvorak-QWERTY, we should append them only when - // command key is pressed because when command key isn't pressed, uncmded - // chars have been appended already. - if ((cmdedChar || cmdedShiftChar) && isCmdSwitchLayout && - (aKeyEvent.IsMeta() || !isDvorakQWERTY)) { - AlternativeCharCode altCharCodes(cmdedChar, cmdedShiftChar); - aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes); - } - MOZ_LOG(gLog, LogLevel::Info, - ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, " - "hasCmdShiftOnlyChar=%s, isCmdSwitchLayout=%s, isDvorakQWERTY=%s, " - "cmdedChar=U+%X, cmdedShiftChar=U+%X", - this, TrueOrFalse(hasCmdShiftOnlyChar), TrueOrFalse(isDvorakQWERTY), - TrueOrFalse(isDvorakQWERTY), cmdedChar, cmdedShiftChar)); - // Special case for 'SS' key of German layout. See the comment of - // hasCmdShiftOnlyChar definition for the detail. - if (hasCmdShiftOnlyChar && originalCmdedShiftChar) { - AlternativeCharCode altCharCodes(0, originalCmdedShiftChar); - aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes); - } - MOZ_LOG(gLog, LogLevel::Info, - ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, " - "hasCmdShiftOnlyChar=%s, originalCmdedShiftChar=U+%X", - this, TrueOrFalse(hasCmdShiftOnlyChar), originalCmdedShiftChar)); - - NS_OBJC_END_TRY_ABORT_BLOCK -} - -uint32_t -TISInputSourceWrapper::ComputeGeckoKeyCode(UInt32 aNativeKeyCode, - UInt32 aKbType, - bool aCmdIsPressed) -{ - MOZ_LOG(gLog, LogLevel::Info, - ("%p TISInputSourceWrapper::ComputeGeckoKeyCode, aNativeKeyCode=0x%X, " - "aKbType=0x%X, aCmdIsPressed=%s, IsOpenedIMEMode()=%s, " - "IsASCIICapable()=%s", - this, aNativeKeyCode, aKbType, TrueOrFalse(aCmdIsPressed), - TrueOrFalse(IsOpenedIMEMode()), TrueOrFalse(IsASCIICapable()))); - - switch (aNativeKeyCode) { - case kVK_Space: return NS_VK_SPACE; - case kVK_Escape: return NS_VK_ESCAPE; - - // modifiers - case kVK_RightCommand: - case kVK_Command: return NS_VK_META; - case kVK_RightShift: - case kVK_Shift: return NS_VK_SHIFT; - case kVK_CapsLock: return NS_VK_CAPS_LOCK; - case kVK_RightControl: - case kVK_Control: return NS_VK_CONTROL; - case kVK_RightOption: - case kVK_Option: return NS_VK_ALT; - - case kVK_ANSI_KeypadClear: return NS_VK_CLEAR; - - // function keys - case kVK_F1: return NS_VK_F1; - case kVK_F2: return NS_VK_F2; - case kVK_F3: return NS_VK_F3; - case kVK_F4: return NS_VK_F4; - case kVK_F5: return NS_VK_F5; - case kVK_F6: return NS_VK_F6; - case kVK_F7: return NS_VK_F7; - case kVK_F8: return NS_VK_F8; - case kVK_F9: return NS_VK_F9; - case kVK_F10: return NS_VK_F10; - case kVK_F11: return NS_VK_F11; - case kVK_F12: return NS_VK_F12; - // case kVK_F13: return NS_VK_F13; // clash with the 3 below - // case kVK_F14: return NS_VK_F14; - // case kVK_F15: return NS_VK_F15; - case kVK_F16: return NS_VK_F16; - case kVK_F17: return NS_VK_F17; - case kVK_F18: return NS_VK_F18; - case kVK_F19: return NS_VK_F19; - - case kVK_PC_Pause: return NS_VK_PAUSE; - case kVK_PC_ScrollLock: return NS_VK_SCROLL_LOCK; - case kVK_PC_PrintScreen: return NS_VK_PRINTSCREEN; - - // keypad - case kVK_ANSI_Keypad0: return NS_VK_NUMPAD0; - case kVK_ANSI_Keypad1: return NS_VK_NUMPAD1; - case kVK_ANSI_Keypad2: return NS_VK_NUMPAD2; - case kVK_ANSI_Keypad3: return NS_VK_NUMPAD3; - case kVK_ANSI_Keypad4: return NS_VK_NUMPAD4; - case kVK_ANSI_Keypad5: return NS_VK_NUMPAD5; - case kVK_ANSI_Keypad6: return NS_VK_NUMPAD6; - case kVK_ANSI_Keypad7: return NS_VK_NUMPAD7; - case kVK_ANSI_Keypad8: return NS_VK_NUMPAD8; - case kVK_ANSI_Keypad9: return NS_VK_NUMPAD9; - - case kVK_ANSI_KeypadMultiply: return NS_VK_MULTIPLY; - case kVK_ANSI_KeypadPlus: return NS_VK_ADD; - case kVK_ANSI_KeypadMinus: return NS_VK_SUBTRACT; - case kVK_ANSI_KeypadDecimal: return NS_VK_DECIMAL; - case kVK_ANSI_KeypadDivide: return NS_VK_DIVIDE; - - case kVK_JIS_KeypadComma: return NS_VK_SEPARATOR; - - // IME keys - case kVK_JIS_Eisu: return NS_VK_EISU; - case kVK_JIS_Kana: return NS_VK_KANA; - - // these may clash with forward delete and help - case kVK_PC_Insert: return NS_VK_INSERT; - case kVK_PC_Delete: return NS_VK_DELETE; - - case kVK_PC_Backspace: return NS_VK_BACK; - case kVK_Tab: return NS_VK_TAB; - - case kVK_Home: return NS_VK_HOME; - case kVK_End: return NS_VK_END; - - case kVK_PageUp: return NS_VK_PAGE_UP; - case kVK_PageDown: return NS_VK_PAGE_DOWN; - - case kVK_LeftArrow: return NS_VK_LEFT; - case kVK_RightArrow: return NS_VK_RIGHT; - case kVK_UpArrow: return NS_VK_UP; - case kVK_DownArrow: return NS_VK_DOWN; - - case kVK_PC_ContextMenu: return NS_VK_CONTEXT_MENU; - - case kVK_ANSI_1: return NS_VK_1; - case kVK_ANSI_2: return NS_VK_2; - case kVK_ANSI_3: return NS_VK_3; - case kVK_ANSI_4: return NS_VK_4; - case kVK_ANSI_5: return NS_VK_5; - case kVK_ANSI_6: return NS_VK_6; - case kVK_ANSI_7: return NS_VK_7; - case kVK_ANSI_8: return NS_VK_8; - case kVK_ANSI_9: return NS_VK_9; - case kVK_ANSI_0: return NS_VK_0; - - case kVK_ANSI_KeypadEnter: - case kVK_Return: - case kVK_Powerbook_KeypadEnter: return NS_VK_RETURN; - } - - // If Cmd key is pressed, that causes switching keyboard layout temporarily. - // E.g., Dvorak-QWERTY. Therefore, if Cmd key is pressed, we should honor it. - UInt32 modifiers = aCmdIsPressed ? cmdKey : 0; - - uint32_t charCode = TranslateToChar(aNativeKeyCode, modifiers, aKbType); - - // Special case for Mac. Mac inputs Yen sign (U+00A5) directly instead of - // Back slash (U+005C). We should return NS_VK_BACK_SLASH for compatibility - // with other platforms. - // XXX How about Won sign (U+20A9) which has same problem as Yen sign? - if (charCode == 0x00A5) { - return NS_VK_BACK_SLASH; - } - - uint32_t keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode); - if (keyCode) { - return keyCode; - } - - // If the unshifed char isn't an ASCII character, use shifted char. - charCode = TranslateToChar(aNativeKeyCode, modifiers | shiftKey, aKbType); - keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode); - if (keyCode) { - return keyCode; - } - - // If this is ASCII capable, give up to compute it. - if (IsASCIICapable()) { - return 0; - } - - // Retry with ASCII capable keyboard layout. - TISInputSourceWrapper currentKeyboardLayout; - currentKeyboardLayout.InitByCurrentASCIICapableKeyboardLayout(); - NS_ENSURE_TRUE(mInputSource != currentKeyboardLayout.mInputSource, 0); - keyCode = currentKeyboardLayout.ComputeGeckoKeyCode(aNativeKeyCode, aKbType, - aCmdIsPressed); - - // However, if keyCode isn't for an alphabet keys or a numeric key, we should - // ignore it. For example, comma key of Thai layout is same as close-square- - // bracket key of US layout and an unicode character key of Thai layout is - // same as comma key of US layout. If we return NS_VK_COMMA for latter key, - // web application developers cannot distinguish with the former key. - return ((keyCode >= NS_VK_A && keyCode <= NS_VK_Z) || - (keyCode >= NS_VK_0 && keyCode <= NS_VK_9)) ? keyCode : 0; -} - -// static -KeyNameIndex -TISInputSourceWrapper::ComputeGeckoKeyNameIndex(UInt32 aNativeKeyCode) -{ - // NOTE: - // When unsupported keys like Convert, Nonconvert of Japanese keyboard is - // pressed: - // on 10.6.x, 'A' key event is fired (and also actually 'a' is inserted). - // on 10.7.x, Nothing happens. - // on 10.8.x, Nothing happens. - // on 10.9.x, FlagsChanged event is fired with keyCode 0xFF. - switch (aNativeKeyCode) { - -#define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \ - case aNativeKey: return aKeyNameIndex; - -#include "NativeKeyToDOMKeyName.h" - -#undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX - - default: - return KEY_NAME_INDEX_Unidentified; - } -} - -// static -CodeNameIndex -TISInputSourceWrapper::ComputeGeckoCodeNameIndex(UInt32 aNativeKeyCode) -{ - switch (aNativeKeyCode) { - -#define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \ - case aNativeKey: return aCodeNameIndex; - -#include "NativeKeyToDOMCodeName.h" - -#undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX - - default: - return CODE_NAME_INDEX_UNKNOWN; - } -} - - -#pragma mark - - - -/****************************************************************************** - * - * TextInputHandler implementation (static methods) - * - ******************************************************************************/ - -NSUInteger TextInputHandler::sLastModifierState = 0; - -// static -CFArrayRef -TextInputHandler::CreateAllKeyboardLayoutList() -{ - const void* keys[] = { kTISPropertyInputSourceType }; - const void* values[] = { kTISTypeKeyboardLayout }; - CFDictionaryRef filter = - ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL); - NS_ASSERTION(filter, "failed to create the filter"); - CFArrayRef list = ::TISCreateInputSourceList(filter, true); - ::CFRelease(filter); - return list; -} - -// static -void -TextInputHandler::DebugPrintAllKeyboardLayouts() -{ - if (MOZ_LOG_TEST(gLog, LogLevel::Info)) { - CFArrayRef list = CreateAllKeyboardLayoutList(); - MOZ_LOG(gLog, LogLevel::Info, ("Keyboard layout configuration:")); - CFIndex idx = ::CFArrayGetCount(list); - TISInputSourceWrapper tis; - for (CFIndex i = 0; i < idx; ++i) { - TISInputSourceRef inputSource = static_cast<TISInputSourceRef>( - const_cast<void *>(::CFArrayGetValueAtIndex(list, i))); - tis.InitByTISInputSourceRef(inputSource); - nsAutoString name, isid; - tis.GetLocalizedName(name); - tis.GetInputSourceID(isid); - MOZ_LOG(gLog, LogLevel::Info, - (" %s\t<%s>%s%s\n", - NS_ConvertUTF16toUTF8(name).get(), - NS_ConvertUTF16toUTF8(isid).get(), - tis.IsASCIICapable() ? "" : "\t(Isn't ASCII capable)", - tis.IsKeyboardLayout() && tis.GetUCKeyboardLayout() ? - "" : "\t(uchr is NOT AVAILABLE)")); - } - ::CFRelease(list); - } -} - - -#pragma mark - - - -/****************************************************************************** - * - * TextInputHandler implementation - * - ******************************************************************************/ - -TextInputHandler::TextInputHandler(nsChildView* aWidget, - NSView<mozView> *aNativeView) : - IMEInputHandler(aWidget, aNativeView) -{ - EnsureToLogAllKeyboardLayoutsAndIMEs(); - [mView installTextInputHandler:this]; -} - -TextInputHandler::~TextInputHandler() -{ - [mView uninstallTextInputHandler]; -} - -bool -TextInputHandler::HandleKeyDownEvent(NSEvent* aNativeEvent) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; - - if (Destroyed()) { - MOZ_LOG(gLog, LogLevel::Info, - ("%p TextInputHandler::HandleKeyDownEvent, " - "widget has been already destroyed", this)); - return false; - } - - // Insert empty line to the log for easier to read. - MOZ_LOG(gLog, LogLevel::Info, ("")); - MOZ_LOG(gLog, LogLevel::Info, - ("%p TextInputHandler::HandleKeyDownEvent, aNativeEvent=%p, " - "type=%s, keyCode=%lld (0x%X), modifierFlags=0x%X, characters=\"%s\", " - "charactersIgnoringModifiers=\"%s\"", - this, aNativeEvent, GetNativeKeyEventType(aNativeEvent), - [aNativeEvent keyCode], [aNativeEvent keyCode], - [aNativeEvent modifierFlags], GetCharacters([aNativeEvent characters]), - GetCharacters([aNativeEvent charactersIgnoringModifiers]))); - - // Except when Command key is pressed, we should hide mouse cursor until - // next mousemove. Handling here means that: - // - Don't hide mouse cursor at pressing modifier key - // - Hide mouse cursor even if the key event will be handled by IME (i.e., - // even without dispatching eKeyPress events) - // - Hide mouse cursor even when a plugin has focus - if (!([aNativeEvent modifierFlags] & NSCommandKeyMask)) { - [NSCursor setHiddenUntilMouseMoves:YES]; - } - - RefPtr<nsChildView> widget(mWidget); - - KeyEventState* currentKeyEvent = PushKeyEvent(aNativeEvent); - AutoKeyEventStateCleaner remover(this); - - ComplexTextInputPanel* ctiPanel = ComplexTextInputPanel::GetSharedComplexTextInputPanel(); - if (ctiPanel && ctiPanel->IsInComposition()) { - nsAutoString committed; - ctiPanel->InterpretKeyEvent(aNativeEvent, committed); - if (!committed.IsEmpty()) { - nsresult rv = mDispatcher->BeginNativeInputTransaction(); - if (NS_WARN_IF(NS_FAILED(rv))) { - MOZ_LOG(gLog, LogLevel::Error, - ("%p IMEInputHandler::HandleKeyDownEvent, " - "FAILED, due to BeginNativeInputTransaction() failure " - "at dispatching keydown for ComplexTextInputPanel", this)); - return false; - } - - WidgetKeyboardEvent imeEvent(true, eKeyDown, widget); - currentKeyEvent->InitKeyEvent(this, imeEvent); - imeEvent.mPluginTextEventString.Assign(committed); - nsEventStatus status = nsEventStatus_eIgnore; - mDispatcher->DispatchKeyboardEvent(eKeyDown, imeEvent, status, - currentKeyEvent); - } - return true; - } - - NSResponder* firstResponder = [[mView window] firstResponder]; - - nsresult rv = mDispatcher->BeginNativeInputTransaction(); - if (NS_WARN_IF(NS_FAILED(rv))) { - MOZ_LOG(gLog, LogLevel::Error, - ("%p IMEInputHandler::HandleKeyDownEvent, " - "FAILED, due to BeginNativeInputTransaction() failure " - "at dispatching keydown for ordinal cases", this)); - return false; - } - - WidgetKeyboardEvent keydownEvent(true, eKeyDown, widget); - currentKeyEvent->InitKeyEvent(this, keydownEvent); - - nsEventStatus status = nsEventStatus_eIgnore; - mDispatcher->DispatchKeyboardEvent(eKeyDown, keydownEvent, status, - currentKeyEvent); - currentKeyEvent->mKeyDownHandled = - (status == nsEventStatus_eConsumeNoDefault); - - if (Destroyed()) { - MOZ_LOG(gLog, LogLevel::Info, - ("%p TextInputHandler::HandleKeyDownEvent, " - "widget was destroyed by keydown event", this)); - return currentKeyEvent->IsDefaultPrevented(); - } - - // The key down event may have shifted the focus, in which - // case we should not fire the key press. - // XXX This is a special code only on Cocoa widget, why is this needed? - if (firstResponder != [[mView window] firstResponder]) { - MOZ_LOG(gLog, LogLevel::Info, - ("%p TextInputHandler::HandleKeyDownEvent, " - "view lost focus by keydown event", this)); - return currentKeyEvent->IsDefaultPrevented(); - } - - if (currentKeyEvent->IsDefaultPrevented()) { - MOZ_LOG(gLog, LogLevel::Info, - ("%p TextInputHandler::HandleKeyDownEvent, " - "keydown event's default is prevented", this)); - return true; - } - - // Let Cocoa interpret the key events, caching IsIMEComposing first. - bool wasComposing = IsIMEComposing(); - bool interpretKeyEventsCalled = false; - // Don't call interpretKeyEvents when a plugin has focus. If we call it, - // for example, a character is inputted twice during a composition in e10s - // mode. - if (!widget->IsPluginFocused() && (IsIMEEnabled() || IsASCIICapableOnly())) { - MOZ_LOG(gLog, LogLevel::Info, - ("%p TextInputHandler::HandleKeyDownEvent, calling interpretKeyEvents", - this)); - [mView interpretKeyEvents:[NSArray arrayWithObject:aNativeEvent]]; - interpretKeyEventsCalled = true; - MOZ_LOG(gLog, LogLevel::Info, - ("%p TextInputHandler::HandleKeyDownEvent, called interpretKeyEvents", - this)); - } - - if (Destroyed()) { - MOZ_LOG(gLog, LogLevel::Info, - ("%p TextInputHandler::HandleKeyDownEvent, widget was destroyed", - this)); - return currentKeyEvent->IsDefaultPrevented(); - } - - MOZ_LOG(gLog, LogLevel::Info, - ("%p TextInputHandler::HandleKeyDownEvent, wasComposing=%s, " - "IsIMEComposing()=%s", - this, TrueOrFalse(wasComposing), TrueOrFalse(IsIMEComposing()))); - - if (currentKeyEvent->CanDispatchKeyPressEvent() && - !wasComposing && !IsIMEComposing()) { - rv = mDispatcher->BeginNativeInputTransaction(); - if (NS_WARN_IF(NS_FAILED(rv))) { - MOZ_LOG(gLog, LogLevel::Error, - ("%p IMEInputHandler::HandleKeyDownEvent, " - "FAILED, due to BeginNativeInputTransaction() failure " - "at dispatching keypress", this)); - return false; - } - - WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget); - currentKeyEvent->InitKeyEvent(this, keypressEvent); - - // If we called interpretKeyEvents and this isn't normal character input - // then IME probably ate the event for some reason. We do not want to - // send a key press event in that case. - // TODO: - // There are some other cases which IME eats the current event. - // 1. If key events were nested during calling interpretKeyEvents, it means - // that IME did something. Then, we should do nothing. - // 2. If one or more commands are called like "deleteBackward", we should - // dispatch keypress event at that time. Note that the command may have - // been a converted or generated action by IME. Then, we shouldn't do - // our default action for this key. - if (!(interpretKeyEventsCalled && - IsNormalCharInputtingEvent(keypressEvent))) { - currentKeyEvent->mKeyPressDispatched = - mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status, - currentKeyEvent); - currentKeyEvent->mKeyPressHandled = - (status == nsEventStatus_eConsumeNoDefault); - currentKeyEvent->mKeyPressDispatched = true; - MOZ_LOG(gLog, LogLevel::Info, - ("%p TextInputHandler::HandleKeyDownEvent, keypress event dispatched", - this)); - } - } - - // Note: mWidget might have become null here. Don't count on it from here on. - - MOZ_LOG(gLog, LogLevel::Info, - ("%p TextInputHandler::HandleKeyDownEvent, " - "keydown handled=%s, keypress handled=%s, causedOtherKeyEvents=%s, " - "compositionDispatched=%s", - this, TrueOrFalse(currentKeyEvent->mKeyDownHandled), - TrueOrFalse(currentKeyEvent->mKeyPressHandled), - TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents), - TrueOrFalse(currentKeyEvent->mCompositionDispatched))); - // Insert empty line to the log for easier to read. - MOZ_LOG(gLog, LogLevel::Info, ("")); - return currentKeyEvent->IsDefaultPrevented(); - - NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false); -} - -void -TextInputHandler::HandleKeyUpEvent(NSEvent* aNativeEvent) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - - MOZ_LOG(gLog, LogLevel::Info, - ("%p TextInputHandler::HandleKeyUpEvent, aNativeEvent=%p, " - "type=%s, keyCode=%lld (0x%X), modifierFlags=0x%X, characters=\"%s\", " - "charactersIgnoringModifiers=\"%s\", " - "IsIMEComposing()=%s", - this, aNativeEvent, GetNativeKeyEventType(aNativeEvent), - [aNativeEvent keyCode], [aNativeEvent keyCode], - [aNativeEvent modifierFlags], GetCharacters([aNativeEvent characters]), - GetCharacters([aNativeEvent charactersIgnoringModifiers]), - TrueOrFalse(IsIMEComposing()))); - - if (Destroyed()) { - MOZ_LOG(gLog, LogLevel::Info, - ("%p TextInputHandler::HandleKeyUpEvent, " - "widget has been already destroyed", this)); - return; - } - - nsresult rv = mDispatcher->BeginNativeInputTransaction(); - if (NS_WARN_IF(NS_FAILED(rv))) { - MOZ_LOG(gLog, LogLevel::Error, - ("%p IMEInputHandler::HandleKeyUpEvent, " - "FAILED, due to BeginNativeInputTransaction() failure", this)); - return; - } - - WidgetKeyboardEvent keyupEvent(true, eKeyUp, mWidget); - InitKeyEvent(aNativeEvent, keyupEvent); - - KeyEventState currentKeyEvent(aNativeEvent); - nsEventStatus status = nsEventStatus_eIgnore; - mDispatcher->DispatchKeyboardEvent(eKeyUp, keyupEvent, status, - ¤tKeyEvent); - - NS_OBJC_END_TRY_ABORT_BLOCK; -} - -void -TextInputHandler::HandleFlagsChanged(NSEvent* aNativeEvent) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - - if (Destroyed()) { - MOZ_LOG(gLog, LogLevel::Info, - ("%p TextInputHandler::HandleFlagsChanged, " - "widget has been already destroyed", this)); - return; - } - - RefPtr<nsChildView> kungFuDeathGrip(mWidget); - mozilla::Unused << kungFuDeathGrip; // Not referenced within this function - - MOZ_LOG(gLog, LogLevel::Info, - ("%p TextInputHandler::HandleFlagsChanged, aNativeEvent=%p, " - "type=%s, keyCode=%s (0x%X), modifierFlags=0x%08X, " - "sLastModifierState=0x%08X, IsIMEComposing()=%s", - this, aNativeEvent, GetNativeKeyEventType(aNativeEvent), - GetKeyNameForNativeKeyCode([aNativeEvent keyCode]), [aNativeEvent keyCode], - [aNativeEvent modifierFlags], sLastModifierState, - TrueOrFalse(IsIMEComposing()))); - - MOZ_ASSERT([aNativeEvent type] == NSFlagsChanged); - - NSUInteger diff = [aNativeEvent modifierFlags] ^ sLastModifierState; - // Device dependent flags for left-control key, both shift keys, both command - // keys and both option keys have been defined in Next's SDK. But we - // shouldn't use it directly as far as possible since Cocoa SDK doesn't - // define them. Fortunately, we need them only when we dispatch keyup - // events. So, we can usually know the actual relation between keyCode and - // device dependent flags. However, we need to remove following flags first - // since the differences don't indicate modifier key state. - // NX_STYLUSPROXIMITYMASK: Probably used for pen like device. - // kCGEventFlagMaskNonCoalesced (= NX_NONCOALSESCEDMASK): See the document for - // Quartz Event Services. - diff &= ~(NX_STYLUSPROXIMITYMASK | kCGEventFlagMaskNonCoalesced); - - switch ([aNativeEvent keyCode]) { - // CapsLock state and other modifier states are different: - // CapsLock state does not revert when the CapsLock key goes up, as the - // modifier state does for other modifier keys on key up. - case kVK_CapsLock: { - // Fire key down event for caps lock. - DispatchKeyEventForFlagsChanged(aNativeEvent, true); - // XXX should we fire keyup event too? The keyup event for CapsLock key - // is never dispatched on Gecko. - // XXX WebKit dispatches keydown event when CapsLock is locked, otherwise, - // keyup event. If we do so, we cannot keep the consistency with other - // platform's behavior... - break; - } - - // If the event is caused by pressing or releasing a modifier key, just - // dispatch the key's event. - case kVK_Shift: - case kVK_RightShift: - case kVK_Command: - case kVK_RightCommand: - case kVK_Control: - case kVK_RightControl: - case kVK_Option: - case kVK_RightOption: - case kVK_Help: { - // We assume that at most one modifier is changed per event if the event - // is caused by pressing or releasing a modifier key. - bool isKeyDown = ([aNativeEvent modifierFlags] & diff) != 0; - DispatchKeyEventForFlagsChanged(aNativeEvent, isKeyDown); - // XXX Some applications might send the event with incorrect device- - // dependent flags. - if (isKeyDown && ((diff & ~NSDeviceIndependentModifierFlagsMask) != 0)) { - unsigned short keyCode = [aNativeEvent keyCode]; - const ModifierKey* modifierKey = - GetModifierKeyForDeviceDependentFlags(diff); - if (modifierKey && modifierKey->keyCode != keyCode) { - // Although, we're not sure the actual cause of this case, the stored - // modifier information and the latest key event information may be - // mismatched. Then, let's reset the stored information. - // NOTE: If this happens, it may fail to handle NSFlagsChanged event - // in the default case (below). However, it's the rare case handler - // and this case occurs rarely. So, we can ignore the edge case bug. - NS_WARNING("Resetting stored modifier key information"); - mModifierKeys.Clear(); - modifierKey = nullptr; - } - if (!modifierKey) { - mModifierKeys.AppendElement(ModifierKey(diff, keyCode)); - } - } - break; - } - - // Currently we don't support Fn key since other browsers don't dispatch - // events for it and we don't have keyCode for this key. - // It should be supported when we implement .key and .char. - case kVK_Function: - break; - - // If the event is caused by something else than pressing or releasing a - // single modifier key (for example by the app having been deactivated - // using command-tab), use the modifiers themselves to determine which - // key's event to dispatch, and whether it's a keyup or keydown event. - // In all cases we assume one or more modifiers are being deactivated - // (never activated) -- otherwise we'd have received one or more events - // corresponding to a single modifier key being pressed. - default: { - NSUInteger modifiers = sLastModifierState; - for (int32_t bit = 0; bit < 32; ++bit) { - NSUInteger flag = 1 << bit; - if (!(diff & flag)) { - continue; - } - - // Given correct information from the application, a flag change here - // will normally be a deactivation (except for some lockable modifiers - // such as CapsLock). But some applications (like VNC) can send an - // activating event with a zero keyCode. So we need to check for that - // here. - bool dispatchKeyDown = ((flag & [aNativeEvent modifierFlags]) != 0); - - unsigned short keyCode = 0; - if (flag & NSDeviceIndependentModifierFlagsMask) { - switch (flag) { - case NSAlphaShiftKeyMask: - keyCode = kVK_CapsLock; - dispatchKeyDown = true; - break; - - case NSNumericPadKeyMask: - // NSNumericPadKeyMask is fired by VNC a lot. But not all of - // these events can really be Clear key events, so we just ignore - // them. - continue; - - case NSHelpKeyMask: - keyCode = kVK_Help; - break; - - case NSFunctionKeyMask: - // An NSFunctionKeyMask change here will normally be a - // deactivation. But sometimes it will be an activation send (by - // VNC for example) with a zero keyCode. - continue; - - // These cases (NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask - // and NSCommandKeyMask) should be handled by the other branch of - // the if statement, below (which handles device dependent flags). - // However, some applications (like VNC) can send key events without - // any device dependent flags, so we handle them here instead. - case NSShiftKeyMask: - keyCode = (modifiers & 0x0004) ? kVK_RightShift : kVK_Shift; - break; - case NSControlKeyMask: - keyCode = (modifiers & 0x2000) ? kVK_RightControl : kVK_Control; - break; - case NSAlternateKeyMask: - keyCode = (modifiers & 0x0040) ? kVK_RightOption : kVK_Option; - break; - case NSCommandKeyMask: - keyCode = (modifiers & 0x0010) ? kVK_RightCommand : kVK_Command; - break; - - default: - continue; - } - } else { - const ModifierKey* modifierKey = - GetModifierKeyForDeviceDependentFlags(flag); - if (!modifierKey) { - // See the note above (in the other branch of the if statement) - // about the NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask - // and NSCommandKeyMask cases. - continue; - } - keyCode = modifierKey->keyCode; - } - - // Remove flags - modifiers &= ~flag; - switch (keyCode) { - case kVK_Shift: { - const ModifierKey* modifierKey = - GetModifierKeyForNativeKeyCode(kVK_RightShift); - if (!modifierKey || - !(modifiers & modifierKey->GetDeviceDependentFlags())) { - modifiers &= ~NSShiftKeyMask; - } - break; - } - case kVK_RightShift: { - const ModifierKey* modifierKey = - GetModifierKeyForNativeKeyCode(kVK_Shift); - if (!modifierKey || - !(modifiers & modifierKey->GetDeviceDependentFlags())) { - modifiers &= ~NSShiftKeyMask; - } - break; - } - case kVK_Command: { - const ModifierKey* modifierKey = - GetModifierKeyForNativeKeyCode(kVK_RightCommand); - if (!modifierKey || - !(modifiers & modifierKey->GetDeviceDependentFlags())) { - modifiers &= ~NSCommandKeyMask; - } - break; - } - case kVK_RightCommand: { - const ModifierKey* modifierKey = - GetModifierKeyForNativeKeyCode(kVK_Command); - if (!modifierKey || - !(modifiers & modifierKey->GetDeviceDependentFlags())) { - modifiers &= ~NSCommandKeyMask; - } - break; - } - case kVK_Control: { - const ModifierKey* modifierKey = - GetModifierKeyForNativeKeyCode(kVK_RightControl); - if (!modifierKey || - !(modifiers & modifierKey->GetDeviceDependentFlags())) { - modifiers &= ~NSControlKeyMask; - } - break; - } - case kVK_RightControl: { - const ModifierKey* modifierKey = - GetModifierKeyForNativeKeyCode(kVK_Control); - if (!modifierKey || - !(modifiers & modifierKey->GetDeviceDependentFlags())) { - modifiers &= ~NSControlKeyMask; - } - break; - } - case kVK_Option: { - const ModifierKey* modifierKey = - GetModifierKeyForNativeKeyCode(kVK_RightOption); - if (!modifierKey || - !(modifiers & modifierKey->GetDeviceDependentFlags())) { - modifiers &= ~NSAlternateKeyMask; - } - break; - } - case kVK_RightOption: { - const ModifierKey* modifierKey = - GetModifierKeyForNativeKeyCode(kVK_Option); - if (!modifierKey || - !(modifiers & modifierKey->GetDeviceDependentFlags())) { - modifiers &= ~NSAlternateKeyMask; - } - break; - } - case kVK_Help: - modifiers &= ~NSHelpKeyMask; - break; - default: - break; - } - - NSEvent* event = - [NSEvent keyEventWithType:NSFlagsChanged - location:[aNativeEvent locationInWindow] - modifierFlags:modifiers - timestamp:[aNativeEvent timestamp] - windowNumber:[aNativeEvent windowNumber] - context:[aNativeEvent context] - characters:@"" - charactersIgnoringModifiers:@"" - isARepeat:NO - keyCode:keyCode]; - DispatchKeyEventForFlagsChanged(event, dispatchKeyDown); - if (Destroyed()) { - break; - } - - // Stop if focus has changed. - // Check to see if mView is still the first responder. - if (![mView isFirstResponder]) { - break; - } - - } - break; - } - } - - // Be aware, the widget may have been destroyed. - sLastModifierState = [aNativeEvent modifierFlags]; - - NS_OBJC_END_TRY_ABORT_BLOCK; -} - -const TextInputHandler::ModifierKey* -TextInputHandler::GetModifierKeyForNativeKeyCode(unsigned short aKeyCode) const -{ - for (ModifierKeyArray::index_type i = 0; i < mModifierKeys.Length(); ++i) { - if (mModifierKeys[i].keyCode == aKeyCode) { - return &((ModifierKey&)mModifierKeys[i]); - } - } - return nullptr; -} - -const TextInputHandler::ModifierKey* -TextInputHandler::GetModifierKeyForDeviceDependentFlags(NSUInteger aFlags) const -{ - for (ModifierKeyArray::index_type i = 0; i < mModifierKeys.Length(); ++i) { - if (mModifierKeys[i].GetDeviceDependentFlags() == - (aFlags & ~NSDeviceIndependentModifierFlagsMask)) { - return &((ModifierKey&)mModifierKeys[i]); - } - } - return nullptr; -} - -void -TextInputHandler::DispatchKeyEventForFlagsChanged(NSEvent* aNativeEvent, - bool aDispatchKeyDown) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - - if (Destroyed()) { - return; - } - - MOZ_LOG(gLog, LogLevel::Info, - ("%p TextInputHandler::DispatchKeyEventForFlagsChanged, aNativeEvent=%p, " - "type=%s, keyCode=%s (0x%X), aDispatchKeyDown=%s, IsIMEComposing()=%s", - this, aNativeEvent, GetNativeKeyEventType(aNativeEvent), - GetKeyNameForNativeKeyCode([aNativeEvent keyCode]), [aNativeEvent keyCode], - TrueOrFalse(aDispatchKeyDown), TrueOrFalse(IsIMEComposing()))); - - if ([aNativeEvent type] != NSFlagsChanged || IsIMEComposing()) { - return; - } - - nsresult rv = mDispatcher->BeginNativeInputTransaction(); - if (NS_WARN_IF(NS_FAILED(rv))) { - MOZ_LOG(gLog, LogLevel::Error, - ("%p IMEInputHandler::DispatchKeyEventForFlagsChanged, " - "FAILED, due to BeginNativeInputTransaction() failure", this)); - return; - } - - EventMessage message = aDispatchKeyDown ? eKeyDown : eKeyUp; - - // Fire a key event. - WidgetKeyboardEvent keyEvent(true, message, mWidget); - InitKeyEvent(aNativeEvent, keyEvent); - - // Attach a plugin event, in case keyEvent gets dispatched to a plugin. Only - // one field is needed -- the type. The other fields can be constructed as - // the need arises. But Gecko doesn't have anything equivalent to the - // NPCocoaEventFlagsChanged type, and this needs to be passed accurately to - // any plugin to which this event is sent. - NPCocoaEvent cocoaEvent; - nsCocoaUtils::InitNPCocoaEvent(&cocoaEvent); - cocoaEvent.type = NPCocoaEventFlagsChanged; - keyEvent.mPluginEvent.Copy(cocoaEvent); - - KeyEventState currentKeyEvent(aNativeEvent); - nsEventStatus status = nsEventStatus_eIgnore; - mDispatcher->DispatchKeyboardEvent(message, keyEvent, status, - ¤tKeyEvent); - - NS_OBJC_END_TRY_ABORT_BLOCK; -} - -void -TextInputHandler::InsertText(NSAttributedString* aAttrString, - NSRange* aReplacementRange) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - - if (Destroyed()) { - return; - } - - KeyEventState* currentKeyEvent = GetCurrentKeyEvent(); - - MOZ_LOG(gLog, LogLevel::Info, - ("%p TextInputHandler::InsertText, aAttrString=\"%s\", " - "aReplacementRange=%p { location=%llu, length=%llu }, " - "IsIMEComposing()=%s, IgnoreIMEComposition()=%s, " - "keyevent=%p, keydownHandled=%s, keypressDispatched=%s, " - "causedOtherKeyEvents=%s, compositionDispatched=%s", - this, GetCharacters([aAttrString string]), aReplacementRange, - aReplacementRange ? aReplacementRange->location : 0, - aReplacementRange ? aReplacementRange->length : 0, - TrueOrFalse(IsIMEComposing()), TrueOrFalse(IgnoreIMEComposition()), - currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr, - currentKeyEvent ? - TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A", - currentKeyEvent ? - TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A", - currentKeyEvent ? - TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A", - currentKeyEvent ? - TrueOrFalse(currentKeyEvent->mCompositionDispatched) : "N/A")); - - if (IgnoreIMEComposition()) { - return; - } - - InputContext context = mWidget->GetInputContext(); - bool isEditable = (context.mIMEState.mEnabled == IMEState::ENABLED || - context.mIMEState.mEnabled == IMEState::PASSWORD); - NSRange selectedRange = SelectedRange(); - - nsAutoString str; - nsCocoaUtils::GetStringForNSString([aAttrString string], str); - - AutoInsertStringClearer clearer(currentKeyEvent); - if (currentKeyEvent) { - currentKeyEvent->mInsertString = &str; - } - - if (!IsIMEComposing() && str.IsEmpty()) { - // nothing to do if there is no content which can be removed. - if (!isEditable) { - return; - } - // If replacement range is specified, we need to remove the range. - // Otherwise, we need to remove the selected range if it's not collapsed. - if (aReplacementRange && aReplacementRange->location != NSNotFound) { - // nothing to do since the range is collapsed. - if (aReplacementRange->length == 0) { - return; - } - // If the replacement range is different from current selected range, - // select the range. - if (!NSEqualRanges(selectedRange, *aReplacementRange)) { - NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange)); - } - selectedRange = SelectedRange(); - } - NS_ENSURE_TRUE_VOID(selectedRange.location != NSNotFound); - if (selectedRange.length == 0) { - return; // nothing to do - } - // If this is caused by a key input, the keypress event which will be - // dispatched later should cause the delete. Therefore, nothing to do here. - // Although, we're not sure if such case is actually possible. - if (!currentKeyEvent) { - return; - } - // Delete the selected range. - RefPtr<TextInputHandler> kungFuDeathGrip(this); - WidgetContentCommandEvent deleteCommandEvent(true, eContentCommandDelete, - mWidget); - DispatchEvent(deleteCommandEvent); - NS_ENSURE_TRUE_VOID(deleteCommandEvent.mSucceeded); - // Be aware! The widget might be destroyed here. - return; - } - - bool isReplacingSpecifiedRange = - isEditable && aReplacementRange && - aReplacementRange->location != NSNotFound && - !NSEqualRanges(selectedRange, *aReplacementRange); - - // If this is not caused by pressing a key, there is a composition or - // replacing a range which is different from current selection, let's - // insert the text as committing a composition. - // If InsertText() is called two or more times, we should insert all - // text with composition events. - // XXX When InsertText() is called multiple times, Chromium dispatches - // only one composition event. So, we need to store InsertText() - // calls and flush later. - if (!currentKeyEvent || currentKeyEvent->mCompositionDispatched || - IsIMEComposing() || isReplacingSpecifiedRange) { - InsertTextAsCommittingComposition(aAttrString, aReplacementRange); - if (currentKeyEvent) { - currentKeyEvent->mCompositionDispatched = true; - } - return; - } - - // Don't let the same event be fired twice when hitting - // enter/return for Bug 420502. However, Korean IME (or some other - // simple IME) may work without marked text. For example, composing - // character may be inserted as committed text and it's modified with - // aReplacementRange. When a keydown starts new composition with - // committing previous character, InsertText() may be called twice, - // one is for committing previous character and then, inserting new - // composing character as committed character. In the latter case, - // |CanDispatchKeyPressEvent()| returns true but we need to dispatch - // keypress event for the new character. So, when IME tries to insert - // printable characters, we should ignore current key event state even - // after the keydown has already caused dispatching composition event. - // XXX Anyway, we should sort out around this at fixing bug 1338460. - if (currentKeyEvent && !currentKeyEvent->CanDispatchKeyPressEvent() && - (str.IsEmpty() || (str.Length() == 1 && !IsPrintableChar(str[0])))) { - return; - } - - // XXX Shouldn't we hold mDispatcher instead of mWidget? - RefPtr<nsChildView> widget(mWidget); - nsresult rv = mDispatcher->BeginNativeInputTransaction(); - if (NS_WARN_IF(NS_FAILED(rv))) { - MOZ_LOG(gLog, LogLevel::Error, - ("%p IMEInputHandler::HandleKeyUpEvent, " - "FAILED, due to BeginNativeInputTransaction() failure", this)); - return; - } - - // Dispatch keypress event with char instead of compositionchange event - WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget); - // XXX Why do we need to dispatch keypress event for not inputting any - // string? If it wants to delete the specified range, should we - // dispatch an eContentCommandDelete event instead? Because this - // must not be caused by a key operation, a part of IME's processing. - keypressEvent.mIsChar = IsPrintableChar(str.CharAt(0)); - - // Don't set other modifiers from the current event, because here in - // -insertText: they've already been taken into account in creating - // the input string. - - if (currentKeyEvent) { - currentKeyEvent->InitKeyEvent(this, keypressEvent); - } else { - nsCocoaUtils::InitInputEvent(keypressEvent, static_cast<NSEvent*>(nullptr)); - keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING; - keypressEvent.mKeyValue = str; - // FYI: TextEventDispatcher will set mKeyCode to 0 for printable key's - // keypress events even if they don't cause inputting non-empty string. - } - - // Remove basic modifiers from keypress event because if they are included, - // nsPlaintextEditor ignores the event. - keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | - MODIFIER_ALT | - MODIFIER_META); - - // TODO: - // If mCurrentKeyEvent.mKeyEvent is null, the text should be inputted as - // composition events. - nsEventStatus status = nsEventStatus_eIgnore; - bool keyPressDispatched = - mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status, - currentKeyEvent); - bool keyPressHandled = (status == nsEventStatus_eConsumeNoDefault); - - // Note: mWidget might have become null here. Don't count on it from here on. - - if (currentKeyEvent) { - currentKeyEvent->mKeyPressHandled = keyPressHandled; - currentKeyEvent->mKeyPressDispatched = keyPressDispatched; - } - - NS_OBJC_END_TRY_ABORT_BLOCK; -} - -bool -TextInputHandler::DoCommandBySelector(const char* aSelector) -{ - RefPtr<nsChildView> widget(mWidget); - - KeyEventState* currentKeyEvent = GetCurrentKeyEvent(); - - MOZ_LOG(gLog, LogLevel::Info, - ("%p TextInputHandler::DoCommandBySelector, aSelector=\"%s\", " - "Destroyed()=%s, keydownHandled=%s, keypressHandled=%s, " - "causedOtherKeyEvents=%s", - this, aSelector ? aSelector : "", TrueOrFalse(Destroyed()), - currentKeyEvent ? - TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A", - currentKeyEvent ? - TrueOrFalse(currentKeyEvent->mKeyPressHandled) : "N/A", - currentKeyEvent ? - TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A")); - - if (currentKeyEvent && currentKeyEvent->CanDispatchKeyPressEvent()) { - nsresult rv = mDispatcher->BeginNativeInputTransaction(); - if (NS_WARN_IF(NS_FAILED(rv))) { - MOZ_LOG(gLog, LogLevel::Error, - ("%p IMEInputHandler::DoCommandBySelector, " - "FAILED, due to BeginNativeInputTransaction() failure " - "at dispatching keypress", this)); - return false; - } - - WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget); - currentKeyEvent->InitKeyEvent(this, keypressEvent); - - nsEventStatus status = nsEventStatus_eIgnore; - currentKeyEvent->mKeyPressDispatched = - mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status, - currentKeyEvent); - currentKeyEvent->mKeyPressHandled = - (status == nsEventStatus_eConsumeNoDefault); - MOZ_LOG(gLog, LogLevel::Info, - ("%p TextInputHandler::DoCommandBySelector, keypress event " - "dispatched, Destroyed()=%s, keypressHandled=%s", - this, TrueOrFalse(Destroyed()), - TrueOrFalse(currentKeyEvent->mKeyPressHandled))); - } - - return (!Destroyed() && currentKeyEvent && - currentKeyEvent->IsDefaultPrevented()); -} - - -#pragma mark - - - -/****************************************************************************** - * - * IMEInputHandler implementation (static methods) - * - ******************************************************************************/ - -bool IMEInputHandler::sStaticMembersInitialized = false; -bool IMEInputHandler::sCachedIsForRTLLangage = false; -CFStringRef IMEInputHandler::sLatestIMEOpenedModeInputSourceID = nullptr; -IMEInputHandler* IMEInputHandler::sFocusedIMEHandler = nullptr; - -// static -void -IMEInputHandler::InitStaticMembers() -{ - if (sStaticMembersInitialized) - return; - sStaticMembersInitialized = true; - // We need to check the keyboard layout changes on all applications. - CFNotificationCenterRef center = ::CFNotificationCenterGetDistributedCenter(); - // XXX Don't we need to remove the observer at shut down? - // Mac Dev Center's document doesn't say how to remove the observer if - // the second parameter is NULL. - ::CFNotificationCenterAddObserver(center, NULL, - OnCurrentTextInputSourceChange, - kTISNotifySelectedKeyboardInputSourceChanged, NULL, - CFNotificationSuspensionBehaviorDeliverImmediately); - // Initiailize with the current keyboard layout - OnCurrentTextInputSourceChange(NULL, NULL, - kTISNotifySelectedKeyboardInputSourceChanged, - NULL, NULL); -} - -// static -void -IMEInputHandler::OnCurrentTextInputSourceChange(CFNotificationCenterRef aCenter, - void* aObserver, - CFStringRef aName, - const void* aObject, - CFDictionaryRef aUserInfo) -{ - // Cache the latest IME opened mode to sLatestIMEOpenedModeInputSourceID. - TISInputSourceWrapper tis; - tis.InitByCurrentInputSource(); - if (tis.IsOpenedIMEMode()) { - tis.GetInputSourceID(sLatestIMEOpenedModeInputSourceID); - } - - if (MOZ_LOG_TEST(gLog, LogLevel::Info)) { - static CFStringRef sLastTIS = nullptr; - CFStringRef newTIS; - tis.GetInputSourceID(newTIS); - if (!sLastTIS || - ::CFStringCompare(sLastTIS, newTIS, 0) != kCFCompareEqualTo) { - TISInputSourceWrapper tis1, tis2, tis3, tis4, tis5; - tis1.InitByCurrentKeyboardLayout(); - tis2.InitByCurrentASCIICapableInputSource(); - tis3.InitByCurrentASCIICapableKeyboardLayout(); - tis4.InitByCurrentInputMethodKeyboardLayoutOverride(); - tis5.InitByTISInputSourceRef(tis.GetKeyboardLayoutInputSource()); - CFStringRef is0 = nullptr, is1 = nullptr, is2 = nullptr, is3 = nullptr, - is4 = nullptr, is5 = nullptr, type0 = nullptr, - lang0 = nullptr, bundleID0 = nullptr; - tis.GetInputSourceID(is0); - tis1.GetInputSourceID(is1); - tis2.GetInputSourceID(is2); - tis3.GetInputSourceID(is3); - tis4.GetInputSourceID(is4); - tis5.GetInputSourceID(is5); - tis.GetInputSourceType(type0); - tis.GetPrimaryLanguage(lang0); - tis.GetBundleID(bundleID0); - - MOZ_LOG(gLog, LogLevel::Info, - ("IMEInputHandler::OnCurrentTextInputSourceChange,\n" - " Current Input Source is changed to:\n" - " currentInputContext=%p\n" - " %s\n" - " type=%s %s\n" - " overridden keyboard layout=%s\n" - " used keyboard layout for translation=%s\n" - " primary language=%s\n" - " bundle ID=%s\n" - " current ASCII capable Input Source=%s\n" - " current Keyboard Layout=%s\n" - " current ASCII capable Keyboard Layout=%s", - [NSTextInputContext currentInputContext], GetCharacters(is0), - GetCharacters(type0), tis.IsASCIICapable() ? "- ASCII capable " : "", - GetCharacters(is4), GetCharacters(is5), - GetCharacters(lang0), GetCharacters(bundleID0), - GetCharacters(is2), GetCharacters(is1), GetCharacters(is3))); - } - sLastTIS = newTIS; - } - - /** - * When the direction is changed, all the children are notified. - * No need to treat the initial case separately because it is covered - * by the general case (sCachedIsForRTLLangage is initially false) - */ - if (sCachedIsForRTLLangage != tis.IsForRTLLanguage()) { - WidgetUtils::SendBidiKeyboardInfoToContent(); - sCachedIsForRTLLangage = tis.IsForRTLLanguage(); - } -} - -// static -void -IMEInputHandler::FlushPendingMethods(nsITimer* aTimer, void* aClosure) -{ - NS_ASSERTION(aClosure, "aClosure is null"); - static_cast<IMEInputHandler*>(aClosure)->ExecutePendingMethods(); -} - -// static -CFArrayRef -IMEInputHandler::CreateAllIMEModeList() -{ - const void* keys[] = { kTISPropertyInputSourceType }; - const void* values[] = { kTISTypeKeyboardInputMode }; - CFDictionaryRef filter = - ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL); - NS_ASSERTION(filter, "failed to create the filter"); - CFArrayRef list = ::TISCreateInputSourceList(filter, true); - ::CFRelease(filter); - return list; -} - -// static -void -IMEInputHandler::DebugPrintAllIMEModes() -{ - if (MOZ_LOG_TEST(gLog, LogLevel::Info)) { - CFArrayRef list = CreateAllIMEModeList(); - MOZ_LOG(gLog, LogLevel::Info, ("IME mode configuration:")); - CFIndex idx = ::CFArrayGetCount(list); - TISInputSourceWrapper tis; - for (CFIndex i = 0; i < idx; ++i) { - TISInputSourceRef inputSource = static_cast<TISInputSourceRef>( - const_cast<void *>(::CFArrayGetValueAtIndex(list, i))); - tis.InitByTISInputSourceRef(inputSource); - nsAutoString name, isid; - tis.GetLocalizedName(name); - tis.GetInputSourceID(isid); - MOZ_LOG(gLog, LogLevel::Info, - (" %s\t<%s>%s%s\n", - NS_ConvertUTF16toUTF8(name).get(), - NS_ConvertUTF16toUTF8(isid).get(), - tis.IsASCIICapable() ? "" : "\t(Isn't ASCII capable)", - tis.IsEnabled() ? "" : "\t(Isn't Enabled)")); - } - ::CFRelease(list); - } -} - -//static -TSMDocumentID -IMEInputHandler::GetCurrentTSMDocumentID() -{ - // At least on Mac OS X 10.6.x and 10.7.x, ::TSMGetActiveDocument() has a bug. - // The result of ::TSMGetActiveDocument() isn't modified for new active text - // input context until [NSTextInputContext currentInputContext] is called. - // Therefore, we need to call it here. - [NSTextInputContext currentInputContext]; - return ::TSMGetActiveDocument(); -} - - -#pragma mark - - - -/****************************************************************************** - * - * IMEInputHandler implementation #1 - * The methods are releated to the pending methods. Some jobs should be - * run after the stack is finished, e.g, some methods cannot run the jobs - * during processing the focus event. And also some other jobs should be - * run at the next focus event is processed. - * The pending methods are recorded in mPendingMethods. They are executed - * by ExecutePendingMethods via FlushPendingMethods. - * - ******************************************************************************/ - -NS_IMETHODIMP -IMEInputHandler::NotifyIME(TextEventDispatcher* aTextEventDispatcher, - const IMENotification& aNotification) -{ - switch (aNotification.mMessage) { - case REQUEST_TO_COMMIT_COMPOSITION: - CommitIMEComposition(); - return NS_OK; - case REQUEST_TO_CANCEL_COMPOSITION: - CancelIMEComposition(); - return NS_OK; - case NOTIFY_IME_OF_FOCUS: - if (IsFocused()) { - nsIWidget* widget = aTextEventDispatcher->GetWidget(); - if (widget && widget->GetInputContext().IsPasswordEditor()) { - EnableSecureEventInput(); - } else { - EnsureSecureEventInputDisabled(); - } - } - OnFocusChangeInGecko(true); - return NS_OK; - case NOTIFY_IME_OF_BLUR: - OnFocusChangeInGecko(false); - return NS_OK; - case NOTIFY_IME_OF_SELECTION_CHANGE: - OnSelectionChange(aNotification); - return NS_OK; - default: - return NS_ERROR_NOT_IMPLEMENTED; - } -} - -NS_IMETHODIMP_(void) -IMEInputHandler::OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) -{ - // XXX When input transaction is being stolen by add-on, what should we do? -} - -NS_IMETHODIMP_(void) -IMEInputHandler::WillDispatchKeyboardEvent( - TextEventDispatcher* aTextEventDispatcher, - WidgetKeyboardEvent& aKeyboardEvent, - uint32_t aIndexOfKeypress, - void* aData) -{ - // If the keyboard event is not caused by a native key event, we can do - // nothing here. - if (!aData) { - return; - } - - KeyEventState* currentKeyEvent = static_cast<KeyEventState*>(aData); - NSEvent* nativeEvent = currentKeyEvent->mKeyEvent; - nsAString* insertString = currentKeyEvent->mInsertString; - if (KeyboardLayoutOverrideRef().mOverrideEnabled) { - TISInputSourceWrapper tis; - tis.InitByLayoutID(KeyboardLayoutOverrideRef().mKeyboardLayout, true); - tis.WillDispatchKeyboardEvent(nativeEvent, insertString, aKeyboardEvent); - return; - } - TISInputSourceWrapper::CurrentInputSource(). - WillDispatchKeyboardEvent(nativeEvent, insertString, aKeyboardEvent); -} - -void -IMEInputHandler::NotifyIMEOfFocusChangeInGecko() -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::NotifyIMEOfFocusChangeInGecko, " - "Destroyed()=%s, IsFocused()=%s, inputContext=%p", - this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()), - mView ? [mView inputContext] : nullptr)); - - if (Destroyed()) { - return; - } - - if (!IsFocused()) { - // retry at next focus event - mPendingMethods |= kNotifyIMEOfFocusChangeInGecko; - return; - } - - MOZ_ASSERT(mView); - NSTextInputContext* inputContext = [mView inputContext]; - NS_ENSURE_TRUE_VOID(inputContext); - - // When an <input> element on a XUL <panel> element gets focus from an <input> - // element on the opener window of the <panel> element, the owner window - // still has native focus. Therefore, IMEs may store the opener window's - // level at this time because they don't know the actual focus is moved to - // different window. If IMEs try to get the newest window level after the - // focus change, we return the window level of the XUL <panel>'s widget. - // Therefore, let's emulate the native focus change. Then, IMEs can refresh - // the stored window level. - [inputContext deactivate]; - [inputContext activate]; - - NS_OBJC_END_TRY_ABORT_BLOCK; -} - -void -IMEInputHandler::DiscardIMEComposition() -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::DiscardIMEComposition, " - "Destroyed()=%s, IsFocused()=%s, mView=%p, inputContext=%p", - this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()), - mView, mView ? [mView inputContext] : nullptr)); - - if (Destroyed()) { - return; - } - - if (!IsFocused()) { - // retry at next focus event - mPendingMethods |= kDiscardIMEComposition; - return; - } - - NS_ENSURE_TRUE_VOID(mView); - NSTextInputContext* inputContext = [mView inputContext]; - NS_ENSURE_TRUE_VOID(inputContext); - mIgnoreIMECommit = true; - [inputContext discardMarkedText]; - mIgnoreIMECommit = false; - - NS_OBJC_END_TRY_ABORT_BLOCK -} - -void -IMEInputHandler::SyncASCIICapableOnly() -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::SyncASCIICapableOnly, " - "Destroyed()=%s, IsFocused()=%s, mIsASCIICapableOnly=%s, " - "GetCurrentTSMDocumentID()=%p", - this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()), - TrueOrFalse(mIsASCIICapableOnly), GetCurrentTSMDocumentID())); - - if (Destroyed()) { - return; - } - - if (!IsFocused()) { - // retry at next focus event - mPendingMethods |= kSyncASCIICapableOnly; - return; - } - - TSMDocumentID doc = GetCurrentTSMDocumentID(); - if (!doc) { - // retry - mPendingMethods |= kSyncASCIICapableOnly; - NS_WARNING("Application is active but there is no active document"); - ResetTimer(); - return; - } - - if (mIsASCIICapableOnly) { - CFArrayRef ASCIICapableTISList = ::TISCreateASCIICapableInputSourceList(); - ::TSMSetDocumentProperty(doc, - kTSMDocumentEnabledInputSourcesPropertyTag, - sizeof(CFArrayRef), - &ASCIICapableTISList); - ::CFRelease(ASCIICapableTISList); - } else { - ::TSMRemoveDocumentProperty(doc, - kTSMDocumentEnabledInputSourcesPropertyTag); - } - - NS_OBJC_END_TRY_ABORT_BLOCK; -} - -void -IMEInputHandler::ResetTimer() -{ - NS_ASSERTION(mPendingMethods != 0, - "There are not pending methods, why this is called?"); - if (mTimer) { - mTimer->Cancel(); - } else { - mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); - NS_ENSURE_TRUE(mTimer, ); - } - mTimer->InitWithFuncCallback(FlushPendingMethods, this, 0, - nsITimer::TYPE_ONE_SHOT); -} - -void -IMEInputHandler::ExecutePendingMethods() -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - - if (mTimer) { - mTimer->Cancel(); - mTimer = nullptr; - } - - if (![[NSApplication sharedApplication] isActive]) { - mIsInFocusProcessing = false; - // If we're not active, we should retry at focus event - return; - } - - uint32_t pendingMethods = mPendingMethods; - // First, reset the pending method flags because if each methods cannot - // run now, they can reentry to the pending flags by theirselves. - mPendingMethods = 0; - - if (pendingMethods & kDiscardIMEComposition) - DiscardIMEComposition(); - if (pendingMethods & kSyncASCIICapableOnly) - SyncASCIICapableOnly(); - if (pendingMethods & kNotifyIMEOfFocusChangeInGecko) { - NotifyIMEOfFocusChangeInGecko(); - } - - mIsInFocusProcessing = false; - - NS_OBJC_END_TRY_ABORT_BLOCK; -} - -#pragma mark - - - -/****************************************************************************** - * - * IMEInputHandler implementation (native event handlers) - * - ******************************************************************************/ - -TextRangeType -IMEInputHandler::ConvertToTextRangeType(uint32_t aUnderlineStyle, - NSRange& aSelectedRange) -{ - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::ConvertToTextRangeType, " - "aUnderlineStyle=%llu, aSelectedRange.length=%llu,", - this, aUnderlineStyle, aSelectedRange.length)); - - // We assume that aUnderlineStyle is NSUnderlineStyleSingle or - // NSUnderlineStyleThick. NSUnderlineStyleThick should indicate a selected - // clause. Otherwise, should indicate non-selected clause. - - if (aSelectedRange.length == 0) { - switch (aUnderlineStyle) { - case NSUnderlineStyleSingle: - return TextRangeType::eRawClause; - case NSUnderlineStyleThick: - return TextRangeType::eSelectedRawClause; - default: - NS_WARNING("Unexpected line style"); - return TextRangeType::eSelectedRawClause; - } - } - - switch (aUnderlineStyle) { - case NSUnderlineStyleSingle: - return TextRangeType::eConvertedClause; - case NSUnderlineStyleThick: - return TextRangeType::eSelectedClause; - default: - NS_WARNING("Unexpected line style"); - return TextRangeType::eSelectedClause; - } -} - -uint32_t -IMEInputHandler::GetRangeCount(NSAttributedString *aAttrString) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; - - // Iterate through aAttrString for the NSUnderlineStyleAttributeName and - // count the different segments adjusting limitRange as we go. - uint32_t count = 0; - NSRange effectiveRange; - NSRange limitRange = NSMakeRange(0, [aAttrString length]); - while (limitRange.length > 0) { - [aAttrString attribute:NSUnderlineStyleAttributeName - atIndex:limitRange.location - longestEffectiveRange:&effectiveRange - inRange:limitRange]; - limitRange = - NSMakeRange(NSMaxRange(effectiveRange), - NSMaxRange(limitRange) - NSMaxRange(effectiveRange)); - count++; - } - - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::GetRangeCount, aAttrString=\"%s\", count=%llu", - this, GetCharacters([aAttrString string]), count)); - - return count; - - NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0); -} - -already_AddRefed<mozilla::TextRangeArray> -IMEInputHandler::CreateTextRangeArray(NSAttributedString *aAttrString, - NSRange& aSelectedRange) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; - - RefPtr<mozilla::TextRangeArray> textRangeArray = - new mozilla::TextRangeArray(); - - // Note that we shouldn't append ranges when composition string - // is empty because it may cause TextComposition confused. - if (![aAttrString length]) { - return textRangeArray.forget(); - } - - // Convert the Cocoa range into the TextRange Array used in Gecko. - // Iterate through the attributed string and map the underline attribute to - // Gecko IME textrange attributes. We may need to change the code here if - // we change the implementation of validAttributesForMarkedText. - NSRange limitRange = NSMakeRange(0, [aAttrString length]); - uint32_t rangeCount = GetRangeCount(aAttrString); - for (uint32_t i = 0; i < rangeCount && limitRange.length > 0; i++) { - NSRange effectiveRange; - id attributeValue = [aAttrString attribute:NSUnderlineStyleAttributeName - atIndex:limitRange.location - longestEffectiveRange:&effectiveRange - inRange:limitRange]; - - TextRange range; - range.mStartOffset = effectiveRange.location; - range.mEndOffset = NSMaxRange(effectiveRange); - range.mRangeType = - ConvertToTextRangeType([attributeValue intValue], aSelectedRange); - textRangeArray->AppendElement(range); - - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::CreateTextRangeArray, " - "range={ mStartOffset=%llu, mEndOffset=%llu, mRangeType=%s }", - this, range.mStartOffset, range.mEndOffset, - ToChar(range.mRangeType))); - - limitRange = - NSMakeRange(NSMaxRange(effectiveRange), - NSMaxRange(limitRange) - NSMaxRange(effectiveRange)); - } - - // Get current caret position. - TextRange range; - range.mStartOffset = aSelectedRange.location + aSelectedRange.length; - range.mEndOffset = range.mStartOffset; - range.mRangeType = TextRangeType::eCaret; - textRangeArray->AppendElement(range); - - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::CreateTextRangeArray, " - "range={ mStartOffset=%llu, mEndOffset=%llu, mRangeType=%s }", - this, range.mStartOffset, range.mEndOffset, - ToChar(range.mRangeType))); - - return textRangeArray.forget(); - - NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL; -} - -bool -IMEInputHandler::DispatchCompositionStartEvent() -{ - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::DispatchCompositionStartEvent, " - "mSelectedRange={ location=%llu, length=%llu }, Destroyed()=%s, " - "mView=%p, mWidget=%p, inputContext=%p, mIsIMEComposing=%s", - this, SelectedRange().location, mSelectedRange.length, - TrueOrFalse(Destroyed()), mView, mWidget, - mView ? [mView inputContext] : nullptr, TrueOrFalse(mIsIMEComposing))); - - RefPtr<IMEInputHandler> kungFuDeathGrip(this); - - nsresult rv = mDispatcher->BeginNativeInputTransaction(); - if (NS_WARN_IF(NS_FAILED(rv))) { - MOZ_LOG(gLog, LogLevel::Error, - ("%p IMEInputHandler::DispatchCompositionStartEvent, " - "FAILED, due to BeginNativeInputTransaction() failure", this)); - return false; - } - - NS_ASSERTION(!mIsIMEComposing, "There is a composition already"); - mIsIMEComposing = true; - - nsEventStatus status; - rv = mDispatcher->StartComposition(status); - if (NS_WARN_IF(NS_FAILED(rv))) { - MOZ_LOG(gLog, LogLevel::Error, - ("%p IMEInputHandler::DispatchCompositionStartEvent, " - "FAILED, due to StartComposition() failure", this)); - return false; - } - - if (Destroyed()) { - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::DispatchCompositionStartEvent, " - "destroyed by compositionstart event", this)); - return false; - } - - // FYI: compositionstart may cause committing composition by the webapp. - if (!mIsIMEComposing) { - return false; - } - - // FYI: The selection range might have been modified by a compositionstart - // event handler. - mIMECompositionStart = SelectedRange().location; - return true; -} - -bool -IMEInputHandler::DispatchCompositionChangeEvent(const nsString& aText, - NSAttributedString* aAttrString, - NSRange& aSelectedRange) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; - - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::DispatchCompositionChangeEvent, " - "aText=\"%s\", aAttrString=\"%s\", " - "aSelectedRange={ location=%llu, length=%llu }, Destroyed()=%s, mView=%p, " - "mWidget=%p, inputContext=%p, mIsIMEComposing=%s", - this, NS_ConvertUTF16toUTF8(aText).get(), - GetCharacters([aAttrString string]), - aSelectedRange.location, aSelectedRange.length, - TrueOrFalse(Destroyed()), mView, mWidget, - mView ? [mView inputContext] : nullptr, TrueOrFalse(mIsIMEComposing))); - - NS_ENSURE_TRUE(!Destroyed(), false); - - NS_ASSERTION(mIsIMEComposing, "We're not in composition"); - - RefPtr<IMEInputHandler> kungFuDeathGrip(this); - - nsresult rv = mDispatcher->BeginNativeInputTransaction(); - if (NS_WARN_IF(NS_FAILED(rv))) { - MOZ_LOG(gLog, LogLevel::Error, - ("%p IMEInputHandler::DispatchCompositionChangeEvent, " - "FAILED, due to BeginNativeInputTransaction() failure", this)); - return false; - } - - RefPtr<TextRangeArray> rangeArray = - CreateTextRangeArray(aAttrString, aSelectedRange); - - rv = mDispatcher->SetPendingComposition(aText, rangeArray); - if (NS_WARN_IF(NS_FAILED(rv))) { - MOZ_LOG(gLog, LogLevel::Error, - ("%p IMEInputHandler::DispatchCompositionChangeEvent, " - "FAILED, due to SetPendingComposition() failure", this)); - return false; - } - - mSelectedRange.location = mIMECompositionStart + aSelectedRange.location; - mSelectedRange.length = aSelectedRange.length; - - if (mIMECompositionString) { - [mIMECompositionString release]; - } - mIMECompositionString = [[aAttrString string] retain]; - - nsEventStatus status; - rv = mDispatcher->FlushPendingComposition(status); - if (NS_WARN_IF(NS_FAILED(rv))) { - MOZ_LOG(gLog, LogLevel::Error, - ("%p IMEInputHandler::DispatchCompositionChangeEvent, " - "FAILED, due to FlushPendingComposition() failure", this)); - return false; - } - - if (Destroyed()) { - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::DispatchCompositionChangeEvent, " - "destroyed by compositionchange event", this)); - return false; - } - - // FYI: compositionstart may cause committing composition by the webapp. - return mIsIMEComposing; - - NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false); -} - -bool -IMEInputHandler::DispatchCompositionCommitEvent(const nsAString* aCommitString) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; - - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::DispatchCompositionCommitEvent, " - "aCommitString=0x%p (\"%s\"), Destroyed()=%s, mView=%p, mWidget=%p, " - "inputContext=%p, mIsIMEComposing=%s", - this, aCommitString, - aCommitString ? NS_ConvertUTF16toUTF8(*aCommitString).get() : "", - TrueOrFalse(Destroyed()), mView, mWidget, - mView ? [mView inputContext] : nullptr, TrueOrFalse(mIsIMEComposing))); - - NS_ASSERTION(mIsIMEComposing, "We're not in composition"); - - RefPtr<IMEInputHandler> kungFuDeathGrip(this); - - if (!Destroyed()) { - // IME may query selection immediately after this, however, in e10s mode, - // OnSelectionChange() will be called asynchronously. Until then, we - // should emulate expected selection range if the webapp does nothing. - mSelectedRange.location = mIMECompositionStart; - if (aCommitString) { - mSelectedRange.location += aCommitString->Length(); - } else if (mIMECompositionString) { - nsAutoString commitString; - nsCocoaUtils::GetStringForNSString(mIMECompositionString, commitString); - mSelectedRange.location += commitString.Length(); - } - mSelectedRange.length = 0; - - nsresult rv = mDispatcher->BeginNativeInputTransaction(); - if (NS_WARN_IF(NS_FAILED(rv))) { - MOZ_LOG(gLog, LogLevel::Error, - ("%p IMEInputHandler::DispatchCompositionCommitEvent, " - "FAILED, due to BeginNativeInputTransaction() failure", this)); - } else { - nsEventStatus status; - rv = mDispatcher->CommitComposition(status, aCommitString); - if (NS_WARN_IF(NS_FAILED(rv))) { - MOZ_LOG(gLog, LogLevel::Error, - ("%p IMEInputHandler::DispatchCompositionCommitEvent, " - "FAILED, due to BeginNativeInputTransaction() failure", this)); - } - } - } - - mIsIMEComposing = false; - mIMECompositionStart = UINT32_MAX; - if (mIMECompositionString) { - [mIMECompositionString release]; - mIMECompositionString = nullptr; - } - - if (Destroyed()) { - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::DispatchCompositionCommitEvent, " - "destroyed by compositioncommit event", this)); - return false; - } - - return true; - - NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false); -} - -void -IMEInputHandler::InsertTextAsCommittingComposition( - NSAttributedString* aAttrString, - NSRange* aReplacementRange) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::InsertTextAsCommittingComposition, " - "aAttrString=\"%s\", aReplacementRange=%p { location=%llu, length=%llu }, " - "Destroyed()=%s, IsIMEComposing()=%s, " - "mMarkedRange={ location=%llu, length=%llu }", - this, GetCharacters([aAttrString string]), aReplacementRange, - aReplacementRange ? aReplacementRange->location : 0, - aReplacementRange ? aReplacementRange->length : 0, - TrueOrFalse(Destroyed()), TrueOrFalse(IsIMEComposing()), - mMarkedRange.location, mMarkedRange.length)); - - if (IgnoreIMECommit()) { - MOZ_CRASH("IMEInputHandler::InsertTextAsCommittingComposition() must not" - "be called while canceling the composition"); - } - - if (Destroyed()) { - return; - } - - // First, commit current composition with the latest composition string if the - // replacement range is different from marked range. - if (IsIMEComposing() && aReplacementRange && - aReplacementRange->location != NSNotFound && - !NSEqualRanges(MarkedRange(), *aReplacementRange)) { - if (!DispatchCompositionCommitEvent()) { - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::InsertTextAsCommittingComposition, " - "destroyed by commiting composition for setting replacement range", - this)); - return; - } - } - - RefPtr<IMEInputHandler> kungFuDeathGrip(this); - - nsString str; - nsCocoaUtils::GetStringForNSString([aAttrString string], str); - - if (!IsIMEComposing()) { - // If there is no selection and replacement range is specified, set the - // range as selection. - if (aReplacementRange && aReplacementRange->location != NSNotFound && - !NSEqualRanges(SelectedRange(), *aReplacementRange)) { - NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange)); - } - - if (!DispatchCompositionStartEvent()) { - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::InsertTextAsCommittingComposition, " - "cannot continue handling composition after compositionstart", this)); - return; - } - } - - if (!DispatchCompositionCommitEvent(&str)) { - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::InsertTextAsCommittingComposition, " - "destroyed by compositioncommit event", this)); - return; - } - - mMarkedRange = NSMakeRange(NSNotFound, 0); - - NS_OBJC_END_TRY_ABORT_BLOCK; -} - -void -IMEInputHandler::SetMarkedText(NSAttributedString* aAttrString, - NSRange& aSelectedRange, - NSRange* aReplacementRange) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - - KeyEventState* currentKeyEvent = GetCurrentKeyEvent(); - - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::SetMarkedText, " - "aAttrString=\"%s\", aSelectedRange={ location=%llu, length=%llu }, " - "aReplacementRange=%p { location=%llu, length=%llu }, " - "Destroyed()=%s, IgnoreIMEComposition()=%s, IsIMEComposing()=%s, " - "mMarkedRange={ location=%llu, length=%llu }, keyevent=%p, " - "keydownHandled=%s, keypressDispatched=%s, causedOtherKeyEvents=%s, " - "compositionDispatched=%s", - this, GetCharacters([aAttrString string]), - aSelectedRange.location, aSelectedRange.length, aReplacementRange, - aReplacementRange ? aReplacementRange->location : 0, - aReplacementRange ? aReplacementRange->length : 0, - TrueOrFalse(Destroyed()), TrueOrFalse(IgnoreIMEComposition()), - TrueOrFalse(IsIMEComposing()), - mMarkedRange.location, mMarkedRange.length, - currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr, - currentKeyEvent ? - TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A", - currentKeyEvent ? - TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A", - currentKeyEvent ? - TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A", - currentKeyEvent ? - TrueOrFalse(currentKeyEvent->mCompositionDispatched) : "N/A")); - - // If SetMarkedText() is called during handling a key press, that means that - // the key event caused this composition. So, keypress event shouldn't - // be dispatched later, let's mark the key event causing composition event. - if (currentKeyEvent) { - currentKeyEvent->mCompositionDispatched = true; - } - - if (Destroyed() || IgnoreIMEComposition()) { - return; - } - - RefPtr<IMEInputHandler> kungFuDeathGrip(this); - - // First, commit current composition with the latest composition string if the - // replacement range is different from marked range. - if (IsIMEComposing() && aReplacementRange && - aReplacementRange->location != NSNotFound && - !NSEqualRanges(MarkedRange(), *aReplacementRange)) { - AutoRestore<bool> ignoreIMECommit(mIgnoreIMECommit); - mIgnoreIMECommit = false; - if (!DispatchCompositionCommitEvent()) { - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::SetMarkedText, " - "destroyed by commiting composition for setting replacement range", - this)); - return; - } - } - - nsString str; - nsCocoaUtils::GetStringForNSString([aAttrString string], str); - - mMarkedRange.length = str.Length(); - - if (!IsIMEComposing() && !str.IsEmpty()) { - // If there is no selection and replacement range is specified, set the - // range as selection. - if (aReplacementRange && aReplacementRange->location != NSNotFound && - !NSEqualRanges(SelectedRange(), *aReplacementRange)) { - NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange)); - } - - mMarkedRange.location = SelectedRange().location; - - if (!DispatchCompositionStartEvent()) { - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::SetMarkedText, cannot continue handling " - "composition after dispatching compositionstart", this)); - return; - } - } - - if (!str.IsEmpty()) { - if (!DispatchCompositionChangeEvent(str, aAttrString, aSelectedRange)) { - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::SetMarkedText, cannot continue handling " - "composition after dispatching compositionchange", this)); - } - return; - } - - // If the composition string becomes empty string, we should commit - // current composition. - if (!DispatchCompositionCommitEvent(&EmptyString())) { - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::SetMarkedText, " - "destroyed by compositioncommit event", this)); - } - - NS_OBJC_END_TRY_ABORT_BLOCK; -} - -NSAttributedString* -IMEInputHandler::GetAttributedSubstringFromRange(NSRange& aRange, - NSRange* aActualRange) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; - - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::GetAttributedSubstringFromRange, " - "aRange={ location=%llu, length=%llu }, aActualRange=%p, Destroyed()=%s", - this, aRange.location, aRange.length, aActualRange, - TrueOrFalse(Destroyed()))); - - if (aActualRange) { - *aActualRange = NSMakeRange(NSNotFound, 0); - } - - if (Destroyed() || aRange.location == NSNotFound || aRange.length == 0) { - return nil; - } - - RefPtr<IMEInputHandler> kungFuDeathGrip(this); - - // If we're in composing, the queried range may be in the composition string. - // In such case, we should use mIMECompositionString since if the composition - // string is handled by a remote process, the content cache may be out of - // date. - // XXX Should we set composition string attributes? Although, Blink claims - // that some attributes of marked text are supported, but they return - // just marked string without any style. So, let's keep current behavior - // at least for now. - NSUInteger compositionLength = - mIMECompositionString ? [mIMECompositionString length] : 0; - if (mIMECompositionStart != UINT32_MAX && - mIMECompositionStart >= aRange.location && - mIMECompositionStart + compositionLength <= - aRange.location + aRange.length) { - NSRange range = - NSMakeRange(aRange.location - mIMECompositionStart, aRange.length); - NSString* nsstr = [mIMECompositionString substringWithRange:range]; - NSMutableAttributedString* result = - [[[NSMutableAttributedString alloc] initWithString:nsstr - attributes:nil] autorelease]; - // XXX We cannot return font information in this case. However, this - // case must occur only when IME tries to confirm if composing string - // is handled as expected. - if (aActualRange) { - *aActualRange = aRange; - } - - if (MOZ_LOG_TEST(gLog, LogLevel::Info)) { - nsAutoString str; - nsCocoaUtils::GetStringForNSString(nsstr, str); - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::GetAttributedSubstringFromRange, " - "computed with mIMECompositionString (result string=\"%s\")", - this, NS_ConvertUTF16toUTF8(str).get())); - } - return result; - } - - nsAutoString str; - WidgetQueryContentEvent textContent(true, eQueryTextContent, mWidget); - WidgetQueryContentEvent::Options options; - int64_t startOffset = aRange.location; - if (IsIMEComposing()) { - // The composition may be at different offset from the selection start - // offset at dispatching compositionstart because start of composition - // is fixed when composition string becomes non-empty in the editor. - // Therefore, we need to use query event which is relative to insertion - // point. - options.mRelativeToInsertionPoint = true; - startOffset -= mIMECompositionStart; - } - textContent.InitForQueryTextContent(startOffset, aRange.length, options); - textContent.RequestFontRanges(); - DispatchEvent(textContent); - - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::GetAttributedSubstringFromRange, " - "textContent={ mSucceeded=%s, mReply={ mString=\"%s\", mOffset=%u } }", - this, TrueOrFalse(textContent.mSucceeded), - NS_ConvertUTF16toUTF8(textContent.mReply.mString).get(), - textContent.mReply.mOffset)); - - if (!textContent.mSucceeded) { - return nil; - } - - // We don't set vertical information at this point. If required, - // OS will calls drawsVerticallyForCharacterAtIndex. - NSMutableAttributedString* result = - nsCocoaUtils::GetNSMutableAttributedString(textContent.mReply.mString, - textContent.mReply.mFontRanges, - false, - mWidget->BackingScaleFactor()); - if (aActualRange) { - aActualRange->location = textContent.mReply.mOffset; - aActualRange->length = textContent.mReply.mString.Length(); - } - return result; - - NS_OBJC_END_TRY_ABORT_BLOCK_NIL; -} - -bool -IMEInputHandler::HasMarkedText() -{ - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::HasMarkedText, " - "mMarkedRange={ location=%llu, length=%llu }", - this, mMarkedRange.location, mMarkedRange.length)); - - return (mMarkedRange.location != NSNotFound) && (mMarkedRange.length != 0); -} - -NSRange -IMEInputHandler::MarkedRange() -{ - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::MarkedRange, " - "mMarkedRange={ location=%llu, length=%llu }", - this, mMarkedRange.location, mMarkedRange.length)); - - if (!HasMarkedText()) { - return NSMakeRange(NSNotFound, 0); - } - return mMarkedRange; -} - -NSRange -IMEInputHandler::SelectedRange() -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; - - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::SelectedRange, Destroyed()=%s, mSelectedRange={ " - "location=%llu, length=%llu }", - this, TrueOrFalse(Destroyed()), mSelectedRange.location, - mSelectedRange.length)); - - if (Destroyed()) { - return mSelectedRange; - } - - if (mSelectedRange.location != NSNotFound) { - MOZ_ASSERT(mIMEHasFocus); - return mSelectedRange; - } - - RefPtr<IMEInputHandler> kungFuDeathGrip(this); - - WidgetQueryContentEvent selection(true, eQuerySelectedText, mWidget); - DispatchEvent(selection); - - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::SelectedRange, selection={ mSucceeded=%s, " - "mReply={ mOffset=%u, mString.Length()=%u } }", - this, TrueOrFalse(selection.mSucceeded), selection.mReply.mOffset, - selection.mReply.mString.Length())); - - if (!selection.mSucceeded) { - return mSelectedRange; - } - - mWritingMode = selection.GetWritingMode(); - mRangeForWritingMode = NSMakeRange(selection.mReply.mOffset, - selection.mReply.mString.Length()); - - if (mIMEHasFocus) { - mSelectedRange = mRangeForWritingMode; - } - - return mRangeForWritingMode; - - NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(mSelectedRange); -} - -bool -IMEInputHandler::DrawsVerticallyForCharacterAtIndex(uint32_t aCharIndex) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; - - if (Destroyed()) { - return false; - } - - if (mRangeForWritingMode.location == NSNotFound) { - // Update cached writing-mode value for the current selection. - SelectedRange(); - } - - if (aCharIndex < mRangeForWritingMode.location || - aCharIndex > mRangeForWritingMode.location + mRangeForWritingMode.length) { - // It's not clear to me whether this ever happens in practice, but if an - // IME ever wants to query writing mode at an offset outside the current - // selection, the writing-mode value may not be correct for the index. - // In that case, use FirstRectForCharacterRange to get a fresh value. - // This does more work than strictly necessary (we don't need the rect here), - // but should be a rare case. - NS_WARNING("DrawsVerticallyForCharacterAtIndex not using cached writing mode"); - NSRange range = NSMakeRange(aCharIndex, 1); - NSRange actualRange; - FirstRectForCharacterRange(range, &actualRange); - } - - return mWritingMode.IsVertical(); - - NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false); -} - -NSRect -IMEInputHandler::FirstRectForCharacterRange(NSRange& aRange, - NSRange* aActualRange) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; - - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::FirstRectForCharacterRange, Destroyed()=%s, " - "aRange={ location=%llu, length=%llu }, aActualRange=%p }", - this, TrueOrFalse(Destroyed()), aRange.location, aRange.length, - aActualRange)); - - // XXX this returns first character rect or caret rect, it is limitation of - // now. We need more work for returns first line rect. But current - // implementation is enough for IMEs. - - NSRect rect = NSMakeRect(0.0, 0.0, 0.0, 0.0); - NSRange actualRange = NSMakeRange(NSNotFound, 0); - if (aActualRange) { - *aActualRange = actualRange; - } - if (Destroyed() || aRange.location == NSNotFound) { - return rect; - } - - RefPtr<IMEInputHandler> kungFuDeathGrip(this); - - LayoutDeviceIntRect r; - bool useCaretRect = (aRange.length == 0); - if (!useCaretRect) { - WidgetQueryContentEvent charRect(true, eQueryTextRect, mWidget); - WidgetQueryContentEvent::Options options; - int64_t startOffset = aRange.location; - if (IsIMEComposing()) { - // The composition may be at different offset from the selection start - // offset at dispatching compositionstart because start of composition - // is fixed when composition string becomes non-empty in the editor. - // Therefore, we need to use query event which is relative to insertion - // point. - options.mRelativeToInsertionPoint = true; - startOffset -= mIMECompositionStart; - } - charRect.InitForQueryTextRect(startOffset, 1, options); - DispatchEvent(charRect); - if (charRect.mSucceeded) { - r = charRect.mReply.mRect; - actualRange.location = charRect.mReply.mOffset; - actualRange.length = charRect.mReply.mString.Length(); - mWritingMode = charRect.GetWritingMode(); - mRangeForWritingMode = actualRange; - } else { - useCaretRect = true; - } - } - - if (useCaretRect) { - WidgetQueryContentEvent caretRect(true, eQueryCaretRect, mWidget); - WidgetQueryContentEvent::Options options; - int64_t startOffset = aRange.location; - if (IsIMEComposing()) { - // The composition may be at different offset from the selection start - // offset at dispatching compositionstart because start of composition - // is fixed when composition string becomes non-empty in the editor. - // Therefore, we need to use query event which is relative to insertion - // point. - options.mRelativeToInsertionPoint = true; - startOffset -= mIMECompositionStart; - } - caretRect.InitForQueryCaretRect(startOffset, options); - DispatchEvent(caretRect); - if (!caretRect.mSucceeded) { - return rect; - } - r = caretRect.mReply.mRect; - r.width = 0; - actualRange.location = caretRect.mReply.mOffset; - actualRange.length = 0; - } - - nsIWidget* rootWidget = mWidget->GetTopLevelWidget(); - NSWindow* rootWindow = - static_cast<NSWindow*>(rootWidget->GetNativeData(NS_NATIVE_WINDOW)); - NSView* rootView = - static_cast<NSView*>(rootWidget->GetNativeData(NS_NATIVE_WIDGET)); - if (!rootWindow || !rootView) { - return rect; - } - rect = nsCocoaUtils::DevPixelsToCocoaPoints(r, mWidget->BackingScaleFactor()); - rect = [rootView convertRect:rect toView:nil]; - rect.origin = nsCocoaUtils::ConvertPointToScreen(rootWindow, rect.origin); - - if (aActualRange) { - *aActualRange = actualRange; - } - - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::FirstRectForCharacterRange, " - "useCaretRect=%s rect={ x=%f, y=%f, width=%f, height=%f }, " - "actualRange={ location=%llu, length=%llu }", - this, TrueOrFalse(useCaretRect), rect.origin.x, rect.origin.y, - rect.size.width, rect.size.height, actualRange.location, - actualRange.length)); - - return rect; - - NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakeRect(0.0, 0.0, 0.0, 0.0)); -} - -NSUInteger -IMEInputHandler::CharacterIndexForPoint(NSPoint& aPoint) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; - - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::CharacterIndexForPoint, aPoint={ x=%f, y=%f }", - this, aPoint.x, aPoint.y)); - - NSWindow* mainWindow = [NSApp mainWindow]; - if (!mWidget || !mainWindow) { - return NSNotFound; - } - - WidgetQueryContentEvent charAt(true, eQueryCharacterAtPoint, mWidget); - NSPoint ptInWindow = nsCocoaUtils::ConvertPointFromScreen(mainWindow, aPoint); - NSPoint ptInView = [mView convertPoint:ptInWindow fromView:nil]; - charAt.mRefPoint.x = - static_cast<int32_t>(ptInView.x) * mWidget->BackingScaleFactor(); - charAt.mRefPoint.y = - static_cast<int32_t>(ptInView.y) * mWidget->BackingScaleFactor(); - mWidget->DispatchWindowEvent(charAt); - if (!charAt.mSucceeded || - charAt.mReply.mOffset == WidgetQueryContentEvent::NOT_FOUND || - charAt.mReply.mOffset >= static_cast<uint32_t>(NSNotFound)) { - return NSNotFound; - } - - return charAt.mReply.mOffset; - - NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSNotFound); -} - -extern "C" { -extern NSString *NSTextInputReplacementRangeAttributeName; -} - -NSArray* -IMEInputHandler::GetValidAttributesForMarkedText() -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; - - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::GetValidAttributesForMarkedText", this)); - - // Return same attributes as Chromium (see render_widget_host_view_mac.mm) - // because most IMEs must be tested with Safari (OS default) and Chrome - // (having most market share). Therefore, we need to follow their behavior. - // XXX It might be better to reuse an array instance for this result because - // this may be called a lot. Note that Chromium does so. - return [NSArray arrayWithObjects:NSUnderlineStyleAttributeName, - NSUnderlineColorAttributeName, - NSMarkedClauseSegmentAttributeName, - NSTextInputReplacementRangeAttributeName, - nil]; - - NS_OBJC_END_TRY_ABORT_BLOCK_NIL; -} - - -#pragma mark - - - -/****************************************************************************** - * - * IMEInputHandler implementation #2 - * - ******************************************************************************/ - -IMEInputHandler::IMEInputHandler(nsChildView* aWidget, - NSView<mozView> *aNativeView) - : TextInputHandlerBase(aWidget, aNativeView) - , mPendingMethods(0) - , mIMECompositionString(nullptr) - , mIMECompositionStart(UINT32_MAX) - , mIsIMEComposing(false) - , mIsIMEEnabled(true) - , mIsASCIICapableOnly(false) - , mIgnoreIMECommit(false) - , mIsInFocusProcessing(false) - , mIMEHasFocus(false) -{ - InitStaticMembers(); - - mMarkedRange.location = NSNotFound; - mMarkedRange.length = 0; - mSelectedRange.location = NSNotFound; - mSelectedRange.length = 0; -} - -IMEInputHandler::~IMEInputHandler() -{ - if (mTimer) { - mTimer->Cancel(); - mTimer = nullptr; - } - if (sFocusedIMEHandler == this) { - sFocusedIMEHandler = nullptr; - } - if (mIMECompositionString) { - [mIMECompositionString release]; - mIMECompositionString = nullptr; - } -} - -void -IMEInputHandler::OnFocusChangeInGecko(bool aFocus) -{ - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::OnFocusChangeInGecko, aFocus=%s, Destroyed()=%s, " - "sFocusedIMEHandler=%p", - this, TrueOrFalse(aFocus), TrueOrFalse(Destroyed()), sFocusedIMEHandler)); - - mSelectedRange.location = NSNotFound; // Marking dirty - mIMEHasFocus = aFocus; - - // This is called when the native focus is changed and when the native focus - // isn't changed but the focus is changed in Gecko. - if (!aFocus) { - if (sFocusedIMEHandler == this) - sFocusedIMEHandler = nullptr; - return; - } - - sFocusedIMEHandler = this; - mIsInFocusProcessing = true; - - // We need to notify IME of focus change in Gecko as native focus change - // because the window level of the focused element in Gecko may be changed. - mPendingMethods |= kNotifyIMEOfFocusChangeInGecko; - ResetTimer(); -} - -bool -IMEInputHandler::OnDestroyWidget(nsChildView* aDestroyingWidget) -{ - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::OnDestroyWidget, aDestroyingWidget=%p, " - "sFocusedIMEHandler=%p, IsIMEComposing()=%s", - this, aDestroyingWidget, sFocusedIMEHandler, - TrueOrFalse(IsIMEComposing()))); - - // If we're not focused, the focused IMEInputHandler may have been - // created by another widget/nsChildView. - if (sFocusedIMEHandler && sFocusedIMEHandler != this) { - sFocusedIMEHandler->OnDestroyWidget(aDestroyingWidget); - } - - if (!TextInputHandlerBase::OnDestroyWidget(aDestroyingWidget)) { - return false; - } - - if (IsIMEComposing()) { - // If our view is in the composition, we should clean up it. - CancelIMEComposition(); - } - - mSelectedRange.location = NSNotFound; // Marking dirty - mIMEHasFocus = false; - - return true; -} - -void -IMEInputHandler::SendCommittedText(NSString *aString) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::SendCommittedText, mView=%p, mWidget=%p, " - "inputContext=%p, mIsIMEComposing=%s", - this, mView, mWidget, mView ? [mView inputContext] : nullptr, - TrueOrFalse(mIsIMEComposing), mWidget)); - - NS_ENSURE_TRUE(mWidget, ); - // XXX We should send the string without mView. - if (!mView) { - return; - } - - NSAttributedString* attrStr = - [[NSAttributedString alloc] initWithString:aString]; - if ([mView conformsToProtocol:@protocol(NSTextInputClient)]) { - NSObject<NSTextInputClient>* textInputClient = - static_cast<NSObject<NSTextInputClient>*>(mView); - [textInputClient insertText:attrStr - replacementRange:NSMakeRange(NSNotFound, 0)]; - } - - // Last resort. If we cannot retrieve NSTextInputProtocol from mView - // or blocking to call our InsertText(), we should call InsertText() - // directly to commit composition forcibly. - if (mIsIMEComposing) { - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::SendCommittedText, trying to insert text directly " - "due to IME not calling our InsertText()", this)); - static_cast<TextInputHandler*>(this)->InsertText(attrStr); - MOZ_ASSERT(!mIsIMEComposing); - } - - [attrStr release]; - - NS_OBJC_END_TRY_ABORT_BLOCK; -} - -void -IMEInputHandler::KillIMEComposition() -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::KillIMEComposition, mView=%p, mWidget=%p, " - "inputContext=%p, mIsIMEComposing=%s, " - "Destroyed()=%s, IsFocused()=%s", - this, mView, mWidget, mView ? [mView inputContext] : nullptr, - TrueOrFalse(mIsIMEComposing), TrueOrFalse(Destroyed()), - TrueOrFalse(IsFocused()))); - - if (Destroyed()) { - return; - } - - if (IsFocused()) { - NS_ENSURE_TRUE_VOID(mView); - NSTextInputContext* inputContext = [mView inputContext]; - NS_ENSURE_TRUE_VOID(inputContext); - [inputContext discardMarkedText]; - return; - } - - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::KillIMEComposition, Pending...", this)); - - // Commit the composition internally. - SendCommittedText(mIMECompositionString); - NS_ASSERTION(!mIsIMEComposing, "We're still in a composition"); - // The pending method will be fired by the next focus event. - mPendingMethods |= kDiscardIMEComposition; - - NS_OBJC_END_TRY_ABORT_BLOCK; -} - -void -IMEInputHandler::CommitIMEComposition() -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - - if (!IsIMEComposing()) - return; - - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::CommitIMEComposition, mIMECompositionString=%s", - this, GetCharacters(mIMECompositionString))); - - KillIMEComposition(); - - if (!IsIMEComposing()) - return; - - // If the composition is still there, KillIMEComposition only kills the - // composition in TSM. We also need to finish the our composition too. - SendCommittedText(mIMECompositionString); - - NS_OBJC_END_TRY_ABORT_BLOCK; -} - -void -IMEInputHandler::CancelIMEComposition() -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - - if (!IsIMEComposing()) - return; - - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::CancelIMEComposition, mIMECompositionString=%s", - this, GetCharacters(mIMECompositionString))); - - // For canceling the current composing, we need to ignore the param of - // insertText. But this code is ugly... - mIgnoreIMECommit = true; - KillIMEComposition(); - mIgnoreIMECommit = false; - - if (!IsIMEComposing()) - return; - - // If the composition is still there, KillIMEComposition only kills the - // composition in TSM. We also need to kill the our composition too. - SendCommittedText(@""); - - NS_OBJC_END_TRY_ABORT_BLOCK; -} - -bool -IMEInputHandler::IsFocused() -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - - NS_ENSURE_TRUE(!Destroyed(), false); - NSWindow* window = [mView window]; - NS_ENSURE_TRUE(window, false); - return [window firstResponder] == mView && - [window isKeyWindow] && - [[NSApplication sharedApplication] isActive]; - - NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false); -} - -bool -IMEInputHandler::IsIMEOpened() -{ - TISInputSourceWrapper tis; - tis.InitByCurrentInputSource(); - return tis.IsOpenedIMEMode(); -} - -void -IMEInputHandler::SetASCIICapableOnly(bool aASCIICapableOnly) -{ - if (aASCIICapableOnly == mIsASCIICapableOnly) - return; - - CommitIMEComposition(); - mIsASCIICapableOnly = aASCIICapableOnly; - SyncASCIICapableOnly(); -} - -void -IMEInputHandler::EnableIME(bool aEnableIME) -{ - if (aEnableIME == mIsIMEEnabled) - return; - - CommitIMEComposition(); - mIsIMEEnabled = aEnableIME; -} - -void -IMEInputHandler::SetIMEOpenState(bool aOpenIME) -{ - if (!IsFocused() || IsIMEOpened() == aOpenIME) - return; - - if (!aOpenIME) { - TISInputSourceWrapper tis; - tis.InitByCurrentASCIICapableInputSource(); - tis.Select(); - return; - } - - // If we know the latest IME opened mode, we should select it. - if (sLatestIMEOpenedModeInputSourceID) { - TISInputSourceWrapper tis; - tis.InitByInputSourceID(sLatestIMEOpenedModeInputSourceID); - tis.Select(); - return; - } - - // XXX If the current input source is a mode of IME, we should turn on it, - // but we haven't found such way... - - // Finally, we should refer the system locale but this is a little expensive, - // we shouldn't retry this (if it was succeeded, we already set - // sLatestIMEOpenedModeInputSourceID at that time). - static bool sIsPrefferredIMESearched = false; - if (sIsPrefferredIMESearched) - return; - sIsPrefferredIMESearched = true; - OpenSystemPreferredLanguageIME(); -} - -void -IMEInputHandler::OpenSystemPreferredLanguageIME() -{ - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::OpenSystemPreferredLanguageIME", this)); - - CFArrayRef langList = ::CFLocaleCopyPreferredLanguages(); - if (!langList) { - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::OpenSystemPreferredLanguageIME, langList is NULL", - this)); - return; - } - CFIndex count = ::CFArrayGetCount(langList); - for (CFIndex i = 0; i < count; i++) { - CFLocaleRef locale = - ::CFLocaleCreate(kCFAllocatorDefault, - static_cast<CFStringRef>(::CFArrayGetValueAtIndex(langList, i))); - if (!locale) { - continue; - } - - bool changed = false; - CFStringRef lang = static_cast<CFStringRef>( - ::CFLocaleGetValue(locale, kCFLocaleLanguageCode)); - NS_ASSERTION(lang, "lang is null"); - if (lang) { - TISInputSourceWrapper tis; - tis.InitByLanguage(lang); - if (tis.IsOpenedIMEMode()) { - if (MOZ_LOG_TEST(gLog, LogLevel::Info)) { - CFStringRef foundTIS; - tis.GetInputSourceID(foundTIS); - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::OpenSystemPreferredLanguageIME, " - "foundTIS=%s, lang=%s", - this, GetCharacters(foundTIS), GetCharacters(lang))); - } - tis.Select(); - changed = true; - } - } - ::CFRelease(locale); - if (changed) { - break; - } - } - ::CFRelease(langList); -} - -void -IMEInputHandler::OnSelectionChange(const IMENotification& aIMENotification) -{ - MOZ_LOG(gLog, LogLevel::Info, - ("%p IMEInputHandler::OnSelectionChange", this)); - - if (aIMENotification.mSelectionChangeData.mOffset == UINT32_MAX) { - mSelectedRange.location = NSNotFound; - mSelectedRange.length = 0; - mRangeForWritingMode.location = NSNotFound; - mRangeForWritingMode.length = 0; - return; - } - - mWritingMode = aIMENotification.mSelectionChangeData.GetWritingMode(); - mRangeForWritingMode = - NSMakeRange(aIMENotification.mSelectionChangeData.mOffset, - aIMENotification.mSelectionChangeData.Length()); - if (mIMEHasFocus) { - mSelectedRange = mRangeForWritingMode; - } -} - -bool -IMEInputHandler::OnHandleEvent(NSEvent* aEvent) -{ - if (!IsFocused()) { - return false; - } - NSTextInputContext* inputContext = [mView inputContext]; - return [inputContext handleEvent:aEvent]; -} - -#pragma mark - - - -/****************************************************************************** - * - * TextInputHandlerBase implementation - * - ******************************************************************************/ - -int32_t TextInputHandlerBase::sSecureEventInputCount = 0; - -NS_IMPL_ISUPPORTS(TextInputHandlerBase, - TextEventDispatcherListener, - nsISupportsWeakReference) - -TextInputHandlerBase::TextInputHandlerBase(nsChildView* aWidget, - NSView<mozView> *aNativeView) - : mWidget(aWidget) - , mDispatcher(aWidget->GetTextEventDispatcher()) -{ - gHandlerInstanceCount++; - mView = [aNativeView retain]; -} - -TextInputHandlerBase::~TextInputHandlerBase() -{ - [mView release]; - if (--gHandlerInstanceCount == 0) { - TISInputSourceWrapper::Shutdown(); - } -} - -bool -TextInputHandlerBase::OnDestroyWidget(nsChildView* aDestroyingWidget) -{ - MOZ_LOG(gLog, LogLevel::Info, - ("%p TextInputHandlerBase::OnDestroyWidget, " - "aDestroyingWidget=%p, mWidget=%p", - this, aDestroyingWidget, mWidget)); - - if (aDestroyingWidget != mWidget) { - return false; - } - - mWidget = nullptr; - mDispatcher = nullptr; - return true; -} - -bool -TextInputHandlerBase::DispatchEvent(WidgetGUIEvent& aEvent) -{ - return mWidget->DispatchWindowEvent(aEvent); -} - -void -TextInputHandlerBase::InitKeyEvent(NSEvent *aNativeKeyEvent, - WidgetKeyboardEvent& aKeyEvent, - const nsAString* aInsertString) -{ - NS_ASSERTION(aNativeKeyEvent, "aNativeKeyEvent must not be NULL"); - - if (mKeyboardOverride.mOverrideEnabled) { - TISInputSourceWrapper tis; - tis.InitByLayoutID(mKeyboardOverride.mKeyboardLayout, true); - tis.InitKeyEvent(aNativeKeyEvent, aKeyEvent, aInsertString); - return; - } - TISInputSourceWrapper::CurrentInputSource(). - InitKeyEvent(aNativeKeyEvent, aKeyEvent, aInsertString); -} - -nsresult -TextInputHandlerBase::SynthesizeNativeKeyEvent( - int32_t aNativeKeyboardLayout, - int32_t aNativeKeyCode, - uint32_t aModifierFlags, - const nsAString& aCharacters, - const nsAString& aUnmodifiedCharacters) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; - - static const uint32_t sModifierFlagMap[][2] = { - { nsIWidget::CAPS_LOCK, NSAlphaShiftKeyMask }, - { nsIWidget::SHIFT_L, NSShiftKeyMask | 0x0002 }, - { nsIWidget::SHIFT_R, NSShiftKeyMask | 0x0004 }, - { nsIWidget::CTRL_L, NSControlKeyMask | 0x0001 }, - { nsIWidget::CTRL_R, NSControlKeyMask | 0x2000 }, - { nsIWidget::ALT_L, NSAlternateKeyMask | 0x0020 }, - { nsIWidget::ALT_R, NSAlternateKeyMask | 0x0040 }, - { nsIWidget::COMMAND_L, NSCommandKeyMask | 0x0008 }, - { nsIWidget::COMMAND_R, NSCommandKeyMask | 0x0010 }, - { nsIWidget::NUMERIC_KEY_PAD, NSNumericPadKeyMask }, - { nsIWidget::HELP, NSHelpKeyMask }, - { nsIWidget::FUNCTION, NSFunctionKeyMask } - }; - - uint32_t modifierFlags = 0; - for (uint32_t i = 0; i < ArrayLength(sModifierFlagMap); ++i) { - if (aModifierFlags & sModifierFlagMap[i][0]) { - modifierFlags |= sModifierFlagMap[i][1]; - } - } - - NSInteger windowNumber = [[mView window] windowNumber]; - bool sendFlagsChangedEvent = IsModifierKey(aNativeKeyCode); - NSEventType eventType = sendFlagsChangedEvent ? NSFlagsChanged : NSKeyDown; - NSEvent* downEvent = - [NSEvent keyEventWithType:eventType - location:NSMakePoint(0,0) - modifierFlags:modifierFlags - timestamp:0 - windowNumber:windowNumber - context:[NSGraphicsContext currentContext] - characters:nsCocoaUtils::ToNSString(aCharacters) - charactersIgnoringModifiers:nsCocoaUtils::ToNSString(aUnmodifiedCharacters) - isARepeat:NO - keyCode:aNativeKeyCode]; - - NSEvent* upEvent = sendFlagsChangedEvent ? - nil : nsCocoaUtils::MakeNewCocoaEventWithType(NSKeyUp, downEvent); - - if (downEvent && (sendFlagsChangedEvent || upEvent)) { - KeyboardLayoutOverride currentLayout = mKeyboardOverride; - mKeyboardOverride.mKeyboardLayout = aNativeKeyboardLayout; - mKeyboardOverride.mOverrideEnabled = true; - [NSApp sendEvent:downEvent]; - if (upEvent) { - [NSApp sendEvent:upEvent]; - } - // processKeyDownEvent and keyUp block exceptions so we're sure to - // reach here to restore mKeyboardOverride - mKeyboardOverride = currentLayout; - } - - return NS_OK; - - NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; -} - -NSInteger -TextInputHandlerBase::GetWindowLevel() -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; - - MOZ_LOG(gLog, LogLevel::Info, - ("%p TextInputHandlerBase::GetWindowLevel, Destryoed()=%s", - this, TrueOrFalse(Destroyed()))); - - if (Destroyed()) { - return NSNormalWindowLevel; - } - - // When an <input> element on a XUL <panel> is focused, the actual focused view - // is the panel's parent view (mView). But the editor is displayed on the - // popped-up widget's view (editorView). We want the latter's window level. - NSView<mozView>* editorView = mWidget->GetEditorView(); - NS_ENSURE_TRUE(editorView, NSNormalWindowLevel); - NSInteger windowLevel = [[editorView window] level]; - - MOZ_LOG(gLog, LogLevel::Info, - ("%p TextInputHandlerBase::GetWindowLevel, windowLevel=%s (%X)", - this, GetWindowLevelName(windowLevel), windowLevel)); - - return windowLevel; - - NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSNormalWindowLevel); -} - -NS_IMETHODIMP -TextInputHandlerBase::AttachNativeKeyEvent(WidgetKeyboardEvent& aKeyEvent) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; - - // Don't try to replace a native event if one already exists. - // OS X doesn't have an OS modifier, can't make a native event. - if (aKeyEvent.mNativeKeyEvent || aKeyEvent.mModifiers & MODIFIER_OS) { - return NS_OK; - } - - MOZ_LOG(gLog, LogLevel::Info, - ("%p TextInputHandlerBase::AttachNativeKeyEvent, key=0x%X, char=0x%X, " - "mod=0x%X", this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode, - aKeyEvent.mModifiers)); - - NSEventType eventType; - if (aKeyEvent.mMessage == eKeyUp) { - eventType = NSKeyUp; - } else { - eventType = NSKeyDown; - } - - static const uint32_t sModifierFlagMap[][2] = { - { MODIFIER_SHIFT, NSShiftKeyMask }, - { MODIFIER_CONTROL, NSControlKeyMask }, - { MODIFIER_ALT, NSAlternateKeyMask }, - { MODIFIER_ALTGRAPH, NSAlternateKeyMask }, - { MODIFIER_META, NSCommandKeyMask }, - { MODIFIER_CAPSLOCK, NSAlphaShiftKeyMask }, - { MODIFIER_NUMLOCK, NSNumericPadKeyMask } - }; - - NSUInteger modifierFlags = 0; - for (uint32_t i = 0; i < ArrayLength(sModifierFlagMap); ++i) { - if (aKeyEvent.mModifiers & sModifierFlagMap[i][0]) { - modifierFlags |= sModifierFlagMap[i][1]; - } - } - - NSInteger windowNumber = [[mView window] windowNumber]; - - NSString* characters; - if (aKeyEvent.mCharCode) { - characters = [NSString stringWithCharacters: - reinterpret_cast<const unichar*>(&(aKeyEvent.mCharCode)) length:1]; - } else { - uint32_t cocoaCharCode = - nsCocoaUtils::ConvertGeckoKeyCodeToMacCharCode(aKeyEvent.mKeyCode); - characters = [NSString stringWithCharacters: - reinterpret_cast<const unichar*>(&cocoaCharCode) length:1]; - } - - aKeyEvent.mNativeKeyEvent = - [NSEvent keyEventWithType:eventType - location:NSMakePoint(0,0) - modifierFlags:modifierFlags - timestamp:0 - windowNumber:windowNumber - context:[NSGraphicsContext currentContext] - characters:characters - charactersIgnoringModifiers:characters - isARepeat:NO - keyCode:0]; // Native key code not currently needed - - return NS_OK; - - NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; -} - -bool -TextInputHandlerBase::SetSelection(NSRange& aRange) -{ - MOZ_ASSERT(!Destroyed()); - - RefPtr<TextInputHandlerBase> kungFuDeathGrip(this); - WidgetSelectionEvent selectionEvent(true, eSetSelection, mWidget); - selectionEvent.mOffset = aRange.location; - selectionEvent.mLength = aRange.length; - selectionEvent.mReversed = false; - selectionEvent.mExpandToClusterBoundary = false; - DispatchEvent(selectionEvent); - NS_ENSURE_TRUE(selectionEvent.mSucceeded, false); - return !Destroyed(); -} - -/* static */ bool -TextInputHandlerBase::IsPrintableChar(char16_t aChar) -{ - return (aChar >= 0x20 && aChar <= 0x7E) || aChar >= 0xA0; -} - - -/* static */ bool -TextInputHandlerBase::IsSpecialGeckoKey(UInt32 aNativeKeyCode) -{ - // this table is used to determine which keys are special and should not - // generate a charCode - switch (aNativeKeyCode) { - // modifiers - we don't get separate events for these yet - case kVK_Escape: - case kVK_Shift: - case kVK_RightShift: - case kVK_Command: - case kVK_RightCommand: - case kVK_CapsLock: - case kVK_Control: - case kVK_RightControl: - case kVK_Option: - case kVK_RightOption: - case kVK_ANSI_KeypadClear: - case kVK_Function: - - // function keys - case kVK_F1: - case kVK_F2: - case kVK_F3: - case kVK_F4: - case kVK_F5: - case kVK_F6: - case kVK_F7: - case kVK_F8: - case kVK_F9: - case kVK_F10: - case kVK_F11: - case kVK_F12: - case kVK_PC_Pause: - case kVK_PC_ScrollLock: - case kVK_PC_PrintScreen: - case kVK_F16: - case kVK_F17: - case kVK_F18: - case kVK_F19: - - case kVK_PC_Insert: - case kVK_PC_Delete: - case kVK_Tab: - case kVK_PC_Backspace: - case kVK_PC_ContextMenu: - - case kVK_JIS_Eisu: - case kVK_JIS_Kana: - - case kVK_Home: - case kVK_End: - case kVK_PageUp: - case kVK_PageDown: - case kVK_LeftArrow: - case kVK_RightArrow: - case kVK_UpArrow: - case kVK_DownArrow: - case kVK_Return: - case kVK_ANSI_KeypadEnter: - case kVK_Powerbook_KeypadEnter: - return true; - } - return false; -} - -/* static */ bool -TextInputHandlerBase::IsNormalCharInputtingEvent( - const WidgetKeyboardEvent& aKeyEvent) -{ - // this is not character inputting event, simply. - if (aKeyEvent.mNativeCharacters.IsEmpty() || - aKeyEvent.IsMeta()) { - return false; - } - return !IsControlChar(aKeyEvent.mNativeCharacters[0]); -} - -/* static */ bool -TextInputHandlerBase::IsModifierKey(UInt32 aNativeKeyCode) -{ - switch (aNativeKeyCode) { - case kVK_CapsLock: - case kVK_RightCommand: - case kVK_Command: - case kVK_Shift: - case kVK_Option: - case kVK_Control: - case kVK_RightShift: - case kVK_RightOption: - case kVK_RightControl: - case kVK_Function: - return true; - } - return false; -} - -/* static */ void -TextInputHandlerBase::EnableSecureEventInput() -{ - sSecureEventInputCount++; - ::EnableSecureEventInput(); -} - -/* static */ void -TextInputHandlerBase::DisableSecureEventInput() -{ - if (!sSecureEventInputCount) { - return; - } - sSecureEventInputCount--; - ::DisableSecureEventInput(); -} - -/* static */ bool -TextInputHandlerBase::IsSecureEventInputEnabled() -{ - NS_ASSERTION(!!sSecureEventInputCount == !!::IsSecureEventInputEnabled(), - "Some other process has enabled secure event input"); - return !!sSecureEventInputCount; -} - -/* static */ void -TextInputHandlerBase::EnsureSecureEventInputDisabled() -{ - while (sSecureEventInputCount) { - TextInputHandlerBase::DisableSecureEventInput(); - } -} - -#pragma mark - - - -/****************************************************************************** - * - * TextInputHandlerBase::KeyEventState implementation - * - ******************************************************************************/ - -void -TextInputHandlerBase::KeyEventState::InitKeyEvent( - TextInputHandlerBase* aHandler, - WidgetKeyboardEvent& aKeyEvent) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - - MOZ_ASSERT(aHandler); - MOZ_RELEASE_ASSERT(mKeyEvent); - - NSEvent* nativeEvent = mKeyEvent; - if (!mInsertedString.IsEmpty()) { - nsAutoString unhandledString; - GetUnhandledString(unhandledString); - NSString* unhandledNSString = - nsCocoaUtils::ToNSString(unhandledString); - // If the key event's some characters were already handled by - // InsertString() calls, we need to create a dummy event which doesn't - // include the handled characters. - nativeEvent = - [NSEvent keyEventWithType:[mKeyEvent type] - location:[mKeyEvent locationInWindow] - modifierFlags:[mKeyEvent modifierFlags] - timestamp:[mKeyEvent timestamp] - windowNumber:[mKeyEvent windowNumber] - context:[mKeyEvent context] - characters:unhandledNSString - charactersIgnoringModifiers:[mKeyEvent charactersIgnoringModifiers] - isARepeat:[mKeyEvent isARepeat] - keyCode:[mKeyEvent keyCode]]; - } - - aHandler->InitKeyEvent(nativeEvent, aKeyEvent, mInsertString); - - NS_OBJC_END_TRY_ABORT_BLOCK; -} - -void -TextInputHandlerBase::KeyEventState::GetUnhandledString( - nsAString& aUnhandledString) const -{ - aUnhandledString.Truncate(); - if (NS_WARN_IF(!mKeyEvent)) { - return; - } - nsAutoString characters; - nsCocoaUtils::GetStringForNSString([mKeyEvent characters], - characters); - if (characters.IsEmpty()) { - return; - } - if (mInsertedString.IsEmpty()) { - aUnhandledString = characters; - return; - } - - // The insertes string must match with the start of characters. - MOZ_ASSERT(StringBeginsWith(characters, mInsertedString)); - - aUnhandledString = nsDependentSubstring(characters, mInsertedString.Length()); -} - -#pragma mark - - - -/****************************************************************************** - * - * TextInputHandlerBase::AutoInsertStringClearer implementation - * - ******************************************************************************/ - -TextInputHandlerBase::AutoInsertStringClearer::~AutoInsertStringClearer() -{ - if (mState && mState->mInsertString) { - // If inserting string is a part of characters of the event, - // we should record it as inserted string. - nsAutoString characters; - nsCocoaUtils::GetStringForNSString([mState->mKeyEvent characters], - characters); - nsAutoString insertedString(mState->mInsertedString); - insertedString += *mState->mInsertString; - if (StringBeginsWith(characters, insertedString)) { - mState->mInsertedString = insertedString; - } - } - if (mState) { - mState->mInsertString = nullptr; - } -} |