diff options
Diffstat (limited to 'dom/base/TextInputProcessor.cpp')
-rw-r--r-- | dom/base/TextInputProcessor.cpp | 1793 |
1 files changed, 1793 insertions, 0 deletions
diff --git a/dom/base/TextInputProcessor.cpp b/dom/base/TextInputProcessor.cpp new file mode 100644 index 0000000000..e3087222b4 --- /dev/null +++ b/dom/base/TextInputProcessor.cpp @@ -0,0 +1,1793 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/Event.h" +#include "mozilla/EventForwards.h" +#include "mozilla/Maybe.h" +#include "mozilla/StaticPrefs_test.h" +#include "mozilla/TextEventDispatcher.h" +#include "mozilla/TextEvents.h" +#include "mozilla/TextInputProcessor.h" +#include "mozilla/widget/IMEData.h" +#include "mozilla/dom/KeyboardEvent.h" +#include "nsContentUtils.h" +#include "nsIDocShell.h" +#include "nsIWidget.h" +#include "nsPIDOMWindow.h" +#include "nsPresContext.h" + +using mozilla::dom::Event; +using mozilla::dom::KeyboardEvent; +using namespace mozilla::widget; + +namespace mozilla { + +/****************************************************************************** + * TextInputProcessorNotification + ******************************************************************************/ + +class TextInputProcessorNotification final + : public nsITextInputProcessorNotification { + typedef IMENotification::SelectionChangeData SelectionChangeData; + typedef IMENotification::SelectionChangeDataBase SelectionChangeDataBase; + typedef IMENotification::TextChangeData TextChangeData; + typedef IMENotification::TextChangeDataBase TextChangeDataBase; + + public: + explicit TextInputProcessorNotification(const char* aType) + : mType(aType), mTextChangeData() {} + + explicit TextInputProcessorNotification( + const TextChangeDataBase& aTextChangeData) + : mType("notify-text-change"), mTextChangeData(aTextChangeData) {} + + explicit TextInputProcessorNotification( + const SelectionChangeDataBase& aSelectionChangeData) + : mType("notify-selection-change"), + mSelectionChangeData(aSelectionChangeData) { + // SelectionChangeDataBase::mString still refers nsString instance owned + // by aSelectionChangeData. So, this needs to copy the instance. + nsString* string = new nsString(aSelectionChangeData.String()); + mSelectionChangeData.mString = string; + } + + NS_DECL_ISUPPORTS + + NS_IMETHOD GetType(nsACString& aType) final { + aType = mType; + return NS_OK; + } + + // "notify-text-change" and "notify-selection-change" + NS_IMETHOD GetOffset(uint32_t* aOffset) final { + if (NS_WARN_IF(!aOffset)) { + return NS_ERROR_INVALID_ARG; + } + if (IsSelectionChange()) { + *aOffset = mSelectionChangeData.mOffset; + return NS_OK; + } + if (IsTextChange()) { + *aOffset = mTextChangeData.mStartOffset; + return NS_OK; + } + return NS_ERROR_NOT_AVAILABLE; + } + + // "notify-selection-change" + NS_IMETHOD GetText(nsAString& aText) final { + if (IsSelectionChange()) { + aText = mSelectionChangeData.String(); + return NS_OK; + } + return NS_ERROR_NOT_AVAILABLE; + } + + NS_IMETHOD GetCollapsed(bool* aCollapsed) final { + if (NS_WARN_IF(!aCollapsed)) { + return NS_ERROR_INVALID_ARG; + } + if (IsSelectionChange()) { + *aCollapsed = mSelectionChangeData.IsCollapsed(); + return NS_OK; + } + return NS_ERROR_NOT_AVAILABLE; + } + + NS_IMETHOD GetLength(uint32_t* aLength) final { + if (NS_WARN_IF(!aLength)) { + return NS_ERROR_INVALID_ARG; + } + if (IsSelectionChange()) { + *aLength = mSelectionChangeData.Length(); + return NS_OK; + } + return NS_ERROR_NOT_AVAILABLE; + } + + NS_IMETHOD GetReversed(bool* aReversed) final { + if (NS_WARN_IF(!aReversed)) { + return NS_ERROR_INVALID_ARG; + } + if (IsSelectionChange()) { + *aReversed = mSelectionChangeData.mReversed; + return NS_OK; + } + return NS_ERROR_NOT_AVAILABLE; + } + + NS_IMETHOD GetWritingMode(nsACString& aWritingMode) final { + if (IsSelectionChange()) { + WritingMode writingMode = mSelectionChangeData.GetWritingMode(); + if (!writingMode.IsVertical()) { + aWritingMode.AssignLiteral("horizontal-tb"); + } else if (writingMode.IsVerticalLR()) { + aWritingMode.AssignLiteral("vertical-lr"); + } else { + aWritingMode.AssignLiteral("vertical-rl"); + } + return NS_OK; + } + return NS_ERROR_NOT_AVAILABLE; + } + + NS_IMETHOD GetCausedByComposition(bool* aCausedByComposition) final { + if (NS_WARN_IF(!aCausedByComposition)) { + return NS_ERROR_INVALID_ARG; + } + if (IsSelectionChange()) { + *aCausedByComposition = mSelectionChangeData.mCausedByComposition; + return NS_OK; + } + return NS_ERROR_NOT_AVAILABLE; + } + + NS_IMETHOD GetCausedBySelectionEvent(bool* aCausedBySelectionEvent) final { + if (NS_WARN_IF(!aCausedBySelectionEvent)) { + return NS_ERROR_INVALID_ARG; + } + if (IsSelectionChange()) { + *aCausedBySelectionEvent = mSelectionChangeData.mCausedBySelectionEvent; + return NS_OK; + } + return NS_ERROR_NOT_AVAILABLE; + } + + NS_IMETHOD GetOccurredDuringComposition( + bool* aOccurredDuringComposition) final { + if (NS_WARN_IF(!aOccurredDuringComposition)) { + return NS_ERROR_INVALID_ARG; + } + if (IsSelectionChange()) { + *aOccurredDuringComposition = + mSelectionChangeData.mOccurredDuringComposition; + return NS_OK; + } + return NS_ERROR_NOT_AVAILABLE; + } + + // "notify-text-change" + NS_IMETHOD GetRemovedLength(uint32_t* aLength) final { + if (NS_WARN_IF(!aLength)) { + return NS_ERROR_INVALID_ARG; + } + if (IsTextChange()) { + *aLength = mTextChangeData.OldLength(); + return NS_OK; + } + return NS_ERROR_NOT_AVAILABLE; + } + + NS_IMETHOD GetAddedLength(uint32_t* aLength) final { + if (NS_WARN_IF(!aLength)) { + return NS_ERROR_INVALID_ARG; + } + if (IsTextChange()) { + *aLength = mTextChangeData.NewLength(); + return NS_OK; + } + return NS_ERROR_NOT_AVAILABLE; + } + + NS_IMETHOD GetCausedOnlyByComposition(bool* aCausedOnlyByComposition) final { + if (NS_WARN_IF(!aCausedOnlyByComposition)) { + return NS_ERROR_INVALID_ARG; + } + if (IsTextChange()) { + *aCausedOnlyByComposition = mTextChangeData.mCausedOnlyByComposition; + return NS_OK; + } + return NS_ERROR_NOT_AVAILABLE; + } + + NS_IMETHOD GetIncludingChangesDuringComposition( + bool* aIncludingChangesDuringComposition) final { + if (NS_WARN_IF(!aIncludingChangesDuringComposition)) { + return NS_ERROR_INVALID_ARG; + } + if (IsTextChange()) { + *aIncludingChangesDuringComposition = + mTextChangeData.mIncludingChangesDuringComposition; + return NS_OK; + } + return NS_ERROR_NOT_AVAILABLE; + } + + NS_IMETHOD GetIncludingChangesWithoutComposition( + bool* aIncludingChangesWithoutComposition) final { + if (NS_WARN_IF(!aIncludingChangesWithoutComposition)) { + return NS_ERROR_INVALID_ARG; + } + if (IsTextChange()) { + *aIncludingChangesWithoutComposition = + mTextChangeData.mIncludingChangesWithoutComposition; + return NS_OK; + } + return NS_ERROR_NOT_AVAILABLE; + } + + protected: + virtual ~TextInputProcessorNotification() { + if (IsSelectionChange()) { + delete mSelectionChangeData.mString; + mSelectionChangeData.mString = nullptr; + } + } + + bool IsTextChange() const { + return mType.EqualsLiteral("notify-text-change"); + } + + bool IsSelectionChange() const { + return mType.EqualsLiteral("notify-selection-change"); + } + + private: + nsAutoCString mType; + union { + TextChangeDataBase mTextChangeData; + SelectionChangeDataBase mSelectionChangeData; + }; + + TextInputProcessorNotification() : mTextChangeData() {} +}; + +NS_IMPL_ISUPPORTS(TextInputProcessorNotification, + nsITextInputProcessorNotification) + +/****************************************************************************** + * TextInputProcessor + ******************************************************************************/ + +NS_IMPL_ISUPPORTS(TextInputProcessor, nsITextInputProcessor, + TextEventDispatcherListener, nsISupportsWeakReference) + +TextInputProcessor::TextInputProcessor() + : mDispatcher(nullptr), mForTests(false) {} + +TextInputProcessor::~TextInputProcessor() { + if (mDispatcher && mDispatcher->IsComposing()) { + // If this is composing and not canceling the composition, nobody can steal + // the rights of TextEventDispatcher from this instance. Therefore, this + // needs to cancel the composition here. + if (NS_SUCCEEDED(IsValidStateForComposition())) { + RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher); + nsEventStatus status = nsEventStatus_eIgnore; + kungFuDeathGrip->CommitComposition(status, &EmptyString()); + } + } +} + +bool TextInputProcessor::IsComposing() const { + return mDispatcher && mDispatcher->IsComposing(); +} + +NS_IMETHODIMP +TextInputProcessor::GetHasComposition(bool* aHasComposition) { + MOZ_RELEASE_ASSERT(aHasComposition, "aHasComposition must not be nullptr"); + MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); + *aHasComposition = IsComposing(); + return NS_OK; +} + +NS_IMETHODIMP +TextInputProcessor::BeginInputTransaction( + mozIDOMWindow* aWindow, nsITextInputProcessorCallback* aCallback, + bool* aSucceeded) { + MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr"); + MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); + if (NS_WARN_IF(!aCallback)) { + *aSucceeded = false; + return NS_ERROR_INVALID_ARG; + } + return BeginInputTransactionInternal(aWindow, aCallback, false, *aSucceeded); +} + +NS_IMETHODIMP +TextInputProcessor::BeginInputTransactionForTests( + mozIDOMWindow* aWindow, nsITextInputProcessorCallback* aCallback, + uint8_t aOptionalArgc, bool* aSucceeded) { + MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr"); + MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); + nsITextInputProcessorCallback* callback = + aOptionalArgc >= 1 ? aCallback : nullptr; + return BeginInputTransactionInternal(aWindow, callback, true, *aSucceeded); +} + +nsresult TextInputProcessor::BeginInputTransactionForFuzzing( + nsPIDOMWindowInner* aWindow, nsITextInputProcessorCallback* aCallback, + bool* aSucceeded) { + MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr"); + return BeginInputTransactionInternal(aWindow, aCallback, false, *aSucceeded); +} + +nsresult TextInputProcessor::BeginInputTransactionInternal( + mozIDOMWindow* aWindow, nsITextInputProcessorCallback* aCallback, + bool aForTests, bool& aSucceeded) { + aSucceeded = false; + if (NS_WARN_IF(!aWindow)) { + return NS_ERROR_INVALID_ARG; + } + nsCOMPtr<nsPIDOMWindowInner> pWindow = nsPIDOMWindowInner::From(aWindow); + if (NS_WARN_IF(!pWindow)) { + return NS_ERROR_INVALID_ARG; + } + nsCOMPtr<nsIDocShell> docShell(pWindow->GetDocShell()); + if (NS_WARN_IF(!docShell)) { + return NS_ERROR_FAILURE; + } + RefPtr<nsPresContext> presContext = docShell->GetPresContext(); + if (NS_WARN_IF(!presContext)) { + return NS_ERROR_FAILURE; + } + nsCOMPtr<nsIWidget> widget = presContext->GetRootWidget(); + if (NS_WARN_IF(!widget)) { + return NS_ERROR_FAILURE; + } + + RefPtr<TextEventDispatcher> dispatcher = widget->GetTextEventDispatcher(); + MOZ_RELEASE_ASSERT(dispatcher, "TextEventDispatcher must not be null"); + + // If the instance was initialized and is being initialized for same + // dispatcher and same purpose, we don't need to initialize the dispatcher + // again. + if (mDispatcher && dispatcher == mDispatcher && aCallback == mCallback && + aForTests == mForTests) { + aSucceeded = true; + return NS_OK; + } + + // If this instance is composing or dispatching an event, don't allow to + // initialize again. Especially, if we allow to begin input transaction with + // another TextEventDispatcher during dispatching an event, it may cause that + // nobody cannot begin input transaction with it if the last event causes + // opening modal dialog. + if (mDispatcher && + (mDispatcher->IsComposing() || mDispatcher->IsDispatchingEvent())) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + // And also if another instance is composing with the new dispatcher or + // dispatching an event, it'll fail to steal its ownership. Then, we should + // not throw an exception, just return false. + if (dispatcher->IsComposing() || dispatcher->IsDispatchingEvent()) { + return NS_OK; + } + + // This instance has finished preparing to link to the dispatcher. Therefore, + // let's forget the old dispatcher and purpose. + if (mDispatcher) { + mDispatcher->EndInputTransaction(this); + if (NS_WARN_IF(mDispatcher)) { + // Forcibly initialize the members if we failed to end the input + // transaction. + UnlinkFromTextEventDispatcher(); + } + } + + nsresult rv = NS_OK; + if (aForTests) { + bool isAPZAware = StaticPrefs::test_events_async_enabled(); + rv = dispatcher->BeginTestInputTransaction(this, isAPZAware); + } else { + rv = dispatcher->BeginInputTransaction(this); + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mDispatcher = dispatcher; + mCallback = aCallback; + mForTests = aForTests; + aSucceeded = true; + return NS_OK; +} + +void TextInputProcessor::UnlinkFromTextEventDispatcher() { + mDispatcher = nullptr; + mForTests = false; + if (mCallback) { + nsCOMPtr<nsITextInputProcessorCallback> callback(mCallback); + mCallback = nullptr; + + RefPtr<TextInputProcessorNotification> notification = + new TextInputProcessorNotification("notify-end-input-transaction"); + bool result = false; + callback->OnNotify(this, notification, &result); + } +} + +nsresult TextInputProcessor::IsValidStateForComposition() { + if (NS_WARN_IF(!mDispatcher)) { + return NS_ERROR_NOT_INITIALIZED; + } + + nsresult rv = mDispatcher->GetState(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +bool TextInputProcessor::IsValidEventTypeForComposition( + const WidgetKeyboardEvent& aKeyboardEvent) const { + // The key event type of composition methods must be "", "keydown" or "keyup". + if (aKeyboardEvent.mMessage == eKeyDown || + aKeyboardEvent.mMessage == eKeyUp) { + return true; + } + if (aKeyboardEvent.mMessage == eUnidentifiedEvent && + aKeyboardEvent.mSpecifiedEventType && + nsDependentAtomString(aKeyboardEvent.mSpecifiedEventType) + .EqualsLiteral("on")) { + return true; + } + return false; +} + +TextInputProcessor::EventDispatcherResult +TextInputProcessor::MaybeDispatchKeydownForComposition( + const WidgetKeyboardEvent* aKeyboardEvent, uint32_t aKeyFlags) { + EventDispatcherResult result; + + result.mResult = IsValidStateForComposition(); + if (NS_WARN_IF(NS_FAILED(result.mResult))) { + result.mCanContinue = false; + return result; + } + + if (!aKeyboardEvent) { + return result; + } + + // If the mMessage is eKeyUp, the caller doesn't want TIP to dispatch + // eKeyDown event. + if (aKeyboardEvent->mMessage == eKeyUp) { + return result; + } + + // Modifier keys are not allowed because managing modifier state in this + // method makes this messy. + if (NS_WARN_IF(aKeyboardEvent->IsModifierKeyEvent())) { + result.mResult = NS_ERROR_INVALID_ARG; + result.mCanContinue = false; + return result; + } + + uint32_t consumedFlags = 0; + + result.mResult = + KeydownInternal(*aKeyboardEvent, aKeyFlags, false, consumedFlags); + result.mDoDefault = !consumedFlags; + if (NS_WARN_IF(NS_FAILED(result.mResult))) { + result.mCanContinue = false; + return result; + } + + result.mCanContinue = NS_SUCCEEDED(IsValidStateForComposition()); + return result; +} + +TextInputProcessor::EventDispatcherResult +TextInputProcessor::MaybeDispatchKeyupForComposition( + const WidgetKeyboardEvent* aKeyboardEvent, uint32_t aKeyFlags) { + EventDispatcherResult result; + + if (!aKeyboardEvent) { + return result; + } + + // If the mMessage is eKeyDown, the caller doesn't want TIP to dispatch + // eKeyUp event. + if (aKeyboardEvent->mMessage == eKeyDown) { + return result; + } + + // If the widget has been destroyed, we can do nothing here. + result.mResult = IsValidStateForComposition(); + if (NS_FAILED(result.mResult)) { + result.mCanContinue = false; + return result; + } + + result.mResult = KeyupInternal(*aKeyboardEvent, aKeyFlags, result.mDoDefault); + if (NS_WARN_IF(NS_FAILED(result.mResult))) { + result.mCanContinue = false; + return result; + } + + result.mCanContinue = NS_SUCCEEDED(IsValidStateForComposition()); + return result; +} + +nsresult TextInputProcessor::PrepareKeyboardEventForComposition( + KeyboardEvent* aDOMKeyEvent, uint32_t& aKeyFlags, uint8_t aOptionalArgc, + WidgetKeyboardEvent*& aKeyboardEvent) { + aKeyboardEvent = nullptr; + + aKeyboardEvent = aOptionalArgc && aDOMKeyEvent + ? aDOMKeyEvent->WidgetEventPtr()->AsKeyboardEvent() + : nullptr; + if (!aKeyboardEvent || aOptionalArgc < 2) { + aKeyFlags = 0; + } + + if (!aKeyboardEvent) { + return NS_OK; + } + + if (NS_WARN_IF(!IsValidEventTypeForComposition(*aKeyboardEvent))) { + return NS_ERROR_INVALID_ARG; + } + + return NS_OK; +} + +NS_IMETHODIMP +TextInputProcessor::StartComposition(Event* aDOMKeyEvent, uint32_t aKeyFlags, + uint8_t aOptionalArgc, bool* aSucceeded) { + MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr"); + MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); + *aSucceeded = false; + + RefPtr<KeyboardEvent> keyEvent; + if (aDOMKeyEvent) { + keyEvent = aDOMKeyEvent->AsKeyboardEvent(); + if (NS_WARN_IF(!keyEvent)) { + return NS_ERROR_INVALID_ARG; + } + } + + RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher); + + WidgetKeyboardEvent* keyboardEvent; + nsresult rv = PrepareKeyboardEventForComposition( + keyEvent, aKeyFlags, aOptionalArgc, keyboardEvent); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + EventDispatcherResult dispatcherResult = + MaybeDispatchKeydownForComposition(keyboardEvent, aKeyFlags); + if (NS_WARN_IF(NS_FAILED(dispatcherResult.mResult)) || + !dispatcherResult.mCanContinue) { + return dispatcherResult.mResult; + } + + if (dispatcherResult.mDoDefault) { + nsEventStatus status = nsEventStatus_eIgnore; + rv = kungFuDeathGrip->StartComposition(status); + *aSucceeded = status != nsEventStatus_eConsumeNoDefault && + kungFuDeathGrip && kungFuDeathGrip->IsComposing(); + } + + MaybeDispatchKeyupForComposition(keyboardEvent, aKeyFlags); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; +} + +NS_IMETHODIMP +TextInputProcessor::SetPendingCompositionString(const nsAString& aString) { + MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); + RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher); + nsresult rv = IsValidStateForComposition(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return kungFuDeathGrip->SetPendingCompositionString(aString); +} + +NS_IMETHODIMP +TextInputProcessor::AppendClauseToPendingComposition(uint32_t aLength, + uint32_t aAttribute) { + MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); + RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher); + TextRangeType textRangeType; + switch (aAttribute) { + case ATTR_RAW_CLAUSE: + case ATTR_SELECTED_RAW_CLAUSE: + case ATTR_CONVERTED_CLAUSE: + case ATTR_SELECTED_CLAUSE: + textRangeType = ToTextRangeType(aAttribute); + break; + default: + return NS_ERROR_INVALID_ARG; + } + nsresult rv = IsValidStateForComposition(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return kungFuDeathGrip->AppendClauseToPendingComposition(aLength, + textRangeType); +} + +NS_IMETHODIMP +TextInputProcessor::SetCaretInPendingComposition(uint32_t aOffset) { + MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); + RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher); + nsresult rv = IsValidStateForComposition(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return kungFuDeathGrip->SetCaretInPendingComposition(aOffset, 0); +} + +NS_IMETHODIMP +TextInputProcessor::FlushPendingComposition(Event* aDOMKeyEvent, + uint32_t aKeyFlags, + uint8_t aOptionalArgc, + bool* aSucceeded) { + MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr"); + MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); + + // Even if this doesn't flush pending composition actually, we need to reset + // pending composition for starting next composition with new user input. + AutoPendingCompositionResetter resetter(this); + + *aSucceeded = false; + RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher); + bool wasComposing = IsComposing(); + + RefPtr<KeyboardEvent> keyEvent; + if (aDOMKeyEvent) { + keyEvent = aDOMKeyEvent->AsKeyboardEvent(); + if (NS_WARN_IF(!keyEvent)) { + return NS_ERROR_INVALID_ARG; + } + } + + WidgetKeyboardEvent* keyboardEvent; + nsresult rv = PrepareKeyboardEventForComposition( + keyEvent, aKeyFlags, aOptionalArgc, keyboardEvent); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + EventDispatcherResult dispatcherResult = + MaybeDispatchKeydownForComposition(keyboardEvent, aKeyFlags); + if (NS_WARN_IF(NS_FAILED(dispatcherResult.mResult)) || + !dispatcherResult.mCanContinue) { + return dispatcherResult.mResult; + } + + // Even if the preceding keydown event was consumed, if the composition + // was already started, we shouldn't prevent the change of composition. + if (dispatcherResult.mDoDefault || wasComposing) { + // Preceding keydown event may cause destroying the widget. + if (NS_FAILED(IsValidStateForComposition())) { + return NS_OK; + } + nsEventStatus status = nsEventStatus_eIgnore; + rv = kungFuDeathGrip->FlushPendingComposition(status); + *aSucceeded = status != nsEventStatus_eConsumeNoDefault; + } + + MaybeDispatchKeyupForComposition(keyboardEvent, aKeyFlags); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; +} + +NS_IMETHODIMP +TextInputProcessor::CommitComposition(Event* aDOMKeyEvent, uint32_t aKeyFlags, + uint8_t aOptionalArgc) { + MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); + + RefPtr<KeyboardEvent> keyEvent; + if (aDOMKeyEvent) { + keyEvent = aDOMKeyEvent->AsKeyboardEvent(); + if (NS_WARN_IF(!keyEvent)) { + return NS_ERROR_INVALID_ARG; + } + } + + WidgetKeyboardEvent* keyboardEvent; + nsresult rv = PrepareKeyboardEventForComposition( + keyEvent, aKeyFlags, aOptionalArgc, keyboardEvent); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return CommitCompositionInternal(keyboardEvent, aKeyFlags); +} + +NS_IMETHODIMP +TextInputProcessor::CommitCompositionWith(const nsAString& aCommitString, + Event* aDOMKeyEvent, + uint32_t aKeyFlags, + uint8_t aOptionalArgc, + bool* aSucceeded) { + MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr"); + MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); + + RefPtr<KeyboardEvent> keyEvent; + if (aDOMKeyEvent) { + keyEvent = aDOMKeyEvent->AsKeyboardEvent(); + if (NS_WARN_IF(!keyEvent)) { + return NS_ERROR_INVALID_ARG; + } + } + + WidgetKeyboardEvent* keyboardEvent; + nsresult rv = PrepareKeyboardEventForComposition( + keyEvent, aKeyFlags, aOptionalArgc, keyboardEvent); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return CommitCompositionInternal(keyboardEvent, aKeyFlags, &aCommitString, + aSucceeded); +} + +nsresult TextInputProcessor::CommitCompositionInternal( + const WidgetKeyboardEvent* aKeyboardEvent, uint32_t aKeyFlags, + const nsAString* aCommitString, bool* aSucceeded) { + if (aSucceeded) { + *aSucceeded = false; + } + RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher); + bool wasComposing = IsComposing(); + + EventDispatcherResult dispatcherResult = + MaybeDispatchKeydownForComposition(aKeyboardEvent, aKeyFlags); + if (NS_WARN_IF(NS_FAILED(dispatcherResult.mResult)) || + !dispatcherResult.mCanContinue) { + return dispatcherResult.mResult; + } + + // Even if the preceding keydown event was consumed, if the composition + // was already started, we shouldn't prevent the commit of composition. + nsresult rv = NS_OK; + if (dispatcherResult.mDoDefault || wasComposing) { + // Preceding keydown event may cause destroying the widget. + if (NS_FAILED(IsValidStateForComposition())) { + return NS_OK; + } + nsEventStatus status = nsEventStatus_eIgnore; + rv = kungFuDeathGrip->CommitComposition(status, aCommitString); + if (aSucceeded) { + *aSucceeded = status != nsEventStatus_eConsumeNoDefault; + } + } + + MaybeDispatchKeyupForComposition(aKeyboardEvent, aKeyFlags); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; +} + +NS_IMETHODIMP +TextInputProcessor::CancelComposition(Event* aDOMKeyEvent, uint32_t aKeyFlags, + uint8_t aOptionalArgc) { + MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); + + RefPtr<KeyboardEvent> keyEvent; + if (aDOMKeyEvent) { + keyEvent = aDOMKeyEvent->AsKeyboardEvent(); + if (NS_WARN_IF(!keyEvent)) { + return NS_ERROR_INVALID_ARG; + } + } + + WidgetKeyboardEvent* keyboardEvent; + nsresult rv = PrepareKeyboardEventForComposition( + keyEvent, aKeyFlags, aOptionalArgc, keyboardEvent); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return CancelCompositionInternal(keyboardEvent, aKeyFlags); +} + +nsresult TextInputProcessor::CancelCompositionInternal( + const WidgetKeyboardEvent* aKeyboardEvent, uint32_t aKeyFlags) { + RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher); + + EventDispatcherResult dispatcherResult = + MaybeDispatchKeydownForComposition(aKeyboardEvent, aKeyFlags); + if (NS_WARN_IF(NS_FAILED(dispatcherResult.mResult)) || + !dispatcherResult.mCanContinue) { + return dispatcherResult.mResult; + } + + nsEventStatus status = nsEventStatus_eIgnore; + nsresult rv = kungFuDeathGrip->CommitComposition(status, &EmptyString()); + + MaybeDispatchKeyupForComposition(aKeyboardEvent, aKeyFlags); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; +} + +NS_IMETHODIMP +TextInputProcessor::NotifyIME(TextEventDispatcher* aTextEventDispatcher, + const IMENotification& aNotification) { + // If This is called while this is being initialized, ignore the call. + // In such case, this method should return NS_ERROR_NOT_IMPLEMENTED because + // we can say, TextInputProcessor doesn't implement any handlers of the + // requests and notifications. + if (!mDispatcher) { + return NS_ERROR_NOT_IMPLEMENTED; + } + MOZ_ASSERT(aTextEventDispatcher == mDispatcher, + "Wrong TextEventDispatcher notifies this"); + NS_ASSERTION(mForTests || mCallback, + "mCallback can be null only when IME is initialized for tests"); + if (mCallback) { + RefPtr<TextInputProcessorNotification> notification; + switch (aNotification.mMessage) { + case REQUEST_TO_COMMIT_COMPOSITION: { + NS_ASSERTION(aTextEventDispatcher->IsComposing(), + "Why is this requested without composition?"); + notification = new TextInputProcessorNotification("request-to-commit"); + break; + } + case REQUEST_TO_CANCEL_COMPOSITION: { + NS_ASSERTION(aTextEventDispatcher->IsComposing(), + "Why is this requested without composition?"); + notification = new TextInputProcessorNotification("request-to-cancel"); + break; + } + case NOTIFY_IME_OF_FOCUS: + notification = new TextInputProcessorNotification("notify-focus"); + break; + case NOTIFY_IME_OF_BLUR: + notification = new TextInputProcessorNotification("notify-blur"); + break; + case NOTIFY_IME_OF_TEXT_CHANGE: + notification = + new TextInputProcessorNotification(aNotification.mTextChangeData); + break; + case NOTIFY_IME_OF_SELECTION_CHANGE: + notification = new TextInputProcessorNotification( + aNotification.mSelectionChangeData); + break; + case NOTIFY_IME_OF_POSITION_CHANGE: + notification = + new TextInputProcessorNotification("notify-position-change"); + break; + default: + return NS_ERROR_NOT_IMPLEMENTED; + } + MOZ_RELEASE_ASSERT(notification); + bool result = false; + nsresult rv = mCallback->OnNotify(this, notification, &result); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return result ? NS_OK : NS_ERROR_FAILURE; + } + + switch (aNotification.mMessage) { + case REQUEST_TO_COMMIT_COMPOSITION: { + NS_ASSERTION(aTextEventDispatcher->IsComposing(), + "Why is this requested without composition?"); + CommitCompositionInternal(); + return NS_OK; + } + case REQUEST_TO_CANCEL_COMPOSITION: { + NS_ASSERTION(aTextEventDispatcher->IsComposing(), + "Why is this requested without composition?"); + CancelCompositionInternal(); + return NS_OK; + } + default: + return NS_ERROR_NOT_IMPLEMENTED; + } +} + +NS_IMETHODIMP_(IMENotificationRequests) +TextInputProcessor::GetIMENotificationRequests() { + // TextInputProcessor should support all change notifications. + return IMENotificationRequests( + IMENotificationRequests::NOTIFY_TEXT_CHANGE | + IMENotificationRequests::NOTIFY_POSITION_CHANGE); +} + +NS_IMETHODIMP_(void) +TextInputProcessor::OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) { + // If This is called while this is being initialized, ignore the call. + if (!mDispatcher) { + return; + } + MOZ_ASSERT(aTextEventDispatcher == mDispatcher, + "Wrong TextEventDispatcher notifies this"); + UnlinkFromTextEventDispatcher(); +} + +NS_IMETHODIMP_(void) +TextInputProcessor::WillDispatchKeyboardEvent( + TextEventDispatcher* aTextEventDispatcher, + WidgetKeyboardEvent& aKeyboardEvent, uint32_t aIndexOfKeypress, + void* aData) { + // TextInputProcessor doesn't set alternative char code nor modify charCode + // even when Ctrl key is pressed. +} + +nsresult TextInputProcessor::PrepareKeyboardEventToDispatch( + WidgetKeyboardEvent& aKeyboardEvent, uint32_t aKeyFlags) { + if (NS_WARN_IF(aKeyboardEvent.mCodeNameIndex == CODE_NAME_INDEX_USE_STRING)) { + return NS_ERROR_INVALID_ARG; + } + if ((aKeyFlags & KEY_NON_PRINTABLE_KEY) && + NS_WARN_IF(aKeyboardEvent.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING)) { + return NS_ERROR_INVALID_ARG; + } + if ((aKeyFlags & KEY_FORCE_PRINTABLE_KEY) && + aKeyboardEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING) { + aKeyboardEvent.GetDOMKeyName(aKeyboardEvent.mKeyValue); + aKeyboardEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING; + } + if (aKeyFlags & KEY_KEEP_KEY_LOCATION_STANDARD) { + // If .location is initialized with specific value, using + // KEY_KEEP_KEY_LOCATION_STANDARD must be a bug of the caller. + // Let's throw an exception for notifying the developer of this bug. + if (NS_WARN_IF(aKeyboardEvent.mLocation)) { + return NS_ERROR_INVALID_ARG; + } + } else if (!aKeyboardEvent.mLocation) { + // If KeyboardEvent.mLocation is 0, it may be uninitialized. If so, we + // should compute proper mLocation value from its .code value. + aKeyboardEvent.mLocation = + WidgetKeyboardEvent::ComputeLocationFromCodeValue( + aKeyboardEvent.mCodeNameIndex); + } + + if (aKeyFlags & KEY_KEEP_KEYCODE_ZERO) { + // If .keyCode is initialized with specific value, using + // KEY_KEEP_KEYCODE_ZERO must be a bug of the caller. Let's throw an + // exception for notifying the developer of such bug. + if (NS_WARN_IF(aKeyboardEvent.mKeyCode)) { + return NS_ERROR_INVALID_ARG; + } + } else if (!aKeyboardEvent.mKeyCode && + aKeyboardEvent.mKeyNameIndex > KEY_NAME_INDEX_Unidentified && + aKeyboardEvent.mKeyNameIndex < KEY_NAME_INDEX_USE_STRING) { + // If KeyboardEvent.keyCode is 0, it may be uninitialized. If so, we may + // be able to decide a good .keyCode value if the .key value is a + // non-printable key. + aKeyboardEvent.mKeyCode = + WidgetKeyboardEvent::ComputeKeyCodeFromKeyNameIndex( + aKeyboardEvent.mKeyNameIndex); + } + + aKeyboardEvent.mIsSynthesizedByTIP = !mForTests; + + // When this emulates real input only in content process, we need to + // initialize edit commands with the main process's widget via PuppetWidget + // because they are initialized by BrowserParent before content process treats + // them. + if (aKeyboardEvent.mIsSynthesizedByTIP && !XRE_IsParentProcess()) { + // Note that retrieving edit commands from content process is expensive. + // Let's skip it when the keyboard event is inputting text. + if (!aKeyboardEvent.IsInputtingText()) { + // FYI: WidgetKeyboardEvent::InitAllEditCommands() isn't available here + // since it checks whether it's called in the main process to + // avoid performance issues so that we need to initialize each + // command manually here. + if (NS_WARN_IF(!aKeyboardEvent.InitEditCommandsFor( + nsIWidget::NativeKeyBindingsForSingleLineEditor))) { + return NS_ERROR_NOT_AVAILABLE; + } + if (NS_WARN_IF(!aKeyboardEvent.InitEditCommandsFor( + nsIWidget::NativeKeyBindingsForMultiLineEditor))) { + return NS_ERROR_NOT_AVAILABLE; + } + if (NS_WARN_IF(!aKeyboardEvent.InitEditCommandsFor( + nsIWidget::NativeKeyBindingsForRichTextEditor))) { + return NS_ERROR_NOT_AVAILABLE; + } + } else { + aKeyboardEvent.PreventNativeKeyBindings(); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +TextInputProcessor::Keydown(Event* aDOMKeyEvent, uint32_t aKeyFlags, + uint8_t aOptionalArgc, uint32_t* aConsumedFlags) { + MOZ_RELEASE_ASSERT(aConsumedFlags, "aConsumedFlags must not be nullptr"); + MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); + if (!aOptionalArgc) { + aKeyFlags = 0; + } + if (NS_WARN_IF(!aDOMKeyEvent)) { + return NS_ERROR_INVALID_ARG; + } + WidgetKeyboardEvent* originalKeyEvent = + aDOMKeyEvent->WidgetEventPtr()->AsKeyboardEvent(); + if (NS_WARN_IF(!originalKeyEvent)) { + return NS_ERROR_INVALID_ARG; + } + return KeydownInternal(*originalKeyEvent, aKeyFlags, true, *aConsumedFlags); +} + +nsresult TextInputProcessor::Keydown(const WidgetKeyboardEvent& aKeyboardEvent, + uint32_t aKeyFlags, + uint32_t* aConsumedFlags) { + uint32_t consumedFlags = 0; + return KeydownInternal(aKeyboardEvent, aKeyFlags, true, + aConsumedFlags ? *aConsumedFlags : consumedFlags); +} + +nsresult TextInputProcessor::KeydownInternal( + const WidgetKeyboardEvent& aKeyboardEvent, uint32_t aKeyFlags, + bool aAllowToDispatchKeypress, uint32_t& aConsumedFlags) { + aConsumedFlags = KEYEVENT_NOT_CONSUMED; + + // We shouldn't modify the internal WidgetKeyboardEvent. + WidgetKeyboardEvent keyEvent(aKeyboardEvent); + nsresult rv = PrepareKeyboardEventToDispatch(keyEvent, aKeyFlags); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + aConsumedFlags = (aKeyFlags & KEY_DEFAULT_PREVENTED) ? KEYDOWN_IS_CONSUMED + : KEYEVENT_NOT_CONSUMED; + + if (WidgetKeyboardEvent::GetModifierForKeyName(keyEvent.mKeyNameIndex)) { + ModifierKeyData modifierKeyData(keyEvent); + if (WidgetKeyboardEvent::IsLockableModifier(keyEvent.mKeyNameIndex)) { + // If the modifier key is lockable modifier key such as CapsLock, + // let's toggle modifier key state at keydown. + ToggleModifierKey(modifierKeyData); + } else { + // Activate modifier flag before dispatching keydown event (i.e., keydown + // event should indicate the releasing modifier is active. + ActivateModifierKey(modifierKeyData); + } + if (aKeyFlags & KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) { + return NS_OK; + } + } else if (NS_WARN_IF(aKeyFlags & KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT)) { + return NS_ERROR_INVALID_ARG; + } + keyEvent.mModifiers = GetActiveModifiers(); + + if (!aAllowToDispatchKeypress && + !(aKeyFlags & KEY_DONT_MARK_KEYDOWN_AS_PROCESSED)) { + keyEvent.mKeyCode = NS_VK_PROCESSKEY; + keyEvent.mKeyNameIndex = KEY_NAME_INDEX_Process; + } + + RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher); + rv = IsValidStateForComposition(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsEventStatus status = + aConsumedFlags ? nsEventStatus_eConsumeNoDefault : nsEventStatus_eIgnore; + if (!kungFuDeathGrip->DispatchKeyboardEvent(eKeyDown, keyEvent, status)) { + // If keydown event isn't dispatched, we don't need to dispatch keypress + // events. + return NS_OK; + } + + aConsumedFlags |= (status == nsEventStatus_eConsumeNoDefault) + ? KEYDOWN_IS_CONSUMED + : KEYEVENT_NOT_CONSUMED; + + if (aAllowToDispatchKeypress && + kungFuDeathGrip->MaybeDispatchKeypressEvents(keyEvent, status)) { + aConsumedFlags |= (status == nsEventStatus_eConsumeNoDefault) + ? KEYPRESS_IS_CONSUMED + : KEYEVENT_NOT_CONSUMED; + } + + return NS_OK; +} + +NS_IMETHODIMP +TextInputProcessor::Keyup(Event* aDOMKeyEvent, uint32_t aKeyFlags, + uint8_t aOptionalArgc, bool* aDoDefault) { + MOZ_RELEASE_ASSERT(aDoDefault, "aDoDefault must not be nullptr"); + MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); + if (!aOptionalArgc) { + aKeyFlags = 0; + } + if (NS_WARN_IF(!aDOMKeyEvent)) { + return NS_ERROR_INVALID_ARG; + } + WidgetKeyboardEvent* originalKeyEvent = + aDOMKeyEvent->WidgetEventPtr()->AsKeyboardEvent(); + if (NS_WARN_IF(!originalKeyEvent)) { + return NS_ERROR_INVALID_ARG; + } + return KeyupInternal(*originalKeyEvent, aKeyFlags, *aDoDefault); +} + +nsresult TextInputProcessor::Keyup(const WidgetKeyboardEvent& aKeyboardEvent, + uint32_t aKeyFlags, bool* aDoDefault) { + bool doDefault = false; + return KeyupInternal(aKeyboardEvent, aKeyFlags, + aDoDefault ? *aDoDefault : doDefault); +} + +nsresult TextInputProcessor::KeyupInternal( + const WidgetKeyboardEvent& aKeyboardEvent, uint32_t aKeyFlags, + bool& aDoDefault) { + aDoDefault = false; + + // We shouldn't modify the internal WidgetKeyboardEvent. + WidgetKeyboardEvent keyEvent(aKeyboardEvent); + nsresult rv = PrepareKeyboardEventToDispatch(keyEvent, aKeyFlags); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + aDoDefault = !(aKeyFlags & KEY_DEFAULT_PREVENTED); + + if (WidgetKeyboardEvent::GetModifierForKeyName(keyEvent.mKeyNameIndex)) { + if (!WidgetKeyboardEvent::IsLockableModifier(keyEvent.mKeyNameIndex)) { + // Inactivate modifier flag before dispatching keyup event (i.e., keyup + // event shouldn't indicate the releasing modifier is active. + InactivateModifierKey(ModifierKeyData(keyEvent)); + } + if (aKeyFlags & KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) { + return NS_OK; + } + } else if (NS_WARN_IF(aKeyFlags & KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT)) { + return NS_ERROR_INVALID_ARG; + } + keyEvent.mModifiers = GetActiveModifiers(); + + if (aKeyFlags & KEY_MARK_KEYUP_AS_PROCESSED) { + keyEvent.mKeyCode = NS_VK_PROCESSKEY; + keyEvent.mKeyNameIndex = KEY_NAME_INDEX_Process; + } + + RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher); + rv = IsValidStateForComposition(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsEventStatus status = + aDoDefault ? nsEventStatus_eIgnore : nsEventStatus_eConsumeNoDefault; + kungFuDeathGrip->DispatchKeyboardEvent(eKeyUp, keyEvent, status); + aDoDefault = (status != nsEventStatus_eConsumeNoDefault); + return NS_OK; +} + +NS_IMETHODIMP +TextInputProcessor::GetModifierState(const nsAString& aModifierKeyName, + bool* aActive) { + MOZ_RELEASE_ASSERT(aActive, "aActive must not be null"); + MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); + Modifiers modifier = WidgetInputEvent::GetModifier(aModifierKeyName); + *aActive = ((GetActiveModifiers() & modifier) != 0); + return NS_OK; +} + +NS_IMETHODIMP +TextInputProcessor::ShareModifierStateOf(nsITextInputProcessor* aOther) { + MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); + if (!aOther) { + mModifierKeyDataArray = nullptr; + return NS_OK; + } + TextInputProcessor* other = static_cast<TextInputProcessor*>(aOther); + if (!other->mModifierKeyDataArray) { + other->mModifierKeyDataArray = new ModifierKeyDataArray(); + } + mModifierKeyDataArray = other->mModifierKeyDataArray; + return NS_OK; +} + +NS_IMETHODIMP +TextInputProcessor::ComputeCodeValueOfNonPrintableKey( + const nsAString& aKeyValue, JS::Handle<JS::Value> aLocation, + uint8_t aOptionalArgc, nsAString& aCodeValue) { + aCodeValue.Truncate(); + + Maybe<uint32_t> location; + if (aOptionalArgc) { + if (aLocation.isNullOrUndefined()) { + // location should be nothing. + } else if (aLocation.isInt32()) { + location = mozilla::Some(static_cast<uint32_t>(aLocation.toInt32())); + } else { + NS_WARNING_ASSERTION(aLocation.isNullOrUndefined() || aLocation.isInt32(), + "aLocation must be undefined, null or int"); + return NS_ERROR_INVALID_ARG; + } + } + + KeyNameIndex keyNameIndex = WidgetKeyboardEvent::GetKeyNameIndex(aKeyValue); + if (keyNameIndex == KEY_NAME_INDEX_Unidentified || + keyNameIndex == KEY_NAME_INDEX_USE_STRING) { + return NS_OK; + } + + CodeNameIndex codeNameIndex = + WidgetKeyboardEvent::ComputeCodeNameIndexFromKeyNameIndex(keyNameIndex, + location); + if (codeNameIndex == CODE_NAME_INDEX_UNKNOWN) { + return NS_OK; + } + MOZ_ASSERT(codeNameIndex != CODE_NAME_INDEX_USE_STRING); + WidgetKeyboardEvent::GetDOMCodeName(codeNameIndex, aCodeValue); + return NS_OK; +} + +NS_IMETHODIMP +TextInputProcessor::GuessCodeValueOfPrintableKeyInUSEnglishKeyboardLayout( + const nsAString& aKeyValue, JS::Handle<JS::Value> aLocation, + uint8_t aOptionalArgc, nsAString& aCodeValue) { + aCodeValue.Truncate(); + + Maybe<uint32_t> location; + if (aOptionalArgc) { + if (aLocation.isNullOrUndefined()) { + // location should be nothing. + } else if (aLocation.isInt32()) { + location = mozilla::Some(static_cast<uint32_t>(aLocation.toInt32())); + } else { + NS_WARNING_ASSERTION(aLocation.isNullOrUndefined() || aLocation.isInt32(), + "aLocation must be undefined, null or int"); + return NS_ERROR_INVALID_ARG; + } + } + CodeNameIndex codeNameIndex = + GuessCodeNameIndexOfPrintableKeyInUSEnglishLayout(aKeyValue, location); + if (codeNameIndex == CODE_NAME_INDEX_UNKNOWN) { + return NS_OK; + } + MOZ_ASSERT(codeNameIndex != CODE_NAME_INDEX_USE_STRING); + WidgetKeyboardEvent::GetDOMCodeName(codeNameIndex, aCodeValue); + return NS_OK; +} + +// static +CodeNameIndex +TextInputProcessor::GuessCodeNameIndexOfPrintableKeyInUSEnglishLayout( + const nsAString& aKeyValue, const Maybe<uint32_t>& aLocation) { + if (aKeyValue.IsEmpty()) { + return CODE_NAME_INDEX_UNKNOWN; + } + // US keyboard layout can input only one character per key. So, we can + // assume that if the key value is 2 or more characters, it's a known + // key name or not a usual key emulation. + if (aKeyValue.Length() > 1) { + return CODE_NAME_INDEX_UNKNOWN; + } + if (aLocation.isSome() && + aLocation.value() == + dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_NUMPAD) { + switch (aKeyValue[0]) { + case '+': + return CODE_NAME_INDEX_NumpadAdd; + case '-': + return CODE_NAME_INDEX_NumpadSubtract; + case '*': + return CODE_NAME_INDEX_NumpadMultiply; + case '/': + return CODE_NAME_INDEX_NumpadDivide; + case '.': + return CODE_NAME_INDEX_NumpadDecimal; + case '0': + return CODE_NAME_INDEX_Numpad0; + case '1': + return CODE_NAME_INDEX_Numpad1; + case '2': + return CODE_NAME_INDEX_Numpad2; + case '3': + return CODE_NAME_INDEX_Numpad3; + case '4': + return CODE_NAME_INDEX_Numpad4; + case '5': + return CODE_NAME_INDEX_Numpad5; + case '6': + return CODE_NAME_INDEX_Numpad6; + case '7': + return CODE_NAME_INDEX_Numpad7; + case '8': + return CODE_NAME_INDEX_Numpad8; + case '9': + return CODE_NAME_INDEX_Numpad9; + default: + return CODE_NAME_INDEX_UNKNOWN; + } + } + + if (aLocation.isSome() && + aLocation.value() != + dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_STANDARD) { + return CODE_NAME_INDEX_UNKNOWN; + } + + // TODO: Support characters inputted with option key on macOS. + switch (aKeyValue[0]) { + case 'a': + case 'A': + return CODE_NAME_INDEX_KeyA; + case 'b': + case 'B': + return CODE_NAME_INDEX_KeyB; + case 'c': + case 'C': + return CODE_NAME_INDEX_KeyC; + case 'd': + case 'D': + return CODE_NAME_INDEX_KeyD; + case 'e': + case 'E': + return CODE_NAME_INDEX_KeyE; + case 'f': + case 'F': + return CODE_NAME_INDEX_KeyF; + case 'g': + case 'G': + return CODE_NAME_INDEX_KeyG; + case 'h': + case 'H': + return CODE_NAME_INDEX_KeyH; + case 'i': + case 'I': + return CODE_NAME_INDEX_KeyI; + case 'j': + case 'J': + return CODE_NAME_INDEX_KeyJ; + case 'k': + case 'K': + return CODE_NAME_INDEX_KeyK; + case 'l': + case 'L': + return CODE_NAME_INDEX_KeyL; + case 'm': + case 'M': + return CODE_NAME_INDEX_KeyM; + case 'n': + case 'N': + return CODE_NAME_INDEX_KeyN; + case 'o': + case 'O': + return CODE_NAME_INDEX_KeyO; + case 'p': + case 'P': + return CODE_NAME_INDEX_KeyP; + case 'q': + case 'Q': + return CODE_NAME_INDEX_KeyQ; + case 'r': + case 'R': + return CODE_NAME_INDEX_KeyR; + case 's': + case 'S': + return CODE_NAME_INDEX_KeyS; + case 't': + case 'T': + return CODE_NAME_INDEX_KeyT; + case 'u': + case 'U': + return CODE_NAME_INDEX_KeyU; + case 'v': + case 'V': + return CODE_NAME_INDEX_KeyV; + case 'w': + case 'W': + return CODE_NAME_INDEX_KeyW; + case 'x': + case 'X': + return CODE_NAME_INDEX_KeyX; + case 'y': + case 'Y': + return CODE_NAME_INDEX_KeyY; + case 'z': + case 'Z': + return CODE_NAME_INDEX_KeyZ; + + case '`': + case '~': + return CODE_NAME_INDEX_Backquote; + case '1': + case '!': + return CODE_NAME_INDEX_Digit1; + case '2': + case '@': + return CODE_NAME_INDEX_Digit2; + case '3': + case '#': + return CODE_NAME_INDEX_Digit3; + case '4': + case '$': + return CODE_NAME_INDEX_Digit4; + case '5': + case '%': + return CODE_NAME_INDEX_Digit5; + case '6': + case '^': + return CODE_NAME_INDEX_Digit6; + case '7': + case '&': + return CODE_NAME_INDEX_Digit7; + case '8': + case '*': + return CODE_NAME_INDEX_Digit8; + case '9': + case '(': + return CODE_NAME_INDEX_Digit9; + case '0': + case ')': + return CODE_NAME_INDEX_Digit0; + case '-': + case '_': + return CODE_NAME_INDEX_Minus; + case '=': + case '+': + return CODE_NAME_INDEX_Equal; + + case '[': + case '{': + return CODE_NAME_INDEX_BracketLeft; + case ']': + case '}': + return CODE_NAME_INDEX_BracketRight; + case '\\': + case '|': + return CODE_NAME_INDEX_Backslash; + + case ';': + case ':': + return CODE_NAME_INDEX_Semicolon; + case '\'': + case '"': + return CODE_NAME_INDEX_Quote; + + case ',': + case '<': + return CODE_NAME_INDEX_Comma; + case '.': + case '>': + return CODE_NAME_INDEX_Period; + case '/': + case '?': + return CODE_NAME_INDEX_Slash; + + case ' ': + return CODE_NAME_INDEX_Space; + + default: + return CODE_NAME_INDEX_UNKNOWN; + } +} + +NS_IMETHODIMP +TextInputProcessor::GuessKeyCodeValueOfPrintableKeyInUSEnglishKeyboardLayout( + const nsAString& aKeyValue, JS::Handle<JS::Value> aLocation, + uint8_t aOptionalArgc, uint32_t* aKeyCodeValue) { + if (NS_WARN_IF(!aKeyCodeValue)) { + return NS_ERROR_INVALID_ARG; + } + + Maybe<uint32_t> location; + if (aOptionalArgc) { + if (aLocation.isNullOrUndefined()) { + // location should be nothing. + } else if (aLocation.isInt32()) { + location = mozilla::Some(static_cast<uint32_t>(aLocation.toInt32())); + } else { + NS_WARNING_ASSERTION(aLocation.isNullOrUndefined() || aLocation.isInt32(), + "aLocation must be undefined, null or int"); + return NS_ERROR_INVALID_ARG; + } + } + + *aKeyCodeValue = + GuessKeyCodeOfPrintableKeyInUSEnglishLayout(aKeyValue, location); + return NS_OK; +} + +// static +uint32_t TextInputProcessor::GuessKeyCodeOfPrintableKeyInUSEnglishLayout( + const nsAString& aKeyValue, const Maybe<uint32_t>& aLocation) { + if (aKeyValue.IsEmpty()) { + return 0; + } + // US keyboard layout can input only one character per key. So, we can + // assume that if the key value is 2 or more characters, it's a known + // key name of a non-printable key or not a usual key emulation. + if (aKeyValue.Length() > 1) { + return 0; + } + + if (aLocation.isSome() && + aLocation.value() == + dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_NUMPAD) { + switch (aKeyValue[0]) { + case '+': + return dom::KeyboardEvent_Binding::DOM_VK_ADD; + case '-': + return dom::KeyboardEvent_Binding::DOM_VK_SUBTRACT; + case '*': + return dom::KeyboardEvent_Binding::DOM_VK_MULTIPLY; + case '/': + return dom::KeyboardEvent_Binding::DOM_VK_DIVIDE; + case '.': + return dom::KeyboardEvent_Binding::DOM_VK_DECIMAL; + case '0': + return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD0; + case '1': + return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD1; + case '2': + return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD2; + case '3': + return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD3; + case '4': + return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD4; + case '5': + return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD5; + case '6': + return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD6; + case '7': + return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD7; + case '8': + return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD8; + case '9': + return dom::KeyboardEvent_Binding::DOM_VK_NUMPAD9; + default: + return 0; + } + } + + if (aLocation.isSome() && + aLocation.value() != + dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_STANDARD) { + return 0; + } + + // TODO: Support characters inputted with option key on macOS. + switch (aKeyValue[0]) { + case 'a': + case 'A': + return dom::KeyboardEvent_Binding::DOM_VK_A; + case 'b': + case 'B': + return dom::KeyboardEvent_Binding::DOM_VK_B; + case 'c': + case 'C': + return dom::KeyboardEvent_Binding::DOM_VK_C; + case 'd': + case 'D': + return dom::KeyboardEvent_Binding::DOM_VK_D; + case 'e': + case 'E': + return dom::KeyboardEvent_Binding::DOM_VK_E; + case 'f': + case 'F': + return dom::KeyboardEvent_Binding::DOM_VK_F; + case 'g': + case 'G': + return dom::KeyboardEvent_Binding::DOM_VK_G; + case 'h': + case 'H': + return dom::KeyboardEvent_Binding::DOM_VK_H; + case 'i': + case 'I': + return dom::KeyboardEvent_Binding::DOM_VK_I; + case 'j': + case 'J': + return dom::KeyboardEvent_Binding::DOM_VK_J; + case 'k': + case 'K': + return dom::KeyboardEvent_Binding::DOM_VK_K; + case 'l': + case 'L': + return dom::KeyboardEvent_Binding::DOM_VK_L; + case 'm': + case 'M': + return dom::KeyboardEvent_Binding::DOM_VK_M; + case 'n': + case 'N': + return dom::KeyboardEvent_Binding::DOM_VK_N; + case 'o': + case 'O': + return dom::KeyboardEvent_Binding::DOM_VK_O; + case 'p': + case 'P': + return dom::KeyboardEvent_Binding::DOM_VK_P; + case 'q': + case 'Q': + return dom::KeyboardEvent_Binding::DOM_VK_Q; + case 'r': + case 'R': + return dom::KeyboardEvent_Binding::DOM_VK_R; + case 's': + case 'S': + return dom::KeyboardEvent_Binding::DOM_VK_S; + case 't': + case 'T': + return dom::KeyboardEvent_Binding::DOM_VK_T; + case 'u': + case 'U': + return dom::KeyboardEvent_Binding::DOM_VK_U; + case 'v': + case 'V': + return dom::KeyboardEvent_Binding::DOM_VK_V; + case 'w': + case 'W': + return dom::KeyboardEvent_Binding::DOM_VK_W; + case 'x': + case 'X': + return dom::KeyboardEvent_Binding::DOM_VK_X; + case 'y': + case 'Y': + return dom::KeyboardEvent_Binding::DOM_VK_Y; + case 'z': + case 'Z': + return dom::KeyboardEvent_Binding::DOM_VK_Z; + + case '`': + case '~': + return dom::KeyboardEvent_Binding::DOM_VK_BACK_QUOTE; + case '1': + case '!': + return dom::KeyboardEvent_Binding::DOM_VK_1; + case '2': + case '@': + return dom::KeyboardEvent_Binding::DOM_VK_2; + case '3': + case '#': + return dom::KeyboardEvent_Binding::DOM_VK_3; + case '4': + case '$': + return dom::KeyboardEvent_Binding::DOM_VK_4; + case '5': + case '%': + return dom::KeyboardEvent_Binding::DOM_VK_5; + case '6': + case '^': + return dom::KeyboardEvent_Binding::DOM_VK_6; + case '7': + case '&': + return dom::KeyboardEvent_Binding::DOM_VK_7; + case '8': + case '*': + return dom::KeyboardEvent_Binding::DOM_VK_8; + case '9': + case '(': + return dom::KeyboardEvent_Binding::DOM_VK_9; + case '0': + case ')': + return dom::KeyboardEvent_Binding::DOM_VK_0; + case '-': + case '_': + return dom::KeyboardEvent_Binding::DOM_VK_HYPHEN_MINUS; + case '=': + case '+': + return dom::KeyboardEvent_Binding::DOM_VK_EQUALS; + + case '[': + case '{': + return dom::KeyboardEvent_Binding::DOM_VK_OPEN_BRACKET; + case ']': + case '}': + return dom::KeyboardEvent_Binding::DOM_VK_CLOSE_BRACKET; + case '\\': + case '|': + return dom::KeyboardEvent_Binding::DOM_VK_BACK_SLASH; + + case ';': + case ':': + return dom::KeyboardEvent_Binding::DOM_VK_SEMICOLON; + case '\'': + case '"': + return dom::KeyboardEvent_Binding::DOM_VK_QUOTE; + + case ',': + case '<': + return dom::KeyboardEvent_Binding::DOM_VK_COMMA; + case '.': + case '>': + return dom::KeyboardEvent_Binding::DOM_VK_PERIOD; + case '/': + case '?': + return dom::KeyboardEvent_Binding::DOM_VK_SLASH; + + case ' ': + return dom::KeyboardEvent_Binding::DOM_VK_SPACE; + + default: + return 0; + } +} + +/****************************************************************************** + * TextInputProcessor::AutoPendingCompositionResetter + ******************************************************************************/ +TextInputProcessor::AutoPendingCompositionResetter:: + AutoPendingCompositionResetter(TextInputProcessor* aTIP) + : mTIP(aTIP) { + MOZ_RELEASE_ASSERT(mTIP.get(), "mTIP must not be null"); +} + +TextInputProcessor::AutoPendingCompositionResetter:: + ~AutoPendingCompositionResetter() { + if (mTIP->mDispatcher) { + mTIP->mDispatcher->ClearPendingComposition(); + } +} + +/****************************************************************************** + * TextInputProcessor::ModifierKeyData + ******************************************************************************/ +TextInputProcessor::ModifierKeyData::ModifierKeyData( + const WidgetKeyboardEvent& aKeyboardEvent) + : mKeyNameIndex(aKeyboardEvent.mKeyNameIndex), + mCodeNameIndex(aKeyboardEvent.mCodeNameIndex) { + mModifier = WidgetKeyboardEvent::GetModifierForKeyName(mKeyNameIndex); + MOZ_ASSERT(mModifier, "mKeyNameIndex must be a modifier key name"); +} + +/****************************************************************************** + * TextInputProcessor::ModifierKeyDataArray + ******************************************************************************/ +Modifiers TextInputProcessor::ModifierKeyDataArray::GetActiveModifiers() const { + Modifiers result = MODIFIER_NONE; + for (uint32_t i = 0; i < Length(); i++) { + result |= ElementAt(i).mModifier; + } + return result; +} + +void TextInputProcessor::ModifierKeyDataArray::ActivateModifierKey( + const TextInputProcessor::ModifierKeyData& aModifierKeyData) { + if (Contains(aModifierKeyData)) { + return; + } + AppendElement(aModifierKeyData); +} + +void TextInputProcessor::ModifierKeyDataArray::InactivateModifierKey( + const TextInputProcessor::ModifierKeyData& aModifierKeyData) { + RemoveElement(aModifierKeyData); +} + +void TextInputProcessor::ModifierKeyDataArray::ToggleModifierKey( + const TextInputProcessor::ModifierKeyData& aModifierKeyData) { + auto index = IndexOf(aModifierKeyData); + if (index == NoIndex) { + AppendElement(aModifierKeyData); + return; + } + RemoveElementAt(index); +} + +} // namespace mozilla |