Bug 1882714 [wpt PR 44850] - Update wpt metadata, a=testonly
[gecko.git] / widget / TextEventDispatcher.cpp
blobcf414a23e39844772ade1fdd5b7de2eb0971137b
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 "nsCharTraits.h"
15 #include "nsIFrame.h"
16 #include "nsIWidget.h"
17 #include "nsPIDOMWindow.h"
18 #include "nsView.h"
20 namespace mozilla {
21 namespace widget {
23 /******************************************************************************
24 * TextEventDispatcher
25 *****************************************************************************/
26 TextEventDispatcher::TextEventDispatcher(nsIWidget* aWidget)
27 : mWidget(aWidget),
28 mDispatchingEvent(0),
29 mInputTransactionType(eNoInputTransaction),
30 mIsComposing(false),
31 mIsHandlingComposition(false),
32 mHasFocus(false) {
33 MOZ_RELEASE_ASSERT(mWidget, "aWidget must not be nullptr");
35 ClearNotificationRequests();
38 nsresult TextEventDispatcher::BeginInputTransaction(
39 TextEventDispatcherListener* aListener) {
40 return BeginInputTransactionInternal(aListener,
41 eSameProcessSyncInputTransaction);
44 nsresult TextEventDispatcher::BeginTestInputTransaction(
45 TextEventDispatcherListener* aListener, bool aIsAPZAware) {
46 return BeginInputTransactionInternal(
47 aListener, aIsAPZAware ? eAsyncTestInputTransaction
48 : eSameProcessSyncTestInputTransaction);
51 nsresult TextEventDispatcher::BeginNativeInputTransaction() {
52 if (NS_WARN_IF(!mWidget)) {
53 return NS_ERROR_FAILURE;
55 RefPtr<TextEventDispatcherListener> listener =
56 mWidget->GetNativeTextEventDispatcherListener();
57 if (NS_WARN_IF(!listener)) {
58 return NS_ERROR_FAILURE;
60 return BeginInputTransactionInternal(listener, eNativeInputTransaction);
63 nsresult TextEventDispatcher::BeginInputTransactionInternal(
64 TextEventDispatcherListener* aListener, InputTransactionType aType) {
65 if (NS_WARN_IF(!aListener)) {
66 return NS_ERROR_INVALID_ARG;
68 nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
69 if (listener) {
70 if (listener == aListener && mInputTransactionType == aType) {
71 UpdateNotificationRequests();
72 return NS_OK;
74 // If this has composition or is dispatching an event, any other listener
75 // can steal ownership. Especially, if the latter case is allowed,
76 // nobody cannot begin input transaction with this if a modal dialog is
77 // opened during dispatching an event.
78 if (IsComposing() || IsDispatchingEvent()) {
79 return NS_ERROR_ALREADY_INITIALIZED;
82 mListener = do_GetWeakReference(aListener);
83 mInputTransactionType = aType;
84 if (listener && listener != aListener) {
85 listener->OnRemovedFrom(this);
87 UpdateNotificationRequests();
88 return NS_OK;
91 nsresult TextEventDispatcher::BeginInputTransactionFor(
92 const WidgetGUIEvent* aEvent, PuppetWidget* aPuppetWidget) {
93 MOZ_ASSERT(XRE_IsContentProcess());
94 MOZ_ASSERT(!IsDispatchingEvent());
96 switch (aEvent->mMessage) {
97 case eKeyDown:
98 case eKeyPress:
99 case eKeyUp:
100 MOZ_ASSERT(aEvent->mClass == eKeyboardEventClass);
101 break;
102 case eCompositionStart:
103 case eCompositionChange:
104 case eCompositionCommit:
105 case eCompositionCommitAsIs:
106 MOZ_ASSERT(aEvent->mClass == eCompositionEventClass);
107 break;
108 default:
109 return NS_ERROR_INVALID_ARG;
112 if (aEvent->mFlags.mIsSynthesizedForTests) {
113 // If the event is for an automated test and this instance dispatched
114 // an event to the parent process, we can assume that this is already
115 // initialized properly.
116 if (mInputTransactionType == eAsyncTestInputTransaction) {
117 return NS_OK;
119 // Even if the event coming from the parent process is synthesized for
120 // tests, this process should treat it as "sync" test here because
121 // it won't be go back to the parent process.
122 nsresult rv = BeginInputTransactionInternal(
123 static_cast<TextEventDispatcherListener*>(aPuppetWidget),
124 eSameProcessSyncTestInputTransaction);
125 if (NS_WARN_IF(NS_FAILED(rv))) {
126 return rv;
128 } else {
129 nsresult rv = BeginNativeInputTransaction();
130 if (NS_WARN_IF(NS_FAILED(rv))) {
131 return rv;
135 // Emulate modifying members which indicate the state of composition.
136 // If we need to manage more states and/or more complexly, we should create
137 // internal methods which are called by both here and each event dispatcher
138 // method of this class.
139 switch (aEvent->mMessage) {
140 case eKeyDown:
141 case eKeyPress:
142 case eKeyUp:
143 return NS_OK;
144 case eCompositionStart:
145 MOZ_ASSERT(!mIsComposing);
146 mIsComposing = mIsHandlingComposition = true;
147 return NS_OK;
148 case eCompositionChange:
149 MOZ_ASSERT(mIsComposing);
150 MOZ_ASSERT(mIsHandlingComposition);
151 mIsComposing = mIsHandlingComposition = true;
152 return NS_OK;
153 case eCompositionCommit:
154 case eCompositionCommitAsIs:
155 MOZ_ASSERT(mIsComposing);
156 MOZ_ASSERT(mIsHandlingComposition);
157 mIsComposing = false;
158 mIsHandlingComposition = true;
159 return NS_OK;
160 default:
161 MOZ_ASSERT_UNREACHABLE("You forgot to handle the event");
162 return NS_ERROR_UNEXPECTED;
165 void TextEventDispatcher::EndInputTransaction(
166 TextEventDispatcherListener* aListener) {
167 if (NS_WARN_IF(IsComposing()) || NS_WARN_IF(IsDispatchingEvent())) {
168 return;
171 mInputTransactionType = eNoInputTransaction;
173 nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
174 if (NS_WARN_IF(!listener)) {
175 return;
178 if (NS_WARN_IF(listener != aListener)) {
179 return;
182 mListener = nullptr;
183 listener->OnRemovedFrom(this);
184 UpdateNotificationRequests();
187 void TextEventDispatcher::OnDestroyWidget() {
188 mWidget = nullptr;
189 mHasFocus = false;
190 ClearNotificationRequests();
191 mPendingComposition.Clear();
192 nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
193 mListener = nullptr;
194 mWritingMode.reset();
195 mInputTransactionType = eNoInputTransaction;
196 if (listener) {
197 listener->OnRemovedFrom(this);
201 nsresult TextEventDispatcher::GetState() const {
202 nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
203 if (!listener) {
204 return NS_ERROR_NOT_INITIALIZED;
206 if (!mWidget || mWidget->Destroyed()) {
207 return NS_ERROR_NOT_AVAILABLE;
209 return NS_OK;
212 void TextEventDispatcher::InitEvent(WidgetGUIEvent& aEvent) const {
213 aEvent.mRefPoint = LayoutDeviceIntPoint(0, 0);
214 aEvent.mFlags.mIsSynthesizedForTests = IsForTests();
215 if (aEvent.mClass != eCompositionEventClass) {
216 return;
218 void* pseudoIMEContext = GetPseudoIMEContext();
219 if (pseudoIMEContext) {
220 aEvent.AsCompositionEvent()->mNativeIMEContext.InitWithRawNativeIMEContext(
221 pseudoIMEContext);
223 #ifdef DEBUG
224 else {
225 MOZ_ASSERT(!XRE_IsContentProcess(),
226 "Why did the content process start native event transaction?");
227 MOZ_ASSERT(aEvent.AsCompositionEvent()->mNativeIMEContext.IsValid(),
228 "Native IME context shouldn't be invalid");
230 #endif // #ifdef DEBUG
233 Maybe<WritingMode> TextEventDispatcher::MaybeQueryWritingModeAtSelection()
234 const {
235 if (mHasFocus || mWritingMode.isSome()) {
236 return mWritingMode;
239 if (NS_WARN_IF(!mWidget)) {
240 return Nothing();
243 // If a remote content has focus and IME does not have focus, it's going to
244 // fail eQuerySelectedText in ContentCacheParent. For avoiding to waste
245 // unnecessary runtime cost and to prevent unnecessary warnings, we should
246 // not dispatch the event in the case.
247 const InputContext inputContext = mWidget->GetInputContext();
248 if (XRE_IsE10sParentProcess() && inputContext.IsOriginContentProcess() &&
249 !inputContext.mIMEState.IsEditable()) {
250 return Nothing();
253 WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
254 mWidget);
255 nsEventStatus status = nsEventStatus_eIgnore;
256 const_cast<TextEventDispatcher*>(this)->DispatchEvent(
257 mWidget, querySelectedTextEvent, status);
258 if (!querySelectedTextEvent.FoundSelection()) {
259 return Nothing();
262 return Some(querySelectedTextEvent.mReply->mWritingMode);
265 nsresult TextEventDispatcher::DispatchEvent(nsIWidget* aWidget,
266 WidgetGUIEvent& aEvent,
267 nsEventStatus& aStatus) {
268 MOZ_ASSERT(!aEvent.AsInputEvent(), "Use DispatchInputEvent()");
270 RefPtr<TextEventDispatcher> kungFuDeathGrip(this);
271 nsCOMPtr<nsIWidget> widget(aWidget);
272 mDispatchingEvent++;
273 nsresult rv = widget->DispatchEvent(&aEvent, aStatus);
274 mDispatchingEvent--;
275 return rv;
278 nsresult TextEventDispatcher::DispatchInputEvent(nsIWidget* aWidget,
279 WidgetInputEvent& aEvent,
280 nsEventStatus& aStatus) {
281 RefPtr<TextEventDispatcher> kungFuDeathGrip(this);
282 nsCOMPtr<nsIWidget> widget(aWidget);
283 mDispatchingEvent++;
285 // If the event is dispatched via nsIWidget::DispatchInputEvent(), it
286 // sends the event to the parent process first since APZ needs to handle it
287 // first. However, some callers (e.g., keyboard apps on B2G and tests
288 // expecting synchronous dispatch) don't want this to do that.
289 nsresult rv = NS_OK;
290 if (ShouldSendInputEventToAPZ()) {
291 aStatus = widget->DispatchInputEvent(&aEvent).mContentStatus;
292 } else {
293 rv = widget->DispatchEvent(&aEvent, aStatus);
296 mDispatchingEvent--;
297 return rv;
300 nsresult TextEventDispatcher::StartComposition(
301 nsEventStatus& aStatus, const WidgetEventTime* aEventTime) {
302 aStatus = nsEventStatus_eIgnore;
304 nsresult rv = GetState();
305 if (NS_WARN_IF(NS_FAILED(rv))) {
306 return rv;
309 if (NS_WARN_IF(mIsComposing)) {
310 return NS_ERROR_FAILURE;
313 // When you change some members from here, you may need same change in
314 // BeginInputTransactionFor().
315 mIsComposing = mIsHandlingComposition = true;
316 WidgetCompositionEvent compositionStartEvent(true, eCompositionStart,
317 mWidget);
318 InitEvent(compositionStartEvent);
319 if (aEventTime) {
320 compositionStartEvent.AssignEventTime(*aEventTime);
322 rv = DispatchEvent(mWidget, compositionStartEvent, aStatus);
323 if (NS_WARN_IF(NS_FAILED(rv))) {
324 return rv;
327 return NS_OK;
330 nsresult TextEventDispatcher::StartCompositionAutomaticallyIfNecessary(
331 nsEventStatus& aStatus, const WidgetEventTime* aEventTime) {
332 if (IsComposing()) {
333 return NS_OK;
336 nsresult rv = StartComposition(aStatus, aEventTime);
337 if (NS_WARN_IF(NS_FAILED(rv))) {
338 return rv;
341 // If started composition has already been committed, we shouldn't dispatch
342 // the compositionchange event.
343 if (!IsComposing()) {
344 aStatus = nsEventStatus_eConsumeNoDefault;
345 return NS_OK;
348 // Note that the widget might be destroyed during a call of
349 // StartComposition(). In such case, we shouldn't keep dispatching next
350 // event.
351 rv = GetState();
352 if (NS_FAILED(rv)) {
353 MOZ_ASSERT(rv != NS_ERROR_NOT_INITIALIZED,
354 "aDispatcher must still be initialized in this case");
355 aStatus = nsEventStatus_eConsumeNoDefault;
356 return NS_OK; // Don't throw exception in this case
359 aStatus = nsEventStatus_eIgnore;
360 return NS_OK;
363 nsresult TextEventDispatcher::CommitComposition(
364 nsEventStatus& aStatus, const nsAString* aCommitString,
365 const WidgetEventTime* aEventTime) {
366 aStatus = nsEventStatus_eIgnore;
368 nsresult rv = GetState();
369 if (NS_WARN_IF(NS_FAILED(rv))) {
370 return rv;
373 // When there is no composition, caller shouldn't try to commit composition
374 // with non-existing composition string nor commit composition with empty
375 // string.
376 if (NS_WARN_IF(!IsComposing() &&
377 (!aCommitString || aCommitString->IsEmpty()))) {
378 return NS_ERROR_FAILURE;
381 nsCOMPtr<nsIWidget> widget(mWidget);
382 rv = StartCompositionAutomaticallyIfNecessary(aStatus, aEventTime);
383 if (NS_WARN_IF(NS_FAILED(rv))) {
384 return rv;
386 if (aStatus == nsEventStatus_eConsumeNoDefault) {
387 return NS_OK;
390 // When you change some members from here, you may need same change in
391 // BeginInputTransactionFor().
393 // End current composition and make this free for other IMEs.
394 mIsComposing = false;
396 EventMessage message =
397 aCommitString ? eCompositionCommit : eCompositionCommitAsIs;
398 WidgetCompositionEvent compositionCommitEvent(true, message, widget);
399 InitEvent(compositionCommitEvent);
400 if (aEventTime) {
401 compositionCommitEvent.AssignEventTime(*aEventTime);
403 if (message == eCompositionCommit) {
404 compositionCommitEvent.mData = *aCommitString;
405 // If aCommitString comes from TextInputProcessor, it may be void, but
406 // editor requires non-void string even when it's empty.
407 compositionCommitEvent.mData.SetIsVoid(false);
408 // Don't send CRLF nor CR, replace it with LF here.
409 compositionCommitEvent.mData.ReplaceSubstring(u"\r\n"_ns, u"\n"_ns);
410 compositionCommitEvent.mData.ReplaceSubstring(u"\r"_ns, u"\n"_ns);
412 rv = DispatchEvent(widget, compositionCommitEvent, aStatus);
413 if (NS_WARN_IF(NS_FAILED(rv))) {
414 return rv;
417 return NS_OK;
420 nsresult TextEventDispatcher::NotifyIME(
421 const IMENotification& aIMENotification) {
422 nsresult rv = NS_ERROR_NOT_IMPLEMENTED;
424 switch (aIMENotification.mMessage) {
425 case NOTIFY_IME_OF_FOCUS: {
426 mWritingMode = MaybeQueryWritingModeAtSelection();
427 break;
429 case NOTIFY_IME_OF_BLUR:
430 mHasFocus = false;
431 mWritingMode.reset();
432 ClearNotificationRequests();
433 break;
434 case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
435 // If content handles composition events when native IME doesn't have
436 // composition, that means that we completely finished handling
437 // composition(s). Note that when focused content is in a remote
438 // process, this is sent when all dispatched composition events
439 // have been handled in the remote process.
440 if (!IsComposing()) {
441 mIsHandlingComposition = false;
443 break;
444 case NOTIFY_IME_OF_SELECTION_CHANGE:
445 if (mHasFocus && aIMENotification.mSelectionChangeData.HasRange()) {
446 mWritingMode =
447 Some(aIMENotification.mSelectionChangeData.GetWritingMode());
449 break;
450 default:
451 break;
454 // First, send the notification to current input transaction's listener.
455 nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
456 if (listener) {
457 rv = listener->NotifyIME(this, aIMENotification);
460 if (!mWidget) {
461 return rv;
464 // If current input transaction isn't for native event handler, we should
465 // send the notification to the native text event dispatcher listener
466 // since native event handler may need to do something from
467 // TextEventDispatcherListener::NotifyIME() even before there is no
468 // input transaction yet. For example, native IME handler may need to
469 // create new context at receiving NOTIFY_IME_OF_FOCUS. In this case,
470 // mListener may not be initialized since input transaction should be
471 // initialized immediately before dispatching every WidgetKeyboardEvent
472 // and WidgetCompositionEvent (dispatching events always occurs after
473 // focus move).
474 nsCOMPtr<TextEventDispatcherListener> nativeListener =
475 mWidget->GetNativeTextEventDispatcherListener();
476 if (listener != nativeListener && nativeListener) {
477 switch (aIMENotification.mMessage) {
478 case REQUEST_TO_COMMIT_COMPOSITION:
479 case REQUEST_TO_CANCEL_COMPOSITION:
480 // It's not necessary to notify native IME of requests.
481 break;
482 default: {
483 // Even if current input transaction's listener returns NS_OK or
484 // something, we need to notify native IME of notifications because
485 // when user typing after TIP does something, the changed information
486 // is necessary for them.
487 nsresult rv2 = nativeListener->NotifyIME(this, aIMENotification);
488 // But return the result from current listener except when the
489 // notification isn't handled.
490 if (rv == NS_ERROR_NOT_IMPLEMENTED) {
491 rv = rv2;
493 break;
498 if (aIMENotification.mMessage == NOTIFY_IME_OF_FOCUS) {
499 mHasFocus = true;
500 UpdateNotificationRequests();
503 return rv;
506 void TextEventDispatcher::ClearNotificationRequests() {
507 mIMENotificationRequests = IMENotificationRequests();
510 void TextEventDispatcher::UpdateNotificationRequests() {
511 ClearNotificationRequests();
513 // If it doesn't has focus, no notifications are available.
514 if (!mHasFocus || !mWidget) {
515 return;
518 // If there is a listener, its requests are necessary.
519 nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
520 if (listener) {
521 mIMENotificationRequests = listener->GetIMENotificationRequests();
524 // Even if this is in non-native input transaction, native IME needs
525 // requests. So, add native IME requests too.
526 if (!IsInNativeInputTransaction()) {
527 nsCOMPtr<TextEventDispatcherListener> nativeListener =
528 mWidget->GetNativeTextEventDispatcherListener();
529 if (nativeListener) {
530 mIMENotificationRequests |= nativeListener->GetIMENotificationRequests();
535 bool TextEventDispatcher::DispatchKeyboardEvent(
536 EventMessage aMessage, const WidgetKeyboardEvent& aKeyboardEvent,
537 nsEventStatus& aStatus, void* aData) {
538 return DispatchKeyboardEventInternal(aMessage, aKeyboardEvent, aStatus,
539 aData);
542 bool TextEventDispatcher::DispatchKeyboardEventInternal(
543 EventMessage aMessage, const WidgetKeyboardEvent& aKeyboardEvent,
544 nsEventStatus& aStatus, void* aData, uint32_t aIndexOfKeypress,
545 bool aNeedsCallback) {
546 // Note that this method is also used for dispatching key events on a plugin
547 // because key events on a plugin should be dispatched same as normal key
548 // events. Then, only some handlers which need to intercept key events
549 // before the focused plugin (e.g., reserved shortcut key handlers) can
550 // consume the events.
551 MOZ_ASSERT(
552 aMessage == eKeyDown || aMessage == eKeyUp || aMessage == eKeyPress,
553 "Invalid aMessage value");
554 nsresult rv = GetState();
555 if (NS_WARN_IF(NS_FAILED(rv))) {
556 return false;
559 // If the key shouldn't cause keypress events, don't this patch them.
560 if (aMessage == eKeyPress && !aKeyboardEvent.ShouldCauseKeypressEvents()) {
561 return false;
564 // Basically, key events shouldn't be dispatched during composition.
565 // Note that plugin process has different IME context. Therefore, we don't
566 // need to check our composition state when the key event is fired on a
567 // plugin.
568 if (IsComposing()) {
569 // However, if we need to behave like other browsers, we need the keydown
570 // and keyup events. Note that this behavior is also allowed by D3E spec.
571 // FYI: keypress events must not be fired during composition.
572 if (!StaticPrefs::dom_keyboardevent_dispatch_during_composition() ||
573 aMessage == eKeyPress) {
574 return false;
576 // XXX If there was mOnlyContentDispatch for this case, it might be useful
577 // because our chrome doesn't assume that key events are fired during
578 // composition.
581 WidgetKeyboardEvent keyEvent(true, aMessage, mWidget);
582 InitEvent(keyEvent);
583 keyEvent.AssignKeyEventData(aKeyboardEvent, false);
584 // Command arrays are not duplicated by AssignKeyEventData() due to
585 // both performance and footprint reasons. So, when TextInputProcessor
586 // emulates real text input or synthesizing keyboard events for tests,
587 // the arrays may be initialized all commands already. If so, we need to
588 // duplicate the arrays here, but we should do this only when we're
589 // dispatching eKeyPress events because BrowserParent::SendRealKeyEvent()
590 // does this only for eKeyPress event. Note that this is not required if
591 // we're in the main process because in the parent process, the edit commands
592 // will be initialized by `ExecuteEditCommands()` (when the event is handled
593 // by editor event listener) or `InitAllEditCommands()` (when the event is
594 // set to a content process). We should test whether these pathes work or
595 // not too.
596 if (XRE_IsContentProcess() && keyEvent.mIsSynthesizedByTIP) {
597 if (aMessage == eKeyPress) {
598 keyEvent.AssignCommands(aKeyboardEvent);
599 } else {
600 // Prevent retriving native edit commands if we're in a content process
601 // because only `eKeyPress` events coming from the main process have
602 // edit commands (See `BrowserParent::SendRealKeyEvent`). And also
603 // retriving edit commands from a content process requires synchonous
604 // IPC and that makes running tests slower. Therefore, we should mark
605 // the `eKeyPress` event does not need to retrieve edit commands anymore.
606 keyEvent.PreventNativeKeyBindings();
610 if (aStatus == nsEventStatus_eConsumeNoDefault) {
611 // If the key event should be dispatched as consumed event, marking it here.
612 // This is useful to prevent double action. This is intended to the system
613 // has already consumed the event but we need to dispatch the event for
614 // compatibility with older version and other browsers. So, we should not
615 // stop cross process forwarding of them.
616 keyEvent.PreventDefaultBeforeDispatch(CrossProcessForwarding::eAllow);
619 // Corrects each member for the specific key event type.
620 if (keyEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING) {
621 MOZ_ASSERT(!aIndexOfKeypress,
622 "aIndexOfKeypress must be 0 for non-printable key");
623 // If the keyboard event isn't caused by printable key, its charCode should
624 // be 0.
625 keyEvent.SetCharCode(0);
626 } else {
627 MOZ_DIAGNOSTIC_ASSERT_IF(aMessage == eKeyDown || aMessage == eKeyUp,
628 !aIndexOfKeypress);
629 MOZ_DIAGNOSTIC_ASSERT_IF(
630 aMessage == eKeyPress,
631 aIndexOfKeypress < std::max<size_t>(keyEvent.mKeyValue.Length(), 1));
632 char16_t ch =
633 keyEvent.mKeyValue.IsEmpty() ? 0 : keyEvent.mKeyValue[aIndexOfKeypress];
634 keyEvent.SetCharCode(static_cast<uint32_t>(ch));
635 if (aMessage == eKeyPress) {
636 // keyCode of eKeyPress events of printable keys should be always 0.
637 keyEvent.mKeyCode = 0;
638 // eKeyPress events are dispatched for every character.
639 // So, each key value of eKeyPress events should be a character.
640 if (ch) {
641 if (!IS_SURROGATE(ch)) {
642 keyEvent.mKeyValue.Assign(ch);
643 } else {
644 const bool isHighSurrogateFollowedByLowSurrogate =
645 aIndexOfKeypress + 1 < keyEvent.mKeyValue.Length() &&
646 NS_IS_HIGH_SURROGATE(ch) &&
647 NS_IS_LOW_SURROGATE(keyEvent.mKeyValue[aIndexOfKeypress + 1]);
648 const bool isLowSurrogateFollowingHighSurrogate =
649 !isHighSurrogateFollowedByLowSurrogate && aIndexOfKeypress > 0 &&
650 NS_IS_LOW_SURROGATE(ch) &&
651 NS_IS_HIGH_SURROGATE(keyEvent.mKeyValue[aIndexOfKeypress - 1]);
652 NS_WARNING_ASSERTION(isHighSurrogateFollowedByLowSurrogate ||
653 isLowSurrogateFollowingHighSurrogate,
654 "Lone surrogate input should not happen");
655 if (StaticPrefs::
656 dom_event_keypress_dispatch_once_per_surrogate_pair()) {
657 if (isHighSurrogateFollowedByLowSurrogate) {
658 keyEvent.mKeyValue.Assign(
659 keyEvent.mKeyValue.BeginReading() + aIndexOfKeypress, 2);
660 keyEvent.SetCharCode(
661 SURROGATE_TO_UCS4(ch, keyEvent.mKeyValue[1]));
662 } else if (isLowSurrogateFollowingHighSurrogate) {
663 // Although not dispatching eKeyPress event (because it's already
664 // dispatched for the low surrogate above), the caller should
665 // treat that this dispatched eKeyPress event normally so that
666 // return true here.
667 return true;
669 // Do not expose ill-formed UTF-16 string because it's a
670 // problematic for Rust-running-as-wasm for example.
671 else {
672 keyEvent.mKeyValue.Truncate();
674 } else if (!StaticPrefs::
675 dom_event_keypress_key_allow_lone_surrogate()) {
676 // If it's a high surrogate followed by a low surrogate, we should
677 // expose the surrogate pair with .key value.
678 if (isHighSurrogateFollowedByLowSurrogate) {
679 keyEvent.mKeyValue.Assign(
680 keyEvent.mKeyValue.BeginReading() + aIndexOfKeypress, 2);
682 // Do not expose low surrogate which should be handled by the
683 // preceding keypress event. And also do not expose ill-formed
684 // UTF-16 because it's a problematic for Rust-running-as-wasm for
685 // example.
686 else {
687 keyEvent.mKeyValue.Truncate();
689 } else {
690 // Here is a path for traditional behavior. We set `.key` to
691 // high-surrogate and low-surrogate separately.
692 keyEvent.mKeyValue.Assign(ch);
695 } else {
696 keyEvent.mKeyValue.Truncate();
700 if (aMessage == eKeyUp) {
701 // mIsRepeat of keyup event must be false.
702 keyEvent.mIsRepeat = false;
704 // mIsComposing should be initialized later.
705 keyEvent.mIsComposing = false;
706 if (mInputTransactionType == eNativeInputTransaction) {
707 // Copy mNativeKeyEvent here because for safety for other users of
708 // AssignKeyEventData(), it doesn't copy this.
709 keyEvent.mNativeKeyEvent = aKeyboardEvent.mNativeKeyEvent;
710 } else {
711 // If it's not a keyboard event for native key event, we should ensure that
712 // mNativeKeyEvent is null.
713 keyEvent.mNativeKeyEvent = nullptr;
715 // TODO: Manage mUniqueId here.
717 // Request the alternative char codes for the key event.
718 // eKeyDown also needs alternative char codes because nsXBLWindowKeyHandler
719 // needs to check if a following keypress event is reserved by chrome for
720 // stopping propagation of its preceding keydown event.
721 keyEvent.mAlternativeCharCodes.Clear();
722 if ((aMessage == eKeyDown || aMessage == eKeyPress) &&
723 (aNeedsCallback || keyEvent.IsControl() || keyEvent.IsAlt() ||
724 keyEvent.IsMeta())) {
725 nsCOMPtr<TextEventDispatcherListener> listener =
726 do_QueryReferent(mListener);
727 if (listener) {
728 DebugOnly<WidgetKeyboardEvent> original(keyEvent);
729 listener->WillDispatchKeyboardEvent(this, keyEvent, aIndexOfKeypress,
730 aData);
731 MOZ_ASSERT(keyEvent.mMessage ==
732 static_cast<WidgetKeyboardEvent&>(original).mMessage);
733 MOZ_ASSERT(keyEvent.mKeyCode ==
734 static_cast<WidgetKeyboardEvent&>(original).mKeyCode);
735 MOZ_ASSERT(keyEvent.mLocation ==
736 static_cast<WidgetKeyboardEvent&>(original).mLocation);
737 MOZ_ASSERT(keyEvent.mIsRepeat ==
738 static_cast<WidgetKeyboardEvent&>(original).mIsRepeat);
739 MOZ_ASSERT(keyEvent.mIsComposing ==
740 static_cast<WidgetKeyboardEvent&>(original).mIsComposing);
741 MOZ_ASSERT(keyEvent.mKeyNameIndex ==
742 static_cast<WidgetKeyboardEvent&>(original).mKeyNameIndex);
743 MOZ_ASSERT(keyEvent.mCodeNameIndex ==
744 static_cast<WidgetKeyboardEvent&>(original).mCodeNameIndex);
745 MOZ_ASSERT(keyEvent.mKeyValue ==
746 static_cast<WidgetKeyboardEvent&>(original).mKeyValue);
747 MOZ_ASSERT(keyEvent.mCodeValue ==
748 static_cast<WidgetKeyboardEvent&>(original).mCodeValue);
752 if (StaticPrefs::
753 dom_keyboardevent_keypress_dispatch_non_printable_keys_only_system_group_in_content() &&
754 keyEvent.mMessage == eKeyPress &&
755 !keyEvent.ShouldKeyPressEventBeFiredOnContent()) {
756 // Note that even if we set it to true, this may be overwritten by
757 // PresShell::DispatchEventToDOM().
758 keyEvent.mFlags.mOnlySystemGroupDispatchInContent = true;
761 // If an editable element has focus and we're in the parent process, we should
762 // retrieve native key bindings right now because even if it matches with a
763 // reserved shortcut key, it should be handled by the editor.
764 if (XRE_IsParentProcess() && mHasFocus &&
765 (aMessage == eKeyDown || aMessage == eKeyPress)) {
766 keyEvent.InitAllEditCommands(mWritingMode);
769 DispatchInputEvent(mWidget, keyEvent, aStatus);
770 return true;
773 bool TextEventDispatcher::MaybeDispatchKeypressEvents(
774 const WidgetKeyboardEvent& aKeyboardEvent, nsEventStatus& aStatus,
775 void* aData, bool aNeedsCallback) {
776 // If the key event was consumed, keypress event shouldn't be fired.
777 if (aStatus == nsEventStatus_eConsumeNoDefault) {
778 return false;
781 // If the key shouldn't cause keypress events, don't fire them.
782 if (!aKeyboardEvent.ShouldCauseKeypressEvents()) {
783 return false;
786 // If the key isn't a printable key or just inputting one character or
787 // no character, we should dispatch only one keypress. Otherwise, i.e.,
788 // if the key is a printable key and inputs multiple characters, keypress
789 // event should be dispatched the count of inputting characters times.
790 size_t keypressCount =
791 aKeyboardEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING
793 : std::max(static_cast<nsAString::size_type>(1),
794 aKeyboardEvent.mKeyValue.Length());
795 bool isDispatched = false;
796 bool consumed = false;
797 for (size_t i = 0; i < keypressCount; i++) {
798 aStatus = nsEventStatus_eIgnore;
799 if (!DispatchKeyboardEventInternal(eKeyPress, aKeyboardEvent, aStatus,
800 aData, i, aNeedsCallback)) {
801 // The widget must have been gone.
802 break;
804 isDispatched = true;
805 if (!consumed) {
806 consumed = (aStatus == nsEventStatus_eConsumeNoDefault);
810 // If one of the keypress event was consumed, return ConsumeNoDefault.
811 if (consumed) {
812 aStatus = nsEventStatus_eConsumeNoDefault;
815 return isDispatched;
818 /******************************************************************************
819 * TextEventDispatcher::PendingComposition
820 *****************************************************************************/
822 TextEventDispatcher::PendingComposition::PendingComposition() { Clear(); }
824 void TextEventDispatcher::PendingComposition::Clear() {
825 mString.Truncate();
826 mClauses = nullptr;
827 mCaret.mRangeType = TextRangeType::eUninitialized;
828 mReplacedNativeLineBreakers = false;
831 void TextEventDispatcher::PendingComposition::EnsureClauseArray() {
832 if (mClauses) {
833 return;
835 mClauses = new TextRangeArray();
838 nsresult TextEventDispatcher::PendingComposition::SetString(
839 const nsAString& aString) {
840 MOZ_ASSERT(!mReplacedNativeLineBreakers);
841 mString = aString;
842 return NS_OK;
845 nsresult TextEventDispatcher::PendingComposition::AppendClause(
846 uint32_t aLength, TextRangeType aTextRangeType) {
847 MOZ_ASSERT(!mReplacedNativeLineBreakers);
849 if (NS_WARN_IF(!aLength)) {
850 return NS_ERROR_INVALID_ARG;
853 switch (aTextRangeType) {
854 case TextRangeType::eRawClause:
855 case TextRangeType::eSelectedRawClause:
856 case TextRangeType::eConvertedClause:
857 case TextRangeType::eSelectedClause: {
858 EnsureClauseArray();
859 TextRange textRange;
860 textRange.mStartOffset =
861 mClauses->IsEmpty() ? 0 : mClauses->LastElement().mEndOffset;
862 textRange.mEndOffset = textRange.mStartOffset + aLength;
863 textRange.mRangeType = aTextRangeType;
864 mClauses->AppendElement(textRange);
865 return NS_OK;
867 default:
868 return NS_ERROR_INVALID_ARG;
872 nsresult TextEventDispatcher::PendingComposition::SetCaret(uint32_t aOffset,
873 uint32_t aLength) {
874 MOZ_ASSERT(!mReplacedNativeLineBreakers);
876 mCaret.mStartOffset = aOffset;
877 mCaret.mEndOffset = mCaret.mStartOffset + aLength;
878 mCaret.mRangeType = TextRangeType::eCaret;
879 return NS_OK;
882 nsresult TextEventDispatcher::PendingComposition::Set(
883 const nsAString& aString, const TextRangeArray* aRanges) {
884 Clear();
886 nsresult rv = SetString(aString);
887 if (NS_WARN_IF(NS_FAILED(rv))) {
888 return rv;
891 if (!aRanges || aRanges->IsEmpty()) {
892 // Create dummy range if mString isn't empty.
893 if (!mString.IsEmpty()) {
894 rv = AppendClause(mString.Length(), TextRangeType::eRawClause);
895 if (NS_WARN_IF(NS_FAILED(rv))) {
896 return rv;
898 ReplaceNativeLineBreakers();
900 return NS_OK;
903 // Adjust offsets in the ranges for XP linefeed character (only \n).
904 for (uint32_t i = 0; i < aRanges->Length(); ++i) {
905 TextRange range = aRanges->ElementAt(i);
906 if (range.mRangeType == TextRangeType::eCaret) {
907 mCaret = range;
908 } else {
909 EnsureClauseArray();
910 mClauses->AppendElement(range);
913 ReplaceNativeLineBreakers();
914 return NS_OK;
917 void TextEventDispatcher::PendingComposition::ReplaceNativeLineBreakers() {
918 mReplacedNativeLineBreakers = true;
920 // If the composition string is empty, we don't need to do anything.
921 if (mString.IsEmpty()) {
922 return;
925 nsAutoString nativeString(mString);
926 // Don't expose CRLF nor CR to web contents, instead, use LF.
927 mString.ReplaceSubstring(u"\r\n"_ns, u"\n"_ns);
928 mString.ReplaceSubstring(u"\r"_ns, u"\n"_ns);
930 // If the length isn't changed, we don't need to adjust any offset and length
931 // of mClauses nor mCaret.
932 if (nativeString.Length() == mString.Length()) {
933 return;
936 if (mClauses) {
937 for (TextRange& clause : *mClauses) {
938 AdjustRange(clause, nativeString);
941 if (mCaret.mRangeType == TextRangeType::eCaret) {
942 AdjustRange(mCaret, nativeString);
946 // static
947 void TextEventDispatcher::PendingComposition::AdjustRange(
948 TextRange& aRange, const nsAString& aNativeString) {
949 TextRange nativeRange = aRange;
950 // XXX Following code wastes runtime cost because this causes computing
951 // mStartOffset for each clause from the start of composition string.
952 // If we'd make TextRange have only its length, we don't need to do
953 // this. However, this must not be so serious problem because
954 // composition string is usually short and separated as a few clauses.
955 if (nativeRange.mStartOffset > 0) {
956 nsAutoString preText(Substring(aNativeString, 0, nativeRange.mStartOffset));
957 preText.ReplaceSubstring(u"\r\n"_ns, u"\n"_ns);
958 aRange.mStartOffset = preText.Length();
960 if (nativeRange.Length() == 0) {
961 aRange.mEndOffset = aRange.mStartOffset;
962 } else {
963 nsAutoString clause(Substring(aNativeString, nativeRange.mStartOffset,
964 nativeRange.Length()));
965 clause.ReplaceSubstring(u"\r\n"_ns, u"\n"_ns);
966 aRange.mEndOffset = aRange.mStartOffset + clause.Length();
970 nsresult TextEventDispatcher::PendingComposition::Flush(
971 TextEventDispatcher* aDispatcher, nsEventStatus& aStatus,
972 const WidgetEventTime* aEventTime) {
973 aStatus = nsEventStatus_eIgnore;
975 nsresult rv = aDispatcher->GetState();
976 if (NS_WARN_IF(NS_FAILED(rv))) {
977 return rv;
980 if (mClauses && !mClauses->IsEmpty() &&
981 mClauses->LastElement().mEndOffset != mString.Length()) {
982 NS_WARNING(
983 "Sum of length of the all clauses must be same as the string "
984 "length");
985 Clear();
986 return NS_ERROR_ILLEGAL_VALUE;
988 if (mCaret.mRangeType == TextRangeType::eCaret) {
989 if (mCaret.mEndOffset > mString.Length()) {
990 NS_WARNING("Caret position is out of the composition string");
991 Clear();
992 return NS_ERROR_ILLEGAL_VALUE;
994 EnsureClauseArray();
995 mClauses->AppendElement(mCaret);
998 // If the composition string is set without Set(), we need to replace native
999 // line breakers in the composition string with XP line breaker.
1000 if (!mReplacedNativeLineBreakers) {
1001 ReplaceNativeLineBreakers();
1004 RefPtr<TextEventDispatcher> kungFuDeathGrip(aDispatcher);
1005 nsCOMPtr<nsIWidget> widget(aDispatcher->mWidget);
1006 WidgetCompositionEvent compChangeEvent(true, eCompositionChange, widget);
1007 aDispatcher->InitEvent(compChangeEvent);
1008 if (aEventTime) {
1009 compChangeEvent.AssignEventTime(*aEventTime);
1011 compChangeEvent.mData = mString;
1012 // If mString comes from TextInputProcessor, it may be void, but editor
1013 // requires non-void string even when it's empty.
1014 compChangeEvent.mData.SetIsVoid(false);
1015 if (mClauses) {
1016 MOZ_ASSERT(!mClauses->IsEmpty(),
1017 "mClauses must be non-empty array when it's not nullptr");
1018 compChangeEvent.mRanges = mClauses;
1021 // While this method dispatches a composition event, some other event handler
1022 // cause more clauses to be added. So, we should clear pending composition
1023 // before dispatching the event.
1024 Clear();
1026 rv = aDispatcher->StartCompositionAutomaticallyIfNecessary(aStatus,
1027 aEventTime);
1028 if (NS_WARN_IF(NS_FAILED(rv))) {
1029 return rv;
1031 if (aStatus == nsEventStatus_eConsumeNoDefault) {
1032 return NS_OK;
1034 rv = aDispatcher->DispatchEvent(widget, compChangeEvent, aStatus);
1035 if (NS_WARN_IF(NS_FAILED(rv))) {
1036 return rv;
1039 return NS_OK;
1042 } // namespace widget
1043 } // namespace mozilla