michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 sw=2 et tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "ContentEventHandler.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIEditor.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsPresContext.h" michael@0: #include "mozilla/EventDispatcher.h" michael@0: #include "mozilla/IMEStateManager.h" michael@0: #include "mozilla/MiscEvents.h" michael@0: #include "mozilla/TextComposition.h" michael@0: #include "mozilla/TextEvents.h" michael@0: michael@0: using namespace mozilla::widget; michael@0: michael@0: namespace mozilla { michael@0: michael@0: /****************************************************************************** michael@0: * TextComposition michael@0: ******************************************************************************/ michael@0: michael@0: TextComposition::TextComposition(nsPresContext* aPresContext, michael@0: nsINode* aNode, michael@0: WidgetGUIEvent* aEvent) michael@0: : mPresContext(aPresContext) michael@0: , mNode(aNode) michael@0: , mNativeContext(aEvent->widget->GetInputContext().mNativeIMEContext) michael@0: , mCompositionStartOffset(0) michael@0: , mCompositionTargetOffset(0) michael@0: , mIsSynthesizedForTests(aEvent->mFlags.mIsSynthesizedForTests) michael@0: , mIsComposing(false) michael@0: , mIsEditorHandlingEvent(false) michael@0: { michael@0: } michael@0: michael@0: void michael@0: TextComposition::Destroy() michael@0: { michael@0: mPresContext = nullptr; michael@0: mNode = nullptr; michael@0: // TODO: If the editor is still alive and this is held by it, we should tell michael@0: // this being destroyed for cleaning up the stuff. michael@0: } michael@0: michael@0: bool michael@0: TextComposition::MatchesNativeContext(nsIWidget* aWidget) const michael@0: { michael@0: return mNativeContext == aWidget->GetInputContext().mNativeIMEContext; michael@0: } michael@0: michael@0: void michael@0: TextComposition::DispatchEvent(WidgetGUIEvent* aEvent, michael@0: nsEventStatus* aStatus, michael@0: EventDispatchingCallback* aCallBack) michael@0: { michael@0: if (aEvent->message == NS_COMPOSITION_UPDATE) { michael@0: mLastData = aEvent->AsCompositionEvent()->data; michael@0: } michael@0: michael@0: EventDispatcher::Dispatch(mNode, mPresContext, michael@0: aEvent, nullptr, aStatus, aCallBack); michael@0: michael@0: if (!mPresContext) { michael@0: return; michael@0: } michael@0: michael@0: // Emulate editor behavior of text event handler if no editor handles michael@0: // composition/text events. michael@0: if (aEvent->message == NS_TEXT_TEXT && !HasEditor()) { michael@0: EditorWillHandleTextEvent(aEvent->AsTextEvent()); michael@0: EditorDidHandleTextEvent(); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: else if (aEvent->message == NS_COMPOSITION_END) { michael@0: MOZ_ASSERT(!mIsComposing, "Why is the editor still composing?"); michael@0: MOZ_ASSERT(!HasEditor(), "Why does the editor still keep to hold this?"); michael@0: } michael@0: #endif // #ifdef DEBUG michael@0: michael@0: // Notify composition update to widget if possible michael@0: NotityUpdateComposition(aEvent); michael@0: } michael@0: michael@0: void michael@0: TextComposition::NotityUpdateComposition(WidgetGUIEvent* aEvent) michael@0: { michael@0: nsEventStatus status; michael@0: michael@0: // When compositon start, notify the rect of first offset character. michael@0: // When not compositon start, notify the rect of selected composition michael@0: // string if text event. michael@0: if (aEvent->message == NS_COMPOSITION_START) { michael@0: nsCOMPtr widget = mPresContext->GetRootWidget(); michael@0: // Update composition start offset michael@0: WidgetQueryContentEvent selectedTextEvent(true, michael@0: NS_QUERY_SELECTED_TEXT, michael@0: widget); michael@0: widget->DispatchEvent(&selectedTextEvent, status); michael@0: if (selectedTextEvent.mSucceeded) { michael@0: mCompositionStartOffset = selectedTextEvent.mReply.mOffset; michael@0: } else { michael@0: // Unknown offset michael@0: NS_WARNING("Cannot get start offset of IME composition"); michael@0: mCompositionStartOffset = 0; michael@0: } michael@0: mCompositionTargetOffset = mCompositionStartOffset; michael@0: } else if (aEvent->eventStructType != NS_TEXT_EVENT) { michael@0: return; michael@0: } else { michael@0: mCompositionTargetOffset = michael@0: mCompositionStartOffset + aEvent->AsTextEvent()->TargetClauseOffset(); michael@0: } michael@0: michael@0: NotifyIME(NOTIFY_IME_OF_COMPOSITION_UPDATE); michael@0: } michael@0: michael@0: void michael@0: TextComposition::DispatchCompositionEventRunnable(uint32_t aEventMessage, michael@0: const nsAString& aData) michael@0: { michael@0: nsContentUtils::AddScriptRunner( michael@0: new CompositionEventDispatcher(mPresContext, mNode, michael@0: aEventMessage, aData)); michael@0: } michael@0: michael@0: void michael@0: TextComposition::SynthesizeCommit(bool aDiscard) michael@0: { michael@0: nsRefPtr kungFuDeathGrip(this); michael@0: nsAutoString data(aDiscard ? EmptyString() : mLastData); michael@0: if (mLastData != data) { michael@0: DispatchCompositionEventRunnable(NS_COMPOSITION_UPDATE, data); michael@0: DispatchCompositionEventRunnable(NS_TEXT_TEXT, data); michael@0: } michael@0: DispatchCompositionEventRunnable(NS_COMPOSITION_END, data); michael@0: } michael@0: michael@0: nsresult michael@0: TextComposition::NotifyIME(IMEMessage aMessage) michael@0: { michael@0: NS_ENSURE_TRUE(mPresContext, NS_ERROR_NOT_AVAILABLE); michael@0: return IMEStateManager::NotifyIME(aMessage, mPresContext); michael@0: } michael@0: michael@0: void michael@0: TextComposition::EditorWillHandleTextEvent(const WidgetTextEvent* aTextEvent) michael@0: { michael@0: mIsComposing = aTextEvent->IsComposing(); michael@0: mRanges = aTextEvent->mRanges; michael@0: mIsEditorHandlingEvent = true; michael@0: michael@0: MOZ_ASSERT(mLastData == aTextEvent->theText, michael@0: "The text of a text event must be same as previous data attribute value " michael@0: "of the latest compositionupdate event"); michael@0: } michael@0: michael@0: void michael@0: TextComposition::EditorDidHandleTextEvent() michael@0: { michael@0: mString = mLastData; michael@0: mIsEditorHandlingEvent = false; michael@0: } michael@0: michael@0: void michael@0: TextComposition::StartHandlingComposition(nsIEditor* aEditor) michael@0: { michael@0: MOZ_ASSERT(!HasEditor(), "There is a handling editor already"); michael@0: mEditorWeak = do_GetWeakReference(aEditor); michael@0: } michael@0: michael@0: void michael@0: TextComposition::EndHandlingComposition(nsIEditor* aEditor) michael@0: { michael@0: #ifdef DEBUG michael@0: nsCOMPtr editor = GetEditor(); michael@0: MOZ_ASSERT(editor == aEditor, "Another editor handled the composition?"); michael@0: #endif // #ifdef DEBUG michael@0: mEditorWeak = nullptr; michael@0: } michael@0: michael@0: already_AddRefed michael@0: TextComposition::GetEditor() const michael@0: { michael@0: nsCOMPtr editor = do_QueryReferent(mEditorWeak); michael@0: return editor.forget(); michael@0: } michael@0: michael@0: bool michael@0: TextComposition::HasEditor() const michael@0: { michael@0: nsCOMPtr editor = GetEditor(); michael@0: return !!editor; michael@0: } michael@0: michael@0: /****************************************************************************** michael@0: * TextComposition::CompositionEventDispatcher michael@0: ******************************************************************************/ michael@0: michael@0: TextComposition::CompositionEventDispatcher::CompositionEventDispatcher( michael@0: nsPresContext* aPresContext, michael@0: nsINode* aEventTarget, michael@0: uint32_t aEventMessage, michael@0: const nsAString& aData) : michael@0: mPresContext(aPresContext), mEventTarget(aEventTarget), michael@0: mEventMessage(aEventMessage), mData(aData) michael@0: { michael@0: mWidget = mPresContext->GetRootWidget(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TextComposition::CompositionEventDispatcher::Run() michael@0: { michael@0: if (!mPresContext->GetPresShell() || michael@0: mPresContext->GetPresShell()->IsDestroying()) { michael@0: return NS_OK; // cannot dispatch any events anymore michael@0: } michael@0: michael@0: nsEventStatus status = nsEventStatus_eIgnore; michael@0: switch (mEventMessage) { michael@0: case NS_COMPOSITION_START: { michael@0: WidgetCompositionEvent compStart(true, NS_COMPOSITION_START, mWidget); michael@0: WidgetQueryContentEvent selectedText(true, NS_QUERY_SELECTED_TEXT, michael@0: mWidget); michael@0: ContentEventHandler handler(mPresContext); michael@0: handler.OnQuerySelectedText(&selectedText); michael@0: NS_ASSERTION(selectedText.mSucceeded, "Failed to get selected text"); michael@0: compStart.data = selectedText.mReply.mString; michael@0: IMEStateManager::DispatchCompositionEvent(mEventTarget, mPresContext, michael@0: &compStart, &status, nullptr); michael@0: break; michael@0: } michael@0: case NS_COMPOSITION_UPDATE: michael@0: case NS_COMPOSITION_END: { michael@0: WidgetCompositionEvent compEvent(true, mEventMessage, mWidget); michael@0: compEvent.data = mData; michael@0: IMEStateManager::DispatchCompositionEvent(mEventTarget, mPresContext, michael@0: &compEvent, &status, nullptr); michael@0: break; michael@0: } michael@0: case NS_TEXT_TEXT: { michael@0: WidgetTextEvent textEvent(true, NS_TEXT_TEXT, mWidget); michael@0: textEvent.theText = mData; michael@0: IMEStateManager::DispatchCompositionEvent(mEventTarget, mPresContext, michael@0: &textEvent, &status, nullptr); michael@0: break; michael@0: } michael@0: default: michael@0: MOZ_CRASH("Unsupported event"); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: /****************************************************************************** michael@0: * TextCompositionArray michael@0: ******************************************************************************/ michael@0: michael@0: TextCompositionArray::index_type michael@0: TextCompositionArray::IndexOf(nsIWidget* aWidget) michael@0: { michael@0: for (index_type i = Length(); i > 0; --i) { michael@0: if (ElementAt(i - 1)->MatchesNativeContext(aWidget)) { michael@0: return i - 1; michael@0: } michael@0: } michael@0: return NoIndex; michael@0: } michael@0: michael@0: TextCompositionArray::index_type michael@0: TextCompositionArray::IndexOf(nsPresContext* aPresContext) michael@0: { michael@0: for (index_type i = Length(); i > 0; --i) { michael@0: if (ElementAt(i - 1)->GetPresContext() == aPresContext) { michael@0: return i - 1; michael@0: } michael@0: } michael@0: return NoIndex; michael@0: } michael@0: michael@0: TextCompositionArray::index_type michael@0: TextCompositionArray::IndexOf(nsPresContext* aPresContext, michael@0: nsINode* aNode) michael@0: { michael@0: index_type index = IndexOf(aPresContext); michael@0: if (index == NoIndex) { michael@0: return NoIndex; michael@0: } michael@0: nsINode* node = ElementAt(index)->GetEventTargetNode(); michael@0: return node == aNode ? index : NoIndex; michael@0: } michael@0: michael@0: TextComposition* michael@0: TextCompositionArray::GetCompositionFor(nsIWidget* aWidget) michael@0: { michael@0: index_type i = IndexOf(aWidget); michael@0: return i != NoIndex ? ElementAt(i) : nullptr; michael@0: } michael@0: michael@0: TextComposition* michael@0: TextCompositionArray::GetCompositionFor(nsPresContext* aPresContext, michael@0: nsINode* aNode) michael@0: { michael@0: index_type i = IndexOf(aPresContext, aNode); michael@0: return i != NoIndex ? ElementAt(i) : nullptr; michael@0: } michael@0: michael@0: TextComposition* michael@0: TextCompositionArray::GetCompositionInContent(nsPresContext* aPresContext, michael@0: nsIContent* aContent) michael@0: { michael@0: // There should be only one composition per content object. michael@0: for (index_type i = Length(); i > 0; --i) { michael@0: nsINode* node = ElementAt(i - 1)->GetEventTargetNode(); michael@0: if (node && nsContentUtils::ContentIsDescendantOf(node, aContent)) { michael@0: return ElementAt(i - 1); michael@0: } michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: } // namespace mozilla