Bug 1671598 [wpt PR 26128] - [AspectRatio] Fix divide by zero with a small float...
[gecko.git] / widget / TextEventDispatcher.cpp
blob73ee6ba8d66aa756bc316158ae1b1ea789fb34d0
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 "mozilla/Preferences.h"
7 #include "mozilla/StaticPrefs_dom.h"
8 #include "mozilla/TextEvents.h"
9 #include "mozilla/TextEventDispatcher.h"
10 #include "nsIFrame.h"
11 #include "nsIWidget.h"
12 #include "nsPIDOMWindow.h"
13 #include "nsView.h"
14 #include "PuppetWidget.h"
16 namespace mozilla {
17 namespace widget {
19 /******************************************************************************
20 * TextEventDispatcher
21 *****************************************************************************/
22 TextEventDispatcher::TextEventDispatcher(nsIWidget* aWidget)
23 : mWidget(aWidget),
24 mDispatchingEvent(0),
25 mInputTransactionType(eNoInputTransaction),
26 mIsComposing(false),
27 mIsHandlingComposition(false),
28 mHasFocus(false) {
29 MOZ_RELEASE_ASSERT(mWidget, "aWidget must not be nullptr");
31 ClearNotificationRequests();
34 nsresult TextEventDispatcher::BeginInputTransaction(
35 TextEventDispatcherListener* aListener) {
36 return BeginInputTransactionInternal(aListener,
37 eSameProcessSyncInputTransaction);
40 nsresult TextEventDispatcher::BeginTestInputTransaction(
41 TextEventDispatcherListener* aListener, bool aIsAPZAware) {
42 return BeginInputTransactionInternal(
43 aListener, aIsAPZAware ? eAsyncTestInputTransaction
44 : eSameProcessSyncTestInputTransaction);
47 nsresult TextEventDispatcher::BeginNativeInputTransaction() {
48 if (NS_WARN_IF(!mWidget)) {
49 return NS_ERROR_FAILURE;
51 RefPtr<TextEventDispatcherListener> listener =
52 mWidget->GetNativeTextEventDispatcherListener();
53 if (NS_WARN_IF(!listener)) {
54 return NS_ERROR_FAILURE;
56 return BeginInputTransactionInternal(listener, eNativeInputTransaction);
59 nsresult TextEventDispatcher::BeginInputTransactionInternal(
60 TextEventDispatcherListener* aListener, InputTransactionType aType) {
61 if (NS_WARN_IF(!aListener)) {
62 return NS_ERROR_INVALID_ARG;
64 nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
65 if (listener) {
66 if (listener == aListener && mInputTransactionType == aType) {
67 UpdateNotificationRequests();
68 return NS_OK;
70 // If this has composition or is dispatching an event, any other listener
71 // can steal ownership. Especially, if the latter case is allowed,
72 // nobody cannot begin input transaction with this if a modal dialog is
73 // opened during dispatching an event.
74 if (IsComposing() || IsDispatchingEvent()) {
75 return NS_ERROR_ALREADY_INITIALIZED;
78 mListener = do_GetWeakReference(aListener);
79 mInputTransactionType = aType;
80 if (listener && listener != aListener) {
81 listener->OnRemovedFrom(this);
83 UpdateNotificationRequests();
84 return NS_OK;
87 nsresult TextEventDispatcher::BeginInputTransactionFor(
88 const WidgetGUIEvent* aEvent, PuppetWidget* aPuppetWidget) {
89 MOZ_ASSERT(XRE_IsContentProcess());
90 MOZ_ASSERT(!IsDispatchingEvent());
92 switch (aEvent->mMessage) {
93 case eKeyDown:
94 case eKeyPress:
95 case eKeyUp:
96 MOZ_ASSERT(aEvent->mClass == eKeyboardEventClass);
97 break;
98 case eCompositionStart:
99 case eCompositionChange:
100 case eCompositionCommit:
101 case eCompositionCommitAsIs:
102 MOZ_ASSERT(aEvent->mClass == eCompositionEventClass);
103 break;
104 default:
105 return NS_ERROR_INVALID_ARG;
108 if (aEvent->mFlags.mIsSynthesizedForTests) {
109 // If the event is for an automated test and this instance dispatched
110 // an event to the parent process, we can assume that this is already
111 // initialized properly.
112 if (mInputTransactionType == eAsyncTestInputTransaction) {
113 return NS_OK;
115 // Even if the event coming from the parent process is synthesized for
116 // tests, this process should treat it as "sync" test here because
117 // it won't be go back to the parent process.
118 nsresult rv = BeginInputTransactionInternal(
119 static_cast<TextEventDispatcherListener*>(aPuppetWidget),
120 eSameProcessSyncTestInputTransaction);
121 if (NS_WARN_IF(NS_FAILED(rv))) {
122 return rv;
124 } else {
125 nsresult rv = BeginNativeInputTransaction();
126 if (NS_WARN_IF(NS_FAILED(rv))) {
127 return rv;
131 // Emulate modifying members which indicate the state of composition.
132 // If we need to manage more states and/or more complexly, we should create
133 // internal methods which are called by both here and each event dispatcher
134 // method of this class.
135 switch (aEvent->mMessage) {
136 case eKeyDown:
137 case eKeyPress:
138 case eKeyUp:
139 return NS_OK;
140 case eCompositionStart:
141 MOZ_ASSERT(!mIsComposing);
142 mIsComposing = mIsHandlingComposition = true;
143 return NS_OK;
144 case eCompositionChange:
145 MOZ_ASSERT(mIsComposing);
146 MOZ_ASSERT(mIsHandlingComposition);
147 mIsComposing = mIsHandlingComposition = true;
148 return NS_OK;
149 case eCompositionCommit:
150 case eCompositionCommitAsIs:
151 MOZ_ASSERT(mIsComposing);
152 MOZ_ASSERT(mIsHandlingComposition);
153 mIsComposing = false;
154 mIsHandlingComposition = true;
155 return NS_OK;
156 default:
157 MOZ_ASSERT_UNREACHABLE("You forgot to handle the event");
158 return NS_ERROR_UNEXPECTED;
161 void TextEventDispatcher::EndInputTransaction(
162 TextEventDispatcherListener* aListener) {
163 if (NS_WARN_IF(IsComposing()) || NS_WARN_IF(IsDispatchingEvent())) {
164 return;
167 mInputTransactionType = eNoInputTransaction;
169 nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
170 if (NS_WARN_IF(!listener)) {
171 return;
174 if (NS_WARN_IF(listener != aListener)) {
175 return;
178 mListener = nullptr;
179 listener->OnRemovedFrom(this);
180 UpdateNotificationRequests();
183 void TextEventDispatcher::OnDestroyWidget() {
184 mWidget = nullptr;
185 mHasFocus = false;
186 ClearNotificationRequests();
187 mPendingComposition.Clear();
188 nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
189 mListener = nullptr;
190 mInputTransactionType = eNoInputTransaction;
191 if (listener) {
192 listener->OnRemovedFrom(this);
196 nsresult TextEventDispatcher::GetState() const {
197 nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
198 if (!listener) {
199 return NS_ERROR_NOT_INITIALIZED;
201 if (!mWidget || mWidget->Destroyed()) {
202 return NS_ERROR_NOT_AVAILABLE;
204 return NS_OK;
207 void TextEventDispatcher::InitEvent(WidgetGUIEvent& aEvent) const {
208 aEvent.mTime = PR_IntervalNow();
209 aEvent.mRefPoint = LayoutDeviceIntPoint(0, 0);
210 aEvent.mFlags.mIsSynthesizedForTests = IsForTests();
211 if (aEvent.mClass != eCompositionEventClass) {
212 return;
214 void* pseudoIMEContext = GetPseudoIMEContext();
215 if (pseudoIMEContext) {
216 aEvent.AsCompositionEvent()->mNativeIMEContext.InitWithRawNativeIMEContext(
217 pseudoIMEContext);
219 #ifdef DEBUG
220 else {
221 MOZ_ASSERT(!XRE_IsContentProcess(),
222 "Why did the content process start native event transaction?");
223 MOZ_ASSERT(aEvent.AsCompositionEvent()->mNativeIMEContext.IsValid(),
224 "Native IME context shouldn't be invalid");
226 #endif // #ifdef DEBUG
229 nsresult TextEventDispatcher::DispatchEvent(nsIWidget* aWidget,
230 WidgetGUIEvent& aEvent,
231 nsEventStatus& aStatus) {
232 MOZ_ASSERT(!aEvent.AsInputEvent(), "Use DispatchInputEvent()");
234 RefPtr<TextEventDispatcher> kungFuDeathGrip(this);
235 nsCOMPtr<nsIWidget> widget(aWidget);
236 mDispatchingEvent++;
237 nsresult rv = widget->DispatchEvent(&aEvent, aStatus);
238 mDispatchingEvent--;
239 return rv;
242 nsresult TextEventDispatcher::DispatchInputEvent(nsIWidget* aWidget,
243 WidgetInputEvent& aEvent,
244 nsEventStatus& aStatus) {
245 RefPtr<TextEventDispatcher> kungFuDeathGrip(this);
246 nsCOMPtr<nsIWidget> widget(aWidget);
247 mDispatchingEvent++;
249 // If the event is dispatched via nsIWidget::DispatchInputEvent(), it
250 // sends the event to the parent process first since APZ needs to handle it
251 // first. However, some callers (e.g., keyboard apps on B2G and tests
252 // expecting synchronous dispatch) don't want this to do that.
253 nsresult rv = NS_OK;
254 if (ShouldSendInputEventToAPZ()) {
255 aStatus = widget->DispatchInputEvent(&aEvent);
256 } else {
257 rv = widget->DispatchEvent(&aEvent, aStatus);
260 mDispatchingEvent--;
261 return rv;
264 nsresult TextEventDispatcher::StartComposition(
265 nsEventStatus& aStatus, const WidgetEventTime* aEventTime) {
266 aStatus = nsEventStatus_eIgnore;
268 nsresult rv = GetState();
269 if (NS_WARN_IF(NS_FAILED(rv))) {
270 return rv;
273 if (NS_WARN_IF(mIsComposing)) {
274 return NS_ERROR_FAILURE;
277 // When you change some members from here, you may need same change in
278 // BeginInputTransactionFor().
279 mIsComposing = mIsHandlingComposition = true;
280 WidgetCompositionEvent compositionStartEvent(true, eCompositionStart,
281 mWidget);
282 InitEvent(compositionStartEvent);
283 if (aEventTime) {
284 compositionStartEvent.AssignEventTime(*aEventTime);
286 rv = DispatchEvent(mWidget, compositionStartEvent, aStatus);
287 if (NS_WARN_IF(NS_FAILED(rv))) {
288 return rv;
291 return NS_OK;
294 nsresult TextEventDispatcher::StartCompositionAutomaticallyIfNecessary(
295 nsEventStatus& aStatus, const WidgetEventTime* aEventTime) {
296 if (IsComposing()) {
297 return NS_OK;
300 nsresult rv = StartComposition(aStatus, aEventTime);
301 if (NS_WARN_IF(NS_FAILED(rv))) {
302 return rv;
305 // If started composition has already been committed, we shouldn't dispatch
306 // the compositionchange event.
307 if (!IsComposing()) {
308 aStatus = nsEventStatus_eConsumeNoDefault;
309 return NS_OK;
312 // Note that the widget might be destroyed during a call of
313 // StartComposition(). In such case, we shouldn't keep dispatching next
314 // event.
315 rv = GetState();
316 if (NS_FAILED(rv)) {
317 MOZ_ASSERT(rv != NS_ERROR_NOT_INITIALIZED,
318 "aDispatcher must still be initialized in this case");
319 aStatus = nsEventStatus_eConsumeNoDefault;
320 return NS_OK; // Don't throw exception in this case
323 aStatus = nsEventStatus_eIgnore;
324 return NS_OK;
327 nsresult TextEventDispatcher::CommitComposition(
328 nsEventStatus& aStatus, const nsAString* aCommitString,
329 const WidgetEventTime* aEventTime) {
330 aStatus = nsEventStatus_eIgnore;
332 nsresult rv = GetState();
333 if (NS_WARN_IF(NS_FAILED(rv))) {
334 return rv;
337 // When there is no composition, caller shouldn't try to commit composition
338 // with non-existing composition string nor commit composition with empty
339 // string.
340 if (NS_WARN_IF(!IsComposing() &&
341 (!aCommitString || aCommitString->IsEmpty()))) {
342 return NS_ERROR_FAILURE;
345 nsCOMPtr<nsIWidget> widget(mWidget);
346 rv = StartCompositionAutomaticallyIfNecessary(aStatus, aEventTime);
347 if (NS_WARN_IF(NS_FAILED(rv))) {
348 return rv;
350 if (aStatus == nsEventStatus_eConsumeNoDefault) {
351 return NS_OK;
354 // When you change some members from here, you may need same change in
355 // BeginInputTransactionFor().
357 // End current composition and make this free for other IMEs.
358 mIsComposing = false;
360 EventMessage message =
361 aCommitString ? eCompositionCommit : eCompositionCommitAsIs;
362 WidgetCompositionEvent compositionCommitEvent(true, message, widget);
363 InitEvent(compositionCommitEvent);
364 if (aEventTime) {
365 compositionCommitEvent.AssignEventTime(*aEventTime);
367 if (message == eCompositionCommit) {
368 compositionCommitEvent.mData = *aCommitString;
369 // If aCommitString comes from TextInputProcessor, it may be void, but
370 // editor requires non-void string even when it's empty.
371 compositionCommitEvent.mData.SetIsVoid(false);
372 // Don't send CRLF nor CR, replace it with LF here.
373 compositionCommitEvent.mData.ReplaceSubstring(u"\r\n"_ns, u"\n"_ns);
374 compositionCommitEvent.mData.ReplaceSubstring(u"\r"_ns, u"\n"_ns);
376 rv = DispatchEvent(widget, compositionCommitEvent, aStatus);
377 if (NS_WARN_IF(NS_FAILED(rv))) {
378 return rv;
381 return NS_OK;
384 nsresult TextEventDispatcher::NotifyIME(
385 const IMENotification& aIMENotification) {
386 nsresult rv = NS_ERROR_NOT_IMPLEMENTED;
388 switch (aIMENotification.mMessage) {
389 case NOTIFY_IME_OF_BLUR:
390 mHasFocus = false;
391 ClearNotificationRequests();
392 break;
393 case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
394 // If content handles composition events when native IME doesn't have
395 // composition, that means that we completely finished handling
396 // composition(s). Note that when focused content is in a remote
397 // process, this is sent when all dispatched composition events
398 // have been handled in the remote process.
399 if (!IsComposing()) {
400 mIsHandlingComposition = false;
402 break;
403 default:
404 break;
407 // First, send the notification to current input transaction's listener.
408 nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
409 if (listener) {
410 rv = listener->NotifyIME(this, aIMENotification);
413 if (!mWidget) {
414 return rv;
417 // If current input transaction isn't for native event handler, we should
418 // send the notification to the native text event dispatcher listener
419 // since native event handler may need to do something from
420 // TextEventDispatcherListener::NotifyIME() even before there is no
421 // input transaction yet. For example, native IME handler may need to
422 // create new context at receiving NOTIFY_IME_OF_FOCUS. In this case,
423 // mListener may not be initialized since input transaction should be
424 // initialized immediately before dispatching every WidgetKeyboardEvent
425 // and WidgetCompositionEvent (dispatching events always occurs after
426 // focus move).
427 nsCOMPtr<TextEventDispatcherListener> nativeListener =
428 mWidget->GetNativeTextEventDispatcherListener();
429 if (listener != nativeListener && nativeListener) {
430 switch (aIMENotification.mMessage) {
431 case REQUEST_TO_COMMIT_COMPOSITION:
432 case REQUEST_TO_CANCEL_COMPOSITION:
433 // It's not necessary to notify native IME of requests.
434 break;
435 default: {
436 // Even if current input transaction's listener returns NS_OK or
437 // something, we need to notify native IME of notifications because
438 // when user typing after TIP does something, the changed information
439 // is necessary for them.
440 nsresult rv2 = nativeListener->NotifyIME(this, aIMENotification);
441 // But return the result from current listener except when the
442 // notification isn't handled.
443 if (rv == NS_ERROR_NOT_IMPLEMENTED) {
444 rv = rv2;
446 break;
451 if (aIMENotification.mMessage == NOTIFY_IME_OF_FOCUS) {
452 mHasFocus = true;
453 UpdateNotificationRequests();
456 return rv;
459 void TextEventDispatcher::ClearNotificationRequests() {
460 mIMENotificationRequests = IMENotificationRequests();
463 void TextEventDispatcher::UpdateNotificationRequests() {
464 ClearNotificationRequests();
466 // If it doesn't has focus, no notifications are available.
467 if (!mHasFocus || !mWidget) {
468 return;
471 // If there is a listener, its requests are necessary.
472 nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
473 if (listener) {
474 mIMENotificationRequests = listener->GetIMENotificationRequests();
477 // Even if this is in non-native input transaction, native IME needs
478 // requests. So, add native IME requests too.
479 if (!IsInNativeInputTransaction()) {
480 nsCOMPtr<TextEventDispatcherListener> nativeListener =
481 mWidget->GetNativeTextEventDispatcherListener();
482 if (nativeListener) {
483 mIMENotificationRequests |= nativeListener->GetIMENotificationRequests();
488 bool TextEventDispatcher::DispatchKeyboardEvent(
489 EventMessage aMessage, const WidgetKeyboardEvent& aKeyboardEvent,
490 nsEventStatus& aStatus, void* aData) {
491 return DispatchKeyboardEventInternal(aMessage, aKeyboardEvent, aStatus,
492 aData);
495 bool TextEventDispatcher::DispatchKeyboardEventInternal(
496 EventMessage aMessage, const WidgetKeyboardEvent& aKeyboardEvent,
497 nsEventStatus& aStatus, void* aData, uint32_t aIndexOfKeypress,
498 bool aNeedsCallback) {
499 // Note that this method is also used for dispatching key events on a plugin
500 // because key events on a plugin should be dispatched same as normal key
501 // events. Then, only some handlers which need to intercept key events
502 // before the focused plugin (e.g., reserved shortcut key handlers) can
503 // consume the events.
504 MOZ_ASSERT(WidgetKeyboardEvent::IsKeyDownOrKeyDownOnPlugin(aMessage) ||
505 WidgetKeyboardEvent::IsKeyUpOrKeyUpOnPlugin(aMessage) ||
506 aMessage == eKeyPress,
507 "Invalid aMessage value");
508 nsresult rv = GetState();
509 if (NS_WARN_IF(NS_FAILED(rv))) {
510 return false;
513 // If the key shouldn't cause keypress events, don't this patch them.
514 if (aMessage == eKeyPress && !aKeyboardEvent.ShouldCauseKeypressEvents()) {
515 return false;
518 // Basically, key events shouldn't be dispatched during composition.
519 // Note that plugin process has different IME context. Therefore, we don't
520 // need to check our composition state when the key event is fired on a
521 // plugin.
522 if (IsComposing() && !WidgetKeyboardEvent::IsKeyEventOnPlugin(aMessage)) {
523 // However, if we need to behave like other browsers, we need the keydown
524 // and keyup events. Note that this behavior is also allowed by D3E spec.
525 // FYI: keypress events must not be fired during composition.
526 if (!StaticPrefs::dom_keyboardevent_dispatch_during_composition() ||
527 aMessage == eKeyPress) {
528 return false;
530 // XXX If there was mOnlyContentDispatch for this case, it might be useful
531 // because our chrome doesn't assume that key events are fired during
532 // composition.
535 WidgetKeyboardEvent keyEvent(true, aMessage, mWidget);
536 InitEvent(keyEvent);
537 keyEvent.AssignKeyEventData(aKeyboardEvent, false);
538 // Command arrays are not duplicated by AssignKeyEventData() due to
539 // both performance and footprint reasons. So, when TextInputProcessor
540 // emulates real text input, the arrays may be initialized all commands
541 // already. If so, we need to duplicate the arrays here.
542 if (keyEvent.mIsSynthesizedByTIP) {
543 keyEvent.AssignCommands(aKeyboardEvent);
546 if (aStatus == nsEventStatus_eConsumeNoDefault) {
547 // If the key event should be dispatched as consumed event, marking it here.
548 // This is useful to prevent double action. This is intended to the system
549 // has already consumed the event but we need to dispatch the event for
550 // compatibility with older version and other browsers. So, we should not
551 // stop cross process forwarding of them.
552 keyEvent.PreventDefaultBeforeDispatch(CrossProcessForwarding::eAllow);
555 // Corrects each member for the specific key event type.
556 if (keyEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING) {
557 MOZ_ASSERT(!aIndexOfKeypress,
558 "aIndexOfKeypress must be 0 for non-printable key");
559 // If the keyboard event isn't caused by printable key, its charCode should
560 // be 0.
561 keyEvent.SetCharCode(0);
562 } else {
563 if (WidgetKeyboardEvent::IsKeyDownOrKeyDownOnPlugin(aMessage) ||
564 WidgetKeyboardEvent::IsKeyUpOrKeyUpOnPlugin(aMessage)) {
565 MOZ_RELEASE_ASSERT(
566 !aIndexOfKeypress,
567 "aIndexOfKeypress must be 0 for either eKeyDown or eKeyUp");
568 } else {
569 MOZ_RELEASE_ASSERT(
570 !aIndexOfKeypress || aIndexOfKeypress < keyEvent.mKeyValue.Length(),
571 "aIndexOfKeypress must be 0 - mKeyValue.Length() - 1");
573 char16_t ch =
574 keyEvent.mKeyValue.IsEmpty() ? 0 : keyEvent.mKeyValue[aIndexOfKeypress];
575 keyEvent.SetCharCode(static_cast<uint32_t>(ch));
576 if (aMessage == eKeyPress) {
577 // keyCode of eKeyPress events of printable keys should be always 0.
578 keyEvent.mKeyCode = 0;
579 // eKeyPress events are dispatched for every character.
580 // So, each key value of eKeyPress events should be a character.
581 if (ch) {
582 keyEvent.mKeyValue.Assign(ch);
583 } else {
584 keyEvent.mKeyValue.Truncate();
588 if (WidgetKeyboardEvent::IsKeyUpOrKeyUpOnPlugin(aMessage)) {
589 // mIsRepeat of keyup event must be false.
590 keyEvent.mIsRepeat = false;
592 // mIsComposing should be initialized later.
593 keyEvent.mIsComposing = false;
594 if (mInputTransactionType == eNativeInputTransaction) {
595 // Copy mNativeKeyEvent here because for safety for other users of
596 // AssignKeyEventData(), it doesn't copy this.
597 keyEvent.mNativeKeyEvent = aKeyboardEvent.mNativeKeyEvent;
598 } else {
599 // If it's not a keyboard event for native key event, we should ensure that
600 // mNativeKeyEvent and mPluginEvent are null/empty.
601 keyEvent.mNativeKeyEvent = nullptr;
602 keyEvent.mPluginEvent.Clear();
604 // TODO: Manage mUniqueId here.
606 // Request the alternative char codes for the key event.
607 // eKeyDown also needs alternative char codes because nsXBLWindowKeyHandler
608 // needs to check if a following keypress event is reserved by chrome for
609 // stopping propagation of its preceding keydown event.
610 keyEvent.mAlternativeCharCodes.Clear();
611 if ((WidgetKeyboardEvent::IsKeyDownOrKeyDownOnPlugin(aMessage) ||
612 aMessage == eKeyPress) &&
613 (aNeedsCallback || keyEvent.IsControl() || keyEvent.IsAlt() ||
614 keyEvent.IsMeta() || keyEvent.IsOS())) {
615 nsCOMPtr<TextEventDispatcherListener> listener =
616 do_QueryReferent(mListener);
617 if (listener) {
618 DebugOnly<WidgetKeyboardEvent> original(keyEvent);
619 listener->WillDispatchKeyboardEvent(this, keyEvent, aIndexOfKeypress,
620 aData);
621 MOZ_ASSERT(keyEvent.mMessage ==
622 static_cast<WidgetKeyboardEvent&>(original).mMessage);
623 MOZ_ASSERT(keyEvent.mKeyCode ==
624 static_cast<WidgetKeyboardEvent&>(original).mKeyCode);
625 MOZ_ASSERT(keyEvent.mLocation ==
626 static_cast<WidgetKeyboardEvent&>(original).mLocation);
627 MOZ_ASSERT(keyEvent.mIsRepeat ==
628 static_cast<WidgetKeyboardEvent&>(original).mIsRepeat);
629 MOZ_ASSERT(keyEvent.mIsComposing ==
630 static_cast<WidgetKeyboardEvent&>(original).mIsComposing);
631 MOZ_ASSERT(keyEvent.mKeyNameIndex ==
632 static_cast<WidgetKeyboardEvent&>(original).mKeyNameIndex);
633 MOZ_ASSERT(keyEvent.mCodeNameIndex ==
634 static_cast<WidgetKeyboardEvent&>(original).mCodeNameIndex);
635 MOZ_ASSERT(keyEvent.mKeyValue ==
636 static_cast<WidgetKeyboardEvent&>(original).mKeyValue);
637 MOZ_ASSERT(keyEvent.mCodeValue ==
638 static_cast<WidgetKeyboardEvent&>(original).mCodeValue);
642 if (StaticPrefs::
643 dom_keyboardevent_keypress_dispatch_non_printable_keys_only_system_group_in_content() &&
644 keyEvent.mMessage == eKeyPress &&
645 !keyEvent.ShouldKeyPressEventBeFiredOnContent()) {
646 // Note that even if we set it to true, this may be overwritten by
647 // PresShell::DispatchEventToDOM().
648 keyEvent.mFlags.mOnlySystemGroupDispatchInContent = true;
651 DispatchInputEvent(mWidget, keyEvent, aStatus);
652 return true;
655 bool TextEventDispatcher::MaybeDispatchKeypressEvents(
656 const WidgetKeyboardEvent& aKeyboardEvent, nsEventStatus& aStatus,
657 void* aData, bool aNeedsCallback) {
658 // If the key event was consumed, keypress event shouldn't be fired.
659 if (aStatus == nsEventStatus_eConsumeNoDefault) {
660 return false;
663 // If the key shouldn't cause keypress events, don't fire them.
664 if (!aKeyboardEvent.ShouldCauseKeypressEvents()) {
665 return false;
668 // If the key isn't a printable key or just inputting one character or
669 // no character, we should dispatch only one keypress. Otherwise, i.e.,
670 // if the key is a printable key and inputs multiple characters, keypress
671 // event should be dispatched the count of inputting characters times.
672 size_t keypressCount =
673 aKeyboardEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING
675 : std::max(static_cast<nsAString::size_type>(1),
676 aKeyboardEvent.mKeyValue.Length());
677 bool isDispatched = false;
678 bool consumed = false;
679 for (size_t i = 0; i < keypressCount; i++) {
680 aStatus = nsEventStatus_eIgnore;
681 if (!DispatchKeyboardEventInternal(eKeyPress, aKeyboardEvent, aStatus,
682 aData, i, aNeedsCallback)) {
683 // The widget must have been gone.
684 break;
686 isDispatched = true;
687 if (!consumed) {
688 consumed = (aStatus == nsEventStatus_eConsumeNoDefault);
692 // If one of the keypress event was consumed, return ConsumeNoDefault.
693 if (consumed) {
694 aStatus = nsEventStatus_eConsumeNoDefault;
697 return isDispatched;
700 /******************************************************************************
701 * TextEventDispatcher::PendingComposition
702 *****************************************************************************/
704 TextEventDispatcher::PendingComposition::PendingComposition() { Clear(); }
706 void TextEventDispatcher::PendingComposition::Clear() {
707 mString.Truncate();
708 mClauses = nullptr;
709 mCaret.mRangeType = TextRangeType::eUninitialized;
710 mReplacedNativeLineBreakers = false;
713 void TextEventDispatcher::PendingComposition::EnsureClauseArray() {
714 if (mClauses) {
715 return;
717 mClauses = new TextRangeArray();
720 nsresult TextEventDispatcher::PendingComposition::SetString(
721 const nsAString& aString) {
722 MOZ_ASSERT(!mReplacedNativeLineBreakers);
723 mString = aString;
724 return NS_OK;
727 nsresult TextEventDispatcher::PendingComposition::AppendClause(
728 uint32_t aLength, TextRangeType aTextRangeType) {
729 MOZ_ASSERT(!mReplacedNativeLineBreakers);
731 if (NS_WARN_IF(!aLength)) {
732 return NS_ERROR_INVALID_ARG;
735 switch (aTextRangeType) {
736 case TextRangeType::eRawClause:
737 case TextRangeType::eSelectedRawClause:
738 case TextRangeType::eConvertedClause:
739 case TextRangeType::eSelectedClause: {
740 EnsureClauseArray();
741 TextRange textRange;
742 textRange.mStartOffset =
743 mClauses->IsEmpty() ? 0 : mClauses->LastElement().mEndOffset;
744 textRange.mEndOffset = textRange.mStartOffset + aLength;
745 textRange.mRangeType = aTextRangeType;
746 mClauses->AppendElement(textRange);
747 return NS_OK;
749 default:
750 return NS_ERROR_INVALID_ARG;
754 nsresult TextEventDispatcher::PendingComposition::SetCaret(uint32_t aOffset,
755 uint32_t aLength) {
756 MOZ_ASSERT(!mReplacedNativeLineBreakers);
758 mCaret.mStartOffset = aOffset;
759 mCaret.mEndOffset = mCaret.mStartOffset + aLength;
760 mCaret.mRangeType = TextRangeType::eCaret;
761 return NS_OK;
764 nsresult TextEventDispatcher::PendingComposition::Set(
765 const nsAString& aString, const TextRangeArray* aRanges) {
766 Clear();
768 nsresult rv = SetString(aString);
769 if (NS_WARN_IF(NS_FAILED(rv))) {
770 return rv;
773 if (!aRanges || aRanges->IsEmpty()) {
774 // Create dummy range if mString isn't empty.
775 if (!mString.IsEmpty()) {
776 rv = AppendClause(mString.Length(), TextRangeType::eRawClause);
777 if (NS_WARN_IF(NS_FAILED(rv))) {
778 return rv;
780 ReplaceNativeLineBreakers();
782 return NS_OK;
785 // Adjust offsets in the ranges for XP linefeed character (only \n).
786 for (uint32_t i = 0; i < aRanges->Length(); ++i) {
787 TextRange range = aRanges->ElementAt(i);
788 if (range.mRangeType == TextRangeType::eCaret) {
789 mCaret = range;
790 } else {
791 EnsureClauseArray();
792 mClauses->AppendElement(range);
795 ReplaceNativeLineBreakers();
796 return NS_OK;
799 void TextEventDispatcher::PendingComposition::ReplaceNativeLineBreakers() {
800 mReplacedNativeLineBreakers = true;
802 // If the composition string is empty, we don't need to do anything.
803 if (mString.IsEmpty()) {
804 return;
807 nsAutoString nativeString(mString);
808 // Don't expose CRLF nor CR to web contents, instead, use LF.
809 mString.ReplaceSubstring(u"\r\n"_ns, u"\n"_ns);
810 mString.ReplaceSubstring(u"\r"_ns, u"\n"_ns);
812 // If the length isn't changed, we don't need to adjust any offset and length
813 // of mClauses nor mCaret.
814 if (nativeString.Length() == mString.Length()) {
815 return;
818 if (mClauses) {
819 for (TextRange& clause : *mClauses) {
820 AdjustRange(clause, nativeString);
823 if (mCaret.mRangeType == TextRangeType::eCaret) {
824 AdjustRange(mCaret, nativeString);
828 // static
829 void TextEventDispatcher::PendingComposition::AdjustRange(
830 TextRange& aRange, const nsAString& aNativeString) {
831 TextRange nativeRange = aRange;
832 // XXX Following code wastes runtime cost because this causes computing
833 // mStartOffset for each clause from the start of composition string.
834 // If we'd make TextRange have only its length, we don't need to do
835 // this. However, this must not be so serious problem because
836 // composition string is usually short and separated as a few clauses.
837 if (nativeRange.mStartOffset > 0) {
838 nsAutoString preText(Substring(aNativeString, 0, nativeRange.mStartOffset));
839 preText.ReplaceSubstring(u"\r\n"_ns, u"\n"_ns);
840 aRange.mStartOffset = preText.Length();
842 if (nativeRange.Length() == 0) {
843 aRange.mEndOffset = aRange.mStartOffset;
844 } else {
845 nsAutoString clause(Substring(aNativeString, nativeRange.mStartOffset,
846 nativeRange.Length()));
847 clause.ReplaceSubstring(u"\r\n"_ns, u"\n"_ns);
848 aRange.mEndOffset = aRange.mStartOffset + clause.Length();
852 nsresult TextEventDispatcher::PendingComposition::Flush(
853 TextEventDispatcher* aDispatcher, nsEventStatus& aStatus,
854 const WidgetEventTime* aEventTime) {
855 aStatus = nsEventStatus_eIgnore;
857 nsresult rv = aDispatcher->GetState();
858 if (NS_WARN_IF(NS_FAILED(rv))) {
859 return rv;
862 if (mClauses && !mClauses->IsEmpty() &&
863 mClauses->LastElement().mEndOffset != mString.Length()) {
864 NS_WARNING(
865 "Sum of length of the all clauses must be same as the string "
866 "length");
867 Clear();
868 return NS_ERROR_ILLEGAL_VALUE;
870 if (mCaret.mRangeType == TextRangeType::eCaret) {
871 if (mCaret.mEndOffset > mString.Length()) {
872 NS_WARNING("Caret position is out of the composition string");
873 Clear();
874 return NS_ERROR_ILLEGAL_VALUE;
876 EnsureClauseArray();
877 mClauses->AppendElement(mCaret);
880 // If the composition string is set without Set(), we need to replace native
881 // line breakers in the composition string with XP line breaker.
882 if (!mReplacedNativeLineBreakers) {
883 ReplaceNativeLineBreakers();
886 RefPtr<TextEventDispatcher> kungFuDeathGrip(aDispatcher);
887 nsCOMPtr<nsIWidget> widget(aDispatcher->mWidget);
888 WidgetCompositionEvent compChangeEvent(true, eCompositionChange, widget);
889 aDispatcher->InitEvent(compChangeEvent);
890 if (aEventTime) {
891 compChangeEvent.AssignEventTime(*aEventTime);
893 compChangeEvent.mData = mString;
894 // If mString comes from TextInputProcessor, it may be void, but editor
895 // requires non-void string even when it's empty.
896 compChangeEvent.mData.SetIsVoid(false);
897 if (mClauses) {
898 MOZ_ASSERT(!mClauses->IsEmpty(),
899 "mClauses must be non-empty array when it's not nullptr");
900 compChangeEvent.mRanges = mClauses;
903 // While this method dispatches a composition event, some other event handler
904 // cause more clauses to be added. So, we should clear pending composition
905 // before dispatching the event.
906 Clear();
908 rv = aDispatcher->StartCompositionAutomaticallyIfNecessary(aStatus,
909 aEventTime);
910 if (NS_WARN_IF(NS_FAILED(rv))) {
911 return rv;
913 if (aStatus == nsEventStatus_eConsumeNoDefault) {
914 return NS_OK;
916 rv = aDispatcher->DispatchEvent(widget, compChangeEvent, aStatus);
917 if (NS_WARN_IF(NS_FAILED(rv))) {
918 return rv;
921 return NS_OK;
924 } // namespace widget
925 } // namespace mozilla