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