/* * (C) 1999 Lars Knoll (knoll@kde.org) * (C) 2000 Gunnstein Lye (gunnstein@netcom.no) * (C) 2000 Frederik Holljen (frederik.holljen@hig.no) * (C) 2001 Peter Kelly (pmk@post.com) * Copyright (C) 2004-2020 Apple Inc. All rights reserved. * Copyright (C) 2011 Motorola Mobility. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "config.h" #include "Range.h" #include "Comment.h" #include "DOMRect.h" #include "DOMRectList.h" #include "DocumentFragment.h" #include "Event.h" #include "Frame.h" #include "FrameSelection.h" #include "FrameView.h" #include "GeometryUtilities.h" #include "HTMLBodyElement.h" #include "HTMLHtmlElement.h" #include "HTMLNames.h" #include "NodeTraversal.h" #include "NodeWithIndex.h" #include "ProcessingInstruction.h" #include "ScopedEventQueue.h" #include "TextIterator.h" #include "VisibleUnits.h" #include "markup.h" #include #include #include #include #include namespace WebCore { using namespace HTMLNames; DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, rangeCounter, ("Range")); enum ContentsProcessDirection { ProcessContentsForward, ProcessContentsBackward }; static ExceptionOr processNodes(Range::ActionType, Vector>&, Node* oldContainer, RefPtr newContainer); static ExceptionOr> processContentsBetweenOffsets(Range::ActionType, RefPtr, RefPtr container, unsigned startOffset, unsigned endOffset); static ExceptionOr> processAncestorsAndTheirSiblings(Range::ActionType, Node* container, ContentsProcessDirection, ExceptionOr>&& passedClonedContainer, Node* commonRoot); WTF_MAKE_ISO_ALLOCATED_IMPL(Range); inline Range::Range(Document& ownerDocument) : m_ownerDocument(ownerDocument) , m_start(ownerDocument) , m_end(ownerDocument) { #ifndef NDEBUG rangeCounter.increment(); #endif m_ownerDocument->attachRange(*this); } Ref Range::create(Document& ownerDocument) { return adoptRef(*new Range(ownerDocument)); } Range::~Range() { ASSERT(!m_isAssociatedWithSelection); m_ownerDocument->detachRange(*this); #ifndef NDEBUG rangeCounter.decrement(); #endif } Node* Range::commonAncestorContainer() const { return commonInclusiveAncestor(startContainer(), endContainer()); } void Range::updateAssociatedSelection() { if (m_isAssociatedWithSelection) m_ownerDocument->selection().updateFromAssociatedLiveRange(); } void Range::updateDocument() { auto& document = startContainer().document(); if (m_ownerDocument.ptr() == &document) return; ASSERT(!m_isAssociatedWithSelection); m_ownerDocument->detachRange(*this); m_ownerDocument = document; m_ownerDocument->attachRange(*this); } ExceptionOr Range::setStart(Ref&& container, unsigned offset) { auto childNode = checkNodeOffsetPair(container, offset); if (childNode.hasException()) return childNode.releaseException(); m_start.set(WTFMove(container), offset, childNode.releaseReturnValue()); if (!is_lteq(treeOrder(makeBoundaryPoint(m_start), makeBoundaryPoint(m_end)))) m_end = m_start; updateAssociatedSelection(); updateDocument(); return { }; } ExceptionOr Range::setEnd(Ref&& container, unsigned offset) { auto childNode = checkNodeOffsetPair(container, offset); if (childNode.hasException()) return childNode.releaseException(); m_end.set(WTFMove(container), offset, childNode.releaseReturnValue()); if (!is_lteq(treeOrder(makeBoundaryPoint(m_start), makeBoundaryPoint(m_end)))) m_start = m_end; updateAssociatedSelection(); updateDocument(); return { }; } void Range::collapse(bool toStart) { if (toStart) m_end = m_start; else m_start = m_end; updateAssociatedSelection(); } ExceptionOr Range::isPointInRange(Node& container, unsigned offset) { if (auto checkResult = checkNodeOffsetPair(container, offset); checkResult.hasException()) { // DOM specification requires this check be done first but since there are no side effects, // we can do it in reverse order to avoid an extra root node check in the common case. if (&container.rootNode() != &startContainer().rootNode()) return false; return checkResult.releaseException(); } return contains(makeSimpleRange(*this), { container, offset }); } ExceptionOr Range::comparePoint(Node& container, unsigned offset) const { if (auto checkResult = checkNodeOffsetPair(container, offset); checkResult.hasException()) { // DOM specification requires this check be done first but since there are no side effects, // we can do it in reverse order to avoid an extra root node check in the common case. if (&container.rootNode() != &startContainer().rootNode()) return Exception { WrongDocumentError }; return checkResult.releaseException(); } auto ordering = treeOrder({ container, offset }, makeSimpleRange(*this)); if (is_lt(ordering)) return -1; if (is_eq(ordering)) return 0; if (is_gt(ordering)) return 1; return Exception { WrongDocumentError }; } ExceptionOr Range::compareNode(Node& node) const { // FIXME: This deprecated function should be removed. // We originally added it for interoperability with Firefox. // Recent versions of Firefox have removed it. // http://developer.mozilla.org/en/docs/DOM:range.compareNode // This method returns 0, 1, 2, or 3 based on if the node is before, after, // before and after(surrounds), or inside the range, respectively. if (!node.isConnected() || &node.document() != m_ownerDocument.ptr()) { // Match historical Firefox behavior. return NODE_BEFORE; } auto nodeRange = makeRangeSelectingNode(node); if (!nodeRange) { // Match historical Firefox behavior. return Exception { NotFoundError }; } auto startOrdering = treeOrder(nodeRange->start, makeBoundaryPoint(m_start)); auto endOrdering = treeOrder(nodeRange->end, makeBoundaryPoint(m_end)); if (is_gteq(startOrdering) && is_lteq(endOrdering)) return NODE_INSIDE; if (is_lteq(startOrdering) && is_gteq(endOrdering)) return NODE_BEFORE_AND_AFTER; if (is_lteq(startOrdering)) return NODE_BEFORE; if (is_gteq(endOrdering)) return NODE_AFTER; return Exception { WrongDocumentError }; } ExceptionOr Range::compareBoundaryPoints(unsigned short how, const Range& sourceRange) const { const RangeBoundaryPoint* thisPoint; const RangeBoundaryPoint* otherPoint; switch (how) { case START_TO_START: thisPoint = &m_start; otherPoint = &sourceRange.m_start; break; case START_TO_END: thisPoint = &m_end; otherPoint = &sourceRange.m_start; break; case END_TO_END: thisPoint = &m_end; otherPoint = &sourceRange.m_end; break; case END_TO_START: thisPoint = &m_start; otherPoint = &sourceRange.m_end; break; default: return Exception { NotSupportedError }; } auto ordering = treeOrder(makeBoundaryPoint(*thisPoint), makeBoundaryPoint(*otherPoint)); if (is_lt(ordering)) return -1; if (is_eq(ordering)) return 0; if (is_gt(ordering)) return 1; return Exception { WrongDocumentError }; } ExceptionOr Range::deleteContents() { auto result = processContents(Delete); if (result.hasException()) return result.releaseException(); return { }; } bool Range::intersectsNode(Node& node) const { return intersects(makeSimpleRange(*this), node); } static inline Node* highestAncestorUnderCommonRoot(Node* node, Node* commonRoot) { if (node == commonRoot) return 0; ASSERT(commonRoot->contains(node)); while (node->parentNode() != commonRoot) node = node->parentNode(); return node; } static inline Node* childOfCommonRootBeforeOffset(Node* container, unsigned offset, Node* commonRoot) { ASSERT(container); ASSERT(commonRoot); if (!commonRoot->contains(container)) return 0; if (container == commonRoot) { container = container->firstChild(); for (unsigned i = 0; container && i < offset; i++) container = container->nextSibling(); } else { while (container->parentNode() != commonRoot) container = container->parentNode(); } return container; } ExceptionOr> Range::processContents(ActionType action) { RefPtr fragment; if (action == Extract || action == Clone) fragment = DocumentFragment::create(m_ownerDocument); if (collapsed()) return fragment; RefPtr commonRoot = commonAncestorContainer(); ASSERT(commonRoot); if (&startContainer() == &endContainer()) { auto result = processContentsBetweenOffsets(action, fragment, &startContainer(), m_start.offset(), m_end.offset()); if (result.hasException()) return result.releaseException(); return fragment; } // Since mutation events can modify the range during the process, the boundary points need to be saved. RangeBoundaryPoint originalStart(m_start); RangeBoundaryPoint originalEnd(m_end); // what is the highest node that partially selects the start / end of the range? RefPtr partialStart = highestAncestorUnderCommonRoot(&originalStart.container(), commonRoot.get()); RefPtr partialEnd = highestAncestorUnderCommonRoot(&originalEnd.container(), commonRoot.get()); // Start and end containers are different. // There are three possibilities here: // 1. Start container == commonRoot (End container must be a descendant) // 2. End container == commonRoot (Start container must be a descendant) // 3. Neither is commonRoot, they are both descendants // // In case 3, we grab everything after the start (up until a direct child // of commonRoot) into leftContents, and everything before the end (up until // a direct child of commonRoot) into rightContents. Then we process all // commonRoot children between leftContents and rightContents // // In case 1 or 2, we skip either processing of leftContents or rightContents, // in which case the last lot of nodes either goes from the first or last // child of commonRoot. // // These are deleted, cloned, or extracted (i.e. both) depending on action. // Note that we are verifying that our common root hierarchy is still intact // after any DOM mutation event, at various stages below. See webkit bug 60350. RefPtr leftContents; if (&originalStart.container() != commonRoot && commonRoot->contains(&originalStart.container())) { auto firstResult = processContentsBetweenOffsets(action, nullptr, &originalStart.container(), originalStart.offset(), originalStart.container().length()); auto secondResult = processAncestorsAndTheirSiblings(action, &originalStart.container(), ProcessContentsForward, WTFMove(firstResult), commonRoot.get()); // FIXME: A bit peculiar that we silently ignore the exception here, but we do have at least some regression tests that rely on this behavior. if (!secondResult.hasException()) leftContents = secondResult.releaseReturnValue(); } RefPtr rightContents; if (&endContainer() != commonRoot && commonRoot->contains(&originalEnd.container())) { auto firstResult = processContentsBetweenOffsets(action, nullptr, &originalEnd.container(), 0, originalEnd.offset()); auto secondResult = processAncestorsAndTheirSiblings(action, &originalEnd.container(), ProcessContentsBackward, WTFMove(firstResult), commonRoot.get()); // FIXME: A bit peculiar that we silently ignore the exception here, but we do have at least some regression tests that rely on this behavior. if (!secondResult.hasException()) rightContents = secondResult.releaseReturnValue(); } // delete all children of commonRoot between the start and end container RefPtr processStart = childOfCommonRootBeforeOffset(&originalStart.container(), originalStart.offset(), commonRoot.get()); if (processStart && &originalStart.container() != commonRoot) // processStart contains nodes before m_start. processStart = processStart->nextSibling(); RefPtr processEnd = childOfCommonRootBeforeOffset(&originalEnd.container(), originalEnd.offset(), commonRoot.get()); // Collapse the range, making sure that the result is not within a node that was partially selected. if (action == Extract || action == Delete) { if (partialStart && commonRoot->contains(partialStart.get())) { auto result = setStart(*partialStart->parentNode(), partialStart->computeNodeIndex() + 1); if (result.hasException()) return result.releaseException(); } else if (partialEnd && commonRoot->contains(partialEnd.get())) { auto result = setStart(*partialEnd->parentNode(), partialEnd->computeNodeIndex()); if (result.hasException()) return result.releaseException(); } collapse(true); } // Now add leftContents, stuff in between, and rightContents to the fragment // (or just delete the stuff in between) if ((action == Extract || action == Clone) && leftContents) { auto result = fragment->appendChild(*leftContents); if (result.hasException()) return result.releaseException(); } if (processStart) { Vector> nodes; for (Node* node = processStart.get(); node && node != processEnd; node = node->nextSibling()) nodes.append(*node); auto result = processNodes(action, nodes, commonRoot.get(), fragment.get()); if (result.hasException()) return result.releaseException(); } if ((action == Extract || action == Clone) && rightContents) { auto result = fragment->appendChild(*rightContents); if (result.hasException()) return result.releaseException(); } return fragment; } static inline ExceptionOr deleteCharacterData(CharacterData& data, unsigned startOffset, unsigned endOffset) { if (data.length() - endOffset) { auto result = data.deleteData(endOffset, data.length() - endOffset); if (result.hasException()) return result.releaseException(); } if (startOffset) { auto result = data.deleteData(0, startOffset); if (result.hasException()) return result.releaseException(); } return { }; } static ExceptionOr> processContentsBetweenOffsets(Range::ActionType action, RefPtr fragment, RefPtr container, unsigned startOffset, unsigned endOffset) { ASSERT(container); ASSERT(startOffset <= endOffset); RefPtr result; switch (container->nodeType()) { case Node::TEXT_NODE: case Node::CDATA_SECTION_NODE: case Node::COMMENT_NODE: endOffset = std::min(endOffset, downcast(*container).length()); startOffset = std::min(startOffset, endOffset); if (action == Range::Extract || action == Range::Clone) { Ref characters = downcast(container->cloneNode(true).get()); auto deleteResult = deleteCharacterData(characters, startOffset, endOffset); if (deleteResult.hasException()) return deleteResult.releaseException(); if (fragment) { result = fragment; auto appendResult = result->appendChild(characters); if (appendResult.hasException()) return appendResult.releaseException(); } else result = WTFMove(characters); } if (action == Range::Extract || action == Range::Delete) { auto deleteResult = downcast(*container).deleteData(startOffset, endOffset - startOffset); if (deleteResult.hasException()) return deleteResult.releaseException(); } break; case Node::PROCESSING_INSTRUCTION_NODE: { auto& instruction = downcast(*container); endOffset = std::min(endOffset, downcast(*container).data().length()); startOffset = std::min(startOffset, endOffset); if (action == Range::Extract || action == Range::Clone) { Ref processingInstruction = downcast(container->cloneNode(true).get()); processingInstruction->setData(processingInstruction->data().substring(startOffset, endOffset - startOffset)); if (fragment) { result = fragment; auto appendResult = result->appendChild(processingInstruction); if (appendResult.hasException()) return appendResult.releaseException(); } else result = WTFMove(processingInstruction); } if (action == Range::Extract || action == Range::Delete) { String data { instruction.data() }; data.remove(startOffset, endOffset - startOffset); instruction.setData(data); } break; } case Node::ELEMENT_NODE: case Node::ATTRIBUTE_NODE: case Node::DOCUMENT_NODE: case Node::DOCUMENT_TYPE_NODE: case Node::DOCUMENT_FRAGMENT_NODE: // FIXME: Should we assert that some nodes never appear here? if (action == Range::Extract || action == Range::Clone) { if (fragment) result = fragment; else result = container->cloneNode(false); } Vector> nodes; Node* n = container->firstChild(); for (unsigned i = startOffset; n && i; i--) n = n->nextSibling(); for (unsigned i = startOffset; n && i < endOffset; i++, n = n->nextSibling()) { if (action != Range::Delete && n->isDocumentTypeNode()) { return Exception { HierarchyRequestError }; } nodes.append(*n); } auto processResult = processNodes(action, nodes, container.get(), result); if (processResult.hasException()) return processResult.releaseException(); break; } return result; } static ExceptionOr processNodes(Range::ActionType action, Vector>& nodes, Node* oldContainer, RefPtr newContainer) { for (auto& node : nodes) { switch (action) { case Range::Delete: { auto result = oldContainer->removeChild(node); if (result.hasException()) return result.releaseException(); break; } case Range::Extract: { auto result = newContainer->appendChild(node); // will remove node from its parent if (result.hasException()) return result.releaseException(); break; } case Range::Clone: { auto result = newContainer->appendChild(node->cloneNode(true)); if (result.hasException()) return result.releaseException(); break; } } } return { }; } ExceptionOr> processAncestorsAndTheirSiblings(Range::ActionType action, Node* container, ContentsProcessDirection direction, ExceptionOr>&& passedClonedContainer, Node* commonRoot) { if (passedClonedContainer.hasException()) return WTFMove(passedClonedContainer); RefPtr clonedContainer = passedClonedContainer.releaseReturnValue(); Vector> ancestors; for (ContainerNode* ancestor = container->parentNode(); ancestor && ancestor != commonRoot; ancestor = ancestor->parentNode()) ancestors.append(*ancestor); RefPtr firstChildInAncestorToProcess = direction == ProcessContentsForward ? container->nextSibling() : container->previousSibling(); for (auto& ancestor : ancestors) { if (action == Range::Extract || action == Range::Clone) { auto clonedAncestor = ancestor->cloneNode(false); // Might have been removed already during mutation event. if (clonedContainer) { auto result = clonedAncestor->appendChild(*clonedContainer); if (result.hasException()) return result.releaseException(); } clonedContainer = WTFMove(clonedAncestor); } // Copy siblings of an ancestor of start/end containers // FIXME: This assertion may fail if DOM is modified during mutation event // FIXME: Share code with Range::processNodes ASSERT(!firstChildInAncestorToProcess || firstChildInAncestorToProcess->parentNode() == ancestor.ptr()); Vector> nodes; for (Node* child = firstChildInAncestorToProcess.get(); child; child = (direction == ProcessContentsForward) ? child->nextSibling() : child->previousSibling()) nodes.append(*child); for (auto& child : nodes) { switch (action) { case Range::Delete: { auto result = ancestor->removeChild(child); if (result.hasException()) return result.releaseException(); break; } case Range::Extract: // will remove child from ancestor if (direction == ProcessContentsForward) { auto result = clonedContainer->appendChild(child); if (result.hasException()) return result.releaseException(); } else { auto result = clonedContainer->insertBefore(child, clonedContainer->firstChild()); if (result.hasException()) return result.releaseException(); } break; case Range::Clone: if (direction == ProcessContentsForward) { auto result = clonedContainer->appendChild(child->cloneNode(true)); if (result.hasException()) return result.releaseException(); } else { auto result = clonedContainer->insertBefore(child->cloneNode(true), clonedContainer->firstChild()); if (result.hasException()) return result.releaseException(); } break; } } firstChildInAncestorToProcess = direction == ProcessContentsForward ? ancestor->nextSibling() : ancestor->previousSibling(); } return clonedContainer; } ExceptionOr> Range::extractContents() { auto result = processContents(Extract); if (result.hasException()) return result.releaseException(); return result.releaseReturnValue().releaseNonNull(); } ExceptionOr> Range::cloneContents() { auto result = processContents(Clone); if (result.hasException()) return result.releaseException(); return result.releaseReturnValue().releaseNonNull(); } ExceptionOr Range::insertNode(Ref&& node) { auto startContainerNodeType = startContainer().nodeType(); if (startContainerNodeType == Node::COMMENT_NODE || startContainerNodeType == Node::PROCESSING_INSTRUCTION_NODE) return Exception { HierarchyRequestError }; bool startIsText = startContainerNodeType == Node::TEXT_NODE; if (startIsText && !startContainer().parentNode()) return Exception { HierarchyRequestError }; if (node.ptr() == &startContainer()) return Exception { HierarchyRequestError }; RefPtr referenceNode = startIsText ? &startContainer() : startContainer().traverseToChildAt(startOffset()); Node* parentNode = referenceNode ? referenceNode->parentNode() : &startContainer(); if (!is(parentNode)) return Exception { HierarchyRequestError }; Ref parent = downcast(*parentNode); auto result = parent->ensurePreInsertionValidity(node, referenceNode.get()); if (result.hasException()) return result.releaseException(); EventQueueScope scope; if (startIsText) { auto result = downcast(startContainer()).splitText(startOffset()); if (result.hasException()) return result.releaseException(); referenceNode = result.releaseReturnValue(); } if (referenceNode == node.ptr()) referenceNode = referenceNode->nextSibling(); auto removeResult = node->remove(); if (removeResult.hasException()) return removeResult.releaseException(); unsigned newOffset = referenceNode ? referenceNode->computeNodeIndex() : parent->countChildNodes(); if (is(node)) newOffset += downcast(node.get()).countChildNodes(); else ++newOffset; auto insertResult = parent->insertBefore(node, referenceNode.get()); if (insertResult.hasException()) return insertResult.releaseException(); if (collapsed()) return setEnd(WTFMove(parent), newOffset); return { }; } String Range::toString() const { auto range = makeSimpleRange(*this); StringBuilder builder; for (auto& node : intersectingNodes(range)) { if (is(node)) { auto offsetRange = characterDataOffsetRange(range, node); builder.appendSubstring(downcast(node).data(), offsetRange.start, offsetRange.end - offsetRange.start); } } return builder.toString(); } // https://w3c.github.io/DOM-Parsing/#widl-Range-createContextualFragment-DocumentFragment-DOMString-fragment ExceptionOr> Range::createContextualFragment(const String& markup) { Node& node = startContainer(); RefPtr element; if (is(node) || is(node)) element = nullptr; else if (is(node)) element = &downcast(node); else element = node.parentElement(); if (!element || (element->document().isHTMLDocument() && is(*element))) element = HTMLBodyElement::create(node.document()); return WebCore::createContextualFragment(*element, markup, AllowScriptingContentAndDoNotMarkAlreadyStarted); } ExceptionOr Range::checkNodeOffsetPair(Node& node, unsigned offset) { switch (node.nodeType()) { case Node::DOCUMENT_TYPE_NODE: return Exception { InvalidNodeTypeError }; case Node::CDATA_SECTION_NODE: case Node::COMMENT_NODE: case Node::TEXT_NODE: case Node::PROCESSING_INSTRUCTION_NODE: if (offset > downcast(node).length()) return Exception { IndexSizeError }; return nullptr; case Node::ATTRIBUTE_NODE: case Node::DOCUMENT_FRAGMENT_NODE: case Node::DOCUMENT_NODE: case Node::ELEMENT_NODE: if (!offset) return nullptr; auto childBefore = node.traverseToChildAt(offset - 1); if (!childBefore) return Exception { IndexSizeError }; return childBefore; } ASSERT_NOT_REACHED(); return Exception { InvalidNodeTypeError }; } Ref Range::cloneRange() const { auto result = create(m_ownerDocument); result->setStart(startContainer(), m_start.offset()); result->setEnd(endContainer(), m_end.offset()); return result; } ExceptionOr Range::setStartAfter(Node& node) { auto parent = node.parentNode(); if (!parent) return Exception { InvalidNodeTypeError }; return setStart(*parent, node.computeNodeIndex() + 1); } ExceptionOr Range::setEndBefore(Node& node) { auto parent = node.parentNode(); if (!parent) return Exception { InvalidNodeTypeError }; return setEnd(*parent, node.computeNodeIndex()); } ExceptionOr Range::setEndAfter(Node& node) { auto parent = node.parentNode(); if (!parent) return Exception { InvalidNodeTypeError }; return setEnd(*parent, node.computeNodeIndex() + 1); } ExceptionOr Range::selectNode(Node& node) { auto parent = node.parentNode(); if (!parent) return Exception { InvalidNodeTypeError }; unsigned index = node.computeNodeIndex(); auto result = setStart(*parent, index); if (result.hasException()) return result.releaseException(); return setEnd(*parent, index + 1); } ExceptionOr Range::selectNodeContents(Node& node) { if (node.isDocumentTypeNode()) return Exception { InvalidNodeTypeError }; m_start.setToBeforeContents(node); m_end.setToAfterContents(node); updateAssociatedSelection(); updateDocument(); return { }; } // https://dom.spec.whatwg.org/#dom-range-surroundcontents ExceptionOr Range::surroundContents(Node& newParent) { Ref protectedNewParent(newParent); // Step 1: If a non-Text node is partially contained in the context object, then throw an InvalidStateError. Node* startNonTextContainer = &startContainer(); if (startNonTextContainer->nodeType() == Node::TEXT_NODE) startNonTextContainer = startNonTextContainer->parentNode(); Node* endNonTextContainer = &endContainer(); if (endNonTextContainer->nodeType() == Node::TEXT_NODE) endNonTextContainer = endNonTextContainer->parentNode(); if (startNonTextContainer != endNonTextContainer) return Exception { InvalidStateError }; // Step 2: If newParent is a Document, DocumentType, or DocumentFragment node, then throw an InvalidNodeTypeError. switch (newParent.nodeType()) { case Node::ATTRIBUTE_NODE: case Node::DOCUMENT_FRAGMENT_NODE: case Node::DOCUMENT_NODE: case Node::DOCUMENT_TYPE_NODE: return Exception { InvalidNodeTypeError }; case Node::CDATA_SECTION_NODE: case Node::COMMENT_NODE: case Node::ELEMENT_NODE: case Node::PROCESSING_INSTRUCTION_NODE: case Node::TEXT_NODE: break; } // Step 3: Let fragment be the result of extracting context object. auto fragment = extractContents(); if (fragment.hasException()) return fragment.releaseException(); // Step 4: If newParent has children, replace all with null within newParent. if (newParent.hasChildNodes()) downcast(newParent).replaceAll(nullptr); // Step 5: Insert newParent into context object. auto insertResult = insertNode(newParent); if (insertResult.hasException()) return insertResult.releaseException(); // Step 6: Append fragment to newParent. auto appendResult = newParent.appendChild(fragment.releaseReturnValue()); if (appendResult.hasException()) return appendResult.releaseException(); // Step 7: Select newParent within context object. return selectNode(newParent); } ExceptionOr Range::setStartBefore(Node& node) { auto parent = node.parentNode(); if (!parent) return Exception { InvalidNodeTypeError }; return setStart(*parent, node.computeNodeIndex()); } #if ENABLE(TREE_DEBUGGING) String Range::debugDescription() const { return makeString("from offset ", m_start.offset(), " of ", startContainer().debugDescription(), " to offset ", m_end.offset(), " of ", endContainer().debugDescription()); } #endif static inline void boundaryNodeChildrenChanged(RangeBoundaryPoint& boundary, ContainerNode& container) { if (boundary.childBefore() && &boundary.container() == &container) boundary.invalidateOffset(); } void Range::nodeChildrenChanged(ContainerNode& container) { ASSERT(&container.document() == m_ownerDocument.ptr()); boundaryNodeChildrenChanged(m_start, container); boundaryNodeChildrenChanged(m_end, container); } static inline void boundaryNodeChildrenWillBeRemoved(RangeBoundaryPoint& boundary, ContainerNode& containerOfNodesToBeRemoved) { if (containerOfNodesToBeRemoved.contains(&boundary.container())) boundary.setToBeforeContents(containerOfNodesToBeRemoved); } void Range::nodeChildrenWillBeRemoved(ContainerNode& container) { ASSERT(&container.document() == m_ownerDocument.ptr()); boundaryNodeChildrenWillBeRemoved(m_start, container); boundaryNodeChildrenWillBeRemoved(m_end, container); } static inline void boundaryNodeWillBeRemoved(RangeBoundaryPoint& boundary, Node& nodeToBeRemoved) { if (boundary.childBefore() == &nodeToBeRemoved) boundary.childBeforeWillBeRemoved(); else if (nodeToBeRemoved.contains(&boundary.container())) boundary.setToBeforeNode(nodeToBeRemoved); } void Range::nodeWillBeRemoved(Node& node) { ASSERT(&node.document() == m_ownerDocument.ptr()); ASSERT(&node != m_ownerDocument.ptr()); ASSERT(node.parentNode()); boundaryNodeWillBeRemoved(m_start, node); boundaryNodeWillBeRemoved(m_end, node); } bool Range::parentlessNodeMovedToNewDocumentAffectsRange(Node& node) { return node.containsIncludingShadowDOM(&m_start.container()); } void Range::updateRangeForParentlessNodeMovedToNewDocument(Node& node) { m_ownerDocument->detachRange(*this); m_ownerDocument = node.document(); m_ownerDocument->attachRange(*this); } static inline void boundaryTextInserted(RangeBoundaryPoint& boundary, Node& text, unsigned offset, unsigned length) { if (&boundary.container() != &text) return; unsigned boundaryOffset = boundary.offset(); if (offset >= boundaryOffset) return; boundary.setOffset(boundaryOffset + length); } void Range::textInserted(Node& text, unsigned offset, unsigned length) { ASSERT(&text.document() == m_ownerDocument.ptr()); boundaryTextInserted(m_start, text, offset, length); boundaryTextInserted(m_end, text, offset, length); } static inline void boundaryTextRemoved(RangeBoundaryPoint& boundary, Node& text, unsigned offset, unsigned length) { if (&boundary.container() != &text) return; unsigned boundaryOffset = boundary.offset(); if (offset >= boundaryOffset) return; if (offset + length >= boundaryOffset) boundary.setOffset(offset); else boundary.setOffset(boundaryOffset - length); } void Range::textRemoved(Node& text, unsigned offset, unsigned length) { ASSERT(&text.document() == m_ownerDocument.ptr()); boundaryTextRemoved(m_start, text, offset, length); boundaryTextRemoved(m_end, text, offset, length); } static inline void boundaryTextNodesMerged(RangeBoundaryPoint& boundary, NodeWithIndex& oldNode, unsigned offset) { if (&boundary.container() == oldNode.node()) boundary.set(*oldNode.node()->previousSibling(), boundary.offset() + offset, 0); else if (&boundary.container() == oldNode.node()->parentNode() && boundary.offset() == static_cast(oldNode.index())) boundary.set(*oldNode.node()->previousSibling(), offset, 0); } void Range::textNodesMerged(NodeWithIndex& oldNode, unsigned offset) { ASSERT(oldNode.node()); ASSERT(&oldNode.node()->document() == m_ownerDocument.ptr()); ASSERT(oldNode.node()->parentNode()); ASSERT(oldNode.node()->isTextNode()); ASSERT(oldNode.node()->previousSibling()); ASSERT(oldNode.node()->previousSibling()->isTextNode()); boundaryTextNodesMerged(m_start, oldNode, offset); boundaryTextNodesMerged(m_end, oldNode, offset); } static inline void boundaryTextNodesSplit(RangeBoundaryPoint& boundary, Text& oldNode) { auto* parent = oldNode.parentNode(); if (&boundary.container() == &oldNode) { unsigned splitOffset = oldNode.length(); unsigned boundaryOffset = boundary.offset(); if (boundaryOffset > splitOffset) { if (parent) boundary.set(*oldNode.nextSibling(), boundaryOffset - splitOffset, 0); else boundary.setOffset(splitOffset); } return; } if (!parent) return; if (&boundary.container() == parent && boundary.childBefore() == &oldNode) { auto* newChild = oldNode.nextSibling(); ASSERT(newChild); boundary.setToAfterNode(*newChild); } } void Range::textNodeSplit(Text& oldNode) { ASSERT(&oldNode.document() == m_ownerDocument.ptr()); ASSERT(!oldNode.parentNode() || oldNode.nextSibling()); ASSERT(!oldNode.parentNode() || oldNode.nextSibling()->isTextNode()); boundaryTextNodesSplit(m_start, oldNode); boundaryTextNodesSplit(m_end, oldNode); } ExceptionOr Range::expand(const String& unit) { auto start = VisiblePosition { makeContainerOffsetPosition(&startContainer(), startOffset()) }; auto end = VisiblePosition { makeContainerOffsetPosition(&endContainer(), endOffset()) }; if (unit == "word") { start = startOfWord(start); end = endOfWord(end); } else if (unit == "sentence") { start = startOfSentence(start); end = endOfSentence(end); } else if (unit == "block") { start = startOfParagraph(start); end = endOfParagraph(end); } else if (unit == "document") { start = startOfDocument(start); end = endOfDocument(end); } else return { }; auto* startContainer = start.deepEquivalent().containerNode(); if (!startContainer) return Exception { TypeError }; auto result = setStart(*startContainer, start.deepEquivalent().computeOffsetInContainerNode()); if (result.hasException()) return result.releaseException(); auto* endContainer = end.deepEquivalent().containerNode(); if (!endContainer) return Exception { TypeError }; return setEnd(*endContainer, end.deepEquivalent().computeOffsetInContainerNode()); } Ref Range::getClientRects() const { startContainer().document().updateLayout(); return DOMRectList::create(RenderObject::clientBorderAndTextRects(makeSimpleRange(*this))); } Ref Range::getBoundingClientRect() const { startContainer().document().updateLayout(); return DOMRect::create(unionRectIgnoringZeroRects(RenderObject::clientBorderAndTextRects(makeSimpleRange(*this)))); } static void setBothEndpoints(Range& range, const SimpleRange& value) { auto startContainer = value.start.container; range.setStart(WTFMove(startContainer), value.start.offset); auto endContainer = value.end.container; range.setEnd(WTFMove(endContainer), value.end.offset); } void Range::updateFromSelection(const SimpleRange& value) { ASSERT(m_isAssociatedWithSelection); m_isAssociatedWithSelection = false; setBothEndpoints(*this, value); m_isAssociatedWithSelection = true; } DOMWindow* Range::window() const { return m_isAssociatedWithSelection ? m_ownerDocument->domWindow() : nullptr; } SimpleRange makeSimpleRange(const Range& range) { return { { range.startContainer(), range.startOffset() }, { range.endContainer(), range.endOffset() } }; } SimpleRange makeSimpleRange(const Ref& range) { return makeSimpleRange(range.get()); } std::optional makeSimpleRange(const Range* range) { if (!range) return std::nullopt; return makeSimpleRange(*range); } std::optional makeSimpleRange(const RefPtr& range) { return makeSimpleRange(range.get()); } Ref createLiveRange(const SimpleRange& range) { auto result = Range::create(range.start.document()); setBothEndpoints(result, range); return result; } RefPtr createLiveRange(const std::optional& range) { if (!range) return nullptr; return createLiveRange(*range); } } // namespace WebCore #if ENABLE(TREE_DEBUGGING) void showTree(const WebCore::Range* range) { if (range) { range->startContainer().showTreeAndMark(&range->startContainer(), "S", &range->endContainer(), "E"); fprintf(stderr, "start offset: %d, end offset: %d\n", range->startOffset(), range->endOffset()); } } #endif