diff options
Diffstat (limited to 'dom/base/TextInputProcessor.cpp')
-rw-r--r-- | dom/base/TextInputProcessor.cpp | 1035 |
1 files changed, 1035 insertions, 0 deletions
diff --git a/dom/base/TextInputProcessor.cpp b/dom/base/TextInputProcessor.cpp new file mode 100644 index 000000000..3ebbc5347 --- /dev/null +++ b/dom/base/TextInputProcessor.cpp @@ -0,0 +1,1035 @@ +/* -*- 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 "gfxPrefs.h" +#include "mozilla/dom/Event.h" +#include "mozilla/EventForwards.h" +#include "mozilla/TextEventDispatcher.h" +#include "mozilla/TextEvents.h" +#include "mozilla/TextInputProcessor.h" +#include "nsContentUtils.h" +#include "nsIDocShell.h" +#include "nsIWidget.h" +#include "nsPIDOMWindow.h" +#include "nsPresContext.h" + +using namespace mozilla::widget; + +namespace mozilla { + +/****************************************************************************** + * TextInputProcessorNotification + ******************************************************************************/ + +class TextInputProcessorNotification final : + public nsITextInputProcessorNotification +{ +public: + explicit TextInputProcessorNotification(const char* aType) + : mType(aType) + { + } + + NS_DECL_ISUPPORTS + + NS_IMETHOD GetType(nsACString& aType) override final + { + aType = mType; + return NS_OK; + } + +protected: + ~TextInputProcessorNotification() { } + +private: + nsAutoCString mType; + + TextInputProcessorNotification() { } +}; + +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::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; + nsresult rv = docShell->GetPresContext(getter_AddRefs(presContext)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + 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(); + } + } + + if (aForTests) { + bool isAPZAware = gfxPrefs::TestEventsAsyncEnabled(); + 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 "" or "keydown". + if (aKeyboardEvent.mMessage == eKeyDown) { + 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; + } + + // 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 + // keyup 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( + nsIDOMKeyEvent* aDOMKeyEvent, + uint32_t& aKeyFlags, + uint8_t aOptionalArgc, + WidgetKeyboardEvent*& aKeyboardEvent) +{ + aKeyboardEvent = nullptr; + + aKeyboardEvent = + aOptionalArgc && aDOMKeyEvent ? + aDOMKeyEvent->AsEvent()->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(nsIDOMKeyEvent* 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<TextEventDispatcher> kungFuDeathGrip(mDispatcher); + + WidgetKeyboardEvent* keyboardEvent; + nsresult rv = + PrepareKeyboardEventForComposition(aDOMKeyEvent, 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(nsIDOMKeyEvent* 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(); + + WidgetKeyboardEvent* keyboardEvent; + nsresult rv = + PrepareKeyboardEventForComposition(aDOMKeyEvent, 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(nsIDOMKeyEvent* aDOMKeyEvent, + uint32_t aKeyFlags, + uint8_t aOptionalArgc) +{ + MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); + + WidgetKeyboardEvent* keyboardEvent; + nsresult rv = + PrepareKeyboardEventForComposition(aDOMKeyEvent, aKeyFlags, aOptionalArgc, + keyboardEvent); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return CommitCompositionInternal(keyboardEvent, aKeyFlags); +} + +NS_IMETHODIMP +TextInputProcessor::CommitCompositionWith(const nsAString& aCommitString, + nsIDOMKeyEvent* aDOMKeyEvent, + uint32_t aKeyFlags, + uint8_t aOptionalArgc, + bool* aSucceeded) +{ + MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr"); + MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); + + WidgetKeyboardEvent* keyboardEvent; + nsresult rv = + PrepareKeyboardEventForComposition(aDOMKeyEvent, 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(nsIDOMKeyEvent* aDOMKeyEvent, + uint32_t aKeyFlags, + uint8_t aOptionalArgc) +{ + MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); + + WidgetKeyboardEvent* keyboardEvent; + nsresult rv = + PrepareKeyboardEventForComposition(aDOMKeyEvent, 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; + 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_(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)? false : true; + + return NS_OK; +} + +NS_IMETHODIMP +TextInputProcessor::Keydown(nsIDOMKeyEvent* 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->AsEvent()->WidgetEventPtr()->AsKeyboardEvent(); + if (NS_WARN_IF(!originalKeyEvent)) { + return NS_ERROR_INVALID_ARG; + } + return KeydownInternal(*originalKeyEvent, aKeyFlags, true, *aConsumedFlags); +} + +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(); + + 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(nsIDOMKeyEvent* 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->AsEvent()->WidgetEventPtr()->AsKeyboardEvent(); + if (NS_WARN_IF(!originalKeyEvent)) { + return NS_ERROR_INVALID_ARG; + } + return KeyupInternal(*originalKeyEvent, aKeyFlags, *aDoDefault); +} + +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(); + + 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()); + if (!mModifierKeyDataArray) { + *aActive = false; + return NS_OK; + } + Modifiers activeModifiers = mModifierKeyDataArray->GetActiveModifiers(); + Modifiers modifier = WidgetInputEvent::GetModifier(aModifierKeyName); + *aActive = ((activeModifiers & 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; +} + +/****************************************************************************** + * 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 |