123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781 |
- /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
- /* 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/Preferences.h"
- #include "mozilla/TextEvents.h"
- #include "mozilla/TextEventDispatcher.h"
- #include "nsIDocShell.h"
- #include "nsIFrame.h"
- #include "nsIPresShell.h"
- #include "nsIWidget.h"
- #include "nsPIDOMWindow.h"
- #include "nsView.h"
- namespace mozilla {
- namespace widget {
- /******************************************************************************
- * TextEventDispatcher
- *****************************************************************************/
- bool TextEventDispatcher::sDispatchKeyEventsDuringComposition = false;
- TextEventDispatcher::TextEventDispatcher(nsIWidget* aWidget)
- : mWidget(aWidget)
- , mDispatchingEvent(0)
- , mInputTransactionType(eNoInputTransaction)
- , mIsComposing(false)
- {
- MOZ_RELEASE_ASSERT(mWidget, "aWidget must not be nullptr");
- static bool sInitialized = false;
- if (!sInitialized) {
- Preferences::AddBoolVarCache(
- &sDispatchKeyEventsDuringComposition,
- "dom.keyboardevent.dispatch_during_composition",
- false);
- sInitialized = true;
- }
- }
- nsresult
- TextEventDispatcher::BeginInputTransaction(
- TextEventDispatcherListener* aListener)
- {
- return BeginInputTransactionInternal(aListener,
- eSameProcessSyncInputTransaction);
- }
- nsresult
- TextEventDispatcher::BeginTestInputTransaction(
- TextEventDispatcherListener* aListener,
- bool aIsAPZAware)
- {
- return BeginInputTransactionInternal(aListener,
- aIsAPZAware ? eAsyncTestInputTransaction :
- eSameProcessSyncTestInputTransaction);
- }
- nsresult
- TextEventDispatcher::BeginNativeInputTransaction()
- {
- if (NS_WARN_IF(!mWidget)) {
- return NS_ERROR_FAILURE;
- }
- RefPtr<TextEventDispatcherListener> listener =
- mWidget->GetNativeTextEventDispatcherListener();
- if (NS_WARN_IF(!listener)) {
- return NS_ERROR_FAILURE;
- }
- return BeginInputTransactionInternal(listener, eNativeInputTransaction);
- }
- nsresult
- TextEventDispatcher::BeginInputTransactionInternal(
- TextEventDispatcherListener* aListener,
- InputTransactionType aType)
- {
- if (NS_WARN_IF(!aListener)) {
- return NS_ERROR_INVALID_ARG;
- }
- nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
- if (listener) {
- if (listener == aListener && mInputTransactionType == aType) {
- return NS_OK;
- }
- // If this has composition or is dispatching an event, any other listener
- // can steal ownership. Especially, if the latter case is allowed,
- // nobody cannot begin input transaction with this if a modal dialog is
- // opened during dispatching an event.
- if (IsComposing() || IsDispatchingEvent()) {
- return NS_ERROR_ALREADY_INITIALIZED;
- }
- }
- mListener = do_GetWeakReference(aListener);
- mInputTransactionType = aType;
- if (listener && listener != aListener) {
- listener->OnRemovedFrom(this);
- }
- return NS_OK;
- }
- void
- TextEventDispatcher::EndInputTransaction(TextEventDispatcherListener* aListener)
- {
- if (NS_WARN_IF(IsComposing()) || NS_WARN_IF(IsDispatchingEvent())) {
- return;
- }
- mInputTransactionType = eNoInputTransaction;
- nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
- if (NS_WARN_IF(!listener)) {
- return;
- }
- if (NS_WARN_IF(listener != aListener)) {
- return;
- }
- mListener = nullptr;
- listener->OnRemovedFrom(this);
- }
- void
- TextEventDispatcher::OnDestroyWidget()
- {
- mWidget = nullptr;
- mPendingComposition.Clear();
- nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
- mListener = nullptr;
- mInputTransactionType = eNoInputTransaction;
- if (listener) {
- listener->OnRemovedFrom(this);
- }
- }
- nsresult
- TextEventDispatcher::GetState() const
- {
- nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
- if (!listener) {
- return NS_ERROR_NOT_INITIALIZED;
- }
- if (!mWidget || mWidget->Destroyed()) {
- return NS_ERROR_NOT_AVAILABLE;
- }
- return NS_OK;
- }
- void
- TextEventDispatcher::InitEvent(WidgetGUIEvent& aEvent) const
- {
- aEvent.mTime = PR_IntervalNow();
- aEvent.mRefPoint = LayoutDeviceIntPoint(0, 0);
- aEvent.mFlags.mIsSynthesizedForTests = IsForTests();
- if (aEvent.mClass != eCompositionEventClass) {
- return;
- }
- void* pseudoIMEContext = GetPseudoIMEContext();
- if (pseudoIMEContext) {
- aEvent.AsCompositionEvent()->mNativeIMEContext.
- InitWithRawNativeIMEContext(pseudoIMEContext);
- }
- #ifdef DEBUG
- else {
- MOZ_ASSERT(!XRE_IsContentProcess(),
- "Why did the content process start native event transaction?");
- MOZ_ASSERT(aEvent.AsCompositionEvent()->mNativeIMEContext.IsValid(),
- "Native IME context shouldn't be invalid");
- }
- #endif // #ifdef DEBUG
- }
- nsresult
- TextEventDispatcher::DispatchEvent(nsIWidget* aWidget,
- WidgetGUIEvent& aEvent,
- nsEventStatus& aStatus)
- {
- MOZ_ASSERT(!aEvent.AsInputEvent(), "Use DispatchInputEvent()");
- RefPtr<TextEventDispatcher> kungFuDeathGrip(this);
- nsCOMPtr<nsIWidget> widget(aWidget);
- mDispatchingEvent++;
- nsresult rv = widget->DispatchEvent(&aEvent, aStatus);
- mDispatchingEvent--;
- return rv;
- }
- nsresult
- TextEventDispatcher::DispatchInputEvent(nsIWidget* aWidget,
- WidgetInputEvent& aEvent,
- nsEventStatus& aStatus)
- {
- RefPtr<TextEventDispatcher> kungFuDeathGrip(this);
- nsCOMPtr<nsIWidget> widget(aWidget);
- mDispatchingEvent++;
- // If the event is dispatched via nsIWidget::DispatchInputEvent(), it
- // sends the event to the parent process first since APZ needs to handle it
- // first. However, some callers (e.g., keyboard apps on B2G and tests
- // expecting synchronous dispatch) don't want this to do that.
- nsresult rv = NS_OK;
- if (ShouldSendInputEventToAPZ()) {
- aStatus = widget->DispatchInputEvent(&aEvent);
- } else {
- rv = widget->DispatchEvent(&aEvent, aStatus);
- }
- mDispatchingEvent--;
- return rv;
- }
- nsresult
- TextEventDispatcher::StartComposition(nsEventStatus& aStatus,
- const WidgetEventTime* aEventTime)
- {
- aStatus = nsEventStatus_eIgnore;
- nsresult rv = GetState();
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return rv;
- }
- if (NS_WARN_IF(mIsComposing)) {
- return NS_ERROR_FAILURE;
- }
- mIsComposing = true;
- WidgetCompositionEvent compositionStartEvent(true, eCompositionStart,
- mWidget);
- InitEvent(compositionStartEvent);
- if (aEventTime) {
- compositionStartEvent.AssignEventTime(*aEventTime);
- }
- rv = DispatchEvent(mWidget, compositionStartEvent, aStatus);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return rv;
- }
- return NS_OK;
- }
- nsresult
- TextEventDispatcher::StartCompositionAutomaticallyIfNecessary(
- nsEventStatus& aStatus,
- const WidgetEventTime* aEventTime)
- {
- if (IsComposing()) {
- return NS_OK;
- }
- nsresult rv = StartComposition(aStatus, aEventTime);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return rv;
- }
- // If started composition has already been committed, we shouldn't dispatch
- // the compositionchange event.
- if (!IsComposing()) {
- aStatus = nsEventStatus_eConsumeNoDefault;
- return NS_OK;
- }
- // Note that the widget might be destroyed during a call of
- // StartComposition(). In such case, we shouldn't keep dispatching next
- // event.
- rv = GetState();
- if (NS_FAILED(rv)) {
- MOZ_ASSERT(rv != NS_ERROR_NOT_INITIALIZED,
- "aDispatcher must still be initialized in this case");
- aStatus = nsEventStatus_eConsumeNoDefault;
- return NS_OK; // Don't throw exception in this case
- }
- aStatus = nsEventStatus_eIgnore;
- return NS_OK;
- }
- nsresult
- TextEventDispatcher::CommitComposition(nsEventStatus& aStatus,
- const nsAString* aCommitString,
- const WidgetEventTime* aEventTime)
- {
- aStatus = nsEventStatus_eIgnore;
- nsresult rv = GetState();
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return rv;
- }
- // When there is no composition, caller shouldn't try to commit composition
- // with non-existing composition string nor commit composition with empty
- // string.
- if (NS_WARN_IF(!IsComposing() &&
- (!aCommitString || aCommitString->IsEmpty()))) {
- return NS_ERROR_FAILURE;
- }
- nsCOMPtr<nsIWidget> widget(mWidget);
- rv = StartCompositionAutomaticallyIfNecessary(aStatus, aEventTime);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return rv;
- }
- if (aStatus == nsEventStatus_eConsumeNoDefault) {
- return NS_OK;
- }
- // End current composition and make this free for other IMEs.
- mIsComposing = false;
- EventMessage message = aCommitString ? eCompositionCommit :
- eCompositionCommitAsIs;
- WidgetCompositionEvent compositionCommitEvent(true, message, widget);
- InitEvent(compositionCommitEvent);
- if (aEventTime) {
- compositionCommitEvent.AssignEventTime(*aEventTime);
- }
- if (message == eCompositionCommit) {
- compositionCommitEvent.mData = *aCommitString;
- // Don't send CRLF, replace it with LF here.
- compositionCommitEvent.mData.ReplaceSubstring(NS_LITERAL_STRING("\r\n"),
- NS_LITERAL_STRING("\n"));
- }
- rv = DispatchEvent(widget, compositionCommitEvent, aStatus);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return rv;
- }
- return NS_OK;
- }
- nsresult
- TextEventDispatcher::NotifyIME(const IMENotification& aIMENotification)
- {
- nsresult rv = NS_ERROR_NOT_IMPLEMENTED;
- // First, send the notification to current input transaction's listener.
- nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
- if (listener) {
- rv = listener->NotifyIME(this, aIMENotification);
- }
- if (mInputTransactionType == eNativeInputTransaction || !mWidget) {
- return rv;
- }
- // If current input transaction isn't for native event handler, we should
- // send the notification to the native text event dispatcher listener
- // since native event handler may need to do something from
- // TextEventDispatcherListener::NotifyIME() even before there is no
- // input transaction yet. For example, native IME handler may need to
- // create new context at receiving NOTIFY_IME_OF_FOCUS. In this case,
- // mListener may not be initialized since input transaction should be
- // initialized immediately before dispatching every WidgetKeyboardEvent
- // and WidgetCompositionEvent (dispatching events always occurs after
- // focus move).
- nsCOMPtr<TextEventDispatcherListener> nativeListener =
- mWidget->GetNativeTextEventDispatcherListener();
- if (!nativeListener) {
- return rv;
- }
- switch (aIMENotification.mMessage) {
- case REQUEST_TO_COMMIT_COMPOSITION:
- case REQUEST_TO_CANCEL_COMPOSITION:
- // It's not necessary to notify native IME of requests.
- return rv;
- default: {
- // Even if current input transaction's listener returns NS_OK or
- // something, we need to notify native IME of notifications because
- // when user typing after TIP does something, the changed information
- // is necessary for them.
- nsresult rv2 =
- nativeListener->NotifyIME(this, aIMENotification);
- // But return the result from current listener except when the
- // notification isn't handled.
- return rv == NS_ERROR_NOT_IMPLEMENTED ? rv2 : rv;
- }
- }
- }
- bool
- TextEventDispatcher::DispatchKeyboardEvent(
- EventMessage aMessage,
- const WidgetKeyboardEvent& aKeyboardEvent,
- nsEventStatus& aStatus,
- void* aData)
- {
- return DispatchKeyboardEventInternal(aMessage, aKeyboardEvent, aStatus,
- aData);
- }
- bool
- TextEventDispatcher::DispatchKeyboardEventInternal(
- EventMessage aMessage,
- const WidgetKeyboardEvent& aKeyboardEvent,
- nsEventStatus& aStatus,
- void* aData,
- uint32_t aIndexOfKeypress,
- bool aNeedsCallback)
- {
- // Note that this method is also used for dispatching key events on a plugin
- // because key events on a plugin should be dispatched same as normal key
- // events. Then, only some handlers which need to intercept key events
- // before the focused plugin (e.g., reserved shortcut key handlers) can
- // consume the events.
- MOZ_ASSERT(WidgetKeyboardEvent::IsKeyDownOrKeyDownOnPlugin(aMessage) ||
- WidgetKeyboardEvent::IsKeyUpOrKeyUpOnPlugin(aMessage) ||
- aMessage == eKeyPress, "Invalid aMessage value");
- nsresult rv = GetState();
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return false;
- }
- // If the key shouldn't cause keypress events, don't this patch them.
- if (aMessage == eKeyPress && !aKeyboardEvent.ShouldCauseKeypressEvents()) {
- return false;
- }
- // Basically, key events shouldn't be dispatched during composition.
- // Note that plugin process has different IME context. Therefore, we don't
- // need to check our composition state when the key event is fired on a
- // plugin.
- if (IsComposing() && !WidgetKeyboardEvent::IsKeyEventOnPlugin(aMessage)) {
- // However, if we need to behave like other browsers, we need the keydown
- // and keyup events. Note that this behavior is also allowed by D3E spec.
- // FYI: keypress events must not be fired during composition.
- if (!sDispatchKeyEventsDuringComposition || aMessage == eKeyPress) {
- return false;
- }
- // XXX If there was mOnlyContentDispatch for this case, it might be useful
- // because our chrome doesn't assume that key events are fired during
- // composition.
- }
- WidgetKeyboardEvent keyEvent(true, aMessage, mWidget);
- InitEvent(keyEvent);
- keyEvent.AssignKeyEventData(aKeyboardEvent, false);
- if (aStatus == nsEventStatus_eConsumeNoDefault) {
- // If the key event should be dispatched as consumed event, marking it here.
- // This is useful to prevent double action. E.g., when the key was already
- // handled by system, our chrome shouldn't handle it.
- keyEvent.PreventDefaultBeforeDispatch();
- }
- // Corrects each member for the specific key event type.
- if (keyEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING) {
- MOZ_ASSERT(!aIndexOfKeypress,
- "aIndexOfKeypress must be 0 for non-printable key");
- // If the keyboard event isn't caused by printable key, its charCode should
- // be 0.
- keyEvent.SetCharCode(0);
- } else {
- if (WidgetKeyboardEvent::IsKeyDownOrKeyDownOnPlugin(aMessage) ||
- WidgetKeyboardEvent::IsKeyUpOrKeyUpOnPlugin(aMessage)) {
- MOZ_RELEASE_ASSERT(!aIndexOfKeypress,
- "aIndexOfKeypress must be 0 for either eKeyDown or eKeyUp");
- } else {
- MOZ_RELEASE_ASSERT(
- !aIndexOfKeypress || aIndexOfKeypress < keyEvent.mKeyValue.Length(),
- "aIndexOfKeypress must be 0 - mKeyValue.Length() - 1");
- }
- wchar_t ch =
- keyEvent.mKeyValue.IsEmpty() ? 0 : keyEvent.mKeyValue[aIndexOfKeypress];
- keyEvent.SetCharCode(static_cast<uint32_t>(ch));
- if (aMessage == eKeyPress) {
- // keyCode of eKeyPress events of printable keys should be always 0.
- keyEvent.mKeyCode = 0;
- // eKeyPress events are dispatched for every character.
- // So, each key value of eKeyPress events should be a character.
- if (ch) {
- keyEvent.mKeyValue.Assign(ch);
- } else {
- keyEvent.mKeyValue.Truncate();
- }
- }
- }
- if (WidgetKeyboardEvent::IsKeyUpOrKeyUpOnPlugin(aMessage)) {
- // mIsRepeat of keyup event must be false.
- keyEvent.mIsRepeat = false;
- }
- // mIsComposing should be initialized later.
- keyEvent.mIsComposing = false;
- if (mInputTransactionType == eNativeInputTransaction) {
- // Copy mNativeKeyEvent here because for safety for other users of
- // AssignKeyEventData(), it doesn't copy this.
- keyEvent.mNativeKeyEvent = aKeyboardEvent.mNativeKeyEvent;
- } else {
- // If it's not a keyboard event for native key event, we should ensure that
- // mNativeKeyEvent and mPluginEvent are null/empty.
- keyEvent.mNativeKeyEvent = nullptr;
- keyEvent.mPluginEvent.Clear();
- }
- // TODO: Manage mUniqueId here.
- // Request the alternative char codes for the key event.
- // eKeyDown also needs alternative char codes because nsXBLWindowKeyHandler
- // needs to check if a following keypress event is reserved by chrome for
- // stopping propagation of its preceding keydown event.
- keyEvent.mAlternativeCharCodes.Clear();
- if ((WidgetKeyboardEvent::IsKeyDownOrKeyDownOnPlugin(aMessage) ||
- aMessage == eKeyPress) &&
- (aNeedsCallback || keyEvent.IsControl() || keyEvent.IsAlt() ||
- keyEvent.IsMeta() || keyEvent.IsOS())) {
- nsCOMPtr<TextEventDispatcherListener> listener =
- do_QueryReferent(mListener);
- if (listener) {
- DebugOnly<WidgetKeyboardEvent> original(keyEvent);
- listener->WillDispatchKeyboardEvent(this, keyEvent, aIndexOfKeypress,
- aData);
- MOZ_ASSERT(keyEvent.mMessage ==
- static_cast<WidgetKeyboardEvent&>(original).mMessage);
- MOZ_ASSERT(keyEvent.mKeyCode ==
- static_cast<WidgetKeyboardEvent&>(original).mKeyCode);
- MOZ_ASSERT(keyEvent.mLocation ==
- static_cast<WidgetKeyboardEvent&>(original).mLocation);
- MOZ_ASSERT(keyEvent.mIsRepeat ==
- static_cast<WidgetKeyboardEvent&>(original).mIsRepeat);
- MOZ_ASSERT(keyEvent.mIsComposing ==
- static_cast<WidgetKeyboardEvent&>(original).mIsComposing);
- MOZ_ASSERT(keyEvent.mKeyNameIndex ==
- static_cast<WidgetKeyboardEvent&>(original).mKeyNameIndex);
- MOZ_ASSERT(keyEvent.mCodeNameIndex ==
- static_cast<WidgetKeyboardEvent&>(original).mCodeNameIndex);
- MOZ_ASSERT(keyEvent.mKeyValue ==
- static_cast<WidgetKeyboardEvent&>(original).mKeyValue);
- MOZ_ASSERT(keyEvent.mCodeValue ==
- static_cast<WidgetKeyboardEvent&>(original).mCodeValue);
- }
- }
- DispatchInputEvent(mWidget, keyEvent, aStatus);
- return true;
- }
- bool
- TextEventDispatcher::MaybeDispatchKeypressEvents(
- const WidgetKeyboardEvent& aKeyboardEvent,
- nsEventStatus& aStatus,
- void* aData,
- bool aNeedsCallback)
- {
- // If the key event was consumed, keypress event shouldn't be fired.
- if (aStatus == nsEventStatus_eConsumeNoDefault) {
- return false;
- }
- // If the key shouldn't cause keypress events, don't fire them.
- if (!aKeyboardEvent.ShouldCauseKeypressEvents()) {
- return false;
- }
- // If the key isn't a printable key or just inputting one character or
- // no character, we should dispatch only one keypress. Otherwise, i.e.,
- // if the key is a printable key and inputs multiple characters, keypress
- // event should be dispatched the count of inputting characters times.
- size_t keypressCount =
- aKeyboardEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING ?
- 1 : std::max(static_cast<nsAString::size_type>(1),
- aKeyboardEvent.mKeyValue.Length());
- bool isDispatched = false;
- bool consumed = false;
- for (size_t i = 0; i < keypressCount; i++) {
- aStatus = nsEventStatus_eIgnore;
- if (!DispatchKeyboardEventInternal(eKeyPress, aKeyboardEvent,
- aStatus, aData, i, aNeedsCallback)) {
- // The widget must have been gone.
- break;
- }
- isDispatched = true;
- if (!consumed) {
- consumed = (aStatus == nsEventStatus_eConsumeNoDefault);
- }
- }
- // If one of the keypress event was consumed, return ConsumeNoDefault.
- if (consumed) {
- aStatus = nsEventStatus_eConsumeNoDefault;
- }
- return isDispatched;
- }
- /******************************************************************************
- * TextEventDispatcher::PendingComposition
- *****************************************************************************/
- TextEventDispatcher::PendingComposition::PendingComposition()
- {
- Clear();
- }
- void
- TextEventDispatcher::PendingComposition::Clear()
- {
- mString.Truncate();
- mClauses = nullptr;
- mCaret.mRangeType = TextRangeType::eUninitialized;
- }
- void
- TextEventDispatcher::PendingComposition::EnsureClauseArray()
- {
- if (mClauses) {
- return;
- }
- mClauses = new TextRangeArray();
- }
- nsresult
- TextEventDispatcher::PendingComposition::SetString(const nsAString& aString)
- {
- mString = aString;
- return NS_OK;
- }
- nsresult
- TextEventDispatcher::PendingComposition::AppendClause(
- uint32_t aLength,
- TextRangeType aTextRangeType)
- {
- if (NS_WARN_IF(!aLength)) {
- return NS_ERROR_INVALID_ARG;
- }
- switch (aTextRangeType) {
- case TextRangeType::eRawClause:
- case TextRangeType::eSelectedRawClause:
- case TextRangeType::eConvertedClause:
- case TextRangeType::eSelectedClause: {
- EnsureClauseArray();
- TextRange textRange;
- textRange.mStartOffset =
- mClauses->IsEmpty() ? 0 : mClauses->LastElement().mEndOffset;
- textRange.mEndOffset = textRange.mStartOffset + aLength;
- textRange.mRangeType = aTextRangeType;
- mClauses->AppendElement(textRange);
- return NS_OK;
- }
- default:
- return NS_ERROR_INVALID_ARG;
- }
- }
- nsresult
- TextEventDispatcher::PendingComposition::SetCaret(uint32_t aOffset,
- uint32_t aLength)
- {
- mCaret.mStartOffset = aOffset;
- mCaret.mEndOffset = mCaret.mStartOffset + aLength;
- mCaret.mRangeType = TextRangeType::eCaret;
- return NS_OK;
- }
- nsresult
- TextEventDispatcher::PendingComposition::Set(const nsAString& aString,
- const TextRangeArray* aRanges)
- {
- Clear();
- nsAutoString str(aString);
- // Don't expose CRLF to web contents, instead, use LF.
- str.ReplaceSubstring(NS_LITERAL_STRING("\r\n"), NS_LITERAL_STRING("\n"));
- nsresult rv = SetString(str);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return rv;
- }
- if (!aRanges || aRanges->IsEmpty()) {
- // Create dummy range if aString isn't empty.
- if (!aString.IsEmpty()) {
- rv = AppendClause(str.Length(), TextRangeType::eRawClause);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return rv;
- }
- }
- return NS_OK;
- }
- // Adjust offsets in the ranges for XP linefeed character (only \n).
- // XXX Following code is the safest approach. However, it wastes performance.
- // For ensuring the clauses do not overlap each other, we should redesign
- // TextRange later.
- for (uint32_t i = 0; i < aRanges->Length(); ++i) {
- TextRange range = aRanges->ElementAt(i);
- TextRange nativeRange = range;
- if (nativeRange.mStartOffset > 0) {
- nsAutoString preText(Substring(aString, 0, nativeRange.mStartOffset));
- preText.ReplaceSubstring(NS_LITERAL_STRING("\r\n"),
- NS_LITERAL_STRING("\n"));
- range.mStartOffset = preText.Length();
- }
- if (nativeRange.Length() == 0) {
- range.mEndOffset = range.mStartOffset;
- } else {
- nsAutoString clause(
- Substring(aString, nativeRange.mStartOffset, nativeRange.Length()));
- clause.ReplaceSubstring(NS_LITERAL_STRING("\r\n"),
- NS_LITERAL_STRING("\n"));
- range.mEndOffset = range.mStartOffset + clause.Length();
- }
- if (range.mRangeType == TextRangeType::eCaret) {
- mCaret = range;
- } else {
- EnsureClauseArray();
- mClauses->AppendElement(range);
- }
- }
- return NS_OK;
- }
- nsresult
- TextEventDispatcher::PendingComposition::Flush(
- TextEventDispatcher* aDispatcher,
- nsEventStatus& aStatus,
- const WidgetEventTime* aEventTime)
- {
- aStatus = nsEventStatus_eIgnore;
- nsresult rv = aDispatcher->GetState();
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return rv;
- }
- if (mClauses && !mClauses->IsEmpty() &&
- mClauses->LastElement().mEndOffset != mString.Length()) {
- NS_WARNING("Sum of length of the all clauses must be same as the string "
- "length");
- Clear();
- return NS_ERROR_ILLEGAL_VALUE;
- }
- if (mCaret.mRangeType == TextRangeType::eCaret) {
- if (mCaret.mEndOffset > mString.Length()) {
- NS_WARNING("Caret position is out of the composition string");
- Clear();
- return NS_ERROR_ILLEGAL_VALUE;
- }
- EnsureClauseArray();
- mClauses->AppendElement(mCaret);
- }
- RefPtr<TextEventDispatcher> kungFuDeathGrip(aDispatcher);
- nsCOMPtr<nsIWidget> widget(aDispatcher->mWidget);
- WidgetCompositionEvent compChangeEvent(true, eCompositionChange, widget);
- aDispatcher->InitEvent(compChangeEvent);
- if (aEventTime) {
- compChangeEvent.AssignEventTime(*aEventTime);
- }
- compChangeEvent.mData = mString;
- if (mClauses) {
- MOZ_ASSERT(!mClauses->IsEmpty(),
- "mClauses must be non-empty array when it's not nullptr");
- compChangeEvent.mRanges = mClauses;
- }
- // While this method dispatches a composition event, some other event handler
- // cause more clauses to be added. So, we should clear pending composition
- // before dispatching the event.
- Clear();
- rv = aDispatcher->StartCompositionAutomaticallyIfNecessary(aStatus,
- aEventTime);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return rv;
- }
- if (aStatus == nsEventStatus_eConsumeNoDefault) {
- return NS_OK;
- }
- rv = aDispatcher->DispatchEvent(widget, compChangeEvent, aStatus);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return rv;
- }
- return NS_OK;
- }
- } // namespace widget
- } // namespace mozilla
|