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/. */
6 #include "TextEventDispatcher.h"
9 #include "PuppetWidget.h"
10 #include "TextEvents.h"
12 #include "mozilla/Preferences.h"
13 #include "mozilla/StaticPrefs_dom.h"
15 #include "nsIWidget.h"
16 #include "nsPIDOMWindow.h"
22 /******************************************************************************
24 *****************************************************************************/
25 TextEventDispatcher::TextEventDispatcher(nsIWidget
* aWidget
)
28 mInputTransactionType(eNoInputTransaction
),
30 mIsHandlingComposition(false),
32 MOZ_RELEASE_ASSERT(mWidget
, "aWidget must not be nullptr");
34 ClearNotificationRequests();
37 nsresult
TextEventDispatcher::BeginInputTransaction(
38 TextEventDispatcherListener
* aListener
) {
39 return BeginInputTransactionInternal(aListener
,
40 eSameProcessSyncInputTransaction
);
43 nsresult
TextEventDispatcher::BeginTestInputTransaction(
44 TextEventDispatcherListener
* aListener
, bool aIsAPZAware
) {
45 return BeginInputTransactionInternal(
46 aListener
, aIsAPZAware
? eAsyncTestInputTransaction
47 : eSameProcessSyncTestInputTransaction
);
50 nsresult
TextEventDispatcher::BeginNativeInputTransaction() {
51 if (NS_WARN_IF(!mWidget
)) {
52 return NS_ERROR_FAILURE
;
54 RefPtr
<TextEventDispatcherListener
> listener
=
55 mWidget
->GetNativeTextEventDispatcherListener();
56 if (NS_WARN_IF(!listener
)) {
57 return NS_ERROR_FAILURE
;
59 return BeginInputTransactionInternal(listener
, eNativeInputTransaction
);
62 nsresult
TextEventDispatcher::BeginInputTransactionInternal(
63 TextEventDispatcherListener
* aListener
, InputTransactionType aType
) {
64 if (NS_WARN_IF(!aListener
)) {
65 return NS_ERROR_INVALID_ARG
;
67 nsCOMPtr
<TextEventDispatcherListener
> listener
= do_QueryReferent(mListener
);
69 if (listener
== aListener
&& mInputTransactionType
== aType
) {
70 UpdateNotificationRequests();
73 // If this has composition or is dispatching an event, any other listener
74 // can steal ownership. Especially, if the latter case is allowed,
75 // nobody cannot begin input transaction with this if a modal dialog is
76 // opened during dispatching an event.
77 if (IsComposing() || IsDispatchingEvent()) {
78 return NS_ERROR_ALREADY_INITIALIZED
;
81 mListener
= do_GetWeakReference(aListener
);
82 mInputTransactionType
= aType
;
83 if (listener
&& listener
!= aListener
) {
84 listener
->OnRemovedFrom(this);
86 UpdateNotificationRequests();
90 nsresult
TextEventDispatcher::BeginInputTransactionFor(
91 const WidgetGUIEvent
* aEvent
, PuppetWidget
* aPuppetWidget
) {
92 MOZ_ASSERT(XRE_IsContentProcess());
93 MOZ_ASSERT(!IsDispatchingEvent());
95 switch (aEvent
->mMessage
) {
99 MOZ_ASSERT(aEvent
->mClass
== eKeyboardEventClass
);
101 case eCompositionStart
:
102 case eCompositionChange
:
103 case eCompositionCommit
:
104 case eCompositionCommitAsIs
:
105 MOZ_ASSERT(aEvent
->mClass
== eCompositionEventClass
);
108 return NS_ERROR_INVALID_ARG
;
111 if (aEvent
->mFlags
.mIsSynthesizedForTests
) {
112 // If the event is for an automated test and this instance dispatched
113 // an event to the parent process, we can assume that this is already
114 // initialized properly.
115 if (mInputTransactionType
== eAsyncTestInputTransaction
) {
118 // Even if the event coming from the parent process is synthesized for
119 // tests, this process should treat it as "sync" test here because
120 // it won't be go back to the parent process.
121 nsresult rv
= BeginInputTransactionInternal(
122 static_cast<TextEventDispatcherListener
*>(aPuppetWidget
),
123 eSameProcessSyncTestInputTransaction
);
124 if (NS_WARN_IF(NS_FAILED(rv
))) {
128 nsresult rv
= BeginNativeInputTransaction();
129 if (NS_WARN_IF(NS_FAILED(rv
))) {
134 // Emulate modifying members which indicate the state of composition.
135 // If we need to manage more states and/or more complexly, we should create
136 // internal methods which are called by both here and each event dispatcher
137 // method of this class.
138 switch (aEvent
->mMessage
) {
143 case eCompositionStart
:
144 MOZ_ASSERT(!mIsComposing
);
145 mIsComposing
= mIsHandlingComposition
= true;
147 case eCompositionChange
:
148 MOZ_ASSERT(mIsComposing
);
149 MOZ_ASSERT(mIsHandlingComposition
);
150 mIsComposing
= mIsHandlingComposition
= true;
152 case eCompositionCommit
:
153 case eCompositionCommitAsIs
:
154 MOZ_ASSERT(mIsComposing
);
155 MOZ_ASSERT(mIsHandlingComposition
);
156 mIsComposing
= false;
157 mIsHandlingComposition
= true;
160 MOZ_ASSERT_UNREACHABLE("You forgot to handle the event");
161 return NS_ERROR_UNEXPECTED
;
164 void TextEventDispatcher::EndInputTransaction(
165 TextEventDispatcherListener
* aListener
) {
166 if (NS_WARN_IF(IsComposing()) || NS_WARN_IF(IsDispatchingEvent())) {
170 mInputTransactionType
= eNoInputTransaction
;
172 nsCOMPtr
<TextEventDispatcherListener
> listener
= do_QueryReferent(mListener
);
173 if (NS_WARN_IF(!listener
)) {
177 if (NS_WARN_IF(listener
!= aListener
)) {
182 listener
->OnRemovedFrom(this);
183 UpdateNotificationRequests();
186 void TextEventDispatcher::OnDestroyWidget() {
189 ClearNotificationRequests();
190 mPendingComposition
.Clear();
191 nsCOMPtr
<TextEventDispatcherListener
> listener
= do_QueryReferent(mListener
);
193 mWritingMode
.reset();
194 mInputTransactionType
= eNoInputTransaction
;
196 listener
->OnRemovedFrom(this);
200 nsresult
TextEventDispatcher::GetState() const {
201 nsCOMPtr
<TextEventDispatcherListener
> listener
= do_QueryReferent(mListener
);
203 return NS_ERROR_NOT_INITIALIZED
;
205 if (!mWidget
|| mWidget
->Destroyed()) {
206 return NS_ERROR_NOT_AVAILABLE
;
211 void TextEventDispatcher::InitEvent(WidgetGUIEvent
& aEvent
) const {
212 aEvent
.mRefPoint
= LayoutDeviceIntPoint(0, 0);
213 aEvent
.mFlags
.mIsSynthesizedForTests
= IsForTests();
214 if (aEvent
.mClass
!= eCompositionEventClass
) {
217 void* pseudoIMEContext
= GetPseudoIMEContext();
218 if (pseudoIMEContext
) {
219 aEvent
.AsCompositionEvent()->mNativeIMEContext
.InitWithRawNativeIMEContext(
224 MOZ_ASSERT(!XRE_IsContentProcess(),
225 "Why did the content process start native event transaction?");
226 MOZ_ASSERT(aEvent
.AsCompositionEvent()->mNativeIMEContext
.IsValid(),
227 "Native IME context shouldn't be invalid");
229 #endif // #ifdef DEBUG
232 Maybe
<WritingMode
> TextEventDispatcher::MaybeQueryWritingModeAtSelection()
234 if (mHasFocus
|| mWritingMode
.isSome()) {
238 if (NS_WARN_IF(!mWidget
)) {
242 // If a remote content has focus and IME does not have focus, it's going to
243 // fail eQuerySelectedText in ContentCacheParent. For avoiding to waste
244 // unnecessary runtime cost and to prevent unnecessary warnings, we should
245 // not dispatch the event in the case.
246 const InputContext inputContext
= mWidget
->GetInputContext();
247 if (XRE_IsE10sParentProcess() && inputContext
.IsOriginContentProcess() &&
248 !inputContext
.mIMEState
.IsEditable()) {
252 WidgetQueryContentEvent
querySelectedTextEvent(true, eQuerySelectedText
,
254 nsEventStatus status
= nsEventStatus_eIgnore
;
255 const_cast<TextEventDispatcher
*>(this)->DispatchEvent(
256 mWidget
, querySelectedTextEvent
, status
);
257 if (!querySelectedTextEvent
.FoundSelection()) {
261 return Some(querySelectedTextEvent
.mReply
->mWritingMode
);
264 nsresult
TextEventDispatcher::DispatchEvent(nsIWidget
* aWidget
,
265 WidgetGUIEvent
& aEvent
,
266 nsEventStatus
& aStatus
) {
267 MOZ_ASSERT(!aEvent
.AsInputEvent(), "Use DispatchInputEvent()");
269 RefPtr
<TextEventDispatcher
> kungFuDeathGrip(this);
270 nsCOMPtr
<nsIWidget
> widget(aWidget
);
272 nsresult rv
= widget
->DispatchEvent(&aEvent
, aStatus
);
277 nsresult
TextEventDispatcher::DispatchInputEvent(nsIWidget
* aWidget
,
278 WidgetInputEvent
& aEvent
,
279 nsEventStatus
& aStatus
) {
280 RefPtr
<TextEventDispatcher
> kungFuDeathGrip(this);
281 nsCOMPtr
<nsIWidget
> widget(aWidget
);
284 // If the event is dispatched via nsIWidget::DispatchInputEvent(), it
285 // sends the event to the parent process first since APZ needs to handle it
286 // first. However, some callers (e.g., keyboard apps on B2G and tests
287 // expecting synchronous dispatch) don't want this to do that.
289 if (ShouldSendInputEventToAPZ()) {
290 aStatus
= widget
->DispatchInputEvent(&aEvent
).mContentStatus
;
292 rv
= widget
->DispatchEvent(&aEvent
, aStatus
);
299 nsresult
TextEventDispatcher::StartComposition(
300 nsEventStatus
& aStatus
, const WidgetEventTime
* aEventTime
) {
301 aStatus
= nsEventStatus_eIgnore
;
303 nsresult rv
= GetState();
304 if (NS_WARN_IF(NS_FAILED(rv
))) {
308 if (NS_WARN_IF(mIsComposing
)) {
309 return NS_ERROR_FAILURE
;
312 // When you change some members from here, you may need same change in
313 // BeginInputTransactionFor().
314 mIsComposing
= mIsHandlingComposition
= true;
315 WidgetCompositionEvent
compositionStartEvent(true, eCompositionStart
,
317 InitEvent(compositionStartEvent
);
319 compositionStartEvent
.AssignEventTime(*aEventTime
);
321 rv
= DispatchEvent(mWidget
, compositionStartEvent
, aStatus
);
322 if (NS_WARN_IF(NS_FAILED(rv
))) {
329 nsresult
TextEventDispatcher::StartCompositionAutomaticallyIfNecessary(
330 nsEventStatus
& aStatus
, const WidgetEventTime
* aEventTime
) {
335 nsresult rv
= StartComposition(aStatus
, aEventTime
);
336 if (NS_WARN_IF(NS_FAILED(rv
))) {
340 // If started composition has already been committed, we shouldn't dispatch
341 // the compositionchange event.
342 if (!IsComposing()) {
343 aStatus
= nsEventStatus_eConsumeNoDefault
;
347 // Note that the widget might be destroyed during a call of
348 // StartComposition(). In such case, we shouldn't keep dispatching next
352 MOZ_ASSERT(rv
!= NS_ERROR_NOT_INITIALIZED
,
353 "aDispatcher must still be initialized in this case");
354 aStatus
= nsEventStatus_eConsumeNoDefault
;
355 return NS_OK
; // Don't throw exception in this case
358 aStatus
= nsEventStatus_eIgnore
;
362 nsresult
TextEventDispatcher::CommitComposition(
363 nsEventStatus
& aStatus
, const nsAString
* aCommitString
,
364 const WidgetEventTime
* aEventTime
) {
365 aStatus
= nsEventStatus_eIgnore
;
367 nsresult rv
= GetState();
368 if (NS_WARN_IF(NS_FAILED(rv
))) {
372 // When there is no composition, caller shouldn't try to commit composition
373 // with non-existing composition string nor commit composition with empty
375 if (NS_WARN_IF(!IsComposing() &&
376 (!aCommitString
|| aCommitString
->IsEmpty()))) {
377 return NS_ERROR_FAILURE
;
380 nsCOMPtr
<nsIWidget
> widget(mWidget
);
381 rv
= StartCompositionAutomaticallyIfNecessary(aStatus
, aEventTime
);
382 if (NS_WARN_IF(NS_FAILED(rv
))) {
385 if (aStatus
== nsEventStatus_eConsumeNoDefault
) {
389 // When you change some members from here, you may need same change in
390 // BeginInputTransactionFor().
392 // End current composition and make this free for other IMEs.
393 mIsComposing
= false;
395 EventMessage message
=
396 aCommitString
? eCompositionCommit
: eCompositionCommitAsIs
;
397 WidgetCompositionEvent
compositionCommitEvent(true, message
, widget
);
398 InitEvent(compositionCommitEvent
);
400 compositionCommitEvent
.AssignEventTime(*aEventTime
);
402 if (message
== eCompositionCommit
) {
403 compositionCommitEvent
.mData
= *aCommitString
;
404 // If aCommitString comes from TextInputProcessor, it may be void, but
405 // editor requires non-void string even when it's empty.
406 compositionCommitEvent
.mData
.SetIsVoid(false);
407 // Don't send CRLF nor CR, replace it with LF here.
408 compositionCommitEvent
.mData
.ReplaceSubstring(u
"\r\n"_ns
, u
"\n"_ns
);
409 compositionCommitEvent
.mData
.ReplaceSubstring(u
"\r"_ns
, u
"\n"_ns
);
411 rv
= DispatchEvent(widget
, compositionCommitEvent
, aStatus
);
412 if (NS_WARN_IF(NS_FAILED(rv
))) {
419 nsresult
TextEventDispatcher::NotifyIME(
420 const IMENotification
& aIMENotification
) {
421 nsresult rv
= NS_ERROR_NOT_IMPLEMENTED
;
423 switch (aIMENotification
.mMessage
) {
424 case NOTIFY_IME_OF_FOCUS
: {
425 mWritingMode
= MaybeQueryWritingModeAtSelection();
428 case NOTIFY_IME_OF_BLUR
:
430 mWritingMode
.reset();
431 ClearNotificationRequests();
433 case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED
:
434 // If content handles composition events when native IME doesn't have
435 // composition, that means that we completely finished handling
436 // composition(s). Note that when focused content is in a remote
437 // process, this is sent when all dispatched composition events
438 // have been handled in the remote process.
439 if (!IsComposing()) {
440 mIsHandlingComposition
= false;
443 case NOTIFY_IME_OF_SELECTION_CHANGE
:
444 if (mHasFocus
&& aIMENotification
.mSelectionChangeData
.HasRange()) {
446 Some(aIMENotification
.mSelectionChangeData
.GetWritingMode());
453 // First, send the notification to current input transaction's listener.
454 nsCOMPtr
<TextEventDispatcherListener
> listener
= do_QueryReferent(mListener
);
456 rv
= listener
->NotifyIME(this, aIMENotification
);
463 // If current input transaction isn't for native event handler, we should
464 // send the notification to the native text event dispatcher listener
465 // since native event handler may need to do something from
466 // TextEventDispatcherListener::NotifyIME() even before there is no
467 // input transaction yet. For example, native IME handler may need to
468 // create new context at receiving NOTIFY_IME_OF_FOCUS. In this case,
469 // mListener may not be initialized since input transaction should be
470 // initialized immediately before dispatching every WidgetKeyboardEvent
471 // and WidgetCompositionEvent (dispatching events always occurs after
473 nsCOMPtr
<TextEventDispatcherListener
> nativeListener
=
474 mWidget
->GetNativeTextEventDispatcherListener();
475 if (listener
!= nativeListener
&& nativeListener
) {
476 switch (aIMENotification
.mMessage
) {
477 case REQUEST_TO_COMMIT_COMPOSITION
:
478 case REQUEST_TO_CANCEL_COMPOSITION
:
479 // It's not necessary to notify native IME of requests.
482 // Even if current input transaction's listener returns NS_OK or
483 // something, we need to notify native IME of notifications because
484 // when user typing after TIP does something, the changed information
485 // is necessary for them.
486 nsresult rv2
= nativeListener
->NotifyIME(this, aIMENotification
);
487 // But return the result from current listener except when the
488 // notification isn't handled.
489 if (rv
== NS_ERROR_NOT_IMPLEMENTED
) {
497 if (aIMENotification
.mMessage
== NOTIFY_IME_OF_FOCUS
) {
499 UpdateNotificationRequests();
505 void TextEventDispatcher::ClearNotificationRequests() {
506 mIMENotificationRequests
= IMENotificationRequests();
509 void TextEventDispatcher::UpdateNotificationRequests() {
510 ClearNotificationRequests();
512 // If it doesn't has focus, no notifications are available.
513 if (!mHasFocus
|| !mWidget
) {
517 // If there is a listener, its requests are necessary.
518 nsCOMPtr
<TextEventDispatcherListener
> listener
= do_QueryReferent(mListener
);
520 mIMENotificationRequests
= listener
->GetIMENotificationRequests();
523 // Even if this is in non-native input transaction, native IME needs
524 // requests. So, add native IME requests too.
525 if (!IsInNativeInputTransaction()) {
526 nsCOMPtr
<TextEventDispatcherListener
> nativeListener
=
527 mWidget
->GetNativeTextEventDispatcherListener();
528 if (nativeListener
) {
529 mIMENotificationRequests
|= nativeListener
->GetIMENotificationRequests();
534 bool TextEventDispatcher::DispatchKeyboardEvent(
535 EventMessage aMessage
, const WidgetKeyboardEvent
& aKeyboardEvent
,
536 nsEventStatus
& aStatus
, void* aData
) {
537 return DispatchKeyboardEventInternal(aMessage
, aKeyboardEvent
, aStatus
,
541 bool TextEventDispatcher::DispatchKeyboardEventInternal(
542 EventMessage aMessage
, const WidgetKeyboardEvent
& aKeyboardEvent
,
543 nsEventStatus
& aStatus
, void* aData
, uint32_t aIndexOfKeypress
,
544 bool aNeedsCallback
) {
545 // Note that this method is also used for dispatching key events on a plugin
546 // because key events on a plugin should be dispatched same as normal key
547 // events. Then, only some handlers which need to intercept key events
548 // before the focused plugin (e.g., reserved shortcut key handlers) can
549 // consume the events.
551 aMessage
== eKeyDown
|| aMessage
== eKeyUp
|| aMessage
== eKeyPress
,
552 "Invalid aMessage value");
553 nsresult rv
= GetState();
554 if (NS_WARN_IF(NS_FAILED(rv
))) {
558 // If the key shouldn't cause keypress events, don't this patch them.
559 if (aMessage
== eKeyPress
&& !aKeyboardEvent
.ShouldCauseKeypressEvents()) {
563 // Basically, key events shouldn't be dispatched during composition.
564 // Note that plugin process has different IME context. Therefore, we don't
565 // need to check our composition state when the key event is fired on a
568 // However, if we need to behave like other browsers, we need the keydown
569 // and keyup events. Note that this behavior is also allowed by D3E spec.
570 // FYI: keypress events must not be fired during composition.
571 if (!StaticPrefs::dom_keyboardevent_dispatch_during_composition() ||
572 aMessage
== eKeyPress
) {
575 // XXX If there was mOnlyContentDispatch for this case, it might be useful
576 // because our chrome doesn't assume that key events are fired during
580 WidgetKeyboardEvent
keyEvent(true, aMessage
, mWidget
);
582 keyEvent
.AssignKeyEventData(aKeyboardEvent
, false);
583 // Command arrays are not duplicated by AssignKeyEventData() due to
584 // both performance and footprint reasons. So, when TextInputProcessor
585 // emulates real text input or synthesizing keyboard events for tests,
586 // the arrays may be initialized all commands already. If so, we need to
587 // duplicate the arrays here, but we should do this only when we're
588 // dispatching eKeyPress events because BrowserParent::SendRealKeyEvent()
589 // does this only for eKeyPress event. Note that this is not required if
590 // we're in the main process because in the parent process, the edit commands
591 // will be initialized by `ExecuteEditCommands()` (when the event is handled
592 // by editor event listener) or `InitAllEditCommands()` (when the event is
593 // set to a content process). We should test whether these pathes work or
595 if (XRE_IsContentProcess() && keyEvent
.mIsSynthesizedByTIP
) {
596 if (aMessage
== eKeyPress
) {
597 keyEvent
.AssignCommands(aKeyboardEvent
);
599 // Prevent retriving native edit commands if we're in a content process
600 // because only `eKeyPress` events coming from the main process have
601 // edit commands (See `BrowserParent::SendRealKeyEvent`). And also
602 // retriving edit commands from a content process requires synchonous
603 // IPC and that makes running tests slower. Therefore, we should mark
604 // the `eKeyPress` event does not need to retrieve edit commands anymore.
605 keyEvent
.PreventNativeKeyBindings();
609 if (aStatus
== nsEventStatus_eConsumeNoDefault
) {
610 // If the key event should be dispatched as consumed event, marking it here.
611 // This is useful to prevent double action. This is intended to the system
612 // has already consumed the event but we need to dispatch the event for
613 // compatibility with older version and other browsers. So, we should not
614 // stop cross process forwarding of them.
615 keyEvent
.PreventDefaultBeforeDispatch(CrossProcessForwarding::eAllow
);
618 // Corrects each member for the specific key event type.
619 if (keyEvent
.mKeyNameIndex
!= KEY_NAME_INDEX_USE_STRING
) {
620 MOZ_ASSERT(!aIndexOfKeypress
,
621 "aIndexOfKeypress must be 0 for non-printable key");
622 // If the keyboard event isn't caused by printable key, its charCode should
624 keyEvent
.SetCharCode(0);
626 MOZ_DIAGNOSTIC_ASSERT_IF(aMessage
== eKeyDown
|| aMessage
== eKeyUp
,
628 MOZ_DIAGNOSTIC_ASSERT_IF(
629 aMessage
== eKeyPress
,
630 aIndexOfKeypress
< std::max
<size_t>(keyEvent
.mKeyValue
.Length(), 1));
632 keyEvent
.mKeyValue
.IsEmpty() ? 0 : keyEvent
.mKeyValue
[aIndexOfKeypress
];
633 keyEvent
.SetCharCode(static_cast<uint32_t>(ch
));
634 if (aMessage
== eKeyPress
) {
635 // keyCode of eKeyPress events of printable keys should be always 0.
636 keyEvent
.mKeyCode
= 0;
637 // eKeyPress events are dispatched for every character.
638 // So, each key value of eKeyPress events should be a character.
640 keyEvent
.mKeyValue
.Assign(ch
);
642 keyEvent
.mKeyValue
.Truncate();
646 if (aMessage
== eKeyUp
) {
647 // mIsRepeat of keyup event must be false.
648 keyEvent
.mIsRepeat
= false;
650 // mIsComposing should be initialized later.
651 keyEvent
.mIsComposing
= false;
652 if (mInputTransactionType
== eNativeInputTransaction
) {
653 // Copy mNativeKeyEvent here because for safety for other users of
654 // AssignKeyEventData(), it doesn't copy this.
655 keyEvent
.mNativeKeyEvent
= aKeyboardEvent
.mNativeKeyEvent
;
657 // If it's not a keyboard event for native key event, we should ensure that
658 // mNativeKeyEvent is null.
659 keyEvent
.mNativeKeyEvent
= nullptr;
661 // TODO: Manage mUniqueId here.
663 // Request the alternative char codes for the key event.
664 // eKeyDown also needs alternative char codes because nsXBLWindowKeyHandler
665 // needs to check if a following keypress event is reserved by chrome for
666 // stopping propagation of its preceding keydown event.
667 keyEvent
.mAlternativeCharCodes
.Clear();
668 if ((aMessage
== eKeyDown
|| aMessage
== eKeyPress
) &&
669 (aNeedsCallback
|| keyEvent
.IsControl() || keyEvent
.IsAlt() ||
670 keyEvent
.IsMeta() || keyEvent
.IsOS())) {
671 nsCOMPtr
<TextEventDispatcherListener
> listener
=
672 do_QueryReferent(mListener
);
674 DebugOnly
<WidgetKeyboardEvent
> original(keyEvent
);
675 listener
->WillDispatchKeyboardEvent(this, keyEvent
, aIndexOfKeypress
,
677 MOZ_ASSERT(keyEvent
.mMessage
==
678 static_cast<WidgetKeyboardEvent
&>(original
).mMessage
);
679 MOZ_ASSERT(keyEvent
.mKeyCode
==
680 static_cast<WidgetKeyboardEvent
&>(original
).mKeyCode
);
681 MOZ_ASSERT(keyEvent
.mLocation
==
682 static_cast<WidgetKeyboardEvent
&>(original
).mLocation
);
683 MOZ_ASSERT(keyEvent
.mIsRepeat
==
684 static_cast<WidgetKeyboardEvent
&>(original
).mIsRepeat
);
685 MOZ_ASSERT(keyEvent
.mIsComposing
==
686 static_cast<WidgetKeyboardEvent
&>(original
).mIsComposing
);
687 MOZ_ASSERT(keyEvent
.mKeyNameIndex
==
688 static_cast<WidgetKeyboardEvent
&>(original
).mKeyNameIndex
);
689 MOZ_ASSERT(keyEvent
.mCodeNameIndex
==
690 static_cast<WidgetKeyboardEvent
&>(original
).mCodeNameIndex
);
691 MOZ_ASSERT(keyEvent
.mKeyValue
==
692 static_cast<WidgetKeyboardEvent
&>(original
).mKeyValue
);
693 MOZ_ASSERT(keyEvent
.mCodeValue
==
694 static_cast<WidgetKeyboardEvent
&>(original
).mCodeValue
);
699 dom_keyboardevent_keypress_dispatch_non_printable_keys_only_system_group_in_content() &&
700 keyEvent
.mMessage
== eKeyPress
&&
701 !keyEvent
.ShouldKeyPressEventBeFiredOnContent()) {
702 // Note that even if we set it to true, this may be overwritten by
703 // PresShell::DispatchEventToDOM().
704 keyEvent
.mFlags
.mOnlySystemGroupDispatchInContent
= true;
707 // If an editable element has focus and we're in the parent process, we should
708 // retrieve native key bindings right now because even if it matches with a
709 // reserved shortcut key, it should be handled by the editor.
710 if (XRE_IsParentProcess() && mHasFocus
&&
711 (aMessage
== eKeyDown
|| aMessage
== eKeyPress
)) {
712 keyEvent
.InitAllEditCommands(mWritingMode
);
715 DispatchInputEvent(mWidget
, keyEvent
, aStatus
);
719 bool TextEventDispatcher::MaybeDispatchKeypressEvents(
720 const WidgetKeyboardEvent
& aKeyboardEvent
, nsEventStatus
& aStatus
,
721 void* aData
, bool aNeedsCallback
) {
722 // If the key event was consumed, keypress event shouldn't be fired.
723 if (aStatus
== nsEventStatus_eConsumeNoDefault
) {
727 // If the key shouldn't cause keypress events, don't fire them.
728 if (!aKeyboardEvent
.ShouldCauseKeypressEvents()) {
732 // If the key isn't a printable key or just inputting one character or
733 // no character, we should dispatch only one keypress. Otherwise, i.e.,
734 // if the key is a printable key and inputs multiple characters, keypress
735 // event should be dispatched the count of inputting characters times.
736 size_t keypressCount
=
737 aKeyboardEvent
.mKeyNameIndex
!= KEY_NAME_INDEX_USE_STRING
739 : std::max(static_cast<nsAString::size_type
>(1),
740 aKeyboardEvent
.mKeyValue
.Length());
741 bool isDispatched
= false;
742 bool consumed
= false;
743 for (size_t i
= 0; i
< keypressCount
; i
++) {
744 aStatus
= nsEventStatus_eIgnore
;
745 if (!DispatchKeyboardEventInternal(eKeyPress
, aKeyboardEvent
, aStatus
,
746 aData
, i
, aNeedsCallback
)) {
747 // The widget must have been gone.
752 consumed
= (aStatus
== nsEventStatus_eConsumeNoDefault
);
756 // If one of the keypress event was consumed, return ConsumeNoDefault.
758 aStatus
= nsEventStatus_eConsumeNoDefault
;
764 /******************************************************************************
765 * TextEventDispatcher::PendingComposition
766 *****************************************************************************/
768 TextEventDispatcher::PendingComposition::PendingComposition() { Clear(); }
770 void TextEventDispatcher::PendingComposition::Clear() {
773 mCaret
.mRangeType
= TextRangeType::eUninitialized
;
774 mReplacedNativeLineBreakers
= false;
777 void TextEventDispatcher::PendingComposition::EnsureClauseArray() {
781 mClauses
= new TextRangeArray();
784 nsresult
TextEventDispatcher::PendingComposition::SetString(
785 const nsAString
& aString
) {
786 MOZ_ASSERT(!mReplacedNativeLineBreakers
);
791 nsresult
TextEventDispatcher::PendingComposition::AppendClause(
792 uint32_t aLength
, TextRangeType aTextRangeType
) {
793 MOZ_ASSERT(!mReplacedNativeLineBreakers
);
795 if (NS_WARN_IF(!aLength
)) {
796 return NS_ERROR_INVALID_ARG
;
799 switch (aTextRangeType
) {
800 case TextRangeType::eRawClause
:
801 case TextRangeType::eSelectedRawClause
:
802 case TextRangeType::eConvertedClause
:
803 case TextRangeType::eSelectedClause
: {
806 textRange
.mStartOffset
=
807 mClauses
->IsEmpty() ? 0 : mClauses
->LastElement().mEndOffset
;
808 textRange
.mEndOffset
= textRange
.mStartOffset
+ aLength
;
809 textRange
.mRangeType
= aTextRangeType
;
810 mClauses
->AppendElement(textRange
);
814 return NS_ERROR_INVALID_ARG
;
818 nsresult
TextEventDispatcher::PendingComposition::SetCaret(uint32_t aOffset
,
820 MOZ_ASSERT(!mReplacedNativeLineBreakers
);
822 mCaret
.mStartOffset
= aOffset
;
823 mCaret
.mEndOffset
= mCaret
.mStartOffset
+ aLength
;
824 mCaret
.mRangeType
= TextRangeType::eCaret
;
828 nsresult
TextEventDispatcher::PendingComposition::Set(
829 const nsAString
& aString
, const TextRangeArray
* aRanges
) {
832 nsresult rv
= SetString(aString
);
833 if (NS_WARN_IF(NS_FAILED(rv
))) {
837 if (!aRanges
|| aRanges
->IsEmpty()) {
838 // Create dummy range if mString isn't empty.
839 if (!mString
.IsEmpty()) {
840 rv
= AppendClause(mString
.Length(), TextRangeType::eRawClause
);
841 if (NS_WARN_IF(NS_FAILED(rv
))) {
844 ReplaceNativeLineBreakers();
849 // Adjust offsets in the ranges for XP linefeed character (only \n).
850 for (uint32_t i
= 0; i
< aRanges
->Length(); ++i
) {
851 TextRange range
= aRanges
->ElementAt(i
);
852 if (range
.mRangeType
== TextRangeType::eCaret
) {
856 mClauses
->AppendElement(range
);
859 ReplaceNativeLineBreakers();
863 void TextEventDispatcher::PendingComposition::ReplaceNativeLineBreakers() {
864 mReplacedNativeLineBreakers
= true;
866 // If the composition string is empty, we don't need to do anything.
867 if (mString
.IsEmpty()) {
871 nsAutoString
nativeString(mString
);
872 // Don't expose CRLF nor CR to web contents, instead, use LF.
873 mString
.ReplaceSubstring(u
"\r\n"_ns
, u
"\n"_ns
);
874 mString
.ReplaceSubstring(u
"\r"_ns
, u
"\n"_ns
);
876 // If the length isn't changed, we don't need to adjust any offset and length
877 // of mClauses nor mCaret.
878 if (nativeString
.Length() == mString
.Length()) {
883 for (TextRange
& clause
: *mClauses
) {
884 AdjustRange(clause
, nativeString
);
887 if (mCaret
.mRangeType
== TextRangeType::eCaret
) {
888 AdjustRange(mCaret
, nativeString
);
893 void TextEventDispatcher::PendingComposition::AdjustRange(
894 TextRange
& aRange
, const nsAString
& aNativeString
) {
895 TextRange nativeRange
= aRange
;
896 // XXX Following code wastes runtime cost because this causes computing
897 // mStartOffset for each clause from the start of composition string.
898 // If we'd make TextRange have only its length, we don't need to do
899 // this. However, this must not be so serious problem because
900 // composition string is usually short and separated as a few clauses.
901 if (nativeRange
.mStartOffset
> 0) {
902 nsAutoString
preText(Substring(aNativeString
, 0, nativeRange
.mStartOffset
));
903 preText
.ReplaceSubstring(u
"\r\n"_ns
, u
"\n"_ns
);
904 aRange
.mStartOffset
= preText
.Length();
906 if (nativeRange
.Length() == 0) {
907 aRange
.mEndOffset
= aRange
.mStartOffset
;
909 nsAutoString
clause(Substring(aNativeString
, nativeRange
.mStartOffset
,
910 nativeRange
.Length()));
911 clause
.ReplaceSubstring(u
"\r\n"_ns
, u
"\n"_ns
);
912 aRange
.mEndOffset
= aRange
.mStartOffset
+ clause
.Length();
916 nsresult
TextEventDispatcher::PendingComposition::Flush(
917 TextEventDispatcher
* aDispatcher
, nsEventStatus
& aStatus
,
918 const WidgetEventTime
* aEventTime
) {
919 aStatus
= nsEventStatus_eIgnore
;
921 nsresult rv
= aDispatcher
->GetState();
922 if (NS_WARN_IF(NS_FAILED(rv
))) {
926 if (mClauses
&& !mClauses
->IsEmpty() &&
927 mClauses
->LastElement().mEndOffset
!= mString
.Length()) {
929 "Sum of length of the all clauses must be same as the string "
932 return NS_ERROR_ILLEGAL_VALUE
;
934 if (mCaret
.mRangeType
== TextRangeType::eCaret
) {
935 if (mCaret
.mEndOffset
> mString
.Length()) {
936 NS_WARNING("Caret position is out of the composition string");
938 return NS_ERROR_ILLEGAL_VALUE
;
941 mClauses
->AppendElement(mCaret
);
944 // If the composition string is set without Set(), we need to replace native
945 // line breakers in the composition string with XP line breaker.
946 if (!mReplacedNativeLineBreakers
) {
947 ReplaceNativeLineBreakers();
950 RefPtr
<TextEventDispatcher
> kungFuDeathGrip(aDispatcher
);
951 nsCOMPtr
<nsIWidget
> widget(aDispatcher
->mWidget
);
952 WidgetCompositionEvent
compChangeEvent(true, eCompositionChange
, widget
);
953 aDispatcher
->InitEvent(compChangeEvent
);
955 compChangeEvent
.AssignEventTime(*aEventTime
);
957 compChangeEvent
.mData
= mString
;
958 // If mString comes from TextInputProcessor, it may be void, but editor
959 // requires non-void string even when it's empty.
960 compChangeEvent
.mData
.SetIsVoid(false);
962 MOZ_ASSERT(!mClauses
->IsEmpty(),
963 "mClauses must be non-empty array when it's not nullptr");
964 compChangeEvent
.mRanges
= mClauses
;
967 // While this method dispatches a composition event, some other event handler
968 // cause more clauses to be added. So, we should clear pending composition
969 // before dispatching the event.
972 rv
= aDispatcher
->StartCompositionAutomaticallyIfNecessary(aStatus
,
974 if (NS_WARN_IF(NS_FAILED(rv
))) {
977 if (aStatus
== nsEventStatus_eConsumeNoDefault
) {
980 rv
= aDispatcher
->DispatchEvent(widget
, compChangeEvent
, aStatus
);
981 if (NS_WARN_IF(NS_FAILED(rv
))) {
988 } // namespace widget
989 } // namespace mozilla