/* -*- Mode: C++; tab-width: 40; 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/. */ #ifndef mozilla_widget_IMEData_h_ #define mozilla_widget_IMEData_h_ #include "mozilla/CheckedInt.h" #include "mozilla/EventForwards.h" #include "mozilla/NativeKeyBindingsType.h" #include "mozilla/ToString.h" #include "nsCOMPtr.h" #include "nsIURI.h" #include "nsPoint.h" #include "nsRect.h" #include "nsString.h" #include "nsXULAppAPI.h" #include "Units.h" class nsIWidget; namespace mozilla { class ContentSelection; class WritingMode; template class Maybe; // Helper class to logging string which may contain various Unicode characters // and/or may be too long string for logging. class MOZ_STACK_CLASS PrintStringDetail : public nsAutoCString { public: static constexpr uint32_t kMaxLengthForCompositionString = 8; static constexpr uint32_t kMaxLengthForSelectedString = 12; static constexpr uint32_t kMaxLengthForEditor = 20; PrintStringDetail() = delete; explicit PrintStringDetail(const nsAString& aString, uint32_t aMaxLength = UINT32_MAX); template explicit PrintStringDetail(const Maybe& aMaybeString, uint32_t aMaxLength = UINT32_MAX); private: static nsCString PrintCharData(char32_t aChar); }; // StartAndEndOffsets represents a range in flat-text. template class StartAndEndOffsets { protected: static IntType MaxOffset() { return std::numeric_limits::max(); } public: StartAndEndOffsets() = delete; explicit StartAndEndOffsets(IntType aStartOffset, IntType aEndOffset) : mStartOffset(aStartOffset), mEndOffset(aStartOffset <= aEndOffset ? aEndOffset : aStartOffset) { MOZ_ASSERT(aStartOffset <= mEndOffset); } IntType StartOffset() const { return mStartOffset; } IntType Length() const { return mEndOffset - mStartOffset; } IntType EndOffset() const { return mEndOffset; } bool IsOffsetInRange(IntType aOffset) const { return aOffset >= mStartOffset && aOffset < mEndOffset; } bool IsOffsetInRangeOrEndOffset(IntType aOffset) const { return aOffset >= mStartOffset && aOffset <= mEndOffset; } void MoveTo(IntType aNewStartOffset) { auto delta = static_cast(mStartOffset) - aNewStartOffset; mStartOffset += delta; mEndOffset += delta; } void SetOffsetAndLength(IntType aNewOffset, IntType aNewLength) { mStartOffset = aNewOffset; CheckedInt endOffset(aNewOffset + aNewLength); mEndOffset = endOffset.isValid() ? endOffset.value() : MaxOffset(); } void SetEndOffset(IntType aEndOffset) { MOZ_ASSERT(mStartOffset <= aEndOffset); mEndOffset = std::max(aEndOffset, mStartOffset); } void SetStartAndEndOffsets(IntType aStartOffset, IntType aEndOffset) { MOZ_ASSERT(aStartOffset <= aEndOffset); mStartOffset = aStartOffset; mEndOffset = aStartOffset <= aEndOffset ? aEndOffset : aStartOffset; } void SetLength(IntType aNewLength) { CheckedInt endOffset(mStartOffset + aNewLength); mEndOffset = endOffset.isValid() ? endOffset.value() : MaxOffset(); } friend std::ostream& operator<<( std::ostream& aStream, const StartAndEndOffsets& aStartAndEndOffsets) { aStream << "{ mStartOffset=" << aStartAndEndOffsets.mStartOffset << ", mEndOffset=" << aStartAndEndOffsets.mEndOffset << ", Length()=" << aStartAndEndOffsets.Length() << " }"; return aStream; } private: IntType mStartOffset; IntType mEndOffset; }; // OffsetAndData class is designed for storing composition string and its // start offset. Length() and EndOffset() return only valid length or // offset. I.e., if the string is too long for inserting at the offset, // the length is shrunken. However, the string itself is not shrunken. // Therefore, moving it to where all of the string can be contained, // they will return longer/bigger value. enum class OffsetAndDataFor { CompositionString, SelectedString, EditorString, }; template class OffsetAndData { protected: static IntType MaxOffset() { return std::numeric_limits::max(); } public: OffsetAndData() = delete; explicit OffsetAndData( IntType aStartOffset, const nsAString& aData, OffsetAndDataFor aFor = OffsetAndDataFor::CompositionString) : mData(aData), mOffset(aStartOffset), mFor(aFor) {} bool IsValid() const { CheckedInt offset(mOffset); offset += mData.Length(); return offset.isValid(); } IntType StartOffset() const { return mOffset; } IntType Length() const { CheckedInt endOffset(CheckedInt(mOffset) + mData.Length()); return endOffset.isValid() ? mData.Length() : MaxOffset() - mOffset; } IntType EndOffset() const { return mOffset + Length(); } StartAndEndOffsets CreateStartAndEndOffsets() const { return StartAndEndOffsets(StartOffset(), EndOffset()); } const nsString& DataRef() const { // In strictly speaking, we should return substring which may be shrunken // for rounding to the max offset. However, it's unrealistic edge case, // and creating new string is not so cheap job in a hot path. Therefore, // this just returns the data as-is. return mData; } bool IsDataEmpty() const { return mData.IsEmpty(); } bool IsOffsetInRange(IntType aOffset) const { return aOffset >= mOffset && aOffset < EndOffset(); } bool IsOffsetInRangeOrEndOffset(IntType aOffset) const { return aOffset >= mOffset && aOffset <= EndOffset(); } void Collapse(IntType aOffset) { mOffset = aOffset; mData.Truncate(); } void MoveTo(IntType aNewOffset) { mOffset = aNewOffset; } void SetOffsetAndData(IntType aStartOffset, const nsAString& aData) { mOffset = aStartOffset; mData = aData; } void SetData(const nsAString& aData) { mData = aData; } void TruncateData(uint32_t aLength = 0) { mData.Truncate(aLength); } void ReplaceData(nsAString::size_type aCutStart, nsAString::size_type aCutLength, const nsAString& aNewString) { mData.Replace(aCutStart, aCutLength, aNewString); } friend std::ostream& operator<<( std::ostream& aStream, const OffsetAndData& aOffsetAndData) { const auto maxDataLength = aOffsetAndData.mFor == OffsetAndDataFor::CompositionString ? PrintStringDetail::kMaxLengthForCompositionString : (aOffsetAndData.mFor == OffsetAndDataFor::SelectedString ? PrintStringDetail::kMaxLengthForSelectedString : PrintStringDetail::kMaxLengthForEditor); aStream << "{ mOffset=" << aOffsetAndData.mOffset << ", mData=" << PrintStringDetail(aOffsetAndData.mData, maxDataLength).get() << ", Length()=" << aOffsetAndData.Length() << ", EndOffset()=" << aOffsetAndData.EndOffset() << " }"; return aStream; } private: nsString mData; IntType mOffset; OffsetAndDataFor mFor; }; namespace widget { /** * Preference for receiving IME updates * * If mWantUpdates is not NOTIFY_NOTHING, nsTextStateManager will observe text * change and/or selection change and call nsIWidget::NotifyIME() with * NOTIFY_IME_OF_SELECTION_CHANGE and/or NOTIFY_IME_OF_TEXT_CHANGE. * Please note that the text change observing cost is very expensive especially * on an HTML editor has focus. * If the IME implementation on a particular platform doesn't care about * NOTIFY_IME_OF_SELECTION_CHANGE and/or NOTIFY_IME_OF_TEXT_CHANGE, * they should set mWantUpdates to NOTIFY_NOTHING to avoid the cost. * If the IME implementation needs notifications even while our process is * deactive, it should also set NOTIFY_DURING_DEACTIVE. */ struct IMENotificationRequests final { typedef uint8_t Notifications; enum : Notifications { NOTIFY_NOTHING = 0, NOTIFY_TEXT_CHANGE = 1 << 1, NOTIFY_POSITION_CHANGE = 1 << 2, // NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR is used when mouse button is pressed // or released on a character in the focused editor. The notification is // notified to IME as a mouse event. If it's consumed by IME, NotifyIME() // returns NS_SUCCESS_EVENT_CONSUMED. Otherwise, it returns NS_OK if it's // handled without any error. NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR = 1 << 3, // NOTE: NOTIFY_DURING_DEACTIVE isn't supported in environments where two // or more compositions are possible. E.g., Mac and Linux (GTK). NOTIFY_DURING_DEACTIVE = 1 << 7, NOTIFY_ALL = NOTIFY_TEXT_CHANGE | NOTIFY_POSITION_CHANGE | NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR, }; IMENotificationRequests() : mWantUpdates(NOTIFY_NOTHING) {} explicit IMENotificationRequests(Notifications aWantUpdates) : mWantUpdates(aWantUpdates) {} IMENotificationRequests operator|( const IMENotificationRequests& aOther) const { return IMENotificationRequests(aOther.mWantUpdates | mWantUpdates); } IMENotificationRequests& operator|=(const IMENotificationRequests& aOther) { mWantUpdates |= aOther.mWantUpdates; return *this; } bool operator==(const IMENotificationRequests& aOther) const { return mWantUpdates == aOther.mWantUpdates; } bool WantTextChange() const { return !!(mWantUpdates & NOTIFY_TEXT_CHANGE); } bool WantPositionChanged() const { return !!(mWantUpdates & NOTIFY_POSITION_CHANGE); } bool WantChanges() const { return WantTextChange(); } bool WantMouseButtonEventOnChar() const { return !!(mWantUpdates & NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR); } bool WantDuringDeactive() const { return !!(mWantUpdates & NOTIFY_DURING_DEACTIVE); } Notifications mWantUpdates; }; /** * IME enabled states. * * WARNING: If you change these values, you also need to edit: * nsIDOMWindowUtils.idl */ enum class IMEEnabled { /** * 'Disabled' means the user cannot use IME. So, the IME open state should * be 'closed' during 'disabled'. */ Disabled, /** * 'Enabled' means the user can use IME. */ Enabled, /** * 'Password' state is a special case for the password editors. * E.g., on mac, the password editors should disable the non-Roman * keyboard layouts at getting focus. Thus, the password editor may have * special rules on some platforms. */ Password, /** * 'Unknown' is useful when you cache this enum. So, this shouldn't be * used with nsIWidget::SetInputContext(). */ Unknown, }; /** * Contains IMEStatus plus information about the current * input context that the IME can use as hints if desired. */ struct IMEState final { IMEEnabled mEnabled; /** * IME open states the mOpen value of SetInputContext() should be one value of * OPEN, CLOSE or DONT_CHANGE_OPEN_STATE. GetInputContext() should return * OPEN, CLOSE or OPEN_STATE_NOT_SUPPORTED. */ enum Open { /** * 'Unsupported' means the platform cannot return actual IME open state. * This value is used only by GetInputContext(). */ OPEN_STATE_NOT_SUPPORTED, /** * 'Don't change' means the widget shouldn't change IME open state when * SetInputContext() is called. */ DONT_CHANGE_OPEN_STATE = OPEN_STATE_NOT_SUPPORTED, /** * 'Open' means that IME should compose in its primary language (or latest * input mode except direct ASCII character input mode). Even if IME is * opened by this value, users should be able to close IME by theirselves. * Web contents can specify this value by |ime-mode: active;|. */ OPEN, /** * 'Closed' means that IME shouldn't handle key events (or should handle * as ASCII character inputs on mobile device). Even if IME is closed by * this value, users should be able to open IME by theirselves. * Web contents can specify this value by |ime-mode: inactive;|. */ CLOSED }; Open mOpen; IMEState() : mEnabled(IMEEnabled::Enabled), mOpen(DONT_CHANGE_OPEN_STATE) {} explicit IMEState(IMEEnabled aEnabled, Open aOpen = DONT_CHANGE_OPEN_STATE) : mEnabled(aEnabled), mOpen(aOpen) {} // Returns true if the user can input characters. // This means that a plain text editor, an HTML editor, a password editor or // a plain text editor whose ime-mode is "disabled". bool IsEditable() const { return mEnabled == IMEEnabled::Enabled || mEnabled == IMEEnabled::Password; } }; // NS_ONLY_ONE_NATIVE_IME_CONTEXT is a special value of native IME context. // If there can be only one IME composition in a process, this can be used. #define NS_ONLY_ONE_NATIVE_IME_CONTEXT \ (reinterpret_cast(static_cast(-1))) struct NativeIMEContext final { // Pointer to native IME context. Typically this is the result of // nsIWidget::GetNativeData(NS_RAW_NATIVE_IME_CONTEXT) in the parent process. // See also NS_ONLY_ONE_NATIVE_IME_CONTEXT. uintptr_t mRawNativeIMEContext; // Process ID of the origin of mNativeIMEContext. // static_cast(-1) if the instance is not initialized properly. // 0 if the instance is originated in the parent process. // 1 or greater if the instance is originated in a content process. uint64_t mOriginProcessID; NativeIMEContext() : mRawNativeIMEContext(0), mOriginProcessID(0) { Init(nullptr); } explicit NativeIMEContext(nsIWidget* aWidget) : mRawNativeIMEContext(0), mOriginProcessID(0) { Init(aWidget); } bool IsValid() const { return mRawNativeIMEContext && mOriginProcessID != static_cast(-1); } bool IsOriginatedInParentProcess() const { return mOriginProcessID != 0 && mOriginProcessID != static_cast(-1); } void Init(nsIWidget* aWidget); void InitWithRawNativeIMEContext(const void* aRawNativeIMEContext) { InitWithRawNativeIMEContext(const_cast(aRawNativeIMEContext)); } void InitWithRawNativeIMEContext(void* aRawNativeIMEContext); bool operator==(const NativeIMEContext& aOther) const { return mRawNativeIMEContext == aOther.mRawNativeIMEContext && mOriginProcessID == aOther.mOriginProcessID; } bool operator!=(const NativeIMEContext& aOther) const { return !(*this == aOther); } }; struct InputContext final { InputContext() : mOrigin(XRE_IsParentProcess() ? ORIGIN_MAIN : ORIGIN_CONTENT), mHasHandledUserInput(false), mInPrivateBrowsing(false) {} // If InputContext instance is a static variable, any heap allocated stuff // of its members need to be deleted at XPCOM shutdown. Otherwise, it's // detected as memory leak. void ShutDown() { mURI = nullptr; mHTMLInputType.Truncate(); mHTMLInputMode.Truncate(); mActionHint.Truncate(); mAutocapitalize.Truncate(); } bool IsPasswordEditor() const { return mHTMLInputType.LowerCaseEqualsLiteral("password"); } NativeKeyBindingsType GetNativeKeyBindingsType() const { MOZ_DIAGNOSTIC_ASSERT(mIMEState.IsEditable()); // See GetInputType in IMEStateManager.cpp if (mHTMLInputType.IsEmpty()) { return NativeKeyBindingsType::RichTextEditor; } return mHTMLInputType.EqualsLiteral("textarea") ? NativeKeyBindingsType::MultiLineEditor : NativeKeyBindingsType::SingleLineEditor; } // https://html.spec.whatwg.org/dev/interaction.html#autocapitalization bool IsAutocapitalizeSupported() const { return !mHTMLInputType.EqualsLiteral("password") && !mHTMLInputType.EqualsLiteral("url") && !mHTMLInputType.EqualsLiteral("email"); } bool IsInputAttributeChanged(const InputContext& aOldContext) const { return mIMEState.mEnabled != aOldContext.mIMEState.mEnabled || #if defined(ANDROID) || defined(MOZ_WIDGET_GTK) || defined(XP_WIN) || \ defined(XP_IOS) // input type and inputmode are supported by Windows IME API, GTK // IME API, Android IME API and iOS API. mHTMLInputType != aOldContext.mHTMLInputType || mHTMLInputMode != aOldContext.mHTMLInputMode || #endif #if defined(ANDROID) || defined(MOZ_WIDGET_GTK) || defined(XP_IOS) // autocapitalize is supported by Android IME API, GTK IME API, and // iOS API mAutocapitalize != aOldContext.mAutocapitalize || #endif #if defined(ANDROID) || defined(XP_IOS) // enterkeyhint is only supported by Android IME API and iOS API. mActionHint != aOldContext.mActionHint || #endif false; } IMEState mIMEState; // The URI of the document which has the editable element. nsCOMPtr mURI; /* The type of the input if the input is a html input field */ nsString mHTMLInputType; // The value of the inputmode nsString mHTMLInputMode; /* A hint for the action that is performed when the input is submitted */ nsString mActionHint; /* A hint for autocapitalize */ nsString mAutocapitalize; /** * mOrigin indicates whether this focus event refers to main or remote * content. */ enum Origin { // Adjusting focus of content on the main process ORIGIN_MAIN, // Adjusting focus of content in a remote process ORIGIN_CONTENT }; Origin mOrigin; /** * True if the document has ever received user input */ bool mHasHandledUserInput; /* Whether the owning document of the input element has been loaded * in private browsing mode. */ bool mInPrivateBrowsing; bool IsOriginMainProcess() const { return mOrigin == ORIGIN_MAIN; } bool IsOriginContentProcess() const { return mOrigin == ORIGIN_CONTENT; } bool IsOriginCurrentProcess() const { if (XRE_IsParentProcess()) { return IsOriginMainProcess(); } return IsOriginContentProcess(); } }; // FYI: Implemented in nsBaseWidget.cpp const char* ToChar(InputContext::Origin aOrigin); struct InputContextAction final { /** * mCause indicates what action causes calling nsIWidget::SetInputContext(). * It must be one of following values. */ enum Cause { // The cause is unknown but originated from content. Focus might have been // changed by content script. CAUSE_UNKNOWN, // The cause is unknown but originated from chrome. Focus might have been // changed by chrome script. CAUSE_UNKNOWN_CHROME, // The cause is user's keyboard operation. CAUSE_KEY, // The cause is user's mouse operation. CAUSE_MOUSE, // The cause is user's touch operation (implies mouse) CAUSE_TOUCH, // The cause is users' long press operation. CAUSE_LONGPRESS, // The cause is unknown but it occurs during user input except keyboard // input. E.g., an event handler of a user input event moves focus. CAUSE_UNKNOWN_DURING_NON_KEYBOARD_INPUT, // The cause is unknown but it occurs during keyboard input. CAUSE_UNKNOWN_DURING_KEYBOARD_INPUT, }; Cause mCause; /** * mFocusChange indicates what happened for focus. */ enum FocusChange { FOCUS_NOT_CHANGED, // A content got focus. GOT_FOCUS, // Focused content lost focus. LOST_FOCUS, // Menu got pseudo focus that means focused content isn't changed but // keyboard events will be handled by menu. MENU_GOT_PSEUDO_FOCUS, // Menu lost pseudo focus that means focused content will handle keyboard // events. MENU_LOST_PSEUDO_FOCUS, // The widget is created. When a widget is crated, it may need to notify // IME module to initialize its native IME context. In such case, this is // used. I.e., this isn't used by IMEStateManager. WIDGET_CREATED }; FocusChange mFocusChange; bool ContentGotFocusByTrustedCause() const { return (mFocusChange == GOT_FOCUS && mCause != CAUSE_UNKNOWN); } bool UserMightRequestOpenVKB() const { // If focus is changed, user must not request to open VKB. if (mFocusChange != FOCUS_NOT_CHANGED) { return false; } switch (mCause) { // If user clicks or touches focused editor, user must request to open // VKB. case CAUSE_MOUSE: case CAUSE_TOUCH: // If script does something during a user input and that causes changing // input context, user might request to open VKB. E.g., user clicks // dummy editor and JS moves focus to an actual editable node. However, // this should return false if the user input is a keyboard event since // physical keyboard operation shouldn't cause opening VKB. case CAUSE_UNKNOWN_DURING_NON_KEYBOARD_INPUT: return true; default: return false; } } /** * IsHandlingUserInput() returns true if it's caused by a user action directly * or it's caused by script or something but it occurred while we're handling * a user action. E.g., when it's caused by Element.focus() in an event * handler of a user input, this returns true. */ static bool IsHandlingUserInput(Cause aCause) { switch (aCause) { case CAUSE_KEY: case CAUSE_MOUSE: case CAUSE_TOUCH: case CAUSE_LONGPRESS: case CAUSE_UNKNOWN_DURING_NON_KEYBOARD_INPUT: case CAUSE_UNKNOWN_DURING_KEYBOARD_INPUT: return true; default: return false; } } bool IsHandlingUserInput() const { return IsHandlingUserInput(mCause); } InputContextAction() : mCause(CAUSE_UNKNOWN), mFocusChange(FOCUS_NOT_CHANGED) {} explicit InputContextAction(Cause aCause, FocusChange aFocusChange = FOCUS_NOT_CHANGED) : mCause(aCause), mFocusChange(aFocusChange) {} }; // IMEMessage is shared by IMEStateManager and TextComposition. // Update values in GeckoEditable.java if you make changes here. // XXX Negative values are used in Android... typedef int8_t IMEMessageType; enum IMEMessage : IMEMessageType { // This is used by IMENotification internally. This means that the instance // hasn't been initialized yet. NOTIFY_IME_OF_NOTHING, // An editable content is getting focus NOTIFY_IME_OF_FOCUS, // An editable content is losing focus NOTIFY_IME_OF_BLUR, // Selection in the focused editable content is changed NOTIFY_IME_OF_SELECTION_CHANGE, // Text in the focused editable content is changed NOTIFY_IME_OF_TEXT_CHANGE, // Notified when a dispatched composition event is handled by the // contents. This must be notified after the other notifications. // Note that if a remote process has focus, this is notified only once when // all dispatched events are handled completely. So, the receiver shouldn't // count number of received this notification for comparing with the number // of dispatched events. // NOTE: If a composition event causes moving focus from the focused editor, // this notification may not be notified as usual. Even in such case, // NOTIFY_IME_OF_BLUR is always sent. So, notification listeners // should tread the blur notification as including this if there is // pending composition events. NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED, // Position or size of focused element may be changed. NOTIFY_IME_OF_POSITION_CHANGE, // Mouse button event is fired on a character in focused editor NOTIFY_IME_OF_MOUSE_BUTTON_EVENT, // Request to commit current composition to IME // (some platforms may not support) REQUEST_TO_COMMIT_COMPOSITION, // Request to cancel current composition to IME // (some platforms may not support) REQUEST_TO_CANCEL_COMPOSITION }; // FYI: Implemented in nsBaseWidget.cpp const char* ToChar(IMEMessage aIMEMessage); struct IMENotification final { IMENotification() : mMessage(NOTIFY_IME_OF_NOTHING), mSelectionChangeData() {} IMENotification(const IMENotification& aOther) : mMessage(NOTIFY_IME_OF_NOTHING) { Assign(aOther); } ~IMENotification() { Clear(); } MOZ_IMPLICIT IMENotification(IMEMessage aMessage) : mMessage(aMessage), mSelectionChangeData() { switch (aMessage) { case NOTIFY_IME_OF_SELECTION_CHANGE: mSelectionChangeData.mString = new nsString(); mSelectionChangeData.Clear(); break; case NOTIFY_IME_OF_TEXT_CHANGE: mTextChangeData.Clear(); break; case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT: mMouseButtonEventData.mEventMessage = eVoidEvent; mMouseButtonEventData.mOffset = UINT32_MAX; mMouseButtonEventData.mCursorPos.MoveTo(0, 0); mMouseButtonEventData.mCharRect.SetRect(0, 0, 0, 0); mMouseButtonEventData.mButton = -1; mMouseButtonEventData.mButtons = 0; mMouseButtonEventData.mModifiers = 0; break; default: break; } } void Assign(const IMENotification& aOther) { bool changingMessage = mMessage != aOther.mMessage; if (changingMessage) { Clear(); mMessage = aOther.mMessage; } switch (mMessage) { case NOTIFY_IME_OF_SELECTION_CHANGE: if (changingMessage) { mSelectionChangeData.mString = new nsString(); } mSelectionChangeData.Assign(aOther.mSelectionChangeData); break; case NOTIFY_IME_OF_TEXT_CHANGE: mTextChangeData = aOther.mTextChangeData; break; case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT: mMouseButtonEventData = aOther.mMouseButtonEventData; break; default: break; } } IMENotification& operator=(const IMENotification& aOther) { Assign(aOther); return *this; } void Clear() { if (mMessage == NOTIFY_IME_OF_SELECTION_CHANGE) { MOZ_ASSERT(mSelectionChangeData.mString); delete mSelectionChangeData.mString; mSelectionChangeData.mString = nullptr; } mMessage = NOTIFY_IME_OF_NOTHING; } bool HasNotification() const { return mMessage != NOTIFY_IME_OF_NOTHING; } void MergeWith(const IMENotification& aNotification) { switch (mMessage) { case NOTIFY_IME_OF_NOTHING: MOZ_ASSERT(aNotification.mMessage != NOTIFY_IME_OF_NOTHING); Assign(aNotification); break; case NOTIFY_IME_OF_SELECTION_CHANGE: MOZ_ASSERT(aNotification.mMessage == NOTIFY_IME_OF_SELECTION_CHANGE); mSelectionChangeData.Assign(aNotification.mSelectionChangeData); break; case NOTIFY_IME_OF_TEXT_CHANGE: MOZ_ASSERT(aNotification.mMessage == NOTIFY_IME_OF_TEXT_CHANGE); mTextChangeData += aNotification.mTextChangeData; break; case NOTIFY_IME_OF_POSITION_CHANGE: case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED: MOZ_ASSERT(aNotification.mMessage == mMessage); break; default: MOZ_CRASH("Merging notification isn't supported"); break; } } IMEMessage mMessage; // NOTIFY_IME_OF_SELECTION_CHANGE specific data struct SelectionChangeDataBase { // Selection range. uint32_t mOffset; // Selected string nsString* mString; // Writing mode at the selection. uint8_t mWritingModeBits; bool mIsInitialized; bool mHasRange; bool mReversed; bool mCausedByComposition; bool mCausedBySelectionEvent; bool mOccurredDuringComposition; void SetWritingMode(const WritingMode& aWritingMode); WritingMode GetWritingMode() const; uint32_t StartOffset() const { MOZ_ASSERT(mHasRange); return mOffset; } uint32_t EndOffset() const { MOZ_ASSERT(mHasRange); return mOffset + Length(); } uint32_t AnchorOffset() const { MOZ_ASSERT(mHasRange); return mOffset + (mReversed ? Length() : 0); } uint32_t FocusOffset() const { MOZ_ASSERT(mHasRange); return mOffset + (mReversed ? 0 : Length()); } const nsString& String() const { MOZ_ASSERT(mHasRange); return *mString; } uint32_t Length() const { MOZ_ASSERT(mHasRange); return mString->Length(); } bool IsInInt32Range() const { return mHasRange && mOffset <= INT32_MAX && Length() <= INT32_MAX && mOffset + Length() <= INT32_MAX; } bool HasRange() const { return mIsInitialized && mHasRange; } bool IsCollapsed() const { return !mHasRange || mString->IsEmpty(); } void ClearSelectionData() { mIsInitialized = false; mHasRange = false; mOffset = UINT32_MAX; mString->Truncate(); mWritingModeBits = 0; mReversed = false; } void Clear() { ClearSelectionData(); mCausedByComposition = false; mCausedBySelectionEvent = false; mOccurredDuringComposition = false; } bool IsInitialized() const { return mIsInitialized; } void Assign(const SelectionChangeDataBase& aOther) { mIsInitialized = aOther.mIsInitialized; mHasRange = aOther.mHasRange; if (mIsInitialized && mHasRange) { mOffset = aOther.mOffset; *mString = aOther.String(); mReversed = aOther.mReversed; mWritingModeBits = aOther.mWritingModeBits; } else { mOffset = UINT32_MAX; mString->Truncate(); mReversed = false; // Let's keep the writing mode for avoiding temporarily changing the // writing mode at no selection range. } AssignReason(aOther.mCausedByComposition, aOther.mCausedBySelectionEvent, aOther.mOccurredDuringComposition); } void Assign(const WidgetQueryContentEvent& aQuerySelectedTextEvent); void AssignReason(bool aCausedByComposition, bool aCausedBySelectionEvent, bool aOccurredDuringComposition) { mCausedByComposition = aCausedByComposition; mCausedBySelectionEvent = aCausedBySelectionEvent; mOccurredDuringComposition = aOccurredDuringComposition; } bool EqualsRange(const SelectionChangeDataBase& aOther) const { if (HasRange() != aOther.HasRange()) { return false; } if (!HasRange()) { return true; } return mOffset == aOther.mOffset && mString->Equals(*aOther.mString); } bool EqualsRangeAndDirection(const SelectionChangeDataBase& aOther) const { return EqualsRange(aOther) && (!HasRange() || mReversed == aOther.mReversed); } bool EqualsRangeAndDirectionAndWritingMode( const SelectionChangeDataBase& aOther) const { return EqualsRangeAndDirection(aOther) && mWritingModeBits == aOther.mWritingModeBits; } bool EqualsRange(const ContentSelection& aContentSelection) const; bool EqualsRangeAndWritingMode( const ContentSelection& aContentSelection) const; OffsetAndData ToUint32OffsetAndData() const { return OffsetAndData(mOffset, *mString, OffsetAndDataFor::SelectedString); } }; // SelectionChangeDataBase cannot have constructors because it's used in // the union. Therefore, SelectionChangeData should only implement // constructors. In other words, add other members to // SelectionChangeDataBase. struct SelectionChangeData final : public SelectionChangeDataBase { SelectionChangeData() { mString = &mStringInstance; Clear(); } explicit SelectionChangeData(const SelectionChangeDataBase& aOther) { mString = &mStringInstance; Assign(aOther); } SelectionChangeData(const SelectionChangeData& aOther) { mString = &mStringInstance; Assign(aOther); } SelectionChangeData& operator=(const SelectionChangeDataBase& aOther) { mString = &mStringInstance; Assign(aOther); return *this; } SelectionChangeData& operator=(const SelectionChangeData& aOther) { mString = &mStringInstance; Assign(aOther); return *this; } private: // When SelectionChangeData is used outside of union, it shouldn't create // nsString instance in the heap as far as possible. nsString mStringInstance; }; struct TextChangeDataBase { // mStartOffset is the start offset of modified or removed text in // original content and inserted text in new content. uint32_t mStartOffset; // mRemovalEndOffset is the end offset of modified or removed text in // original content. If the value is same as mStartOffset, no text hasn't // been removed yet. uint32_t mRemovedEndOffset; // mAddedEndOffset is the end offset of inserted text or same as // mStartOffset if just removed. The vlaue is offset in the new content. uint32_t mAddedEndOffset; // Note that TextChangeDataBase may be the result of merging two or more // changes especially in e10s mode. // mCausedOnlyByComposition is true only when *all* merged changes are // caused by composition. bool mCausedOnlyByComposition; // mIncludingChangesDuringComposition is true if at least one change which // is not caused by composition occurred during the last composition. // Note that if after the last composition is finished and there are some // changes not caused by composition, this is set to false. bool mIncludingChangesDuringComposition; // mIncludingChangesWithoutComposition is true if there is at least one // change which did occur when there wasn't a composition ongoing. bool mIncludingChangesWithoutComposition; uint32_t OldLength() const { MOZ_ASSERT(IsValid()); return mRemovedEndOffset - mStartOffset; } uint32_t NewLength() const { MOZ_ASSERT(IsValid()); return mAddedEndOffset - mStartOffset; } // Positive if text is added. Negative if text is removed. int64_t Difference() const { return mAddedEndOffset - mRemovedEndOffset; } bool IsInInt32Range() const { MOZ_ASSERT(IsValid()); return mStartOffset <= INT32_MAX && mRemovedEndOffset <= INT32_MAX && mAddedEndOffset <= INT32_MAX; } bool IsValid() const { return !(mStartOffset == UINT32_MAX && !mRemovedEndOffset && !mAddedEndOffset); } void Clear() { mStartOffset = UINT32_MAX; mRemovedEndOffset = mAddedEndOffset = 0; } void MergeWith(const TextChangeDataBase& aOther); TextChangeDataBase& operator+=(const TextChangeDataBase& aOther) { MergeWith(aOther); return *this; } #ifdef DEBUG void Test(); #endif // #ifdef DEBUG }; // TextChangeDataBase cannot have constructors because they are used in union. // Therefore, TextChangeData should only implement constructor. In other // words, add other members to TextChangeDataBase. struct TextChangeData : public TextChangeDataBase { TextChangeData() { Clear(); } TextChangeData(uint32_t aStartOffset, uint32_t aRemovedEndOffset, uint32_t aAddedEndOffset, bool aCausedByComposition, bool aOccurredDuringComposition) { MOZ_ASSERT(aRemovedEndOffset >= aStartOffset, "removed end offset must not be smaller than start offset"); MOZ_ASSERT(aAddedEndOffset >= aStartOffset, "added end offset must not be smaller than start offset"); mStartOffset = aStartOffset; mRemovedEndOffset = aRemovedEndOffset; mAddedEndOffset = aAddedEndOffset; mCausedOnlyByComposition = aCausedByComposition; mIncludingChangesDuringComposition = !aCausedByComposition && aOccurredDuringComposition; mIncludingChangesWithoutComposition = !aCausedByComposition && !aOccurredDuringComposition; } }; struct MouseButtonEventData { // The value of WidgetEvent::mMessage EventMessage mEventMessage; // Character offset from the start of the focused editor under the cursor uint32_t mOffset; // Cursor position in pixels relative to the widget LayoutDeviceIntPoint mCursorPos; // Character rect in pixels under the cursor relative to the widget LayoutDeviceIntRect mCharRect; // The value of WidgetMouseEventBase::button and buttons int16_t mButton; int16_t mButtons; // The value of WidgetInputEvent::modifiers Modifiers mModifiers; }; union { // NOTIFY_IME_OF_SELECTION_CHANGE specific data SelectionChangeDataBase mSelectionChangeData; // NOTIFY_IME_OF_TEXT_CHANGE specific data TextChangeDataBase mTextChangeData; // NOTIFY_IME_OF_MOUSE_BUTTON_EVENT specific data MouseButtonEventData mMouseButtonEventData; }; void SetData(const SelectionChangeDataBase& aSelectionChangeData) { MOZ_RELEASE_ASSERT(mMessage == NOTIFY_IME_OF_SELECTION_CHANGE); mSelectionChangeData.Assign(aSelectionChangeData); } void SetData(const TextChangeDataBase& aTextChangeData) { MOZ_RELEASE_ASSERT(mMessage == NOTIFY_IME_OF_TEXT_CHANGE); mTextChangeData = aTextChangeData; } }; struct CandidateWindowPosition { // Upper left corner of the candidate window if mExcludeRect is false. // Otherwise, the position currently interested. E.g., caret position. LayoutDeviceIntPoint mPoint; // Rect which shouldn't be overlapped with the candidate window. // This is valid only when mExcludeRect is true. LayoutDeviceIntRect mRect; // See explanation of mPoint and mRect. bool mExcludeRect; }; std::ostream& operator<<(std::ostream& aStream, const IMEEnabled& aEnabled); std::ostream& operator<<(std::ostream& aStream, const IMEState::Open& aOpen); std::ostream& operator<<(std::ostream& aStream, const IMEState& aState); std::ostream& operator<<(std::ostream& aStream, const InputContext::Origin& aOrigin); std::ostream& operator<<(std::ostream& aStream, const InputContext& aContext); std::ostream& operator<<(std::ostream& aStream, const InputContextAction::Cause& aCause); std::ostream& operator<<(std::ostream& aStream, const InputContextAction::FocusChange& aFocusChange); std::ostream& operator<<(std::ostream& aStream, const IMENotification::SelectionChangeDataBase& aData); std::ostream& operator<<(std::ostream& aStream, const IMENotification::TextChangeDataBase& aData); } // namespace widget } // namespace mozilla #endif // #ifndef mozilla_widget_IMEData_h_