diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /widget/IMEData.h | |
parent | Initial commit. (diff) | |
download | firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | widget/IMEData.h | 1077 |
1 files changed, 1077 insertions, 0 deletions
diff --git a/widget/IMEData.h b/widget/IMEData.h new file mode 100644 index 0000000000..8664ce0ed9 --- /dev/null +++ b/widget/IMEData.h @@ -0,0 +1,1077 @@ +/* -*- 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 T> +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 <typename StringType> + explicit PrintStringDetail(const Maybe<StringType>& aMaybeString, + uint32_t aMaxLength = UINT32_MAX); + + private: + static nsCString PrintCharData(char32_t aChar); +}; + +// StartAndEndOffsets represents a range in flat-text. +template <typename IntType> +class StartAndEndOffsets { + protected: + static IntType MaxOffset() { return std::numeric_limits<IntType>::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<int64_t>(mStartOffset) - aNewStartOffset; + mStartOffset += delta; + mEndOffset += delta; + } + void SetOffsetAndLength(IntType aNewOffset, IntType aNewLength) { + mStartOffset = aNewOffset; + CheckedInt<IntType> 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<IntType> endOffset(mStartOffset + aNewLength); + mEndOffset = endOffset.isValid() ? endOffset.value() : MaxOffset(); + } + + friend std::ostream& operator<<( + std::ostream& aStream, + const StartAndEndOffsets<IntType>& 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 <typename IntType> +class OffsetAndData { + protected: + static IntType MaxOffset() { return std::numeric_limits<IntType>::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<IntType> offset(mOffset); + offset += mData.Length(); + return offset.isValid(); + } + IntType StartOffset() const { return mOffset; } + IntType Length() const { + CheckedInt<IntType> endOffset(CheckedInt<IntType>(mOffset) + + mData.Length()); + return endOffset.isValid() ? mData.Length() : MaxOffset() - mOffset; + } + IntType EndOffset() const { return mOffset + Length(); } + StartAndEndOffsets<IntType> CreateStartAndEndOffsets() const { + return StartAndEndOffsets<IntType>(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<IntType>& 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<void*>(static_cast<intptr_t>(-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. + 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<uintptr_t>(-1); + } + + void Init(nsIWidget* aWidget); + void InitWithRawNativeIMEContext(const void* aRawNativeIMEContext) { + InitWithRawNativeIMEContext(const_cast<void*>(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) + // input type and inputmode are supported by Windows IME API, GTK + // IME API and Android IME API + mHTMLInputType != aOldContext.mHTMLInputType || + mHTMLInputMode != aOldContext.mHTMLInputMode || +#endif +#if defined(ANDROID) || defined(MOZ_WIDGET_GTK) + // autocapitalize is supported by Android IME API and GTK IME API + mAutocapitalize != aOldContext.mAutocapitalize || +#endif +#if defined(ANDROID) + // enterkeyhint is only supported by Android IME API. + mActionHint != aOldContext.mActionHint || +#endif + false; + } + + IMEState mIMEState; + + // The URI of the document which has the editable element. + nsCOMPtr<nsIURI> 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<uint32_t> ToUint32OffsetAndData() const { + return OffsetAndData<uint32_t>(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_ |