/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=4 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/. */ /* * Implementation of DOM Traversal's TreeWalker */ #include "mozilla/dom/TreeWalker.h" #include "nsIContent.h" #include "nsError.h" #include "nsINode.h" #include "nsContentUtils.h" #include "mozilla/dom/NodeFilterBinding.h" #include "mozilla/dom/TreeWalkerBinding.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() { /* destructor code */ } /* * 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 TreeWalker::ParentNode(ErrorResult& aResult) { nsCOMPtr 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 TreeWalker::FirstChild(ErrorResult& aResult) { return FirstChildInternal(false, aResult); } already_AddRefed TreeWalker::LastChild(ErrorResult& aResult) { return FirstChildInternal(true, aResult); } already_AddRefed TreeWalker::PreviousSibling(ErrorResult& aResult) { return NextSiblingInternal(true, aResult); } already_AddRefed TreeWalker::NextSibling(ErrorResult& aResult) { return NextSiblingInternal(false, aResult); } already_AddRefed TreeWalker::PreviousNode(ErrorResult& aResult) { nsCOMPtr 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; } already_AddRefed TreeWalker::NextNode(ErrorResult& aResult) { int16_t filtered = NodeFilter_Binding::FILTER_ACCEPT; // pre-init for inner loop nsCOMPtr node = mCurrentNode; while (1) { 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 node.forget(); } } 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 node.forget(); } } 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 TreeWalker::FirstChildInternal(bool aReversed, ErrorResult& aResult) { nsCOMPtr 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 TreeWalker::NextSiblingInternal( bool aReversed, ErrorResult& aResult) { nsCOMPtr node = mCurrentNode; if (node == mRoot) { return nullptr; } while (1) { 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 aGivenProto, JS::MutableHandle aReflector) { return TreeWalker_Binding::Wrap(aCx, this, aGivenProto, aReflector); } } // namespace mozilla::dom