1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "mozilla/dom/Event.h"
8 #include "mozilla/EventForwards.h"
9 #include "mozilla/Maybe.h"
10 #include "mozilla/StaticPrefs_test.h"
11 #include "mozilla/TextEventDispatcher.h"
12 #include "mozilla/TextEvents.h"
13 #include "mozilla/TextInputProcessor.h"
14 #include "mozilla/WritingModes.h"
15 #include "mozilla/widget/IMEData.h"
16 #include "mozilla/dom/KeyboardEvent.h"
17 #include "nsContentUtils.h"
18 #include "nsIDocShell.h"
19 #include "nsIWidget.h"
20 #include "nsPIDOMWindow.h"
21 #include "nsPresContext.h"
22 
23 using mozilla::dom::Event;
24 using mozilla::dom::KeyboardEvent;
25 using namespace mozilla::widget;
26 
27 namespace mozilla {
28 
29 /******************************************************************************
30  * TextInputProcessorNotification
31  ******************************************************************************/
32 
33 class TextInputProcessorNotification final
34     : public nsITextInputProcessorNotification {
35   typedef IMENotification::SelectionChangeData SelectionChangeData;
36   typedef IMENotification::SelectionChangeDataBase SelectionChangeDataBase;
37   typedef IMENotification::TextChangeData TextChangeData;
38   typedef IMENotification::TextChangeDataBase TextChangeDataBase;
39 
40  public:
TextInputProcessorNotification(const char * aType)41   explicit TextInputProcessorNotification(const char* aType)
42       : mType(aType), mTextChangeData() {}
43 
TextInputProcessorNotification(const TextChangeDataBase & aTextChangeData)44   explicit TextInputProcessorNotification(
45       const TextChangeDataBase& aTextChangeData)
46       : mType("notify-text-change"), mTextChangeData(aTextChangeData) {}
47 
TextInputProcessorNotification(const SelectionChangeDataBase & aSelectionChangeData)48   explicit TextInputProcessorNotification(
49       const SelectionChangeDataBase& aSelectionChangeData)
50       : mType("notify-selection-change"),
51         mSelectionChangeData(aSelectionChangeData) {
52     // SelectionChangeDataBase::mString still refers nsString instance owned
53     // by aSelectionChangeData.  So, this needs to copy the instance.
54     nsString* string = new nsString(aSelectionChangeData.String());
55     mSelectionChangeData.mString = string;
56   }
57 
58   NS_DECL_ISUPPORTS
59 
GetType(nsACString & aType)60   NS_IMETHOD GetType(nsACString& aType) final {
61     aType = mType;
62     return NS_OK;
63   }
64 
65   // "notify-text-change" and "notify-selection-change"
GetOffset(uint32_t * aOffset)66   NS_IMETHOD GetOffset(uint32_t* aOffset) final {
67     if (NS_WARN_IF(!aOffset)) {
68       return NS_ERROR_INVALID_ARG;
69     }
70     if (IsSelectionChange()) {
71       *aOffset = mSelectionChangeData.mOffset;
72       return NS_OK;
73     }
74     if (IsTextChange()) {
75       *aOffset = mTextChangeData.mStartOffset;
76       return NS_OK;
77     }
78     return NS_ERROR_NOT_AVAILABLE;
79   }
80 
81   // "notify-selection-change"
GetText(nsAString & aText)82   NS_IMETHOD GetText(nsAString& aText) final {
83     if (IsSelectionChange()) {
84       aText = mSelectionChangeData.String();
85       return NS_OK;
86     }
87     return NS_ERROR_NOT_AVAILABLE;
88   }
89 
GetCollapsed(bool * aCollapsed)90   NS_IMETHOD GetCollapsed(bool* aCollapsed) final {
91     if (NS_WARN_IF(!aCollapsed)) {
92       return NS_ERROR_INVALID_ARG;
93     }
94     if (IsSelectionChange()) {
95       *aCollapsed = mSelectionChangeData.IsCollapsed();
96       return NS_OK;
97     }
98     return NS_ERROR_NOT_AVAILABLE;
99   }
100 
GetLength(uint32_t * aLength)101   NS_IMETHOD GetLength(uint32_t* aLength) final {
102     if (NS_WARN_IF(!aLength)) {
103       return NS_ERROR_INVALID_ARG;
104     }
105     if (IsSelectionChange()) {
106       *aLength = mSelectionChangeData.Length();
107       return NS_OK;
108     }
109     return NS_ERROR_NOT_AVAILABLE;
110   }
111 
GetReversed(bool * aReversed)112   NS_IMETHOD GetReversed(bool* aReversed) final {
113     if (NS_WARN_IF(!aReversed)) {
114       return NS_ERROR_INVALID_ARG;
115     }
116     if (IsSelectionChange()) {
117       *aReversed = mSelectionChangeData.mReversed;
118       return NS_OK;
119     }
120     return NS_ERROR_NOT_AVAILABLE;
121   }
122 
GetWritingMode(nsACString & aWritingMode)123   NS_IMETHOD GetWritingMode(nsACString& aWritingMode) final {
124     if (IsSelectionChange()) {
125       WritingMode writingMode = mSelectionChangeData.GetWritingMode();
126       if (!writingMode.IsVertical()) {
127         aWritingMode.AssignLiteral("horizontal-tb");
128       } else if (writingMode.IsVerticalLR()) {
129         aWritingMode.AssignLiteral("vertical-lr");
130       } else {
131         aWritingMode.AssignLiteral("vertical-rl");
132       }
133       return NS_OK;
134     }
135     return NS_ERROR_NOT_AVAILABLE;
136   }
137 
GetCausedByComposition(bool * aCausedByComposition)138   NS_IMETHOD GetCausedByComposition(bool* aCausedByComposition) final {
139     if (NS_WARN_IF(!aCausedByComposition)) {
140       return NS_ERROR_INVALID_ARG;
141     }
142     if (IsSelectionChange()) {
143       *aCausedByComposition = mSelectionChangeData.mCausedByComposition;
144       return NS_OK;
145     }
146     return NS_ERROR_NOT_AVAILABLE;
147   }
148 
GetCausedBySelectionEvent(bool * aCausedBySelectionEvent)149   NS_IMETHOD GetCausedBySelectionEvent(bool* aCausedBySelectionEvent) final {
150     if (NS_WARN_IF(!aCausedBySelectionEvent)) {
151       return NS_ERROR_INVALID_ARG;
152     }
153     if (IsSelectionChange()) {
154       *aCausedBySelectionEvent = mSelectionChangeData.mCausedBySelectionEvent;
155       return NS_OK;
156     }
157     return NS_ERROR_NOT_AVAILABLE;
158   }
159 
GetOccurredDuringComposition(bool * aOccurredDuringComposition)160   NS_IMETHOD GetOccurredDuringComposition(
161       bool* aOccurredDuringComposition) final {
162     if (NS_WARN_IF(!aOccurredDuringComposition)) {
163       return NS_ERROR_INVALID_ARG;
164     }
165     if (IsSelectionChange()) {
166       *aOccurredDuringComposition =
167           mSelectionChangeData.mOccurredDuringComposition;
168       return NS_OK;
169     }
170     return NS_ERROR_NOT_AVAILABLE;
171   }
172 
173   // "notify-text-change"
GetRemovedLength(uint32_t * aLength)174   NS_IMETHOD GetRemovedLength(uint32_t* aLength) final {
175     if (NS_WARN_IF(!aLength)) {
176       return NS_ERROR_INVALID_ARG;
177     }
178     if (IsTextChange()) {
179       *aLength = mTextChangeData.OldLength();
180       return NS_OK;
181     }
182     return NS_ERROR_NOT_AVAILABLE;
183   }
184 
GetAddedLength(uint32_t * aLength)185   NS_IMETHOD GetAddedLength(uint32_t* aLength) final {
186     if (NS_WARN_IF(!aLength)) {
187       return NS_ERROR_INVALID_ARG;
188     }
189     if (IsTextChange()) {
190       *aLength = mTextChangeData.NewLength();
191       return NS_OK;
192     }
193     return NS_ERROR_NOT_AVAILABLE;
194   }
195 
GetCausedOnlyByComposition(bool * aCausedOnlyByComposition)196   NS_IMETHOD GetCausedOnlyByComposition(bool* aCausedOnlyByComposition) final {
197     if (NS_WARN_IF(!aCausedOnlyByComposition)) {
198       return NS_ERROR_INVALID_ARG;
199     }
200     if (IsTextChange()) {
201       *aCausedOnlyByComposition = mTextChangeData.mCausedOnlyByComposition;
202       return NS_OK;
203     }
204     return NS_ERROR_NOT_AVAILABLE;
205   }
206 
GetIncludingChangesDuringComposition(bool * aIncludingChangesDuringComposition)207   NS_IMETHOD GetIncludingChangesDuringComposition(
208       bool* aIncludingChangesDuringComposition) final {
209     if (NS_WARN_IF(!aIncludingChangesDuringComposition)) {
210       return NS_ERROR_INVALID_ARG;
211     }
212     if (IsTextChange()) {
213       *aIncludingChangesDuringComposition =
214           mTextChangeData.mIncludingChangesDuringComposition;
215       return NS_OK;
216     }
217     return NS_ERROR_NOT_AVAILABLE;
218   }
219 
GetIncludingChangesWithoutComposition(bool * aIncludingChangesWithoutComposition)220   NS_IMETHOD GetIncludingChangesWithoutComposition(
221       bool* aIncludingChangesWithoutComposition) final {
222     if (NS_WARN_IF(!aIncludingChangesWithoutComposition)) {
223       return NS_ERROR_INVALID_ARG;
224     }
225     if (IsTextChange()) {
226       *aIncludingChangesWithoutComposition =
227           mTextChangeData.mIncludingChangesWithoutComposition;
228       return NS_OK;
229     }
230     return NS_ERROR_NOT_AVAILABLE;
231   }
232 
233  protected:
~TextInputProcessorNotification()234   virtual ~TextInputProcessorNotification() {
235     if (IsSelectionChange()) {
236       delete mSelectionChangeData.mString;
237       mSelectionChangeData.mString = nullptr;
238     }
239   }
240 
IsTextChange() const241   bool IsTextChange() const {
242     return mType.EqualsLiteral("notify-text-change");
243   }
244 
IsSelectionChange() const245   bool IsSelectionChange() const {
246     return mType.EqualsLiteral("notify-selection-change");
247   }
248 
249  private:
250   nsAutoCString mType;
251   union {
252     TextChangeDataBase mTextChangeData;
253     SelectionChangeDataBase mSelectionChangeData;
254   };
255 
TextInputProcessorNotification()256   TextInputProcessorNotification() : mTextChangeData() {}
257 };
258 
NS_IMPL_ISUPPORTS(TextInputProcessorNotification,nsITextInputProcessorNotification)259 NS_IMPL_ISUPPORTS(TextInputProcessorNotification,
260                   nsITextInputProcessorNotification)
261 
262 /******************************************************************************
263  * TextInputProcessor
264  ******************************************************************************/
265 
266 NS_IMPL_ISUPPORTS(TextInputProcessor, nsITextInputProcessor,
267                   TextEventDispatcherListener, nsISupportsWeakReference)
268 
269 TextInputProcessor::TextInputProcessor()
270     : mDispatcher(nullptr), mForTests(false) {}
271 
~TextInputProcessor()272 TextInputProcessor::~TextInputProcessor() {
273   if (mDispatcher && mDispatcher->IsComposing()) {
274     // If this is composing and not canceling the composition, nobody can steal
275     // the rights of TextEventDispatcher from this instance.  Therefore, this
276     // needs to cancel the composition here.
277     if (NS_SUCCEEDED(IsValidStateForComposition())) {
278       RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher);
279       nsEventStatus status = nsEventStatus_eIgnore;
280       kungFuDeathGrip->CommitComposition(status, &EmptyString());
281     }
282   }
283 }
284 
IsComposing() const285 bool TextInputProcessor::IsComposing() const {
286   return mDispatcher && mDispatcher->IsComposing();
287 }
288 
289 NS_IMETHODIMP
GetHasComposition(bool * aHasComposition)290 TextInputProcessor::GetHasComposition(bool* aHasComposition) {
291   MOZ_RELEASE_ASSERT(aHasComposition, "aHasComposition must not be nullptr");
292   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
293   *aHasComposition = IsComposing();
294   return NS_OK;
295 }
296 
297 NS_IMETHODIMP
BeginInputTransaction(mozIDOMWindow * aWindow,nsITextInputProcessorCallback * aCallback,bool * aSucceeded)298 TextInputProcessor::BeginInputTransaction(
299     mozIDOMWindow* aWindow, nsITextInputProcessorCallback* aCallback,
300     bool* aSucceeded) {
301   MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr");
302   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
303   if (NS_WARN_IF(!aCallback)) {
304     *aSucceeded = false;
305     return NS_ERROR_INVALID_ARG;
306   }
307   return BeginInputTransactionInternal(aWindow, aCallback, false, *aSucceeded);
308 }
309 
310 NS_IMETHODIMP
BeginInputTransactionForTests(mozIDOMWindow * aWindow,nsITextInputProcessorCallback * aCallback,uint8_t aOptionalArgc,bool * aSucceeded)311 TextInputProcessor::BeginInputTransactionForTests(
312     mozIDOMWindow* aWindow, nsITextInputProcessorCallback* aCallback,
313     uint8_t aOptionalArgc, bool* aSucceeded) {
314   MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr");
315   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
316   nsITextInputProcessorCallback* callback =
317       aOptionalArgc >= 1 ? aCallback : nullptr;
318   return BeginInputTransactionInternal(aWindow, callback, true, *aSucceeded);
319 }
320 
BeginInputTransactionForFuzzing(nsPIDOMWindowInner * aWindow,nsITextInputProcessorCallback * aCallback,bool * aSucceeded)321 nsresult TextInputProcessor::BeginInputTransactionForFuzzing(
322     nsPIDOMWindowInner* aWindow, nsITextInputProcessorCallback* aCallback,
323     bool* aSucceeded) {
324   MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr");
325   return BeginInputTransactionInternal(aWindow, aCallback, false, *aSucceeded);
326 }
327 
BeginInputTransactionInternal(mozIDOMWindow * aWindow,nsITextInputProcessorCallback * aCallback,bool aForTests,bool & aSucceeded)328 nsresult TextInputProcessor::BeginInputTransactionInternal(
329     mozIDOMWindow* aWindow, nsITextInputProcessorCallback* aCallback,
330     bool aForTests, bool& aSucceeded) {
331   aSucceeded = false;
332   if (NS_WARN_IF(!aWindow)) {
333     return NS_ERROR_INVALID_ARG;
334   }
335   nsCOMPtr<nsPIDOMWindowInner> pWindow = nsPIDOMWindowInner::From(aWindow);
336   if (NS_WARN_IF(!pWindow)) {
337     return NS_ERROR_INVALID_ARG;
338   }
339   nsCOMPtr<nsIDocShell> docShell(pWindow->GetDocShell());
340   if (NS_WARN_IF(!docShell)) {
341     return NS_ERROR_FAILURE;
342   }
343   RefPtr<nsPresContext> presContext = docShell->GetPresContext();
344   if (NS_WARN_IF(!presContext)) {
345     return NS_ERROR_FAILURE;
346   }
347   nsCOMPtr<nsIWidget> widget = presContext->GetRootWidget();
348   if (NS_WARN_IF(!widget)) {
349     return NS_ERROR_FAILURE;
350   }
351 
352   RefPtr<TextEventDispatcher> dispatcher = widget->GetTextEventDispatcher();
353   MOZ_RELEASE_ASSERT(dispatcher, "TextEventDispatcher must not be null");
354 
355   // If the instance was initialized and is being initialized for same
356   // dispatcher and same purpose, we don't need to initialize the dispatcher
357   // again.
358   if (mDispatcher && dispatcher == mDispatcher && aCallback == mCallback &&
359       aForTests == mForTests) {
360     aSucceeded = true;
361     return NS_OK;
362   }
363 
364   // If this instance is composing or dispatching an event, don't allow to
365   // initialize again.  Especially, if we allow to begin input transaction with
366   // another TextEventDispatcher during dispatching an event, it may cause that
367   // nobody cannot begin input transaction with it if the last event causes
368   // opening modal dialog.
369   if (mDispatcher &&
370       (mDispatcher->IsComposing() || mDispatcher->IsDispatchingEvent())) {
371     return NS_ERROR_ALREADY_INITIALIZED;
372   }
373 
374   // And also if another instance is composing with the new dispatcher or
375   // dispatching an event, it'll fail to steal its ownership.  Then, we should
376   // not throw an exception, just return false.
377   if (dispatcher->IsComposing() || dispatcher->IsDispatchingEvent()) {
378     return NS_OK;
379   }
380 
381   // This instance has finished preparing to link to the dispatcher.  Therefore,
382   // let's forget the old dispatcher and purpose.
383   if (mDispatcher) {
384     mDispatcher->EndInputTransaction(this);
385     if (NS_WARN_IF(mDispatcher)) {
386       // Forcibly initialize the members if we failed to end the input
387       // transaction.
388       UnlinkFromTextEventDispatcher();
389     }
390   }
391 
392   nsresult rv = NS_OK;
393   if (aForTests) {
394     bool isAPZAware = StaticPrefs::test_events_async_enabled();
395     rv = dispatcher->BeginTestInputTransaction(this, isAPZAware);
396   } else {
397     rv = dispatcher->BeginInputTransaction(this);
398   }
399 
400   if (NS_WARN_IF(NS_FAILED(rv))) {
401     return rv;
402   }
403 
404   mDispatcher = dispatcher;
405   mCallback = aCallback;
406   mForTests = aForTests;
407   aSucceeded = true;
408   return NS_OK;
409 }
410 
UnlinkFromTextEventDispatcher()411 void TextInputProcessor::UnlinkFromTextEventDispatcher() {
412   mDispatcher = nullptr;
413   mForTests = false;
414   if (mCallback) {
415     nsCOMPtr<nsITextInputProcessorCallback> callback(mCallback);
416     mCallback = nullptr;
417 
418     RefPtr<TextInputProcessorNotification> notification =
419         new TextInputProcessorNotification("notify-end-input-transaction");
420     bool result = false;
421     callback->OnNotify(this, notification, &result);
422   }
423 }
424 
IsValidStateForComposition()425 nsresult TextInputProcessor::IsValidStateForComposition() {
426   if (NS_WARN_IF(!mDispatcher)) {
427     return NS_ERROR_NOT_INITIALIZED;
428   }
429 
430   nsresult rv = mDispatcher->GetState();
431   if (NS_WARN_IF(NS_FAILED(rv))) {
432     return rv;
433   }
434 
435   return NS_OK;
436 }
437 
IsValidEventTypeForComposition(const WidgetKeyboardEvent & aKeyboardEvent) const438 bool TextInputProcessor::IsValidEventTypeForComposition(
439     const WidgetKeyboardEvent& aKeyboardEvent) const {
440   // The key event type of composition methods must be "", "keydown" or "keyup".
441   if (aKeyboardEvent.mMessage == eKeyDown ||
442       aKeyboardEvent.mMessage == eKeyUp) {
443     return true;
444   }
445   if (aKeyboardEvent.mMessage == eUnidentifiedEvent &&
446       aKeyboardEvent.mSpecifiedEventType &&
447       nsDependentAtomString(aKeyboardEvent.mSpecifiedEventType)
448           .EqualsLiteral("on")) {
449     return true;
450   }
451   return false;
452 }
453 
454 TextInputProcessor::EventDispatcherResult
MaybeDispatchKeydownForComposition(const WidgetKeyboardEvent * aKeyboardEvent,uint32_t aKeyFlags)455 TextInputProcessor::MaybeDispatchKeydownForComposition(
456     const WidgetKeyboardEvent* aKeyboardEvent, uint32_t aKeyFlags) {
457   EventDispatcherResult result;
458 
459   result.mResult = IsValidStateForComposition();
460   if (NS_WARN_IF(NS_FAILED(result.mResult))) {
461     result.mCanContinue = false;
462     return result;
463   }
464 
465   if (!aKeyboardEvent) {
466     return result;
467   }
468 
469   // If the mMessage is eKeyUp, the caller doesn't want TIP to dispatch
470   // eKeyDown event.
471   if (aKeyboardEvent->mMessage == eKeyUp) {
472     return result;
473   }
474 
475   // Modifier keys are not allowed because managing modifier state in this
476   // method makes this messy.
477   if (NS_WARN_IF(aKeyboardEvent->IsModifierKeyEvent())) {
478     result.mResult = NS_ERROR_INVALID_ARG;
479     result.mCanContinue = false;
480     return result;
481   }
482 
483   uint32_t consumedFlags = 0;
484 
485   result.mResult =
486       KeydownInternal(*aKeyboardEvent, aKeyFlags, false, consumedFlags);
487   result.mDoDefault = !consumedFlags;
488   if (NS_WARN_IF(NS_FAILED(result.mResult))) {
489     result.mCanContinue = false;
490     return result;
491   }
492 
493   result.mCanContinue = NS_SUCCEEDED(IsValidStateForComposition());
494   return result;
495 }
496 
497 TextInputProcessor::EventDispatcherResult
MaybeDispatchKeyupForComposition(const WidgetKeyboardEvent * aKeyboardEvent,uint32_t aKeyFlags)498 TextInputProcessor::MaybeDispatchKeyupForComposition(
499     const WidgetKeyboardEvent* aKeyboardEvent, uint32_t aKeyFlags) {
500   EventDispatcherResult result;
501 
502   if (!aKeyboardEvent) {
503     return result;
504   }
505 
506   // If the mMessage is eKeyDown, the caller doesn't want TIP to dispatch
507   // eKeyUp event.
508   if (aKeyboardEvent->mMessage == eKeyDown) {
509     return result;
510   }
511 
512   // If the widget has been destroyed, we can do nothing here.
513   result.mResult = IsValidStateForComposition();
514   if (NS_FAILED(result.mResult)) {
515     result.mCanContinue = false;
516     return result;
517   }
518 
519   result.mResult = KeyupInternal(*aKeyboardEvent, aKeyFlags, result.mDoDefault);
520   if (NS_WARN_IF(NS_FAILED(result.mResult))) {
521     result.mCanContinue = false;
522     return result;
523   }
524 
525   result.mCanContinue = NS_SUCCEEDED(IsValidStateForComposition());
526   return result;
527 }
528 
PrepareKeyboardEventForComposition(KeyboardEvent * aDOMKeyEvent,uint32_t & aKeyFlags,uint8_t aOptionalArgc,WidgetKeyboardEvent * & aKeyboardEvent)529 nsresult TextInputProcessor::PrepareKeyboardEventForComposition(
530     KeyboardEvent* aDOMKeyEvent, uint32_t& aKeyFlags, uint8_t aOptionalArgc,
531     WidgetKeyboardEvent*& aKeyboardEvent) {
532   aKeyboardEvent = nullptr;
533 
534   aKeyboardEvent = aOptionalArgc && aDOMKeyEvent
535                        ? aDOMKeyEvent->WidgetEventPtr()->AsKeyboardEvent()
536                        : nullptr;
537   if (!aKeyboardEvent || aOptionalArgc < 2) {
538     aKeyFlags = 0;
539   }
540 
541   if (!aKeyboardEvent) {
542     return NS_OK;
543   }
544 
545   if (NS_WARN_IF(!IsValidEventTypeForComposition(*aKeyboardEvent))) {
546     return NS_ERROR_INVALID_ARG;
547   }
548 
549   return NS_OK;
550 }
551 
552 NS_IMETHODIMP
StartComposition(Event * aDOMKeyEvent,uint32_t aKeyFlags,uint8_t aOptionalArgc,bool * aSucceeded)553 TextInputProcessor::StartComposition(Event* aDOMKeyEvent, uint32_t aKeyFlags,
554                                      uint8_t aOptionalArgc, bool* aSucceeded) {
555   MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr");
556   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
557   *aSucceeded = false;
558 
559   RefPtr<KeyboardEvent> keyEvent;
560   if (aDOMKeyEvent) {
561     keyEvent = aDOMKeyEvent->AsKeyboardEvent();
562     if (NS_WARN_IF(!keyEvent)) {
563       return NS_ERROR_INVALID_ARG;
564     }
565   }
566 
567   RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher);
568 
569   WidgetKeyboardEvent* keyboardEvent;
570   nsresult rv = PrepareKeyboardEventForComposition(
571       keyEvent, aKeyFlags, aOptionalArgc, keyboardEvent);
572   if (NS_WARN_IF(NS_FAILED(rv))) {
573     return rv;
574   }
575 
576   EventDispatcherResult dispatcherResult =
577       MaybeDispatchKeydownForComposition(keyboardEvent, aKeyFlags);
578   if (NS_WARN_IF(NS_FAILED(dispatcherResult.mResult)) ||
579       !dispatcherResult.mCanContinue) {
580     return dispatcherResult.mResult;
581   }
582 
583   if (dispatcherResult.mDoDefault) {
584     nsEventStatus status = nsEventStatus_eIgnore;
585     rv = kungFuDeathGrip->StartComposition(status);
586     *aSucceeded = status != nsEventStatus_eConsumeNoDefault &&
587                   kungFuDeathGrip && kungFuDeathGrip->IsComposing();
588   }
589 
590   MaybeDispatchKeyupForComposition(keyboardEvent, aKeyFlags);
591 
592   if (NS_WARN_IF(NS_FAILED(rv))) {
593     return rv;
594   }
595   return NS_OK;
596 }
597 
598 NS_IMETHODIMP
SetPendingCompositionString(const nsAString & aString)599 TextInputProcessor::SetPendingCompositionString(const nsAString& aString) {
600   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
601   RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher);
602   nsresult rv = IsValidStateForComposition();
603   if (NS_WARN_IF(NS_FAILED(rv))) {
604     return rv;
605   }
606   return kungFuDeathGrip->SetPendingCompositionString(aString);
607 }
608 
609 NS_IMETHODIMP
AppendClauseToPendingComposition(uint32_t aLength,uint32_t aAttribute)610 TextInputProcessor::AppendClauseToPendingComposition(uint32_t aLength,
611                                                      uint32_t aAttribute) {
612   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
613   RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher);
614   TextRangeType textRangeType;
615   switch (aAttribute) {
616     case ATTR_RAW_CLAUSE:
617     case ATTR_SELECTED_RAW_CLAUSE:
618     case ATTR_CONVERTED_CLAUSE:
619     case ATTR_SELECTED_CLAUSE:
620       textRangeType = ToTextRangeType(aAttribute);
621       break;
622     default:
623       return NS_ERROR_INVALID_ARG;
624   }
625   nsresult rv = IsValidStateForComposition();
626   if (NS_WARN_IF(NS_FAILED(rv))) {
627     return rv;
628   }
629   return kungFuDeathGrip->AppendClauseToPendingComposition(aLength,
630                                                            textRangeType);
631 }
632 
633 NS_IMETHODIMP
SetCaretInPendingComposition(uint32_t aOffset)634 TextInputProcessor::SetCaretInPendingComposition(uint32_t aOffset) {
635   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
636   RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher);
637   nsresult rv = IsValidStateForComposition();
638   if (NS_WARN_IF(NS_FAILED(rv))) {
639     return rv;
640   }
641   return kungFuDeathGrip->SetCaretInPendingComposition(aOffset, 0);
642 }
643 
644 NS_IMETHODIMP
FlushPendingComposition(Event * aDOMKeyEvent,uint32_t aKeyFlags,uint8_t aOptionalArgc,bool * aSucceeded)645 TextInputProcessor::FlushPendingComposition(Event* aDOMKeyEvent,
646                                             uint32_t aKeyFlags,
647                                             uint8_t aOptionalArgc,
648                                             bool* aSucceeded) {
649   MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr");
650   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
651 
652   // Even if this doesn't flush pending composition actually, we need to reset
653   // pending composition for starting next composition with new user input.
654   AutoPendingCompositionResetter resetter(this);
655 
656   *aSucceeded = false;
657   RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher);
658   bool wasComposing = IsComposing();
659 
660   RefPtr<KeyboardEvent> keyEvent;
661   if (aDOMKeyEvent) {
662     keyEvent = aDOMKeyEvent->AsKeyboardEvent();
663     if (NS_WARN_IF(!keyEvent)) {
664       return NS_ERROR_INVALID_ARG;
665     }
666   }
667 
668   WidgetKeyboardEvent* keyboardEvent;
669   nsresult rv = PrepareKeyboardEventForComposition(
670       keyEvent, aKeyFlags, aOptionalArgc, keyboardEvent);
671   if (NS_WARN_IF(NS_FAILED(rv))) {
672     return rv;
673   }
674 
675   EventDispatcherResult dispatcherResult =
676       MaybeDispatchKeydownForComposition(keyboardEvent, aKeyFlags);
677   if (NS_WARN_IF(NS_FAILED(dispatcherResult.mResult)) ||
678       !dispatcherResult.mCanContinue) {
679     return dispatcherResult.mResult;
680   }
681 
682   // Even if the preceding keydown event was consumed, if the composition
683   // was already started, we shouldn't prevent the change of composition.
684   if (dispatcherResult.mDoDefault || wasComposing) {
685     // Preceding keydown event may cause destroying the widget.
686     if (NS_FAILED(IsValidStateForComposition())) {
687       return NS_OK;
688     }
689     nsEventStatus status = nsEventStatus_eIgnore;
690     rv = kungFuDeathGrip->FlushPendingComposition(status);
691     *aSucceeded = status != nsEventStatus_eConsumeNoDefault;
692   }
693 
694   MaybeDispatchKeyupForComposition(keyboardEvent, aKeyFlags);
695 
696   if (NS_WARN_IF(NS_FAILED(rv))) {
697     return rv;
698   }
699   return NS_OK;
700 }
701 
702 NS_IMETHODIMP
CommitComposition(Event * aDOMKeyEvent,uint32_t aKeyFlags,uint8_t aOptionalArgc)703 TextInputProcessor::CommitComposition(Event* aDOMKeyEvent, uint32_t aKeyFlags,
704                                       uint8_t aOptionalArgc) {
705   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
706 
707   RefPtr<KeyboardEvent> keyEvent;
708   if (aDOMKeyEvent) {
709     keyEvent = aDOMKeyEvent->AsKeyboardEvent();
710     if (NS_WARN_IF(!keyEvent)) {
711       return NS_ERROR_INVALID_ARG;
712     }
713   }
714 
715   WidgetKeyboardEvent* keyboardEvent;
716   nsresult rv = PrepareKeyboardEventForComposition(
717       keyEvent, aKeyFlags, aOptionalArgc, keyboardEvent);
718   if (NS_WARN_IF(NS_FAILED(rv))) {
719     return rv;
720   }
721 
722   return CommitCompositionInternal(keyboardEvent, aKeyFlags);
723 }
724 
725 NS_IMETHODIMP
CommitCompositionWith(const nsAString & aCommitString,Event * aDOMKeyEvent,uint32_t aKeyFlags,uint8_t aOptionalArgc,bool * aSucceeded)726 TextInputProcessor::CommitCompositionWith(const nsAString& aCommitString,
727                                           Event* aDOMKeyEvent,
728                                           uint32_t aKeyFlags,
729                                           uint8_t aOptionalArgc,
730                                           bool* aSucceeded) {
731   MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr");
732   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
733 
734   RefPtr<KeyboardEvent> keyEvent;
735   if (aDOMKeyEvent) {
736     keyEvent = aDOMKeyEvent->AsKeyboardEvent();
737     if (NS_WARN_IF(!keyEvent)) {
738       return NS_ERROR_INVALID_ARG;
739     }
740   }
741 
742   WidgetKeyboardEvent* keyboardEvent;
743   nsresult rv = PrepareKeyboardEventForComposition(
744       keyEvent, aKeyFlags, aOptionalArgc, keyboardEvent);
745   if (NS_WARN_IF(NS_FAILED(rv))) {
746     return rv;
747   }
748 
749   return CommitCompositionInternal(keyboardEvent, aKeyFlags, &aCommitString,
750                                    aSucceeded);
751 }
752 
CommitCompositionInternal(const WidgetKeyboardEvent * aKeyboardEvent,uint32_t aKeyFlags,const nsAString * aCommitString,bool * aSucceeded)753 nsresult TextInputProcessor::CommitCompositionInternal(
754     const WidgetKeyboardEvent* aKeyboardEvent, uint32_t aKeyFlags,
755     const nsAString* aCommitString, bool* aSucceeded) {
756   if (aSucceeded) {
757     *aSucceeded = false;
758   }
759   RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher);
760   bool wasComposing = IsComposing();
761 
762   EventDispatcherResult dispatcherResult =
763       MaybeDispatchKeydownForComposition(aKeyboardEvent, aKeyFlags);
764   if (NS_WARN_IF(NS_FAILED(dispatcherResult.mResult)) ||
765       !dispatcherResult.mCanContinue) {
766     return dispatcherResult.mResult;
767   }
768 
769   // Even if the preceding keydown event was consumed, if the composition
770   // was already started, we shouldn't prevent the commit of composition.
771   nsresult rv = NS_OK;
772   if (dispatcherResult.mDoDefault || wasComposing) {
773     // Preceding keydown event may cause destroying the widget.
774     if (NS_FAILED(IsValidStateForComposition())) {
775       return NS_OK;
776     }
777     nsEventStatus status = nsEventStatus_eIgnore;
778     rv = kungFuDeathGrip->CommitComposition(status, aCommitString);
779     if (aSucceeded) {
780       *aSucceeded = status != nsEventStatus_eConsumeNoDefault;
781     }
782   }
783 
784   MaybeDispatchKeyupForComposition(aKeyboardEvent, aKeyFlags);
785 
786   if (NS_WARN_IF(NS_FAILED(rv))) {
787     return rv;
788   }
789   return NS_OK;
790 }
791 
792 NS_IMETHODIMP
CancelComposition(Event * aDOMKeyEvent,uint32_t aKeyFlags,uint8_t aOptionalArgc)793 TextInputProcessor::CancelComposition(Event* aDOMKeyEvent, uint32_t aKeyFlags,
794                                       uint8_t aOptionalArgc) {
795   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
796 
797   RefPtr<KeyboardEvent> keyEvent;
798   if (aDOMKeyEvent) {
799     keyEvent = aDOMKeyEvent->AsKeyboardEvent();
800     if (NS_WARN_IF(!keyEvent)) {
801       return NS_ERROR_INVALID_ARG;
802     }
803   }
804 
805   WidgetKeyboardEvent* keyboardEvent;
806   nsresult rv = PrepareKeyboardEventForComposition(
807       keyEvent, aKeyFlags, aOptionalArgc, keyboardEvent);
808   if (NS_WARN_IF(NS_FAILED(rv))) {
809     return rv;
810   }
811 
812   return CancelCompositionInternal(keyboardEvent, aKeyFlags);
813 }
814 
CancelCompositionInternal(const WidgetKeyboardEvent * aKeyboardEvent,uint32_t aKeyFlags)815 nsresult TextInputProcessor::CancelCompositionInternal(
816     const WidgetKeyboardEvent* aKeyboardEvent, uint32_t aKeyFlags) {
817   RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher);
818 
819   EventDispatcherResult dispatcherResult =
820       MaybeDispatchKeydownForComposition(aKeyboardEvent, aKeyFlags);
821   if (NS_WARN_IF(NS_FAILED(dispatcherResult.mResult)) ||
822       !dispatcherResult.mCanContinue) {
823     return dispatcherResult.mResult;
824   }
825 
826   nsEventStatus status = nsEventStatus_eIgnore;
827   nsresult rv = kungFuDeathGrip->CommitComposition(status, &EmptyString());
828 
829   MaybeDispatchKeyupForComposition(aKeyboardEvent, aKeyFlags);
830 
831   if (NS_WARN_IF(NS_FAILED(rv))) {
832     return rv;
833   }
834   return NS_OK;
835 }
836 
837 NS_IMETHODIMP
NotifyIME(TextEventDispatcher * aTextEventDispatcher,const IMENotification & aNotification)838 TextInputProcessor::NotifyIME(TextEventDispatcher* aTextEventDispatcher,
839                               const IMENotification& aNotification) {
840   // If This is called while this is being initialized, ignore the call.
841   // In such case, this method should return NS_ERROR_NOT_IMPLEMENTED because
842   // we can say, TextInputProcessor doesn't implement any handlers of the
843   // requests and notifications.
844   if (!mDispatcher) {
845     return NS_ERROR_NOT_IMPLEMENTED;
846   }
847   MOZ_ASSERT(aTextEventDispatcher == mDispatcher,
848              "Wrong TextEventDispatcher notifies this");
849   NS_ASSERTION(mForTests || mCallback,
850                "mCallback can be null only when IME is initialized for tests");
851   if (mCallback) {
852     RefPtr<TextInputProcessorNotification> notification;
853     switch (aNotification.mMessage) {
854       case REQUEST_TO_COMMIT_COMPOSITION: {
855         NS_ASSERTION(aTextEventDispatcher->IsComposing(),
856                      "Why is this requested without composition?");
857         notification = new TextInputProcessorNotification("request-to-commit");
858         break;
859       }
860       case REQUEST_TO_CANCEL_COMPOSITION: {
861         NS_ASSERTION(aTextEventDispatcher->IsComposing(),
862                      "Why is this requested without composition?");
863         notification = new TextInputProcessorNotification("request-to-cancel");
864         break;
865       }
866       case NOTIFY_IME_OF_FOCUS:
867         notification = new TextInputProcessorNotification("notify-focus");
868         break;
869       case NOTIFY_IME_OF_BLUR:
870         notification = new TextInputProcessorNotification("notify-blur");
871         break;
872       case NOTIFY_IME_OF_TEXT_CHANGE:
873         notification =
874             new TextInputProcessorNotification(aNotification.mTextChangeData);
875         break;
876       case NOTIFY_IME_OF_SELECTION_CHANGE:
877         notification = new TextInputProcessorNotification(
878             aNotification.mSelectionChangeData);
879         break;
880       case NOTIFY_IME_OF_POSITION_CHANGE:
881         notification =
882             new TextInputProcessorNotification("notify-position-change");
883         break;
884       default:
885         return NS_ERROR_NOT_IMPLEMENTED;
886     }
887     MOZ_RELEASE_ASSERT(notification);
888     bool result = false;
889     nsresult rv = mCallback->OnNotify(this, notification, &result);
890     if (NS_WARN_IF(NS_FAILED(rv))) {
891       return rv;
892     }
893     return result ? NS_OK : NS_ERROR_FAILURE;
894   }
895 
896   switch (aNotification.mMessage) {
897     case REQUEST_TO_COMMIT_COMPOSITION: {
898       NS_ASSERTION(aTextEventDispatcher->IsComposing(),
899                    "Why is this requested without composition?");
900       CommitCompositionInternal();
901       return NS_OK;
902     }
903     case REQUEST_TO_CANCEL_COMPOSITION: {
904       NS_ASSERTION(aTextEventDispatcher->IsComposing(),
905                    "Why is this requested without composition?");
906       CancelCompositionInternal();
907       return NS_OK;
908     }
909     default:
910       return NS_ERROR_NOT_IMPLEMENTED;
911   }
912 }
913 
NS_IMETHODIMP_(IMENotificationRequests)914 NS_IMETHODIMP_(IMENotificationRequests)
915 TextInputProcessor::GetIMENotificationRequests() {
916   // TextInputProcessor should support all change notifications.
917   return IMENotificationRequests(
918       IMENotificationRequests::NOTIFY_TEXT_CHANGE |
919       IMENotificationRequests::NOTIFY_POSITION_CHANGE);
920 }
921 
NS_IMETHODIMP_(void)922 NS_IMETHODIMP_(void)
923 TextInputProcessor::OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) {
924   // If This is called while this is being initialized, ignore the call.
925   if (!mDispatcher) {
926     return;
927   }
928   MOZ_ASSERT(aTextEventDispatcher == mDispatcher,
929              "Wrong TextEventDispatcher notifies this");
930   UnlinkFromTextEventDispatcher();
931 }
932 
NS_IMETHODIMP_(void)933 NS_IMETHODIMP_(void)
934 TextInputProcessor::WillDispatchKeyboardEvent(
935     TextEventDispatcher* aTextEventDispatcher,
936     WidgetKeyboardEvent& aKeyboardEvent, uint32_t aIndexOfKeypress,
937     void* aData) {
938   // TextInputProcessor doesn't set alternative char code nor modify charCode
939   // even when Ctrl key is pressed.
940 }
941 
PrepareKeyboardEventToDispatch(WidgetKeyboardEvent & aKeyboardEvent,uint32_t aKeyFlags)942 nsresult TextInputProcessor::PrepareKeyboardEventToDispatch(
943     WidgetKeyboardEvent& aKeyboardEvent, uint32_t aKeyFlags) {
944   if (NS_WARN_IF(aKeyboardEvent.mCodeNameIndex == CODE_NAME_INDEX_USE_STRING)) {
945     return NS_ERROR_INVALID_ARG;
946   }
947   if ((aKeyFlags & KEY_NON_PRINTABLE_KEY) &&
948       NS_WARN_IF(aKeyboardEvent.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING)) {
949     return NS_ERROR_INVALID_ARG;
950   }
951   if ((aKeyFlags & KEY_FORCE_PRINTABLE_KEY) &&
952       aKeyboardEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING) {
953     aKeyboardEvent.GetDOMKeyName(aKeyboardEvent.mKeyValue);
954     aKeyboardEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
955   }
956   if (aKeyFlags & KEY_KEEP_KEY_LOCATION_STANDARD) {
957     // If .location is initialized with specific value, using
958     // KEY_KEEP_KEY_LOCATION_STANDARD must be a bug of the caller.
959     // Let's throw an exception for notifying the developer of this bug.
960     if (NS_WARN_IF(aKeyboardEvent.mLocation)) {
961       return NS_ERROR_INVALID_ARG;
962     }
963   } else if (!aKeyboardEvent.mLocation) {
964     // If KeyboardEvent.mLocation is 0, it may be uninitialized.  If so, we
965     // should compute proper mLocation value from its .code value.
966     aKeyboardEvent.mLocation =
967         WidgetKeyboardEvent::ComputeLocationFromCodeValue(
968             aKeyboardEvent.mCodeNameIndex);
969   }
970 
971   if (aKeyFlags & KEY_KEEP_KEYCODE_ZERO) {
972     // If .keyCode is initialized with specific value, using
973     // KEY_KEEP_KEYCODE_ZERO must be a bug of the caller.  Let's throw an
974     // exception for notifying the developer of such bug.
975     if (NS_WARN_IF(aKeyboardEvent.mKeyCode)) {
976       return NS_ERROR_INVALID_ARG;
977     }
978   } else if (!aKeyboardEvent.mKeyCode &&
979              aKeyboardEvent.mKeyNameIndex > KEY_NAME_INDEX_Unidentified &&
980              aKeyboardEvent.mKeyNameIndex < KEY_NAME_INDEX_USE_STRING) {
981     // If KeyboardEvent.keyCode is 0, it may be uninitialized.  If so, we may
982     // be able to decide a good .keyCode value if the .key value is a
983     // non-printable key.
984     aKeyboardEvent.mKeyCode =
985         WidgetKeyboardEvent::ComputeKeyCodeFromKeyNameIndex(
986             aKeyboardEvent.mKeyNameIndex);
987   }
988 
989   aKeyboardEvent.mIsSynthesizedByTIP = true;
990   aKeyboardEvent.mFlags.mIsSynthesizedForTests = mForTests;
991 
992   return NS_OK;
993 }
994 
InitEditCommands(WidgetKeyboardEvent & aKeyboardEvent) const995 nsresult TextInputProcessor::InitEditCommands(
996     WidgetKeyboardEvent& aKeyboardEvent) const {
997   MOZ_ASSERT(XRE_IsContentProcess());
998   MOZ_ASSERT(aKeyboardEvent.mMessage == eKeyPress);
999 
1000   // When this emulates real input only in content process, we need to
1001   // initialize edit commands with the main process's widget via PuppetWidget
1002   // because they are initialized by BrowserParent before content process treats
1003   // them.
1004   // And also when this synthesizes keyboard events for tests, we need default
1005   // shortcut keys on the platform for making any developers get constant
1006   // results in any environments.
1007 
1008   // Note that retrieving edit commands via PuppetWidget is expensive.
1009   // Let's skip it when the keyboard event is inputting text.
1010   if (aKeyboardEvent.IsInputtingText()) {
1011     aKeyboardEvent.PreventNativeKeyBindings();
1012     return NS_OK;
1013   }
1014 
1015   Maybe<WritingMode> writingMode;
1016   if (RefPtr<TextEventDispatcher> dispatcher = mDispatcher) {
1017     writingMode = dispatcher->MaybeWritingModeAtSelection();
1018   }
1019 
1020   // FYI: WidgetKeyboardEvent::InitAllEditCommands() isn't available here
1021   //      since it checks whether it's called in the main process to
1022   //      avoid performance issues so that we need to initialize each
1023   //      command manually here.
1024   if (NS_WARN_IF(!aKeyboardEvent.InitEditCommandsFor(
1025           nsIWidget::NativeKeyBindingsForSingleLineEditor, writingMode))) {
1026     return NS_ERROR_NOT_AVAILABLE;
1027   }
1028   if (NS_WARN_IF(!aKeyboardEvent.InitEditCommandsFor(
1029           nsIWidget::NativeKeyBindingsForMultiLineEditor, writingMode))) {
1030     return NS_ERROR_NOT_AVAILABLE;
1031   }
1032   if (NS_WARN_IF(!aKeyboardEvent.InitEditCommandsFor(
1033           nsIWidget::NativeKeyBindingsForRichTextEditor, writingMode))) {
1034     return NS_ERROR_NOT_AVAILABLE;
1035   }
1036 
1037   return NS_OK;
1038 }
1039 
1040 NS_IMETHODIMP
Keydown(Event * aDOMKeyEvent,uint32_t aKeyFlags,uint8_t aOptionalArgc,uint32_t * aConsumedFlags)1041 TextInputProcessor::Keydown(Event* aDOMKeyEvent, uint32_t aKeyFlags,
1042                             uint8_t aOptionalArgc, uint32_t* aConsumedFlags) {
1043   MOZ_RELEASE_ASSERT(aConsumedFlags, "aConsumedFlags must not be nullptr");
1044   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
1045   if (!aOptionalArgc) {
1046     aKeyFlags = 0;
1047   }
1048   if (NS_WARN_IF(!aDOMKeyEvent)) {
1049     return NS_ERROR_INVALID_ARG;
1050   }
1051   WidgetKeyboardEvent* originalKeyEvent =
1052       aDOMKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
1053   if (NS_WARN_IF(!originalKeyEvent)) {
1054     return NS_ERROR_INVALID_ARG;
1055   }
1056   return KeydownInternal(*originalKeyEvent, aKeyFlags, true, *aConsumedFlags);
1057 }
1058 
Keydown(const WidgetKeyboardEvent & aKeyboardEvent,uint32_t aKeyFlags,uint32_t * aConsumedFlags)1059 nsresult TextInputProcessor::Keydown(const WidgetKeyboardEvent& aKeyboardEvent,
1060                                      uint32_t aKeyFlags,
1061                                      uint32_t* aConsumedFlags) {
1062   uint32_t consumedFlags = 0;
1063   return KeydownInternal(aKeyboardEvent, aKeyFlags, true,
1064                          aConsumedFlags ? *aConsumedFlags : consumedFlags);
1065 }
1066 
KeydownInternal(const WidgetKeyboardEvent & aKeyboardEvent,uint32_t aKeyFlags,bool aAllowToDispatchKeypress,uint32_t & aConsumedFlags)1067 nsresult TextInputProcessor::KeydownInternal(
1068     const WidgetKeyboardEvent& aKeyboardEvent, uint32_t aKeyFlags,
1069     bool aAllowToDispatchKeypress, uint32_t& aConsumedFlags) {
1070   aConsumedFlags = KEYEVENT_NOT_CONSUMED;
1071 
1072   // We shouldn't modify the internal WidgetKeyboardEvent.
1073   WidgetKeyboardEvent keyEvent(aKeyboardEvent);
1074   keyEvent.mFlags.mIsTrusted = true;
1075   keyEvent.mMessage = eKeyDown;
1076   nsresult rv = PrepareKeyboardEventToDispatch(keyEvent, aKeyFlags);
1077   if (NS_WARN_IF(NS_FAILED(rv))) {
1078     return rv;
1079   }
1080 
1081   aConsumedFlags = (aKeyFlags & KEY_DEFAULT_PREVENTED) ? KEYDOWN_IS_CONSUMED
1082                                                        : KEYEVENT_NOT_CONSUMED;
1083 
1084   if (WidgetKeyboardEvent::GetModifierForKeyName(keyEvent.mKeyNameIndex)) {
1085     ModifierKeyData modifierKeyData(keyEvent);
1086     if (WidgetKeyboardEvent::IsLockableModifier(keyEvent.mKeyNameIndex)) {
1087       // If the modifier key is lockable modifier key such as CapsLock,
1088       // let's toggle modifier key state at keydown.
1089       ToggleModifierKey(modifierKeyData);
1090     } else {
1091       // Activate modifier flag before dispatching keydown event (i.e., keydown
1092       // event should indicate the releasing modifier is active.
1093       ActivateModifierKey(modifierKeyData);
1094     }
1095     if (aKeyFlags & KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) {
1096       return NS_OK;
1097     }
1098   } else if (NS_WARN_IF(aKeyFlags & KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT)) {
1099     return NS_ERROR_INVALID_ARG;
1100   }
1101   keyEvent.mModifiers = GetActiveModifiers();
1102 
1103   if (!aAllowToDispatchKeypress &&
1104       !(aKeyFlags & KEY_DONT_MARK_KEYDOWN_AS_PROCESSED)) {
1105     keyEvent.mKeyCode = NS_VK_PROCESSKEY;
1106     keyEvent.mKeyNameIndex = KEY_NAME_INDEX_Process;
1107   }
1108 
1109   RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher);
1110   rv = IsValidStateForComposition();
1111   if (NS_WARN_IF(NS_FAILED(rv))) {
1112     return rv;
1113   }
1114 
1115   nsEventStatus status =
1116       aConsumedFlags ? nsEventStatus_eConsumeNoDefault : nsEventStatus_eIgnore;
1117   if (!kungFuDeathGrip->DispatchKeyboardEvent(eKeyDown, keyEvent, status)) {
1118     // If keydown event isn't dispatched, we don't need to dispatch keypress
1119     // events.
1120     return NS_OK;
1121   }
1122 
1123   aConsumedFlags |= (status == nsEventStatus_eConsumeNoDefault)
1124                         ? KEYDOWN_IS_CONSUMED
1125                         : KEYEVENT_NOT_CONSUMED;
1126 
1127   if (!aAllowToDispatchKeypress) {
1128     return NS_OK;
1129   }
1130 
1131   keyEvent.mMessage = eKeyPress;
1132 
1133   // Only `eKeyPress` events, editor wants to execute system default edit
1134   // commands mapped to the key combination.  In e10s world, edit commands can
1135   // be retrieved only in the parent process due to the performance reason.
1136   // Therefore, BrowserParent initializes edit commands for all cases before
1137   // sending the event to focused content process.  For emulating this, we
1138   // need to do it now for synthesizing `eKeyPress` events if and only if
1139   // we're dispatching the events in a content process.
1140   if (XRE_IsContentProcess()) {
1141     nsresult rv = InitEditCommands(keyEvent);
1142     if (NS_WARN_IF(NS_FAILED(rv))) {
1143       return rv;
1144     }
1145   }
1146   if (kungFuDeathGrip->MaybeDispatchKeypressEvents(keyEvent, status)) {
1147     aConsumedFlags |= (status == nsEventStatus_eConsumeNoDefault)
1148                           ? KEYPRESS_IS_CONSUMED
1149                           : KEYEVENT_NOT_CONSUMED;
1150   }
1151 
1152   return NS_OK;
1153 }
1154 
1155 NS_IMETHODIMP
Keyup(Event * aDOMKeyEvent,uint32_t aKeyFlags,uint8_t aOptionalArgc,bool * aDoDefault)1156 TextInputProcessor::Keyup(Event* aDOMKeyEvent, uint32_t aKeyFlags,
1157                           uint8_t aOptionalArgc, bool* aDoDefault) {
1158   MOZ_RELEASE_ASSERT(aDoDefault, "aDoDefault must not be nullptr");
1159   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
1160   if (!aOptionalArgc) {
1161     aKeyFlags = 0;
1162   }
1163   if (NS_WARN_IF(!aDOMKeyEvent)) {
1164     return NS_ERROR_INVALID_ARG;
1165   }
1166   WidgetKeyboardEvent* originalKeyEvent =
1167       aDOMKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
1168   if (NS_WARN_IF(!originalKeyEvent)) {
1169     return NS_ERROR_INVALID_ARG;
1170   }
1171   return KeyupInternal(*originalKeyEvent, aKeyFlags, *aDoDefault);
1172 }
1173 
Keyup(const WidgetKeyboardEvent & aKeyboardEvent,uint32_t aKeyFlags,bool * aDoDefault)1174 nsresult TextInputProcessor::Keyup(const WidgetKeyboardEvent& aKeyboardEvent,
1175                                    uint32_t aKeyFlags, bool* aDoDefault) {
1176   bool doDefault = false;
1177   return KeyupInternal(aKeyboardEvent, aKeyFlags,
1178                        aDoDefault ? *aDoDefault : doDefault);
1179 }
1180 
KeyupInternal(const WidgetKeyboardEvent & aKeyboardEvent,uint32_t aKeyFlags,bool & aDoDefault)1181 nsresult TextInputProcessor::KeyupInternal(
1182     const WidgetKeyboardEvent& aKeyboardEvent, uint32_t aKeyFlags,
1183     bool& aDoDefault) {
1184   aDoDefault = false;
1185 
1186   // We shouldn't modify the internal WidgetKeyboardEvent.
1187   WidgetKeyboardEvent keyEvent(aKeyboardEvent);
1188   keyEvent.mFlags.mIsTrusted = true;
1189   keyEvent.mMessage = eKeyUp;
1190   nsresult rv = PrepareKeyboardEventToDispatch(keyEvent, aKeyFlags);
1191   if (NS_WARN_IF(NS_FAILED(rv))) {
1192     return rv;
1193   }
1194 
1195   aDoDefault = !(aKeyFlags & KEY_DEFAULT_PREVENTED);
1196 
1197   if (WidgetKeyboardEvent::GetModifierForKeyName(keyEvent.mKeyNameIndex)) {
1198     if (!WidgetKeyboardEvent::IsLockableModifier(keyEvent.mKeyNameIndex)) {
1199       // Inactivate modifier flag before dispatching keyup event (i.e., keyup
1200       // event shouldn't indicate the releasing modifier is active.
1201       InactivateModifierKey(ModifierKeyData(keyEvent));
1202     }
1203     if (aKeyFlags & KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) {
1204       return NS_OK;
1205     }
1206   } else if (NS_WARN_IF(aKeyFlags & KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT)) {
1207     return NS_ERROR_INVALID_ARG;
1208   }
1209   keyEvent.mModifiers = GetActiveModifiers();
1210 
1211   if (aKeyFlags & KEY_MARK_KEYUP_AS_PROCESSED) {
1212     keyEvent.mKeyCode = NS_VK_PROCESSKEY;
1213     keyEvent.mKeyNameIndex = KEY_NAME_INDEX_Process;
1214   }
1215 
1216   RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher);
1217   rv = IsValidStateForComposition();
1218   if (NS_WARN_IF(NS_FAILED(rv))) {
1219     return rv;
1220   }
1221 
1222   nsEventStatus status =
1223       aDoDefault ? nsEventStatus_eIgnore : nsEventStatus_eConsumeNoDefault;
1224   kungFuDeathGrip->DispatchKeyboardEvent(eKeyUp, keyEvent, status);
1225   aDoDefault = (status != nsEventStatus_eConsumeNoDefault);
1226   return NS_OK;
1227 }
1228 
1229 NS_IMETHODIMP
GetModifierState(const nsAString & aModifierKeyName,bool * aActive)1230 TextInputProcessor::GetModifierState(const nsAString& aModifierKeyName,
1231                                      bool* aActive) {
1232   MOZ_RELEASE_ASSERT(aActive, "aActive must not be null");
1233   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
1234   Modifiers modifier = WidgetInputEvent::GetModifier(aModifierKeyName);
1235   *aActive = ((GetActiveModifiers() & modifier) != 0);
1236   return NS_OK;
1237 }
1238 
1239 NS_IMETHODIMP
ShareModifierStateOf(nsITextInputProcessor * aOther)1240 TextInputProcessor::ShareModifierStateOf(nsITextInputProcessor* aOther) {
1241   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
1242   if (!aOther) {
1243     mModifierKeyDataArray = nullptr;
1244     return NS_OK;
1245   }
1246   TextInputProcessor* other = static_cast<TextInputProcessor*>(aOther);
1247   if (!other->mModifierKeyDataArray) {
1248     other->mModifierKeyDataArray = new ModifierKeyDataArray();
1249   }
1250   mModifierKeyDataArray = other->mModifierKeyDataArray;
1251   return NS_OK;
1252 }
1253 
1254 NS_IMETHODIMP
ComputeCodeValueOfNonPrintableKey(const nsAString & aKeyValue,JS::Handle<JS::Value> aLocation,uint8_t aOptionalArgc,nsAString & aCodeValue)1255 TextInputProcessor::ComputeCodeValueOfNonPrintableKey(
1256     const nsAString& aKeyValue, JS::Handle<JS::Value> aLocation,
1257     uint8_t aOptionalArgc, nsAString& aCodeValue) {
1258   aCodeValue.Truncate();
1259 
1260   Maybe<uint32_t> location;
1261   if (aOptionalArgc) {
1262     if (aLocation.isNullOrUndefined()) {
1263       // location should be nothing.
1264     } else if (aLocation.isInt32()) {
1265       location = mozilla::Some(static_cast<uint32_t>(aLocation.toInt32()));
1266     } else {
1267       NS_WARNING_ASSERTION(aLocation.isNullOrUndefined() || aLocation.isInt32(),
1268                            "aLocation must be undefined, null or int");
1269       return NS_ERROR_INVALID_ARG;
1270     }
1271   }
1272 
1273   KeyNameIndex keyNameIndex = WidgetKeyboardEvent::GetKeyNameIndex(aKeyValue);
1274   if (keyNameIndex == KEY_NAME_INDEX_Unidentified ||
1275       keyNameIndex == KEY_NAME_INDEX_USE_STRING) {
1276     return NS_OK;
1277   }
1278 
1279   CodeNameIndex codeNameIndex =
1280       WidgetKeyboardEvent::ComputeCodeNameIndexFromKeyNameIndex(keyNameIndex,
1281                                                                 location);
1282   if (codeNameIndex == CODE_NAME_INDEX_UNKNOWN) {
1283     return NS_OK;
1284   }
1285   MOZ_ASSERT(codeNameIndex != CODE_NAME_INDEX_USE_STRING);
1286   WidgetKeyboardEvent::GetDOMCodeName(codeNameIndex, aCodeValue);
1287   return NS_OK;
1288 }
1289 
1290 NS_IMETHODIMP
GuessCodeValueOfPrintableKeyInUSEnglishKeyboardLayout(const nsAString & aKeyValue,JS::Handle<JS::Value> aLocation,uint8_t aOptionalArgc,nsAString & aCodeValue)1291 TextInputProcessor::GuessCodeValueOfPrintableKeyInUSEnglishKeyboardLayout(
1292     const nsAString& aKeyValue, JS::Handle<JS::Value> aLocation,
1293     uint8_t aOptionalArgc, nsAString& aCodeValue) {
1294   aCodeValue.Truncate();
1295 
1296   Maybe<uint32_t> location;
1297   if (aOptionalArgc) {
1298     if (aLocation.isNullOrUndefined()) {
1299       // location should be nothing.
1300     } else if (aLocation.isInt32()) {
1301       location = mozilla::Some(static_cast<uint32_t>(aLocation.toInt32()));
1302     } else {
1303       NS_WARNING_ASSERTION(aLocation.isNullOrUndefined() || aLocation.isInt32(),
1304                            "aLocation must be undefined, null or int");
1305       return NS_ERROR_INVALID_ARG;
1306     }
1307   }
1308   CodeNameIndex codeNameIndex =
1309       GuessCodeNameIndexOfPrintableKeyInUSEnglishLayout(aKeyValue, location);
1310   if (codeNameIndex == CODE_NAME_INDEX_UNKNOWN) {
1311     return NS_OK;
1312   }
1313   MOZ_ASSERT(codeNameIndex != CODE_NAME_INDEX_USE_STRING);
1314   WidgetKeyboardEvent::GetDOMCodeName(codeNameIndex, aCodeValue);
1315   return NS_OK;
1316 }
1317 
1318 // static
1319 CodeNameIndex
GuessCodeNameIndexOfPrintableKeyInUSEnglishLayout(const nsAString & aKeyValue,const Maybe<uint32_t> & aLocation)1320 TextInputProcessor::GuessCodeNameIndexOfPrintableKeyInUSEnglishLayout(
1321     const nsAString& aKeyValue, const Maybe<uint32_t>& aLocation) {
1322   if (aKeyValue.IsEmpty()) {
1323     return CODE_NAME_INDEX_UNKNOWN;
1324   }
1325   // US keyboard layout can input only one character per key.  So, we can
1326   // assume that if the key value is 2 or more characters, it's a known
1327   // key name or not a usual key emulation.
1328   if (aKeyValue.Length() > 1) {
1329     return CODE_NAME_INDEX_UNKNOWN;
1330   }
1331   if (aLocation.isSome() &&
1332       aLocation.value() ==
1333           dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_NUMPAD) {
1334     switch (aKeyValue[0]) {
1335       case '+':
1336         return CODE_NAME_INDEX_NumpadAdd;
1337       case '-':
1338         return CODE_NAME_INDEX_NumpadSubtract;
1339       case '*':
1340         return CODE_NAME_INDEX_NumpadMultiply;
1341       case '/':
1342         return CODE_NAME_INDEX_NumpadDivide;
1343       case '.':
1344         return CODE_NAME_INDEX_NumpadDecimal;
1345       case '0':
1346         return CODE_NAME_INDEX_Numpad0;
1347       case '1':
1348         return CODE_NAME_INDEX_Numpad1;
1349       case '2':
1350         return CODE_NAME_INDEX_Numpad2;
1351       case '3':
1352         return CODE_NAME_INDEX_Numpad3;
1353       case '4':
1354         return CODE_NAME_INDEX_Numpad4;
1355       case '5':
1356         return CODE_NAME_INDEX_Numpad5;
1357       case '6':
1358         return CODE_NAME_INDEX_Numpad6;
1359       case '7':
1360         return CODE_NAME_INDEX_Numpad7;
1361       case '8':
1362         return CODE_NAME_INDEX_Numpad8;
1363       case '9':
1364         return CODE_NAME_INDEX_Numpad9;
1365       default:
1366         return CODE_NAME_INDEX_UNKNOWN;
1367     }
1368   }
1369 
1370   if (aLocation.isSome() &&
1371       aLocation.value() !=
1372           dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_STANDARD) {
1373     return CODE_NAME_INDEX_UNKNOWN;
1374   }
1375 
1376   // TODO: Support characters inputted with option key on macOS.
1377   switch (aKeyValue[0]) {
1378     case 'a':
1379     case 'A':
1380       return CODE_NAME_INDEX_KeyA;
1381     case 'b':
1382     case 'B':
1383       return CODE_NAME_INDEX_KeyB;
1384     case 'c':
1385     case 'C':
1386       return CODE_NAME_INDEX_KeyC;
1387     case 'd':
1388     case 'D':
1389       return CODE_NAME_INDEX_KeyD;
1390     case 'e':
1391     case 'E':
1392       return CODE_NAME_INDEX_KeyE;
1393     case 'f':
1394     case 'F':
1395       return CODE_NAME_INDEX_KeyF;
1396     case 'g':
1397     case 'G':
1398       return CODE_NAME_INDEX_KeyG;
1399     case 'h':
1400     case 'H':
1401       return CODE_NAME_INDEX_KeyH;
1402     case 'i':
1403     case 'I':
1404       return CODE_NAME_INDEX_KeyI;
1405     case 'j':
1406     case 'J':
1407       return CODE_NAME_INDEX_KeyJ;
1408     case 'k':
1409     case 'K':
1410       return CODE_NAME_INDEX_KeyK;
1411     case 'l':
1412     case 'L':
1413       return CODE_NAME_INDEX_KeyL;
1414     case 'm':
1415     case 'M':
1416       return CODE_NAME_INDEX_KeyM;
1417     case 'n':
1418     case 'N':
1419       return CODE_NAME_INDEX_KeyN;
1420     case 'o':
1421     case 'O':
1422       return CODE_NAME_INDEX_KeyO;
1423     case 'p':
1424     case 'P':
1425       return CODE_NAME_INDEX_KeyP;
1426     case 'q':
1427     case 'Q':
1428       return CODE_NAME_INDEX_KeyQ;
1429     case 'r':
1430     case 'R':
1431       return CODE_NAME_INDEX_KeyR;
1432     case 's':
1433     case 'S':
1434       return CODE_NAME_INDEX_KeyS;
1435     case 't':
1436     case 'T':
1437       return CODE_NAME_INDEX_KeyT;
1438     case 'u':
1439     case 'U':
1440       return CODE_NAME_INDEX_KeyU;
1441     case 'v':
1442     case 'V':
1443       return CODE_NAME_INDEX_KeyV;
1444     case 'w':
1445     case 'W':
1446       return CODE_NAME_INDEX_KeyW;
1447     case 'x':
1448     case 'X':
1449       return CODE_NAME_INDEX_KeyX;
1450     case 'y':
1451     case 'Y':
1452       return CODE_NAME_INDEX_KeyY;
1453     case 'z':
1454     case 'Z':
1455       return CODE_NAME_INDEX_KeyZ;
1456 
1457     case '`':
1458     case '~':
1459       return CODE_NAME_INDEX_Backquote;
1460     case '1':
1461     case '!':
1462       return CODE_NAME_INDEX_Digit1;
1463     case '2':
1464     case '@':
1465       return CODE_NAME_INDEX_Digit2;
1466     case '3':
1467     case '#':
1468       return CODE_NAME_INDEX_Digit3;
1469     case '4':
1470     case '$':
1471       return CODE_NAME_INDEX_Digit4;
1472     case '5':
1473     case '%':
1474       return CODE_NAME_INDEX_Digit5;
1475     case '6':
1476     case '^':
1477       return CODE_NAME_INDEX_Digit6;
1478     case '7':
1479     case '&':
1480       return CODE_NAME_INDEX_Digit7;
1481     case '8':
1482     case '*':
1483       return CODE_NAME_INDEX_Digit8;
1484     case '9':
1485     case '(':
1486       return CODE_NAME_INDEX_Digit9;
1487     case '0':
1488     case ')':
1489       return CODE_NAME_INDEX_Digit0;
1490     case '-':
1491     case '_':
1492       return CODE_NAME_INDEX_Minus;
1493     case '=':
1494     case '+':
1495       return CODE_NAME_INDEX_Equal;
1496 
1497     case '[':
1498     case '{':
1499       return CODE_NAME_INDEX_BracketLeft;
1500     case ']':
1501     case '}':
1502       return CODE_NAME_INDEX_BracketRight;
1503     case '\\':
1504     case '|':
1505       return CODE_NAME_INDEX_Backslash;
1506 
1507     case ';':
1508     case ':':
1509       return CODE_NAME_INDEX_Semicolon;
1510     case '\'':
1511     case '"':
1512       return CODE_NAME_INDEX_Quote;
1513 
1514     case ',':
1515     case '<':
1516       return CODE_NAME_INDEX_Comma;
1517     case '.':
1518     case '>':
1519       return CODE_NAME_INDEX_Period;
1520     case '/':
1521     case '?':
1522       return CODE_NAME_INDEX_Slash;
1523 
1524     case ' ':
1525       return CODE_NAME_INDEX_Space;
1526 
1527     default:
1528       return CODE_NAME_INDEX_UNKNOWN;
1529   }
1530 }
1531 
1532 NS_IMETHODIMP
GuessKeyCodeValueOfPrintableKeyInUSEnglishKeyboardLayout(const nsAString & aKeyValue,JS::Handle<JS::Value> aLocation,uint8_t aOptionalArgc,uint32_t * aKeyCodeValue)1533 TextInputProcessor::GuessKeyCodeValueOfPrintableKeyInUSEnglishKeyboardLayout(
1534     const nsAString& aKeyValue, JS::Handle<JS::Value> aLocation,
1535     uint8_t aOptionalArgc, uint32_t* aKeyCodeValue) {
1536   if (NS_WARN_IF(!aKeyCodeValue)) {
1537     return NS_ERROR_INVALID_ARG;
1538   }
1539 
1540   Maybe<uint32_t> location;
1541   if (aOptionalArgc) {
1542     if (aLocation.isNullOrUndefined()) {
1543       // location should be nothing.
1544     } else if (aLocation.isInt32()) {
1545       location = mozilla::Some(static_cast<uint32_t>(aLocation.toInt32()));
1546     } else {
1547       NS_WARNING_ASSERTION(aLocation.isNullOrUndefined() || aLocation.isInt32(),
1548                            "aLocation must be undefined, null or int");
1549       return NS_ERROR_INVALID_ARG;
1550     }
1551   }
1552 
1553   *aKeyCodeValue =
1554       GuessKeyCodeOfPrintableKeyInUSEnglishLayout(aKeyValue, location);
1555   return NS_OK;
1556 }
1557 
1558 // static
GuessKeyCodeOfPrintableKeyInUSEnglishLayout(const nsAString & aKeyValue,const Maybe<uint32_t> & aLocation)1559 uint32_t TextInputProcessor::GuessKeyCodeOfPrintableKeyInUSEnglishLayout(
1560     const nsAString& aKeyValue, const Maybe<uint32_t>& aLocation) {
1561   if (aKeyValue.IsEmpty()) {
1562     return 0;
1563   }
1564   // US keyboard layout can input only one character per key.  So, we can
1565   // assume that if the key value is 2 or more characters, it's a known
1566   // key name of a non-printable key or not a usual key emulation.
1567   if (aKeyValue.Length() > 1) {
1568     return 0;
1569   }
1570 
1571   if (aLocation.isSome() &&
1572       aLocation.value() ==
1573           dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_NUMPAD) {
1574     switch (aKeyValue[0]) {
1575       case '+':
1576         return dom::KeyboardEvent_Binding::DOM_VK_ADD;
1577       case '-':
1578         return dom::KeyboardEvent_Binding::DOM_VK_SUBTRACT;
1579       case '*':
1580         return dom::KeyboardEvent_Binding::DOM_VK_MULTIPLY;
1581       case '/':
1582         return dom::KeyboardEvent_Binding::DOM_VK_DIVIDE;
1583       case '.':
1584         return dom::KeyboardEvent_Binding::DOM_VK_DECIMAL;
1585       case '0':
1586         return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD0;
1587       case '1':
1588         return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD1;
1589       case '2':
1590         return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD2;
1591       case '3':
1592         return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD3;
1593       case '4':
1594         return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD4;
1595       case '5':
1596         return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD5;
1597       case '6':
1598         return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD6;
1599       case '7':
1600         return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD7;
1601       case '8':
1602         return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD8;
1603       case '9':
1604         return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD9;
1605       default:
1606         return 0;
1607     }
1608   }
1609 
1610   if (aLocation.isSome() &&
1611       aLocation.value() !=
1612           dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_STANDARD) {
1613     return 0;
1614   }
1615 
1616   // TODO: Support characters inputted with option key on macOS.
1617   switch (aKeyValue[0]) {
1618     case 'a':
1619     case 'A':
1620       return dom::KeyboardEvent_Binding::DOM_VK_A;
1621     case 'b':
1622     case 'B':
1623       return dom::KeyboardEvent_Binding::DOM_VK_B;
1624     case 'c':
1625     case 'C':
1626       return dom::KeyboardEvent_Binding::DOM_VK_C;
1627     case 'd':
1628     case 'D':
1629       return dom::KeyboardEvent_Binding::DOM_VK_D;
1630     case 'e':
1631     case 'E':
1632       return dom::KeyboardEvent_Binding::DOM_VK_E;
1633     case 'f':
1634     case 'F':
1635       return dom::KeyboardEvent_Binding::DOM_VK_F;
1636     case 'g':
1637     case 'G':
1638       return dom::KeyboardEvent_Binding::DOM_VK_G;
1639     case 'h':
1640     case 'H':
1641       return dom::KeyboardEvent_Binding::DOM_VK_H;
1642     case 'i':
1643     case 'I':
1644       return dom::KeyboardEvent_Binding::DOM_VK_I;
1645     case 'j':
1646     case 'J':
1647       return dom::KeyboardEvent_Binding::DOM_VK_J;
1648     case 'k':
1649     case 'K':
1650       return dom::KeyboardEvent_Binding::DOM_VK_K;
1651     case 'l':
1652     case 'L':
1653       return dom::KeyboardEvent_Binding::DOM_VK_L;
1654     case 'm':
1655     case 'M':
1656       return dom::KeyboardEvent_Binding::DOM_VK_M;
1657     case 'n':
1658     case 'N':
1659       return dom::KeyboardEvent_Binding::DOM_VK_N;
1660     case 'o':
1661     case 'O':
1662       return dom::KeyboardEvent_Binding::DOM_VK_O;
1663     case 'p':
1664     case 'P':
1665       return dom::KeyboardEvent_Binding::DOM_VK_P;
1666     case 'q':
1667     case 'Q':
1668       return dom::KeyboardEvent_Binding::DOM_VK_Q;
1669     case 'r':
1670     case 'R':
1671       return dom::KeyboardEvent_Binding::DOM_VK_R;
1672     case 's':
1673     case 'S':
1674       return dom::KeyboardEvent_Binding::DOM_VK_S;
1675     case 't':
1676     case 'T':
1677       return dom::KeyboardEvent_Binding::DOM_VK_T;
1678     case 'u':
1679     case 'U':
1680       return dom::KeyboardEvent_Binding::DOM_VK_U;
1681     case 'v':
1682     case 'V':
1683       return dom::KeyboardEvent_Binding::DOM_VK_V;
1684     case 'w':
1685     case 'W':
1686       return dom::KeyboardEvent_Binding::DOM_VK_W;
1687     case 'x':
1688     case 'X':
1689       return dom::KeyboardEvent_Binding::DOM_VK_X;
1690     case 'y':
1691     case 'Y':
1692       return dom::KeyboardEvent_Binding::DOM_VK_Y;
1693     case 'z':
1694     case 'Z':
1695       return dom::KeyboardEvent_Binding::DOM_VK_Z;
1696 
1697     case '`':
1698     case '~':
1699       return dom::KeyboardEvent_Binding::DOM_VK_BACK_QUOTE;
1700     case '1':
1701     case '!':
1702       return dom::KeyboardEvent_Binding::DOM_VK_1;
1703     case '2':
1704     case '@':
1705       return dom::KeyboardEvent_Binding::DOM_VK_2;
1706     case '3':
1707     case '#':
1708       return dom::KeyboardEvent_Binding::DOM_VK_3;
1709     case '4':
1710     case '$':
1711       return dom::KeyboardEvent_Binding::DOM_VK_4;
1712     case '5':
1713     case '%':
1714       return dom::KeyboardEvent_Binding::DOM_VK_5;
1715     case '6':
1716     case '^':
1717       return dom::KeyboardEvent_Binding::DOM_VK_6;
1718     case '7':
1719     case '&':
1720       return dom::KeyboardEvent_Binding::DOM_VK_7;
1721     case '8':
1722     case '*':
1723       return dom::KeyboardEvent_Binding::DOM_VK_8;
1724     case '9':
1725     case '(':
1726       return dom::KeyboardEvent_Binding::DOM_VK_9;
1727     case '0':
1728     case ')':
1729       return dom::KeyboardEvent_Binding::DOM_VK_0;
1730     case '-':
1731     case '_':
1732       return dom::KeyboardEvent_Binding::DOM_VK_HYPHEN_MINUS;
1733     case '=':
1734     case '+':
1735       return dom::KeyboardEvent_Binding::DOM_VK_EQUALS;
1736 
1737     case '[':
1738     case '{':
1739       return dom::KeyboardEvent_Binding::DOM_VK_OPEN_BRACKET;
1740     case ']':
1741     case '}':
1742       return dom::KeyboardEvent_Binding::DOM_VK_CLOSE_BRACKET;
1743     case '\\':
1744     case '|':
1745       return dom::KeyboardEvent_Binding::DOM_VK_BACK_SLASH;
1746 
1747     case ';':
1748     case ':':
1749       return dom::KeyboardEvent_Binding::DOM_VK_SEMICOLON;
1750     case '\'':
1751     case '"':
1752       return dom::KeyboardEvent_Binding::DOM_VK_QUOTE;
1753 
1754     case ',':
1755     case '<':
1756       return dom::KeyboardEvent_Binding::DOM_VK_COMMA;
1757     case '.':
1758     case '>':
1759       return dom::KeyboardEvent_Binding::DOM_VK_PERIOD;
1760     case '/':
1761     case '?':
1762       return dom::KeyboardEvent_Binding::DOM_VK_SLASH;
1763 
1764     case ' ':
1765       return dom::KeyboardEvent_Binding::DOM_VK_SPACE;
1766 
1767     default:
1768       return 0;
1769   }
1770 }
1771 
1772 /******************************************************************************
1773  * TextInputProcessor::AutoPendingCompositionResetter
1774  ******************************************************************************/
1775 TextInputProcessor::AutoPendingCompositionResetter::
AutoPendingCompositionResetter(TextInputProcessor * aTIP)1776     AutoPendingCompositionResetter(TextInputProcessor* aTIP)
1777     : mTIP(aTIP) {
1778   MOZ_RELEASE_ASSERT(mTIP.get(), "mTIP must not be null");
1779 }
1780 
1781 TextInputProcessor::AutoPendingCompositionResetter::
~AutoPendingCompositionResetter()1782     ~AutoPendingCompositionResetter() {
1783   if (mTIP->mDispatcher) {
1784     mTIP->mDispatcher->ClearPendingComposition();
1785   }
1786 }
1787 
1788 /******************************************************************************
1789  * TextInputProcessor::ModifierKeyData
1790  ******************************************************************************/
ModifierKeyData(const WidgetKeyboardEvent & aKeyboardEvent)1791 TextInputProcessor::ModifierKeyData::ModifierKeyData(
1792     const WidgetKeyboardEvent& aKeyboardEvent)
1793     : mKeyNameIndex(aKeyboardEvent.mKeyNameIndex),
1794       mCodeNameIndex(aKeyboardEvent.mCodeNameIndex) {
1795   mModifier = WidgetKeyboardEvent::GetModifierForKeyName(mKeyNameIndex);
1796   MOZ_ASSERT(mModifier, "mKeyNameIndex must be a modifier key name");
1797 }
1798 
1799 /******************************************************************************
1800  * TextInputProcessor::ModifierKeyDataArray
1801  ******************************************************************************/
GetActiveModifiers() const1802 Modifiers TextInputProcessor::ModifierKeyDataArray::GetActiveModifiers() const {
1803   Modifiers result = MODIFIER_NONE;
1804   for (uint32_t i = 0; i < Length(); i++) {
1805     result |= ElementAt(i).mModifier;
1806   }
1807   return result;
1808 }
1809 
ActivateModifierKey(const TextInputProcessor::ModifierKeyData & aModifierKeyData)1810 void TextInputProcessor::ModifierKeyDataArray::ActivateModifierKey(
1811     const TextInputProcessor::ModifierKeyData& aModifierKeyData) {
1812   if (Contains(aModifierKeyData)) {
1813     return;
1814   }
1815   AppendElement(aModifierKeyData);
1816 }
1817 
InactivateModifierKey(const TextInputProcessor::ModifierKeyData & aModifierKeyData)1818 void TextInputProcessor::ModifierKeyDataArray::InactivateModifierKey(
1819     const TextInputProcessor::ModifierKeyData& aModifierKeyData) {
1820   RemoveElement(aModifierKeyData);
1821 }
1822 
ToggleModifierKey(const TextInputProcessor::ModifierKeyData & aModifierKeyData)1823 void TextInputProcessor::ModifierKeyDataArray::ToggleModifierKey(
1824     const TextInputProcessor::ModifierKeyData& aModifierKeyData) {
1825   auto index = IndexOf(aModifierKeyData);
1826   if (index == NoIndex) {
1827     AppendElement(aModifierKeyData);
1828     return;
1829   }
1830   RemoveElementAt(index);
1831 }
1832 
1833 }  // namespace mozilla
1834