summaryrefslogtreecommitdiffstats
path: root/dom/base/TextInputProcessor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/base/TextInputProcessor.cpp')
-rw-r--r--dom/base/TextInputProcessor.cpp1793
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