Bug 1843831 [wpt PR 41060] - LoAF: Expose script start position instead of column...
[gecko.git] / widget / TextEventDispatcher.cpp
blob6ecd5d355cf1ff15b66af8ddaa4a24974203d9b8
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"
8 #include "IMEData.h"
9 #include "PuppetWidget.h"
10 #include "TextEvents.h"
12 #include "mozilla/Preferences.h"
13 #include "mozilla/StaticPrefs_dom.h"
14 #include "nsIFrame.h"
15 #include "nsIWidget.h"
16 #include "nsPIDOMWindow.h"
17 #include "nsView.h"
19 namespace mozilla {
20 namespace widget {
22 /******************************************************************************
23 * TextEventDispatcher
24 *****************************************************************************/
25 TextEventDispatcher::TextEventDispatcher(nsIWidget* aWidget)
26 : mWidget(aWidget),
27 mDispatchingEvent(0),
28 mInputTransactionType(eNoInputTransaction),
29 mIsComposing(false),
30 mIsHandlingComposition(false),
31 mHasFocus(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);
68 if (listener) {
69 if (listener == aListener && mInputTransactionType == aType) {
70 UpdateNotificationRequests();
71 return NS_OK;
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();
87 return NS_OK;
90 nsresult TextEventDispatcher::BeginInputTransactionFor(
91 const WidgetGUIEvent* aEvent, PuppetWidget* aPuppetWidget) {
92 MOZ_ASSERT(XRE_IsContentProcess());
93 MOZ_ASSERT(!IsDispatchingEvent());
95 switch (aEvent->mMessage) {
96 case eKeyDown:
97 case eKeyPress:
98 case eKeyUp:
99 MOZ_ASSERT(aEvent->mClass == eKeyboardEventClass);
100 break;
101 case eCompositionStart:
102 case eCompositionChange:
103 case eCompositionCommit:
104 case eCompositionCommitAsIs:
105 MOZ_ASSERT(aEvent->mClass == eCompositionEventClass);
106 break;
107 default:
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) {
116 return NS_OK;
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))) {
125 return rv;
127 } else {
128 nsresult rv = BeginNativeInputTransaction();
129 if (NS_WARN_IF(NS_FAILED(rv))) {
130 return 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) {
139 case eKeyDown:
140 case eKeyPress:
141 case eKeyUp:
142 return NS_OK;
143 case eCompositionStart:
144 MOZ_ASSERT(!mIsComposing);
145 mIsComposing = mIsHandlingComposition = true;
146 return NS_OK;
147 case eCompositionChange:
148 MOZ_ASSERT(mIsComposing);
149 MOZ_ASSERT(mIsHandlingComposition);
150 mIsComposing = mIsHandlingComposition = true;
151 return NS_OK;
152 case eCompositionCommit:
153 case eCompositionCommitAsIs:
154 MOZ_ASSERT(mIsComposing);
155 MOZ_ASSERT(mIsHandlingComposition);
156 mIsComposing = false;
157 mIsHandlingComposition = true;
158 return NS_OK;
159 default:
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())) {
167 return;
170 mInputTransactionType = eNoInputTransaction;
172 nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
173 if (NS_WARN_IF(!listener)) {
174 return;
177 if (NS_WARN_IF(listener != aListener)) {
178 return;
181 mListener = nullptr;
182 listener->OnRemovedFrom(this);
183 UpdateNotificationRequests();
186 void TextEventDispatcher::OnDestroyWidget() {
187 mWidget = nullptr;
188 mHasFocus = false;
189 ClearNotificationRequests();
190 mPendingComposition.Clear();
191 nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
192 mListener = nullptr;
193 mWritingMode.reset();
194 mInputTransactionType = eNoInputTransaction;
195 if (listener) {
196 listener->OnRemovedFrom(this);
200 nsresult TextEventDispatcher::GetState() const {
201 nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
202 if (!listener) {
203 return NS_ERROR_NOT_INITIALIZED;
205 if (!mWidget || mWidget->Destroyed()) {
206 return NS_ERROR_NOT_AVAILABLE;
208 return NS_OK;
211 void TextEventDispatcher::InitEvent(WidgetGUIEvent& aEvent) const {
212 aEvent.mRefPoint = LayoutDeviceIntPoint(0, 0);
213 aEvent.mFlags.mIsSynthesizedForTests = IsForTests();
214 if (aEvent.mClass != eCompositionEventClass) {
215 return;
217 void* pseudoIMEContext = GetPseudoIMEContext();
218 if (pseudoIMEContext) {
219 aEvent.AsCompositionEvent()->mNativeIMEContext.InitWithRawNativeIMEContext(
220 pseudoIMEContext);
222 #ifdef DEBUG
223 else {
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()
233 const {
234 if (mHasFocus || mWritingMode.isSome()) {
235 return mWritingMode;
238 if (NS_WARN_IF(!mWidget)) {
239 return Nothing();
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()) {
249 return Nothing();
252 WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
253 mWidget);
254 nsEventStatus status = nsEventStatus_eIgnore;
255 const_cast<TextEventDispatcher*>(this)->DispatchEvent(
256 mWidget, querySelectedTextEvent, status);
257 if (!querySelectedTextEvent.FoundSelection()) {
258 return Nothing();
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);
271 mDispatchingEvent++;
272 nsresult rv = widget->DispatchEvent(&aEvent, aStatus);
273 mDispatchingEvent--;
274 return rv;
277 nsresult TextEventDispatcher::DispatchInputEvent(nsIWidget* aWidget,
278 WidgetInputEvent& aEvent,
279 nsEventStatus& aStatus) {
280 RefPtr<TextEventDispatcher> kungFuDeathGrip(this);
281 nsCOMPtr<nsIWidget> widget(aWidget);
282 mDispatchingEvent++;
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.
288 nsresult rv = NS_OK;
289 if (ShouldSendInputEventToAPZ()) {
290 aStatus = widget->DispatchInputEvent(&aEvent).mContentStatus;
291 } else {
292 rv = widget->DispatchEvent(&aEvent, aStatus);
295 mDispatchingEvent--;
296 return rv;
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))) {
305 return 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,
316 mWidget);
317 InitEvent(compositionStartEvent);
318 if (aEventTime) {
319 compositionStartEvent.AssignEventTime(*aEventTime);
321 rv = DispatchEvent(mWidget, compositionStartEvent, aStatus);
322 if (NS_WARN_IF(NS_FAILED(rv))) {
323 return rv;
326 return NS_OK;
329 nsresult TextEventDispatcher::StartCompositionAutomaticallyIfNecessary(
330 nsEventStatus& aStatus, const WidgetEventTime* aEventTime) {
331 if (IsComposing()) {
332 return NS_OK;
335 nsresult rv = StartComposition(aStatus, aEventTime);
336 if (NS_WARN_IF(NS_FAILED(rv))) {
337 return rv;
340 // If started composition has already been committed, we shouldn't dispatch
341 // the compositionchange event.
342 if (!IsComposing()) {
343 aStatus = nsEventStatus_eConsumeNoDefault;
344 return NS_OK;
347 // Note that the widget might be destroyed during a call of
348 // StartComposition(). In such case, we shouldn't keep dispatching next
349 // event.
350 rv = GetState();
351 if (NS_FAILED(rv)) {
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;
359 return NS_OK;
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))) {
369 return 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
374 // string.
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))) {
383 return rv;
385 if (aStatus == nsEventStatus_eConsumeNoDefault) {
386 return NS_OK;
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);
399 if (aEventTime) {
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))) {
413 return rv;
416 return NS_OK;
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();
426 break;
428 case NOTIFY_IME_OF_BLUR:
429 mHasFocus = false;
430 mWritingMode.reset();
431 ClearNotificationRequests();
432 break;
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;
442 break;
443 case NOTIFY_IME_OF_SELECTION_CHANGE:
444 if (mHasFocus && aIMENotification.mSelectionChangeData.HasRange()) {
445 mWritingMode =
446 Some(aIMENotification.mSelectionChangeData.GetWritingMode());
448 break;
449 default:
450 break;
453 // First, send the notification to current input transaction's listener.
454 nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
455 if (listener) {
456 rv = listener->NotifyIME(this, aIMENotification);
459 if (!mWidget) {
460 return rv;
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
472 // focus move).
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.
480 break;
481 default: {
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) {
490 rv = rv2;
492 break;
497 if (aIMENotification.mMessage == NOTIFY_IME_OF_FOCUS) {
498 mHasFocus = true;
499 UpdateNotificationRequests();
502 return rv;
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) {
514 return;
517 // If there is a listener, its requests are necessary.
518 nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
519 if (listener) {
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,
538 aData);
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.
550 MOZ_ASSERT(
551 aMessage == eKeyDown || aMessage == eKeyUp || aMessage == eKeyPress,
552 "Invalid aMessage value");
553 nsresult rv = GetState();
554 if (NS_WARN_IF(NS_FAILED(rv))) {
555 return false;
558 // If the key shouldn't cause keypress events, don't this patch them.
559 if (aMessage == eKeyPress && !aKeyboardEvent.ShouldCauseKeypressEvents()) {
560 return false;
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
566 // plugin.
567 if (IsComposing()) {
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) {
573 return false;
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
577 // composition.
580 WidgetKeyboardEvent keyEvent(true, aMessage, mWidget);
581 InitEvent(keyEvent);
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
594 // not too.
595 if (XRE_IsContentProcess() && keyEvent.mIsSynthesizedByTIP) {
596 if (aMessage == eKeyPress) {
597 keyEvent.AssignCommands(aKeyboardEvent);
598 } else {
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
623 // be 0.
624 keyEvent.SetCharCode(0);
625 } else {
626 MOZ_DIAGNOSTIC_ASSERT_IF(aMessage == eKeyDown || aMessage == eKeyUp,
627 !aIndexOfKeypress);
628 MOZ_DIAGNOSTIC_ASSERT_IF(
629 aMessage == eKeyPress,
630 aIndexOfKeypress < std::max<size_t>(keyEvent.mKeyValue.Length(), 1));
631 char16_t ch =
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.
639 if (ch) {
640 keyEvent.mKeyValue.Assign(ch);
641 } else {
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;
656 } else {
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);
673 if (listener) {
674 DebugOnly<WidgetKeyboardEvent> original(keyEvent);
675 listener->WillDispatchKeyboardEvent(this, keyEvent, aIndexOfKeypress,
676 aData);
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);
698 if (StaticPrefs::
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);
716 return true;
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) {
724 return false;
727 // If the key shouldn't cause keypress events, don't fire them.
728 if (!aKeyboardEvent.ShouldCauseKeypressEvents()) {
729 return false;
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.
748 break;
750 isDispatched = true;
751 if (!consumed) {
752 consumed = (aStatus == nsEventStatus_eConsumeNoDefault);
756 // If one of the keypress event was consumed, return ConsumeNoDefault.
757 if (consumed) {
758 aStatus = nsEventStatus_eConsumeNoDefault;
761 return isDispatched;
764 /******************************************************************************
765 * TextEventDispatcher::PendingComposition
766 *****************************************************************************/
768 TextEventDispatcher::PendingComposition::PendingComposition() { Clear(); }
770 void TextEventDispatcher::PendingComposition::Clear() {
771 mString.Truncate();
772 mClauses = nullptr;
773 mCaret.mRangeType = TextRangeType::eUninitialized;
774 mReplacedNativeLineBreakers = false;
777 void TextEventDispatcher::PendingComposition::EnsureClauseArray() {
778 if (mClauses) {
779 return;
781 mClauses = new TextRangeArray();
784 nsresult TextEventDispatcher::PendingComposition::SetString(
785 const nsAString& aString) {
786 MOZ_ASSERT(!mReplacedNativeLineBreakers);
787 mString = aString;
788 return NS_OK;
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: {
804 EnsureClauseArray();
805 TextRange textRange;
806 textRange.mStartOffset =
807 mClauses->IsEmpty() ? 0 : mClauses->LastElement().mEndOffset;
808 textRange.mEndOffset = textRange.mStartOffset + aLength;
809 textRange.mRangeType = aTextRangeType;
810 mClauses->AppendElement(textRange);
811 return NS_OK;
813 default:
814 return NS_ERROR_INVALID_ARG;
818 nsresult TextEventDispatcher::PendingComposition::SetCaret(uint32_t aOffset,
819 uint32_t aLength) {
820 MOZ_ASSERT(!mReplacedNativeLineBreakers);
822 mCaret.mStartOffset = aOffset;
823 mCaret.mEndOffset = mCaret.mStartOffset + aLength;
824 mCaret.mRangeType = TextRangeType::eCaret;
825 return NS_OK;
828 nsresult TextEventDispatcher::PendingComposition::Set(
829 const nsAString& aString, const TextRangeArray* aRanges) {
830 Clear();
832 nsresult rv = SetString(aString);
833 if (NS_WARN_IF(NS_FAILED(rv))) {
834 return 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))) {
842 return rv;
844 ReplaceNativeLineBreakers();
846 return NS_OK;
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) {
853 mCaret = range;
854 } else {
855 EnsureClauseArray();
856 mClauses->AppendElement(range);
859 ReplaceNativeLineBreakers();
860 return NS_OK;
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()) {
868 return;
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()) {
879 return;
882 if (mClauses) {
883 for (TextRange& clause : *mClauses) {
884 AdjustRange(clause, nativeString);
887 if (mCaret.mRangeType == TextRangeType::eCaret) {
888 AdjustRange(mCaret, nativeString);
892 // static
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;
908 } else {
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))) {
923 return rv;
926 if (mClauses && !mClauses->IsEmpty() &&
927 mClauses->LastElement().mEndOffset != mString.Length()) {
928 NS_WARNING(
929 "Sum of length of the all clauses must be same as the string "
930 "length");
931 Clear();
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");
937 Clear();
938 return NS_ERROR_ILLEGAL_VALUE;
940 EnsureClauseArray();
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);
954 if (aEventTime) {
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);
961 if (mClauses) {
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.
970 Clear();
972 rv = aDispatcher->StartCompositionAutomaticallyIfNecessary(aStatus,
973 aEventTime);
974 if (NS_WARN_IF(NS_FAILED(rv))) {
975 return rv;
977 if (aStatus == nsEventStatus_eConsumeNoDefault) {
978 return NS_OK;
980 rv = aDispatcher->DispatchEvent(widget, compChangeEvent, aStatus);
981 if (NS_WARN_IF(NS_FAILED(rv))) {
982 return rv;
985 return NS_OK;
988 } // namespace widget
989 } // namespace mozilla