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