mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-31 14:15:30 +00:00
8b160783a8
Bug 1319660 - 1. Don't take shortcut if old replacement ranges don't match; r=esawin The block at [1] is a shortcut we take when we reconcile Java text changes with Gecko text changes. However, we only checked that the new ranges are the same, i.e. that the new Gecko text is the same as the new Java text. We should also be checking that the old ranges are the same, i.e. that the replaced Gecko text is the same as the replaced Java text. [1] https://dxr.mozilla.org/mozilla-central/rev/bbbd2f7539f224a482cc6d2dd10e6a5f31c8baf3/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditable.java#1233 Bug 1319660 - 2. Use previous node instead of sibling when adjusting last node; r=masayuki r=smaug nsContentIterator in pre mode adjusts its last node if the node is a childless node like <br>. However, right now it's using GetPrevSibling, which can lead to error in some edge cases such as: <p></p><div><br></div> In this case, if the last node is <br> with offset 0, GetPrevSibling will return <p> because <p> is <br>'s parent's previous sibling, and the last node will be set to <p>. However, the correct last node in this case is <div>, because <br> with offset 0 refers to the position to the left of <br>, which is <div> with offset 0. In this case, PrevNode returns the correct <div> value, so we should set the last node to the result of PrevNode. For the first node, for a childless node in pre mode, GetNextSibling and NextNode are the same, so there is no bug in this case. Nevertheless, this patch changes the call to NextNode to be consistent with calling PrevNode for the last node. Bug 1319660 - 3. Add test for correctly adjusting last node in content iterator; r=masayuki Add a test for the previous patch that makes sure querying selected text in an edge case works correctly. Bug 1319660 - 4. Add test for start node regression; r=me Add a new test case for the NextNode() regression. r=me for trivial test-only patch. Bug 1319660 - 5. Restore GetNextSibling call for first node of pre-content-iterator; r=smaug The last patch changed the `GetNextSibling()` call to `NextNode()` because I assumed they're equivalent in this case. That turned out to not be the case because we can reach this line even if the node has children -- the index just has to be after the last child. So this patch restores the `GetNextSibling` call to restore the correct behavior. I also added some comment to clarify that we can reach this line due to one of two conditions: 1) the node has no children; 2) the node has children but the index is after the last child. This patch also replaces the `HasChildren()` check when setting `cChild`. If the index is after the last child (i.e. index == childCount), `GetChildAt()` fails and we erroneously log an assertion warning, even though the input was valid. The new check handles all cases whether start node has children or not.
1554 lines
44 KiB
C++
1554 lines
44 KiB
C++
/* -*- 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 "mozilla/DebugOnly.h"
|
|
#include "nsISupports.h"
|
|
#include "nsIDOMNodeList.h"
|
|
#include "nsIContentIterator.h"
|
|
#include "nsRange.h"
|
|
#include "nsIContent.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "nsTArray.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsINode.h"
|
|
#include "nsCycleCollectionParticipant.h"
|
|
|
|
using mozilla::DebugOnly;
|
|
|
|
// couple of utility static functs
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NodeToParentOffset: returns the node's parent and offset.
|
|
//
|
|
|
|
static nsINode*
|
|
NodeToParentOffset(nsINode* aNode, int32_t* aOffset)
|
|
{
|
|
*aOffset = 0;
|
|
|
|
nsINode* parent = aNode->GetParentNode();
|
|
|
|
if (parent) {
|
|
*aOffset = parent->IndexOf(aNode);
|
|
NS_WARNING_ASSERTION(*aOffset >= 0, "bad offset");
|
|
}
|
|
|
|
return parent;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NodeIsInTraversalRange: returns true if content is visited during
|
|
// the traversal of the range in the specified mode.
|
|
//
|
|
static bool
|
|
NodeIsInTraversalRange(nsINode* aNode, bool aIsPreMode,
|
|
nsINode* aStartNode, int32_t aStartOffset,
|
|
nsINode* aEndNode, int32_t aEndOffset)
|
|
{
|
|
if (NS_WARN_IF(!aStartNode) || NS_WARN_IF(!aEndNode) || NS_WARN_IF(!aNode)) {
|
|
return false;
|
|
}
|
|
|
|
// If a leaf node contains an end point of the traversal range, it is
|
|
// always in the traversal range.
|
|
if (aNode == aStartNode || aNode == aEndNode) {
|
|
if (aNode->IsNodeOfType(nsINode::eDATA_NODE)) {
|
|
return true; // text node or something
|
|
}
|
|
if (!aNode->HasChildren()) {
|
|
MOZ_ASSERT(aNode != aStartNode || !aStartOffset,
|
|
"aStartNode doesn't have children and not a data node, "
|
|
"aStartOffset should be 0");
|
|
MOZ_ASSERT(aNode != aEndNode || !aEndOffset,
|
|
"aStartNode doesn't have children and not a data node, "
|
|
"aStartOffset should be 0");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
nsINode* parent = aNode->GetParentNode();
|
|
if (!parent) {
|
|
return false;
|
|
}
|
|
|
|
int32_t indx = parent->IndexOf(aNode);
|
|
NS_WARNING_ASSERTION(indx != -1, "bad indx");
|
|
|
|
if (!aIsPreMode) {
|
|
++indx;
|
|
}
|
|
|
|
return nsContentUtils::ComparePoints(aStartNode, aStartOffset,
|
|
parent, indx) <= 0 &&
|
|
nsContentUtils::ComparePoints(aEndNode, aEndOffset,
|
|
parent, indx) >= 0;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* A simple iterator class for traversing the content in "close tag" order
|
|
*/
|
|
class nsContentIterator : public nsIContentIterator
|
|
{
|
|
public:
|
|
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
|
NS_DECL_CYCLE_COLLECTION_CLASS(nsContentIterator)
|
|
|
|
explicit nsContentIterator(bool aPre);
|
|
|
|
// nsIContentIterator interface methods ------------------------------
|
|
|
|
virtual nsresult Init(nsINode* aRoot) override;
|
|
|
|
virtual nsresult Init(nsIDOMRange* aRange) override;
|
|
|
|
virtual void First() override;
|
|
|
|
virtual void Last() override;
|
|
|
|
virtual void Next() override;
|
|
|
|
virtual void Prev() override;
|
|
|
|
virtual nsINode* GetCurrentNode() override;
|
|
|
|
virtual bool IsDone() override;
|
|
|
|
virtual nsresult PositionAt(nsINode* aCurNode) override;
|
|
|
|
protected:
|
|
virtual ~nsContentIterator();
|
|
|
|
// Recursively get the deepest first/last child of aRoot. This will return
|
|
// aRoot itself if it has no children.
|
|
nsINode* GetDeepFirstChild(nsINode* aRoot,
|
|
nsTArray<int32_t>* aIndexes = nullptr);
|
|
nsIContent* GetDeepFirstChild(nsIContent* aRoot,
|
|
nsTArray<int32_t>* aIndexes = nullptr);
|
|
nsINode* GetDeepLastChild(nsINode* aRoot,
|
|
nsTArray<int32_t>* aIndexes = nullptr);
|
|
nsIContent* GetDeepLastChild(nsIContent* aRoot,
|
|
nsTArray<int32_t>* aIndexes = nullptr);
|
|
|
|
// Get the next/previous sibling of aNode, or its parent's, or grandparent's,
|
|
// etc. Returns null if aNode and all its ancestors have no next/previous
|
|
// sibling.
|
|
nsIContent* GetNextSibling(nsINode* aNode,
|
|
nsTArray<int32_t>* aIndexes = nullptr);
|
|
nsIContent* GetPrevSibling(nsINode* aNode,
|
|
nsTArray<int32_t>* aIndexes = nullptr);
|
|
|
|
nsINode* NextNode(nsINode* aNode, nsTArray<int32_t>* aIndexes = nullptr);
|
|
nsINode* PrevNode(nsINode* aNode, nsTArray<int32_t>* aIndexes = nullptr);
|
|
|
|
// WARNING: This function is expensive
|
|
nsresult RebuildIndexStack();
|
|
|
|
void MakeEmpty();
|
|
|
|
virtual void LastRelease();
|
|
|
|
nsCOMPtr<nsINode> mCurNode;
|
|
nsCOMPtr<nsINode> mFirst;
|
|
nsCOMPtr<nsINode> mLast;
|
|
nsCOMPtr<nsINode> mCommonParent;
|
|
|
|
// used by nsContentIterator to cache indices
|
|
AutoTArray<int32_t, 8> mIndexes;
|
|
|
|
// used by nsSubtreeIterator to cache indices. Why put them in the base
|
|
// class? Because otherwise I have to duplicate the routines GetNextSibling
|
|
// etc across both classes, with slight variations for caching. Or
|
|
// alternately, create a base class for the cache itself and have all the
|
|
// cache manipulation go through a vptr. I think this is the best space and
|
|
// speed combo, even though it's ugly.
|
|
int32_t mCachedIndex;
|
|
// another note about mCachedIndex: why should the subtree iterator use a
|
|
// trivial cached index instead of the mre robust array of indicies (which is
|
|
// what the basic content iterator uses)? The reason is that subtree
|
|
// iterators do not do much transitioning between parents and children. They
|
|
// tend to stay at the same level. In fact, you can prove (though I won't
|
|
// attempt it here) that they change levels at most n+m times, where n is the
|
|
// height of the parent hierarchy from the range start to the common
|
|
// ancestor, and m is the the height of the parent hierarchy from the range
|
|
// end to the common ancestor. If we used the index array, we would pay the
|
|
// price up front for n, and then pay the cost for m on the fly later on.
|
|
// With the simple cache, we only "pay as we go". Either way, we call
|
|
// IndexOf() once for each change of level in the hierarchy. Since a trivial
|
|
// index is much simpler, we use it for the subtree iterator.
|
|
|
|
bool mIsDone;
|
|
bool mPre;
|
|
|
|
private:
|
|
|
|
// no copies or assigns FIX ME
|
|
nsContentIterator(const nsContentIterator&);
|
|
nsContentIterator& operator=(const nsContentIterator&);
|
|
|
|
};
|
|
|
|
|
|
/******************************************************
|
|
* repository cruft
|
|
******************************************************/
|
|
|
|
already_AddRefed<nsIContentIterator>
|
|
NS_NewContentIterator()
|
|
{
|
|
nsCOMPtr<nsIContentIterator> iter = new nsContentIterator(false);
|
|
return iter.forget();
|
|
}
|
|
|
|
|
|
already_AddRefed<nsIContentIterator>
|
|
NS_NewPreContentIterator()
|
|
{
|
|
nsCOMPtr<nsIContentIterator> iter = new nsContentIterator(true);
|
|
return iter.forget();
|
|
}
|
|
|
|
|
|
/******************************************************
|
|
* XPCOM cruft
|
|
******************************************************/
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsContentIterator)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(nsContentIterator,
|
|
LastRelease())
|
|
|
|
NS_INTERFACE_MAP_BEGIN(nsContentIterator)
|
|
NS_INTERFACE_MAP_ENTRY(nsIContentIterator)
|
|
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentIterator)
|
|
NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsContentIterator)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION(nsContentIterator,
|
|
mCurNode,
|
|
mFirst,
|
|
mLast,
|
|
mCommonParent)
|
|
|
|
void
|
|
nsContentIterator::LastRelease()
|
|
{
|
|
mCurNode = nullptr;
|
|
mFirst = nullptr;
|
|
mLast = nullptr;
|
|
mCommonParent = nullptr;
|
|
}
|
|
|
|
/******************************************************
|
|
* constructor/destructor
|
|
******************************************************/
|
|
|
|
nsContentIterator::nsContentIterator(bool aPre) :
|
|
// don't need to explicitly initialize |nsCOMPtr|s, they will automatically
|
|
// be nullptr
|
|
mCachedIndex(0), mIsDone(false), mPre(aPre)
|
|
{
|
|
}
|
|
|
|
|
|
nsContentIterator::~nsContentIterator()
|
|
{
|
|
}
|
|
|
|
|
|
/******************************************************
|
|
* Init routines
|
|
******************************************************/
|
|
|
|
|
|
nsresult
|
|
nsContentIterator::Init(nsINode* aRoot)
|
|
{
|
|
if (NS_WARN_IF(!aRoot)) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
|
|
mIsDone = false;
|
|
mIndexes.Clear();
|
|
|
|
if (mPre) {
|
|
mFirst = aRoot;
|
|
mLast = GetDeepLastChild(aRoot);
|
|
NS_WARNING_ASSERTION(mLast, "GetDeepLastChild returned null");
|
|
} else {
|
|
mFirst = GetDeepFirstChild(aRoot);
|
|
NS_WARNING_ASSERTION(mFirst, "GetDeepFirstChild returned null");
|
|
mLast = aRoot;
|
|
}
|
|
|
|
mCommonParent = aRoot;
|
|
mCurNode = mFirst;
|
|
RebuildIndexStack();
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsContentIterator::Init(nsIDOMRange* aDOMRange)
|
|
{
|
|
if (NS_WARN_IF(!aDOMRange)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
nsRange* range = static_cast<nsRange*>(aDOMRange);
|
|
|
|
mIsDone = false;
|
|
|
|
// get common content parent
|
|
mCommonParent = range->GetCommonAncestor();
|
|
if (NS_WARN_IF(!mCommonParent)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// get the start node and offset
|
|
int32_t startIndx = range->StartOffset();
|
|
NS_WARNING_ASSERTION(startIndx >= 0, "bad startIndx");
|
|
nsINode* startNode = range->GetStartParent();
|
|
if (NS_WARN_IF(!startNode)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// get the end node and offset
|
|
int32_t endIndx = range->EndOffset();
|
|
NS_WARNING_ASSERTION(endIndx >= 0, "bad endIndx");
|
|
nsINode* endNode = range->GetEndParent();
|
|
if (NS_WARN_IF(!endNode)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
bool startIsData = startNode->IsNodeOfType(nsINode::eDATA_NODE);
|
|
|
|
// short circuit when start node == end node
|
|
if (startNode == endNode) {
|
|
// Check to see if we have a collapsed range, if so, there is nothing to
|
|
// iterate over.
|
|
//
|
|
// XXX: CharacterDataNodes (text nodes) are currently an exception, since
|
|
// we always want to be able to iterate text nodes at the end points
|
|
// of a range.
|
|
|
|
if (!startIsData && startIndx == endIndx) {
|
|
MakeEmpty();
|
|
return NS_OK;
|
|
}
|
|
|
|
if (startIsData) {
|
|
// It's a character data node.
|
|
mFirst = startNode->AsContent();
|
|
mLast = mFirst;
|
|
mCurNode = mFirst;
|
|
|
|
DebugOnly<nsresult> rv = RebuildIndexStack();
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "RebuildIndexStack failed");
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
// Find first node in range.
|
|
|
|
nsIContent* cChild = nullptr;
|
|
|
|
// Valid start indices are 0 <= startIndx <= childCount. That means if start
|
|
// node has no children, only offset 0 is valid.
|
|
if (!startIsData && uint32_t(startIndx) < startNode->GetChildCount()) {
|
|
cChild = startNode->GetChildAt(startIndx);
|
|
NS_WARNING_ASSERTION(cChild, "GetChildAt returned null");
|
|
}
|
|
|
|
if (!cChild) {
|
|
// No children (possibly a <br> or text node), or index is after last child.
|
|
|
|
if (mPre) {
|
|
// XXX: In the future, if start offset is after the last
|
|
// character in the cdata node, should we set mFirst to
|
|
// the next sibling?
|
|
|
|
// Normally we would skip the start node because the start node is outside
|
|
// of the range in pre mode. However, if startIndx == 0, it means the node
|
|
// has no children, and the node may be <br> or something. We don't skip
|
|
// the node in this case in order to address bug 1215798.
|
|
if (!startIsData && startIndx) {
|
|
mFirst = GetNextSibling(startNode);
|
|
NS_WARNING_ASSERTION(mFirst, "GetNextSibling returned null");
|
|
|
|
// Does mFirst node really intersect the range? The range could be
|
|
// 'degenerate', i.e., not collapsed but still contain no content.
|
|
if (mFirst &&
|
|
NS_WARN_IF(!NodeIsInTraversalRange(mFirst, mPre, startNode,
|
|
startIndx, endNode, endIndx))) {
|
|
mFirst = nullptr;
|
|
}
|
|
} else {
|
|
mFirst = startNode->AsContent();
|
|
}
|
|
} else {
|
|
// post-order
|
|
if (NS_WARN_IF(!startNode->IsContent())) {
|
|
// What else can we do?
|
|
mFirst = nullptr;
|
|
} else {
|
|
mFirst = startNode->AsContent();
|
|
}
|
|
}
|
|
} else {
|
|
if (mPre) {
|
|
mFirst = cChild;
|
|
} else {
|
|
// post-order
|
|
mFirst = GetDeepFirstChild(cChild);
|
|
NS_WARNING_ASSERTION(mFirst, "GetDeepFirstChild returned null");
|
|
|
|
// Does mFirst node really intersect the range? The range could be
|
|
// 'degenerate', i.e., not collapsed but still contain no content.
|
|
|
|
if (mFirst &&
|
|
!NodeIsInTraversalRange(mFirst, mPre, startNode, startIndx,
|
|
endNode, endIndx)) {
|
|
mFirst = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Find last node in range.
|
|
|
|
bool endIsData = endNode->IsNodeOfType(nsINode::eDATA_NODE);
|
|
|
|
if (endIsData || !endNode->HasChildren() || endIndx == 0) {
|
|
if (mPre) {
|
|
if (NS_WARN_IF(!endNode->IsContent())) {
|
|
// Not much else to do here...
|
|
mLast = nullptr;
|
|
} else {
|
|
// If the end node is an empty element and the end offset is 0,
|
|
// the last element should be the previous node (i.e., shouldn't
|
|
// include the end node in the range).
|
|
if (!endIsData && !endNode->HasChildren() && !endIndx) {
|
|
mLast = PrevNode(endNode);
|
|
NS_WARNING_ASSERTION(mLast, "PrevNode returned null");
|
|
if (NS_WARN_IF(!NodeIsInTraversalRange(mLast, mPre,
|
|
startNode, startIndx,
|
|
endNode, endIndx))) {
|
|
mLast = nullptr;
|
|
}
|
|
} else {
|
|
mLast = endNode->AsContent();
|
|
}
|
|
}
|
|
} else {
|
|
// post-order
|
|
//
|
|
// XXX: In the future, if end offset is before the first character in the
|
|
// cdata node, should we set mLast to the prev sibling?
|
|
|
|
if (!endIsData) {
|
|
mLast = GetPrevSibling(endNode);
|
|
NS_WARNING_ASSERTION(mLast, "GetPrevSibling returned null");
|
|
|
|
if (!NodeIsInTraversalRange(mLast, mPre,
|
|
startNode, startIndx,
|
|
endNode, endIndx)) {
|
|
mLast = nullptr;
|
|
}
|
|
} else {
|
|
mLast = endNode->AsContent();
|
|
}
|
|
}
|
|
} else {
|
|
int32_t indx = endIndx;
|
|
|
|
cChild = endNode->GetChildAt(--indx);
|
|
|
|
if (NS_WARN_IF(!cChild)) {
|
|
// No child at offset!
|
|
NS_NOTREACHED("nsContentIterator::nsContentIterator");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (mPre) {
|
|
mLast = GetDeepLastChild(cChild);
|
|
NS_WARNING_ASSERTION(mLast, "GetDeepLastChild returned null");
|
|
|
|
if (NS_WARN_IF(!NodeIsInTraversalRange(mLast, mPre,
|
|
startNode, startIndx,
|
|
endNode, endIndx))) {
|
|
mLast = nullptr;
|
|
}
|
|
} else {
|
|
// post-order
|
|
mLast = cChild;
|
|
}
|
|
}
|
|
|
|
// If either first or last is null, they both have to be null!
|
|
|
|
if (!mFirst || !mLast) {
|
|
mFirst = nullptr;
|
|
mLast = nullptr;
|
|
}
|
|
|
|
mCurNode = mFirst;
|
|
mIsDone = !mCurNode;
|
|
|
|
if (!mCurNode) {
|
|
mIndexes.Clear();
|
|
} else {
|
|
DebugOnly<nsresult> rv = RebuildIndexStack();
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "RebuildIndexStack failed");
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
/******************************************************
|
|
* Helper routines
|
|
******************************************************/
|
|
// WARNING: This function is expensive
|
|
nsresult
|
|
nsContentIterator::RebuildIndexStack()
|
|
{
|
|
// Make sure we start at the right indexes on the stack! Build array up
|
|
// to common parent of start and end. Perhaps it's too many entries, but
|
|
// that's far better than too few.
|
|
nsINode* parent;
|
|
nsINode* current;
|
|
|
|
mIndexes.Clear();
|
|
current = mCurNode;
|
|
if (!current) {
|
|
return NS_OK;
|
|
}
|
|
|
|
while (current != mCommonParent) {
|
|
parent = current->GetParentNode();
|
|
|
|
if (NS_WARN_IF(!parent)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mIndexes.InsertElementAt(0, parent->IndexOf(current));
|
|
|
|
current = parent;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
nsContentIterator::MakeEmpty()
|
|
{
|
|
mCurNode = nullptr;
|
|
mFirst = nullptr;
|
|
mLast = nullptr;
|
|
mCommonParent = nullptr;
|
|
mIsDone = true;
|
|
mIndexes.Clear();
|
|
}
|
|
|
|
nsINode*
|
|
nsContentIterator::GetDeepFirstChild(nsINode* aRoot,
|
|
nsTArray<int32_t>* aIndexes)
|
|
{
|
|
if (NS_WARN_IF(!aRoot) || !aRoot->HasChildren()) {
|
|
return aRoot;
|
|
}
|
|
// We can't pass aRoot itself to the full GetDeepFirstChild, because that
|
|
// will only take nsIContent and aRoot might be a document. Pass aRoot's
|
|
// child, but be sure to preserve aIndexes.
|
|
if (aIndexes) {
|
|
aIndexes->AppendElement(0);
|
|
}
|
|
return GetDeepFirstChild(aRoot->GetFirstChild(), aIndexes);
|
|
}
|
|
|
|
nsIContent*
|
|
nsContentIterator::GetDeepFirstChild(nsIContent* aRoot,
|
|
nsTArray<int32_t>* aIndexes)
|
|
{
|
|
if (NS_WARN_IF(!aRoot)) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsIContent* node = aRoot;
|
|
nsIContent* child = node->GetFirstChild();
|
|
|
|
while (child) {
|
|
if (aIndexes) {
|
|
// Add this node to the stack of indexes
|
|
aIndexes->AppendElement(0);
|
|
}
|
|
node = child;
|
|
child = node->GetFirstChild();
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
nsINode*
|
|
nsContentIterator::GetDeepLastChild(nsINode* aRoot,
|
|
nsTArray<int32_t>* aIndexes)
|
|
{
|
|
if (NS_WARN_IF(!aRoot) || !aRoot->HasChildren()) {
|
|
return aRoot;
|
|
}
|
|
// We can't pass aRoot itself to the full GetDeepLastChild, because that will
|
|
// only take nsIContent and aRoot might be a document. Pass aRoot's child,
|
|
// but be sure to preserve aIndexes.
|
|
if (aIndexes) {
|
|
aIndexes->AppendElement(aRoot->GetChildCount() - 1);
|
|
}
|
|
return GetDeepLastChild(aRoot->GetLastChild(), aIndexes);
|
|
}
|
|
|
|
nsIContent*
|
|
nsContentIterator::GetDeepLastChild(nsIContent* aRoot,
|
|
nsTArray<int32_t>* aIndexes)
|
|
{
|
|
if (NS_WARN_IF(!aRoot)) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsIContent* node = aRoot;
|
|
int32_t numChildren = node->GetChildCount();
|
|
|
|
while (numChildren) {
|
|
nsIContent* child = node->GetChildAt(--numChildren);
|
|
|
|
if (aIndexes) {
|
|
// Add this node to the stack of indexes
|
|
aIndexes->AppendElement(numChildren);
|
|
}
|
|
numChildren = child->GetChildCount();
|
|
node = child;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
// Get the next sibling, or parent's next sibling, or grandpa's next sibling...
|
|
nsIContent*
|
|
nsContentIterator::GetNextSibling(nsINode* aNode,
|
|
nsTArray<int32_t>* aIndexes)
|
|
{
|
|
if (NS_WARN_IF(!aNode)) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsINode* parent = aNode->GetParentNode();
|
|
if (NS_WARN_IF(!parent)) {
|
|
return nullptr;
|
|
}
|
|
|
|
int32_t indx = 0;
|
|
|
|
NS_ASSERTION(!aIndexes || !aIndexes->IsEmpty(),
|
|
"ContentIterator stack underflow");
|
|
if (aIndexes && !aIndexes->IsEmpty()) {
|
|
// use the last entry on the Indexes array for the current index
|
|
indx = (*aIndexes)[aIndexes->Length()-1];
|
|
} else {
|
|
indx = mCachedIndex;
|
|
}
|
|
NS_WARNING_ASSERTION(indx >= 0, "bad indx");
|
|
|
|
// reverify that the index of the current node hasn't changed.
|
|
// not super cheap, but a lot cheaper than IndexOf(), and still O(1).
|
|
// ignore result this time - the index may now be out of range.
|
|
nsIContent* sib = parent->GetChildAt(indx);
|
|
if (sib != aNode) {
|
|
// someone changed our index - find the new index the painful way
|
|
indx = parent->IndexOf(aNode);
|
|
NS_WARNING_ASSERTION(indx >= 0, "bad indx");
|
|
}
|
|
|
|
// indx is now canonically correct
|
|
if ((sib = parent->GetChildAt(++indx))) {
|
|
// update index cache
|
|
if (aIndexes && !aIndexes->IsEmpty()) {
|
|
aIndexes->ElementAt(aIndexes->Length()-1) = indx;
|
|
} else {
|
|
mCachedIndex = indx;
|
|
}
|
|
} else {
|
|
if (parent != mCommonParent) {
|
|
if (aIndexes) {
|
|
// pop node off the stack, go up one level and return parent or fail.
|
|
// Don't leave the index empty, especially if we're
|
|
// returning nullptr. This confuses other parts of the code.
|
|
if (aIndexes->Length() > 1) {
|
|
aIndexes->RemoveElementAt(aIndexes->Length()-1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ok to leave cache out of date here if parent == mCommonParent?
|
|
sib = GetNextSibling(parent, aIndexes);
|
|
}
|
|
|
|
return sib;
|
|
}
|
|
|
|
// Get the prev sibling, or parent's prev sibling, or grandpa's prev sibling...
|
|
nsIContent*
|
|
nsContentIterator::GetPrevSibling(nsINode* aNode,
|
|
nsTArray<int32_t>* aIndexes)
|
|
{
|
|
if (NS_WARN_IF(!aNode)) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsINode* parent = aNode->GetParentNode();
|
|
if (NS_WARN_IF(!parent)) {
|
|
return nullptr;
|
|
}
|
|
|
|
int32_t indx = 0;
|
|
|
|
NS_ASSERTION(!aIndexes || !aIndexes->IsEmpty(),
|
|
"ContentIterator stack underflow");
|
|
if (aIndexes && !aIndexes->IsEmpty()) {
|
|
// use the last entry on the Indexes array for the current index
|
|
indx = (*aIndexes)[aIndexes->Length()-1];
|
|
} else {
|
|
indx = mCachedIndex;
|
|
}
|
|
|
|
// reverify that the index of the current node hasn't changed
|
|
// ignore result this time - the index may now be out of range.
|
|
nsIContent* sib = parent->GetChildAt(indx);
|
|
if (sib != aNode) {
|
|
// someone changed our index - find the new index the painful way
|
|
indx = parent->IndexOf(aNode);
|
|
NS_WARNING_ASSERTION(indx >= 0, "bad indx");
|
|
}
|
|
|
|
// indx is now canonically correct
|
|
if (indx > 0 && (sib = parent->GetChildAt(--indx))) {
|
|
// update index cache
|
|
if (aIndexes && !aIndexes->IsEmpty()) {
|
|
aIndexes->ElementAt(aIndexes->Length()-1) = indx;
|
|
} else {
|
|
mCachedIndex = indx;
|
|
}
|
|
} else if (parent != mCommonParent) {
|
|
if (aIndexes && !aIndexes->IsEmpty()) {
|
|
// pop node off the stack, go up one level and try again.
|
|
aIndexes->RemoveElementAt(aIndexes->Length()-1);
|
|
}
|
|
return GetPrevSibling(parent, aIndexes);
|
|
}
|
|
|
|
return sib;
|
|
}
|
|
|
|
nsINode*
|
|
nsContentIterator::NextNode(nsINode* aNode, nsTArray<int32_t>* aIndexes)
|
|
{
|
|
nsINode* node = aNode;
|
|
|
|
// if we are a Pre-order iterator, use pre-order
|
|
if (mPre) {
|
|
// if it has children then next node is first child
|
|
if (node->HasChildren()) {
|
|
nsIContent* firstChild = node->GetFirstChild();
|
|
MOZ_ASSERT(firstChild);
|
|
|
|
// update cache
|
|
if (aIndexes) {
|
|
// push an entry on the index stack
|
|
aIndexes->AppendElement(0);
|
|
} else {
|
|
mCachedIndex = 0;
|
|
}
|
|
|
|
return firstChild;
|
|
}
|
|
|
|
// else next sibling is next
|
|
return GetNextSibling(node, aIndexes);
|
|
}
|
|
|
|
// post-order
|
|
nsINode* parent = node->GetParentNode();
|
|
if (NS_WARN_IF(!parent)) {
|
|
MOZ_ASSERT(parent, "The node is the root node but not the last node");
|
|
mIsDone = true;
|
|
return node;
|
|
}
|
|
nsIContent* sibling = nullptr;
|
|
int32_t indx = 0;
|
|
|
|
// get the cached index
|
|
NS_ASSERTION(!aIndexes || !aIndexes->IsEmpty(),
|
|
"ContentIterator stack underflow");
|
|
if (aIndexes && !aIndexes->IsEmpty()) {
|
|
// use the last entry on the Indexes array for the current index
|
|
indx = (*aIndexes)[aIndexes->Length()-1];
|
|
} else {
|
|
indx = mCachedIndex;
|
|
}
|
|
|
|
// reverify that the index of the current node hasn't changed. not super
|
|
// cheap, but a lot cheaper than IndexOf(), and still O(1). ignore result
|
|
// this time - the index may now be out of range.
|
|
if (indx >= 0) {
|
|
sibling = parent->GetChildAt(indx);
|
|
}
|
|
if (sibling != node) {
|
|
// someone changed our index - find the new index the painful way
|
|
indx = parent->IndexOf(node);
|
|
NS_WARNING_ASSERTION(indx >= 0, "bad indx");
|
|
}
|
|
|
|
// indx is now canonically correct
|
|
sibling = parent->GetChildAt(++indx);
|
|
if (sibling) {
|
|
// update cache
|
|
if (aIndexes && !aIndexes->IsEmpty()) {
|
|
// replace an entry on the index stack
|
|
aIndexes->ElementAt(aIndexes->Length()-1) = indx;
|
|
} else {
|
|
mCachedIndex = indx;
|
|
}
|
|
|
|
// next node is sibling's "deep left" child
|
|
return GetDeepFirstChild(sibling, aIndexes);
|
|
}
|
|
|
|
// else it's the parent, update cache
|
|
if (aIndexes) {
|
|
// Pop an entry off the index stack. Don't leave the index empty,
|
|
// especially if we're returning nullptr. This confuses other parts of the
|
|
// code.
|
|
if (aIndexes->Length() > 1) {
|
|
aIndexes->RemoveElementAt(aIndexes->Length()-1);
|
|
}
|
|
} else {
|
|
// this might be wrong, but we are better off guessing
|
|
mCachedIndex = 0;
|
|
}
|
|
|
|
return parent;
|
|
}
|
|
|
|
nsINode*
|
|
nsContentIterator::PrevNode(nsINode* aNode, nsTArray<int32_t>* aIndexes)
|
|
{
|
|
nsINode* node = aNode;
|
|
|
|
// if we are a Pre-order iterator, use pre-order
|
|
if (mPre) {
|
|
nsINode* parent = node->GetParentNode();
|
|
if (NS_WARN_IF(!parent)) {
|
|
MOZ_ASSERT(parent, "The node is the root node but not the first node");
|
|
mIsDone = true;
|
|
return aNode;
|
|
}
|
|
nsIContent* sibling = nullptr;
|
|
int32_t indx = 0;
|
|
|
|
// get the cached index
|
|
NS_ASSERTION(!aIndexes || !aIndexes->IsEmpty(),
|
|
"ContentIterator stack underflow");
|
|
if (aIndexes && !aIndexes->IsEmpty()) {
|
|
// use the last entry on the Indexes array for the current index
|
|
indx = (*aIndexes)[aIndexes->Length()-1];
|
|
} else {
|
|
indx = mCachedIndex;
|
|
}
|
|
|
|
// reverify that the index of the current node hasn't changed. not super
|
|
// cheap, but a lot cheaper than IndexOf(), and still O(1). ignore result
|
|
// this time - the index may now be out of range.
|
|
if (indx >= 0) {
|
|
sibling = parent->GetChildAt(indx);
|
|
NS_WARNING_ASSERTION(sibling, "GetChildAt returned null");
|
|
}
|
|
|
|
if (sibling != node) {
|
|
// someone changed our index - find the new index the painful way
|
|
indx = parent->IndexOf(node);
|
|
NS_WARNING_ASSERTION(indx >= 0, "bad indx");
|
|
}
|
|
|
|
// indx is now canonically correct
|
|
if (indx && (sibling = parent->GetChildAt(--indx))) {
|
|
// update cache
|
|
if (aIndexes && !aIndexes->IsEmpty()) {
|
|
// replace an entry on the index stack
|
|
aIndexes->ElementAt(aIndexes->Length()-1) = indx;
|
|
} else {
|
|
mCachedIndex = indx;
|
|
}
|
|
|
|
// prev node is sibling's "deep right" child
|
|
return GetDeepLastChild(sibling, aIndexes);
|
|
}
|
|
|
|
// else it's the parent, update cache
|
|
if (aIndexes && !aIndexes->IsEmpty()) {
|
|
// pop an entry off the index stack
|
|
aIndexes->RemoveElementAt(aIndexes->Length()-1);
|
|
} else {
|
|
// this might be wrong, but we are better off guessing
|
|
mCachedIndex = 0;
|
|
}
|
|
return parent;
|
|
}
|
|
|
|
// post-order
|
|
int32_t numChildren = node->GetChildCount();
|
|
NS_WARNING_ASSERTION(numChildren >= 0, "no children");
|
|
|
|
// if it has children then prev node is last child
|
|
if (numChildren) {
|
|
nsIContent* lastChild = node->GetLastChild();
|
|
NS_WARNING_ASSERTION(lastChild, "GetLastChild returned null");
|
|
numChildren--;
|
|
|
|
// update cache
|
|
if (aIndexes) {
|
|
// push an entry on the index stack
|
|
aIndexes->AppendElement(numChildren);
|
|
} else {
|
|
mCachedIndex = numChildren;
|
|
}
|
|
|
|
return lastChild;
|
|
}
|
|
|
|
// else prev sibling is previous
|
|
return GetPrevSibling(node, aIndexes);
|
|
}
|
|
|
|
/******************************************************
|
|
* ContentIterator routines
|
|
******************************************************/
|
|
|
|
void
|
|
nsContentIterator::First()
|
|
{
|
|
if (mFirst) {
|
|
mozilla::DebugOnly<nsresult> rv = PositionAt(mFirst);
|
|
NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to position iterator!");
|
|
}
|
|
|
|
mIsDone = mFirst == nullptr;
|
|
}
|
|
|
|
|
|
void
|
|
nsContentIterator::Last()
|
|
{
|
|
// Note that mLast can be nullptr if MakeEmpty() is called in Init() since
|
|
// at that time, Init() returns NS_OK.
|
|
if (!mLast) {
|
|
MOZ_ASSERT(mIsDone);
|
|
return;
|
|
}
|
|
|
|
mozilla::DebugOnly<nsresult> rv = PositionAt(mLast);
|
|
NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to position iterator!");
|
|
|
|
mIsDone = mLast == nullptr;
|
|
}
|
|
|
|
|
|
void
|
|
nsContentIterator::Next()
|
|
{
|
|
if (mIsDone || NS_WARN_IF(!mCurNode)) {
|
|
return;
|
|
}
|
|
|
|
if (mCurNode == mLast) {
|
|
mIsDone = true;
|
|
return;
|
|
}
|
|
|
|
mCurNode = NextNode(mCurNode, &mIndexes);
|
|
}
|
|
|
|
|
|
void
|
|
nsContentIterator::Prev()
|
|
{
|
|
if (NS_WARN_IF(mIsDone) || NS_WARN_IF(!mCurNode)) {
|
|
return;
|
|
}
|
|
|
|
if (mCurNode == mFirst) {
|
|
mIsDone = true;
|
|
return;
|
|
}
|
|
|
|
mCurNode = PrevNode(mCurNode, &mIndexes);
|
|
}
|
|
|
|
|
|
bool
|
|
nsContentIterator::IsDone()
|
|
{
|
|
return mIsDone;
|
|
}
|
|
|
|
|
|
// Keeping arrays of indexes for the stack of nodes makes PositionAt
|
|
// interesting...
|
|
nsresult
|
|
nsContentIterator::PositionAt(nsINode* aCurNode)
|
|
{
|
|
if (NS_WARN_IF(!aCurNode)) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
|
|
nsINode* newCurNode = aCurNode;
|
|
nsINode* tempNode = mCurNode;
|
|
|
|
mCurNode = aCurNode;
|
|
// take an early out if this doesn't actually change the position
|
|
if (mCurNode == tempNode) {
|
|
mIsDone = false; // paranoia
|
|
return NS_OK;
|
|
}
|
|
|
|
// Check to see if the node falls within the traversal range.
|
|
|
|
nsINode* firstNode = mFirst;
|
|
nsINode* lastNode = mLast;
|
|
int32_t firstOffset = 0, lastOffset = 0;
|
|
|
|
if (firstNode && lastNode) {
|
|
if (mPre) {
|
|
firstNode = NodeToParentOffset(mFirst, &firstOffset);
|
|
NS_WARNING_ASSERTION(firstNode, "NodeToParentOffset returned null");
|
|
NS_WARNING_ASSERTION(firstOffset >= 0, "bad firstOffset");
|
|
|
|
if (lastNode->GetChildCount()) {
|
|
lastOffset = 0;
|
|
} else {
|
|
lastNode = NodeToParentOffset(mLast, &lastOffset);
|
|
NS_WARNING_ASSERTION(lastNode, "NodeToParentOffset returned null");
|
|
NS_WARNING_ASSERTION(lastOffset >= 0, "bad lastOffset");
|
|
++lastOffset;
|
|
}
|
|
} else {
|
|
uint32_t numChildren = firstNode->GetChildCount();
|
|
|
|
if (numChildren) {
|
|
firstOffset = numChildren;
|
|
NS_WARNING_ASSERTION(firstOffset >= 0, "bad firstOffset");
|
|
} else {
|
|
firstNode = NodeToParentOffset(mFirst, &firstOffset);
|
|
NS_WARNING_ASSERTION(firstNode, "NodeToParentOffset returned null");
|
|
NS_WARNING_ASSERTION(firstOffset >= 0, "bad firstOffset");
|
|
}
|
|
|
|
lastNode = NodeToParentOffset(mLast, &lastOffset);
|
|
NS_WARNING_ASSERTION(lastNode, "NodeToParentOffset returned null");
|
|
NS_WARNING_ASSERTION(lastOffset >= 0, "bad lastOffset");
|
|
++lastOffset;
|
|
}
|
|
}
|
|
|
|
// The end positions are always in the range even if it has no parent. We
|
|
// need to allow that or 'iter->Init(root)' would assert in Last() or First()
|
|
// for example, bug 327694.
|
|
if (mFirst != mCurNode && mLast != mCurNode &&
|
|
(NS_WARN_IF(!firstNode) || NS_WARN_IF(!lastNode) ||
|
|
NS_WARN_IF(!NodeIsInTraversalRange(mCurNode, mPre,
|
|
firstNode, firstOffset,
|
|
lastNode, lastOffset)))) {
|
|
mIsDone = true;
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// We can be at ANY node in the sequence. Need to regenerate the array of
|
|
// indexes back to the root or common parent!
|
|
AutoTArray<nsINode*, 8> oldParentStack;
|
|
AutoTArray<int32_t, 8> newIndexes;
|
|
|
|
// Get a list of the parents up to the root, then compare the new node with
|
|
// entries in that array until we find a match (lowest common ancestor). If
|
|
// no match, use IndexOf, take the parent, and repeat. This avoids using
|
|
// IndexOf() N times on possibly large arrays. We still end up doing it a
|
|
// fair bit. It's better to use Clone() if possible.
|
|
|
|
// we know the depth we're down (though we may not have started at the top).
|
|
oldParentStack.SetCapacity(mIndexes.Length() + 1);
|
|
|
|
// We want to loop mIndexes.Length() + 1 times here, because we want to make
|
|
// sure we include mCommonParent in the oldParentStack, for use in the next
|
|
// for loop, and mIndexes only has entries for nodes from tempNode up through
|
|
// an ancestor of tempNode that's a child of mCommonParent.
|
|
for (int32_t i = mIndexes.Length() + 1; i > 0 && tempNode; i--) {
|
|
// Insert at head since we're walking up
|
|
oldParentStack.InsertElementAt(0, tempNode);
|
|
|
|
nsINode* parent = tempNode->GetParentNode();
|
|
|
|
if (NS_WARN_IF(!parent)) {
|
|
// this node has no parent, and thus no index
|
|
break;
|
|
}
|
|
|
|
if (parent == mCurNode) {
|
|
// The position was moved to a parent of the current position. All we
|
|
// need to do is drop some indexes. Shortcut here.
|
|
mIndexes.RemoveElementsAt(mIndexes.Length() - oldParentStack.Length(),
|
|
oldParentStack.Length());
|
|
mIsDone = false;
|
|
return NS_OK;
|
|
}
|
|
tempNode = parent;
|
|
}
|
|
|
|
// Ok. We have the array of old parents. Look for a match.
|
|
while (newCurNode) {
|
|
nsINode* parent = newCurNode->GetParentNode();
|
|
|
|
if (NS_WARN_IF(!parent)) {
|
|
// this node has no parent, and thus no index
|
|
break;
|
|
}
|
|
|
|
int32_t indx = parent->IndexOf(newCurNode);
|
|
NS_WARNING_ASSERTION(indx >= 0, "bad indx");
|
|
|
|
// insert at the head!
|
|
newIndexes.InsertElementAt(0, indx);
|
|
|
|
// look to see if the parent is in the stack
|
|
indx = oldParentStack.IndexOf(parent);
|
|
if (indx >= 0) {
|
|
// ok, the parent IS on the old stack! Rework things. We want
|
|
// newIndexes to replace all nodes equal to or below the match. Note
|
|
// that index oldParentStack.Length() - 1 is the last node, which is one
|
|
// BELOW the last index in the mIndexes stack. In other words, we want
|
|
// to remove elements starting at index (indx + 1).
|
|
int32_t numToDrop = oldParentStack.Length() - (1 + indx);
|
|
if (numToDrop > 0) {
|
|
mIndexes.RemoveElementsAt(mIndexes.Length() - numToDrop, numToDrop);
|
|
}
|
|
mIndexes.AppendElements(newIndexes);
|
|
|
|
break;
|
|
}
|
|
newCurNode = parent;
|
|
}
|
|
|
|
// phew!
|
|
|
|
mIsDone = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsINode*
|
|
nsContentIterator::GetCurrentNode()
|
|
{
|
|
if (mIsDone) {
|
|
return nullptr;
|
|
}
|
|
|
|
NS_ASSERTION(mCurNode, "Null current node in an iterator that's not done!");
|
|
|
|
return mCurNode;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*====================================================================================*/
|
|
/*====================================================================================*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************
|
|
* nsContentSubtreeIterator
|
|
******************************************************/
|
|
|
|
|
|
/*
|
|
* A simple iterator class for traversing the content in "top subtree" order
|
|
*/
|
|
class nsContentSubtreeIterator : public nsContentIterator
|
|
{
|
|
public:
|
|
nsContentSubtreeIterator() : nsContentIterator(false) {}
|
|
|
|
NS_DECL_ISUPPORTS_INHERITED
|
|
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsContentSubtreeIterator, nsContentIterator)
|
|
|
|
// nsContentIterator overrides ------------------------------
|
|
|
|
virtual nsresult Init(nsINode* aRoot) override;
|
|
|
|
virtual nsresult Init(nsIDOMRange* aRange) override;
|
|
|
|
virtual void Next() override;
|
|
|
|
virtual void Prev() override;
|
|
|
|
virtual nsresult PositionAt(nsINode* aCurNode) override;
|
|
|
|
// Must override these because we don't do PositionAt
|
|
virtual void First() override;
|
|
|
|
// Must override these because we don't do PositionAt
|
|
virtual void Last() override;
|
|
|
|
protected:
|
|
virtual ~nsContentSubtreeIterator() {}
|
|
|
|
// Returns the highest inclusive ancestor of aNode that's in the range
|
|
// (possibly aNode itself). Returns null if aNode is null, or is not itself
|
|
// in the range. A node is in the range if (node, 0) comes strictly after
|
|
// the range endpoint, and (node, node.length) comes strictly before it, so
|
|
// the range's start and end nodes will never be considered "in" it.
|
|
nsIContent* GetTopAncestorInRange(nsINode* aNode);
|
|
|
|
// no copy's or assigns FIX ME
|
|
nsContentSubtreeIterator(const nsContentSubtreeIterator&);
|
|
nsContentSubtreeIterator& operator=(const nsContentSubtreeIterator&);
|
|
|
|
virtual void LastRelease() override;
|
|
|
|
RefPtr<nsRange> mRange;
|
|
|
|
// these arrays all typically are used and have elements
|
|
AutoTArray<nsIContent*, 8> mEndNodes;
|
|
AutoTArray<int32_t, 8> mEndOffsets;
|
|
};
|
|
|
|
NS_IMPL_ADDREF_INHERITED(nsContentSubtreeIterator, nsContentIterator)
|
|
NS_IMPL_RELEASE_INHERITED(nsContentSubtreeIterator, nsContentIterator)
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsContentSubtreeIterator)
|
|
NS_INTERFACE_MAP_END_INHERITING(nsContentIterator)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_INHERITED(nsContentSubtreeIterator, nsContentIterator,
|
|
mRange)
|
|
|
|
void
|
|
nsContentSubtreeIterator::LastRelease()
|
|
{
|
|
mRange = nullptr;
|
|
nsContentIterator::LastRelease();
|
|
}
|
|
|
|
/******************************************************
|
|
* repository cruft
|
|
******************************************************/
|
|
|
|
already_AddRefed<nsIContentIterator>
|
|
NS_NewContentSubtreeIterator()
|
|
{
|
|
nsCOMPtr<nsIContentIterator> iter = new nsContentSubtreeIterator();
|
|
return iter.forget();
|
|
}
|
|
|
|
|
|
|
|
/******************************************************
|
|
* Init routines
|
|
******************************************************/
|
|
|
|
|
|
nsresult
|
|
nsContentSubtreeIterator::Init(nsINode* aRoot)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsContentSubtreeIterator::Init(nsIDOMRange* aRange)
|
|
{
|
|
MOZ_ASSERT(aRange);
|
|
|
|
mIsDone = false;
|
|
|
|
mRange = static_cast<nsRange*>(aRange);
|
|
|
|
// get the start node and offset, convert to nsINode
|
|
mCommonParent = mRange->GetCommonAncestor();
|
|
nsINode* startParent = mRange->GetStartParent();
|
|
int32_t startOffset = mRange->StartOffset();
|
|
nsINode* endParent = mRange->GetEndParent();
|
|
int32_t endOffset = mRange->EndOffset();
|
|
MOZ_ASSERT(mCommonParent && startParent && endParent);
|
|
// Bug 767169
|
|
MOZ_ASSERT(uint32_t(startOffset) <= startParent->Length() &&
|
|
uint32_t(endOffset) <= endParent->Length());
|
|
|
|
// short circuit when start node == end node
|
|
if (startParent == endParent) {
|
|
nsINode* child = startParent->GetFirstChild();
|
|
|
|
if (!child || startOffset == endOffset) {
|
|
// Text node, empty container, or collapsed
|
|
MakeEmpty();
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
// cache ancestors
|
|
nsContentUtils::GetAncestorsAndOffsets(endParent->AsDOMNode(), endOffset,
|
|
&mEndNodes, &mEndOffsets);
|
|
|
|
nsIContent* firstCandidate = nullptr;
|
|
nsIContent* lastCandidate = nullptr;
|
|
|
|
// find first node in range
|
|
int32_t offset = mRange->StartOffset();
|
|
|
|
nsINode* node = nullptr;
|
|
if (!startParent->GetChildCount()) {
|
|
// no children, start at the node itself
|
|
node = startParent;
|
|
} else {
|
|
nsIContent* child = startParent->GetChildAt(offset);
|
|
if (!child) {
|
|
// offset after last child
|
|
node = startParent;
|
|
} else {
|
|
firstCandidate = child;
|
|
}
|
|
}
|
|
|
|
if (!firstCandidate) {
|
|
// then firstCandidate is next node after node
|
|
firstCandidate = GetNextSibling(node);
|
|
|
|
if (!firstCandidate) {
|
|
MakeEmpty();
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
firstCandidate = GetDeepFirstChild(firstCandidate);
|
|
|
|
// confirm that this first possible contained node is indeed contained. Else
|
|
// we have a range that does not fully contain any node.
|
|
|
|
bool nodeBefore, nodeAfter;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
nsRange::CompareNodeToRange(firstCandidate, mRange, &nodeBefore, &nodeAfter));
|
|
|
|
if (nodeBefore || nodeAfter) {
|
|
MakeEmpty();
|
|
return NS_OK;
|
|
}
|
|
|
|
// cool, we have the first node in the range. Now we walk up its ancestors
|
|
// to find the most senior that is still in the range. That's the real first
|
|
// node.
|
|
mFirst = GetTopAncestorInRange(firstCandidate);
|
|
|
|
// now to find the last node
|
|
offset = mRange->EndOffset();
|
|
int32_t numChildren = endParent->GetChildCount();
|
|
|
|
if (offset > numChildren) {
|
|
// Can happen for text nodes
|
|
offset = numChildren;
|
|
}
|
|
if (!offset || !numChildren) {
|
|
node = endParent;
|
|
} else {
|
|
lastCandidate = endParent->GetChildAt(--offset);
|
|
NS_ASSERTION(lastCandidate,
|
|
"tree traversal trouble in nsContentSubtreeIterator::Init");
|
|
}
|
|
|
|
if (!lastCandidate) {
|
|
// then lastCandidate is prev node before node
|
|
lastCandidate = GetPrevSibling(node);
|
|
}
|
|
|
|
if (!lastCandidate) {
|
|
MakeEmpty();
|
|
return NS_OK;
|
|
}
|
|
|
|
lastCandidate = GetDeepLastChild(lastCandidate);
|
|
|
|
// confirm that this last possible contained node is indeed contained. Else
|
|
// we have a range that does not fully contain any node.
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
nsRange::CompareNodeToRange(lastCandidate, mRange, &nodeBefore, &nodeAfter));
|
|
|
|
if (nodeBefore || nodeAfter) {
|
|
MakeEmpty();
|
|
return NS_OK;
|
|
}
|
|
|
|
// cool, we have the last node in the range. Now we walk up its ancestors to
|
|
// find the most senior that is still in the range. That's the real first
|
|
// node.
|
|
mLast = GetTopAncestorInRange(lastCandidate);
|
|
|
|
mCurNode = mFirst;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/****************************************************************
|
|
* nsContentSubtreeIterator overrides of ContentIterator routines
|
|
****************************************************************/
|
|
|
|
// we can't call PositionAt in a subtree iterator...
|
|
void
|
|
nsContentSubtreeIterator::First()
|
|
{
|
|
mIsDone = mFirst == nullptr;
|
|
|
|
mCurNode = mFirst;
|
|
}
|
|
|
|
// we can't call PositionAt in a subtree iterator...
|
|
void
|
|
nsContentSubtreeIterator::Last()
|
|
{
|
|
mIsDone = mLast == nullptr;
|
|
|
|
mCurNode = mLast;
|
|
}
|
|
|
|
|
|
void
|
|
nsContentSubtreeIterator::Next()
|
|
{
|
|
if (mIsDone || !mCurNode) {
|
|
return;
|
|
}
|
|
|
|
if (mCurNode == mLast) {
|
|
mIsDone = true;
|
|
return;
|
|
}
|
|
|
|
nsINode* nextNode = GetNextSibling(mCurNode);
|
|
NS_ASSERTION(nextNode, "No next sibling!?! This could mean deadlock!");
|
|
|
|
int32_t i = mEndNodes.IndexOf(nextNode);
|
|
while (i != -1) {
|
|
// as long as we are finding ancestors of the endpoint of the range,
|
|
// dive down into their children
|
|
nextNode = nextNode->GetFirstChild();
|
|
NS_ASSERTION(nextNode, "Iterator error, expected a child node!");
|
|
|
|
// should be impossible to get a null pointer. If we went all the way
|
|
// down the child chain to the bottom without finding an interior node,
|
|
// then the previous node should have been the last, which was
|
|
// was tested at top of routine.
|
|
i = mEndNodes.IndexOf(nextNode);
|
|
}
|
|
|
|
mCurNode = nextNode;
|
|
|
|
// This shouldn't be needed, but since our selection code can put us
|
|
// in a situation where mLast is in generated content, we need this
|
|
// to stop the iterator when we've walked past past the last node!
|
|
mIsDone = mCurNode == nullptr;
|
|
}
|
|
|
|
|
|
void
|
|
nsContentSubtreeIterator::Prev()
|
|
{
|
|
// Prev should be optimized to use the mStartNodes, just as Next
|
|
// uses mEndNodes.
|
|
if (mIsDone || !mCurNode) {
|
|
return;
|
|
}
|
|
|
|
if (mCurNode == mFirst) {
|
|
mIsDone = true;
|
|
return;
|
|
}
|
|
|
|
// If any of these function calls return null, so will all succeeding ones,
|
|
// so mCurNode will wind up set to null.
|
|
nsINode* prevNode = GetDeepFirstChild(mCurNode);
|
|
|
|
prevNode = PrevNode(prevNode);
|
|
|
|
prevNode = GetDeepLastChild(prevNode);
|
|
|
|
mCurNode = GetTopAncestorInRange(prevNode);
|
|
|
|
// This shouldn't be needed, but since our selection code can put us
|
|
// in a situation where mFirst is in generated content, we need this
|
|
// to stop the iterator when we've walked past past the first node!
|
|
mIsDone = mCurNode == nullptr;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsContentSubtreeIterator::PositionAt(nsINode* aCurNode)
|
|
{
|
|
NS_ERROR("Not implemented!");
|
|
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
/****************************************************************
|
|
* nsContentSubtreeIterator helper routines
|
|
****************************************************************/
|
|
|
|
nsIContent*
|
|
nsContentSubtreeIterator::GetTopAncestorInRange(nsINode* aNode)
|
|
{
|
|
if (!aNode || !aNode->GetParentNode()) {
|
|
return nullptr;
|
|
}
|
|
|
|
// aNode has a parent, so it must be content.
|
|
nsIContent* content = aNode->AsContent();
|
|
|
|
// sanity check: aNode is itself in the range
|
|
bool nodeBefore, nodeAfter;
|
|
nsresult res = nsRange::CompareNodeToRange(aNode, mRange,
|
|
&nodeBefore, &nodeAfter);
|
|
NS_ASSERTION(NS_SUCCEEDED(res) && !nodeBefore && !nodeAfter,
|
|
"aNode isn't in mRange, or something else weird happened");
|
|
if (NS_FAILED(res) || nodeBefore || nodeAfter) {
|
|
return nullptr;
|
|
}
|
|
|
|
while (content) {
|
|
nsIContent* parent = content->GetParent();
|
|
// content always has a parent. If its parent is the root, however --
|
|
// i.e., either it's not content, or it is content but its own parent is
|
|
// null -- then we're finished, since we don't go up to the root.
|
|
//
|
|
// We have to special-case this because CompareNodeToRange treats the root
|
|
// node differently -- see bug 765205.
|
|
if (!parent || !parent->GetParentNode()) {
|
|
return content;
|
|
}
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
nsRange::CompareNodeToRange(parent, mRange, &nodeBefore, &nodeAfter));
|
|
|
|
if (nodeBefore || nodeAfter) {
|
|
return content;
|
|
}
|
|
content = parent;
|
|
}
|
|
|
|
MOZ_CRASH("This should only be possible if aNode was null");
|
|
}
|