gecko-dev/widget/IMEData.h
Masayuki Nakano cb321ed332 Bug 1690827 - part 1: Number each composition for native IME or synthesized in the parent process r=smaug
For handling (ignoring) "too late" composition commit request from content
process, we need to distinguish a request is for which composition.  Therefore,
we need to number each composition originated in the parent process.

This patch makes `TextComposition` instance which is created at first
composition event for a composition consider a composition ID in the parent
process and set it to composition events which are dispatched into the DOM
tree in the parent or sent to a remote process.

And also this patch adds the composition ID param to the request method of
committing composition and reply methods to notify the parent process of ending
a composition event handling.

The last patch handle them in `ContentCacheInParent` to consider
whether a request/reply should be ignored or handled.

Differential Revision: https://phabricator.services.mozilla.com/D179310
2023-06-14 01:57:33 +00:00

1086 lines
37 KiB
C++

/* -*- 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.
// static_cast<uint64_t>(-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<uint64_t>(-1);
}
bool IsOriginatedInParentProcess() const {
return mOriginProcessID != 0 &&
mOriginProcessID != static_cast<uint64_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_