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