/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 |