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
This commit is contained in:
Masayuki Nakano 2023-06-14 01:57:33 +00:00
parent 765a840831
commit cb321ed332
14 changed files with 122 additions and 78 deletions

View File

@ -306,7 +306,7 @@ void IMEStateManager::MaybeStartOffsetUpdatedInChild(nsIWidget* aWidget,
return;
}
RefPtr<TextComposition> composition = GetTextCompositionFor(aWidget);
TextComposition* const composition = GetTextCompositionFor(aWidget);
if (NS_WARN_IF(!composition)) {
MOZ_LOG(sISMLog, LogLevel::Warning,
("MaybeStartOffsetUpdatedInChild(aWidget=0x%p, aStartOffset=%u), "
@ -2328,36 +2328,24 @@ nsresult IMEStateManager::GetFocusSelectionAndRootElement(
}
// static
already_AddRefed<TextComposition> IMEStateManager::GetTextCompositionFor(
nsIWidget* aWidget) {
if (!sTextCompositions) {
return nullptr;
}
RefPtr<TextComposition> textComposition =
sTextCompositions->GetCompositionFor(aWidget);
return textComposition.forget();
TextComposition* IMEStateManager::GetTextCompositionFor(nsIWidget* aWidget) {
return sTextCompositions ? sTextCompositions->GetCompositionFor(aWidget)
: nullptr;
}
// static
already_AddRefed<TextComposition> IMEStateManager::GetTextCompositionFor(
TextComposition* IMEStateManager::GetTextCompositionFor(
const WidgetCompositionEvent* aCompositionEvent) {
if (!sTextCompositions) {
return nullptr;
}
RefPtr<TextComposition> textComposition =
sTextCompositions->GetCompositionFor(aCompositionEvent);
return textComposition.forget();
return sTextCompositions
? sTextCompositions->GetCompositionFor(aCompositionEvent)
: nullptr;
}
// static
already_AddRefed<TextComposition> IMEStateManager::GetTextCompositionFor(
TextComposition* IMEStateManager::GetTextCompositionFor(
nsPresContext* aPresContext) {
if (!sTextCompositions) {
return nullptr;
}
RefPtr<TextComposition> textComposition =
sTextCompositions->GetCompositionFor(aPresContext);
return textComposition.forget();
return sTextCompositions ? sTextCompositions->GetCompositionFor(aPresContext)
: nullptr;
}
} // namespace mozilla

View File

@ -277,13 +277,12 @@ class IMEStateManager {
/**
* Get TextComposition from widget.
*/
static already_AddRefed<TextComposition> GetTextCompositionFor(
nsIWidget* aWidget);
static TextComposition* GetTextCompositionFor(nsIWidget* aWidget);
/**
* Returns TextComposition instance for the event.
*/
static already_AddRefed<TextComposition> GetTextCompositionFor(
static TextComposition* GetTextCompositionFor(
const WidgetCompositionEvent* aCompositionEvent);
/**
@ -291,8 +290,7 @@ class IMEStateManager {
* Be aware, even if another pres context which shares native IME context with
* specified pres context has composition, this returns nullptr.
*/
static already_AddRefed<TextComposition> GetTextCompositionFor(
nsPresContext* aPresContext);
static TextComposition* GetTextCompositionFor(nsPresContext* aPresContext);
/**
* Send a notification to IME. It depends on the IME or platform spec what

View File

@ -45,6 +45,26 @@ namespace mozilla {
#define IDEOGRAPHIC_SPACE (u"\x3000"_ns)
static uint32_t GetOrCreateCompositionId(WidgetCompositionEvent* aEvent) {
// If we're in the parent process, return new composition ID.
if (XRE_IsParentProcess()) {
static uint32_t sNextCompositionId = 1u;
if (MOZ_UNLIKELY(sNextCompositionId == UINT32_MAX)) {
sNextCompositionId = 1u;
}
// FYI: When we send the event to a remote process, TextComposition will
// set aEvent->mCompositionId to this value. Therefore, we don't need to
// set it here.
return sNextCompositionId++;
}
// If aEvent comes from the parent process, the event has composition ID
// considered by the parent process. Then, we should use it.
// Otherwise, aEvent is synthesized in this process, it won't cross the
// process boundary between this process and the parent process. Therefore,
// we don't need to set meaningful composition ID for the text composition.
return aEvent->mCompositionId;
}
/******************************************************************************
* TextComposition
******************************************************************************/
@ -58,6 +78,7 @@ TextComposition::TextComposition(nsPresContext* aPresContext, nsINode* aNode,
mNode(aNode),
mBrowserParent(aBrowserParent),
mNativeContext(aCompositionEvent->mNativeIMEContext),
mCompositionId(GetOrCreateCompositionId(aCompositionEvent)),
mCompositionStartOffset(0),
mTargetClauseOffsetInComposition(0),
mCompositionStartOffsetInTextNode(UINT32_MAX),
@ -244,8 +265,8 @@ void TextComposition::OnCompositionEventDiscarded(
"Shouldn't be called with untrusted event");
if (mBrowserParent) {
// The composition event should be discarded in the child process too.
Unused << mBrowserParent->SendCompositionEvent(*aCompositionEvent);
Unused << mBrowserParent->SendCompositionEvent(*aCompositionEvent,
mCompositionId);
}
// XXX If composition events are discarded, should we dispatch them with
@ -343,7 +364,8 @@ void TextComposition::DispatchCompositionEvent(
// If the content is a container of BrowserParent, composition should be in
// the remote process.
if (mBrowserParent) {
Unused << mBrowserParent->SendCompositionEvent(*aCompositionEvent);
Unused << mBrowserParent->SendCompositionEvent(*aCompositionEvent,
mCompositionId);
aCompositionEvent->StopPropagation();
if (aCompositionEvent->CausesDOMTextEvent()) {
mLastData = aCompositionEvent->mData;
@ -644,6 +666,7 @@ void TextComposition::DispatchCompositionEventRunnable(
}
nsresult TextComposition::RequestToCommit(nsIWidget* aWidget, bool aDiscard) {
MOZ_ASSERT(this == IMEStateManager::GetTextCompositionFor(aWidget));
// If this composition is already requested to be committed or canceled,
// or has already finished in IME, we don't need to request it again because
// request from this instance shouldn't cause committing nor canceling current

View File

@ -51,6 +51,8 @@ class TextComposition final {
TextComposition(nsPresContext* aPresContext, nsINode* aNode,
BrowserParent* aBrowserParent,
WidgetCompositionEvent* aCompositionEvent);
TextComposition() = delete;
TextComposition(const TextComposition& aOther) = delete;
bool Destroyed() const { return !mPresContext; }
nsPresContext* GetPresContext() const { return mPresContext; }
@ -92,6 +94,10 @@ class TextComposition final {
// came from nsDOMWindowUtils.
bool IsSynthesizedForTests() const { return mIsSynthesizedForTests; }
// Returns the composition ID. It must be 0 if the composition is synthesized
// in a content process. Otherwise, returns 1 or larger value.
uint32_t Id() const { return mCompositionId; }
const widget::NativeIMEContext& GetNativeIMEContext() const {
return mNativeContext;
}
@ -323,6 +329,11 @@ class TextComposition final {
// editor.
nsString mString;
// Composition ID of this composition. If this is in a parent process,
// this is 1 or larger. If the composition is created for managing a
// composition synthesized in a content process, this is 0.
const uint32_t mCompositionId = 0;
// Offset of the composition string from start of the editor
uint32_t mCompositionStartOffset;
// Offset of the selected clause of the composition string from
@ -387,26 +398,6 @@ class TextComposition final {
// when DispatchCompositionEvent() is called.
bool mWasCompositionStringEmpty;
// Hide the default constructor and copy constructor.
TextComposition()
: mPresContext(nullptr),
mNativeContext(nullptr),
mCompositionStartOffset(0),
mTargetClauseOffsetInComposition(0),
mCompositionStartOffsetInTextNode(UINT32_MAX),
mCompositionLengthInTextNode(UINT32_MAX),
mIsSynthesizedForTests(false),
mIsComposing(false),
mIsEditorHandlingEvent(false),
mIsRequestingCommit(false),
mIsRequestingCancel(false),
mRequestedToCommitOrCancel(false),
mHasReceivedCommitEvent(false),
mWasNativeCompositionEndEventDiscarded(false),
mAllowControlCharacters(false),
mWasCompositionStringEmpty(true) {}
TextComposition(const TextComposition& aOther);
/**
* If we're requesting IME to commit or cancel composition, or we've already
* requested it, or we've already known this composition has been ended in

View File

@ -2088,7 +2088,8 @@ mozilla::ipc::IPCResult BrowserChild::RecvCompositionEvent(
WidgetCompositionEvent localEvent(aEvent);
localEvent.mWidget = mPuppetWidget;
DispatchWidgetEventViaAPZ(localEvent);
Unused << SendOnEventNeedingAckHandled(aEvent.mMessage);
Unused << SendOnEventNeedingAckHandled(aEvent.mMessage,
localEvent.mCompositionId);
return IPC_OK();
}
@ -2102,7 +2103,7 @@ mozilla::ipc::IPCResult BrowserChild::RecvSelectionEvent(
WidgetSelectionEvent localEvent(aEvent);
localEvent.mWidget = mPuppetWidget;
DispatchWidgetEventViaAPZ(localEvent);
Unused << SendOnEventNeedingAckHandled(aEvent.mMessage);
Unused << SendOnEventNeedingAckHandled(aEvent.mMessage, 0u);
return IPC_OK();
}

View File

@ -2404,7 +2404,7 @@ mozilla::ipc::IPCResult BrowserParent::RecvNotifyIMEPositionChange(
}
mozilla::ipc::IPCResult BrowserParent::RecvOnEventNeedingAckHandled(
const EventMessage& aMessage) {
const EventMessage& aMessage, const uint32_t& aCompositionId) {
// This is called when the child process receives WidgetCompositionEvent or
// WidgetSelectionEvent.
// FYI: Don't check if widget is nullptr here because it's more important to
@ -2414,7 +2414,7 @@ mozilla::ipc::IPCResult BrowserParent::RecvOnEventNeedingAckHandled(
// While calling OnEventNeedingAckHandled(), BrowserParent *might* be
// destroyed since it may send notifications to IME.
RefPtr<BrowserParent> kungFuDeathGrip(this);
mContentCache.OnEventNeedingAckHandled(widget, aMessage);
mContentCache.OnEventNeedingAckHandled(widget, aMessage, aCompositionId);
return IPC_OK();
}
@ -3057,11 +3057,19 @@ bool BrowserParent::HandleQueryContentEvent(WidgetQueryContentEvent& aEvent) {
return true;
}
bool BrowserParent::SendCompositionEvent(WidgetCompositionEvent& aEvent) {
bool BrowserParent::SendCompositionEvent(WidgetCompositionEvent& aEvent,
uint32_t aCompositionId) {
if (mIsDestroyed) {
return false;
}
// When the composition is handled in a remote process, we need to handle
// commit/cancel result for composition with the composition ID to avoid
// to abort newer composition. Therefore, we need to let the remote process
// know the composition ID.
MOZ_ASSERT(aCompositionId != 0);
aEvent.mCompositionId = aCompositionId;
if (!mContentCache.OnCompositionEvent(aEvent)) {
return true;
}
@ -3215,7 +3223,8 @@ void BrowserParent::UnsetLastMouseRemoteTarget(BrowserParent* aBrowserParent) {
}
mozilla::ipc::IPCResult BrowserParent::RecvRequestIMEToCommitComposition(
const bool& aCancel, bool* aIsCommitted, nsString* aCommittedString) {
const bool& aCancel, const uint32_t& aCompositionId, bool* aIsCommitted,
nsString* aCommittedString) {
nsCOMPtr<nsIWidget> widget = GetTextInputHandlingWidget();
if (!widget) {
*aIsCommitted = false;
@ -3223,7 +3232,7 @@ mozilla::ipc::IPCResult BrowserParent::RecvRequestIMEToCommitComposition(
}
*aIsCommitted = mContentCache.RequestIMEToCommitComposition(
widget, aCancel, *aCommittedString);
widget, aCancel, aCompositionId, *aCommittedString);
return IPC_OK();
}

View File

@ -353,10 +353,11 @@ class BrowserParent final : public PBrowserParent,
const widget::IMENotification& aEventMessage);
mozilla::ipc::IPCResult RecvOnEventNeedingAckHandled(
const EventMessage& aMessage);
const EventMessage& aMessage, const uint32_t& aCompositionId);
mozilla::ipc::IPCResult RecvRequestIMEToCommitComposition(
const bool& aCancel, bool* aIsCommitted, nsString* aCommittedString);
const bool& aCancel, const uint32_t& aCompositionId, bool* aIsCommitted,
nsString* aCommittedString);
mozilla::ipc::IPCResult RecvGetInputContext(widget::IMEState* aIMEState);
@ -580,7 +581,8 @@ class BrowserParent final : public PBrowserParent,
* If you need to check if it's actually posted to the remote process,
* you can refer aEvent.HasBeenPostedToRemoteProcess().
*/
bool SendCompositionEvent(mozilla::WidgetCompositionEvent& aEvent);
bool SendCompositionEvent(mozilla::WidgetCompositionEvent& aEvent,
uint32_t aCompositionId);
bool SendSelectionEvent(mozilla::WidgetSelectionEvent& aEvent);

View File

@ -318,6 +318,8 @@ parent:
* Requests chrome to commit or cancel composition of IME.
*
* cancel Set true if composition should be cancelled.
* compositionId Set the composition ID to requesting commit
* (stored in TextComposition).
*
* isCommitted Returns true if the request causes composition
* being committed synchronously.
@ -326,7 +328,8 @@ parent:
* try to restore selected string which was
* replaced with the composition.
*/
[Nested=inside_cpow] sync RequestIMEToCommitComposition(bool cancel)
[Nested=inside_cpow] sync RequestIMEToCommitComposition(bool cancel,
uint32_t aCompositionId)
returns (bool isCommitted, nsString committedString);
/**
@ -334,9 +337,13 @@ parent:
* composition event or a selection event which is sent from the parent
* process.
*
* message The message value of the handled event.
* message The message value of the handled event.
* compositionId The composition ID of handled composition event if
* the message is a composition event message. Otherwise,
* 0.
*/
[Nested=inside_cpow] async OnEventNeedingAckHandled(EventMessage message);
[Nested=inside_cpow] async OnEventNeedingAckHandled(EventMessage message,
uint32_t compositionId);
/**
* Request that the parent process move focus to the browser's frame. If

View File

@ -1389,19 +1389,21 @@ void ContentCacheInParent::OnSelectionEvent(
}
void ContentCacheInParent::OnEventNeedingAckHandled(nsIWidget* aWidget,
EventMessage aMessage) {
EventMessage aMessage,
uint32_t aCompositionId) {
// This is called when the child process receives WidgetCompositionEvent or
// WidgetSelectionEvent.
MOZ_LOG(
sContentCacheLog, LogLevel::Info,
("0x%p OnEventNeedingAckHandled(aWidget=0x%p, "
"aMessage=%s), mPendingEventsNeedingAck=%u, "
"aMessage=%s, aCompositionId=%" PRIu32 "), mPendingEventsNeedingAck=%u, "
"mWidgetHasComposition=%s, mPendingCompositionCount=%" PRIu8 ", "
"mPendingCommitCount=%" PRIu8 ", mIsChildIgnoringCompositionEvents=%s",
this, aWidget, ToChar(aMessage), mPendingEventsNeedingAck,
GetBoolName(mWidgetHasComposition), mPendingCompositionCount,
mPendingCommitCount, GetBoolName(mIsChildIgnoringCompositionEvents)));
this, aWidget, ToChar(aMessage), aCompositionId,
mPendingEventsNeedingAck, GetBoolName(mWidgetHasComposition),
mPendingCompositionCount, mPendingCommitCount,
GetBoolName(mIsChildIgnoringCompositionEvents)));
#if MOZ_DIAGNOSTIC_ASSERT_ENABLED && !defined(FUZZING_SNAPSHOT)
mReceivedEventMessages.AppendElement(aMessage);
@ -1513,16 +1515,19 @@ void ContentCacheInParent::OnEventNeedingAckHandled(nsIWidget* aWidget,
}
bool ContentCacheInParent::RequestIMEToCommitComposition(
nsIWidget* aWidget, bool aCancel, nsAString& aCommittedString) {
nsIWidget* aWidget, bool aCancel, uint32_t aCompositionId,
nsAString& aCommittedString) {
MOZ_LOG(
sContentCacheLog, LogLevel::Info,
("0x%p RequestToCommitComposition(aWidget=%p, "
"aCancel=%s), mPendingCompositionCount=%" PRIu8 ", "
"mPendingCommitCount=%" PRIu8 ", mIsChildIgnoringCompositionEvents=%s, "
"aCancel=%s, aCompositionId=%" PRIu32
"), mPendingCompositionCount=%" PRIu8 ", mPendingCommitCount=%" PRIu8
", mIsChildIgnoringCompositionEvents=%s, "
"IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)=%s, "
"mWidgetHasComposition=%s, mCommitStringByRequest=%p",
this, aWidget, GetBoolName(aCancel), mPendingCompositionCount,
mPendingCommitCount, GetBoolName(mIsChildIgnoringCompositionEvents),
this, aWidget, GetBoolName(aCancel), aCompositionId,
mPendingCompositionCount, mPendingCommitCount,
GetBoolName(mIsChildIgnoringCompositionEvents),
GetBoolName(
IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)),
GetBoolName(mWidgetHasComposition), mCommitStringByRequest));

View File

@ -426,7 +426,8 @@ class ContentCacheInParent final : public ContentCache {
* BrowserParent or aWidget. Therefore, the caller must not destroy
* this instance during a call of this method.
*/
void OnEventNeedingAckHandled(nsIWidget* aWidget, EventMessage aMessage);
void OnEventNeedingAckHandled(nsIWidget* aWidget, EventMessage aMessage,
uint32_t aCompositionId);
/**
* RequestIMEToCommitComposition() requests aWidget to commit or cancel
@ -436,6 +437,9 @@ class ContentCacheInParent final : public ContentCache {
* the composition.
* @param aCancel When the caller tries to cancel the composition, true.
* Otherwise, i.e., tries to commit the composition, false.
* @param aCompositionId
* The composition ID which should be committed or
* canceled.
* @param aCommittedString The committed string (i.e., the last data of
* dispatched composition events during requesting
* IME to commit composition.
@ -443,6 +447,7 @@ class ContentCacheInParent final : public ContentCache {
* synchronously.
*/
bool RequestIMEToCommitComposition(nsIWidget* aWidget, bool aCancel,
uint32_t aCompositionId,
nsAString& aCommittedString);
/**

View File

@ -371,6 +371,9 @@ struct NativeIMEContext final {
// 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) {
@ -384,7 +387,12 @@ struct NativeIMEContext final {
bool IsValid() const {
return mRawNativeIMEContext &&
mOriginProcessID != static_cast<uintptr_t>(-1);
mOriginProcessID != static_cast<uint64_t>(-1);
}
bool IsOriginatedInParentProcess() const {
return mOriginProcessID != 0 &&
mOriginProcessID != static_cast<uint64_t>(-1);
}
void Init(nsIWidget* aWidget);

View File

@ -645,7 +645,7 @@ nsresult PuppetWidget::RequestIMEToCommitComposition(bool aCancel) {
bool isCommitted = false;
nsAutoString committedString;
if (NS_WARN_IF(!mBrowserChild->SendRequestIMEToCommitComposition(
aCancel, &isCommitted, &committedString))) {
aCancel, composition->Id(), &isCommitted, &committedString))) {
return NS_ERROR_FAILURE;
}
@ -673,7 +673,7 @@ nsresult PuppetWidget::RequestIMEToCommitComposition(bool aCancel) {
mIgnoreCompositionEvents = true;
Unused << mBrowserChild->SendOnEventNeedingAckHandled(
eCompositionCommitRequestHandled);
eCompositionCommitRequestHandled, composition->Id());
// NOTE: PuppetWidget might be destroyed already.
return NS_OK;

View File

@ -885,6 +885,11 @@ class WidgetCompositionEvent : public WidgetGUIEvent {
// the another event's mMessage.
EventMessage mOriginalMessage;
// Composition ID considered by TextComposition. If the event has not been
// handled by TextComposition yet, this is 0. And also if the event is for
// a composition synthesized in a content process, this is always 0.
uint32_t mCompositionId = 0;
void AssignCompositionEventData(const WidgetCompositionEvent& aEvent,
bool aCopyTargets) {
AssignGUIEventData(aEvent, aCopyTargets);

View File

@ -551,6 +551,7 @@ struct ParamTraits<mozilla::WidgetCompositionEvent> {
WriteParam(aWriter, static_cast<const mozilla::WidgetGUIEvent&>(aParam));
WriteParam(aWriter, aParam.mData);
WriteParam(aWriter, aParam.mNativeIMEContext);
WriteParam(aWriter, aParam.mCompositionId);
bool hasRanges = !!aParam.mRanges;
WriteParam(aWriter, hasRanges);
if (hasRanges) {
@ -563,6 +564,7 @@ struct ParamTraits<mozilla::WidgetCompositionEvent> {
if (!ReadParam(aReader, static_cast<mozilla::WidgetGUIEvent*>(aResult)) ||
!ReadParam(aReader, &aResult->mData) ||
!ReadParam(aReader, &aResult->mNativeIMEContext) ||
!ReadParam(aReader, &aResult->mCompositionId) ||
!ReadParam(aReader, &hasRanges)) {
return false;
}