Bug 1077345 part.4 Add NS_COMPOSITION_COMMIT_AS_IS event which automatically commits composition with the last data r=smaug
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -2133,23 +2133,25 @@ nsDOMWindowUtils::SendCompositionEvent(c
// Now we don't support manually dispatching composition update with this
// API. A compositionupdate is dispatched when a DOM text event modifies
// composition string automatically. For backward compatibility, this
// shouldn't return error in this case.
NS_WARNING("Don't call nsIDOMWindowUtils.sendCompositionEvent() for "
"compositionupdate since it's ignored and the event is "
"fired automatically when it's necessary");
return NS_OK;
+ } else if (aType.EqualsLiteral("compositioncommitasis")) {
+ msg = NS_COMPOSITION_COMMIT_AS_IS;
} else {
return NS_ERROR_FAILURE;
}
WidgetCompositionEvent compositionEvent(true, msg, widget);
InitEvent(compositionEvent);
- if (msg != NS_COMPOSITION_START) {
+ if (msg != NS_COMPOSITION_START && msg != NS_COMPOSITION_COMMIT_AS_IS) {
compositionEvent.mData = aData;
}
compositionEvent.mFlags.mIsSynthesizedForTests = true;
nsEventStatus status;
nsresult rv = widget->DispatchEvent(&compositionEvent, status);
NS_ENSURE_SUCCESS(rv, rv);
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -814,16 +814,17 @@ EventStateManager::PreHandleEvent(nsPres
compositionEvent->widget);
DoQuerySelectedText(&selectedText);
NS_ASSERTION(selectedText.mSucceeded, "Failed to get selected text");
compositionEvent->mData = selectedText.mReply.mString;
}
// through to compositionend handling
case NS_COMPOSITION_END:
case NS_COMPOSITION_CHANGE:
+ case NS_COMPOSITION_COMMIT_AS_IS:
{
WidgetCompositionEvent* compositionEvent = aEvent->AsCompositionEvent();
if (IsTargetCrossProcess(compositionEvent)) {
// Will not be handled locally, remote the event
if (GetCrossProcessTarget()->SendCompositionEvent(*compositionEvent)) {
// Cancel local dispatching
aEvent->mFlags.mPropagationStopped = true;
}
--- a/dom/events/IMEStateManager.cpp
+++ b/dom/events/IMEStateManager.cpp
@@ -138,16 +138,18 @@ GetEventMessageName(uint32_t aMessage)
case NS_COMPOSITION_START:
return "NS_COMPOSITION_START";
case NS_COMPOSITION_END:
return "NS_COMPOSITION_END";
case NS_COMPOSITION_UPDATE:
return "NS_COMPOSITION_UPDATE";
case NS_COMPOSITION_CHANGE:
return "NS_COMPOSITION_CHANGE";
+ case NS_COMPOSITION_COMMIT_AS_IS:
+ return "NS_COMPOSITION_COMMIT_AS_IS";
default:
return "unacceptable event message";
}
}
static const char*
GetNotifyIMEMessageName(IMEMessage aMessage)
{
--- a/dom/events/TextComposition.cpp
+++ b/dom/events/TextComposition.cpp
@@ -79,38 +79,42 @@ TextComposition::MaybeDispatchCompositio
if (mLastData == aCompositionEvent->mData) {
return true;
}
CloneAndDispatchAs(aCompositionEvent, NS_COMPOSITION_UPDATE);
return IsValidStateForComposition(aCompositionEvent->widget);
}
-void
+BaseEventFlags
TextComposition::CloneAndDispatchAs(
const WidgetCompositionEvent* aCompositionEvent,
- uint32_t aMessage)
+ uint32_t aMessage,
+ nsEventStatus* aStatus,
+ EventDispatchingCallback* aCallBack)
{
MOZ_ASSERT(IsValidStateForComposition(aCompositionEvent->widget),
"Should be called only when it's safe to dispatch an event");
WidgetCompositionEvent compositionEvent(aCompositionEvent->mFlags.mIsTrusted,
aMessage, aCompositionEvent->widget);
compositionEvent.time = aCompositionEvent->time;
compositionEvent.timeStamp = aCompositionEvent->timeStamp;
compositionEvent.mData = aCompositionEvent->mData;
compositionEvent.mFlags.mIsSynthesizedForTests =
aCompositionEvent->mFlags.mIsSynthesizedForTests;
- nsEventStatus status = nsEventStatus_eConsumeNoDefault;
+ nsEventStatus dummyStatus = nsEventStatus_eConsumeNoDefault;
+ nsEventStatus* status = aStatus ? aStatus : &dummyStatus;
if (aMessage == NS_COMPOSITION_UPDATE) {
mLastData = compositionEvent.mData;
}
EventDispatcher::Dispatch(mNode, mPresContext,
- &compositionEvent, nullptr, &status, nullptr);
+ &compositionEvent, nullptr, status, aCallBack);
+ return compositionEvent.mFlags;
}
void
TextComposition::OnCompositionEventDiscarded(
const WidgetCompositionEvent* aCompositionEvent)
{
// Note that this method is never called for synthesized events for emulating
// commit or cancel composition.
@@ -131,16 +135,32 @@ TextComposition::OnCompositionEventDisca
void
TextComposition::DispatchCompositionEvent(
WidgetCompositionEvent* aCompositionEvent,
nsEventStatus* aStatus,
EventDispatchingCallback* aCallBack,
bool aIsSynthesized)
{
+ if (aCompositionEvent->message == NS_COMPOSITION_COMMIT_AS_IS) {
+ NS_ASSERTION(!aCompositionEvent->mRanges,
+ "mRanges of NS_COMPOSITION_COMMIT_AS_IS should be null");
+ aCompositionEvent->mRanges = nullptr;
+ NS_ASSERTION(aCompositionEvent->mData.IsEmpty(),
+ "mData of NS_COMPOSITION_COMMIT_AS_IS should be empty string");
+ if (mLastData == IDEOGRAPHIC_SPACE) {
+ // If the last data is an ideographic space (FullWidth space), it must be
+ // a placeholder character of some Chinese IME. So, committing with
+ // this data must not be expected by users. Let's use empty string.
+ aCompositionEvent->mData.Truncate();
+ } else {
+ aCompositionEvent->mData = mLastData;
+ }
+ }
+
if (!IsValidStateForComposition(aCompositionEvent->widget)) {
*aStatus = nsEventStatus_eConsumeNoDefault;
return;
}
// If this instance has requested to commit or cancel composition but
// is not synthesizing commit event, that means that the IME commits or
// cancels the composition asynchronously. Typically, iBus behaves so.
@@ -163,16 +183,17 @@ TextComposition::DispatchCompositionEven
// 1. committing string is empty string at requesting commit but the last
// data isn't IDEOGRAPHIC SPACE.
// 2. non-empty string is committed at requesting cancel.
if (!aIsSynthesized && (mIsRequestingCommit || mIsRequestingCancel)) {
nsString* committingData = nullptr;
switch (aCompositionEvent->message) {
case NS_COMPOSITION_END:
case NS_COMPOSITION_CHANGE:
+ case NS_COMPOSITION_COMMIT_AS_IS:
committingData = &aCompositionEvent->mData;
break;
default:
NS_WARNING("Unexpected event comes during committing or "
"canceling composition");
break;
}
if (committingData) {
@@ -180,42 +201,72 @@ TextComposition::DispatchCompositionEven
mLastData != IDEOGRAPHIC_SPACE) {
committingData->Assign(mLastData);
} else if (mIsRequestingCancel && !committingData->IsEmpty()) {
committingData->Truncate();
}
}
}
- if (aCompositionEvent->CausesDOMTextEvent()) {
+ bool dispatchEvent = true;
+ bool dispatchDOMTextEvent = aCompositionEvent->CausesDOMTextEvent();
+
+ // When mIsComposing is false but the committing string is different from
+ // the last data (E.g., previous NS_COMPOSITION_CHANGE event made the
+ // composition string empty or didn't have clause information), we don't
+ // need to dispatch redundant DOM text event.
+ if (dispatchDOMTextEvent &&
+ aCompositionEvent->message != NS_COMPOSITION_CHANGE &&
+ !mIsComposing && mLastData == aCompositionEvent->mData) {
+ dispatchEvent = dispatchDOMTextEvent = false;
+ }
+
+ if (dispatchDOMTextEvent) {
if (!MaybeDispatchCompositionUpdate(aCompositionEvent)) {
return;
}
}
- EventDispatcher::Dispatch(mNode, mPresContext,
- aCompositionEvent, nullptr, aStatus, aCallBack);
+ if (dispatchEvent) {
+ // If the composition event should cause a DOM text event, we should
+ // overwrite the event message as NS_COMPOSITION_CHANGE because due to
+ // the limitation of mapping between event messages and DOM event types,
+ // we cannot map multiple event messages to a DOM event type.
+ if (dispatchDOMTextEvent &&
+ aCompositionEvent->message != NS_COMPOSITION_CHANGE) {
+ aCompositionEvent->mFlags =
+ CloneAndDispatchAs(aCompositionEvent, NS_COMPOSITION_CHANGE,
+ aStatus, aCallBack);
+ } else {
+ EventDispatcher::Dispatch(mNode, mPresContext,
+ aCompositionEvent, nullptr, aStatus, aCallBack);
+ }
+ } else {
+ *aStatus = nsEventStatus_eConsumeNoDefault;
+ }
if (!IsValidStateForComposition(aCompositionEvent->widget)) {
return;
}
// Emulate editor behavior of compositionchange event (DOM text event) handler
// if no editor handles composition events.
- if (aCompositionEvent->CausesDOMTextEvent() && !HasEditor()) {
+ if (dispatchDOMTextEvent && !HasEditor()) {
EditorWillHandleCompositionChangeEvent(aCompositionEvent);
EditorDidHandleCompositionChangeEvent();
}
-#ifdef DEBUG
- else if (aCompositionEvent->CausesDOMCompositionEndEvent()) {
+ if (aCompositionEvent->CausesDOMCompositionEndEvent()) {
+ // Dispatch a compositionend event if it's necessary.
+ if (aCompositionEvent->message != NS_COMPOSITION_END) {
+ CloneAndDispatchAs(aCompositionEvent, NS_COMPOSITION_END);
+ }
MOZ_ASSERT(!mIsComposing, "Why is the editor still composing?");
MOZ_ASSERT(!HasEditor(), "Why does the editor still keep to hold this?");
}
-#endif // #ifdef DEBUG
// Notify composition update to widget if possible
NotityUpdateComposition(aCompositionEvent);
}
void
TextComposition::NotityUpdateComposition(
const WidgetCompositionEvent* aCompositionEvent)
@@ -298,52 +349,62 @@ TextComposition::RequestToCommit(nsIWidg
return rv;
}
} else {
// Emulates to commit or cancel the composition
// FYI: These events may be discarded by PresShell if it's not safe to
// dispatch the event.
nsCOMPtr<nsIWidget> widget(aWidget);
nsAutoString commitData(aDiscard ? EmptyString() : lastData);
- bool changingData = lastData != commitData;
+ if (commitData == mLastData) {
+ WidgetCompositionEvent commitEvent(true, NS_COMPOSITION_COMMIT_AS_IS,
+ widget);
+ commitEvent.mFlags.mIsSynthesizedForTests = true;
+ nsEventStatus status = nsEventStatus_eIgnore;
+ widget->DispatchEvent(&commitEvent, status);
+ } else {
+ WidgetCompositionEvent changeEvent(true, NS_COMPOSITION_CHANGE, widget);
+ changeEvent.mData = commitData;
+ changeEvent.mFlags.mIsSynthesizedForTests = true;
- WidgetCompositionEvent changeEvent(true, NS_COMPOSITION_CHANGE, widget);
- changeEvent.mData = commitData;
- changeEvent.mFlags.mIsSynthesizedForTests = true;
-
- MaybeDispatchCompositionUpdate(&changeEvent);
+ MaybeDispatchCompositionUpdate(&changeEvent);
- // If changing the data or committing string isn't empty, we need to
- // dispatch compositionchange event for setting the composition string
- // without IME selection.
- if (IsValidStateForComposition(widget) &&
- (changingData || !commitData.IsEmpty())) {
- nsEventStatus status = nsEventStatus_eIgnore;
- widget->DispatchEvent(&changeEvent, status);
- }
+ // If changing the data or committing string isn't empty, we need to
+ // dispatch compositionchange event for setting the composition string
+ // without IME selection.
+ if (IsValidStateForComposition(widget)) {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ widget->DispatchEvent(&changeEvent, status);
+ }
- if (IsValidStateForComposition(widget)) {
- nsEventStatus status = nsEventStatus_eIgnore;
- WidgetCompositionEvent endEvent(true, NS_COMPOSITION_END, widget);
- endEvent.mData = commitData;
- endEvent.mFlags.mIsSynthesizedForTests = true;
- widget->DispatchEvent(&endEvent, status);
+ if (IsValidStateForComposition(widget)) {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetCompositionEvent endEvent(true, NS_COMPOSITION_END, widget);
+ endEvent.mData = commitData;
+ endEvent.mFlags.mIsSynthesizedForTests = true;
+ widget->DispatchEvent(&endEvent, status);
+ }
}
}
}
mRequestedToCommitOrCancel = true;
// If the request is performed synchronously, this must be already destroyed.
if (Destroyed()) {
return NS_OK;
}
// Otherwise, synthesize the commit in content.
nsAutoString data(aDiscard ? EmptyString() : lastData);
+ if (data == mLastData) {
+ DispatchCompositionEventRunnable(NS_COMPOSITION_COMMIT_AS_IS, EmptyString(),
+ true);
+ return NS_OK;
+ }
// If the last composition string and new data are different, we need to
// dispatch compositionchange event for removing IME selection. However, if
// the commit string is empty string and it's not changed from the last data,
// we don't need to dispatch compositionchange event.
if (lastData != data || !data.IsEmpty()) {
DispatchCompositionEventRunnable(NS_COMPOSITION_CHANGE, data, true);
}
DispatchCompositionEventRunnable(NS_COMPOSITION_END, data, true);
@@ -454,19 +515,22 @@ TextComposition::CompositionEventDispatc
compStart.mFlags.mIsSynthesizedForTests =
mTextComposition->IsSynthesizedForTests();
IMEStateManager::DispatchCompositionEvent(mEventTarget, presContext,
&compStart, &status, nullptr,
mIsSynthesizedEvent);
break;
}
case NS_COMPOSITION_END:
- case NS_COMPOSITION_CHANGE: {
+ case NS_COMPOSITION_CHANGE:
+ case NS_COMPOSITION_COMMIT_AS_IS: {
WidgetCompositionEvent compEvent(true, mEventMessage, widget);
- compEvent.mData = mData;
+ if (mEventMessage != NS_COMPOSITION_COMMIT_AS_IS) {
+ compEvent.mData = mData;
+ }
compEvent.mFlags.mIsSynthesizedForTests =
mTextComposition->IsSynthesizedForTests();
IMEStateManager::DispatchCompositionEvent(mEventTarget, presContext,
&compEvent, &status, nullptr,
mIsSynthesizedEvent);
break;
}
default:
--- a/dom/events/TextComposition.h
+++ b/dom/events/TextComposition.h
@@ -273,20 +273,24 @@ private:
* destroying this composition.
*/
bool MaybeDispatchCompositionUpdate(
const WidgetCompositionEvent* aCompositionEvent);
/**
* CloneAndDispatchAs() dispatches a composition event which is
* duplicateed from aCompositionEvent and set the aMessage.
+ *
+ * @return Returns BaseEventFlags which is the result of dispatched event.
*/
- void CloneAndDispatchAs(
- const WidgetCompositionEvent* aCompositionEvent,
- uint32_t aMessage);
+ BaseEventFlags CloneAndDispatchAs(
+ const WidgetCompositionEvent* aCompositionEvent,
+ uint32_t aMessage,
+ nsEventStatus* aStatus = nullptr,
+ EventDispatchingCallback* aCallBack = nullptr);
/**
* If IME has already dispatched compositionend event but it was discarded
* by PresShell due to not safe to dispatch, this returns true.
*/
bool WasNativeCompositionEndEventDiscarded() const
{
return mWasNativeCompositionEndEventDiscarded;
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -1032,20 +1032,22 @@ interface nsIDOMWindowUtils : nsISupport
/**
* Synthesize a composition event to the window.
*
* Cannot be accessed from unprivileged context (not content-accessible)
* Will throw a DOM security error if called without chrome privileges.
*
* @param aType The event type: "compositionstart", "compositionend" or
- * "compositionupdate".
+ * "compositioncommitasis".
* @param aData The data property value. Note that this isn't applied
* for compositionstart event because its value is the
- * selected text which is automatically computed.
+ * selected text which is automatically computed. And also
+ * this isn't applied for compositioncommitasis because
+ * the last data will be set automatically.
* @param aLocale The locale property value.
*/
void sendCompositionEvent(in AString aType,
in AString aData,
in AString aLocale);
/**
* Creating synthesizer of composition string on the window.
--- a/editor/libeditor/nsEditor.cpp
+++ b/editor/libeditor/nsEditor.cpp
@@ -4910,16 +4910,17 @@ nsEditor::IsAcceptableInputEvent(nsIDOME
case NS_USER_DEFINED_EVENT:
// If events are not created with proper event interface, their message
// are initialized with NS_USER_DEFINED_EVENT. Let's ignore such event.
return false;
case NS_COMPOSITION_START:
case NS_COMPOSITION_END:
case NS_COMPOSITION_UPDATE:
case NS_COMPOSITION_CHANGE:
+ case NS_COMPOSITION_COMMIT_AS_IS:
// Don't allow composition events whose internal event are not
// WidgetCompositionEvent.
widgetGUIEvent = aEvent->GetInternalNSEvent()->AsCompositionEvent();
needsWidget = true;
break;
default:
break;
}
--- a/testing/mochitest/tests/SimpleTest/EventUtils.js
+++ b/testing/mochitest/tests/SimpleTest/EventUtils.js
@@ -866,22 +866,23 @@ const COMPOSITION_ATTR_SELECTEDRAWTEXT
const COMPOSITION_ATTR_CONVERTEDTEXT = 0x04;
const COMPOSITION_ATTR_SELECTEDCONVERTEDTEXT = 0x05;
/**
* Synthesize a composition event.
*
* @param aEvent The composition event information. This must
* have |type| member. The value must be
- * "compositionstart" or "compositionend".
+ * "compositionstart", "compositionend" or
+ * "compositioncommitasis".
* And also this may have |data| and |locale| which
* would be used for the value of each property of
- * the composition event. Note that the data would
- * be ignored if the event type were
- * "compositionstart".
+ * the composition event. Note that the |data| is
+ * ignored if the event type is "compositionstart"
+ * or "compositioncommitasis".
* @param aWindow Optional (If null, current |window| will be used)
*/
function synthesizeComposition(aEvent, aWindow)
{
var utils = _getDOMWindowUtils(aWindow);
if (!utils) {
return;
}
--- a/widget/BasicEvents.h
+++ b/widget/BasicEvents.h
@@ -171,27 +171,37 @@
#define NS_MUTATION_CHARACTERDATAMODIFIED (NS_MUTATION_START+6)
#define NS_MUTATION_END (NS_MUTATION_START+6)
#define NS_USER_DEFINED_EVENT 2000
// composition events
#define NS_COMPOSITION_EVENT_START 2200
#define NS_COMPOSITION_START (NS_COMPOSITION_EVENT_START)
+// NS_COMPOSITION_END is the message for DOM compositionend event.
+// This event should NOT be dispatched from widget if NS_COMPOSITION_COMMIT
+// is available.
#define NS_COMPOSITION_END (NS_COMPOSITION_EVENT_START + 1)
// NS_COMPOSITION_UPDATE is the message for DOM compositionupdate event.
// This event should NOT be dispatched from widget since it will be dispatched
// by mozilla::TextComposition automatically if NS_COMPOSITION_CHANGE event
// will change composition string.
#define NS_COMPOSITION_UPDATE (NS_COMPOSITION_EVENT_START + 2)
// NS_COMPOSITION_CHANGE is the message for representing a change of
// composition string. This should be dispatched from widget even if
// composition string isn't changed but the ranges are changed. This causes
// a DOM "text" event which is a non-standard DOM event.
#define NS_COMPOSITION_CHANGE (NS_COMPOSITION_EVENT_START + 3)
+// NS_COMPOSITION_COMMIT_AS_IS is the message for representing a commit of
+// composition string. TextComposition will commit composition with the
+// last data. TextComposition will dispatch this event to the DOM tree as
+// NS_COMPOSITION_CHANGE without clause information. After that,
+// NS_COMPOSITION_END will be dispatched automatically.
+// Its mData and mRanges should be empty and nullptr.
+#define NS_COMPOSITION_COMMIT_AS_IS (NS_COMPOSITION_EVENT_START + 4)
// UI events
#define NS_UI_EVENT_START 2500
// this is not to be confused with NS_ACTIVATE!
#define NS_UI_ACTIVATE (NS_UI_EVENT_START)
#define NS_UI_FOCUSIN (NS_UI_EVENT_START + 1)
#define NS_UI_FOCUSOUT (NS_UI_EVENT_START + 2)
--- a/widget/EventForwards.h
+++ b/widget/EventForwards.h
@@ -97,16 +97,17 @@ namespace mozilla {
#define NS_ROOT_EVENT_CLASS(aPrefix, aName) NS_EVENT_CLASS(aPrefix, aName)
#include "mozilla/EventClassList.h"
#undef NS_EVENT_CLASS
#undef NS_ROOT_EVENT_CLASS
// BasicEvents.h
+struct BaseEventFlags;
struct EventFlags;
// TextEvents.h
struct AlternativeCharCode;
// TextRange.h
struct TextRangeStyle;
struct TextRange;
--- a/widget/TextEvents.h
+++ b/widget/TextEvents.h
@@ -340,22 +340,24 @@ public:
uint32_t RangeCount() const
{
return mRanges ? mRanges->Length() : 0;
}
bool CausesDOMTextEvent() const
{
- return message == NS_COMPOSITION_CHANGE;
+ return message == NS_COMPOSITION_CHANGE ||
+ message == NS_COMPOSITION_COMMIT_AS_IS;
}
bool CausesDOMCompositionEndEvent() const
{
- return message == NS_COMPOSITION_END;
+ return message == NS_COMPOSITION_END ||
+ message == NS_COMPOSITION_COMMIT_AS_IS;
}
};
/******************************************************************************
* mozilla::WidgetQueryContentEvent
******************************************************************************/
class WidgetQueryContentEvent : public WidgetGUIEvent
--- a/widget/WidgetEventImpl.cpp
+++ b/widget/WidgetEventImpl.cpp
@@ -135,16 +135,17 @@ WidgetEvent::HasKeyEventMessage() const
bool
WidgetEvent::HasIMEEventMessage() const
{
switch (message) {
case NS_COMPOSITION_START:
case NS_COMPOSITION_END:
case NS_COMPOSITION_UPDATE:
case NS_COMPOSITION_CHANGE:
+ case NS_COMPOSITION_COMMIT_AS_IS:
return true;
default:
return false;
}
}
bool
WidgetEvent::HasPluginActivationEventMessage() const