/src/mozilla-central/widget/TextEventDispatcher.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
3 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
4 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
5 | | |
6 | | #include "mozilla/Preferences.h" |
7 | | #include "mozilla/TextEvents.h" |
8 | | #include "mozilla/TextEventDispatcher.h" |
9 | | #include "nsIDocShell.h" |
10 | | #include "nsIFrame.h" |
11 | | #include "nsIPresShell.h" |
12 | | #include "nsIWidget.h" |
13 | | #include "nsPIDOMWindow.h" |
14 | | #include "nsView.h" |
15 | | #include "PuppetWidget.h" |
16 | | |
17 | | namespace mozilla { |
18 | | namespace widget { |
19 | | |
20 | | /****************************************************************************** |
21 | | * TextEventDispatcher |
22 | | *****************************************************************************/ |
23 | | |
24 | | bool TextEventDispatcher::sDispatchKeyEventsDuringComposition = false; |
25 | | bool TextEventDispatcher::sDispatchKeyPressEventsOnlySystemGroupInContent = |
26 | | false; |
27 | | |
28 | | TextEventDispatcher::TextEventDispatcher(nsIWidget* aWidget) |
29 | | : mWidget(aWidget) |
30 | | , mDispatchingEvent(0) |
31 | | , mInputTransactionType(eNoInputTransaction) |
32 | | , mIsComposing(false) |
33 | | , mIsHandlingComposition(false) |
34 | | , mHasFocus(false) |
35 | 0 | { |
36 | 0 | MOZ_RELEASE_ASSERT(mWidget, "aWidget must not be nullptr"); |
37 | 0 |
|
38 | 0 | static bool sInitialized = false; |
39 | 0 | if (!sInitialized) { |
40 | 0 | Preferences::AddBoolVarCache( |
41 | 0 | &sDispatchKeyEventsDuringComposition, |
42 | 0 | "dom.keyboardevent.dispatch_during_composition", |
43 | 0 | true); |
44 | 0 | Preferences::AddBoolVarCache( |
45 | 0 | &sDispatchKeyPressEventsOnlySystemGroupInContent, |
46 | 0 | "dom.keyboardevent.keypress." |
47 | 0 | "dispatch_non_printable_keys_only_system_group_in_content", |
48 | 0 | true); |
49 | 0 | sInitialized = true; |
50 | 0 | } |
51 | 0 |
|
52 | 0 | ClearNotificationRequests(); |
53 | 0 | } |
54 | | |
55 | | nsresult |
56 | | TextEventDispatcher::BeginInputTransaction( |
57 | | TextEventDispatcherListener* aListener) |
58 | 0 | { |
59 | 0 | return BeginInputTransactionInternal(aListener, |
60 | 0 | eSameProcessSyncInputTransaction); |
61 | 0 | } |
62 | | |
63 | | nsresult |
64 | | TextEventDispatcher::BeginTestInputTransaction( |
65 | | TextEventDispatcherListener* aListener, |
66 | | bool aIsAPZAware) |
67 | 0 | { |
68 | 0 | return BeginInputTransactionInternal(aListener, |
69 | 0 | aIsAPZAware ? eAsyncTestInputTransaction : |
70 | 0 | eSameProcessSyncTestInputTransaction); |
71 | 0 | } |
72 | | |
73 | | nsresult |
74 | | TextEventDispatcher::BeginNativeInputTransaction() |
75 | 0 | { |
76 | 0 | if (NS_WARN_IF(!mWidget)) { |
77 | 0 | return NS_ERROR_FAILURE; |
78 | 0 | } |
79 | 0 | RefPtr<TextEventDispatcherListener> listener = |
80 | 0 | mWidget->GetNativeTextEventDispatcherListener(); |
81 | 0 | if (NS_WARN_IF(!listener)) { |
82 | 0 | return NS_ERROR_FAILURE; |
83 | 0 | } |
84 | 0 | return BeginInputTransactionInternal(listener, eNativeInputTransaction); |
85 | 0 | } |
86 | | |
87 | | nsresult |
88 | | TextEventDispatcher::BeginInputTransactionInternal( |
89 | | TextEventDispatcherListener* aListener, |
90 | | InputTransactionType aType) |
91 | 0 | { |
92 | 0 | if (NS_WARN_IF(!aListener)) { |
93 | 0 | return NS_ERROR_INVALID_ARG; |
94 | 0 | } |
95 | 0 | nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener); |
96 | 0 | if (listener) { |
97 | 0 | if (listener == aListener && mInputTransactionType == aType) { |
98 | 0 | UpdateNotificationRequests(); |
99 | 0 | return NS_OK; |
100 | 0 | } |
101 | 0 | // If this has composition or is dispatching an event, any other listener |
102 | 0 | // can steal ownership. Especially, if the latter case is allowed, |
103 | 0 | // nobody cannot begin input transaction with this if a modal dialog is |
104 | 0 | // opened during dispatching an event. |
105 | 0 | if (IsComposing() || IsDispatchingEvent()) { |
106 | 0 | return NS_ERROR_ALREADY_INITIALIZED; |
107 | 0 | } |
108 | 0 | } |
109 | 0 | mListener = do_GetWeakReference(aListener); |
110 | 0 | mInputTransactionType = aType; |
111 | 0 | if (listener && listener != aListener) { |
112 | 0 | listener->OnRemovedFrom(this); |
113 | 0 | } |
114 | 0 | UpdateNotificationRequests(); |
115 | 0 | return NS_OK; |
116 | 0 | } |
117 | | |
118 | | nsresult |
119 | | TextEventDispatcher::BeginInputTransactionFor(const WidgetGUIEvent* aEvent, |
120 | | PuppetWidget* aPuppetWidget) |
121 | 0 | { |
122 | 0 | MOZ_ASSERT(XRE_IsContentProcess()); |
123 | 0 | MOZ_ASSERT(!IsDispatchingEvent()); |
124 | 0 |
|
125 | 0 | switch (aEvent->mMessage) { |
126 | 0 | case eKeyDown: |
127 | 0 | case eKeyPress: |
128 | 0 | case eKeyUp: |
129 | 0 | MOZ_ASSERT(aEvent->mClass == eKeyboardEventClass); |
130 | 0 | break; |
131 | 0 | case eCompositionStart: |
132 | 0 | case eCompositionChange: |
133 | 0 | case eCompositionCommit: |
134 | 0 | case eCompositionCommitAsIs: |
135 | 0 | MOZ_ASSERT(aEvent->mClass == eCompositionEventClass); |
136 | 0 | break; |
137 | 0 | default: |
138 | 0 | return NS_ERROR_INVALID_ARG; |
139 | 0 | } |
140 | 0 | |
141 | 0 | if (aEvent->mFlags.mIsSynthesizedForTests) { |
142 | 0 | // If the event is for an automated test and this instance dispatched |
143 | 0 | // an event to the parent process, we can assume that this is already |
144 | 0 | // initialized properly. |
145 | 0 | if (mInputTransactionType == eAsyncTestInputTransaction) { |
146 | 0 | return NS_OK; |
147 | 0 | } |
148 | 0 | // Even if the event coming from the parent process is synthesized for |
149 | 0 | // tests, this process should treat it as "sync" test here because |
150 | 0 | // it won't be go back to the parent process. |
151 | 0 | nsresult rv = |
152 | 0 | BeginInputTransactionInternal( |
153 | 0 | static_cast<TextEventDispatcherListener*>(aPuppetWidget), |
154 | 0 | eSameProcessSyncTestInputTransaction); |
155 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
156 | 0 | return rv; |
157 | 0 | } |
158 | 0 | } else { |
159 | 0 | nsresult rv = BeginNativeInputTransaction(); |
160 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
161 | 0 | return rv; |
162 | 0 | } |
163 | 0 | } |
164 | 0 | |
165 | 0 | // Emulate modifying members which indicate the state of composition. |
166 | 0 | // If we need to manage more states and/or more complexly, we should create |
167 | 0 | // internal methods which are called by both here and each event dispatcher |
168 | 0 | // method of this class. |
169 | 0 | switch (aEvent->mMessage) { |
170 | 0 | case eKeyDown: |
171 | 0 | case eKeyPress: |
172 | 0 | case eKeyUp: |
173 | 0 | return NS_OK; |
174 | 0 | case eCompositionStart: |
175 | 0 | MOZ_ASSERT(!mIsComposing); |
176 | 0 | mIsComposing = mIsHandlingComposition = true; |
177 | 0 | return NS_OK; |
178 | 0 | case eCompositionChange: |
179 | 0 | MOZ_ASSERT(mIsComposing); |
180 | 0 | MOZ_ASSERT(mIsHandlingComposition); |
181 | 0 | mIsComposing = mIsHandlingComposition = true; |
182 | 0 | return NS_OK; |
183 | 0 | case eCompositionCommit: |
184 | 0 | case eCompositionCommitAsIs: |
185 | 0 | MOZ_ASSERT(mIsComposing); |
186 | 0 | MOZ_ASSERT(mIsHandlingComposition); |
187 | 0 | mIsComposing = false; |
188 | 0 | mIsHandlingComposition = true; |
189 | 0 | return NS_OK; |
190 | 0 | default: |
191 | 0 | MOZ_ASSERT_UNREACHABLE("You forgot to handle the event"); |
192 | 0 | return NS_ERROR_UNEXPECTED; |
193 | 0 | } |
194 | 0 | } |
195 | | void |
196 | | TextEventDispatcher::EndInputTransaction(TextEventDispatcherListener* aListener) |
197 | 0 | { |
198 | 0 | if (NS_WARN_IF(IsComposing()) || NS_WARN_IF(IsDispatchingEvent())) { |
199 | 0 | return; |
200 | 0 | } |
201 | 0 | |
202 | 0 | mInputTransactionType = eNoInputTransaction; |
203 | 0 |
|
204 | 0 | nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener); |
205 | 0 | if (NS_WARN_IF(!listener)) { |
206 | 0 | return; |
207 | 0 | } |
208 | 0 | |
209 | 0 | if (NS_WARN_IF(listener != aListener)) { |
210 | 0 | return; |
211 | 0 | } |
212 | 0 | |
213 | 0 | mListener = nullptr; |
214 | 0 | listener->OnRemovedFrom(this); |
215 | 0 | UpdateNotificationRequests(); |
216 | 0 | } |
217 | | |
218 | | void |
219 | | TextEventDispatcher::OnDestroyWidget() |
220 | 0 | { |
221 | 0 | mWidget = nullptr; |
222 | 0 | mHasFocus = false; |
223 | 0 | ClearNotificationRequests(); |
224 | 0 | mPendingComposition.Clear(); |
225 | 0 | nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener); |
226 | 0 | mListener = nullptr; |
227 | 0 | mInputTransactionType = eNoInputTransaction; |
228 | 0 | if (listener) { |
229 | 0 | listener->OnRemovedFrom(this); |
230 | 0 | } |
231 | 0 | } |
232 | | |
233 | | nsresult |
234 | | TextEventDispatcher::GetState() const |
235 | 0 | { |
236 | 0 | nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener); |
237 | 0 | if (!listener) { |
238 | 0 | return NS_ERROR_NOT_INITIALIZED; |
239 | 0 | } |
240 | 0 | if (!mWidget || mWidget->Destroyed()) { |
241 | 0 | return NS_ERROR_NOT_AVAILABLE; |
242 | 0 | } |
243 | 0 | return NS_OK; |
244 | 0 | } |
245 | | |
246 | | void |
247 | | TextEventDispatcher::InitEvent(WidgetGUIEvent& aEvent) const |
248 | 0 | { |
249 | 0 | aEvent.mTime = PR_IntervalNow(); |
250 | 0 | aEvent.mRefPoint = LayoutDeviceIntPoint(0, 0); |
251 | 0 | aEvent.mFlags.mIsSynthesizedForTests = IsForTests(); |
252 | 0 | if (aEvent.mClass != eCompositionEventClass) { |
253 | 0 | return; |
254 | 0 | } |
255 | 0 | void* pseudoIMEContext = GetPseudoIMEContext(); |
256 | 0 | if (pseudoIMEContext) { |
257 | 0 | aEvent.AsCompositionEvent()->mNativeIMEContext. |
258 | 0 | InitWithRawNativeIMEContext(pseudoIMEContext); |
259 | 0 | } |
260 | | #ifdef DEBUG |
261 | | else { |
262 | | MOZ_ASSERT(!XRE_IsContentProcess(), |
263 | | "Why did the content process start native event transaction?"); |
264 | | MOZ_ASSERT(aEvent.AsCompositionEvent()->mNativeIMEContext.IsValid(), |
265 | | "Native IME context shouldn't be invalid"); |
266 | | } |
267 | | #endif // #ifdef DEBUG |
268 | | } |
269 | | |
270 | | nsresult |
271 | | TextEventDispatcher::DispatchEvent(nsIWidget* aWidget, |
272 | | WidgetGUIEvent& aEvent, |
273 | | nsEventStatus& aStatus) |
274 | 0 | { |
275 | 0 | MOZ_ASSERT(!aEvent.AsInputEvent(), "Use DispatchInputEvent()"); |
276 | 0 |
|
277 | 0 | RefPtr<TextEventDispatcher> kungFuDeathGrip(this); |
278 | 0 | nsCOMPtr<nsIWidget> widget(aWidget); |
279 | 0 | mDispatchingEvent++; |
280 | 0 | nsresult rv = widget->DispatchEvent(&aEvent, aStatus); |
281 | 0 | mDispatchingEvent--; |
282 | 0 | return rv; |
283 | 0 | } |
284 | | |
285 | | nsresult |
286 | | TextEventDispatcher::DispatchInputEvent(nsIWidget* aWidget, |
287 | | WidgetInputEvent& aEvent, |
288 | | nsEventStatus& aStatus) |
289 | 0 | { |
290 | 0 | RefPtr<TextEventDispatcher> kungFuDeathGrip(this); |
291 | 0 | nsCOMPtr<nsIWidget> widget(aWidget); |
292 | 0 | mDispatchingEvent++; |
293 | 0 |
|
294 | 0 | // If the event is dispatched via nsIWidget::DispatchInputEvent(), it |
295 | 0 | // sends the event to the parent process first since APZ needs to handle it |
296 | 0 | // first. However, some callers (e.g., keyboard apps on B2G and tests |
297 | 0 | // expecting synchronous dispatch) don't want this to do that. |
298 | 0 | nsresult rv = NS_OK; |
299 | 0 | if (ShouldSendInputEventToAPZ()) { |
300 | 0 | aStatus = widget->DispatchInputEvent(&aEvent); |
301 | 0 | } else { |
302 | 0 | rv = widget->DispatchEvent(&aEvent, aStatus); |
303 | 0 | } |
304 | 0 |
|
305 | 0 | mDispatchingEvent--; |
306 | 0 | return rv; |
307 | 0 | } |
308 | | |
309 | | nsresult |
310 | | TextEventDispatcher::StartComposition(nsEventStatus& aStatus, |
311 | | const WidgetEventTime* aEventTime) |
312 | 0 | { |
313 | 0 | aStatus = nsEventStatus_eIgnore; |
314 | 0 |
|
315 | 0 | nsresult rv = GetState(); |
316 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
317 | 0 | return rv; |
318 | 0 | } |
319 | 0 | |
320 | 0 | if (NS_WARN_IF(mIsComposing)) { |
321 | 0 | return NS_ERROR_FAILURE; |
322 | 0 | } |
323 | 0 | |
324 | 0 | // When you change some members from here, you may need same change in |
325 | 0 | // BeginInputTransactionFor(). |
326 | 0 | mIsComposing = mIsHandlingComposition = true; |
327 | 0 | WidgetCompositionEvent compositionStartEvent(true, eCompositionStart, |
328 | 0 | mWidget); |
329 | 0 | InitEvent(compositionStartEvent); |
330 | 0 | if (aEventTime) { |
331 | 0 | compositionStartEvent.AssignEventTime(*aEventTime); |
332 | 0 | } |
333 | 0 | rv = DispatchEvent(mWidget, compositionStartEvent, aStatus); |
334 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
335 | 0 | return rv; |
336 | 0 | } |
337 | 0 | |
338 | 0 | return NS_OK; |
339 | 0 | } |
340 | | |
341 | | nsresult |
342 | | TextEventDispatcher::StartCompositionAutomaticallyIfNecessary( |
343 | | nsEventStatus& aStatus, |
344 | | const WidgetEventTime* aEventTime) |
345 | 0 | { |
346 | 0 | if (IsComposing()) { |
347 | 0 | return NS_OK; |
348 | 0 | } |
349 | 0 | |
350 | 0 | nsresult rv = StartComposition(aStatus, aEventTime); |
351 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
352 | 0 | return rv; |
353 | 0 | } |
354 | 0 | |
355 | 0 | // If started composition has already been committed, we shouldn't dispatch |
356 | 0 | // the compositionchange event. |
357 | 0 | if (!IsComposing()) { |
358 | 0 | aStatus = nsEventStatus_eConsumeNoDefault; |
359 | 0 | return NS_OK; |
360 | 0 | } |
361 | 0 | |
362 | 0 | // Note that the widget might be destroyed during a call of |
363 | 0 | // StartComposition(). In such case, we shouldn't keep dispatching next |
364 | 0 | // event. |
365 | 0 | rv = GetState(); |
366 | 0 | if (NS_FAILED(rv)) { |
367 | 0 | MOZ_ASSERT(rv != NS_ERROR_NOT_INITIALIZED, |
368 | 0 | "aDispatcher must still be initialized in this case"); |
369 | 0 | aStatus = nsEventStatus_eConsumeNoDefault; |
370 | 0 | return NS_OK; // Don't throw exception in this case |
371 | 0 | } |
372 | 0 |
|
373 | 0 | aStatus = nsEventStatus_eIgnore; |
374 | 0 | return NS_OK; |
375 | 0 | } |
376 | | |
377 | | nsresult |
378 | | TextEventDispatcher::CommitComposition(nsEventStatus& aStatus, |
379 | | const nsAString* aCommitString, |
380 | | const WidgetEventTime* aEventTime) |
381 | 0 | { |
382 | 0 | aStatus = nsEventStatus_eIgnore; |
383 | 0 |
|
384 | 0 | nsresult rv = GetState(); |
385 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
386 | 0 | return rv; |
387 | 0 | } |
388 | 0 | |
389 | 0 | // When there is no composition, caller shouldn't try to commit composition |
390 | 0 | // with non-existing composition string nor commit composition with empty |
391 | 0 | // string. |
392 | 0 | if (NS_WARN_IF(!IsComposing() && |
393 | 0 | (!aCommitString || aCommitString->IsEmpty()))) { |
394 | 0 | return NS_ERROR_FAILURE; |
395 | 0 | } |
396 | 0 | |
397 | 0 | nsCOMPtr<nsIWidget> widget(mWidget); |
398 | 0 | rv = StartCompositionAutomaticallyIfNecessary(aStatus, aEventTime); |
399 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
400 | 0 | return rv; |
401 | 0 | } |
402 | 0 | if (aStatus == nsEventStatus_eConsumeNoDefault) { |
403 | 0 | return NS_OK; |
404 | 0 | } |
405 | 0 | |
406 | 0 | // When you change some members from here, you may need same change in |
407 | 0 | // BeginInputTransactionFor(). |
408 | 0 | |
409 | 0 | // End current composition and make this free for other IMEs. |
410 | 0 | mIsComposing = false; |
411 | 0 |
|
412 | 0 | EventMessage message = aCommitString ? eCompositionCommit : |
413 | 0 | eCompositionCommitAsIs; |
414 | 0 | WidgetCompositionEvent compositionCommitEvent(true, message, widget); |
415 | 0 | InitEvent(compositionCommitEvent); |
416 | 0 | if (aEventTime) { |
417 | 0 | compositionCommitEvent.AssignEventTime(*aEventTime); |
418 | 0 | } |
419 | 0 | if (message == eCompositionCommit) { |
420 | 0 | compositionCommitEvent.mData = *aCommitString; |
421 | 0 | // Don't send CRLF nor CR, replace it with LF here. |
422 | 0 | compositionCommitEvent.mData.ReplaceSubstring(NS_LITERAL_STRING("\r\n"), |
423 | 0 | NS_LITERAL_STRING("\n")); |
424 | 0 | compositionCommitEvent.mData.ReplaceSubstring(NS_LITERAL_STRING("\r"), |
425 | 0 | NS_LITERAL_STRING("\n")); |
426 | 0 | } |
427 | 0 | rv = DispatchEvent(widget, compositionCommitEvent, aStatus); |
428 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
429 | 0 | return rv; |
430 | 0 | } |
431 | 0 | |
432 | 0 | return NS_OK; |
433 | 0 | } |
434 | | |
435 | | nsresult |
436 | | TextEventDispatcher::NotifyIME(const IMENotification& aIMENotification) |
437 | 0 | { |
438 | 0 | nsresult rv = NS_ERROR_NOT_IMPLEMENTED; |
439 | 0 |
|
440 | 0 | switch (aIMENotification.mMessage) { |
441 | 0 | case NOTIFY_IME_OF_BLUR: |
442 | 0 | mHasFocus = false; |
443 | 0 | ClearNotificationRequests(); |
444 | 0 | break; |
445 | 0 | case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED: |
446 | 0 | // If content handles composition events when native IME doesn't have |
447 | 0 | // composition, that means that we completely finished handling |
448 | 0 | // composition(s). Note that when focused content is in a remote |
449 | 0 | // process, this is sent when all dispatched composition events |
450 | 0 | // have been handled in the remote process. |
451 | 0 | if (!IsComposing()) { |
452 | 0 | mIsHandlingComposition = false; |
453 | 0 | } |
454 | 0 | break; |
455 | 0 | default: |
456 | 0 | break; |
457 | 0 | } |
458 | 0 | |
459 | 0 | |
460 | 0 | // First, send the notification to current input transaction's listener. |
461 | 0 | nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener); |
462 | 0 | if (listener) { |
463 | 0 | rv = listener->NotifyIME(this, aIMENotification); |
464 | 0 | } |
465 | 0 |
|
466 | 0 | if (!mWidget) { |
467 | 0 | return rv; |
468 | 0 | } |
469 | 0 | |
470 | 0 | // If current input transaction isn't for native event handler, we should |
471 | 0 | // send the notification to the native text event dispatcher listener |
472 | 0 | // since native event handler may need to do something from |
473 | 0 | // TextEventDispatcherListener::NotifyIME() even before there is no |
474 | 0 | // input transaction yet. For example, native IME handler may need to |
475 | 0 | // create new context at receiving NOTIFY_IME_OF_FOCUS. In this case, |
476 | 0 | // mListener may not be initialized since input transaction should be |
477 | 0 | // initialized immediately before dispatching every WidgetKeyboardEvent |
478 | 0 | // and WidgetCompositionEvent (dispatching events always occurs after |
479 | 0 | // focus move). |
480 | 0 | nsCOMPtr<TextEventDispatcherListener> nativeListener = |
481 | 0 | mWidget->GetNativeTextEventDispatcherListener(); |
482 | 0 | if (listener != nativeListener && nativeListener) { |
483 | 0 | switch (aIMENotification.mMessage) { |
484 | 0 | case REQUEST_TO_COMMIT_COMPOSITION: |
485 | 0 | case REQUEST_TO_CANCEL_COMPOSITION: |
486 | 0 | // It's not necessary to notify native IME of requests. |
487 | 0 | break; |
488 | 0 | default: { |
489 | 0 | // Even if current input transaction's listener returns NS_OK or |
490 | 0 | // something, we need to notify native IME of notifications because |
491 | 0 | // when user typing after TIP does something, the changed information |
492 | 0 | // is necessary for them. |
493 | 0 | nsresult rv2 = |
494 | 0 | nativeListener->NotifyIME(this, aIMENotification); |
495 | 0 | // But return the result from current listener except when the |
496 | 0 | // notification isn't handled. |
497 | 0 | if (rv == NS_ERROR_NOT_IMPLEMENTED) { |
498 | 0 | rv = rv2; |
499 | 0 | } |
500 | 0 | break; |
501 | 0 | } |
502 | 0 | } |
503 | 0 | } |
504 | 0 |
|
505 | 0 | if (aIMENotification.mMessage == NOTIFY_IME_OF_FOCUS) { |
506 | 0 | mHasFocus = true; |
507 | 0 | UpdateNotificationRequests(); |
508 | 0 | } |
509 | 0 |
|
510 | 0 | return rv; |
511 | 0 | } |
512 | | |
513 | | void |
514 | | TextEventDispatcher::ClearNotificationRequests() |
515 | 0 | { |
516 | 0 | mIMENotificationRequests = IMENotificationRequests(); |
517 | 0 | } |
518 | | |
519 | | void |
520 | | TextEventDispatcher::UpdateNotificationRequests() |
521 | 0 | { |
522 | 0 | ClearNotificationRequests(); |
523 | 0 |
|
524 | 0 | // If it doesn't has focus, no notifications are available. |
525 | 0 | if (!mHasFocus || !mWidget) { |
526 | 0 | return; |
527 | 0 | } |
528 | 0 | |
529 | 0 | // If there is a listener, its requests are necessary. |
530 | 0 | nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener); |
531 | 0 | if (listener) { |
532 | 0 | mIMENotificationRequests = listener->GetIMENotificationRequests(); |
533 | 0 | } |
534 | 0 |
|
535 | 0 | // Even if this is in non-native input transaction, native IME needs |
536 | 0 | // requests. So, add native IME requests too. |
537 | 0 | if (!IsInNativeInputTransaction()) { |
538 | 0 | nsCOMPtr<TextEventDispatcherListener> nativeListener = |
539 | 0 | mWidget->GetNativeTextEventDispatcherListener(); |
540 | 0 | if (nativeListener) { |
541 | 0 | mIMENotificationRequests |= nativeListener->GetIMENotificationRequests(); |
542 | 0 | } |
543 | 0 | } |
544 | 0 | } |
545 | | |
546 | | bool |
547 | | TextEventDispatcher::DispatchKeyboardEvent( |
548 | | EventMessage aMessage, |
549 | | const WidgetKeyboardEvent& aKeyboardEvent, |
550 | | nsEventStatus& aStatus, |
551 | | void* aData) |
552 | 0 | { |
553 | 0 | return DispatchKeyboardEventInternal(aMessage, aKeyboardEvent, aStatus, |
554 | 0 | aData); |
555 | 0 | } |
556 | | |
557 | | bool |
558 | | TextEventDispatcher::DispatchKeyboardEventInternal( |
559 | | EventMessage aMessage, |
560 | | const WidgetKeyboardEvent& aKeyboardEvent, |
561 | | nsEventStatus& aStatus, |
562 | | void* aData, |
563 | | uint32_t aIndexOfKeypress, |
564 | | bool aNeedsCallback) |
565 | 0 | { |
566 | 0 | // Note that this method is also used for dispatching key events on a plugin |
567 | 0 | // because key events on a plugin should be dispatched same as normal key |
568 | 0 | // events. Then, only some handlers which need to intercept key events |
569 | 0 | // before the focused plugin (e.g., reserved shortcut key handlers) can |
570 | 0 | // consume the events. |
571 | 0 | MOZ_ASSERT(WidgetKeyboardEvent::IsKeyDownOrKeyDownOnPlugin(aMessage) || |
572 | 0 | WidgetKeyboardEvent::IsKeyUpOrKeyUpOnPlugin(aMessage) || |
573 | 0 | aMessage == eKeyPress, "Invalid aMessage value"); |
574 | 0 | nsresult rv = GetState(); |
575 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
576 | 0 | return false; |
577 | 0 | } |
578 | 0 | |
579 | 0 | // If the key shouldn't cause keypress events, don't this patch them. |
580 | 0 | if (aMessage == eKeyPress && !aKeyboardEvent.ShouldCauseKeypressEvents()) { |
581 | 0 | return false; |
582 | 0 | } |
583 | 0 | |
584 | 0 | // Basically, key events shouldn't be dispatched during composition. |
585 | 0 | // Note that plugin process has different IME context. Therefore, we don't |
586 | 0 | // need to check our composition state when the key event is fired on a |
587 | 0 | // plugin. |
588 | 0 | if (IsComposing() && !WidgetKeyboardEvent::IsKeyEventOnPlugin(aMessage)) { |
589 | 0 | // However, if we need to behave like other browsers, we need the keydown |
590 | 0 | // and keyup events. Note that this behavior is also allowed by D3E spec. |
591 | 0 | // FYI: keypress events must not be fired during composition. |
592 | 0 | if (!sDispatchKeyEventsDuringComposition || aMessage == eKeyPress) { |
593 | 0 | return false; |
594 | 0 | } |
595 | 0 | // XXX If there was mOnlyContentDispatch for this case, it might be useful |
596 | 0 | // because our chrome doesn't assume that key events are fired during |
597 | 0 | // composition. |
598 | 0 | } |
599 | 0 | |
600 | 0 | WidgetKeyboardEvent keyEvent(true, aMessage, mWidget); |
601 | 0 | InitEvent(keyEvent); |
602 | 0 | keyEvent.AssignKeyEventData(aKeyboardEvent, false); |
603 | 0 |
|
604 | 0 | if (aStatus == nsEventStatus_eConsumeNoDefault) { |
605 | 0 | // If the key event should be dispatched as consumed event, marking it here. |
606 | 0 | // This is useful to prevent double action. This is intended to the system |
607 | 0 | // has already consumed the event but we need to dispatch the event for |
608 | 0 | // compatibility with older version and other browsers. So, we should not |
609 | 0 | // stop cross process forwarding of them. |
610 | 0 | keyEvent.PreventDefaultBeforeDispatch(CrossProcessForwarding::eAllow); |
611 | 0 | } |
612 | 0 |
|
613 | 0 | // Corrects each member for the specific key event type. |
614 | 0 | if (keyEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING) { |
615 | 0 | MOZ_ASSERT(!aIndexOfKeypress, |
616 | 0 | "aIndexOfKeypress must be 0 for non-printable key"); |
617 | 0 | // If the keyboard event isn't caused by printable key, its charCode should |
618 | 0 | // be 0. |
619 | 0 | keyEvent.SetCharCode(0); |
620 | 0 | } else { |
621 | 0 | if (WidgetKeyboardEvent::IsKeyDownOrKeyDownOnPlugin(aMessage) || |
622 | 0 | WidgetKeyboardEvent::IsKeyUpOrKeyUpOnPlugin(aMessage)) { |
623 | 0 | MOZ_RELEASE_ASSERT(!aIndexOfKeypress, |
624 | 0 | "aIndexOfKeypress must be 0 for either eKeyDown or eKeyUp"); |
625 | 0 | } else { |
626 | 0 | MOZ_RELEASE_ASSERT( |
627 | 0 | !aIndexOfKeypress || aIndexOfKeypress < keyEvent.mKeyValue.Length(), |
628 | 0 | "aIndexOfKeypress must be 0 - mKeyValue.Length() - 1"); |
629 | 0 | } |
630 | 0 | wchar_t ch = |
631 | 0 | keyEvent.mKeyValue.IsEmpty() ? 0 : keyEvent.mKeyValue[aIndexOfKeypress]; |
632 | 0 | keyEvent.SetCharCode(static_cast<uint32_t>(ch)); |
633 | 0 | if (aMessage == eKeyPress) { |
634 | 0 | // keyCode of eKeyPress events of printable keys should be always 0. |
635 | 0 | keyEvent.mKeyCode = 0; |
636 | 0 | // eKeyPress events are dispatched for every character. |
637 | 0 | // So, each key value of eKeyPress events should be a character. |
638 | 0 | if (ch) { |
639 | 0 | keyEvent.mKeyValue.Assign(ch); |
640 | 0 | } else { |
641 | 0 | keyEvent.mKeyValue.Truncate(); |
642 | 0 | } |
643 | 0 | } |
644 | 0 | } |
645 | 0 | if (WidgetKeyboardEvent::IsKeyUpOrKeyUpOnPlugin(aMessage)) { |
646 | 0 | // mIsRepeat of keyup event must be false. |
647 | 0 | keyEvent.mIsRepeat = false; |
648 | 0 | } |
649 | 0 | // mIsComposing should be initialized later. |
650 | 0 | keyEvent.mIsComposing = false; |
651 | 0 | if (mInputTransactionType == eNativeInputTransaction) { |
652 | 0 | // Copy mNativeKeyEvent here because for safety for other users of |
653 | 0 | // AssignKeyEventData(), it doesn't copy this. |
654 | 0 | keyEvent.mNativeKeyEvent = aKeyboardEvent.mNativeKeyEvent; |
655 | 0 | } else { |
656 | 0 | // If it's not a keyboard event for native key event, we should ensure that |
657 | 0 | // mNativeKeyEvent and mPluginEvent are null/empty. |
658 | 0 | keyEvent.mNativeKeyEvent = nullptr; |
659 | 0 | keyEvent.mPluginEvent.Clear(); |
660 | 0 | } |
661 | 0 | // TODO: Manage mUniqueId here. |
662 | 0 |
|
663 | 0 | // Request the alternative char codes for the key event. |
664 | 0 | // eKeyDown also needs alternative char codes because nsXBLWindowKeyHandler |
665 | 0 | // needs to check if a following keypress event is reserved by chrome for |
666 | 0 | // stopping propagation of its preceding keydown event. |
667 | 0 | keyEvent.mAlternativeCharCodes.Clear(); |
668 | 0 | if ((WidgetKeyboardEvent::IsKeyDownOrKeyDownOnPlugin(aMessage) || |
669 | 0 | aMessage == eKeyPress) && |
670 | 0 | (aNeedsCallback || keyEvent.IsControl() || keyEvent.IsAlt() || |
671 | 0 | keyEvent.IsMeta() || keyEvent.IsOS())) { |
672 | 0 | nsCOMPtr<TextEventDispatcherListener> listener = |
673 | 0 | do_QueryReferent(mListener); |
674 | 0 | if (listener) { |
675 | 0 | DebugOnly<WidgetKeyboardEvent> original(keyEvent); |
676 | 0 | listener->WillDispatchKeyboardEvent(this, keyEvent, aIndexOfKeypress, |
677 | 0 | aData); |
678 | 0 | MOZ_ASSERT(keyEvent.mMessage == |
679 | 0 | static_cast<WidgetKeyboardEvent&>(original).mMessage); |
680 | 0 | MOZ_ASSERT(keyEvent.mKeyCode == |
681 | 0 | static_cast<WidgetKeyboardEvent&>(original).mKeyCode); |
682 | 0 | MOZ_ASSERT(keyEvent.mLocation == |
683 | 0 | static_cast<WidgetKeyboardEvent&>(original).mLocation); |
684 | 0 | MOZ_ASSERT(keyEvent.mIsRepeat == |
685 | 0 | static_cast<WidgetKeyboardEvent&>(original).mIsRepeat); |
686 | 0 | MOZ_ASSERT(keyEvent.mIsComposing == |
687 | 0 | static_cast<WidgetKeyboardEvent&>(original).mIsComposing); |
688 | 0 | MOZ_ASSERT(keyEvent.mKeyNameIndex == |
689 | 0 | static_cast<WidgetKeyboardEvent&>(original).mKeyNameIndex); |
690 | 0 | MOZ_ASSERT(keyEvent.mCodeNameIndex == |
691 | 0 | static_cast<WidgetKeyboardEvent&>(original).mCodeNameIndex); |
692 | 0 | MOZ_ASSERT(keyEvent.mKeyValue == |
693 | 0 | static_cast<WidgetKeyboardEvent&>(original).mKeyValue); |
694 | 0 | MOZ_ASSERT(keyEvent.mCodeValue == |
695 | 0 | static_cast<WidgetKeyboardEvent&>(original).mCodeValue); |
696 | 0 | } |
697 | 0 | } |
698 | 0 |
|
699 | 0 | if (sDispatchKeyPressEventsOnlySystemGroupInContent && |
700 | 0 | keyEvent.mMessage == eKeyPress && |
701 | 0 | !keyEvent.ShouldKeyPressEventBeFiredOnContent()) { |
702 | 0 | // Note that even if we set it to true, this may be overwritten by |
703 | 0 | // PresShell::DispatchEventToDOM(). |
704 | 0 | keyEvent.mFlags.mOnlySystemGroupDispatchInContent = true; |
705 | 0 | } |
706 | 0 |
|
707 | 0 | DispatchInputEvent(mWidget, keyEvent, aStatus); |
708 | 0 | return true; |
709 | 0 | } |
710 | | |
711 | | bool |
712 | | TextEventDispatcher::MaybeDispatchKeypressEvents( |
713 | | const WidgetKeyboardEvent& aKeyboardEvent, |
714 | | nsEventStatus& aStatus, |
715 | | void* aData, |
716 | | bool aNeedsCallback) |
717 | 0 | { |
718 | 0 | // If the key event was consumed, keypress event shouldn't be fired. |
719 | 0 | if (aStatus == nsEventStatus_eConsumeNoDefault) { |
720 | 0 | return false; |
721 | 0 | } |
722 | 0 | |
723 | 0 | // If the key shouldn't cause keypress events, don't fire them. |
724 | 0 | if (!aKeyboardEvent.ShouldCauseKeypressEvents()) { |
725 | 0 | return false; |
726 | 0 | } |
727 | 0 | |
728 | 0 | // If the key isn't a printable key or just inputting one character or |
729 | 0 | // no character, we should dispatch only one keypress. Otherwise, i.e., |
730 | 0 | // if the key is a printable key and inputs multiple characters, keypress |
731 | 0 | // event should be dispatched the count of inputting characters times. |
732 | 0 | size_t keypressCount = |
733 | 0 | aKeyboardEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING ? |
734 | 0 | 1 : std::max(static_cast<nsAString::size_type>(1), |
735 | 0 | aKeyboardEvent.mKeyValue.Length()); |
736 | 0 | bool isDispatched = false; |
737 | 0 | bool consumed = false; |
738 | 0 | for (size_t i = 0; i < keypressCount; i++) { |
739 | 0 | aStatus = nsEventStatus_eIgnore; |
740 | 0 | if (!DispatchKeyboardEventInternal(eKeyPress, aKeyboardEvent, |
741 | 0 | aStatus, aData, i, aNeedsCallback)) { |
742 | 0 | // The widget must have been gone. |
743 | 0 | break; |
744 | 0 | } |
745 | 0 | isDispatched = true; |
746 | 0 | if (!consumed) { |
747 | 0 | consumed = (aStatus == nsEventStatus_eConsumeNoDefault); |
748 | 0 | } |
749 | 0 | } |
750 | 0 |
|
751 | 0 | // If one of the keypress event was consumed, return ConsumeNoDefault. |
752 | 0 | if (consumed) { |
753 | 0 | aStatus = nsEventStatus_eConsumeNoDefault; |
754 | 0 | } |
755 | 0 |
|
756 | 0 | return isDispatched; |
757 | 0 | } |
758 | | |
759 | | /****************************************************************************** |
760 | | * TextEventDispatcher::PendingComposition |
761 | | *****************************************************************************/ |
762 | | |
763 | | TextEventDispatcher::PendingComposition::PendingComposition() |
764 | 0 | { |
765 | 0 | Clear(); |
766 | 0 | } |
767 | | |
768 | | void |
769 | | TextEventDispatcher::PendingComposition::Clear() |
770 | 0 | { |
771 | 0 | mString.Truncate(); |
772 | 0 | mClauses = nullptr; |
773 | 0 | mCaret.mRangeType = TextRangeType::eUninitialized; |
774 | 0 | mReplacedNativeLineBreakers = false; |
775 | 0 | } |
776 | | |
777 | | void |
778 | | TextEventDispatcher::PendingComposition::EnsureClauseArray() |
779 | 0 | { |
780 | 0 | if (mClauses) { |
781 | 0 | return; |
782 | 0 | } |
783 | 0 | mClauses = new TextRangeArray(); |
784 | 0 | } |
785 | | |
786 | | nsresult |
787 | | TextEventDispatcher::PendingComposition::SetString(const nsAString& aString) |
788 | 0 | { |
789 | 0 | MOZ_ASSERT(!mReplacedNativeLineBreakers); |
790 | 0 | mString = aString; |
791 | 0 | return NS_OK; |
792 | 0 | } |
793 | | |
794 | | nsresult |
795 | | TextEventDispatcher::PendingComposition::AppendClause( |
796 | | uint32_t aLength, |
797 | | TextRangeType aTextRangeType) |
798 | 0 | { |
799 | 0 | MOZ_ASSERT(!mReplacedNativeLineBreakers); |
800 | 0 |
|
801 | 0 | if (NS_WARN_IF(!aLength)) { |
802 | 0 | return NS_ERROR_INVALID_ARG; |
803 | 0 | } |
804 | 0 | |
805 | 0 | switch (aTextRangeType) { |
806 | 0 | case TextRangeType::eRawClause: |
807 | 0 | case TextRangeType::eSelectedRawClause: |
808 | 0 | case TextRangeType::eConvertedClause: |
809 | 0 | case TextRangeType::eSelectedClause: { |
810 | 0 | EnsureClauseArray(); |
811 | 0 | TextRange textRange; |
812 | 0 | textRange.mStartOffset = |
813 | 0 | mClauses->IsEmpty() ? 0 : mClauses->LastElement().mEndOffset; |
814 | 0 | textRange.mEndOffset = textRange.mStartOffset + aLength; |
815 | 0 | textRange.mRangeType = aTextRangeType; |
816 | 0 | mClauses->AppendElement(textRange); |
817 | 0 | return NS_OK; |
818 | 0 | } |
819 | 0 | default: |
820 | 0 | return NS_ERROR_INVALID_ARG; |
821 | 0 | } |
822 | 0 | } |
823 | | |
824 | | nsresult |
825 | | TextEventDispatcher::PendingComposition::SetCaret(uint32_t aOffset, |
826 | | uint32_t aLength) |
827 | 0 | { |
828 | 0 | MOZ_ASSERT(!mReplacedNativeLineBreakers); |
829 | 0 |
|
830 | 0 | mCaret.mStartOffset = aOffset; |
831 | 0 | mCaret.mEndOffset = mCaret.mStartOffset + aLength; |
832 | 0 | mCaret.mRangeType = TextRangeType::eCaret; |
833 | 0 | return NS_OK; |
834 | 0 | } |
835 | | |
836 | | nsresult |
837 | | TextEventDispatcher::PendingComposition::Set(const nsAString& aString, |
838 | | const TextRangeArray* aRanges) |
839 | 0 | { |
840 | 0 | Clear(); |
841 | 0 |
|
842 | 0 | nsresult rv = SetString(aString); |
843 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
844 | 0 | return rv; |
845 | 0 | } |
846 | 0 | |
847 | 0 | if (!aRanges || aRanges->IsEmpty()) { |
848 | 0 | // Create dummy range if mString isn't empty. |
849 | 0 | if (!mString.IsEmpty()) { |
850 | 0 | rv = AppendClause(mString.Length(), TextRangeType::eRawClause); |
851 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
852 | 0 | return rv; |
853 | 0 | } |
854 | 0 | ReplaceNativeLineBreakers(); |
855 | 0 | } |
856 | 0 | return NS_OK; |
857 | 0 | } |
858 | 0 | |
859 | 0 | // Adjust offsets in the ranges for XP linefeed character (only \n). |
860 | 0 | for (uint32_t i = 0; i < aRanges->Length(); ++i) { |
861 | 0 | TextRange range = aRanges->ElementAt(i); |
862 | 0 | if (range.mRangeType == TextRangeType::eCaret) { |
863 | 0 | mCaret = range; |
864 | 0 | } else { |
865 | 0 | EnsureClauseArray(); |
866 | 0 | mClauses->AppendElement(range); |
867 | 0 | } |
868 | 0 | } |
869 | 0 | ReplaceNativeLineBreakers(); |
870 | 0 | return NS_OK; |
871 | 0 | } |
872 | | |
873 | | void |
874 | | TextEventDispatcher::PendingComposition::ReplaceNativeLineBreakers() |
875 | 0 | { |
876 | 0 | mReplacedNativeLineBreakers = true; |
877 | 0 |
|
878 | 0 | // If the composition string is empty, we don't need to do anything. |
879 | 0 | if (mString.IsEmpty()) { |
880 | 0 | return; |
881 | 0 | } |
882 | 0 | |
883 | 0 | nsAutoString nativeString(mString); |
884 | 0 | // Don't expose CRLF nor CR to web contents, instead, use LF. |
885 | 0 | mString.ReplaceSubstring(NS_LITERAL_STRING("\r\n"), NS_LITERAL_STRING("\n")); |
886 | 0 | mString.ReplaceSubstring(NS_LITERAL_STRING("\r"), NS_LITERAL_STRING("\n")); |
887 | 0 |
|
888 | 0 | // If the length isn't changed, we don't need to adjust any offset and length |
889 | 0 | // of mClauses nor mCaret. |
890 | 0 | if (nativeString.Length() == mString.Length()) { |
891 | 0 | return; |
892 | 0 | } |
893 | 0 | |
894 | 0 | if (mClauses) { |
895 | 0 | for (TextRange& clause : *mClauses) { |
896 | 0 | AdjustRange(clause, nativeString); |
897 | 0 | } |
898 | 0 | } |
899 | 0 | if (mCaret.mRangeType == TextRangeType::eCaret) { |
900 | 0 | AdjustRange(mCaret, nativeString); |
901 | 0 | } |
902 | 0 | } |
903 | | |
904 | | // static |
905 | | void |
906 | | TextEventDispatcher::PendingComposition::AdjustRange( |
907 | | TextRange& aRange, |
908 | | const nsAString& aNativeString) |
909 | 0 | { |
910 | 0 | TextRange nativeRange = aRange; |
911 | 0 | // XXX Following code wastes runtime cost because this causes computing |
912 | 0 | // mStartOffset for each clause from the start of composition string. |
913 | 0 | // If we'd make TextRange have only its length, we don't need to do |
914 | 0 | // this. However, this must not be so serious problem because |
915 | 0 | // composition string is usually short and separated as a few clauses. |
916 | 0 | if (nativeRange.mStartOffset > 0) { |
917 | 0 | nsAutoString preText( |
918 | 0 | Substring(aNativeString, 0, nativeRange.mStartOffset)); |
919 | 0 | preText.ReplaceSubstring(NS_LITERAL_STRING("\r\n"), |
920 | 0 | NS_LITERAL_STRING("\n")); |
921 | 0 | aRange.mStartOffset = preText.Length(); |
922 | 0 | } |
923 | 0 | if (nativeRange.Length() == 0) { |
924 | 0 | aRange.mEndOffset = aRange.mStartOffset; |
925 | 0 | } else { |
926 | 0 | nsAutoString clause( |
927 | 0 | Substring(aNativeString, nativeRange.mStartOffset, nativeRange.Length())); |
928 | 0 | clause.ReplaceSubstring(NS_LITERAL_STRING("\r\n"), |
929 | 0 | NS_LITERAL_STRING("\n")); |
930 | 0 | aRange.mEndOffset = aRange.mStartOffset + clause.Length(); |
931 | 0 | } |
932 | 0 | } |
933 | | |
934 | | nsresult |
935 | | TextEventDispatcher::PendingComposition::Flush( |
936 | | TextEventDispatcher* aDispatcher, |
937 | | nsEventStatus& aStatus, |
938 | | const WidgetEventTime* aEventTime) |
939 | 0 | { |
940 | 0 | aStatus = nsEventStatus_eIgnore; |
941 | 0 |
|
942 | 0 | nsresult rv = aDispatcher->GetState(); |
943 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
944 | 0 | return rv; |
945 | 0 | } |
946 | 0 | |
947 | 0 | if (mClauses && !mClauses->IsEmpty() && |
948 | 0 | mClauses->LastElement().mEndOffset != mString.Length()) { |
949 | 0 | NS_WARNING("Sum of length of the all clauses must be same as the string " |
950 | 0 | "length"); |
951 | 0 | Clear(); |
952 | 0 | return NS_ERROR_ILLEGAL_VALUE; |
953 | 0 | } |
954 | 0 | if (mCaret.mRangeType == TextRangeType::eCaret) { |
955 | 0 | if (mCaret.mEndOffset > mString.Length()) { |
956 | 0 | NS_WARNING("Caret position is out of the composition string"); |
957 | 0 | Clear(); |
958 | 0 | return NS_ERROR_ILLEGAL_VALUE; |
959 | 0 | } |
960 | 0 | EnsureClauseArray(); |
961 | 0 | mClauses->AppendElement(mCaret); |
962 | 0 | } |
963 | 0 |
|
964 | 0 | // If the composition string is set without Set(), we need to replace native |
965 | 0 | // line breakers in the composition string with XP line breaker. |
966 | 0 | if (!mReplacedNativeLineBreakers) { |
967 | 0 | ReplaceNativeLineBreakers(); |
968 | 0 | } |
969 | 0 |
|
970 | 0 | RefPtr<TextEventDispatcher> kungFuDeathGrip(aDispatcher); |
971 | 0 | nsCOMPtr<nsIWidget> widget(aDispatcher->mWidget); |
972 | 0 | WidgetCompositionEvent compChangeEvent(true, eCompositionChange, widget); |
973 | 0 | aDispatcher->InitEvent(compChangeEvent); |
974 | 0 | if (aEventTime) { |
975 | 0 | compChangeEvent.AssignEventTime(*aEventTime); |
976 | 0 | } |
977 | 0 | compChangeEvent.mData = mString; |
978 | 0 | if (mClauses) { |
979 | 0 | MOZ_ASSERT(!mClauses->IsEmpty(), |
980 | 0 | "mClauses must be non-empty array when it's not nullptr"); |
981 | 0 | compChangeEvent.mRanges = mClauses; |
982 | 0 | } |
983 | 0 |
|
984 | 0 | // While this method dispatches a composition event, some other event handler |
985 | 0 | // cause more clauses to be added. So, we should clear pending composition |
986 | 0 | // before dispatching the event. |
987 | 0 | Clear(); |
988 | 0 |
|
989 | 0 | rv = aDispatcher->StartCompositionAutomaticallyIfNecessary(aStatus, |
990 | 0 | aEventTime); |
991 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
992 | 0 | return rv; |
993 | 0 | } |
994 | 0 | if (aStatus == nsEventStatus_eConsumeNoDefault) { |
995 | 0 | return NS_OK; |
996 | 0 | } |
997 | 0 | rv = aDispatcher->DispatchEvent(widget, compChangeEvent, aStatus); |
998 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
999 | 0 | return rv; |
1000 | 0 | } |
1001 | 0 | |
1002 | 0 | return NS_OK; |
1003 | 0 | } |
1004 | | |
1005 | | } // namespace widget |
1006 | | } // namespace mozilla |