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 "IMEContentObserver.h" michael@0: #include "mozilla/AsyncEventDispatcher.h" michael@0: #include "mozilla/EventStateManager.h" michael@0: #include "mozilla/IMEStateManager.h" michael@0: #include "mozilla/TextComposition.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsIAtom.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsIDOMRange.h" michael@0: #include "nsIFrame.h" michael@0: #include "nsINode.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsISelectionController.h" michael@0: #include "nsISelectionPrivate.h" michael@0: #include "nsISupports.h" michael@0: #include "nsIWidget.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsWeakReference.h" michael@0: michael@0: namespace mozilla { michael@0: michael@0: using namespace widget; michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION(IMEContentObserver, michael@0: mWidget, mSelection, michael@0: mRootContent, mEditableNode, mDocShell) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IMEContentObserver) michael@0: NS_INTERFACE_MAP_ENTRY(nsISelectionListener) michael@0: NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) michael@0: NS_INTERFACE_MAP_ENTRY(nsIReflowObserver) michael@0: NS_INTERFACE_MAP_ENTRY(nsIScrollObserver) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISelectionListener) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(IMEContentObserver) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(IMEContentObserver) michael@0: michael@0: IMEContentObserver::IMEContentObserver() michael@0: : mESM(nullptr) michael@0: { michael@0: } michael@0: michael@0: void michael@0: IMEContentObserver::Init(nsIWidget* aWidget, michael@0: nsPresContext* aPresContext, michael@0: nsIContent* aContent) michael@0: { michael@0: mESM = aPresContext->EventStateManager(); michael@0: mESM->OnStartToObserveContent(this); michael@0: michael@0: mWidget = aWidget; michael@0: mEditableNode = IMEStateManager::GetRootEditableNode(aPresContext, aContent); michael@0: if (!mEditableNode) { michael@0: return; michael@0: } michael@0: michael@0: nsIPresShell* presShell = aPresContext->PresShell(); michael@0: michael@0: // get selection and root content michael@0: nsCOMPtr selCon; michael@0: if (mEditableNode->IsNodeOfType(nsINode::eCONTENT)) { michael@0: nsIFrame* frame = michael@0: static_cast(mEditableNode.get())->GetPrimaryFrame(); michael@0: NS_ENSURE_TRUE_VOID(frame); michael@0: michael@0: frame->GetSelectionController(aPresContext, michael@0: getter_AddRefs(selCon)); michael@0: } else { michael@0: // mEditableNode is a document michael@0: selCon = do_QueryInterface(presShell); michael@0: } michael@0: NS_ENSURE_TRUE_VOID(selCon); michael@0: michael@0: selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, michael@0: getter_AddRefs(mSelection)); michael@0: NS_ENSURE_TRUE_VOID(mSelection); michael@0: michael@0: nsCOMPtr selDomRange; michael@0: if (NS_SUCCEEDED(mSelection->GetRangeAt(0, getter_AddRefs(selDomRange)))) { michael@0: nsRange* selRange = static_cast(selDomRange.get()); michael@0: NS_ENSURE_TRUE_VOID(selRange && selRange->GetStartParent()); michael@0: michael@0: mRootContent = selRange->GetStartParent()-> michael@0: GetSelectionRootContent(presShell); michael@0: } else { michael@0: mRootContent = mEditableNode->GetSelectionRootContent(presShell); michael@0: } michael@0: if (!mRootContent && mEditableNode->IsNodeOfType(nsINode::eDOCUMENT)) { michael@0: // The document node is editable, but there are no contents, this document michael@0: // is not editable. michael@0: return; michael@0: } michael@0: NS_ENSURE_TRUE_VOID(mRootContent); michael@0: michael@0: if (IMEStateManager::IsTestingIME()) { michael@0: nsIDocument* doc = aPresContext->Document(); michael@0: (new AsyncEventDispatcher(doc, NS_LITERAL_STRING("MozIMEFocusIn"), michael@0: false, false))->RunDOMEventWhenSafe(); michael@0: } michael@0: michael@0: aWidget->NotifyIME(IMENotification(NOTIFY_IME_OF_FOCUS)); michael@0: michael@0: // NOTIFY_IME_OF_FOCUS might cause recreating IMEContentObserver michael@0: // instance via IMEStateManager::UpdateIMEState(). So, this michael@0: // instance might already have been destroyed, check it. michael@0: if (!mRootContent) { michael@0: return; michael@0: } michael@0: michael@0: mDocShell = aPresContext->GetDocShell(); michael@0: michael@0: ObserveEditableNode(); michael@0: } michael@0: michael@0: void michael@0: IMEContentObserver::ObserveEditableNode() michael@0: { michael@0: MOZ_ASSERT(mSelection); michael@0: MOZ_ASSERT(mRootContent); michael@0: michael@0: mUpdatePreference = mWidget->GetIMEUpdatePreference(); michael@0: if (mUpdatePreference.WantSelectionChange()) { michael@0: // add selection change listener michael@0: nsCOMPtr selPrivate(do_QueryInterface(mSelection)); michael@0: NS_ENSURE_TRUE_VOID(selPrivate); michael@0: nsresult rv = selPrivate->AddSelectionListener(this); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: } michael@0: michael@0: if (mUpdatePreference.WantTextChange()) { michael@0: // add text change observer michael@0: mRootContent->AddMutationObserver(this); michael@0: } michael@0: michael@0: if (mUpdatePreference.WantPositionChanged() && mDocShell) { michael@0: // Add scroll position listener and reflow observer to detect position and michael@0: // size changes michael@0: mDocShell->AddWeakScrollObserver(this); michael@0: mDocShell->AddWeakReflowObserver(this); michael@0: } michael@0: } michael@0: michael@0: void michael@0: IMEContentObserver::Destroy() michael@0: { michael@0: // If CreateTextStateManager failed, mRootContent will be null, michael@0: // and we should not call NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR)) michael@0: if (mRootContent) { michael@0: if (IMEStateManager::IsTestingIME() && mEditableNode) { michael@0: nsIDocument* doc = mEditableNode->OwnerDoc(); michael@0: (new AsyncEventDispatcher(doc, NS_LITERAL_STRING("MozIMEFocusOut"), michael@0: false, false))->RunDOMEventWhenSafe(); michael@0: } michael@0: mWidget->NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR)); michael@0: } michael@0: // Even if there are some pending notification, it'll never notify the widget. michael@0: mWidget = nullptr; michael@0: if (mUpdatePreference.WantSelectionChange() && mSelection) { michael@0: nsCOMPtr selPrivate(do_QueryInterface(mSelection)); michael@0: if (selPrivate) { michael@0: selPrivate->RemoveSelectionListener(this); michael@0: } michael@0: } michael@0: mSelection = nullptr; michael@0: if (mUpdatePreference.WantTextChange() && mRootContent) { michael@0: mRootContent->RemoveMutationObserver(this); michael@0: } michael@0: if (mUpdatePreference.WantPositionChanged() && mDocShell) { michael@0: mDocShell->RemoveWeakScrollObserver(this); michael@0: mDocShell->RemoveWeakReflowObserver(this); michael@0: } michael@0: mRootContent = nullptr; michael@0: mEditableNode = nullptr; michael@0: mDocShell = nullptr; michael@0: mUpdatePreference.mWantUpdates = nsIMEUpdatePreference::NOTIFY_NOTHING; michael@0: michael@0: if (mESM) { michael@0: mESM->OnStopObservingContent(this); michael@0: mESM = nullptr; michael@0: } michael@0: } michael@0: michael@0: void michael@0: IMEContentObserver::DisconnectFromEventStateManager() michael@0: { michael@0: mESM = nullptr; michael@0: } michael@0: michael@0: bool michael@0: IMEContentObserver::IsManaging(nsPresContext* aPresContext, michael@0: nsIContent* aContent) michael@0: { michael@0: if (!mSelection || !mRootContent || !mEditableNode) { michael@0: return false; // failed to initialize. michael@0: } michael@0: if (!mRootContent->IsInDoc()) { michael@0: return false; // the focused editor has already been reframed. michael@0: } michael@0: return mEditableNode == IMEStateManager::GetRootEditableNode(aPresContext, michael@0: aContent); michael@0: } michael@0: michael@0: bool michael@0: IMEContentObserver::IsEditorHandlingEventForComposition() const michael@0: { michael@0: if (!mWidget) { michael@0: return false; michael@0: } michael@0: nsRefPtr composition = michael@0: IMEStateManager::GetTextCompositionFor(mWidget); michael@0: if (!composition) { michael@0: return false; michael@0: } michael@0: return composition->IsEditorHandlingEvent(); michael@0: } michael@0: michael@0: nsresult michael@0: IMEContentObserver::GetSelectionAndRoot(nsISelection** aSelection, michael@0: nsIContent** aRootContent) const michael@0: { michael@0: if (!mEditableNode || !mSelection) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: NS_ASSERTION(mSelection && mRootContent, "uninitialized content observer"); michael@0: NS_ADDREF(*aSelection = mSelection); michael@0: NS_ADDREF(*aRootContent = mRootContent); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Helper class, used for selection change notification michael@0: class SelectionChangeEvent : public nsRunnable michael@0: { michael@0: public: michael@0: SelectionChangeEvent(IMEContentObserver* aDispatcher, michael@0: bool aCausedByComposition) michael@0: : mDispatcher(aDispatcher) michael@0: , mCausedByComposition(aCausedByComposition) michael@0: { michael@0: MOZ_ASSERT(mDispatcher); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: if (mDispatcher->GetWidget()) { michael@0: IMENotification notification(NOTIFY_IME_OF_SELECTION_CHANGE); michael@0: notification.mSelectionChangeData.mCausedByComposition = michael@0: mCausedByComposition; michael@0: mDispatcher->GetWidget()->NotifyIME(notification); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mDispatcher; michael@0: bool mCausedByComposition; michael@0: }; michael@0: michael@0: nsresult michael@0: IMEContentObserver::NotifySelectionChanged(nsIDOMDocument* aDOMDocument, michael@0: nsISelection* aSelection, michael@0: int16_t aReason) michael@0: { michael@0: bool causedByComposition = IsEditorHandlingEventForComposition(); michael@0: if (causedByComposition && michael@0: !mUpdatePreference.WantChangesCausedByComposition()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: int32_t count = 0; michael@0: nsresult rv = aSelection->GetRangeCount(&count); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (count > 0 && mWidget) { michael@0: nsContentUtils::AddScriptRunner( michael@0: new SelectionChangeEvent(this, causedByComposition)); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Helper class, used for position change notification michael@0: class PositionChangeEvent MOZ_FINAL : public nsRunnable michael@0: { michael@0: public: michael@0: PositionChangeEvent(IMEContentObserver* aDispatcher) michael@0: : mDispatcher(aDispatcher) michael@0: { michael@0: MOZ_ASSERT(mDispatcher); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: if (mDispatcher->GetWidget()) { michael@0: mDispatcher->GetWidget()->NotifyIME( michael@0: IMENotification(NOTIFY_IME_OF_POSITION_CHANGE)); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mDispatcher; michael@0: }; michael@0: michael@0: void michael@0: IMEContentObserver::ScrollPositionChanged() michael@0: { michael@0: if (mWidget) { michael@0: nsContentUtils::AddScriptRunner(new PositionChangeEvent(this)); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: IMEContentObserver::Reflow(DOMHighResTimeStamp aStart, michael@0: DOMHighResTimeStamp aEnd) michael@0: { michael@0: if (mWidget) { michael@0: nsContentUtils::AddScriptRunner(new PositionChangeEvent(this)); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: IMEContentObserver::ReflowInterruptible(DOMHighResTimeStamp aStart, michael@0: DOMHighResTimeStamp aEnd) michael@0: { michael@0: if (mWidget) { michael@0: nsContentUtils::AddScriptRunner(new PositionChangeEvent(this)); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Helper class, used for text change notification michael@0: class TextChangeEvent : public nsRunnable michael@0: { michael@0: public: michael@0: TextChangeEvent(IMEContentObserver* aDispatcher, michael@0: uint32_t aStart, uint32_t aOldEnd, uint32_t aNewEnd, michael@0: bool aCausedByComposition) michael@0: : mDispatcher(aDispatcher) michael@0: , mStart(aStart) michael@0: , mOldEnd(aOldEnd) michael@0: , mNewEnd(aNewEnd) michael@0: , mCausedByComposition(aCausedByComposition) michael@0: { michael@0: MOZ_ASSERT(mDispatcher); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: if (mDispatcher->GetWidget()) { michael@0: IMENotification notification(NOTIFY_IME_OF_TEXT_CHANGE); michael@0: notification.mTextChangeData.mStartOffset = mStart; michael@0: notification.mTextChangeData.mOldEndOffset = mOldEnd; michael@0: notification.mTextChangeData.mNewEndOffset = mNewEnd; michael@0: notification.mTextChangeData.mCausedByComposition = mCausedByComposition; michael@0: mDispatcher->GetWidget()->NotifyIME(notification); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mDispatcher; michael@0: uint32_t mStart, mOldEnd, mNewEnd; michael@0: bool mCausedByComposition; michael@0: }; michael@0: michael@0: void michael@0: IMEContentObserver::CharacterDataChanged(nsIDocument* aDocument, michael@0: nsIContent* aContent, michael@0: CharacterDataChangeInfo* aInfo) michael@0: { michael@0: NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT), michael@0: "character data changed for non-text node"); michael@0: michael@0: bool causedByComposition = IsEditorHandlingEventForComposition(); michael@0: if (causedByComposition && michael@0: !mUpdatePreference.WantChangesCausedByComposition()) { michael@0: return; michael@0: } michael@0: michael@0: uint32_t offset = 0; michael@0: // get offsets of change and fire notification michael@0: nsresult rv = michael@0: ContentEventHandler::GetFlatTextOffsetOfRange(mRootContent, aContent, michael@0: aInfo->mChangeStart, michael@0: &offset, michael@0: LINE_BREAK_TYPE_NATIVE); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: michael@0: uint32_t oldEnd = offset + aInfo->mChangeEnd - aInfo->mChangeStart; michael@0: uint32_t newEnd = offset + aInfo->mReplaceLength; michael@0: michael@0: nsContentUtils::AddScriptRunner( michael@0: new TextChangeEvent(this, offset, oldEnd, newEnd, causedByComposition)); michael@0: } michael@0: michael@0: void michael@0: IMEContentObserver::NotifyContentAdded(nsINode* aContainer, michael@0: int32_t aStartIndex, michael@0: int32_t aEndIndex) michael@0: { michael@0: bool causedByComposition = IsEditorHandlingEventForComposition(); michael@0: if (causedByComposition && michael@0: !mUpdatePreference.WantChangesCausedByComposition()) { michael@0: return; michael@0: } michael@0: michael@0: uint32_t offset = 0; michael@0: nsresult rv = michael@0: ContentEventHandler::GetFlatTextOffsetOfRange(mRootContent, aContainer, michael@0: aStartIndex, &offset, michael@0: LINE_BREAK_TYPE_NATIVE); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: michael@0: // get offset at the end of the last added node michael@0: nsIContent* childAtStart = aContainer->GetChildAt(aStartIndex); michael@0: uint32_t addingLength = 0; michael@0: rv = ContentEventHandler::GetFlatTextOffsetOfRange(childAtStart, aContainer, michael@0: aEndIndex, &addingLength, michael@0: LINE_BREAK_TYPE_NATIVE); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: michael@0: if (!addingLength) { michael@0: return; michael@0: } michael@0: michael@0: nsContentUtils::AddScriptRunner( michael@0: new TextChangeEvent(this, offset, offset, offset + addingLength, michael@0: causedByComposition)); michael@0: } michael@0: michael@0: void michael@0: IMEContentObserver::ContentAppended(nsIDocument* aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aFirstNewContent, michael@0: int32_t aNewIndexInContainer) michael@0: { michael@0: NotifyContentAdded(aContainer, aNewIndexInContainer, michael@0: aContainer->GetChildCount()); michael@0: } michael@0: michael@0: void michael@0: IMEContentObserver::ContentInserted(nsIDocument* aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aChild, michael@0: int32_t aIndexInContainer) michael@0: { michael@0: NotifyContentAdded(NODE_FROM(aContainer, aDocument), michael@0: aIndexInContainer, aIndexInContainer + 1); michael@0: } michael@0: michael@0: void michael@0: IMEContentObserver::ContentRemoved(nsIDocument* aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aChild, michael@0: int32_t aIndexInContainer, michael@0: nsIContent* aPreviousSibling) michael@0: { michael@0: bool causedByComposition = IsEditorHandlingEventForComposition(); michael@0: if (causedByComposition && michael@0: !mUpdatePreference.WantChangesCausedByComposition()) { michael@0: return; michael@0: } michael@0: michael@0: uint32_t offset = 0; michael@0: nsresult rv = michael@0: ContentEventHandler::GetFlatTextOffsetOfRange(mRootContent, michael@0: NODE_FROM(aContainer, michael@0: aDocument), michael@0: aIndexInContainer, &offset, michael@0: LINE_BREAK_TYPE_NATIVE); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: michael@0: // get offset at the end of the deleted node michael@0: int32_t nodeLength = michael@0: aChild->IsNodeOfType(nsINode::eTEXT) ? michael@0: static_cast(aChild->TextLength()) : michael@0: std::max(static_cast(aChild->GetChildCount()), 1); michael@0: MOZ_ASSERT(nodeLength >= 0, "The node length is out of range"); michael@0: uint32_t textLength = 0; michael@0: rv = ContentEventHandler::GetFlatTextOffsetOfRange(aChild, aChild, michael@0: nodeLength, &textLength, michael@0: LINE_BREAK_TYPE_NATIVE); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: michael@0: if (!textLength) { michael@0: return; michael@0: } michael@0: michael@0: nsContentUtils::AddScriptRunner( michael@0: new TextChangeEvent(this, offset, offset + textLength, offset, michael@0: causedByComposition)); michael@0: } michael@0: michael@0: static nsIContent* michael@0: GetContentBR(dom::Element* aElement) michael@0: { michael@0: if (!aElement->IsNodeOfType(nsINode::eCONTENT)) { michael@0: return nullptr; michael@0: } michael@0: nsIContent* content = static_cast(aElement); michael@0: return content->IsHTML(nsGkAtoms::br) ? content : nullptr; michael@0: } michael@0: michael@0: void michael@0: IMEContentObserver::AttributeWillChange(nsIDocument* aDocument, michael@0: dom::Element* aElement, michael@0: int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, michael@0: int32_t aModType) michael@0: { michael@0: nsIContent *content = GetContentBR(aElement); michael@0: mPreAttrChangeLength = content ? michael@0: ContentEventHandler::GetNativeTextLength(content) : 0; michael@0: } michael@0: michael@0: void michael@0: IMEContentObserver::AttributeChanged(nsIDocument* aDocument, michael@0: dom::Element* aElement, michael@0: int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, michael@0: int32_t aModType) michael@0: { michael@0: bool causedByComposition = IsEditorHandlingEventForComposition(); michael@0: if (causedByComposition && michael@0: !mUpdatePreference.WantChangesCausedByComposition()) { michael@0: return; michael@0: } michael@0: michael@0: nsIContent *content = GetContentBR(aElement); michael@0: if (!content) { michael@0: return; michael@0: } michael@0: michael@0: uint32_t postAttrChangeLength = michael@0: ContentEventHandler::GetNativeTextLength(content); michael@0: if (postAttrChangeLength == mPreAttrChangeLength) { michael@0: return; michael@0: } michael@0: uint32_t start; michael@0: nsresult rv = michael@0: ContentEventHandler::GetFlatTextOffsetOfRange(mRootContent, content, michael@0: 0, &start, michael@0: LINE_BREAK_TYPE_NATIVE); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: michael@0: nsContentUtils::AddScriptRunner( michael@0: new TextChangeEvent(this, start, start + mPreAttrChangeLength, michael@0: start + postAttrChangeLength, causedByComposition)); michael@0: } michael@0: michael@0: } // namespace mozilla