Coverage Report

Created: 2018-09-25 14:53

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