Bug 599550 - Properly sync PuppetWidget IME notifications; r=blassey,cjones a=blocking-fennec

This commit is contained in:
Jim Chen 2010-10-01 10:17:37 -04:00
parent ea46b24ebc
commit 53943af34f
7 changed files with 96 additions and 31 deletions

View File

@ -91,6 +91,26 @@ parent:
sync SyncMessage(nsString aMessage, nsString aJSON)
returns (nsString[] retval);
/**
* The IME sequence number (seqno) parameter is used to make sure
* that a notification is discarded if it arrives at the chrome process
* too late. If the notification is late and we accept it, we will have
* an out-of-date view of the content process, which means events that we
* dispatch based on this out-of-date view will be wrong also.
* (see Bug 599550 and Bug 591047 comments 44, 50, and 54)
*
* Chrome increments seqno and includes it in each IME event sent to
* content, and content sends its current seqno back to chrome with each
* notification. A notification is up-to-date only if the content
* seqno is the same as the current chrome seqno, meaning no additional
* event was sent to content before the notification was received
*
* On blur, chrome returns the current seqno to content, and content
* uses it to discard subsequent events until the content seqno and
* chrome seqno-on-blur match again. These events, meant for the blurred
* textfield, are discarded to prevent events going to the wrong target
*/
/**
* Notifies chrome that there is a focus change involving an editable
* object (input, textarea, document, contentEditable. etc.)
@ -98,9 +118,10 @@ parent:
* focus PR_TRUE if editable object is receiving focus
* PR_FALSE if losing focus
* preference Native widget preference for IME updates
* seqno Current seqno value on the chrome side
*/
sync NotifyIMEFocus(PRBool focus)
returns (nsIMEUpdatePreference preference);
returns (nsIMEUpdatePreference preference, PRUint32 seqno);
/**
* Notifies chrome that there has been a change in text content
@ -120,10 +141,11 @@ parent:
* Notifies chrome that there has been a change in selection
* Only called when NotifyIMEFocus returns PR_TRUE for mWantUpdates
*
* seqno Current seqno value on the content side
* anchor Offset where the selection started
* focus Offset where the caret is
*/
NotifyIMESelection(PRUint32 anchor, PRUint32 focus);
NotifyIMESelection(PRUint32 seqno, PRUint32 anchor, PRUint32 focus);
/**
* Notifies chrome to refresh its text cache

View File

@ -317,12 +317,14 @@ TabParent::RecvAsyncMessage(const nsString& aMessage,
bool
TabParent::RecvNotifyIMEFocus(const PRBool& aFocus,
nsIMEUpdatePreference* aPreference)
nsIMEUpdatePreference* aPreference,
PRUint32* aSeqno)
{
nsCOMPtr<nsIWidget> widget = GetWidget();
if (!widget)
return true;
*aSeqno = mIMESeqno;
mIMETabParent = aFocus ? this : nsnull;
mIMESelectionAnchor = 0;
mIMESelectionFocus = 0;
@ -355,16 +357,19 @@ TabParent::RecvNotifyIMETextChange(const PRUint32& aStart,
}
bool
TabParent::RecvNotifyIMESelection(const PRUint32& aAnchor,
TabParent::RecvNotifyIMESelection(const PRUint32& aSeqno,
const PRUint32& aAnchor,
const PRUint32& aFocus)
{
nsCOMPtr<nsIWidget> widget = GetWidget();
if (!widget)
return true;
mIMESelectionAnchor = aAnchor;
mIMESelectionFocus = aFocus;
widget->OnIMESelectionChange();
if (aSeqno == mIMESeqno) {
mIMESelectionAnchor = aAnchor;
mIMESelectionFocus = aFocus;
widget->OnIMESelectionChange();
}
return true;
}
@ -444,12 +449,13 @@ TabParent::HandleQueryContentEvent(nsQueryContentEvent& aEvent)
}
bool
TabParent::SendCompositionEvent(const nsCompositionEvent& event)
TabParent::SendCompositionEvent(nsCompositionEvent& event)
{
mIMEComposing = event.message == NS_COMPOSITION_START;
mIMECompositionStart = PR_MIN(mIMESelectionAnchor, mIMESelectionFocus);
if (mIMECompositionEnding)
return true;
event.seqno = ++mIMESeqno;
return PBrowserParent::SendCompositionEvent(event);
}
@ -461,7 +467,7 @@ TabParent::SendCompositionEvent(const nsCompositionEvent& event)
* here and pass the text as the EndIMEComposition return value
*/
bool
TabParent::SendTextEvent(const nsTextEvent& event)
TabParent::SendTextEvent(nsTextEvent& event)
{
if (mIMECompositionEnding) {
mIMECompositionText = event.theText;
@ -476,14 +482,16 @@ TabParent::SendTextEvent(const nsTextEvent& event)
mIMESelectionAnchor = mIMESelectionFocus =
mIMECompositionStart + event.theText.Length();
event.seqno = ++mIMESeqno;
return PBrowserParent::SendTextEvent(event);
}
bool
TabParent::SendSelectionEvent(const nsSelectionEvent& event)
TabParent::SendSelectionEvent(nsSelectionEvent& event)
{
mIMESelectionAnchor = event.mOffset + (event.mReversed ? event.mLength : 0);
mIMESelectionFocus = event.mOffset + (!event.mReversed ? event.mLength : 0);
event.seqno = ++mIMESeqno;
return PBrowserParent::SendSelectionEvent(event);
}

View File

@ -92,11 +92,13 @@ public:
virtual bool RecvAsyncMessage(const nsString& aMessage,
const nsString& aJSON);
virtual bool RecvNotifyIMEFocus(const PRBool& aFocus,
nsIMEUpdatePreference* aPreference);
nsIMEUpdatePreference* aPreference,
PRUint32* aSeqno);
virtual bool RecvNotifyIMETextChange(const PRUint32& aStart,
const PRUint32& aEnd,
const PRUint32& aNewEnd);
virtual bool RecvNotifyIMESelection(const PRUint32& aAnchor,
virtual bool RecvNotifyIMESelection(const PRUint32& aSeqno,
const PRUint32& aAnchor,
const PRUint32& aFocus);
virtual bool RecvNotifyIMETextHint(const nsString& aText);
virtual bool RecvEndIMEComposition(const PRBool& aCancel,
@ -179,9 +181,9 @@ public:
static TabParent *GetIMETabParent() { return mIMETabParent; }
bool HandleQueryContentEvent(nsQueryContentEvent& aEvent);
bool SendCompositionEvent(const nsCompositionEvent& event);
bool SendTextEvent(const nsTextEvent& event);
bool SendSelectionEvent(const nsSelectionEvent& event);
bool SendCompositionEvent(nsCompositionEvent& event);
bool SendTextEvent(nsTextEvent& event);
bool SendSelectionEvent(nsSelectionEvent& event);
protected:
bool ReceiveMessage(const nsString& aMessage,
PRBool aSync,
@ -232,6 +234,7 @@ protected:
// Compositions in almost all cases are small enough for nsAutoString
nsAutoString mIMECompositionText;
PRUint32 mIMECompositionStart;
PRUint32 mIMESeqno;
private:
already_AddRefed<nsFrameLoader> GetFrameLoader() const;

View File

@ -1063,6 +1063,9 @@ private:
nsTextEvent()
{
}
public:
PRUint32 seqno;
#endif // MOZ_IPC
public:
@ -1091,6 +1094,9 @@ private:
nsCompositionEvent()
{
}
public:
PRUint32 seqno;
#endif // MOZ_IPC
public:
@ -1309,6 +1315,9 @@ private:
nsSelectionEvent()
{
}
public:
PRUint32 seqno;
#endif // MOZ_IPC
public:

View File

@ -165,6 +165,7 @@ struct ParamTraits<nsTextEvent>
static void Write(Message* aMsg, const paramType& aParam)
{
WriteParam(aMsg, static_cast<nsInputEvent>(aParam));
WriteParam(aMsg, aParam.seqno);
WriteParam(aMsg, aParam.theText);
WriteParam(aMsg, aParam.isChar);
WriteParam(aMsg, aParam.rangeCount);
@ -175,6 +176,7 @@ struct ParamTraits<nsTextEvent>
static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
{
if (!ReadParam(aMsg, aIter, static_cast<nsInputEvent*>(aResult)) ||
!ReadParam(aMsg, aIter, &aResult->seqno) ||
!ReadParam(aMsg, aIter, &aResult->theText) ||
!ReadParam(aMsg, aIter, &aResult->isChar) ||
!ReadParam(aMsg, aIter, &aResult->rangeCount))
@ -212,11 +214,13 @@ struct ParamTraits<nsCompositionEvent>
static void Write(Message* aMsg, const paramType& aParam)
{
WriteParam(aMsg, static_cast<nsInputEvent>(aParam));
WriteParam(aMsg, aParam.seqno);
}
static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
{
return ReadParam(aMsg, aIter, static_cast<nsInputEvent*>(aResult));
return ReadParam(aMsg, aIter, static_cast<nsInputEvent*>(aResult)) &&
ReadParam(aMsg, aIter, &aResult->seqno);
}
};
@ -261,6 +265,7 @@ struct ParamTraits<nsSelectionEvent>
static void Write(Message* aMsg, const paramType& aParam)
{
WriteParam(aMsg, static_cast<nsGUIEvent>(aParam));
WriteParam(aMsg, aParam.seqno);
WriteParam(aMsg, aParam.mOffset);
WriteParam(aMsg, aParam.mLength);
WriteParam(aMsg, aParam.mReversed);
@ -271,6 +276,7 @@ struct ParamTraits<nsSelectionEvent>
static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
{
return ReadParam(aMsg, aIter, static_cast<nsGUIEvent*>(aResult)) &&
ReadParam(aMsg, aIter, &aResult->seqno) &&
ReadParam(aMsg, aIter, &aResult->mOffset) &&
ReadParam(aMsg, aIter, &aResult->mLength) &&
ReadParam(aMsg, aIter, &aResult->mReversed) &&

View File

@ -111,7 +111,8 @@ PuppetWidget::Create(nsIWidget *aParent,
gfxASurface::ContentFromFormat(gfxASurface::ImageFormatARGB32));
mIMEComposing = PR_FALSE;
mIMESuppressNotifySel = PR_FALSE;
mIMELastReceivedSeqno = 0;
mIMELastBlurSeqno = 0;
PuppetWidget* parent = static_cast<PuppetWidget*>(aParent);
if (parent) {
@ -277,15 +278,28 @@ PuppetWidget::DispatchEvent(nsGUIEvent* event, nsEventStatus& aStatus)
if (mEventCallback) {
if (event->message == NS_COMPOSITION_START) {
mIMEComposing = PR_TRUE;
} else if (event->message == NS_SELECTION_SET) {
mIMESuppressNotifySel = PR_TRUE;
}
switch (event->eventStructType) {
case NS_COMPOSITION_EVENT:
mIMELastReceivedSeqno = static_cast<nsCompositionEvent*>(event)->seqno;
if (mIMELastReceivedSeqno < mIMELastBlurSeqno)
return NS_OK;
break;
case NS_TEXT_EVENT:
mIMELastReceivedSeqno = static_cast<nsTextEvent*>(event)->seqno;
if (mIMELastReceivedSeqno < mIMELastBlurSeqno)
return NS_OK;
break;
case NS_SELECTION_EVENT:
mIMELastReceivedSeqno = static_cast<nsSelectionEvent*>(event)->seqno;
if (mIMELastReceivedSeqno < mIMELastBlurSeqno)
return NS_OK;
break;
}
aStatus = (*mEventCallback)(event);
if (event->message == NS_COMPOSITION_END) {
mIMEComposing = PR_FALSE;
} else if (event->message == NS_SELECTION_SET) {
mIMESuppressNotifySel = PR_FALSE;
}
} else if (mChild) {
event->widget = mChild;
@ -401,9 +415,10 @@ PuppetWidget::OnIMEFocusChange(PRBool aFocus)
ResetInputState();
}
PRUint32 chromeSeqno;
mIMEPreference.mWantUpdates = PR_FALSE;
mIMEPreference.mWantHints = PR_FALSE;
if (!mTabChild->SendNotifyIMEFocus(aFocus, &mIMEPreference))
if (!mTabChild->SendNotifyIMEFocus(aFocus, &mIMEPreference, &chromeSeqno))
return NS_ERROR_FAILURE;
if (aFocus) {
@ -411,6 +426,8 @@ PuppetWidget::OnIMEFocusChange(PRBool aFocus)
// call OnIMEFocusChange on blur but no other updates
return NS_SUCCESS_IME_NO_UPDATES;
OnIMESelectionChange(); // Update selection
} else {
mIMELastBlurSeqno = chromeSeqno;
}
return NS_OK;
}
@ -444,13 +461,6 @@ PuppetWidget::OnIMESelectionChange(void)
if (!mTabChild)
return NS_ERROR_FAILURE;
// When we send selection notifications during a composition or during a
// set selection event, there is a race condition where the notification
// arrives at chrome too late, which leads to chrome thinking the
// selection was elsewhere. Suppress notifications here to avoid that.
if (mIMEComposing || mIMESuppressNotifySel)
return NS_OK;
if (mIMEPreference.mWantUpdates) {
nsEventStatus status;
nsQueryContentEvent queryEvent(PR_TRUE, NS_QUERY_SELECTED_TEXT, this);
@ -458,7 +468,8 @@ PuppetWidget::OnIMESelectionChange(void)
DispatchEvent(&queryEvent, status);
if (queryEvent.mSucceeded) {
mTabChild->SendNotifyIMESelection(queryEvent.GetSelectionStart(),
mTabChild->SendNotifyIMESelection(mIMELastReceivedSeqno,
queryEvent.GetSelectionStart(),
queryEvent.GetSelectionEnd());
}
}

View File

@ -216,7 +216,13 @@ private:
// IME
nsIMEUpdatePreference mIMEPreference;
PRPackedBool mIMEComposing;
PRPackedBool mIMESuppressNotifySel;
// Latest seqno received through events
PRUint32 mIMELastReceivedSeqno;
// Chrome's seqno value when last blur occurred
// arriving events with seqno up to this should be discarded
// Note that if seqno overflows (~50 days at 1 ms increment rate),
// events will be discarded until new focus/blur occurs
PRUint32 mIMELastBlurSeqno;
};
} // namespace widget