diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /widget/cocoa/TextInputHandler.h | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'widget/cocoa/TextInputHandler.h')
-rw-r--r-- | widget/cocoa/TextInputHandler.h | 1195 |
1 files changed, 1195 insertions, 0 deletions
diff --git a/widget/cocoa/TextInputHandler.h b/widget/cocoa/TextInputHandler.h new file mode 100644 index 000000000..de7c77593 --- /dev/null +++ b/widget/cocoa/TextInputHandler.h @@ -0,0 +1,1195 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sw=2 et tw=80: */ +/* 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/. */ + +#ifndef TextInputHandler_h_ +#define TextInputHandler_h_ + +#include "nsCocoaUtils.h" + +#import <Carbon/Carbon.h> +#import <Cocoa/Cocoa.h> +#include "mozView.h" +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsITimer.h" +#include "nsTArray.h" +#include "mozilla/EventForwards.h" +#include "mozilla/TextEventDispatcherListener.h" +#include "WritingModes.h" + +class nsChildView; + +namespace mozilla { +namespace widget { + +// Key code constants +enum +{ +#if !defined(MAC_OS_X_VERSION_10_12) || \ + MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12 + kVK_RightCommand = 0x36, // right command key +#endif + + kVK_PC_PrintScreen = kVK_F13, + kVK_PC_ScrollLock = kVK_F14, + kVK_PC_Pause = kVK_F15, + + kVK_PC_Insert = kVK_Help, + kVK_PC_Backspace = kVK_Delete, + kVK_PC_Delete = kVK_ForwardDelete, + + kVK_PC_ContextMenu = 0x6E, + + kVK_Powerbook_KeypadEnter = 0x34 // Enter on Powerbook's keyboard is different +}; + +/** + * TISInputSourceWrapper is a wrapper for the TISInputSourceRef. If we get the + * TISInputSourceRef from InputSourceID, we need to release the CFArray instance + * which is returned by TISCreateInputSourceList. However, when we release the + * list, we cannot access the TISInputSourceRef. So, it's not usable, and it + * may cause the memory leak bugs. nsTISInputSource automatically releases the + * list when the instance is destroyed. + */ +class TISInputSourceWrapper +{ +public: + static TISInputSourceWrapper& CurrentInputSource(); + /** + * Shutdown() should be called when nobody doesn't need to use this class. + */ + static void Shutdown(); + + TISInputSourceWrapper() + { + mInputSourceList = nullptr; + Clear(); + } + + explicit TISInputSourceWrapper(const char* aID) + { + mInputSourceList = nullptr; + InitByInputSourceID(aID); + } + + explicit TISInputSourceWrapper(SInt32 aLayoutID) + { + mInputSourceList = nullptr; + InitByLayoutID(aLayoutID); + } + + explicit TISInputSourceWrapper(TISInputSourceRef aInputSource) + { + mInputSourceList = nullptr; + InitByTISInputSourceRef(aInputSource); + } + + ~TISInputSourceWrapper() { Clear(); } + + void InitByInputSourceID(const char* aID); + void InitByInputSourceID(const nsAFlatString &aID); + void InitByInputSourceID(const CFStringRef aID); + /** + * InitByLayoutID() initializes the keyboard layout by the layout ID. + * + * @param aLayoutID An ID of keyboard layout. + * 0: US + * 1: Greek + * 2: German + * 3: Swedish-Pro + * 4: Dvorak-Qwerty Cmd + * 5: Thai + * 6: Arabic + * 7: French + * 8: Hebrew + * 9: Lithuanian + * 10: Norwegian + * 11: Spanish + * @param aOverrideKeyboard When testing set to TRUE, otherwise, set to + * FALSE. When TRUE, we use an ANSI keyboard + * instead of the actual keyboard. + */ + void InitByLayoutID(SInt32 aLayoutID, bool aOverrideKeyboard = false); + void InitByCurrentInputSource(); + void InitByCurrentKeyboardLayout(); + void InitByCurrentASCIICapableInputSource(); + void InitByCurrentASCIICapableKeyboardLayout(); + void InitByCurrentInputMethodKeyboardLayoutOverride(); + void InitByTISInputSourceRef(TISInputSourceRef aInputSource); + void InitByLanguage(CFStringRef aLanguage); + + /** + * If the instance is initialized with a keyboard layout input source, + * returns it. + * If the instance is initialized with an IME mode input source, the result + * references the keyboard layout for the IME mode. However, this can be + * initialized only when the IME mode is actually selected. I.e, if IME mode + * input source is initialized with LayoutID or SourceID, this returns null. + */ + TISInputSourceRef GetKeyboardLayoutInputSource() const + { + return mKeyboardLayout; + } + const UCKeyboardLayout* GetUCKeyboardLayout(); + + bool IsOpenedIMEMode(); + bool IsIMEMode(); + bool IsKeyboardLayout(); + + bool IsASCIICapable() + { + NS_ENSURE_TRUE(mInputSource, false); + return GetBoolProperty(kTISPropertyInputSourceIsASCIICapable); + } + + bool IsEnabled() + { + NS_ENSURE_TRUE(mInputSource, false); + return GetBoolProperty(kTISPropertyInputSourceIsEnabled); + } + + bool GetLanguageList(CFArrayRef &aLanguageList); + bool GetPrimaryLanguage(CFStringRef &aPrimaryLanguage); + bool GetPrimaryLanguage(nsAString &aPrimaryLanguage); + + bool GetLocalizedName(CFStringRef &aName) + { + NS_ENSURE_TRUE(mInputSource, false); + return GetStringProperty(kTISPropertyLocalizedName, aName); + } + + bool GetLocalizedName(nsAString &aName) + { + NS_ENSURE_TRUE(mInputSource, false); + return GetStringProperty(kTISPropertyLocalizedName, aName); + } + + bool GetInputSourceID(CFStringRef &aID) + { + NS_ENSURE_TRUE(mInputSource, false); + return GetStringProperty(kTISPropertyInputSourceID, aID); + } + + bool GetInputSourceID(nsAString &aID) + { + NS_ENSURE_TRUE(mInputSource, false); + return GetStringProperty(kTISPropertyInputSourceID, aID); + } + + bool GetBundleID(CFStringRef &aBundleID) + { + NS_ENSURE_TRUE(mInputSource, false); + return GetStringProperty(kTISPropertyBundleID, aBundleID); + } + + bool GetBundleID(nsAString &aBundleID) + { + NS_ENSURE_TRUE(mInputSource, false); + return GetStringProperty(kTISPropertyBundleID, aBundleID); + } + + bool GetInputSourceType(CFStringRef &aType) + { + NS_ENSURE_TRUE(mInputSource, false); + return GetStringProperty(kTISPropertyInputSourceType, aType); + } + + bool GetInputSourceType(nsAString &aType) + { + NS_ENSURE_TRUE(mInputSource, false); + return GetStringProperty(kTISPropertyInputSourceType, aType); + } + + bool IsForRTLLanguage(); + bool IsInitializedByCurrentInputSource(); + + enum { + // 40 is an actual result of the ::LMGetKbdType() when we connect an + // unknown keyboard and set the keyboard type to ANSI manually on the + // set up dialog. + eKbdType_ANSI = 40 + }; + + void Select(); + void Clear(); + + /** + * InitKeyEvent() initializes aKeyEvent for aNativeKeyEvent. + * + * @param aNativeKeyEvent A native key event for which you want to + * dispatch a Gecko key event. + * @param aKeyEvent The result -- a Gecko key event initialized + * from the native key event. + * @param aInsertString If caller expects that the event will cause + * a character to be input (say in an editor), + * the caller should set this. Otherwise, + * if caller sets null to this, this method will + * compute the character to be input from + * characters of aNativeKeyEvent. + */ + void InitKeyEvent(NSEvent *aNativeKeyEvent, WidgetKeyboardEvent& aKeyEvent, + const nsAString *aInsertString = nullptr); + + /** + * WillDispatchKeyboardEvent() computes aKeyEvent.mAlternativeCharCodes and + * recompute aKeyEvent.mCharCode if it's necessary. + * + * @param aNativeKeyEvent A native key event for which you want to + * dispatch a Gecko key event. + * @param aInsertString If caller expects that the event will cause + * a character to be input (say in an editor), + * the caller should set this. Otherwise, + * if caller sets null to this, this method will + * compute the character to be input from + * characters of aNativeKeyEvent. + * @param aKeyEvent The result -- a Gecko key event initialized + * from the native key event. This must be + * eKeyPress event. + */ + void WillDispatchKeyboardEvent(NSEvent* aNativeKeyEvent, + const nsAString* aInsertString, + WidgetKeyboardEvent& aKeyEvent); + + /** + * ComputeGeckoKeyCode() returns Gecko keycode for aNativeKeyCode on current + * keyboard layout. + * + * @param aNativeKeyCode A native keycode. + * @param aKbType A native Keyboard Type value. Typically, + * this is a result of ::LMGetKbdType(). + * @param aCmdIsPressed TRUE if Cmd key is pressed. Otherwise, FALSE. + * @return The computed Gecko keycode. + */ + uint32_t ComputeGeckoKeyCode(UInt32 aNativeKeyCode, UInt32 aKbType, + bool aCmdIsPressed); + + /** + * ComputeGeckoKeyNameIndex() returns Gecko key name index for the key. + * + * @param aNativeKeyCode A native keycode. + */ + static KeyNameIndex ComputeGeckoKeyNameIndex(UInt32 aNativeKeyCode); + + /** + * ComputeGeckoCodeNameIndex() returns Gecko code name index for the key. + * + * @param aNativeKeyCode A native keycode. + */ + static CodeNameIndex ComputeGeckoCodeNameIndex(UInt32 aNativeKeyCode); + +protected: + /** + * TranslateToString() computes the inputted text from the native keyCode, + * modifier flags and keyboard type. + * + * @param aKeyCode A native keyCode. + * @param aModifiers Combination of native modifier flags. + * @param aKbType A native Keyboard Type value. Typically, + * this is a result of ::LMGetKbdType(). + * @param aStr Result, i.e., inputted text. + * The result can be two or more characters. + * @return If succeeded, TRUE. Otherwise, FALSE. + * Even if TRUE, aStr can be empty string. + */ + bool TranslateToString(UInt32 aKeyCode, UInt32 aModifiers, + UInt32 aKbType, nsAString &aStr); + + /** + * TranslateToChar() computes the inputted character from the native keyCode, + * modifier flags and keyboard type. If two or more characters would be + * input, this returns 0. + * + * @param aKeyCode A native keyCode. + * @param aModifiers Combination of native modifier flags. + * @param aKbType A native Keyboard Type value. Typically, + * this is a result of ::LMGetKbdType(). + * @return If succeeded and the result is one character, + * returns the charCode of it. Otherwise, + * returns 0. + */ + uint32_t TranslateToChar(UInt32 aKeyCode, UInt32 aModifiers, UInt32 aKbType); + + /** + * ComputeInsertString() computes string to be inserted with the key event. + * + * @param aNativeKeyEvent The native key event which causes our keyboard + * event(s). + * @param aKeyEvent A Gecko key event which was partially + * initialized with aNativeKeyEvent. + * @param aInsertString The string to be inputting by aNativeKeyEvent. + * This should be specified by InsertText(). + * In other words, if the key event doesn't cause + * a call of InsertText(), this can be nullptr. + * @param aResult The string which should be set to charCode of + * keypress event(s). + */ + void ComputeInsertStringForCharCode(NSEvent* aNativeKeyEvent, + const WidgetKeyboardEvent& aKeyEvent, + const nsAString* aInsertString, + nsAString& aResult); + + /** + * IsPrintableKeyEvent() returns true if aNativeKeyEvent is caused by + * a printable key. Otherwise, returns false. + */ + bool IsPrintableKeyEvent(NSEvent* aNativeKeyEvent) const; + + /** + * GetKbdType() returns physical keyboard type. + */ + UInt32 GetKbdType() const; + + bool GetBoolProperty(const CFStringRef aKey); + bool GetStringProperty(const CFStringRef aKey, CFStringRef &aStr); + bool GetStringProperty(const CFStringRef aKey, nsAString &aStr); + + TISInputSourceRef mInputSource; + TISInputSourceRef mKeyboardLayout; + CFArrayRef mInputSourceList; + const UCKeyboardLayout* mUCKeyboardLayout; + int8_t mIsRTL; + + bool mOverrideKeyboard; + + static TISInputSourceWrapper* sCurrentInputSource; +}; + +/** + * TextInputHandlerBase is a base class of IMEInputHandler and TextInputHandler. + * Utility methods should be implemented this level. + */ + +class TextInputHandlerBase : public TextEventDispatcherListener +{ +public: + /** + * Other TextEventDispatcherListener methods should be implemented in + * IMEInputHandler. + */ + NS_DECL_ISUPPORTS + + /** + * DispatchEvent() dispatches aEvent on mWidget. + * + * @param aEvent An event which you want to dispatch. + * @return TRUE if the event is consumed by web contents + * or chrome contents. Otherwise, FALSE. + */ + bool DispatchEvent(WidgetGUIEvent& aEvent); + + /** + * SetSelection() dispatches eSetSelection event for the aRange. + * + * @param aRange The range which will be selected. + * @return TRUE if setting selection is succeeded and + * the widget hasn't been destroyed. + * Otherwise, FALSE. + */ + bool SetSelection(NSRange& aRange); + + /** + * InitKeyEvent() initializes aKeyEvent for aNativeKeyEvent. + * + * @param aNativeKeyEvent A native key event for which you want to + * dispatch a Gecko key event. + * @param aKeyEvent The result -- a Gecko key event initialized + * from the native key event. + * @param aInsertString If caller expects that the event will cause + * a character to be input (say in an editor), + * the caller should set this. Otherwise, + * if caller sets null to this, this method will + * compute the character to be input from + * characters of aNativeKeyEvent. + */ + void InitKeyEvent(NSEvent *aNativeKeyEvent, WidgetKeyboardEvent& aKeyEvent, + const nsAString *aInsertString = nullptr); + + /** + * SynthesizeNativeKeyEvent() is an implementation of + * nsIWidget::SynthesizeNativeKeyEvent(). See the document in nsIWidget.h + * for the detail. + */ + nsresult SynthesizeNativeKeyEvent(int32_t aNativeKeyboardLayout, + int32_t aNativeKeyCode, + uint32_t aModifierFlags, + const nsAString& aCharacters, + const nsAString& aUnmodifiedCharacters); + + /** + * Utility method intended for testing. Attempts to construct a native key + * event that would have been generated during an actual key press. This + * *does not dispatch* the native event. Instead, it is attached to the + * |mNativeKeyEvent| field of the Gecko event that is passed in. + * @param aKeyEvent Gecko key event to attach the native event to + */ + NS_IMETHOD AttachNativeKeyEvent(WidgetKeyboardEvent& aKeyEvent); + + /** + * GetWindowLevel() returns the window level of current focused (in Gecko) + * window. E.g., if an <input> element in XUL panel has focus, this returns + * the XUL panel's window level. + */ + NSInteger GetWindowLevel(); + + /** + * IsSpecialGeckoKey() checks whether aNativeKeyCode is mapped to a special + * Gecko keyCode. A key is "special" if it isn't used for text input. + * + * @param aNativeKeyCode A native keycode. + * @return If the keycode is mapped to a special key, + * TRUE. Otherwise, FALSE. + */ + static bool IsSpecialGeckoKey(UInt32 aNativeKeyCode); + + + /** + * EnableSecureEventInput() and DisableSecureEventInput() wrap the Carbon + * Event Manager APIs with the same names. In addition they keep track of + * how many times we've called them (in the same process) -- unlike the + * Carbon Event Manager APIs, which only keep track of how many times they've + * been called from any and all processes. + * + * The Carbon Event Manager's IsSecureEventInputEnabled() returns whether + * secure event input mode is enabled (in any process). This class's + * IsSecureEventInputEnabled() returns whether we've made any calls to + * EnableSecureEventInput() that are not (yet) offset by the calls we've + * made to DisableSecureEventInput(). + */ + static void EnableSecureEventInput(); + static void DisableSecureEventInput(); + static bool IsSecureEventInputEnabled(); + + /** + * EnsureSecureEventInputDisabled() calls DisableSecureEventInput() until + * our call count becomes 0. + */ + static void EnsureSecureEventInputDisabled(); + +public: + /** + * mWidget must not be destroyed without OnDestroyWidget being called. + * + * @param aDestroyingWidget Destroying widget. This might not be mWidget. + * @return This result doesn't have any meaning for + * callers. When aDstroyingWidget isn't the same + * as mWidget, FALSE. Then, inherited methods in + * sub classes should return from this method + * without cleaning up. + */ + virtual bool OnDestroyWidget(nsChildView* aDestroyingWidget); + +protected: + // The creator of this instance, client and its text event dispatcher. + // These members must not be nullptr after initialized until + // OnDestroyWidget() is called. + nsChildView* mWidget; // [WEAK] + RefPtr<TextEventDispatcher> mDispatcher; + + // The native view for mWidget. + // This view handles the actual text inputting. + NSView<mozView>* mView; // [STRONG] + + TextInputHandlerBase(nsChildView* aWidget, NSView<mozView> *aNativeView); + virtual ~TextInputHandlerBase(); + + bool Destroyed() { return !mWidget; } + + /** + * mCurrentKeyEvent indicates what key event we are handling. While + * handling a native keydown event, we need to store the event for insertText, + * doCommandBySelector and various action message handlers of NSResponder + * such as [NSResponder insertNewline:sender]. + */ + struct KeyEventState + { + // Handling native key event + NSEvent* mKeyEvent; + // String specified by InsertText(). This is not null only during a + // call of InsertText(). + nsAString* mInsertString; + // String which are included in [mKeyEvent characters] and already handled + // by InsertText() call(s). + nsString mInsertedString; + // Whether keydown event was consumed by web contents or chrome contents. + bool mKeyDownHandled; + // Whether keypress event was dispatched for mKeyEvent. + bool mKeyPressDispatched; + // Whether keypress event was consumed by web contents or chrome contents. + bool mKeyPressHandled; + // Whether the key event causes other key events via IME or something. + bool mCausedOtherKeyEvents; + // Whether the key event causes composition change or committing + // composition. So, even if InsertText() is called, this may be false + // if it dispatches keypress event. + bool mCompositionDispatched; + + KeyEventState() : mKeyEvent(nullptr) + { + Clear(); + } + + explicit KeyEventState(NSEvent* aNativeKeyEvent) : mKeyEvent(nullptr) + { + Clear(); + Set(aNativeKeyEvent); + } + + KeyEventState(const KeyEventState &aOther) = delete; + + ~KeyEventState() + { + Clear(); + } + + void Set(NSEvent* aNativeKeyEvent) + { + NS_PRECONDITION(aNativeKeyEvent, "aNativeKeyEvent must not be NULL"); + Clear(); + mKeyEvent = [aNativeKeyEvent retain]; + } + + void Clear() + { + if (mKeyEvent) { + [mKeyEvent release]; + mKeyEvent = nullptr; + } + mInsertString = nullptr; + mInsertedString.Truncate(); + mKeyDownHandled = false; + mKeyPressDispatched = false; + mKeyPressHandled = false; + mCausedOtherKeyEvents = false; + mCompositionDispatched = false; + } + + bool IsDefaultPrevented() const + { + return mKeyDownHandled || mKeyPressHandled || mCausedOtherKeyEvents || + mCompositionDispatched; + } + + bool CanDispatchKeyPressEvent() const + { + return !mKeyPressDispatched && !IsDefaultPrevented(); + } + + void InitKeyEvent(TextInputHandlerBase* aHandler, + WidgetKeyboardEvent& aKeyEvent); + + /** + * GetUnhandledString() returns characters of the event which have not been + * handled with InsertText() yet. For example, if there is a composition + * caused by a dead key press like '`' and it's committed by some key + * combinations like |Cmd+v|, then, the |v|'s KeyDown event's |characters| + * is |`v|. Then, after |`| is committed with a call of InsertString(), + * this returns only 'v'. + */ + void GetUnhandledString(nsAString& aUnhandledString) const; + }; + + /** + * Helper classes for guaranteeing cleaning mCurrentKeyEvent + */ + class AutoKeyEventStateCleaner + { + public: + explicit AutoKeyEventStateCleaner(TextInputHandlerBase* aHandler) : + mHandler(aHandler) + { + } + + ~AutoKeyEventStateCleaner() + { + mHandler->RemoveCurrentKeyEvent(); + } + private: + RefPtr<TextInputHandlerBase> mHandler; + }; + + class MOZ_STACK_CLASS AutoInsertStringClearer + { + public: + explicit AutoInsertStringClearer(KeyEventState* aState) + : mState(aState) + { + } + ~AutoInsertStringClearer(); + + private: + KeyEventState* mState; + }; + + /** + * mCurrentKeyEvents stores all key events which are being processed. + * When we call interpretKeyEvents, IME may generate other key events. + * mCurrentKeyEvents[0] is the latest key event. + */ + nsTArray<KeyEventState*> mCurrentKeyEvents; + + /** + * mFirstKeyEvent must be used for first key event. This member prevents + * memory fragmentation for most key events. + */ + KeyEventState mFirstKeyEvent; + + /** + * PushKeyEvent() adds the current key event to mCurrentKeyEvents. + */ + KeyEventState* PushKeyEvent(NSEvent* aNativeKeyEvent) + { + uint32_t nestCount = mCurrentKeyEvents.Length(); + for (uint32_t i = 0; i < nestCount; i++) { + // When the key event is caused by another key event, all key events + // which are being handled should be marked as "consumed". + mCurrentKeyEvents[i]->mCausedOtherKeyEvents = true; + } + + KeyEventState* keyEvent = nullptr; + if (nestCount == 0) { + mFirstKeyEvent.Set(aNativeKeyEvent); + keyEvent = &mFirstKeyEvent; + } else { + keyEvent = new KeyEventState(aNativeKeyEvent); + } + return *mCurrentKeyEvents.AppendElement(keyEvent); + } + + /** + * RemoveCurrentKeyEvent() removes the current key event from + * mCurrentKeyEvents. + */ + void RemoveCurrentKeyEvent() + { + NS_ASSERTION(mCurrentKeyEvents.Length() > 0, + "RemoveCurrentKeyEvent() is called unexpectedly"); + KeyEventState* keyEvent = GetCurrentKeyEvent(); + mCurrentKeyEvents.RemoveElementAt(mCurrentKeyEvents.Length() - 1); + if (keyEvent == &mFirstKeyEvent) { + keyEvent->Clear(); + } else { + delete keyEvent; + } + } + + /** + * GetCurrentKeyEvent() returns current processing key event. + */ + KeyEventState* GetCurrentKeyEvent() + { + if (mCurrentKeyEvents.Length() == 0) { + return nullptr; + } + return mCurrentKeyEvents[mCurrentKeyEvents.Length() - 1]; + } + + struct KeyboardLayoutOverride final + { + int32_t mKeyboardLayout; + bool mOverrideEnabled; + + KeyboardLayoutOverride() : + mKeyboardLayout(0), mOverrideEnabled(false) + { + } + }; + + const KeyboardLayoutOverride& KeyboardLayoutOverrideRef() const + { + return mKeyboardOverride; + } + + /** + * IsPrintableChar() checks whether the unicode character is + * a non-printable ASCII character or not. Note that this returns + * TRUE even if aChar is a non-printable UNICODE character. + * + * @param aChar A unicode character. + * @return TRUE if aChar is a printable ASCII character + * or a unicode character. Otherwise, i.e, + * if aChar is a non-printable ASCII character, + * FALSE. + */ + static bool IsPrintableChar(char16_t aChar); + + /** + * IsNormalCharInputtingEvent() checks whether aKeyEvent causes text input. + * + * @param aKeyEvent A key event. + * @return TRUE if the key event causes text input. + * Otherwise, FALSE. + */ + static bool IsNormalCharInputtingEvent(const WidgetKeyboardEvent& aKeyEvent); + + /** + * IsModifierKey() checks whether the native keyCode is for a modifier key. + * + * @param aNativeKeyCode A native keyCode. + * @return TRUE if aNativeKeyCode is for a modifier key. + * Otherwise, FALSE. + */ + static bool IsModifierKey(UInt32 aNativeKeyCode); + +private: + KeyboardLayoutOverride mKeyboardOverride; + + static int32_t sSecureEventInputCount; +}; + +/** + * IMEInputHandler manages: + * 1. The IME/keyboard layout statement of nsChildView. + * 2. The IME composition statement of nsChildView. + * And also provides the methods which controls the current IME transaction of + * the instance. + * + * Note that an nsChildView handles one or more NSView's events. E.g., even if + * a text editor on XUL panel element, the input events handled on the parent + * (or its ancestor) widget handles it (the native focus is set to it). The + * actual focused view is notified by OnFocusChangeInGecko. + */ + +class IMEInputHandler : public TextInputHandlerBase +{ +public: + // TextEventDispatcherListener methods + NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher, + const IMENotification& aNotification) override; + NS_IMETHOD_(void) OnRemovedFrom( + TextEventDispatcher* aTextEventDispatcher) override; + NS_IMETHOD_(void) WillDispatchKeyboardEvent( + TextEventDispatcher* aTextEventDispatcher, + WidgetKeyboardEvent& aKeyboardEvent, + uint32_t aIndexOfKeypress, + void* aData) override; + +public: + virtual bool OnDestroyWidget(nsChildView* aDestroyingWidget) override; + + virtual void OnFocusChangeInGecko(bool aFocus); + + void OnSelectionChange(const IMENotification& aIMENotification); + + /** + * Call [NSTextInputContext handleEvent] for mouse event support of IME + */ + bool OnHandleEvent(NSEvent* aEvent); + + /** + * SetMarkedText() is a handler of setMarkedText of NSTextInput. + * + * @param aAttrString This mut be an instance of NSAttributedString. + * If the aString parameter to + * [ChildView setMarkedText:setSelectedRange:] + * isn't an instance of NSAttributedString, + * create an NSAttributedString from it and pass + * that instead. + * @param aSelectedRange Current selected range (or caret position). + * @param aReplacementRange The range which will be replaced with the + * aAttrString instead of current marked range. + */ + void SetMarkedText(NSAttributedString* aAttrString, + NSRange& aSelectedRange, + NSRange* aReplacementRange = nullptr); + + /** + * GetAttributedSubstringFromRange() returns an NSAttributedString instance + * which is allocated as autorelease for aRange. + * + * @param aRange The range of string which you want. + * @param aActualRange The actual range of the result. + * @return The string in aRange. If the string is empty, + * this returns nil. If succeeded, this returns + * an instance which is allocated as autorelease. + * If this has some troubles, returns nil. + */ + NSAttributedString* GetAttributedSubstringFromRange( + NSRange& aRange, + NSRange* aActualRange = nullptr); + + /** + * SelectedRange() returns current selected range. + * + * @return If an editor has focus, this returns selection + * range in the editor. Otherwise, this returns + * selection range in the focused document. + */ + NSRange SelectedRange(); + + /** + * DrawsVerticallyForCharacterAtIndex() returns whether the character at + * the given index is being rendered vertically. + * + * @param aCharIndex The character offset to query. + * + * @return True if writing-mode is vertical at the given + * character offset; otherwise false. + */ + bool DrawsVerticallyForCharacterAtIndex(uint32_t aCharIndex); + + /** + * FirstRectForCharacterRange() returns first *character* rect in the range. + * Cocoa needs the first line rect in the range, but we cannot compute it + * on current implementation. + * + * @param aRange A range of text to examine. Its position is + * an offset from the beginning of the focused + * editor or document. + * @param aActualRange If this is not null, this returns the actual + * range used for computing the result. + * @return An NSRect containing the first character in + * aRange, in screen coordinates. + * If the length of aRange is 0, the width will + * be 0. + */ + NSRect FirstRectForCharacterRange(NSRange& aRange, + NSRange* aActualRange = nullptr); + + /** + * CharacterIndexForPoint() returns an offset of a character at aPoint. + * XXX This isn't implemented, always returns 0. + * + * @param The point in screen coordinates. + * @return The offset of the character at aPoint from + * the beginning of the focused editor or + * document. + */ + NSUInteger CharacterIndexForPoint(NSPoint& aPoint); + + /** + * GetValidAttributesForMarkedText() returns attributes which we support. + * + * @return Always empty array for now. + */ + NSArray* GetValidAttributesForMarkedText(); + + bool HasMarkedText(); + NSRange MarkedRange(); + + bool IsIMEComposing() { return mIsIMEComposing; } + bool IsIMEOpened(); + bool IsIMEEnabled() { return mIsIMEEnabled; } + bool IsASCIICapableOnly() { return mIsASCIICapableOnly; } + bool IgnoreIMECommit() { return mIgnoreIMECommit; } + + bool IgnoreIMEComposition() + { + // Ignore the IME composition events when we're pending to discard the + // composition and we are not to handle the IME composition now. + return (mPendingMethods & kDiscardIMEComposition) && + (mIsInFocusProcessing || !IsFocused()); + } + + void CommitIMEComposition(); + void CancelIMEComposition(); + + void EnableIME(bool aEnableIME); + void SetIMEOpenState(bool aOpen); + void SetASCIICapableOnly(bool aASCIICapableOnly); + + /** + * True if OSX believes that our view has keyboard focus. + */ + bool IsFocused(); + + static CFArrayRef CreateAllIMEModeList(); + static void DebugPrintAllIMEModes(); + + // Don't use ::TSMGetActiveDocument() API directly, the document may not + // be what you want. + static TSMDocumentID GetCurrentTSMDocumentID(); + +protected: + // We cannot do some jobs in the given stack by some reasons. + // Following flags and the timer provide the execution pending mechanism, + // See the comment in nsCocoaTextInputHandler.mm. + nsCOMPtr<nsITimer> mTimer; + enum { + kNotifyIMEOfFocusChangeInGecko = 1, + kDiscardIMEComposition = 2, + kSyncASCIICapableOnly = 4 + }; + uint32_t mPendingMethods; + + IMEInputHandler(nsChildView* aWidget, NSView<mozView> *aNativeView); + virtual ~IMEInputHandler(); + + void ResetTimer(); + + virtual void ExecutePendingMethods(); + + /** + * InsertTextAsCommittingComposition() commits current composition. If there + * is no composition, this starts a composition and commits it immediately. + * + * @param aAttrString A string which is committed. + * @param aReplacementRange The range which will be replaced with the + * aAttrString instead of current selection. + */ + void InsertTextAsCommittingComposition(NSAttributedString* aAttrString, + NSRange* aReplacementRange); + +private: + // If mIsIMEComposing is true, the composition string is stored here. + NSString* mIMECompositionString; + // If mIsIMEComposing is true, the start offset of the composition string. + uint32_t mIMECompositionStart; + + NSRange mMarkedRange; + NSRange mSelectedRange; + + NSRange mRangeForWritingMode; // range within which mWritingMode applies + mozilla::WritingMode mWritingMode; + + bool mIsIMEComposing; + bool mIsIMEEnabled; + bool mIsASCIICapableOnly; + bool mIgnoreIMECommit; + // This flag is enabled by OnFocusChangeInGecko, and will be cleared by + // ExecutePendingMethods. When this is true, IsFocus() returns TRUE. At + // that time, the focus processing in Gecko might not be finished yet. So, + // you cannot use WidgetQueryContentEvent or something. + bool mIsInFocusProcessing; + bool mIMEHasFocus; + + void KillIMEComposition(); + void SendCommittedText(NSString *aString); + void OpenSystemPreferredLanguageIME(); + + // Pending methods + void NotifyIMEOfFocusChangeInGecko(); + void DiscardIMEComposition(); + void SyncASCIICapableOnly(); + + static bool sStaticMembersInitialized; + static CFStringRef sLatestIMEOpenedModeInputSourceID; + static void InitStaticMembers(); + static void OnCurrentTextInputSourceChange(CFNotificationCenterRef aCenter, + void* aObserver, + CFStringRef aName, + const void* aObject, + CFDictionaryRef aUserInfo); + + static void FlushPendingMethods(nsITimer* aTimer, void* aClosure); + + /** + * ConvertToTextRangeStyle converts the given native underline style to + * our defined text range type. + * + * @param aUnderlineStyle NSUnderlineStyleSingle or + * NSUnderlineStyleThick. + * @param aSelectedRange Current selected range (or caret position). + * @return NS_TEXTRANGE_*. + */ + TextRangeType ConvertToTextRangeType(uint32_t aUnderlineStyle, + NSRange& aSelectedRange); + + /** + * GetRangeCount() computes the range count of aAttrString. + * + * @param aAttrString An NSAttributedString instance whose number of + * NSUnderlineStyleAttributeName ranges you with + * to know. + * @return The count of NSUnderlineStyleAttributeName + * ranges in aAttrString. + */ + uint32_t GetRangeCount(NSAttributedString *aString); + + /** + * CreateTextRangeArray() returns text ranges for clauses and/or caret. + * + * @param aAttrString An NSAttributedString instance which indicates + * current composition string. + * @param aSelectedRange Current selected range (or caret position). + * @return The result is set to the + * NSUnderlineStyleAttributeName ranges in + * aAttrString. + */ + already_AddRefed<mozilla::TextRangeArray> + CreateTextRangeArray(NSAttributedString *aAttrString, + NSRange& aSelectedRange); + + /** + * DispatchCompositionStartEvent() dispatches a compositionstart event and + * initializes the members indicating composition state. + * + * @return true if it can continues handling composition. + * Otherwise, e.g., canceled by the web page, + * this returns false. + */ + bool DispatchCompositionStartEvent(); + + /** + * DispatchCompositionChangeEvent() dispatches a compositionchange event on + * mWidget and modifies the members indicating composition state. + * + * @param aText User text input. + * @param aAttrString An NSAttributedString instance which indicates + * current composition string. + * @param aSelectedRange Current selected range (or caret position). + * + * @return true if it can continues handling composition. + * Otherwise, e.g., canceled by the web page, + * this returns false. + */ + bool DispatchCompositionChangeEvent(const nsString& aText, + NSAttributedString* aAttrString, + NSRange& aSelectedRange); + + /** + * DispatchCompositionCommitEvent() dispatches a compositioncommit event or + * compositioncommitasis event. If aCommitString is null, dispatches + * compositioncommitasis event. I.e., if aCommitString is null, this + * commits the composition with the last data. Otherwise, commits the + * composition with aCommitString value. + * + * @return true if the widget isn't destroyed. + * Otherwise, false. + */ + bool DispatchCompositionCommitEvent(const nsAString* aCommitString = nullptr); + + // The focused IME handler. Please note that the handler might lost the + // actual focus by deactivating the application. If we are active, this + // must have the actual focused handle. + // We cannot access to the NSInputManager during we aren't active, so, the + // focused handler can have an IME transaction even if we are deactive. + static IMEInputHandler* sFocusedIMEHandler; + + static bool sCachedIsForRTLLangage; +}; + +/** + * TextInputHandler implements the NSTextInput protocol. + */ +class TextInputHandler : public IMEInputHandler +{ +public: + static NSUInteger sLastModifierState; + + static CFArrayRef CreateAllKeyboardLayoutList(); + static void DebugPrintAllKeyboardLayouts(); + + TextInputHandler(nsChildView* aWidget, NSView<mozView> *aNativeView); + virtual ~TextInputHandler(); + + /** + * KeyDown event handler. + * + * @param aNativeEvent A native NSKeyDown event. + * @return TRUE if the event is consumed by web contents + * or chrome contents. Otherwise, FALSE. + */ + bool HandleKeyDownEvent(NSEvent* aNativeEvent); + + /** + * KeyUp event handler. + * + * @param aNativeEvent A native NSKeyUp event. + */ + void HandleKeyUpEvent(NSEvent* aNativeEvent); + + /** + * FlagsChanged event handler. + * + * @param aNativeEvent A native NSFlagsChanged event. + */ + void HandleFlagsChanged(NSEvent* aNativeEvent); + + /** + * Insert the string to content. I.e., this is a text input event handler. + * If this is called during keydown event handling, this may dispatch a + * eKeyPress event. If this is called during composition, this commits + * the composition by the aAttrString. + * + * @param aAttrString An inserted string. + * @param aReplacementRange The range which will be replaced with the + * aAttrString instead of current selection. + */ + void InsertText(NSAttributedString *aAttrString, + NSRange* aReplacementRange = nullptr); + + /** + * doCommandBySelector event handler. + * + * @param aSelector A selector of the command. + * @return TRUE if the command is consumed. Otherwise, + * FALSE. + */ + bool DoCommandBySelector(const char* aSelector); + + /** + * KeyPressWasHandled() checks whether keypress event was handled or not. + * + * @return TRUE if keypress event for latest native key + * event was handled. Otherwise, FALSE. + * If this handler isn't handling any key events, + * always returns FALSE. + */ + bool KeyPressWasHandled() + { + KeyEventState* currentKeyEvent = GetCurrentKeyEvent(); + return currentKeyEvent && currentKeyEvent->mKeyPressHandled; + } + +protected: + // Stores the association of device dependent modifier flags with a modifier + // keyCode. Being device dependent, this association may differ from one kind + // of hardware to the next. + struct ModifierKey + { + NSUInteger flags; + unsigned short keyCode; + + ModifierKey(NSUInteger aFlags, unsigned short aKeyCode) : + flags(aFlags), keyCode(aKeyCode) + { + } + + NSUInteger GetDeviceDependentFlags() const + { + return (flags & ~NSDeviceIndependentModifierFlagsMask); + } + + NSUInteger GetDeviceIndependentFlags() const + { + return (flags & NSDeviceIndependentModifierFlagsMask); + } + }; + typedef nsTArray<ModifierKey> ModifierKeyArray; + ModifierKeyArray mModifierKeys; + + /** + * GetModifierKeyForNativeKeyCode() returns the stored ModifierKey for + * the key. + */ + const ModifierKey* + GetModifierKeyForNativeKeyCode(unsigned short aKeyCode) const; + + /** + * GetModifierKeyForDeviceDependentFlags() returns the stored ModifierKey for + * the device dependent flags. + */ + const ModifierKey* + GetModifierKeyForDeviceDependentFlags(NSUInteger aFlags) const; + + /** + * DispatchKeyEventForFlagsChanged() dispatches keydown event or keyup event + * for the aNativeEvent. + * + * @param aNativeEvent A native flagschanged event which you want to + * dispatch our key event for. + * @param aDispatchKeyDown TRUE if you want to dispatch a keydown event. + * Otherwise, i.e., to dispatch keyup event, + * FALSE. + */ + void DispatchKeyEventForFlagsChanged(NSEvent* aNativeEvent, + bool aDispatchKeyDown); +}; + +} // namespace widget +} // namespace mozilla + +#endif // TextInputHandler_h_ |