/* -*- 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 "ContentEventHandler.h" #include "mozilla/Assertions.h" #include "mozilla/CheckedInt.h" #include "mozilla/ContentIterator.h" #include "mozilla/IMEStateManager.h" #include "mozilla/IntegerRange.h" #include "mozilla/Maybe.h" #include "mozilla/PresShell.h" #include "mozilla/RangeBoundary.h" #include "mozilla/RangeUtils.h" #include "mozilla/TextComposition.h" #include "mozilla/TextEditor.h" #include "mozilla/TextEvents.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/HTMLBRElement.h" #include "mozilla/dom/HTMLUnknownElement.h" #include "mozilla/dom/Selection.h" #include "mozilla/dom/Text.h" #include "nsCaret.h" #include "nsCOMPtr.h" #include "nsContentUtils.h" #include "nsCopySupport.h" #include "nsElementTable.h" #include "nsFocusManager.h" #include "nsFontMetrics.h" #include "nsFrameSelection.h" #include "nsIFrame.h" #include "nsLayoutUtils.h" #include "nsPresContext.h" #include "nsQueryObject.h" #include "nsRange.h" #include "nsTextFragment.h" #include "nsTextFrame.h" #include "nsView.h" #include "mozilla/ViewportUtils.h" #include // Work around conflicting define in rpcndr.h #if defined(small) # undef small #endif // defined(small) namespace mozilla { using namespace dom; using namespace widget; /******************************************************************/ /* ContentEventHandler::RawRange */ /******************************************************************/ void ContentEventHandler::RawRange::AssertStartIsBeforeOrEqualToEnd() { MOZ_ASSERT( *nsContentUtils::ComparePoints( mStart.Container(), *mStart.Offset(NodePosition::OffsetFilter::kValidOrInvalidOffsets), mEnd.Container(), *mEnd.Offset(NodePosition::OffsetFilter::kValidOrInvalidOffsets)) <= 0); } nsresult ContentEventHandler::RawRange::SetStart( const RawRangeBoundary& aStart) { nsINode* newRoot = RangeUtils::ComputeRootNode(aStart.Container()); if (!newRoot) { return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR; } if (!aStart.IsSetAndValid()) { return NS_ERROR_DOM_INDEX_SIZE_ERR; } // Collapse if not positioned yet, or if positioned in another document. if (!IsPositioned() || newRoot != mRoot) { mRoot = newRoot; mStart.CopyFrom(aStart, RangeBoundaryIsMutationObserved::Yes); mEnd.CopyFrom(aStart, RangeBoundaryIsMutationObserved::Yes); return NS_OK; } mStart.CopyFrom(aStart, RangeBoundaryIsMutationObserved::Yes); AssertStartIsBeforeOrEqualToEnd(); return NS_OK; } nsresult ContentEventHandler::RawRange::SetEnd(const RawRangeBoundary& aEnd) { nsINode* newRoot = RangeUtils::ComputeRootNode(aEnd.Container()); if (!newRoot) { return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR; } if (!aEnd.IsSetAndValid()) { return NS_ERROR_DOM_INDEX_SIZE_ERR; } // Collapse if not positioned yet, or if positioned in another document. if (!IsPositioned() || newRoot != mRoot) { mRoot = newRoot; mStart.CopyFrom(aEnd, RangeBoundaryIsMutationObserved::Yes); mEnd.CopyFrom(aEnd, RangeBoundaryIsMutationObserved::Yes); return NS_OK; } mEnd.CopyFrom(aEnd, RangeBoundaryIsMutationObserved::Yes); AssertStartIsBeforeOrEqualToEnd(); return NS_OK; } nsresult ContentEventHandler::RawRange::SetEndAfter(nsINode* aEndContainer) { return SetEnd(RangeUtils::GetRawRangeBoundaryAfter(aEndContainer)); } void ContentEventHandler::RawRange::SetStartAndEnd(const nsRange* aRange) { DebugOnly rv = SetStartAndEnd(aRange->StartRef().AsRaw(), aRange->EndRef().AsRaw()); MOZ_ASSERT(!aRange->IsPositioned() || NS_SUCCEEDED(rv)); } nsresult ContentEventHandler::RawRange::SetStartAndEnd( const RawRangeBoundary& aStart, const RawRangeBoundary& aEnd) { nsINode* newStartRoot = RangeUtils::ComputeRootNode(aStart.Container()); if (!newStartRoot) { return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR; } if (!aStart.IsSetAndValid()) { return NS_ERROR_DOM_INDEX_SIZE_ERR; } if (aStart.Container() == aEnd.Container()) { if (!aEnd.IsSetAndValid()) { return NS_ERROR_DOM_INDEX_SIZE_ERR; } MOZ_ASSERT(*aStart.Offset(RawRangeBoundary::OffsetFilter::kValidOffsets) <= *aEnd.Offset(RawRangeBoundary::OffsetFilter::kValidOffsets)); mRoot = newStartRoot; mStart.CopyFrom(aStart, RangeBoundaryIsMutationObserved::Yes); mEnd.CopyFrom(aEnd, RangeBoundaryIsMutationObserved::Yes); return NS_OK; } nsINode* newEndRoot = RangeUtils::ComputeRootNode(aEnd.Container()); if (!newEndRoot) { return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR; } if (!aEnd.IsSetAndValid()) { return NS_ERROR_DOM_INDEX_SIZE_ERR; } // If they have different root, this should be collapsed at the end point. if (newStartRoot != newEndRoot) { mRoot = newEndRoot; mStart.CopyFrom(aEnd, RangeBoundaryIsMutationObserved::Yes); mEnd.CopyFrom(aEnd, RangeBoundaryIsMutationObserved::Yes); return NS_OK; } // Otherwise, set the range as specified. mRoot = newStartRoot; mStart.CopyFrom(aStart, RangeBoundaryIsMutationObserved::Yes); mEnd.CopyFrom(aEnd, RangeBoundaryIsMutationObserved::Yes); AssertStartIsBeforeOrEqualToEnd(); return NS_OK; } nsresult ContentEventHandler::RawRange::SelectNodeContents( const nsINode* aNodeToSelectContents) { nsINode* const newRoot = RangeUtils::ComputeRootNode(const_cast(aNodeToSelectContents)); if (!newRoot) { return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR; } mRoot = newRoot; mStart = RangeBoundary(const_cast(aNodeToSelectContents), nullptr); mEnd = RangeBoundary(const_cast(aNodeToSelectContents), aNodeToSelectContents->GetLastChild()); return NS_OK; } /******************************************************************/ /* ContentEventHandler */ /******************************************************************/ // NOTE // // ContentEventHandler *creates* ranges as following rules: // 1. Start of range: // 1.1. Cases: [textNode or text[Node or textNode[ // When text node is start of a range, start node is the text node and // start offset is any number between 0 and the length of the text. // 1.2. Case: [: // When start of an element node is start of a range, start node is // parent of the element and start offset is the element's index in the // parent. // 1.3. Case: [ // When after an empty element node is start of a range, start node is // parent of the element and start offset is the element's index in the // parent + 1. // 1.4. Case: [ // When start of a non-empty element is start of a range, start node is // the element and start offset is 0. // 1.5. Case: [ // When start of a range is 0 and there are no nodes causing text, // start node is the root node and start offset is 0. // 1.6. Case: [ // When start of a range is out of bounds, start node is the root node // and start offset is number of the children. // 2. End of range: // 2.1. Cases: ]textNode or text]Node or textNode] // When a text node is end of a range, end node is the text node and // end offset is any number between 0 and the length of the text. // 2.2. Case: ] // When before an element node (meaning before the open tag of the // element) is end of a range, end node is previous node causing text. // Note that this case shouldn't be handled directly. If rule 2.1 and // 2.3 are handled correctly, the loop with ContentIterator shouldn't // reach the element node since the loop should've finished already at // handling the last node which caused some text. // 2.3. Case: ] // When a line break is caused before a non-empty element node and it's // end of a range, end node is the element and end offset is 0. // (i.e., including open tag of the element) // 2.4. Cases: ] // When after an empty element node is end of a range, end node is // parent of the element node and end offset is the element's index in // the parent + 1. (i.e., including close tag of the element or empty // element) // 2.5. Case: ] // When end of a range is out of bounds, end node is the root node and // end offset is number of the children. // // ContentEventHandler *treats* ranges as following additional rules: // 1. When the start node is an element node which doesn't have children, // it includes a line break caused before itself (i.e., includes its open // tag). For example, if start position is {
, 0 }, the line break // caused by
should be included into the flatten text. // 2. When the end node is an element node which doesn't have children, // it includes the end (i.e., includes its close tag except empty element). // Although, currently, any close tags don't cause line break, this also // includes its open tag. For example, if end position is {
, 0 }, the // line break caused by the
should be included into the flatten text. ContentEventHandler::ContentEventHandler(nsPresContext* aPresContext) : mDocument(aPresContext->Document()) {} nsresult ContentEventHandler::InitBasic(bool aRequireFlush) { NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE); if (aRequireFlush) { // If text frame which has overflowing selection underline is dirty, // we need to flush the pending reflow here. mDocument->FlushPendingNotifications(FlushType::Layout); } return NS_OK; } nsresult ContentEventHandler::InitRootContent( const Selection& aNormalSelection) { // Root content should be computed with normal selection because normal // selection is typically has at least one range but the other selections // not so. If there is a range, computing its root is easy, but if // there are no ranges, we need to use ancestor limit instead. MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal); if (!aNormalSelection.RangeCount()) { // If there is no selection range, we should compute the selection root // from ancestor limiter or root content of the document. mRootElement = Element::FromNodeOrNull(aNormalSelection.GetAncestorLimiter()); if (!mRootElement) { mRootElement = mDocument->GetRootElement(); if (NS_WARN_IF(!mRootElement)) { return NS_ERROR_NOT_AVAILABLE; } } return NS_OK; } RefPtr range(aNormalSelection.GetRangeAt(0)); if (NS_WARN_IF(!range)) { return NS_ERROR_UNEXPECTED; } // If there is a selection, we should retrieve the selection root from // the range since when the window is inactivated, the ancestor limiter // of selection was cleared by blur event handler of EditorBase but the // selection range still keeps storing the nodes. If the active element of // the deactive window is or