Bug 1275914 part.2 Modify TextComposition::mCompositionStartOffset after every composition event dispatch r?smaug
When composition string hasn't been non-empty, insertion point of the composition string can be changed by a DOM event handler. E.g., compositionstart, first compositionupdate and first text. Therefore, TextComposition should update the composition start offset cache after every event dispatch.
MozReview-Commit-ID: FOPewPTRuCn
--- a/dom/events/TextComposition.cpp
+++ b/dom/events/TextComposition.cpp
@@ -64,16 +64,17 @@ TextComposition::TextComposition(nsPresC
, mIsEditorHandlingEvent(false)
, mIsRequestingCommit(false)
, mIsRequestingCancel(false)
, mRequestedToCommitOrCancel(false)
, mWasNativeCompositionEndEventDiscarded(false)
, mAllowControlCharacters(
Preferences::GetBool("dom.compositionevent.allow_control_characters",
false))
+ , mWasCompositionStringEmpty(true)
{
MOZ_ASSERT(aCompositionEvent->mNativeIMEContext.IsValid());
}
void
TextComposition::Destroy()
{
mPresContext = nullptr;
@@ -147,16 +148,18 @@ TextComposition::DispatchEvent(WidgetCom
EventDispatchingCallback* aCallBack,
const WidgetCompositionEvent *aOriginalEvent)
{
nsPluginInstanceOwner::GeneratePluginEvent(aOriginalEvent,
aDispatchEvent);
EventDispatcher::Dispatch(mNode, mPresContext,
aDispatchEvent, nullptr, aStatus, aCallBack);
+
+ OnCompositionEventDispatched(aDispatchEvent);
}
void
TextComposition::OnCompositionEventDiscarded(
WidgetCompositionEvent* aCompositionEvent)
{
// Note that this method is never called for synthesized events for emulating
// commit or cancel composition.
@@ -236,16 +239,18 @@ RemoveControlCharactersFrom(nsAString& a
void
TextComposition::DispatchCompositionEvent(
WidgetCompositionEvent* aCompositionEvent,
nsEventStatus* aStatus,
EventDispatchingCallback* aCallBack,
bool aIsSynthesized)
{
+ mWasCompositionStringEmpty = mString.IsEmpty();
+
// If the content is a container of TabParent, composition should be in the
// remote process.
if (mTabParent) {
Unused << mTabParent->SendCompositionEvent(*aCompositionEvent);
aCompositionEvent->StopPropagation();
if (aCompositionEvent->CausesDOMTextEvent()) {
mLastData = aCompositionEvent->mData;
mLastRanges = aCompositionEvent->mRanges;
@@ -397,17 +402,17 @@ TextComposition::DispatchCompositionEven
// Dispatch a compositionend event if it's necessary.
if (aCompositionEvent->mMessage != eCompositionEnd) {
CloneAndDispatchAs(aCompositionEvent, eCompositionEnd);
}
MOZ_ASSERT(!mIsComposing, "Why is the editor still composing?");
MOZ_ASSERT(!HasEditor(), "Why does the editor still keep to hold this?");
}
- OnCompositionEventHandled(aCompositionEvent);
+ MaybeNotifyIMEOfCompositionEventHandled(aCompositionEvent);
}
// static
void
TextComposition::HandleSelectionEvent(nsPresContext* aPresContext,
TabParent* aTabParent,
WidgetSelectionEvent* aSelectionEvent)
{
@@ -425,42 +430,67 @@ TextComposition::HandleSelectionEvent(ns
// XXX During setting selection, a selection listener may change selection
// again. In such case, sHandlingSelectionEvent doesn't indicate if
// the selection change is caused by a selection event. However, it
// must be non-realistic scenario.
handler.OnSelectionEvent(aSelectionEvent);
}
void
-TextComposition::OnCompositionEventHandled(
+TextComposition::OnCompositionEventDispatched(
const WidgetCompositionEvent* aCompositionEvent)
{
MOZ_RELEASE_ASSERT(!mTabParent);
- nsEventStatus status;
+ if (!IsValidStateForComposition(aCompositionEvent->mWidget)) {
+ return;
+ }
+
+ // Every composition event may cause changing composition start offset,
+ // especially when there is no composition string. Therefore, we need to
+ // update mCompositionStartOffset with the latest offset.
- // When compositon start, notify the rect of first offset character.
- // When not compositon start, notify the rect of selected composition
- // string if compositionchange event.
- if (aCompositionEvent->mMessage == eCompositionStart) {
+ MOZ_ASSERT(aCompositionEvent->mMessage != eCompositionStart ||
+ mWasCompositionStringEmpty,
+ "mWasCompositionStringEmpty should be true if the dispatched "
+ "event is eCompositionStart");
+
+ if (mWasCompositionStringEmpty &&
+ !aCompositionEvent->CausesDOMCompositionEndEvent()) {
+ // If there was no composition string, current selection start may be the
+ // offset for inserting composition string.
nsCOMPtr<nsIWidget> widget = mPresContext->GetRootWidget();
- // Update composition start offset
WidgetQueryContentEvent selectedTextEvent(true, eQuerySelectedText, widget);
- widget->DispatchEvent(&selectedTextEvent, status);
- if (selectedTextEvent.mSucceeded) {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ if (mString.IsEmpty()) {
+ widget->DispatchEvent(&selectedTextEvent, status);
+ } else {
+ MOZ_ASSERT(aCompositionEvent->mMessage == eCompositionChange);
+ // TODO: Update mCompositionStartOffset with first IME selection
+ }
+ if (NS_WARN_IF(!selectedTextEvent.mSucceeded)) {
+ // XXX Is this okay?
+ mCompositionStartOffset = 0;
+ } else {
mCompositionStartOffset = selectedTextEvent.mReply.mOffset;
- } else {
- // Unknown offset
- NS_WARNING("Cannot get start offset of IME composition");
- mCompositionStartOffset = 0;
}
mTargetClauseOffsetInComposition = 0;
- } else if (aCompositionEvent->CausesDOMTextEvent()) {
+ }
+
+ if (aCompositionEvent->CausesDOMTextEvent()) {
mTargetClauseOffsetInComposition = aCompositionEvent->TargetClauseOffset();
- } else {
+ }
+}
+
+void
+TextComposition::MaybeNotifyIMEOfCompositionEventHandled(
+ const WidgetCompositionEvent* aCompositionEvent)
+{
+ if (aCompositionEvent->mMessage != eCompositionStart &&
+ !aCompositionEvent->CausesDOMTextEvent()) {
return;
}
RefPtr<IMEContentObserver> contentObserver =
IMEStateManager::GetActiveContentObserver();
// When IMEContentObserver is managing the editor which has this composition,
// composition event handled notification should be sent after the observer
// notifies all pending notifications. Therefore, we should use it.
--- a/dom/events/TextComposition.h
+++ b/dom/events/TextComposition.h
@@ -253,30 +253,35 @@ private:
// Allow control characters appear in composition string.
// When this is false, control characters except
// CHARACTER TABULATION (horizontal tab) are removed from
// both composition string and data attribute of compositionupdate
// and compositionend events.
bool mAllowControlCharacters;
+ // mWasCompositionStringEmpty is true if the composition string was empty
+ // when DispatchCompositionEvent() is called.
+ bool mWasCompositionStringEmpty;
+
// Hide the default constructor and copy constructor.
TextComposition()
: mPresContext(nullptr)
, mNativeContext(nullptr)
, mCompositionStartOffset(0)
, mTargetClauseOffsetInComposition(0)
, mIsSynthesizedForTests(false)
, mIsComposing(false)
, mIsEditorHandlingEvent(false)
, mIsRequestingCommit(false)
, mIsRequestingCancel(false)
, mRequestedToCommitOrCancel(false)
, mWasNativeCompositionEndEventDiscarded(false)
, mAllowControlCharacters(false)
+ , mWasCompositionStringEmpty(true)
{}
TextComposition(const TextComposition& aOther);
/**
* GetEditor() returns nsIEditor pointer of mEditorWeak.
*/
already_AddRefed<nsIEditor> GetEditor() const;
@@ -370,19 +375,28 @@ private:
/**
* OnCompositionEventDiscarded() is called when PresShell discards
* compositionupdate, compositionend or compositionchange event due to not
* safe to dispatch event.
*/
void OnCompositionEventDiscarded(WidgetCompositionEvent* aCompositionEvent);
/**
- * Calculate composition offset then notify composition update to widget
+ * OnCompositionEventDispatched() is called after a composition event is
+ * dispatched.
*/
- void OnCompositionEventHandled(
+ void OnCompositionEventDispatched(
+ const WidgetCompositionEvent* aDispatchEvent);
+
+ /**
+ * MaybeNotifyIMEOfCompositionEventHandled() notifies IME of composition
+ * event handled. This should be called after dispatching a composition
+ * event which came from widget.
+ */
+ void MaybeNotifyIMEOfCompositionEventHandled(
const WidgetCompositionEvent* aCompositionEvent);
/**
* CompositionEventDispatcher dispatches the specified composition (or text)
* event.
*/
class CompositionEventDispatcher : public Runnable
{