Line data Source code
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/. */
6 :
7 : #include "ContentEventHandler.h"
8 : #include "IMEContentObserver.h"
9 : #include "IMEStateManager.h"
10 : #include "nsContentUtils.h"
11 : #include "nsIContent.h"
12 : #include "nsIPresShell.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/MiscEvents.h"
19 : #include "mozilla/Preferences.h"
20 : #include "mozilla/TextComposition.h"
21 : #include "mozilla/TextEvents.h"
22 : #include "mozilla/Unused.h"
23 : #include "mozilla/dom/TabParent.h"
24 :
25 : #ifdef XP_MACOSX
26 : // Some defiens will be conflict with OSX SDK
27 : #define TextRange _TextRange
28 : #define TextRangeArray _TextRangeArray
29 : #define Comment _Comment
30 : #endif
31 :
32 : #include "nsPluginInstanceOwner.h"
33 :
34 : #ifdef XP_MACOSX
35 : #undef TextRange
36 : #undef TextRangeArray
37 : #undef Comment
38 : #endif
39 :
40 : using namespace mozilla::widget;
41 :
42 : namespace mozilla {
43 :
44 : #define IDEOGRAPHIC_SPACE (NS_LITERAL_STRING(u"\x3000"))
45 :
46 : /******************************************************************************
47 : * TextComposition
48 : ******************************************************************************/
49 :
50 : bool TextComposition::sHandlingSelectionEvent = false;
51 :
52 0 : TextComposition::TextComposition(nsPresContext* aPresContext,
53 : nsINode* aNode,
54 : TabParent* aTabParent,
55 0 : WidgetCompositionEvent* aCompositionEvent)
56 : : mPresContext(aPresContext)
57 : , mNode(aNode)
58 : , mTabParent(aTabParent)
59 : , mNativeContext(aCompositionEvent->mNativeIMEContext)
60 : , mCompositionStartOffset(0)
61 : , mTargetClauseOffsetInComposition(0)
62 0 : , mIsSynthesizedForTests(aCompositionEvent->mFlags.mIsSynthesizedForTests)
63 : , mIsComposing(false)
64 : , mIsEditorHandlingEvent(false)
65 : , mIsRequestingCommit(false)
66 : , mIsRequestingCancel(false)
67 : , mRequestedToCommitOrCancel(false)
68 : , mWasNativeCompositionEndEventDiscarded(false)
69 : , mAllowControlCharacters(
70 0 : Preferences::GetBool("dom.compositionevent.allow_control_characters",
71 : false))
72 0 : , mWasCompositionStringEmpty(true)
73 : {
74 0 : MOZ_ASSERT(aCompositionEvent->mNativeIMEContext.IsValid());
75 0 : }
76 :
77 : void
78 0 : TextComposition::Destroy()
79 : {
80 0 : mPresContext = nullptr;
81 0 : mNode = nullptr;
82 0 : mTabParent = nullptr;
83 : // TODO: If the editor is still alive and this is held by it, we should tell
84 : // this being destroyed for cleaning up the stuff.
85 0 : }
86 :
87 : bool
88 0 : TextComposition::IsValidStateForComposition(nsIWidget* aWidget) const
89 : {
90 0 : return !Destroyed() && aWidget && !aWidget->Destroyed() &&
91 0 : mPresContext->GetPresShell() &&
92 0 : !mPresContext->GetPresShell()->IsDestroying();
93 : }
94 :
95 : bool
96 0 : TextComposition::MaybeDispatchCompositionUpdate(
97 : const WidgetCompositionEvent* aCompositionEvent)
98 : {
99 0 : MOZ_RELEASE_ASSERT(!mTabParent);
100 :
101 0 : if (!IsValidStateForComposition(aCompositionEvent->mWidget)) {
102 0 : return false;
103 : }
104 :
105 0 : if (mLastData == aCompositionEvent->mData) {
106 0 : return true;
107 : }
108 0 : CloneAndDispatchAs(aCompositionEvent, eCompositionUpdate);
109 0 : return IsValidStateForComposition(aCompositionEvent->mWidget);
110 : }
111 :
112 : BaseEventFlags
113 0 : TextComposition::CloneAndDispatchAs(
114 : const WidgetCompositionEvent* aCompositionEvent,
115 : EventMessage aMessage,
116 : nsEventStatus* aStatus,
117 : EventDispatchingCallback* aCallBack)
118 : {
119 0 : MOZ_RELEASE_ASSERT(!mTabParent);
120 :
121 0 : MOZ_ASSERT(IsValidStateForComposition(aCompositionEvent->mWidget),
122 : "Should be called only when it's safe to dispatch an event");
123 :
124 0 : WidgetCompositionEvent compositionEvent(aCompositionEvent->IsTrusted(),
125 0 : aMessage, aCompositionEvent->mWidget);
126 0 : compositionEvent.mTime = aCompositionEvent->mTime;
127 0 : compositionEvent.mTimeStamp = aCompositionEvent->mTimeStamp;
128 0 : compositionEvent.mData = aCompositionEvent->mData;
129 0 : compositionEvent.mNativeIMEContext = aCompositionEvent->mNativeIMEContext;
130 0 : compositionEvent.mOriginalMessage = aCompositionEvent->mMessage;
131 0 : compositionEvent.mFlags.mIsSynthesizedForTests =
132 0 : aCompositionEvent->mFlags.mIsSynthesizedForTests;
133 :
134 0 : nsEventStatus dummyStatus = nsEventStatus_eConsumeNoDefault;
135 0 : nsEventStatus* status = aStatus ? aStatus : &dummyStatus;
136 0 : if (aMessage == eCompositionUpdate) {
137 0 : mLastData = compositionEvent.mData;
138 0 : mLastRanges = aCompositionEvent->mRanges;
139 : }
140 :
141 0 : DispatchEvent(&compositionEvent, status, aCallBack, aCompositionEvent);
142 0 : return compositionEvent.mFlags;
143 : }
144 :
145 : void
146 0 : TextComposition::DispatchEvent(WidgetCompositionEvent* aDispatchEvent,
147 : nsEventStatus* aStatus,
148 : EventDispatchingCallback* aCallBack,
149 : const WidgetCompositionEvent *aOriginalEvent)
150 : {
151 : nsPluginInstanceOwner::GeneratePluginEvent(aOriginalEvent,
152 0 : aDispatchEvent);
153 :
154 0 : EventDispatcher::Dispatch(mNode, mPresContext,
155 0 : aDispatchEvent, nullptr, aStatus, aCallBack);
156 :
157 0 : OnCompositionEventDispatched(aDispatchEvent);
158 0 : }
159 :
160 : void
161 0 : TextComposition::OnCompositionEventDiscarded(
162 : WidgetCompositionEvent* aCompositionEvent)
163 : {
164 : // Note that this method is never called for synthesized events for emulating
165 : // commit or cancel composition.
166 :
167 0 : MOZ_ASSERT(aCompositionEvent->IsTrusted(),
168 : "Shouldn't be called with untrusted event");
169 :
170 0 : if (mTabParent) {
171 : // The composition event should be discarded in the child process too.
172 0 : Unused << mTabParent->SendCompositionEvent(*aCompositionEvent);
173 : }
174 :
175 : // XXX If composition events are discarded, should we dispatch them with
176 : // runnable event? However, even if we do so, it might make native IME
177 : // confused due to async modification. Especially when native IME is
178 : // TSF.
179 0 : if (!aCompositionEvent->CausesDOMCompositionEndEvent()) {
180 0 : return;
181 : }
182 :
183 0 : mWasNativeCompositionEndEventDiscarded = true;
184 : }
185 :
186 : static inline bool
187 0 : IsControlChar(uint32_t aCharCode)
188 : {
189 0 : return aCharCode < ' ' || aCharCode == 0x7F;
190 : }
191 :
192 : static size_t
193 0 : FindFirstControlCharacter(const nsAString& aStr)
194 : {
195 0 : const char16_t* sourceBegin = aStr.BeginReading();
196 0 : const char16_t* sourceEnd = aStr.EndReading();
197 :
198 0 : for (const char16_t* source = sourceBegin; source < sourceEnd; ++source) {
199 0 : if (*source != '\t' && IsControlChar(*source)) {
200 0 : return source - sourceBegin;
201 : }
202 : }
203 :
204 0 : return -1;
205 : }
206 :
207 : static void
208 0 : RemoveControlCharactersFrom(nsAString& aStr, TextRangeArray* aRanges)
209 : {
210 0 : size_t firstControlCharOffset = FindFirstControlCharacter(aStr);
211 0 : if (firstControlCharOffset == (size_t)-1) {
212 0 : return;
213 : }
214 :
215 0 : nsAutoString copy(aStr);
216 0 : const char16_t* sourceBegin = copy.BeginReading();
217 0 : const char16_t* sourceEnd = copy.EndReading();
218 :
219 0 : char16_t* dest = aStr.BeginWriting();
220 0 : if (NS_WARN_IF(!dest)) {
221 0 : return;
222 : }
223 :
224 0 : char16_t* curDest = dest + firstControlCharOffset;
225 0 : size_t i = firstControlCharOffset;
226 0 : for (const char16_t* source = sourceBegin + firstControlCharOffset;
227 0 : source < sourceEnd; ++source) {
228 0 : if (*source == '\t' || *source == '\n' || !IsControlChar(*source)) {
229 0 : *curDest = *source;
230 0 : ++curDest;
231 0 : ++i;
232 0 : } else if (aRanges) {
233 0 : aRanges->RemoveCharacter(i);
234 : }
235 : }
236 :
237 0 : aStr.SetLength(curDest - dest);
238 : }
239 :
240 : void
241 0 : TextComposition::DispatchCompositionEvent(
242 : WidgetCompositionEvent* aCompositionEvent,
243 : nsEventStatus* aStatus,
244 : EventDispatchingCallback* aCallBack,
245 : bool aIsSynthesized)
246 : {
247 0 : mWasCompositionStringEmpty = mString.IsEmpty();
248 :
249 : // If the content is a container of TabParent, composition should be in the
250 : // remote process.
251 0 : if (mTabParent) {
252 0 : Unused << mTabParent->SendCompositionEvent(*aCompositionEvent);
253 0 : aCompositionEvent->StopPropagation();
254 0 : if (aCompositionEvent->CausesDOMTextEvent()) {
255 0 : mLastData = aCompositionEvent->mData;
256 0 : mLastRanges = aCompositionEvent->mRanges;
257 : // Although, the composition event hasn't been actually handled yet,
258 : // emulate an editor to be handling the composition event.
259 0 : EditorWillHandleCompositionChangeEvent(aCompositionEvent);
260 0 : EditorDidHandleCompositionChangeEvent();
261 : }
262 0 : return;
263 : }
264 :
265 0 : if (!mAllowControlCharacters) {
266 0 : RemoveControlCharactersFrom(aCompositionEvent->mData,
267 0 : aCompositionEvent->mRanges);
268 : }
269 0 : if (aCompositionEvent->mMessage == eCompositionCommitAsIs) {
270 0 : NS_ASSERTION(!aCompositionEvent->mRanges,
271 : "mRanges of eCompositionCommitAsIs should be null");
272 0 : aCompositionEvent->mRanges = nullptr;
273 0 : NS_ASSERTION(aCompositionEvent->mData.IsEmpty(),
274 : "mData of eCompositionCommitAsIs should be empty string");
275 : bool removePlaceholderCharacter =
276 : Preferences::GetBool("intl.ime.remove_placeholder_character_at_commit",
277 0 : false);
278 0 : if (removePlaceholderCharacter && mLastData == IDEOGRAPHIC_SPACE) {
279 : // If the last data is an ideographic space (FullWidth space), it might be
280 : // a placeholder character of some Chinese IME. So, committing with
281 : // this data might not be expected by users. Let's use empty string.
282 0 : aCompositionEvent->mData.Truncate();
283 : } else {
284 0 : aCompositionEvent->mData = mLastData;
285 : }
286 0 : } else if (aCompositionEvent->mMessage == eCompositionCommit) {
287 0 : NS_ASSERTION(!aCompositionEvent->mRanges,
288 : "mRanges of eCompositionCommit should be null");
289 0 : aCompositionEvent->mRanges = nullptr;
290 : }
291 :
292 0 : if (!IsValidStateForComposition(aCompositionEvent->mWidget)) {
293 0 : *aStatus = nsEventStatus_eConsumeNoDefault;
294 0 : return;
295 : }
296 :
297 : // If this instance has requested to commit or cancel composition but
298 : // is not synthesizing commit event, that means that the IME commits or
299 : // cancels the composition asynchronously. Typically, iBus behaves so.
300 : // Then, synthesized events which were dispatched immediately after
301 : // the request has already committed our editor's composition string and
302 : // told it to web apps. Therefore, we should ignore the delayed events.
303 0 : if (mRequestedToCommitOrCancel && !aIsSynthesized) {
304 0 : *aStatus = nsEventStatus_eConsumeNoDefault;
305 0 : return;
306 : }
307 :
308 : // IME may commit composition with empty string for a commit request or
309 : // with non-empty string for a cancel request. We should prevent such
310 : // unexpected result. E.g., web apps may be confused if they implement
311 : // autocomplete which attempts to commit composition forcibly when the user
312 : // selects one of suggestions but composition string is cleared by IME.
313 : // Note that most Chinese IMEs don't expose actual composition string to us.
314 : // They typically tell us an IDEOGRAPHIC SPACE or empty string as composition
315 : // string. Therefore, we should hack it only when:
316 : // 1. committing string is empty string at requesting commit but the last
317 : // data isn't IDEOGRAPHIC SPACE.
318 : // 2. non-empty string is committed at requesting cancel.
319 0 : if (!aIsSynthesized && (mIsRequestingCommit || mIsRequestingCancel)) {
320 0 : nsString* committingData = nullptr;
321 0 : switch (aCompositionEvent->mMessage) {
322 : case eCompositionEnd:
323 : case eCompositionChange:
324 : case eCompositionCommitAsIs:
325 : case eCompositionCommit:
326 0 : committingData = &aCompositionEvent->mData;
327 0 : break;
328 : default:
329 : NS_WARNING("Unexpected event comes during committing or "
330 0 : "canceling composition");
331 0 : break;
332 : }
333 0 : if (committingData) {
334 0 : if (mIsRequestingCommit && committingData->IsEmpty() &&
335 0 : mLastData != IDEOGRAPHIC_SPACE) {
336 0 : committingData->Assign(mLastData);
337 0 : } else if (mIsRequestingCancel && !committingData->IsEmpty()) {
338 0 : committingData->Truncate();
339 : }
340 : }
341 : }
342 :
343 0 : bool dispatchEvent = true;
344 0 : bool dispatchDOMTextEvent = aCompositionEvent->CausesDOMTextEvent();
345 :
346 : // When mIsComposing is false but the committing string is different from
347 : // the last data (E.g., previous eCompositionChange event made the
348 : // composition string empty or didn't have clause information), we don't
349 : // need to dispatch redundant DOM text event.
350 0 : if (dispatchDOMTextEvent &&
351 0 : aCompositionEvent->mMessage != eCompositionChange &&
352 0 : !mIsComposing && mLastData == aCompositionEvent->mData) {
353 0 : dispatchEvent = dispatchDOMTextEvent = false;
354 : }
355 :
356 : // widget may dispatch redundant eCompositionChange event
357 : // which modifies neither composition string, clauses nor caret
358 : // position. In such case, we shouldn't dispatch DOM events.
359 0 : if (dispatchDOMTextEvent &&
360 0 : aCompositionEvent->mMessage == eCompositionChange &&
361 0 : mLastData == aCompositionEvent->mData &&
362 0 : mRanges && aCompositionEvent->mRanges &&
363 0 : mRanges->Equals(*aCompositionEvent->mRanges)) {
364 0 : dispatchEvent = dispatchDOMTextEvent = false;
365 : }
366 :
367 0 : if (dispatchDOMTextEvent) {
368 0 : if (!MaybeDispatchCompositionUpdate(aCompositionEvent)) {
369 0 : return;
370 : }
371 : }
372 :
373 0 : if (dispatchEvent) {
374 : // If the composition event should cause a DOM text event, we should
375 : // overwrite the event message as eCompositionChange because due to
376 : // the limitation of mapping between event messages and DOM event types,
377 : // we cannot map multiple event messages to a DOM event type.
378 0 : if (dispatchDOMTextEvent &&
379 0 : aCompositionEvent->mMessage != eCompositionChange) {
380 : aCompositionEvent->mFlags =
381 : CloneAndDispatchAs(aCompositionEvent, eCompositionChange,
382 0 : aStatus, aCallBack);
383 : } else {
384 0 : DispatchEvent(aCompositionEvent, aStatus, aCallBack);
385 : }
386 : } else {
387 0 : *aStatus = nsEventStatus_eConsumeNoDefault;
388 : }
389 :
390 0 : if (!IsValidStateForComposition(aCompositionEvent->mWidget)) {
391 0 : return;
392 : }
393 :
394 : // Emulate editor behavior of compositionchange event (DOM text event) handler
395 : // if no editor handles composition events.
396 0 : if (dispatchDOMTextEvent && !HasEditor()) {
397 0 : EditorWillHandleCompositionChangeEvent(aCompositionEvent);
398 0 : EditorDidHandleCompositionChangeEvent();
399 : }
400 :
401 0 : if (aCompositionEvent->CausesDOMCompositionEndEvent()) {
402 : // Dispatch a compositionend event if it's necessary.
403 0 : if (aCompositionEvent->mMessage != eCompositionEnd) {
404 0 : CloneAndDispatchAs(aCompositionEvent, eCompositionEnd);
405 : }
406 0 : MOZ_ASSERT(!mIsComposing, "Why is the editor still composing?");
407 0 : MOZ_ASSERT(!HasEditor(), "Why does the editor still keep to hold this?");
408 : }
409 :
410 0 : MaybeNotifyIMEOfCompositionEventHandled(aCompositionEvent);
411 : }
412 :
413 : // static
414 : void
415 0 : TextComposition::HandleSelectionEvent(nsPresContext* aPresContext,
416 : TabParent* aTabParent,
417 : WidgetSelectionEvent* aSelectionEvent)
418 : {
419 : // If the content is a container of TabParent, composition should be in the
420 : // remote process.
421 0 : if (aTabParent) {
422 0 : Unused << aTabParent->SendSelectionEvent(*aSelectionEvent);
423 0 : aSelectionEvent->StopPropagation();
424 0 : return;
425 : }
426 :
427 0 : ContentEventHandler handler(aPresContext);
428 0 : AutoRestore<bool> saveHandlingSelectionEvent(sHandlingSelectionEvent);
429 0 : sHandlingSelectionEvent = true;
430 : // XXX During setting selection, a selection listener may change selection
431 : // again. In such case, sHandlingSelectionEvent doesn't indicate if
432 : // the selection change is caused by a selection event. However, it
433 : // must be non-realistic scenario.
434 0 : handler.OnSelectionEvent(aSelectionEvent);
435 : }
436 :
437 : uint32_t
438 0 : TextComposition::GetSelectionStartOffset()
439 : {
440 0 : nsCOMPtr<nsIWidget> widget = mPresContext->GetRootWidget();
441 0 : WidgetQueryContentEvent selectedTextEvent(true, eQuerySelectedText, widget);
442 : // Due to a bug of widget, mRanges may not be nullptr even though composition
443 : // string is empty. So, we need to check it here for avoiding to return
444 : // odd start offset.
445 0 : if (!mLastData.IsEmpty() && mRanges && mRanges->HasClauses()) {
446 0 : selectedTextEvent.InitForQuerySelectedText(
447 0 : ToSelectionType(mRanges->GetFirstClause()->mRangeType));
448 : } else {
449 0 : NS_WARNING_ASSERTION(
450 : !mLastData.IsEmpty() || !mRanges || !mRanges->HasClauses(),
451 : "Shouldn't have empty clause info when composition string is empty");
452 0 : selectedTextEvent.InitForQuerySelectedText(SelectionType::eNormal);
453 : }
454 :
455 : // The editor which has this composition is observed by active
456 : // IMEContentObserver, we can use the cache of it.
457 : RefPtr<IMEContentObserver> contentObserver =
458 0 : IMEStateManager::GetActiveContentObserver();
459 0 : bool doQuerySelection = true;
460 0 : if (contentObserver) {
461 0 : if (contentObserver->IsManaging(this)) {
462 0 : doQuerySelection = false;
463 0 : contentObserver->HandleQueryContentEvent(&selectedTextEvent);
464 : }
465 : // If another editor already has focus, we cannot retrieve selection
466 : // in the editor which has this composition...
467 0 : else if (NS_WARN_IF(contentObserver->GetPresContext() == mPresContext)) {
468 0 : return 0; // XXX Is this okay?
469 : }
470 : }
471 :
472 : // Otherwise, using slow path (i.e., compute every time with
473 : // ContentEventHandler)
474 0 : if (doQuerySelection) {
475 0 : ContentEventHandler handler(mPresContext);
476 0 : handler.HandleQueryContentEvent(&selectedTextEvent);
477 : }
478 :
479 0 : if (NS_WARN_IF(!selectedTextEvent.mSucceeded)) {
480 0 : return 0; // XXX Is this okay?
481 : }
482 0 : return selectedTextEvent.mReply.mOffset;
483 : }
484 :
485 : void
486 0 : TextComposition::OnCompositionEventDispatched(
487 : const WidgetCompositionEvent* aCompositionEvent)
488 : {
489 0 : MOZ_RELEASE_ASSERT(!mTabParent);
490 :
491 0 : if (!IsValidStateForComposition(aCompositionEvent->mWidget)) {
492 0 : return;
493 : }
494 :
495 : // Every composition event may cause changing composition start offset,
496 : // especially when there is no composition string. Therefore, we need to
497 : // update mCompositionStartOffset with the latest offset.
498 :
499 0 : MOZ_ASSERT(aCompositionEvent->mMessage != eCompositionStart ||
500 : mWasCompositionStringEmpty,
501 : "mWasCompositionStringEmpty should be true if the dispatched "
502 : "event is eCompositionStart");
503 :
504 0 : if (mWasCompositionStringEmpty &&
505 0 : !aCompositionEvent->CausesDOMCompositionEndEvent()) {
506 : // If there was no composition string, current selection start may be the
507 : // offset for inserting composition string.
508 : // Update composition start offset with current selection start.
509 0 : mCompositionStartOffset = GetSelectionStartOffset();
510 0 : mTargetClauseOffsetInComposition = 0;
511 : }
512 :
513 0 : if (aCompositionEvent->CausesDOMTextEvent()) {
514 0 : mTargetClauseOffsetInComposition = aCompositionEvent->TargetClauseOffset();
515 : }
516 : }
517 :
518 : void
519 0 : TextComposition::OnStartOffsetUpdatedInChild(uint32_t aStartOffset)
520 : {
521 0 : mCompositionStartOffset = aStartOffset;
522 0 : }
523 :
524 : void
525 0 : TextComposition::MaybeNotifyIMEOfCompositionEventHandled(
526 : const WidgetCompositionEvent* aCompositionEvent)
527 : {
528 0 : if (aCompositionEvent->mMessage != eCompositionStart &&
529 0 : !aCompositionEvent->CausesDOMTextEvent()) {
530 0 : return;
531 : }
532 :
533 : RefPtr<IMEContentObserver> contentObserver =
534 0 : IMEStateManager::GetActiveContentObserver();
535 : // When IMEContentObserver is managing the editor which has this composition,
536 : // composition event handled notification should be sent after the observer
537 : // notifies all pending notifications. Therefore, we should use it.
538 : // XXX If IMEContentObserver suddenly loses focus after here and notifying
539 : // widget of pending notifications, we won't notify widget of composition
540 : // event handled. Although, this is a bug but it should be okay since
541 : // destroying IMEContentObserver notifies IME of blur. So, native IME
542 : // handler can treat it as this notification too.
543 0 : if (contentObserver && contentObserver->IsManaging(this)) {
544 0 : contentObserver->MaybeNotifyCompositionEventHandled();
545 0 : return;
546 : }
547 : // Otherwise, e.g., this composition is in non-active window, we should
548 : // notify widget directly.
549 0 : NotifyIME(NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED);
550 : }
551 :
552 : void
553 0 : TextComposition::DispatchCompositionEventRunnable(EventMessage aEventMessage,
554 : const nsAString& aData,
555 : bool aIsSynthesizingCommit)
556 : {
557 : nsContentUtils::AddScriptRunner(
558 : new CompositionEventDispatcher(this, mNode, aEventMessage, aData,
559 0 : aIsSynthesizingCommit));
560 0 : }
561 :
562 : nsresult
563 0 : TextComposition::RequestToCommit(nsIWidget* aWidget, bool aDiscard)
564 : {
565 : // If this composition is already requested to be committed or canceled,
566 : // we don't need to request it again because even if the first request
567 : // failed, new request won't success, probably. And we shouldn't synthesize
568 : // events for committing or canceling composition twice or more times.
569 0 : if (mRequestedToCommitOrCancel) {
570 0 : return NS_OK;
571 : }
572 :
573 0 : RefPtr<TextComposition> kungFuDeathGrip(this);
574 0 : const nsAutoString lastData(mLastData);
575 :
576 : {
577 0 : AutoRestore<bool> saveRequestingCancel(mIsRequestingCancel);
578 0 : AutoRestore<bool> saveRequestingCommit(mIsRequestingCommit);
579 0 : if (aDiscard) {
580 0 : mIsRequestingCancel = true;
581 0 : mIsRequestingCommit = false;
582 : } else {
583 0 : mIsRequestingCancel = false;
584 0 : mIsRequestingCommit = true;
585 : }
586 : // FYI: CompositionEvents caused by a call of NotifyIME() may be
587 : // discarded by PresShell if it's not safe to dispatch the event.
588 : nsresult rv =
589 0 : aWidget->NotifyIME(IMENotification(aDiscard ?
590 : REQUEST_TO_CANCEL_COMPOSITION :
591 0 : REQUEST_TO_COMMIT_COMPOSITION));
592 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
593 0 : return rv;
594 : }
595 : }
596 :
597 0 : mRequestedToCommitOrCancel = true;
598 :
599 : // If the request is performed synchronously, this must be already destroyed.
600 0 : if (Destroyed()) {
601 0 : return NS_OK;
602 : }
603 :
604 : // Otherwise, synthesize the commit in content.
605 0 : nsAutoString data(aDiscard ? EmptyString() : lastData);
606 0 : if (data == mLastData) {
607 0 : DispatchCompositionEventRunnable(eCompositionCommitAsIs, EmptyString(),
608 0 : true);
609 : } else {
610 0 : DispatchCompositionEventRunnable(eCompositionCommit, data, true);
611 : }
612 0 : return NS_OK;
613 : }
614 :
615 : nsresult
616 0 : TextComposition::NotifyIME(IMEMessage aMessage)
617 : {
618 0 : NS_ENSURE_TRUE(mPresContext, NS_ERROR_NOT_AVAILABLE);
619 0 : return IMEStateManager::NotifyIME(aMessage, mPresContext, mTabParent);
620 : }
621 :
622 : void
623 0 : TextComposition::EditorWillHandleCompositionChangeEvent(
624 : const WidgetCompositionEvent* aCompositionChangeEvent)
625 : {
626 0 : mIsComposing = aCompositionChangeEvent->IsComposing();
627 0 : mRanges = aCompositionChangeEvent->mRanges;
628 0 : mIsEditorHandlingEvent = true;
629 :
630 0 : MOZ_ASSERT(mLastData == aCompositionChangeEvent->mData,
631 : "The text of a compositionchange event must be same as previous data "
632 : "attribute value of the latest compositionupdate event");
633 0 : }
634 :
635 : void
636 0 : TextComposition::OnEditorDestroyed()
637 : {
638 0 : MOZ_RELEASE_ASSERT(!mTabParent);
639 :
640 0 : MOZ_ASSERT(!mIsEditorHandlingEvent,
641 : "The editor should have stopped listening events");
642 0 : nsCOMPtr<nsIWidget> widget = GetWidget();
643 0 : if (NS_WARN_IF(!widget)) {
644 : // XXX If this could happen, how do we notify IME of destroying the editor?
645 0 : return;
646 : }
647 :
648 : // Try to cancel the composition.
649 0 : RequestToCommit(widget, true);
650 : }
651 :
652 : void
653 0 : TextComposition::EditorDidHandleCompositionChangeEvent()
654 : {
655 0 : mString = mLastData;
656 0 : mIsEditorHandlingEvent = false;
657 0 : }
658 :
659 : void
660 0 : TextComposition::StartHandlingComposition(EditorBase* aEditorBase)
661 : {
662 0 : MOZ_RELEASE_ASSERT(!mTabParent);
663 :
664 0 : MOZ_ASSERT(!HasEditor(), "There is a handling editor already");
665 0 : mEditorBaseWeak = do_GetWeakReference(static_cast<nsIEditor*>(aEditorBase));
666 0 : }
667 :
668 : void
669 0 : TextComposition::EndHandlingComposition(EditorBase* aEditorBase)
670 : {
671 0 : MOZ_RELEASE_ASSERT(!mTabParent);
672 :
673 : #ifdef DEBUG
674 0 : RefPtr<EditorBase> editorBase = GetEditorBase();
675 0 : MOZ_ASSERT(editorBase == aEditorBase,
676 : "Another editor handled the composition?");
677 : #endif // #ifdef DEBUG
678 0 : mEditorBaseWeak = nullptr;
679 0 : }
680 :
681 : already_AddRefed<EditorBase>
682 0 : TextComposition::GetEditorBase() const
683 : {
684 0 : nsCOMPtr<nsIEditor> editor = do_QueryReferent(mEditorBaseWeak);
685 0 : RefPtr<EditorBase> editorBase = static_cast<EditorBase*>(editor.get());
686 0 : return editorBase.forget();
687 : }
688 :
689 : bool
690 0 : TextComposition::HasEditor() const
691 : {
692 0 : return mEditorBaseWeak && mEditorBaseWeak->IsAlive();
693 : }
694 :
695 : /******************************************************************************
696 : * TextComposition::CompositionEventDispatcher
697 : ******************************************************************************/
698 :
699 0 : TextComposition::CompositionEventDispatcher::CompositionEventDispatcher(
700 : TextComposition* aComposition,
701 : nsINode* aEventTarget,
702 : EventMessage aEventMessage,
703 : const nsAString& aData,
704 0 : bool aIsSynthesizedEvent)
705 : : Runnable("TextComposition::CompositionEventDispatcher")
706 : , mTextComposition(aComposition)
707 : , mEventTarget(aEventTarget)
708 : , mData(aData)
709 : , mEventMessage(aEventMessage)
710 0 : , mIsSynthesizedEvent(aIsSynthesizedEvent)
711 : {
712 0 : }
713 :
714 : NS_IMETHODIMP
715 0 : TextComposition::CompositionEventDispatcher::Run()
716 : {
717 : // The widget can be different from the widget which has dispatched
718 : // composition events because GetWidget() returns a widget which is proper
719 : // for calling NotifyIME(). However, this must no be problem since both
720 : // widget should share native IME context. Therefore, even if an event
721 : // handler uses the widget for requesting IME to commit or cancel, it works.
722 0 : nsCOMPtr<nsIWidget> widget(mTextComposition->GetWidget());
723 0 : if (!mTextComposition->IsValidStateForComposition(widget)) {
724 0 : return NS_OK; // cannot dispatch any events anymore
725 : }
726 :
727 0 : RefPtr<nsPresContext> presContext = mTextComposition->mPresContext;
728 0 : nsEventStatus status = nsEventStatus_eIgnore;
729 0 : switch (mEventMessage) {
730 : case eCompositionStart: {
731 0 : WidgetCompositionEvent compStart(true, eCompositionStart, widget);
732 0 : compStart.mNativeIMEContext = mTextComposition->mNativeContext;
733 0 : WidgetQueryContentEvent selectedText(true, eQuerySelectedText, widget);
734 0 : ContentEventHandler handler(presContext);
735 0 : handler.OnQuerySelectedText(&selectedText);
736 0 : NS_ASSERTION(selectedText.mSucceeded, "Failed to get selected text");
737 0 : compStart.mData = selectedText.mReply.mString;
738 0 : compStart.mFlags.mIsSynthesizedForTests =
739 0 : mTextComposition->IsSynthesizedForTests();
740 0 : IMEStateManager::DispatchCompositionEvent(mEventTarget, presContext,
741 : &compStart, &status, nullptr,
742 0 : mIsSynthesizedEvent);
743 0 : break;
744 : }
745 : case eCompositionChange:
746 : case eCompositionCommitAsIs:
747 : case eCompositionCommit: {
748 0 : WidgetCompositionEvent compEvent(true, mEventMessage, widget);
749 0 : compEvent.mNativeIMEContext = mTextComposition->mNativeContext;
750 0 : if (mEventMessage != eCompositionCommitAsIs) {
751 0 : compEvent.mData = mData;
752 : }
753 0 : compEvent.mFlags.mIsSynthesizedForTests =
754 0 : mTextComposition->IsSynthesizedForTests();
755 0 : IMEStateManager::DispatchCompositionEvent(mEventTarget, presContext,
756 : &compEvent, &status, nullptr,
757 0 : mIsSynthesizedEvent);
758 0 : break;
759 : }
760 : default:
761 0 : MOZ_CRASH("Unsupported event");
762 : }
763 0 : return NS_OK;
764 : }
765 :
766 : /******************************************************************************
767 : * TextCompositionArray
768 : ******************************************************************************/
769 :
770 : TextCompositionArray::index_type
771 0 : TextCompositionArray::IndexOf(const NativeIMEContext& aNativeIMEContext)
772 : {
773 0 : if (!aNativeIMEContext.IsValid()) {
774 0 : return NoIndex;
775 : }
776 0 : for (index_type i = Length(); i > 0; --i) {
777 0 : if (ElementAt(i - 1)->GetNativeIMEContext() == aNativeIMEContext) {
778 0 : return i - 1;
779 : }
780 : }
781 0 : return NoIndex;
782 : }
783 :
784 : TextCompositionArray::index_type
785 0 : TextCompositionArray::IndexOf(nsIWidget* aWidget)
786 : {
787 0 : return IndexOf(aWidget->GetNativeIMEContext());
788 : }
789 :
790 : TextCompositionArray::index_type
791 0 : TextCompositionArray::IndexOf(nsPresContext* aPresContext)
792 : {
793 0 : for (index_type i = Length(); i > 0; --i) {
794 0 : if (ElementAt(i - 1)->GetPresContext() == aPresContext) {
795 0 : return i - 1;
796 : }
797 : }
798 0 : return NoIndex;
799 : }
800 :
801 : TextCompositionArray::index_type
802 0 : TextCompositionArray::IndexOf(nsPresContext* aPresContext,
803 : nsINode* aNode)
804 : {
805 0 : index_type index = IndexOf(aPresContext);
806 0 : if (index == NoIndex) {
807 0 : return NoIndex;
808 : }
809 0 : nsINode* node = ElementAt(index)->GetEventTargetNode();
810 0 : return node == aNode ? index : NoIndex;
811 : }
812 :
813 : TextComposition*
814 0 : TextCompositionArray::GetCompositionFor(nsIWidget* aWidget)
815 : {
816 0 : index_type i = IndexOf(aWidget);
817 0 : if (i == NoIndex) {
818 0 : return nullptr;
819 : }
820 0 : return ElementAt(i);
821 : }
822 :
823 : TextComposition*
824 0 : TextCompositionArray::GetCompositionFor(
825 : const WidgetCompositionEvent* aCompositionEvent)
826 : {
827 0 : index_type i = IndexOf(aCompositionEvent->mNativeIMEContext);
828 0 : if (i == NoIndex) {
829 0 : return nullptr;
830 : }
831 0 : return ElementAt(i);
832 : }
833 :
834 : TextComposition*
835 0 : TextCompositionArray::GetCompositionFor(nsPresContext* aPresContext)
836 : {
837 0 : index_type i = IndexOf(aPresContext);
838 0 : if (i == NoIndex) {
839 0 : return nullptr;
840 : }
841 0 : return ElementAt(i);
842 : }
843 :
844 : TextComposition*
845 0 : TextCompositionArray::GetCompositionFor(nsPresContext* aPresContext,
846 : nsINode* aNode)
847 : {
848 0 : index_type i = IndexOf(aPresContext, aNode);
849 0 : if (i == NoIndex) {
850 0 : return nullptr;
851 : }
852 0 : return ElementAt(i);
853 : }
854 :
855 : TextComposition*
856 0 : TextCompositionArray::GetCompositionInContent(nsPresContext* aPresContext,
857 : nsIContent* aContent)
858 : {
859 : // There should be only one composition per content object.
860 0 : for (index_type i = Length(); i > 0; --i) {
861 0 : nsINode* node = ElementAt(i - 1)->GetEventTargetNode();
862 0 : if (node && nsContentUtils::ContentIsDescendantOf(node, aContent)) {
863 0 : return ElementAt(i - 1);
864 : }
865 : }
866 0 : return nullptr;
867 : }
868 :
869 : } // namespace mozilla
|