/* * (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, 2005, 2006, 2007, 2008, 2009, 2010, 2011 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 "sky/engine/core/dom/Range.h" #include "sky/engine/bindings/exception_state.h" #include "sky/engine/core/dom/ClientRect.h" #include "sky/engine/core/dom/ClientRectList.h" #include "sky/engine/core/dom/DocumentFragment.h" #include "sky/engine/core/dom/ExceptionCode.h" #include "sky/engine/core/dom/Node.h" #include "sky/engine/core/dom/NodeTraversal.h" #include "sky/engine/core/dom/NodeWithIndex.h" #include "sky/engine/core/dom/Text.h" #include "sky/engine/core/editing/TextIterator.h" #include "sky/engine/core/editing/VisiblePosition.h" #include "sky/engine/core/editing/VisibleUnits.h" #include "sky/engine/core/html/HTMLElement.h" #include "sky/engine/core/rendering/RenderBoxModelObject.h" #include "sky/engine/core/rendering/RenderText.h" #include "sky/engine/platform/geometry/FloatQuad.h" #include "sky/engine/wtf/RefCountedLeakCounter.h" #include "sky/engine/wtf/text/CString.h" #include "sky/engine/wtf/text/StringBuilder.h" #ifndef NDEBUG #include #endif namespace blink { DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, rangeCounter, ("Range")); inline Range::Range(Document& ownerDocument) : m_ownerDocument(&ownerDocument) , m_start(m_ownerDocument) , m_end(m_ownerDocument) { #ifndef NDEBUG rangeCounter.increment(); #endif m_ownerDocument->attachRange(this); } PassRefPtr Range::create(Document& ownerDocument) { return adoptRef(new Range(ownerDocument)); } PassRefPtr Range::create(Document* ownerDocument) { return adoptRef(new Range(*ownerDocument)); } inline Range::Range(Document& ownerDocument, Node* startContainer, int startOffset, Node* endContainer, int endOffset) : m_ownerDocument(&ownerDocument) , m_start(m_ownerDocument) , m_end(m_ownerDocument) { #ifndef NDEBUG rangeCounter.increment(); #endif m_ownerDocument->attachRange(this); // Simply setting the containers and offsets directly would not do any of the checking // that setStart and setEnd do, so we call those functions. setStart(startContainer, startOffset); setEnd(endContainer, endOffset); } PassRefPtr Range::create(Document& ownerDocument, Node* startContainer, int startOffset, Node* endContainer, int endOffset) { return adoptRef(new Range(ownerDocument, startContainer, startOffset, endContainer, endOffset)); } PassRefPtr Range::create(Document& ownerDocument, const Position& start, const Position& end) { return adoptRef(new Range(ownerDocument, start.containerNode(), start.computeOffsetInContainerNode(), end.containerNode(), end.computeOffsetInContainerNode())); } Range::~Range() { #if !ENABLE(OILPAN) // Always detach (even if we've already detached) to fix https://bugs.webkit.org/show_bug.cgi?id=26044 m_ownerDocument->detachRange(this); #endif #ifndef NDEBUG rangeCounter.decrement(); #endif } void Range::setDocument(Document& document) { ASSERT(m_ownerDocument != document); ASSERT(m_ownerDocument); m_ownerDocument->detachRange(this); m_ownerDocument = &document; m_start.setToStartOfNode(document); m_end.setToStartOfNode(document); m_ownerDocument->attachRange(this); } Node* Range::commonAncestorContainer() const { return commonAncestorContainer(m_start.container(), m_end.container()); } Node* Range::commonAncestorContainer(Node* containerA, Node* containerB) { for (Node* parentA = containerA; parentA; parentA = parentA->parentNode()) { for (Node* parentB = containerB; parentB; parentB = parentB->parentNode()) { if (parentA == parentB) return parentA; } } return 0; } static inline bool checkForDifferentRootContainer(const RangeBoundaryPoint& start, const RangeBoundaryPoint& end) { Node* endRootContainer = end.container(); while (endRootContainer->parentNode()) endRootContainer = endRootContainer->parentNode(); Node* startRootContainer = start.container(); while (startRootContainer->parentNode()) startRootContainer = startRootContainer->parentNode(); return startRootContainer != endRootContainer || (Range::compareBoundaryPoints(start, end, ASSERT_NO_EXCEPTION) > 0); } void Range::setStart(PassRefPtr refNode, int offset, ExceptionState& exceptionState) { if (!refNode) { exceptionState.ThrowDOMException(NotFoundError, "The node provided was null."); return; } bool didMoveDocument = false; if (refNode->document() != m_ownerDocument) { setDocument(refNode->document()); didMoveDocument = true; } Node* childNode = checkNodeWOffset(refNode.get(), offset, exceptionState); if (exceptionState.had_exception()) return; m_start.set(refNode, offset, childNode); if (didMoveDocument || checkForDifferentRootContainer(m_start, m_end)) collapse(true); } void Range::setEnd(PassRefPtr refNode, int offset, ExceptionState& exceptionState) { if (!refNode) { exceptionState.ThrowDOMException(NotFoundError, "The node provided was null."); return; } bool didMoveDocument = false; if (refNode->document() != m_ownerDocument) { setDocument(refNode->document()); didMoveDocument = true; } Node* childNode = checkNodeWOffset(refNode.get(), offset, exceptionState); if (exceptionState.had_exception()) return; m_end.set(refNode, offset, childNode); if (didMoveDocument || checkForDifferentRootContainer(m_start, m_end)) collapse(false); } void Range::setStart(const Position& start, ExceptionState& exceptionState) { Position parentAnchored = start.parentAnchoredEquivalent(); setStart(parentAnchored.containerNode(), parentAnchored.offsetInContainerNode(), exceptionState); } void Range::setEnd(const Position& end, ExceptionState& exceptionState) { Position parentAnchored = end.parentAnchoredEquivalent(); setEnd(parentAnchored.containerNode(), parentAnchored.offsetInContainerNode(), exceptionState); } void Range::collapse(bool toStart) { if (toStart) m_end = m_start; else m_start = m_end; } bool Range::isPointInRange(Node* refNode, int offset, ExceptionState& exceptionState) { if (!refNode) { exceptionState.ThrowDOMException(HierarchyRequestError, "The node provided was null."); return false; } if (!refNode->inActiveDocument() || refNode->document() != m_ownerDocument) { return false; } checkNodeWOffset(refNode, offset, exceptionState); if (exceptionState.had_exception()) return false; return compareBoundaryPoints(refNode, offset, m_start.container(), m_start.offset(), exceptionState) >= 0 && !exceptionState.had_exception() && compareBoundaryPoints(refNode, offset, m_end.container(), m_end.offset(), exceptionState) <= 0 && !exceptionState.had_exception(); } short Range::comparePoint(Node* refNode, int offset, ExceptionState& exceptionState) const { // http://developer.mozilla.org/en/docs/DOM:range.comparePoint // This method returns -1, 0 or 1 depending on if the point described by the // refNode node and an offset within the node is before, same as, or after the range respectively. if (!refNode->inActiveDocument()) { exceptionState.ThrowDOMException(WrongDocumentError, "The node provided is not in an active document."); return 0; } if (refNode->document() != m_ownerDocument) { exceptionState.ThrowDOMException(WrongDocumentError, "The node provided is not in this Range's Document."); return 0; } checkNodeWOffset(refNode, offset, exceptionState); if (exceptionState.had_exception()) return 0; // compare to start, and point comes before if (compareBoundaryPoints(refNode, offset, m_start.container(), m_start.offset(), exceptionState) < 0) return -1; if (exceptionState.had_exception()) return 0; // compare to end, and point comes after if (compareBoundaryPoints(refNode, offset, m_end.container(), m_end.offset(), exceptionState) > 0 && !exceptionState.had_exception()) return 1; // point is in the middle of this range, or on the boundary points return 0; } Range::CompareResults Range::compareNode(Node* refNode, ExceptionState& exceptionState) const { // 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 (!refNode) { exceptionState.ThrowDOMException(NotFoundError, "The node provided was null."); return NODE_BEFORE; } if (!refNode->inActiveDocument()) { // Firefox doesn't throw an exception for this case; it returns 0. return NODE_BEFORE; } if (refNode->document() != m_ownerDocument) { // Firefox doesn't throw an exception for this case; it returns 0. return NODE_BEFORE; } ContainerNode* parentNode = refNode->parentNode(); int nodeIndex = refNode->nodeIndex(); if (!parentNode) { // if the node is the top document we should return NODE_BEFORE_AND_AFTER // but we throw to match firefox behavior exceptionState.ThrowDOMException(NotFoundError, "The provided node has no parent."); return NODE_BEFORE; } if (comparePoint(parentNode, nodeIndex, exceptionState) < 0) { // starts before if (comparePoint(parentNode, nodeIndex + 1, exceptionState) > 0) // ends after the range return NODE_BEFORE_AND_AFTER; return NODE_BEFORE; // ends before or in the range } // starts at or after the range start if (comparePoint(parentNode, nodeIndex + 1, exceptionState) > 0) // ends after the range return NODE_AFTER; return NODE_INSIDE; // ends inside the range } short Range::compareBoundaryPoints(CompareHow how, const Range* sourceRange, ExceptionState& exceptionState) const { if (!(how == START_TO_START || how == START_TO_END || how == END_TO_END || how == END_TO_START)) { exceptionState.ThrowDOMException(NotSupportedError, "The comparison method provided must be one of 'START_TO_START', 'START_TO_END', 'END_TO_END', or 'END_TO_START'."); return 0; } Node* thisCont = commonAncestorContainer(); Node* sourceCont = sourceRange->commonAncestorContainer(); if (thisCont->document() != sourceCont->document()) { exceptionState.ThrowDOMException(WrongDocumentError, "The source range is in a different document than this range."); return 0; } Node* thisTop = thisCont; Node* sourceTop = sourceCont; while (thisTop->parentNode()) thisTop = thisTop->parentNode(); while (sourceTop->parentNode()) sourceTop = sourceTop->parentNode(); if (thisTop != sourceTop) { // in different DocumentFragments exceptionState.ThrowDOMException(WrongDocumentError, "The source range is in a different document than this range."); return 0; } switch (how) { case START_TO_START: return compareBoundaryPoints(m_start, sourceRange->m_start, exceptionState); case START_TO_END: return compareBoundaryPoints(m_end, sourceRange->m_start, exceptionState); case END_TO_END: return compareBoundaryPoints(m_end, sourceRange->m_end, exceptionState); case END_TO_START: return compareBoundaryPoints(m_start, sourceRange->m_end, exceptionState); } ASSERT_NOT_REACHED(); return 0; } short Range::compareBoundaryPoints(Node* containerA, int offsetA, Node* containerB, int offsetB, ExceptionState& exceptionState) { ASSERT(containerA); ASSERT(containerB); if (!containerA) return -1; if (!containerB) return 1; // see DOM2 traversal & range section 2.5 // case 1: both points have the same container if (containerA == containerB) { if (offsetA == offsetB) return 0; // A is equal to B if (offsetA < offsetB) return -1; // A is before B else return 1; // A is after B } // case 2: node C (container B or an ancestor) is a child node of A Node* c = containerB; while (c && c->parentNode() != containerA) c = c->parentNode(); if (c) { int offsetC = 0; Node* n = containerA->firstChild(); while (n != c && offsetC < offsetA) { offsetC++; n = n->nextSibling(); } if (offsetA <= offsetC) return -1; // A is before B else return 1; // A is after B } // case 3: node C (container A or an ancestor) is a child node of B c = containerA; while (c && c->parentNode() != containerB) c = c->parentNode(); if (c) { int offsetC = 0; Node* n = containerB->firstChild(); while (n != c && offsetC < offsetB) { offsetC++; n = n->nextSibling(); } if (offsetC < offsetB) return -1; // A is before B else return 1; // A is after B } // case 4: containers A & B are siblings, or children of siblings // ### we need to do a traversal here instead Node* commonAncestor = commonAncestorContainer(containerA, containerB); if (!commonAncestor) { exceptionState.ThrowDOMException(WrongDocumentError, "The two ranges are in separate documents."); return 0; } Node* childA = containerA; while (childA && childA->parentNode() != commonAncestor) childA = childA->parentNode(); if (!childA) childA = commonAncestor; Node* childB = containerB; while (childB && childB->parentNode() != commonAncestor) childB = childB->parentNode(); if (!childB) childB = commonAncestor; if (childA == childB) return 0; // A is equal to B Node* n = commonAncestor->firstChild(); while (n) { if (n == childA) return -1; // A is before B if (n == childB) return 1; // A is after B n = n->nextSibling(); } // Should never reach this point. ASSERT_NOT_REACHED(); return 0; } short Range::compareBoundaryPoints(const RangeBoundaryPoint& boundaryA, const RangeBoundaryPoint& boundaryB, ExceptionState& exceptionState) { return compareBoundaryPoints(boundaryA.container(), boundaryA.offset(), boundaryB.container(), boundaryB.offset(), exceptionState); } bool Range::boundaryPointsValid() const { TrackExceptionState exceptionState; return compareBoundaryPoints(m_start, m_end, exceptionState) <= 0 && !exceptionState.had_exception(); } void Range::deleteContents(ExceptionState& exceptionState) { ASSERT(boundaryPointsValid()); { processContents(DELETE_CONTENTS, exceptionState); } } bool Range::intersectsNode(Node* refNode, ExceptionState& exceptionState) { // http://developer.mozilla.org/en/docs/DOM:range.intersectsNode // Returns a bool if the node intersects the range. if (!refNode) { exceptionState.ThrowDOMException(NotFoundError, "The node provided is null."); return false; } if (!refNode->inActiveDocument() || refNode->document() != m_ownerDocument) { // Firefox doesn't throw an exception for these cases; it returns false. return false; } ContainerNode* parentNode = refNode->parentNode(); int nodeIndex = refNode->nodeIndex(); if (!parentNode) { // if the node is the top document we should return NODE_BEFORE_AND_AFTER // but we throw to match firefox behavior exceptionState.ThrowDOMException(NotFoundError, "The node provided has no parent."); return false; } if (comparePoint(parentNode, nodeIndex, exceptionState) < 0 // starts before start && comparePoint(parentNode, nodeIndex + 1, exceptionState) < 0) { // ends before start return false; } if (comparePoint(parentNode, nodeIndex, exceptionState) > 0 // starts after end && comparePoint(parentNode, nodeIndex + 1, exceptionState) > 0) { // ends after end return false; } return true; // all other cases } 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; } PassRefPtr Range::processContents(ActionType action, ExceptionState& exceptionState) { typedef Vector > NodeVector; RefPtr fragment = nullptr; if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) fragment = DocumentFragment::create(*m_ownerDocument.get()); if (collapsed()) return fragment.release(); RefPtr commonRoot = commonAncestorContainer(); ASSERT(commonRoot); if (m_start.container() == m_end.container()) { processContentsBetweenOffsets(action, fragment, m_start.container(), m_start.offset(), m_end.offset(), exceptionState); return fragment; } // Since mutation observers 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 = nullptr; if (originalStart.container() != commonRoot && commonRoot->contains(originalStart.container())) { leftContents = processContentsBetweenOffsets(action, nullptr, originalStart.container(), originalStart.offset(), originalStart.container()->lengthOfContents(), exceptionState); leftContents = processAncestorsAndTheirSiblings(action, originalStart.container(), ProcessContentsForward, leftContents, commonRoot.get(), exceptionState); } RefPtr rightContents = nullptr; if (m_end.container() != commonRoot && commonRoot->contains(originalEnd.container())) { rightContents = processContentsBetweenOffsets(action, nullptr, originalEnd.container(), 0, originalEnd.offset(), exceptionState); rightContents = processAncestorsAndTheirSiblings(action, originalEnd.container(), ProcessContentsBackward, rightContents, commonRoot.get(), exceptionState); } // 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_CONTENTS || action == DELETE_CONTENTS) { if (partialStart && commonRoot->contains(partialStart.get())) { // FIXME: We should not continue if we have an earlier error. exceptionState.ClearException(); setStart(partialStart->parentNode(), partialStart->nodeIndex() + 1, exceptionState); } else if (partialEnd && commonRoot->contains(partialEnd.get())) { // FIXME: We should not continue if we have an earlier error. exceptionState.ClearException(); setStart(partialEnd->parentNode(), partialEnd->nodeIndex(), exceptionState); } if (exceptionState.had_exception()) return nullptr; m_end = m_start; } originalStart.clear(); originalEnd.clear(); // Now add leftContents, stuff in between, and rightContents to the fragment // (or just delete the stuff in between) if ((action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) && leftContents) fragment->appendChild(leftContents, exceptionState); if (processStart) { NodeVector nodes; for (Node* n = processStart.get(); n && n != processEnd; n = n->nextSibling()) nodes.append(n); processNodes(action, nodes, commonRoot, fragment, exceptionState); } if ((action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) && rightContents) fragment->appendChild(rightContents, exceptionState); return fragment.release(); } static inline void deleteCharacterData(PassRefPtr data, unsigned startOffset, unsigned endOffset, ExceptionState& exceptionState) { if (data->length() - endOffset) data->deleteData(endOffset, data->length() - endOffset, exceptionState); if (startOffset) data->deleteData(0, startOffset, exceptionState); } PassRefPtr Range::processContentsBetweenOffsets(ActionType action, PassRefPtr fragment, Node* container, unsigned startOffset, unsigned endOffset, ExceptionState& exceptionState) { ASSERT(container); ASSERT(startOffset <= endOffset); // This switch statement must be consistent with that of Node::lengthOfContents. RefPtr result = nullptr; switch (container->nodeType()) { case Node::TEXT_NODE: endOffset = std::min(endOffset, toCharacterData(container)->length()); if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) { RefPtr c = static_pointer_cast(container->cloneNode(true)); deleteCharacterData(c, startOffset, endOffset, exceptionState); if (fragment) { result = fragment; result->appendChild(c.release(), exceptionState); } else result = c.release(); } if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) toCharacterData(container)->deleteData(startOffset, endOffset - startOffset, exceptionState); break; case Node::ELEMENT_NODE: case Node::DOCUMENT_NODE: case Node::DOCUMENT_FRAGMENT_NODE: // FIXME: Should we assert that some nodes never appear here? if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) { if (fragment) result = fragment; else result = container->cloneNode(false); } Node* n = container->firstChild(); Vector > nodes; for (unsigned i = startOffset; n && i; i--) n = n->nextSibling(); for (unsigned i = startOffset; n && i < endOffset; i++, n = n->nextSibling()) nodes.append(n); processNodes(action, nodes, container, result, exceptionState); break; } return result.release(); } void Range::processNodes(ActionType action, Vector >& nodes, PassRefPtr oldContainer, PassRefPtr newContainer, ExceptionState& exceptionState) { for (unsigned i = 0; i < nodes.size(); i++) { switch (action) { case DELETE_CONTENTS: oldContainer->removeChild(nodes[i].get(), exceptionState); break; case EXTRACT_CONTENTS: newContainer->appendChild(nodes[i].release(), exceptionState); // will remove n from its parent break; case CLONE_CONTENTS: newContainer->appendChild(nodes[i]->cloneNode(true), exceptionState); break; } } } PassRefPtr Range::processAncestorsAndTheirSiblings(ActionType action, Node* container, ContentsProcessDirection direction, PassRefPtr passedClonedContainer, Node* commonRoot, ExceptionState& exceptionState) { typedef Vector > NodeVector; RefPtr clonedContainer = passedClonedContainer; NodeVector ancestors; for (ContainerNode* n = container->parentNode(); n && n != commonRoot; n = n->parentNode()) ancestors.append(n); RefPtr firstChildInAncestorToProcess = direction == ProcessContentsForward ? container->nextSibling() : container->previousSibling(); for (NodeVector::const_iterator it = ancestors.begin(); it != ancestors.end(); ++it) { RefPtr ancestor = *it; if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) { if (RefPtr clonedAncestor = ancestor->cloneNode(false)) { // Might have been removed already during mutation event. clonedAncestor->appendChild(clonedContainer, exceptionState); clonedContainer = 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); NodeVector nodes; for (Node* child = firstChildInAncestorToProcess.get(); child; child = (direction == ProcessContentsForward) ? child->nextSibling() : child->previousSibling()) nodes.append(child); for (NodeVector::const_iterator it = nodes.begin(); it != nodes.end(); ++it) { Node* child = it->get(); switch (action) { case DELETE_CONTENTS: // Prior call of ancestor->removeChild() may cause a tree change due to DOMSubtreeModified event. // Therefore, we need to make sure |ancestor| is still |child|'s parent. if (ancestor == child->parentNode()) ancestor->removeChild(child, exceptionState); break; case EXTRACT_CONTENTS: // will remove child from ancestor if (direction == ProcessContentsForward) clonedContainer->appendChild(child, exceptionState); else clonedContainer->insertBefore(child, clonedContainer->firstChild(), exceptionState); break; case CLONE_CONTENTS: if (direction == ProcessContentsForward) clonedContainer->appendChild(child->cloneNode(true), exceptionState); else clonedContainer->insertBefore(child->cloneNode(true), clonedContainer->firstChild(), exceptionState); break; } } firstChildInAncestorToProcess = direction == ProcessContentsForward ? ancestor->nextSibling() : ancestor->previousSibling(); } return clonedContainer.release(); } PassRefPtr Range::extractContents(ExceptionState& exceptionState) { checkExtractPrecondition(exceptionState); if (exceptionState.had_exception()) return nullptr; return processContents(EXTRACT_CONTENTS, exceptionState); } PassRefPtr Range::cloneContents(ExceptionState& exceptionState) { return processContents(CLONE_CONTENTS, exceptionState); } void Range::insertNode(PassRefPtr prpNewNode, ExceptionState& exceptionState) { RefPtr newNode = prpNewNode; if (!newNode) { exceptionState.ThrowDOMException(NotFoundError, "The node provided is null."); return; } // HierarchyRequestError: Raised if the container of the start of the Range is of a type that // does not allow children of the type of newNode or if newNode is an ancestor of the container. // an extra one here - if a text node is going to split, it must have a parent to insert into bool startIsText = m_start.container()->isTextNode(); if (startIsText && !m_start.container()->parentNode()) { exceptionState.ThrowDOMException(HierarchyRequestError, "This operation would split a text node, but there's no parent into which to insert."); return; } // In the case where the container is a text node, we check against the container's parent, because // text nodes get split up upon insertion. Node* checkAgainst; if (startIsText) checkAgainst = m_start.container()->parentNode(); else checkAgainst = m_start.container(); Node::NodeType newNodeType = newNode->nodeType(); int numNewChildren; if (newNodeType == Node::DOCUMENT_FRAGMENT_NODE) { // check each child node, not the DocumentFragment itself numNewChildren = 0; for (Node* c = toDocumentFragment(newNode)->firstChild(); c; c = c->nextSibling()) { ++numNewChildren; } } else { numNewChildren = 1; } for (Node* n = m_start.container(); n; n = n->parentNode()) { if (n == newNode) { exceptionState.ThrowDOMException(HierarchyRequestError, "The node to be inserted contains the insertion point; it may not be inserted into itself."); return; } } // InvalidNodeTypeError: Raised if newNode is an Attr, Entity, Notation, or Document node. switch (newNodeType) { case Node::DOCUMENT_NODE: exceptionState.ThrowDOMException(InvalidNodeTypeError, "The node to be inserted is a '" + newNode->nodeName() + "' node, which may not be inserted here."); return; default: break; } bool collapsed = m_start == m_end; RefPtr container = nullptr; if (startIsText) { container = m_start.container(); RefPtr newText = toText(container)->splitText(m_start.offset(), exceptionState); if (exceptionState.had_exception()) return; container = m_start.container(); container->parentNode()->insertBefore(newNode.release(), newText.get(), exceptionState); if (exceptionState.had_exception()) return; if (collapsed) { // The load event would be fired e.g. by ContainerNode::updateTreeAfterInsertion // Given circumstance may mutate the tree so newText->parentNode() may become null if (!newText->parentNode()) { exceptionState.ThrowDOMException(HierarchyRequestError, "This operation would set range's end to parent with new offset, but there's no parent into which to continue."); return; } m_end.setToBeforeChild(*newText); } } else { RefPtr lastChild = (newNodeType == Node::DOCUMENT_FRAGMENT_NODE) ? toDocumentFragment(newNode)->lastChild() : newNode.get(); if (lastChild && lastChild == m_start.childBefore()) { // The insertion will do nothing, but we need to extend the range to include // the inserted nodes. Node* firstChild = (newNodeType == Node::DOCUMENT_FRAGMENT_NODE) ? toDocumentFragment(newNode)->firstChild() : newNode.get(); ASSERT(firstChild); m_start.setToBeforeChild(*firstChild); return; } container = m_start.container(); container->insertBefore(newNode.release(), NodeTraversal::childAt(*container, m_start.offset()), exceptionState); if (exceptionState.had_exception()) return; // Note that m_start.offset() may have changed as a result of container->insertBefore, // when the node we are inserting comes before the range in the same container. if (collapsed && numNewChildren) m_end.set(m_start.container(), m_start.offset() + numNewChildren, lastChild.get()); } } String Range::toString() const { StringBuilder builder; Node* pastLast = pastLastNode(); for (Node* n = firstNode(); n != pastLast; n = NodeTraversal::next(*n)) { Node::NodeType type = n->nodeType(); if (type == Node::TEXT_NODE) { String data = toCharacterData(n)->data(); int length = data.length(); int start = (n == m_start.container()) ? std::min(std::max(0, m_start.offset()), length) : 0; int end = (n == m_end.container()) ? std::min(std::max(start, m_end.offset()), length) : length; builder.append(data, start, end - start); } } return builder.toString(); } String Range::text() const { return plainText(this, TextIteratorEmitsObjectReplacementCharacter); } void Range::detach() { // This is now a no-op as per the DOM specification. } Node* Range::checkNodeWOffset(Node* n, int offset, ExceptionState& exceptionState) const { switch (n->nodeType()) { case Node::TEXT_NODE: if (static_cast(offset) > toCharacterData(n)->length()) exceptionState.ThrowDOMException(IndexSizeError, "The offset " + String::number(offset) + " is larger than or equal to the node's length (" + String::number(toCharacterData(n)->length()) + ")."); return 0; case Node::DOCUMENT_FRAGMENT_NODE: case Node::DOCUMENT_NODE: case Node::ELEMENT_NODE: { if (!offset) return 0; Node* childBefore = NodeTraversal::childAt(*n, offset - 1); if (!childBefore) exceptionState.ThrowDOMException(IndexSizeError, "There is no child at offset " + String::number(offset) + "."); return childBefore; } } ASSERT_NOT_REACHED(); return 0; } void Range::checkNodeBA(Node* n, ExceptionState& exceptionState) const { if (!n) { exceptionState.ThrowDOMException(NotFoundError, "The node provided is null."); return; } // InvalidNodeTypeError: Raised if the root container of refNode is not an // Attr, Document, DocumentFragment node, // or if refNode is a Document, DocumentFragment, Attr, Entity, or Notation node. if (!n->parentNode()) { exceptionState.ThrowDOMException(InvalidNodeTypeError, "the given Node has no parent."); return; } switch (n->nodeType()) { case Node::DOCUMENT_FRAGMENT_NODE: case Node::DOCUMENT_NODE: exceptionState.ThrowDOMException(InvalidNodeTypeError, "The node provided is of type '" + n->nodeName() + "'."); return; case Node::ELEMENT_NODE: case Node::TEXT_NODE: break; } Node* root = n; while (ContainerNode* parent = root->parentNode()) root = parent; switch (root->nodeType()) { case Node::DOCUMENT_NODE: case Node::DOCUMENT_FRAGMENT_NODE: case Node::ELEMENT_NODE: break; case Node::TEXT_NODE: exceptionState.ThrowDOMException(InvalidNodeTypeError, "The node provided is of type '" + n->nodeName() + "'."); return; } } PassRefPtr Range::cloneRange() const { return Range::create(*m_ownerDocument.get(), m_start.container(), m_start.offset(), m_end.container(), m_end.offset()); } void Range::setStartAfter(Node* refNode, ExceptionState& exceptionState) { checkNodeBA(refNode, exceptionState); if (exceptionState.had_exception()) return; setStart(refNode->parentNode(), refNode->nodeIndex() + 1, exceptionState); } void Range::setEndBefore(Node* refNode, ExceptionState& exceptionState) { checkNodeBA(refNode, exceptionState); if (exceptionState.had_exception()) return; setEnd(refNode->parentNode(), refNode->nodeIndex(), exceptionState); } void Range::setEndAfter(Node* refNode, ExceptionState& exceptionState) { checkNodeBA(refNode, exceptionState); if (exceptionState.had_exception()) return; setEnd(refNode->parentNode(), refNode->nodeIndex() + 1, exceptionState); } void Range::selectNode(Node* refNode, ExceptionState& exceptionState) { if (!refNode) { exceptionState.ThrowDOMException(NotFoundError, "The node provided is null."); return; } if (!refNode->parentNode()) { exceptionState.ThrowDOMException(InvalidNodeTypeError, "the given Node has no parent."); return; } // InvalidNodeTypeError: Raised if an ancestor of refNode is an Entity, Notation or // DocumentType node or if refNode is a Document, DocumentFragment, Attr, Entity, or Notation // node. switch (refNode->nodeType()) { case Node::ELEMENT_NODE: case Node::TEXT_NODE: break; case Node::DOCUMENT_FRAGMENT_NODE: case Node::DOCUMENT_NODE: exceptionState.ThrowDOMException(InvalidNodeTypeError, "The node provided is of type '" + refNode->nodeName() + "'."); return; } if (m_ownerDocument != refNode->document()) setDocument(refNode->document()); setStartBefore(refNode); setEndAfter(refNode); } void Range::selectNodeContents(Node* refNode, ExceptionState& exceptionState) { if (!refNode) { exceptionState.ThrowDOMException(NotFoundError, "The node provided is null."); return; } if (m_ownerDocument != refNode->document()) setDocument(refNode->document()); m_start.setToStartOfNode(*refNode); m_end.setToEndOfNode(*refNode); } void Range::surroundContents(PassRefPtr passNewParent, ExceptionState& exceptionState) { RefPtr newParent = passNewParent; if (!newParent) { exceptionState.ThrowDOMException(NotFoundError, "The node provided is null."); return; } // InvalidStateError: Raised if the Range partially selects a non-Text node. Node* startNonTextContainer = m_start.container(); if (startNonTextContainer->nodeType() == Node::TEXT_NODE) startNonTextContainer = startNonTextContainer->parentNode(); Node* endNonTextContainer = m_end.container(); if (endNonTextContainer->nodeType() == Node::TEXT_NODE) endNonTextContainer = endNonTextContainer->parentNode(); if (startNonTextContainer != endNonTextContainer) { exceptionState.ThrowDOMException(InvalidStateError, "The Range has partially selected a non-Text node."); return; } // InvalidNodeTypeError: Raised if node is an Attr, Entity, DocumentType, Notation, // Document, or DocumentFragment node. switch (newParent->nodeType()) { case Node::DOCUMENT_FRAGMENT_NODE: case Node::DOCUMENT_NODE: exceptionState.ThrowDOMException(InvalidNodeTypeError, "The node provided is of type '" + newParent->nodeName() + "'."); return; case Node::ELEMENT_NODE: case Node::TEXT_NODE: break; } // Raise a HierarchyRequestError if m_start.container() doesn't accept children like newParent. Node* parentOfNewParent = m_start.container(); // If m_start.container() is a character data node, it will be split and it will be its parent that will // need to accept newParent (or in the case of a comment, it logically "would" be inserted into the parent, // although this will fail below for another reason). if (parentOfNewParent->isTextNode()) parentOfNewParent = parentOfNewParent->parentNode(); if (!parentOfNewParent) { exceptionState.ThrowDOMException(HierarchyRequestError, "The container node is a detached character data node; no parent node is available for insertion."); return; } if (newParent->contains(m_start.container())) { exceptionState.ThrowDOMException(HierarchyRequestError, "The node provided contains the insertion point; it may not be inserted into itself."); return; } // FIXME: Do we need a check if the node would end up with a child node of a type not // allowed by the type of node? while (Node* n = newParent->firstChild()) { toContainerNode(newParent)->removeChild(n, exceptionState); if (exceptionState.had_exception()) return; } RefPtr fragment = extractContents(exceptionState); if (exceptionState.had_exception()) return; insertNode(newParent, exceptionState); if (exceptionState.had_exception()) return; newParent->appendChild(fragment.release(), exceptionState); if (exceptionState.had_exception()) return; selectNode(newParent.get(), exceptionState); } void Range::setStartBefore(Node* refNode, ExceptionState& exceptionState) { checkNodeBA(refNode, exceptionState); if (exceptionState.had_exception()) return; setStart(refNode->parentNode(), refNode->nodeIndex(), exceptionState); } void Range::checkExtractPrecondition(ExceptionState&) { // FIXME: Remove. } Node* Range::firstNode() const { if (m_start.container()->offsetInCharacters()) return m_start.container(); if (Node* child = NodeTraversal::childAt(*m_start.container(), m_start.offset())) return child; if (!m_start.offset()) return m_start.container(); return NodeTraversal::nextSkippingChildren(*m_start.container()); } Node* Range::pastLastNode() const { if (m_end.container()->offsetInCharacters()) return NodeTraversal::nextSkippingChildren(*m_end.container()); if (Node* child = NodeTraversal::childAt(*m_end.container(), m_end.offset())) return child; return NodeTraversal::nextSkippingChildren(*m_end.container()); } IntRect Range::boundingBox() const { IntRect result; Vector rects; textRects(rects); const size_t n = rects.size(); for (size_t i = 0; i < n; ++i) result.unite(rects[i]); return result; } void Range::textRects(Vector& rects, bool useSelectionHeight) const { Node* startContainer = m_start.container(); ASSERT(startContainer); Node* endContainer = m_end.container(); ASSERT(endContainer); Node* stopNode = pastLastNode(); for (Node* node = firstNode(); node != stopNode; node = NodeTraversal::next(*node)) { RenderObject* r = node->renderer(); if (!r || !r->isText()) continue; RenderText* renderText = toRenderText(r); int startOffset = node == startContainer ? m_start.offset() : 0; int endOffset = node == endContainer ? m_end.offset() : std::numeric_limits::max(); renderText->absoluteRectsForRange(rects, startOffset, endOffset, useSelectionHeight); } } void Range::textQuads(Vector& quads, bool useSelectionHeight) const { Node* startContainer = m_start.container(); ASSERT(startContainer); Node* endContainer = m_end.container(); ASSERT(endContainer); Node* stopNode = pastLastNode(); for (Node* node = firstNode(); node != stopNode; node = NodeTraversal::next(*node)) { RenderObject* r = node->renderer(); if (!r || !r->isText()) continue; RenderText* renderText = toRenderText(r); int startOffset = node == startContainer ? m_start.offset() : 0; int endOffset = node == endContainer ? m_end.offset() : std::numeric_limits::max(); renderText->absoluteQuadsForRange(quads, startOffset, endOffset, useSelectionHeight); } } #ifndef NDEBUG void Range::formatForDebugger(char* buffer, unsigned length) const { StringBuilder result; const int FormatBufferSize = 1024; char s[FormatBufferSize]; result.appendLiteral("from offset "); result.appendNumber(m_start.offset()); result.appendLiteral(" of "); m_start.container()->formatForDebugger(s, FormatBufferSize); result.append(s); result.appendLiteral(" to offset "); result.appendNumber(m_end.offset()); result.appendLiteral(" of "); m_end.container()->formatForDebugger(s, FormatBufferSize); result.append(s); strncpy(buffer, result.toString().utf8().data(), length - 1); } #endif bool areRangesEqual(const Range* a, const Range* b) { if (a == b) return true; if (!a || !b) return false; return a->startPosition() == b->startPosition() && a->endPosition() == b->endPosition(); } PassRefPtr rangeOfContents(Node* node) { ASSERT(node); RefPtr range = Range::create(node->document()); range->selectNodeContents(node, IGNORE_EXCEPTION); return range.release(); } static inline void boundaryNodeChildrenChanged(RangeBoundaryPoint& boundary, ContainerNode* container) { if (!boundary.childBefore()) return; if (boundary.container() != container) return; boundary.invalidateOffset(); } void Range::nodeChildrenChanged(ContainerNode* container) { ASSERT(container); ASSERT(container->document() == m_ownerDocument); boundaryNodeChildrenChanged(m_start, container); boundaryNodeChildrenChanged(m_end, container); } static inline void boundaryNodeChildrenWillBeRemoved(RangeBoundaryPoint& boundary, ContainerNode& container) { for (Node* nodeToBeRemoved = container.firstChild(); nodeToBeRemoved; nodeToBeRemoved = nodeToBeRemoved->nextSibling()) { if (boundary.childBefore() == nodeToBeRemoved) { boundary.setToStartOfNode(container); return; } for (Node* n = boundary.container(); n; n = n->parentNode()) { if (n == nodeToBeRemoved) { boundary.setToStartOfNode(container); return; } } } } void Range::nodeChildrenWillBeRemoved(ContainerNode& container) { ASSERT(container.document() == m_ownerDocument); boundaryNodeChildrenWillBeRemoved(m_start, container); boundaryNodeChildrenWillBeRemoved(m_end, container); } static inline void boundaryNodeWillBeRemoved(RangeBoundaryPoint& boundary, Node& nodeToBeRemoved) { if (boundary.childBefore() == nodeToBeRemoved) { boundary.childBeforeWillBeRemoved(); return; } for (Node* n = boundary.container(); n; n = n->parentNode()) { if (n == nodeToBeRemoved) { boundary.setToBeforeChild(nodeToBeRemoved); return; } } } void Range::nodeWillBeRemoved(Node& node) { ASSERT(node.document() == m_ownerDocument); ASSERT(node != m_ownerDocument.get()); // FIXME: Once DOMNodeRemovedFromDocument mutation event removed, we // should change following if-statement to ASSERT(!node->parentNode). if (!node.parentNode()) return; boundaryNodeWillBeRemoved(m_start, node); boundaryNodeWillBeRemoved(m_end, node); } 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::didInsertText(Node* text, unsigned offset, unsigned length) { ASSERT(text); ASSERT(text->document() == m_ownerDocument); 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::didRemoveText(Node* text, unsigned offset, unsigned length) { ASSERT(text); ASSERT(text->document() == m_ownerDocument); boundaryTextRemoved(m_start, text, offset, length); boundaryTextRemoved(m_end, text, offset, length); } static inline void boundaryTextNodesMerged(RangeBoundaryPoint& boundary, const 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() == oldNode.index()) boundary.set(oldNode.node().previousSibling(), offset, 0); } void Range::didMergeTextNodes(const NodeWithIndex& oldNode, unsigned offset) { ASSERT(oldNode.node().document() == m_ownerDocument); 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); } void Range::updateOwnerDocumentIfNeeded() { ASSERT(m_start.container()); ASSERT(m_end.container()); Document& newDocument = m_start.container()->document(); ASSERT(newDocument == m_end.container()->document()); if (newDocument == m_ownerDocument) return; m_ownerDocument->detachRange(this); m_ownerDocument = &newDocument; m_ownerDocument->attachRange(this); } static inline void boundaryTextNodeSplit(RangeBoundaryPoint& boundary, Text& oldNode) { Node* boundaryContainer = boundary.container(); unsigned boundaryOffset = boundary.offset(); if (boundary.childBefore() == &oldNode) boundary.set(boundaryContainer, boundaryOffset + 1, oldNode.nextSibling()); else if (boundary.container() == &oldNode && boundaryOffset > oldNode.length()) boundary.set(oldNode.nextSibling(), boundaryOffset - oldNode.length(), 0); } void Range::didSplitTextNode(Text& oldNode) { ASSERT(oldNode.document() == m_ownerDocument); ASSERT(oldNode.parentNode()); ASSERT(oldNode.nextSibling()); ASSERT(oldNode.nextSibling()->isTextNode()); boundaryTextNodeSplit(m_start, oldNode); boundaryTextNodeSplit(m_end, oldNode); ASSERT(boundaryPointsValid()); } void Range::expand(const String& unit, ExceptionState& exceptionState) { VisiblePosition start(startPosition()); VisiblePosition end(endPosition()); 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; setStart(start.deepEquivalent().containerNode(), start.deepEquivalent().computeOffsetInContainerNode(), exceptionState); setEnd(end.deepEquivalent().containerNode(), end.deepEquivalent().computeOffsetInContainerNode(), exceptionState); } PassRefPtr Range::getClientRects() const { m_ownerDocument->updateLayout(); Vector quads; getBorderAndTextQuads(quads); return ClientRectList::create(quads); } PassRefPtr Range::getBoundingClientRect() const { return ClientRect::create(boundingRect()); } void Range::getBorderAndTextQuads(Vector& quads) const { Node* startContainer = m_start.container(); Node* endContainer = m_end.container(); Node* stopNode = pastLastNode(); HashSet > nodeSet; for (Node* node = firstNode(); node != stopNode; node = NodeTraversal::next(*node)) { if (node->isElementNode()) nodeSet.add(node); } for (Node* node = firstNode(); node != stopNode; node = NodeTraversal::next(*node)) { if (node->isElementNode()) { if (!nodeSet.contains(node->parentNode())) { if (RenderBoxModelObject* renderBoxModelObject = toElement(node)->renderBoxModelObject()) { Vector elementQuads; renderBoxModelObject->absoluteQuads(elementQuads); quads.appendVector(elementQuads); } } } else if (node->isTextNode()) { if (RenderText* renderText = toText(node)->renderer()) { int startOffset = (node == startContainer) ? m_start.offset() : 0; int endOffset = (node == endContainer) ? m_end.offset() : INT_MAX; Vector textQuads; renderText->absoluteQuadsForRange(textQuads, startOffset, endOffset); quads.appendVector(textQuads); } } } } FloatRect Range::boundingRect() const { m_ownerDocument->updateLayout(); Vector quads; getBorderAndTextQuads(quads); if (quads.isEmpty()) return FloatRect(); FloatRect result; for (size_t i = 0; i < quads.size(); ++i) result.unite(quads[i].boundingBox()); return result; } } // namespace blink #ifndef NDEBUG void showTree(const blink::Range* range) { if (range && range->boundaryPointsValid()) { range->startContainer()->showTreeAndMark(range->startContainer(), "S", range->endContainer(), "E"); fprintf(stderr, "start offset: %d, end offset: %d\n", range->startOffset(), range->endOffset()); } } #endif