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