Files
mayankleoboy1 2c390be335 Bug 2061082 - Avoid strong references when walking an unfiltered TreeWalker. r=smaug
TreeWalker::NextNode() holds every node it visits in an nsCOMPtr because
TestNode() can invoke a NodeFilter, and that script can remove nodes from the
tree. nsINode uses a cycle collected refcount, so each step costs several
AddRef/Release pairs; on the bug 2061082 testcase these are around two thirds
of NextNode()'s time.

When mFilter is null TestNode() only reads mInAcceptNode, the node type and
mWhatToShow, so it cannot run script, and the loop performs no reference count
decrements until its final assignment to mCurrentNode. Nothing can therefore
mutate the tree or destroy a node mid-walk, and raw pointers are sufficient.

Templatize the body on the pointer type so both variants share one copy of the
traversal logic, and dispatch on mFilter. This removes one AddRef and one
Release of the five refcount operations performed per unfiltered step.

The same reasoning applies to the other traversal methods, but only NextNode()
was measured, so they are left alone.

Differential Revision: https://phabricator.services.mozilla.com/D314243
2026-08-07 03:19:59 +00:00

346 lines
8.6 KiB
C++

/* 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/. */
/*
* Implementation of DOM Traversal's TreeWalker
*/
#include "mozilla/dom/TreeWalker.h"
#include "mozilla/dom/NodeFilterBinding.h"
#include "mozilla/dom/TreeWalkerBinding.h"
#include "nsContentUtils.h"
#include "nsError.h"
#include "nsIContent.h"
#include "nsINode.h"
namespace mozilla::dom {
/*
* Factories, constructors and destructors
*/
TreeWalker::TreeWalker(nsINode* aRoot, uint32_t aWhatToShow,
NodeFilter* aFilter)
: nsTraversal(aRoot, aWhatToShow, aFilter), mCurrentNode(aRoot) {}
TreeWalker::~TreeWalker() = default;
/*
* nsISupports and cycle collection stuff
*/
NS_IMPL_CYCLE_COLLECTION(TreeWalker, mFilter, mCurrentNode, mRoot)
// QueryInterface implementation for TreeWalker
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TreeWalker)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
// Have to pass in dom::TreeWalker because a11y has an a11y::TreeWalker that
// passes TreeWalker so refcount logging would get confused on the name
// collision.
NS_IMPL_CYCLE_COLLECTING_ADDREF(dom::TreeWalker)
NS_IMPL_CYCLE_COLLECTING_RELEASE(dom::TreeWalker)
void TreeWalker::SetCurrentNode(nsINode& aNode, ErrorResult& aResult) {
aResult = nsContentUtils::CheckSameOrigin(mRoot, &aNode);
if (aResult.Failed()) {
return;
}
mCurrentNode = &aNode;
}
already_AddRefed<nsINode> TreeWalker::ParentNode(ErrorResult& aResult) {
nsCOMPtr<nsINode> node = mCurrentNode;
while (node && node != mRoot) {
node = node->GetParentNode();
if (node) {
int16_t filtered = TestNode(node, aResult);
if (aResult.Failed()) {
return nullptr;
}
if (filtered == NodeFilter_Binding::FILTER_ACCEPT) {
mCurrentNode = node;
return node.forget();
}
}
}
return nullptr;
}
already_AddRefed<nsINode> TreeWalker::FirstChild(ErrorResult& aResult) {
return FirstChildInternal(false, aResult);
}
already_AddRefed<nsINode> TreeWalker::LastChild(ErrorResult& aResult) {
return FirstChildInternal(true, aResult);
}
already_AddRefed<nsINode> TreeWalker::PreviousSibling(ErrorResult& aResult) {
return NextSiblingInternal(true, aResult);
}
already_AddRefed<nsINode> TreeWalker::NextSibling(ErrorResult& aResult) {
return NextSiblingInternal(false, aResult);
}
already_AddRefed<nsINode> TreeWalker::PreviousNode(ErrorResult& aResult) {
nsCOMPtr<nsINode> node = mCurrentNode;
while (node != mRoot) {
while (nsINode* previousSibling = node->GetPreviousSibling()) {
node = previousSibling;
int16_t filtered = TestNode(node, aResult);
if (aResult.Failed()) {
return nullptr;
}
nsINode* lastChild;
while (filtered != NodeFilter_Binding::FILTER_REJECT &&
(lastChild = node->GetLastChild())) {
node = lastChild;
filtered = TestNode(node, aResult);
if (aResult.Failed()) {
return nullptr;
}
}
if (filtered == NodeFilter_Binding::FILTER_ACCEPT) {
mCurrentNode = node;
return node.forget();
}
}
if (node == mRoot) {
break;
}
node = node->GetParentNode();
if (!node) {
break;
}
int16_t filtered = TestNode(node, aResult);
if (aResult.Failed()) {
return nullptr;
}
if (filtered == NodeFilter_Binding::FILTER_ACCEPT) {
mCurrentNode = node;
return node.forget();
}
}
return nullptr;
}
static already_AddRefed<nsINode> EnsureAddRefed(nsCOMPtr<nsINode>& aNode) {
return aNode.forget();
}
static already_AddRefed<nsINode> EnsureAddRefed(nsINode*& aNode) {
return do_AddRef(aNode);
}
already_AddRefed<nsINode> TreeWalker::NextNode(ErrorResult& aResult) {
// With no filter TestNode() cannot run script, and the loop performs no
// refcount decrements before its final assignment to mCurrentNode, so
// nothing can destroy a node mid-walk.
if (!mFilter) {
return NextNodeInternal<nsINode*>(aResult);
}
return NextNodeInternal<nsCOMPtr<nsINode>>(aResult);
}
template <typename NodePtr>
already_AddRefed<nsINode> TreeWalker::NextNodeInternal(ErrorResult& aResult) {
int16_t filtered =
NodeFilter_Binding::FILTER_ACCEPT; // pre-init for inner loop
NodePtr node = mCurrentNode;
while (true) {
nsINode* firstChild;
while (filtered != NodeFilter_Binding::FILTER_REJECT &&
(firstChild = node->GetFirstChild())) {
node = firstChild;
filtered = TestNode(node, aResult);
if (aResult.Failed()) {
return nullptr;
}
if (filtered == NodeFilter_Binding::FILTER_ACCEPT) {
// Node found
mCurrentNode = node;
return EnsureAddRefed(node);
}
}
nsINode* sibling = nullptr;
nsINode* temp = node;
do {
if (temp == mRoot) break;
sibling = temp->GetNextSibling();
if (sibling) break;
temp = temp->GetParentNode();
} while (temp);
if (!sibling) break;
node = sibling;
// Found a sibling. Either ours or ancestor's
filtered = TestNode(node, aResult);
if (aResult.Failed()) {
return nullptr;
}
if (filtered == NodeFilter_Binding::FILTER_ACCEPT) {
// Node found
mCurrentNode = node;
return EnsureAddRefed(node);
}
}
return nullptr;
}
/*
* TreeWalker helper functions
*/
/*
* Implements FirstChild and LastChild which only vary in which direction
* they search.
* @param aReversed Controls whether we search forwards or backwards
* @param aResult Whether we threw or not.
* @returns The desired node. Null if no child is found
*/
already_AddRefed<nsINode> TreeWalker::FirstChildInternal(bool aReversed,
ErrorResult& aResult) {
nsCOMPtr<nsINode> node =
aReversed ? mCurrentNode->GetLastChild() : mCurrentNode->GetFirstChild();
while (node) {
int16_t filtered = TestNode(node, aResult);
if (aResult.Failed()) {
return nullptr;
}
switch (filtered) {
case NodeFilter_Binding::FILTER_ACCEPT:
// Node found
mCurrentNode = node;
return node.forget();
case NodeFilter_Binding::FILTER_SKIP: {
nsINode* child =
aReversed ? node->GetLastChild() : node->GetFirstChild();
if (child) {
node = child;
continue;
}
break;
}
case NodeFilter_Binding::FILTER_REJECT:
// Keep searching
break;
}
do {
nsINode* sibling =
aReversed ? node->GetPreviousSibling() : node->GetNextSibling();
if (sibling) {
node = sibling;
break;
}
nsINode* parent = node->GetParentNode();
if (!parent || parent == mRoot || parent == mCurrentNode) {
return nullptr;
}
node = parent;
} while (node);
}
return nullptr;
}
/*
* Implements NextSibling and PreviousSibling which only vary in which
* direction they search.
* @param aReversed Controls whether we search forwards or backwards
* @param aResult Whether we threw or not.
* @returns The desired node. Null if no child is found
*/
already_AddRefed<nsINode> TreeWalker::NextSiblingInternal(
bool aReversed, ErrorResult& aResult) {
nsCOMPtr<nsINode> node = mCurrentNode;
if (node == mRoot) {
return nullptr;
}
while (true) {
nsINode* sibling =
aReversed ? node->GetPreviousSibling() : node->GetNextSibling();
while (sibling) {
node = sibling;
int16_t filtered = TestNode(node, aResult);
if (aResult.Failed()) {
return nullptr;
}
if (filtered == NodeFilter_Binding::FILTER_ACCEPT) {
// Node found
mCurrentNode = node;
return node.forget();
}
// If rejected or no children, try a sibling
if (filtered == NodeFilter_Binding::FILTER_REJECT ||
!(sibling =
aReversed ? node->GetLastChild() : node->GetFirstChild())) {
sibling =
aReversed ? node->GetPreviousSibling() : node->GetNextSibling();
}
}
node = node->GetParentNode();
if (!node || node == mRoot) {
return nullptr;
}
// Is parent transparent in filtered view?
int16_t filtered = TestNode(node, aResult);
if (aResult.Failed()) {
return nullptr;
}
if (filtered == NodeFilter_Binding::FILTER_ACCEPT) {
return nullptr;
}
}
}
bool TreeWalker::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
JS::MutableHandle<JSObject*> aReflector) {
return TreeWalker_Binding::Wrap(aCx, this, aGivenProto, aReflector);
}
} // namespace mozilla::dom