1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ContentEventHandler.h"
8 #include "IMEContentObserver.h"
9 #include "IMEStateManager.h"
10 #include "nsContentUtils.h"
11 #include "nsIContent.h"
12 #include "nsIMutationObserver.h"
13 #include "nsPresContext.h"
14 #include "mozilla/AutoRestore.h"
15 #include "mozilla/EditorBase.h"
16 #include "mozilla/EventDispatcher.h"
17 #include "mozilla/IMEStateManager.h"
18 #include "mozilla/IntegerRange.h"
19 #include "mozilla/MiscEvents.h"
20 #include "mozilla/PresShell.h"
21 #include "mozilla/RangeBoundary.h"
22 #include "mozilla/StaticPrefs_dom.h"
23 #include "mozilla/StaticPrefs_intl.h"
24 #include "mozilla/TextComposition.h"
25 #include "mozilla/TextEvents.h"
26 #include "mozilla/Unused.h"
27 #include "mozilla/dom/BrowserParent.h"
30 // Some defiens will be conflict with OSX SDK
31 # define TextRange _TextRange
32 # define TextRangeArray _TextRangeArray
33 # define Comment _Comment
38 # undef TextRangeArray
42 using namespace mozilla::widget
;
46 #define IDEOGRAPHIC_SPACE (u"\x3000"_ns)
48 static uint32_t GetOrCreateCompositionId(WidgetCompositionEvent
* aEvent
) {
49 // If we're in the parent process, return new composition ID.
50 if (XRE_IsParentProcess()) {
51 static uint32_t sNextCompositionId
= 1u;
52 if (MOZ_UNLIKELY(sNextCompositionId
== UINT32_MAX
)) {
53 sNextCompositionId
= 1u;
55 // FYI: When we send the event to a remote process, TextComposition will
56 // set aEvent->mCompositionId to this value. Therefore, we don't need to
58 return sNextCompositionId
++;
60 // If aEvent comes from the parent process, the event has composition ID
61 // considered by the parent process. Then, we should use it.
62 // Otherwise, aEvent is synthesized in this process, it won't cross the
63 // process boundary between this process and the parent process. Therefore,
64 // we don't need to set meaningful composition ID for the text composition.
65 return aEvent
->mCompositionId
;
68 /******************************************************************************
70 ******************************************************************************/
72 bool TextComposition::sHandlingSelectionEvent
= false;
74 TextComposition::TextComposition(nsPresContext
* aPresContext
, nsINode
* aNode
,
75 BrowserParent
* aBrowserParent
,
76 WidgetCompositionEvent
* aCompositionEvent
)
77 : mPresContext(aPresContext
),
79 mBrowserParent(aBrowserParent
),
80 mNativeContext(aCompositionEvent
->mNativeIMEContext
),
81 mCompositionId(GetOrCreateCompositionId(aCompositionEvent
)),
82 mCompositionStartOffset(0),
83 mTargetClauseOffsetInComposition(0),
84 mCompositionStartOffsetInTextNode(UINT32_MAX
),
85 mCompositionLengthInTextNode(UINT32_MAX
),
86 mIsSynthesizedForTests(aCompositionEvent
->mFlags
.mIsSynthesizedForTests
),
88 mIsEditorHandlingEvent(false),
89 mIsRequestingCommit(false),
90 mIsRequestingCancel(false),
91 mRequestedToCommitOrCancel(false),
92 mHasDispatchedDOMTextEvent(false),
93 mHasReceivedCommitEvent(false),
94 mWasNativeCompositionEndEventDiscarded(false),
95 mAllowControlCharacters(
96 StaticPrefs::dom_compositionevent_allow_control_characters()),
97 mWasCompositionStringEmpty(true) {
98 MOZ_ASSERT(aCompositionEvent
->mNativeIMEContext
.IsValid());
101 void TextComposition::Destroy() {
102 mPresContext
= nullptr;
104 mBrowserParent
= nullptr;
105 mContainerTextNode
= nullptr;
106 mCompositionStartOffsetInTextNode
= UINT32_MAX
;
107 mCompositionLengthInTextNode
= UINT32_MAX
;
108 // TODO: If the editor is still alive and this is held by it, we should tell
109 // this being destroyed for cleaning up the stuff.
112 void TextComposition::OnCharacterDataChanged(
113 Text
& aText
, const CharacterDataChangeInfo
& aInfo
) {
114 if (mContainerTextNode
!= &aText
||
115 mCompositionStartOffsetInTextNode
== UINT32_MAX
||
116 mCompositionLengthInTextNode
== UINT32_MAX
) {
120 // Ignore changes after composition string.
121 if (aInfo
.mChangeStart
>=
122 mCompositionStartOffsetInTextNode
+ mCompositionLengthInTextNode
) {
126 // If the change ends before the composition string, we need only to adjust
128 if (aInfo
.mChangeEnd
<= mCompositionStartOffsetInTextNode
) {
129 MOZ_ASSERT(aInfo
.LengthOfRemovedText() <=
130 mCompositionStartOffsetInTextNode
);
131 mCompositionStartOffsetInTextNode
-= aInfo
.LengthOfRemovedText();
132 mCompositionStartOffsetInTextNode
+= aInfo
.mReplaceLength
;
136 // If this is caused by a splitting text node, the composition string
137 // may be split out to the new right node. In the case,
138 // CompositionTransaction::DoTransaction handles it with warking the
139 // following text nodes. Therefore, we should NOT shrink the composing
140 // range for avoind breaking the fix of bug 1310912. Although the handling
141 // looks buggy so that we need to move the handling into here later.
142 if (aInfo
.mDetails
&&
143 aInfo
.mDetails
->mType
== CharacterDataChangeInfo::Details::eSplit
) {
147 // If the change removes/replaces the last character of the composition
148 // string, we should shrink the composition range before the change start.
149 // Then, the replace string will be never updated by coming composition
151 if (aInfo
.mChangeEnd
>=
152 mCompositionStartOffsetInTextNode
+ mCompositionLengthInTextNode
) {
153 // If deleting the first character of the composition string, collapse IME
154 // selection temporarily. Updating composition string will insert new
155 // composition string there.
156 if (aInfo
.mChangeStart
<= mCompositionStartOffsetInTextNode
) {
157 mCompositionStartOffsetInTextNode
= aInfo
.mChangeStart
;
158 mCompositionLengthInTextNode
= 0u;
161 // If some characters in the composition still stay, composition range
162 // should be shrunken.
163 MOZ_ASSERT(aInfo
.mChangeStart
> mCompositionStartOffsetInTextNode
);
164 mCompositionLengthInTextNode
=
165 aInfo
.mChangeStart
- mCompositionStartOffsetInTextNode
;
169 // If removed range starts in the composition string, we need only adjust
170 // the length to make composition range contain the replace string.
171 if (aInfo
.mChangeStart
>= mCompositionStartOffsetInTextNode
) {
172 MOZ_ASSERT(aInfo
.LengthOfRemovedText() <= mCompositionLengthInTextNode
);
173 mCompositionLengthInTextNode
-= aInfo
.LengthOfRemovedText();
174 mCompositionLengthInTextNode
+= aInfo
.mReplaceLength
;
178 // If preceding characers of the composition string is also removed, new
179 // composition start will be there and new composition ends at current
181 const uint32_t removedLengthInCompositionString
=
182 aInfo
.mChangeEnd
- mCompositionStartOffsetInTextNode
;
183 mCompositionStartOffsetInTextNode
= aInfo
.mChangeStart
;
184 mCompositionLengthInTextNode
-= removedLengthInCompositionString
;
185 mCompositionLengthInTextNode
+= aInfo
.mReplaceLength
;
188 bool TextComposition::IsValidStateForComposition(nsIWidget
* aWidget
) const {
189 return !Destroyed() && aWidget
&& !aWidget
->Destroyed() &&
190 mPresContext
->GetPresShell() &&
191 !mPresContext
->PresShell()->IsDestroying();
194 bool TextComposition::MaybeDispatchCompositionUpdate(
195 const WidgetCompositionEvent
* aCompositionEvent
) {
196 MOZ_RELEASE_ASSERT(!mBrowserParent
);
198 if (!IsValidStateForComposition(aCompositionEvent
->mWidget
)) {
202 // Note that we don't need to dispatch eCompositionUpdate event even if
203 // mHasDispatchedDOMTextEvent is false and eCompositionCommit event is
204 // dispatched with empty string immediately after eCompositionStart
205 // because composition string has never been changed from empty string to
206 // non-empty string in such composition even if selected string was not
207 // empty string (mLastData isn't set to selected text when this receives
208 // eCompositionStart).
209 if (mLastData
== aCompositionEvent
->mData
) {
212 CloneAndDispatchAs(aCompositionEvent
, eCompositionUpdate
);
213 return IsValidStateForComposition(aCompositionEvent
->mWidget
);
216 BaseEventFlags
TextComposition::CloneAndDispatchAs(
217 const WidgetCompositionEvent
* aCompositionEvent
, EventMessage aMessage
,
218 nsEventStatus
* aStatus
, EventDispatchingCallback
* aCallBack
) {
219 MOZ_RELEASE_ASSERT(!mBrowserParent
);
221 MOZ_ASSERT(IsValidStateForComposition(aCompositionEvent
->mWidget
),
222 "Should be called only when it's safe to dispatch an event");
224 WidgetCompositionEvent
compositionEvent(aCompositionEvent
->IsTrusted(),
225 aMessage
, aCompositionEvent
->mWidget
);
226 compositionEvent
.mTimeStamp
= aCompositionEvent
->mTimeStamp
;
227 compositionEvent
.mData
= aCompositionEvent
->mData
;
228 compositionEvent
.mNativeIMEContext
= aCompositionEvent
->mNativeIMEContext
;
229 compositionEvent
.mOriginalMessage
= aCompositionEvent
->mMessage
;
230 compositionEvent
.mFlags
.mIsSynthesizedForTests
=
231 aCompositionEvent
->mFlags
.mIsSynthesizedForTests
;
233 nsEventStatus dummyStatus
= nsEventStatus_eConsumeNoDefault
;
234 nsEventStatus
* status
= aStatus
? aStatus
: &dummyStatus
;
235 if (aMessage
== eCompositionUpdate
) {
236 mLastData
= compositionEvent
.mData
;
237 mLastRanges
= aCompositionEvent
->mRanges
;
240 DispatchEvent(&compositionEvent
, status
, aCallBack
, aCompositionEvent
);
241 return compositionEvent
.mFlags
;
244 void TextComposition::DispatchEvent(
245 WidgetCompositionEvent
* aDispatchEvent
, nsEventStatus
* aStatus
,
246 EventDispatchingCallback
* aCallBack
,
247 const WidgetCompositionEvent
* aOriginalEvent
) {
248 if (aDispatchEvent
->mMessage
== eCompositionChange
) {
249 aDispatchEvent
->mFlags
.mOnlySystemGroupDispatchInContent
= true;
251 RefPtr
<nsINode
> node
= mNode
;
252 RefPtr
<nsPresContext
> presContext
= mPresContext
;
253 EventDispatcher::Dispatch(node
, presContext
, aDispatchEvent
, nullptr, aStatus
,
256 OnCompositionEventDispatched(aDispatchEvent
);
259 void TextComposition::OnCompositionEventDiscarded(
260 WidgetCompositionEvent
* aCompositionEvent
) {
261 // Note that this method is never called for synthesized events for emulating
262 // commit or cancel composition.
264 MOZ_ASSERT(aCompositionEvent
->IsTrusted(),
265 "Shouldn't be called with untrusted event");
267 if (mBrowserParent
) {
268 Unused
<< mBrowserParent
->SendCompositionEvent(*aCompositionEvent
,
272 // XXX If composition events are discarded, should we dispatch them with
273 // runnable event? However, even if we do so, it might make native IME
274 // confused due to async modification. Especially when native IME is
276 if (!aCompositionEvent
->CausesDOMCompositionEndEvent()) {
280 mWasNativeCompositionEndEventDiscarded
= true;
283 static inline bool IsControlChar(uint32_t aCharCode
) {
284 return aCharCode
< ' ' || aCharCode
== 0x7F;
287 static size_t FindFirstControlCharacter(const nsAString
& aStr
) {
288 const char16_t
* sourceBegin
= aStr
.BeginReading();
289 const char16_t
* sourceEnd
= aStr
.EndReading();
291 for (const char16_t
* source
= sourceBegin
; source
< sourceEnd
; ++source
) {
292 if (*source
!= '\t' && IsControlChar(*source
)) {
293 return source
- sourceBegin
;
300 static void RemoveControlCharactersFrom(nsAString
& aStr
,
301 TextRangeArray
* aRanges
) {
302 size_t firstControlCharOffset
= FindFirstControlCharacter(aStr
);
303 if (firstControlCharOffset
== (size_t)-1) {
307 nsAutoString
copy(aStr
);
308 const char16_t
* sourceBegin
= copy
.BeginReading();
309 const char16_t
* sourceEnd
= copy
.EndReading();
311 char16_t
* dest
= aStr
.BeginWriting();
312 if (NS_WARN_IF(!dest
)) {
316 char16_t
* curDest
= dest
+ firstControlCharOffset
;
317 size_t i
= firstControlCharOffset
;
318 for (const char16_t
* source
= sourceBegin
+ firstControlCharOffset
;
319 source
< sourceEnd
; ++source
) {
320 if (*source
== '\t' || *source
== '\n' || !IsControlChar(*source
)) {
324 } else if (aRanges
) {
325 aRanges
->RemoveCharacter(i
);
329 aStr
.SetLength(curDest
- dest
);
332 nsString
TextComposition::CommitStringIfCommittedAsIs() const {
333 nsString
result(mLastData
);
334 if (!mAllowControlCharacters
) {
335 RemoveControlCharactersFrom(result
, nullptr);
337 if (StaticPrefs::intl_ime_remove_placeholder_character_at_commit() &&
338 mLastData
== IDEOGRAPHIC_SPACE
) {
339 return EmptyString();
344 void TextComposition::DispatchCompositionEvent(
345 WidgetCompositionEvent
* aCompositionEvent
, nsEventStatus
* aStatus
,
346 EventDispatchingCallback
* aCallBack
, bool aIsSynthesized
) {
347 mWasCompositionStringEmpty
= mString
.IsEmpty();
349 if (aCompositionEvent
->IsFollowedByCompositionEnd()) {
350 mHasReceivedCommitEvent
= true;
353 // If this instance has requested to commit or cancel composition but
354 // is not synthesizing commit event, that means that the IME commits or
355 // cancels the composition asynchronously. Typically, iBus behaves so.
356 // Then, synthesized events which were dispatched immediately after
357 // the request has already committed our editor's composition string and
358 // told it to web apps. Therefore, we should ignore the delayed events.
359 if (mRequestedToCommitOrCancel
&& !aIsSynthesized
) {
360 *aStatus
= nsEventStatus_eConsumeNoDefault
;
364 // If the content is a container of BrowserParent, composition should be in
365 // the remote process.
366 if (mBrowserParent
) {
367 Unused
<< mBrowserParent
->SendCompositionEvent(*aCompositionEvent
,
369 aCompositionEvent
->StopPropagation();
370 if (aCompositionEvent
->CausesDOMTextEvent()) {
371 mLastData
= aCompositionEvent
->mData
;
372 mLastRanges
= aCompositionEvent
->mRanges
;
373 // Although, the composition event hasn't been actually handled yet,
374 // emulate an editor to be handling the composition event.
375 EditorWillHandleCompositionChangeEvent(aCompositionEvent
);
376 EditorDidHandleCompositionChangeEvent();
381 if (!mAllowControlCharacters
) {
382 RemoveControlCharactersFrom(aCompositionEvent
->mData
,
383 aCompositionEvent
->mRanges
);
385 if (aCompositionEvent
->mMessage
== eCompositionCommitAsIs
) {
386 NS_ASSERTION(!aCompositionEvent
->mRanges
,
387 "mRanges of eCompositionCommitAsIs should be null");
388 aCompositionEvent
->mRanges
= nullptr;
389 NS_ASSERTION(aCompositionEvent
->mData
.IsEmpty(),
390 "mData of eCompositionCommitAsIs should be empty string");
391 if (StaticPrefs::intl_ime_remove_placeholder_character_at_commit() &&
392 mLastData
== IDEOGRAPHIC_SPACE
) {
393 // If the last data is an ideographic space (FullWidth space), it might be
394 // a placeholder character of some Chinese IME. So, committing with
395 // this data might not be expected by users. Let's use empty string.
396 aCompositionEvent
->mData
.Truncate();
398 aCompositionEvent
->mData
= mLastData
;
400 } else if (aCompositionEvent
->mMessage
== eCompositionCommit
) {
401 NS_ASSERTION(!aCompositionEvent
->mRanges
,
402 "mRanges of eCompositionCommit should be null");
403 aCompositionEvent
->mRanges
= nullptr;
406 if (!IsValidStateForComposition(aCompositionEvent
->mWidget
)) {
407 *aStatus
= nsEventStatus_eConsumeNoDefault
;
411 // IME may commit composition with empty string for a commit request or
412 // with non-empty string for a cancel request. We should prevent such
413 // unexpected result. E.g., web apps may be confused if they implement
414 // autocomplete which attempts to commit composition forcibly when the user
415 // selects one of suggestions but composition string is cleared by IME.
416 // Note that most Chinese IMEs don't expose actual composition string to us.
417 // They typically tell us an IDEOGRAPHIC SPACE or empty string as composition
418 // string. Therefore, we should hack it only when:
419 // 1. committing string is empty string at requesting commit but the last
420 // data isn't IDEOGRAPHIC SPACE.
421 // 2. non-empty string is committed at requesting cancel.
422 if (!aIsSynthesized
&& (mIsRequestingCommit
|| mIsRequestingCancel
)) {
423 nsString
* committingData
= nullptr;
424 switch (aCompositionEvent
->mMessage
) {
425 case eCompositionEnd
:
426 case eCompositionChange
:
427 case eCompositionCommitAsIs
:
428 case eCompositionCommit
:
429 committingData
= &aCompositionEvent
->mData
;
433 "Unexpected event comes during committing or "
434 "canceling composition");
437 if (committingData
) {
438 if (mIsRequestingCommit
&& committingData
->IsEmpty() &&
439 mLastData
!= IDEOGRAPHIC_SPACE
) {
440 committingData
->Assign(mLastData
);
441 } else if (mIsRequestingCancel
&& !committingData
->IsEmpty()) {
442 committingData
->Truncate();
447 bool dispatchEvent
= true;
448 bool dispatchDOMTextEvent
= aCompositionEvent
->CausesDOMTextEvent();
450 // When mIsComposing is false but the committing string is different from
451 // the last data (E.g., previous eCompositionChange event made the
452 // composition string empty or didn't have clause information), we don't
453 // need to dispatch redundant DOM text event. (But note that we need to
454 // dispatch eCompositionChange event if we have not dispatched
455 // eCompositionChange event yet and commit string replaces selected string
456 // with empty string since selected string hasn't been replaced with empty
458 if (dispatchDOMTextEvent
&&
459 aCompositionEvent
->mMessage
!= eCompositionChange
&& !mIsComposing
&&
460 mHasDispatchedDOMTextEvent
&& mLastData
== aCompositionEvent
->mData
) {
461 dispatchEvent
= dispatchDOMTextEvent
= false;
464 // widget may dispatch redundant eCompositionChange event
465 // which modifies neither composition string, clauses nor caret
466 // position. In such case, we shouldn't dispatch DOM events.
467 if (dispatchDOMTextEvent
&&
468 aCompositionEvent
->mMessage
== eCompositionChange
&&
469 mLastData
== aCompositionEvent
->mData
&& mRanges
&&
470 aCompositionEvent
->mRanges
&&
471 mRanges
->Equals(*aCompositionEvent
->mRanges
)) {
472 dispatchEvent
= dispatchDOMTextEvent
= false;
475 if (dispatchDOMTextEvent
) {
476 if (!MaybeDispatchCompositionUpdate(aCompositionEvent
)) {
482 // If the composition event should cause a DOM text event, we should
483 // overwrite the event message as eCompositionChange because due to
484 // the limitation of mapping between event messages and DOM event types,
485 // we cannot map multiple event messages to a DOM event type.
486 if (dispatchDOMTextEvent
&&
487 aCompositionEvent
->mMessage
!= eCompositionChange
) {
488 mHasDispatchedDOMTextEvent
= true;
489 aCompositionEvent
->mFlags
= CloneAndDispatchAs(
490 aCompositionEvent
, eCompositionChange
, aStatus
, aCallBack
);
492 if (aCompositionEvent
->mMessage
== eCompositionChange
) {
493 mHasDispatchedDOMTextEvent
= true;
495 DispatchEvent(aCompositionEvent
, aStatus
, aCallBack
);
498 *aStatus
= nsEventStatus_eConsumeNoDefault
;
501 if (!IsValidStateForComposition(aCompositionEvent
->mWidget
)) {
505 // Emulate editor behavior of compositionchange event (DOM text event) handler
506 // if no editor handles composition events.
507 if (dispatchDOMTextEvent
&& !HasEditor()) {
508 EditorWillHandleCompositionChangeEvent(aCompositionEvent
);
509 EditorDidHandleCompositionChangeEvent();
512 if (aCompositionEvent
->CausesDOMCompositionEndEvent()) {
513 // Dispatch a compositionend event if it's necessary.
514 if (aCompositionEvent
->mMessage
!= eCompositionEnd
) {
515 CloneAndDispatchAs(aCompositionEvent
, eCompositionEnd
);
517 MOZ_ASSERT(!mIsComposing
, "Why is the editor still composing?");
518 MOZ_ASSERT(!HasEditor(), "Why does the editor still keep to hold this?");
521 MaybeNotifyIMEOfCompositionEventHandled(aCompositionEvent
);
525 void TextComposition::HandleSelectionEvent(
526 nsPresContext
* aPresContext
, BrowserParent
* aBrowserParent
,
527 WidgetSelectionEvent
* aSelectionEvent
) {
528 // If the content is a container of BrowserParent, composition should be in
529 // the remote process.
530 if (aBrowserParent
) {
531 Unused
<< aBrowserParent
->SendSelectionEvent(*aSelectionEvent
);
532 aSelectionEvent
->StopPropagation();
536 AutoRestore
<bool> saveHandlingSelectionEvent(sHandlingSelectionEvent
);
537 sHandlingSelectionEvent
= true;
539 if (RefPtr
<IMEContentObserver
> contentObserver
=
540 IMEStateManager::GetActiveContentObserver()) {
541 contentObserver
->MaybeHandleSelectionEvent(aPresContext
, aSelectionEvent
);
545 ContentEventHandler
handler(aPresContext
);
546 // XXX During setting selection, a selection listener may change selection
547 // again. In such case, sHandlingSelectionEvent doesn't indicate if
548 // the selection change is caused by a selection event. However, it
549 // must be non-realistic scenario.
550 handler
.OnSelectionEvent(aSelectionEvent
);
553 uint32_t TextComposition::GetSelectionStartOffset() {
554 nsCOMPtr
<nsIWidget
> widget
= mPresContext
->GetRootWidget();
555 WidgetQueryContentEvent
querySelectedTextEvent(true, eQuerySelectedText
,
557 // Due to a bug of widget, mRanges may not be nullptr even though composition
558 // string is empty. So, we need to check it here for avoiding to return
560 if (!mLastData
.IsEmpty() && mRanges
&& mRanges
->HasClauses()) {
561 querySelectedTextEvent
.InitForQuerySelectedText(
562 ToSelectionType(mRanges
->GetFirstClause()->mRangeType
));
564 NS_WARNING_ASSERTION(
565 !mLastData
.IsEmpty() || !mRanges
|| !mRanges
->HasClauses(),
566 "Shouldn't have empty clause info when composition string is empty");
567 querySelectedTextEvent
.InitForQuerySelectedText(SelectionType::eNormal
);
570 // The editor which has this composition is observed by active
571 // IMEContentObserver, we can use the cache of it.
572 RefPtr
<IMEContentObserver
> contentObserver
=
573 IMEStateManager::GetActiveContentObserver();
574 bool doQuerySelection
= true;
575 if (contentObserver
) {
576 if (contentObserver
->IsObserving(*this)) {
577 doQuerySelection
= false;
578 contentObserver
->HandleQueryContentEvent(&querySelectedTextEvent
);
580 // If another editor already has focus, we cannot retrieve selection
581 // in the editor which has this composition...
582 else if (NS_WARN_IF(contentObserver
->GetPresContext() == mPresContext
)) {
583 return 0; // XXX Is this okay?
587 // Otherwise, using slow path (i.e., compute every time with
588 // ContentEventHandler)
589 if (doQuerySelection
) {
590 ContentEventHandler
handler(mPresContext
);
591 handler
.HandleQueryContentEvent(&querySelectedTextEvent
);
594 if (NS_WARN_IF(querySelectedTextEvent
.DidNotFindSelection())) {
595 return 0; // XXX Is this okay?
597 return querySelectedTextEvent
.mReply
->AnchorOffset();
600 void TextComposition::OnCompositionEventDispatched(
601 const WidgetCompositionEvent
* aCompositionEvent
) {
602 MOZ_RELEASE_ASSERT(!mBrowserParent
);
604 if (!IsValidStateForComposition(aCompositionEvent
->mWidget
)) {
608 // Every composition event may cause changing composition start offset,
609 // especially when there is no composition string. Therefore, we need to
610 // update mCompositionStartOffset with the latest offset.
612 MOZ_ASSERT(aCompositionEvent
->mMessage
!= eCompositionStart
||
613 mWasCompositionStringEmpty
,
614 "mWasCompositionStringEmpty should be true if the dispatched "
615 "event is eCompositionStart");
617 if (mWasCompositionStringEmpty
&&
618 !aCompositionEvent
->CausesDOMCompositionEndEvent()) {
619 // If there was no composition string, current selection start may be the
620 // offset for inserting composition string.
621 // Update composition start offset with current selection start.
622 mCompositionStartOffset
= GetSelectionStartOffset();
623 mTargetClauseOffsetInComposition
= 0;
626 if (aCompositionEvent
->CausesDOMTextEvent()) {
627 mTargetClauseOffsetInComposition
= aCompositionEvent
->TargetClauseOffset();
631 void TextComposition::OnStartOffsetUpdatedInChild(uint32_t aStartOffset
) {
632 mCompositionStartOffset
= aStartOffset
;
635 void TextComposition::MaybeNotifyIMEOfCompositionEventHandled(
636 const WidgetCompositionEvent
* aCompositionEvent
) {
637 if (aCompositionEvent
->mMessage
!= eCompositionStart
&&
638 !aCompositionEvent
->CausesDOMTextEvent()) {
642 RefPtr
<IMEContentObserver
> contentObserver
=
643 IMEStateManager::GetActiveContentObserver();
644 // When IMEContentObserver is managing the editor which has this composition,
645 // composition event handled notification should be sent after the observer
646 // notifies all pending notifications. Therefore, we should use it.
647 // XXX If IMEContentObserver suddenly loses focus after here and notifying
648 // widget of pending notifications, we won't notify widget of composition
649 // event handled. Although, this is a bug but it should be okay since
650 // destroying IMEContentObserver notifies IME of blur. So, native IME
651 // handler can treat it as this notification too.
652 if (contentObserver
&& contentObserver
->IsObserving(*this)) {
653 contentObserver
->MaybeNotifyCompositionEventHandled();
656 // Otherwise, e.g., this composition is in non-active window, we should
657 // notify widget directly.
658 NotifyIME(NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED
);
661 void TextComposition::DispatchCompositionEventRunnable(
662 EventMessage aEventMessage
, const nsAString
& aData
,
663 bool aIsSynthesizingCommit
) {
664 nsContentUtils::AddScriptRunner(new CompositionEventDispatcher(
665 this, mNode
, aEventMessage
, aData
, aIsSynthesizingCommit
));
668 nsresult
TextComposition::RequestToCommit(nsIWidget
* aWidget
, bool aDiscard
) {
669 MOZ_ASSERT(this == IMEStateManager::GetTextCompositionFor(aWidget
));
670 // If this composition is already requested to be committed or canceled,
671 // or has already finished in IME, we don't need to request it again because
672 // request from this instance shouldn't cause committing nor canceling current
673 // composition in IME, and even if the first request failed, new request
674 // won't success, probably. And we shouldn't synthesize events for
675 // committing or canceling composition twice or more times.
676 if (!CanRequsetIMEToCommitOrCancelComposition()) {
680 RefPtr
<TextComposition
> kungFuDeathGrip(this);
681 const nsAutoString
lastData(mLastData
);
683 if (IMEStateManager::CanSendNotificationToWidget()) {
684 AutoRestore
<bool> saveRequestingCancel(mIsRequestingCancel
);
685 AutoRestore
<bool> saveRequestingCommit(mIsRequestingCommit
);
687 mIsRequestingCancel
= true;
688 mIsRequestingCommit
= false;
690 mIsRequestingCancel
= false;
691 mIsRequestingCommit
= true;
693 // FYI: CompositionEvents caused by a call of NotifyIME() may be
694 // discarded by PresShell if it's not safe to dispatch the event.
695 nsresult rv
= aWidget
->NotifyIME(
696 IMENotification(aDiscard
? REQUEST_TO_CANCEL_COMPOSITION
697 : REQUEST_TO_COMMIT_COMPOSITION
));
698 if (NS_WARN_IF(NS_FAILED(rv
))) {
703 mRequestedToCommitOrCancel
= true;
705 // If the request is performed synchronously, this must be already destroyed.
710 // Otherwise, synthesize the commit in content.
711 nsAutoString
data(aDiscard
? EmptyString() : lastData
);
712 if (data
== mLastData
) {
713 DispatchCompositionEventRunnable(eCompositionCommitAsIs
, u
""_ns
, true);
715 DispatchCompositionEventRunnable(eCompositionCommit
, data
, true);
720 nsresult
TextComposition::NotifyIME(IMEMessage aMessage
) {
721 NS_ENSURE_TRUE(mPresContext
, NS_ERROR_NOT_AVAILABLE
);
722 return IMEStateManager::NotifyIME(aMessage
, mPresContext
, mBrowserParent
);
725 void TextComposition::EditorWillHandleCompositionChangeEvent(
726 const WidgetCompositionEvent
* aCompositionChangeEvent
) {
727 mIsComposing
= aCompositionChangeEvent
->IsComposing();
728 mRanges
= aCompositionChangeEvent
->mRanges
;
729 mIsEditorHandlingEvent
= true;
732 mLastData
== aCompositionChangeEvent
->mData
,
733 "The text of a compositionchange event must be same as previous data "
734 "attribute value of the latest compositionupdate event");
737 void TextComposition::OnEditorDestroyed() {
738 MOZ_RELEASE_ASSERT(!mBrowserParent
);
740 MOZ_ASSERT(!mIsEditorHandlingEvent
,
741 "The editor should have stopped listening events");
742 nsCOMPtr
<nsIWidget
> widget
= GetWidget();
743 if (NS_WARN_IF(!widget
)) {
744 // XXX If this could happen, how do we notify IME of destroying the editor?
748 // Try to cancel the composition.
749 RequestToCommit(widget
, true);
752 void TextComposition::EditorDidHandleCompositionChangeEvent() {
754 mIsEditorHandlingEvent
= false;
757 void TextComposition::StartHandlingComposition(EditorBase
* aEditorBase
) {
758 MOZ_RELEASE_ASSERT(!mBrowserParent
);
760 MOZ_ASSERT(!HasEditor(), "There is a handling editor already");
761 mEditorBaseWeak
= do_GetWeakReference(static_cast<nsIEditor
*>(aEditorBase
));
764 void TextComposition::EndHandlingComposition(EditorBase
* aEditorBase
) {
765 MOZ_RELEASE_ASSERT(!mBrowserParent
);
768 RefPtr
<EditorBase
> editorBase
= GetEditorBase();
769 MOZ_ASSERT(!editorBase
|| editorBase
== aEditorBase
,
770 "Another editor handled the composition?");
771 #endif // #ifdef DEBUG
772 mEditorBaseWeak
= nullptr;
775 already_AddRefed
<EditorBase
> TextComposition::GetEditorBase() const {
776 nsCOMPtr
<nsIEditor
> editor
= do_QueryReferent(mEditorBaseWeak
);
777 RefPtr
<EditorBase
> editorBase
= static_cast<EditorBase
*>(editor
.get());
778 return editorBase
.forget();
781 bool TextComposition::HasEditor() const {
782 return mEditorBaseWeak
&& mEditorBaseWeak
->IsAlive();
785 RawRangeBoundary
TextComposition::FirstIMESelectionStartRef() const {
786 RefPtr
<EditorBase
> editorBase
= GetEditorBase();
788 return RawRangeBoundary();
791 nsISelectionController
* selectionController
=
792 editorBase
->GetSelectionController();
793 if (NS_WARN_IF(!selectionController
)) {
794 return RawRangeBoundary();
797 const nsRange
* firstRange
= nullptr;
798 static const SelectionType kIMESelectionTypes
[] = {
799 SelectionType::eIMERawClause
, SelectionType::eIMESelectedRawClause
,
800 SelectionType::eIMEConvertedClause
, SelectionType::eIMESelectedClause
};
801 for (auto selectionType
: kIMESelectionTypes
) {
802 dom::Selection
* selection
=
803 selectionController
->GetSelection(ToRawSelectionType(selectionType
));
807 const uint32_t rangeCount
= selection
->RangeCount();
808 for (const uint32_t i
: IntegerRange(rangeCount
)) {
809 MOZ_ASSERT(selection
->RangeCount() == rangeCount
);
810 const nsRange
* range
= selection
->GetRangeAt(i
);
812 if (MOZ_UNLIKELY(NS_WARN_IF(!range
)) ||
813 MOZ_UNLIKELY(NS_WARN_IF(!range
->GetStartContainer()))) {
820 // In most cases, all composition string should be in same text node.
821 if (firstRange
->GetStartContainer() == range
->GetStartContainer()) {
822 if (firstRange
->StartOffset() > range
->StartOffset()) {
827 // However, if web apps have inserted different nodes in composition
828 // string, composition string may span 2 or more nodes.
829 if (firstRange
->GetStartContainer()->GetNextSibling() ==
830 range
->GetStartContainer()) {
831 // Fast path for some known applications like Google Keep.
835 // Unfortunately, really slow path.
836 // The ranges should always have a common ancestor, hence, be comparable.
837 if (*nsContentUtils::ComparePoints(range
->StartRef(),
838 firstRange
->StartRef()) == -1) {
843 return firstRange
? firstRange
->StartRef().AsRaw() : RawRangeBoundary();
846 RawRangeBoundary
TextComposition::LastIMESelectionEndRef() const {
847 RefPtr
<EditorBase
> editorBase
= GetEditorBase();
849 return RawRangeBoundary();
852 nsISelectionController
* selectionController
=
853 editorBase
->GetSelectionController();
854 if (NS_WARN_IF(!selectionController
)) {
855 return RawRangeBoundary();
858 const nsRange
* lastRange
= nullptr;
859 static const SelectionType kIMESelectionTypes
[] = {
860 SelectionType::eIMERawClause
, SelectionType::eIMESelectedRawClause
,
861 SelectionType::eIMEConvertedClause
, SelectionType::eIMESelectedClause
};
862 for (auto selectionType
: kIMESelectionTypes
) {
863 dom::Selection
* selection
=
864 selectionController
->GetSelection(ToRawSelectionType(selectionType
));
868 const uint32_t rangeCount
= selection
->RangeCount();
869 for (const uint32_t i
: IntegerRange(rangeCount
)) {
870 MOZ_ASSERT(selection
->RangeCount() == rangeCount
);
871 const nsRange
* range
= selection
->GetRangeAt(i
);
873 if (MOZ_UNLIKELY(NS_WARN_IF(!range
)) ||
874 MOZ_UNLIKELY(NS_WARN_IF(!range
->GetEndContainer()))) {
881 // In most cases, all composition string should be in same text node.
882 if (lastRange
->GetEndContainer() == range
->GetEndContainer()) {
883 if (lastRange
->EndOffset() < range
->EndOffset()) {
888 // However, if web apps have inserted different nodes in composition
889 // string, composition string may span 2 or more nodes.
890 if (lastRange
->GetEndContainer() ==
891 range
->GetEndContainer()->GetNextSibling()) {
892 // Fast path for some known applications like Google Keep.
896 // Unfortunately, really slow path.
897 // The ranges should always have a common ancestor, hence, be comparable.
898 if (*nsContentUtils::ComparePoints(lastRange
->EndRef(),
899 range
->EndRef()) == -1) {
904 return lastRange
? lastRange
->EndRef().AsRaw() : RawRangeBoundary();
907 /******************************************************************************
908 * TextComposition::CompositionEventDispatcher
909 ******************************************************************************/
911 TextComposition::CompositionEventDispatcher::CompositionEventDispatcher(
912 TextComposition
* aTextComposition
, nsINode
* aEventTarget
,
913 EventMessage aEventMessage
, const nsAString
& aData
,
914 bool aIsSynthesizedEvent
)
915 : Runnable("TextComposition::CompositionEventDispatcher"),
916 mTextComposition(aTextComposition
),
917 mEventTarget(aEventTarget
),
919 mEventMessage(aEventMessage
),
920 mIsSynthesizedEvent(aIsSynthesizedEvent
) {}
923 TextComposition::CompositionEventDispatcher::Run() {
924 // The widget can be different from the widget which has dispatched
925 // composition events because GetWidget() returns a widget which is proper
926 // for calling NotifyIME(). However, this must no be problem since both
927 // widget should share native IME context. Therefore, even if an event
928 // handler uses the widget for requesting IME to commit or cancel, it works.
929 nsCOMPtr
<nsIWidget
> widget(mTextComposition
->GetWidget());
930 if (!mTextComposition
->IsValidStateForComposition(widget
)) {
931 return NS_OK
; // cannot dispatch any events anymore
934 RefPtr
<nsPresContext
> presContext
= mTextComposition
->mPresContext
;
935 nsCOMPtr
<nsINode
> eventTarget
= mEventTarget
;
936 RefPtr
<BrowserParent
> browserParent
= mTextComposition
->mBrowserParent
;
937 nsEventStatus status
= nsEventStatus_eIgnore
;
938 switch (mEventMessage
) {
939 case eCompositionStart
: {
940 WidgetCompositionEvent
compStart(true, eCompositionStart
, widget
);
941 compStart
.mNativeIMEContext
= mTextComposition
->mNativeContext
;
942 WidgetQueryContentEvent
querySelectedTextEvent(true, eQuerySelectedText
,
944 ContentEventHandler
handler(presContext
);
945 handler
.OnQuerySelectedText(&querySelectedTextEvent
);
946 NS_ASSERTION(querySelectedTextEvent
.Succeeded(),
947 "Failed to get selected text");
948 if (querySelectedTextEvent
.FoundSelection()) {
949 compStart
.mData
= querySelectedTextEvent
.mReply
->DataRef();
951 compStart
.mFlags
.mIsSynthesizedForTests
=
952 mTextComposition
->IsSynthesizedForTests();
953 IMEStateManager::DispatchCompositionEvent(
954 eventTarget
, presContext
, browserParent
, &compStart
, &status
, nullptr,
955 mIsSynthesizedEvent
);
958 case eCompositionChange
:
959 case eCompositionCommitAsIs
:
960 case eCompositionCommit
: {
961 WidgetCompositionEvent
compEvent(true, mEventMessage
, widget
);
962 compEvent
.mNativeIMEContext
= mTextComposition
->mNativeContext
;
963 if (mEventMessage
!= eCompositionCommitAsIs
) {
964 compEvent
.mData
= mData
;
966 compEvent
.mFlags
.mIsSynthesizedForTests
=
967 mTextComposition
->IsSynthesizedForTests();
968 IMEStateManager::DispatchCompositionEvent(
969 eventTarget
, presContext
, browserParent
, &compEvent
, &status
, nullptr,
970 mIsSynthesizedEvent
);
974 MOZ_CRASH("Unsupported event");
979 /******************************************************************************
980 * TextCompositionArray
981 ******************************************************************************/
983 TextCompositionArray::index_type
TextCompositionArray::IndexOf(
984 const NativeIMEContext
& aNativeIMEContext
) {
985 if (!aNativeIMEContext
.IsValid()) {
988 for (index_type i
= Length(); i
> 0; --i
) {
989 if (ElementAt(i
- 1)->GetNativeIMEContext() == aNativeIMEContext
) {
996 TextCompositionArray::index_type
TextCompositionArray::IndexOf(
997 nsIWidget
* aWidget
) {
998 return IndexOf(aWidget
->GetNativeIMEContext());
1001 TextCompositionArray::index_type
TextCompositionArray::IndexOf(
1002 nsPresContext
* aPresContext
) {
1003 for (index_type i
= Length(); i
> 0; --i
) {
1004 if (ElementAt(i
- 1)->GetPresContext() == aPresContext
) {
1011 TextCompositionArray::index_type
TextCompositionArray::IndexOf(
1012 nsPresContext
* aPresContext
, nsINode
* aNode
) {
1013 index_type index
= IndexOf(aPresContext
);
1014 if (index
== NoIndex
) {
1017 nsINode
* node
= ElementAt(index
)->GetEventTargetNode();
1018 return node
== aNode
? index
: NoIndex
;
1021 TextComposition
* TextCompositionArray::GetCompositionFor(nsIWidget
* aWidget
) {
1022 index_type i
= IndexOf(aWidget
);
1026 return ElementAt(i
);
1029 TextComposition
* TextCompositionArray::GetCompositionFor(
1030 const WidgetCompositionEvent
* aCompositionEvent
) {
1031 index_type i
= IndexOf(aCompositionEvent
->mNativeIMEContext
);
1035 return ElementAt(i
);
1038 TextComposition
* TextCompositionArray::GetCompositionFor(
1039 nsPresContext
* aPresContext
) {
1040 index_type i
= IndexOf(aPresContext
);
1044 return ElementAt(i
);
1047 TextComposition
* TextCompositionArray::GetCompositionFor(
1048 nsPresContext
* aPresContext
, nsINode
* aNode
) {
1049 index_type i
= IndexOf(aPresContext
, aNode
);
1053 return ElementAt(i
);
1056 TextComposition
* TextCompositionArray::GetCompositionInContent(
1057 nsPresContext
* aPresContext
, nsIContent
* aContent
) {
1058 // There should be only one composition per content object.
1059 for (index_type i
= Length(); i
> 0; --i
) {
1060 nsINode
* node
= ElementAt(i
- 1)->GetEventTargetNode();
1061 if (node
&& node
->IsInclusiveDescendantOf(aContent
)) {
1062 return ElementAt(i
- 1);
1068 } // namespace mozilla