/* * Copyright (C) 2020 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "SimpleRange.h" #include "CharacterData.h" #include "Frame.h" #include "HTMLFrameOwnerElement.h" #include "NodeTraversal.h" #include "ShadowRoot.h" namespace WebCore { SimpleRange::SimpleRange(const BoundaryPoint& start, const BoundaryPoint& end) : start(start) , end(end) { } SimpleRange::SimpleRange(BoundaryPoint&& start, BoundaryPoint&& end) : start(WTFMove(start)) , end(WTFMove(end)) { } bool operator==(const SimpleRange& a, const SimpleRange& b) { return a.start == b.start && a.end == b.end; } std::optional makeRangeSelectingNode(Node& node) { auto parent = node.parentNode(); if (!parent) return std::nullopt; unsigned offset = node.computeNodeIndex(); return SimpleRange { { *parent, offset }, { *parent, offset + 1 } }; } SimpleRange makeRangeSelectingNodeContents(Node& node) { return { makeBoundaryPointBeforeNodeContents(node), makeBoundaryPointAfterNodeContents(node) }; } OffsetRange characterDataOffsetRange(const SimpleRange& range, const Node& node) { return { &node == range.start.container.ptr() ? range.start.offset : 0, &node == range.end.container.ptr() ? range.end.offset : std::numeric_limits::max() }; } static RefPtr firstIntersectingNode(const SimpleRange& range) { if (range.start.container->isCharacterDataNode()) return range.start.container.ptr(); if (auto child = range.start.container->traverseToChildAt(range.start.offset)) return child; return NodeTraversal::nextSkippingChildren(range.start.container); } static RefPtr nodePastLastIntersectingNode(const SimpleRange& range) { if (range.end.container->isCharacterDataNode()) return NodeTraversal::nextSkippingChildren(range.end.container); if (auto child = range.end.container->traverseToChildAt(range.end.offset)) return child; return NodeTraversal::nextSkippingChildren(range.end.container); } static RefPtr firstIntersectingNodeWithDeprecatedZeroOffsetStartQuirk(const SimpleRange& range) { if (range.start.container->isCharacterDataNode()) return range.start.container.ptr(); if (auto child = range.start.container->traverseToChildAt(range.start.offset)) return child; if (!range.start.offset) return range.start.container.ptr(); return NodeTraversal::nextSkippingChildren(range.start.container); } IntersectingNodeIterator::IntersectingNodeIterator(const SimpleRange& range) : m_node(firstIntersectingNode(range)) , m_pastLastNode(nodePastLastIntersectingNode(range)) { enforceEndInvariant(); } IntersectingNodeIterator::IntersectingNodeIterator(const SimpleRange& range, QuirkFlag) : m_node(firstIntersectingNodeWithDeprecatedZeroOffsetStartQuirk(range)) , m_pastLastNode(nodePastLastIntersectingNode(range)) { enforceEndInvariant(); } void IntersectingNodeIterator::advance() { ASSERT(m_node); m_node = NodeTraversal::next(*m_node); enforceEndInvariant(); } void IntersectingNodeIterator::advanceSkippingChildren() { ASSERT(m_node); m_node = m_node->contains(m_pastLastNode.get()) ? nullptr : NodeTraversal::nextSkippingChildren(*m_node); enforceEndInvariant(); } void IntersectingNodeIterator::enforceEndInvariant() { if (m_node == m_pastLastNode || !m_node) { m_node = nullptr; m_pastLastNode = nullptr; } } template Node* commonInclusiveAncestor(const SimpleRange& range) { return commonInclusiveAncestor(range.start.container, range.end.container); } template Node* commonInclusiveAncestor(const SimpleRange&); template bool contains(const SimpleRange& range, const BoundaryPoint& point) { return is_lteq(treeOrder(range.start, point)) && is_lteq(treeOrder(point, range.end)); } template bool contains(const SimpleRange&, const BoundaryPoint&); template bool contains(const SimpleRange& range, const std::optional& point) { return point && contains(range, *point); } template bool contains(const SimpleRange&, const std::optional&); bool containsForTesting(TreeType type, const SimpleRange& range, const BoundaryPoint& point) { switch (type) { case Tree: return contains(range, point); case ShadowIncludingTree: return contains(range, point); case ComposedTree: return contains(range, point); } ASSERT_NOT_REACHED(); return false; } template PartialOrdering treeOrder(const SimpleRange& range, const BoundaryPoint& point) { if (auto order = treeOrder(range.start, point); !is_lt(order)) return order; if (auto order = treeOrder(range.end, point); !is_gt(order)) return order; return PartialOrdering::equivalent; } template PartialOrdering treeOrder(const BoundaryPoint& point, const SimpleRange& range) { if (auto order = treeOrder(point, range.start); !is_gt(order)) return order; if (auto order = treeOrder(point, range.end); !is_lt(order)) return order; return PartialOrdering::equivalent; } template PartialOrdering treeOrder(const SimpleRange&, const BoundaryPoint&); template PartialOrdering treeOrder(const BoundaryPoint&, const SimpleRange&); template bool contains(const SimpleRange& outerRange, const SimpleRange& innerRange) { return is_lteq(treeOrder(outerRange.start, innerRange.start)) && is_gteq(treeOrder(outerRange.end, innerRange.end)); } template bool contains(const SimpleRange&, const SimpleRange&); template bool contains(const SimpleRange&, const SimpleRange&); bool containsForTesting(TreeType type, const SimpleRange& outerRange, const SimpleRange& innerRange) { switch (type) { case Tree: return contains(outerRange, innerRange); case ShadowIncludingTree: return contains(outerRange, innerRange); case ComposedTree: return contains(outerRange, innerRange); } ASSERT_NOT_REACHED(); return false; } template bool intersects(const SimpleRange& a, const SimpleRange& b) { return is_lteq(treeOrder(a.start, b.end)) && is_lteq(treeOrder(b.start, a.end)); } template bool intersects(const SimpleRange&, const SimpleRange&); template bool intersects(const SimpleRange&, const SimpleRange&); bool intersectsForTesting(TreeType type, const SimpleRange& a, const SimpleRange& b) { switch (type) { case Tree: return intersects(a, b); case ShadowIncludingTree: return intersects(a, b); case ComposedTree: return intersects(a, b); } ASSERT_NOT_REACHED(); return false; } static bool compareByComposedTreeOrder(const BoundaryPoint& a, const BoundaryPoint& b) { return is_lt(treeOrder(a, b)); } SimpleRange unionRange(const SimpleRange& a, const SimpleRange& b) { return { std::min(a.start, b.start, compareByComposedTreeOrder), std::max(a.end, b.end, compareByComposedTreeOrder) }; } std::optional intersection(const std::optional& a, const std::optional& b) { // FIXME: Can this be done more efficiently, with fewer calls to treeOrder? if (!a || !b || !intersects(*a, *b)) return std::nullopt; return { { std::max(a->start, b->start, compareByComposedTreeOrder), std::min(a->end, b->end, compareByComposedTreeOrder) } }; } template bool contains(const SimpleRange& range, const Node& node) { // FIXME: Consider a more efficient algorithm that avoids always computing the node index. // FIXME: Does this const_cast point to a design problem? auto nodeRange = makeRangeSelectingNode(const_cast(node)); return nodeRange && contains(range, *nodeRange); } template bool contains(const SimpleRange&, const Node&); template bool contains(const SimpleRange&, const Node&); bool containsForTesting(TreeType type, const SimpleRange& range, const Node& node) { switch (type) { case Tree: return contains(range, node); case ShadowIncludingTree: return contains(range, node); case ComposedTree: return contains(range, node); } ASSERT_NOT_REACHED(); return false; } template bool contains(const Node& outer, const Node& inner) { for (auto inclusiveAncestor = &inner; inclusiveAncestor; inclusiveAncestor = parent(*inclusiveAncestor)) { if (inclusiveAncestor == &outer) return true; } return false; } template<> bool contains(const Node& outer, const Node& inner) { // We specialize here because we want to take advantage of optimizations in Node::isDescendantOf. return outer.contains(inner); } template bool intersects(const SimpleRange& range, const Node& node) { // FIXME: Consider a more efficient algorithm that avoids always computing the node index. // FIXME: Does this const_cast point to a design problem? auto nodeRange = makeRangeSelectingNode(const_cast(node)); if (!nodeRange) return contains(node, range.start.container); return is_lt(treeOrder(nodeRange->start, range.end)) && is_lt(treeOrder(range.start, nodeRange->end)); } template bool intersects(const SimpleRange&, const Node&); template bool intersects(const SimpleRange&, const Node&); bool intersectsForTesting(TreeType type, const SimpleRange& range, const Node& node) { switch (type) { case Tree: return intersects(range, node); case ShadowIncludingTree: return intersects(range, node); case ComposedTree: return intersects(range, node); } ASSERT_NOT_REACHED(); return false; } bool containsCrossingDocumentBoundaries(const SimpleRange& range, Node& node) { auto* ancestor = &node; while (&range.start.document() != &ancestor->document()) { ancestor = ancestor->document().ownerElement(); if (!ancestor) return false; } return contains(range, *ancestor); } }