/* -*- 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/RangeUtils.h" #include "mozilla/Assertions.h" #include "mozilla/dom/AbstractRange.h" #include "mozilla/dom/ShadowRoot.h" #include "nsContentUtils.h" namespace mozilla { using namespace dom; template bool RangeUtils::IsValidPoints(const RangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary); template bool RangeUtils::IsValidPoints(const RangeBoundary& aStartBoundary, const RawRangeBoundary& aEndBoundary); template bool RangeUtils::IsValidPoints(const RawRangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary); template bool RangeUtils::IsValidPoints(const RawRangeBoundary& aStartBoundary, const RawRangeBoundary& aEndBoundary); // static nsINode* RangeUtils::ComputeRootNode(nsINode* aNode) { if (!aNode) { return nullptr; } if (aNode->IsContent()) { if (aNode->NodeInfo()->NameAtom() == nsGkAtoms::documentTypeNodeName) { return nullptr; } nsIContent* content = aNode->AsContent(); // If the node is in a shadow tree then the ShadowRoot is the root. // // FIXME(emilio): Should this be after the NAC check below? We can have NAC // inside Shadow DOM which will peek this path rather than the one below. if (ShadowRoot* containingShadow = content->GetContainingShadow()) { return containingShadow; } // If the node is in NAC, then the NAC parent should be the root. if (nsINode* root = content->GetClosestNativeAnonymousSubtreeRootParent()) { return root; } } // Elements etc. must be in document or in document fragment, // text nodes in document, in document fragment or in attribute. if (nsINode* root = aNode->GetUncomposedDoc()) { return root; } NS_ASSERTION(!aNode->SubtreeRoot()->IsDocument(), "GetUncomposedDoc should have returned a doc"); // We allow this because of backward compatibility. return aNode->SubtreeRoot(); } // static template bool RangeUtils::IsValidPoints( const RangeBoundaryBase& aStartBoundary, const RangeBoundaryBase& aEndBoundary) { // Use NS_WARN_IF() only for the cases where the arguments are unexpected. if (NS_WARN_IF(!aStartBoundary.IsSetAndValid()) || NS_WARN_IF(!aEndBoundary.IsSetAndValid())) { return false; } // Otherwise, don't use NS_WARN_IF() for preventing to make console messy. // Instead, check one by one since it is easier to catch the error reason // with debugger. if (ComputeRootNode(aStartBoundary.Container()) != ComputeRootNode(aEndBoundary.Container())) { return false; } const Maybe order = nsContentUtils::ComparePoints(aStartBoundary, aEndBoundary); if (!order) { MOZ_ASSERT_UNREACHABLE(); return false; } return *order != 1; } // static Maybe RangeUtils::IsNodeContainedInRange(nsINode& aNode, AbstractRange* aAbstractRange) { bool nodeIsBeforeRange{false}; bool nodeIsAfterRange{false}; const nsresult rv = CompareNodeToRange(&aNode, aAbstractRange, &nodeIsBeforeRange, &nodeIsAfterRange); if (NS_FAILED(rv)) { return Nothing(); } return Some(!nodeIsBeforeRange && !nodeIsAfterRange); } // Utility routine to detect if a content node is completely contained in a // range If outNodeBefore is returned true, then the node starts before the // range does. If outNodeAfter is returned true, then the node ends after the // range does. Note that both of the above might be true. If neither are true, // the node is contained inside of the range. // XXX - callers responsibility to ensure node in same doc as range! // static nsresult RangeUtils::CompareNodeToRange(nsINode* aNode, AbstractRange* aAbstractRange, bool* aNodeIsBeforeRange, bool* aNodeIsAfterRange) { MOZ_ASSERT(aNodeIsBeforeRange); MOZ_ASSERT(aNodeIsAfterRange); if (NS_WARN_IF(!aNode) || NS_WARN_IF(!aAbstractRange) || NS_WARN_IF(!aAbstractRange->IsPositioned())) { return NS_ERROR_INVALID_ARG; } // create a pair of dom points that expresses location of node: // NODE(start), NODE(end) // Let incoming range be: // {RANGE(start), RANGE(end)} // if (RANGE(start) <= NODE(start)) and (RANGE(end) => NODE(end)) // then the Node is contained (completely) by the Range. // gather up the dom point info int32_t nodeStart, nodeEnd; nsINode* parent = aNode->GetParentNode(); if (!parent) { // can't make a parent/offset pair to represent start or // end of the root node, because it has no parent. // so instead represent it by (node,0) and (node,numChildren) parent = aNode; nodeStart = 0; uint32_t childCount = aNode->GetChildCount(); MOZ_ASSERT(childCount <= INT32_MAX, "There shouldn't be over INT32_MAX children"); nodeEnd = static_cast(childCount); } else { nodeStart = parent->ComputeIndexOf(aNode); nodeEnd = nodeStart + 1; MOZ_ASSERT(nodeStart < nodeEnd, "nodeStart shouldn't be INT32_MAX"); } // XXX nsContentUtils::ComparePoints() may be expensive. If some callers // just want one of aNodeIsBeforeRange or aNodeIsAfterRange, we can // skip the other comparison. // In the ComparePoints calls below we use a container & offset instead of // a range boundary because the range boundary constructor warns if you pass // in a -1 offset and the ComputeIndexOf call above can return -1 if aNode // is native anonymous content. ComparePoints has comments about offsets // being -1 and it seems to deal with it, or at least we aren't aware of any // problems arising because of it. We don't have a better idea how to get // rid of the warning without much larger changes so we do this just to // silence the warning. (Bug 1438996) // is RANGE(start) <= NODE(start) ? Maybe order = nsContentUtils::ComparePoints( aAbstractRange->StartRef().Container(), *aAbstractRange->StartRef().Offset( RangeBoundary::OffsetFilter::kValidOrInvalidOffsets), parent, nodeStart); if (NS_WARN_IF(!order)) { return NS_ERROR_DOM_WRONG_DOCUMENT_ERR; } *aNodeIsBeforeRange = *order > 0; // is RANGE(end) >= NODE(end) ? order = nsContentUtils::ComparePoints( aAbstractRange->EndRef().Container(), *aAbstractRange->EndRef().Offset( RangeBoundary::OffsetFilter::kValidOrInvalidOffsets), parent, nodeEnd); if (NS_WARN_IF(!order)) { return NS_ERROR_DOM_WRONG_DOCUMENT_ERR; } *aNodeIsAfterRange = *order < 0; return NS_OK; } } // namespace mozilla