/* * (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 Apple Inc. 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 "RangeException.h" #include "CString.h" #include "ClientRect.h" #include "ClientRectList.h" #include "DocumentFragment.h" #include "FrameView.h" #include "HTMLElement.h" #include "NodeWithIndex.h" #include "ProcessingInstruction.h" #include "Text.h" #include "TextIterator.h" #include "VisiblePosition.h" #include "htmlediting.h" #include "markup.h" #include "visible_units.h" #include <stdio.h> #include <wtf/RefCountedLeakCounter.h> namespace WebCore { using namespace std; #ifndef NDEBUG static WTF::RefCountedLeakCounter rangeCounter("Range"); #endif inline Range::Range(PassRefPtr<Document> ownerDocument) : m_ownerDocument(ownerDocument) , m_start(m_ownerDocument) , m_end(m_ownerDocument) { #ifndef NDEBUG rangeCounter.increment(); #endif m_ownerDocument->attachRange(this); } PassRefPtr<Range> Range::create(PassRefPtr<Document> ownerDocument) { return adoptRef(new Range(ownerDocument)); } inline Range::Range(PassRefPtr<Document> ownerDocument, PassRefPtr<Node> startContainer, int startOffset, PassRefPtr<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. ExceptionCode ec = 0; setStart(startContainer, startOffset, ec); ASSERT(!ec); setEnd(endContainer, endOffset, ec); ASSERT(!ec); } PassRefPtr<Range> Range::create(PassRefPtr<Document> ownerDocument, PassRefPtr<Node> startContainer, int startOffset, PassRefPtr<Node> endContainer, int endOffset) { return adoptRef(new Range(ownerDocument, startContainer, startOffset, endContainer, endOffset)); } PassRefPtr<Range> Range::create(PassRefPtr<Document> ownerDocument, const Position& start, const Position& end) { // FIXME: we shouldn't be using deprecatedEditingOffset here return adoptRef(new Range(ownerDocument, start.node(), start.deprecatedEditingOffset(), end.node(), end.deprecatedEditingOffset())); } Range::~Range() { // Always detach (even if we've already detached) to fix https://bugs.webkit.org/show_bug.cgi?id=26044 m_ownerDocument->detachRange(this); #ifndef NDEBUG rangeCounter.decrement(); #endif } Node* Range::startContainer(ExceptionCode& ec) const { if (!m_start.container()) { ec = INVALID_STATE_ERR; return 0; } return m_start.container(); } int Range::startOffset(ExceptionCode& ec) const { if (!m_start.container()) { ec = INVALID_STATE_ERR; return 0; } return m_start.offset(); } Node* Range::endContainer(ExceptionCode& ec) const { if (!m_start.container()) { ec = INVALID_STATE_ERR; return 0; } return m_end.container(); } int Range::endOffset(ExceptionCode& ec) const { if (!m_start.container()) { ec = INVALID_STATE_ERR; return 0; } return m_end.offset(); } Node* Range::commonAncestorContainer(ExceptionCode& ec) const { if (!m_start.container()) { ec = INVALID_STATE_ERR; return 0; } 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; } bool Range::collapsed(ExceptionCode& ec) const { if (!m_start.container()) { ec = INVALID_STATE_ERR; return 0; } return m_start == m_end; } void Range::setStart(PassRefPtr<Node> refNode, int offset, ExceptionCode& ec) { if (!m_start.container()) { ec = INVALID_STATE_ERR; return; } if (!refNode) { ec = NOT_FOUND_ERR; return; } if (refNode->document() != m_ownerDocument) { ec = WRONG_DOCUMENT_ERR; return; } ec = 0; Node* childNode = checkNodeWOffset(refNode.get(), offset, ec); if (ec) return; m_start.set(refNode, offset, childNode); // check if different root container Node* endRootContainer = m_end.container(); while (endRootContainer->parentNode()) endRootContainer = endRootContainer->parentNode(); Node* startRootContainer = m_start.container(); while (startRootContainer->parentNode()) startRootContainer = startRootContainer->parentNode(); if (startRootContainer != endRootContainer) collapse(true, ec); // check if new start after end else if (compareBoundaryPoints(m_start, m_end) > 0) collapse(true, ec); } void Range::setEnd(PassRefPtr<Node> refNode, int offset, ExceptionCode& ec) { if (!m_start.container()) { ec = INVALID_STATE_ERR; return; } if (!refNode) { ec = NOT_FOUND_ERR; return; } if (refNode->document() != m_ownerDocument) { ec = WRONG_DOCUMENT_ERR; return; } ec = 0; Node* childNode = checkNodeWOffset(refNode.get(), offset, ec); if (ec) return; m_end.set(refNode, offset, childNode); // check if different root container Node* endRootContainer = m_end.container(); while (endRootContainer->parentNode()) endRootContainer = endRootContainer->parentNode(); Node* startRootContainer = m_start.container(); while (startRootContainer->parentNode()) startRootContainer = startRootContainer->parentNode(); if (startRootContainer != endRootContainer) collapse(false, ec); // check if new end before start if (compareBoundaryPoints(m_start, m_end) > 0) collapse(false, ec); } void Range::collapse(bool toStart, ExceptionCode& ec) { if (!m_start.container()) { ec = INVALID_STATE_ERR; return; } if (toStart) m_end = m_start; else m_start = m_end; } bool Range::isPointInRange(Node* refNode, int offset, ExceptionCode& ec) { if (!m_start.container()) { ec = INVALID_STATE_ERR; return false; } if (!refNode) { ec = HIERARCHY_REQUEST_ERR; return false; } if (!refNode->attached()) { // Firefox doesn't throw an exception for this case; it returns false. return false; } if (refNode->document() != m_ownerDocument) { ec = WRONG_DOCUMENT_ERR; return false; } ec = 0; checkNodeWOffset(refNode, offset, ec); if (ec) return false; return compareBoundaryPoints(refNode, offset, m_start.container(), m_start.offset()) >= 0 && compareBoundaryPoints(refNode, offset, m_end.container(), m_end.offset()) <= 0; } short Range::comparePoint(Node* refNode, int offset, ExceptionCode& ec) 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 (!m_start.container()) { ec = INVALID_STATE_ERR; return 0; } if (!refNode) { ec = HIERARCHY_REQUEST_ERR; return 0; } if (!refNode->attached() || refNode->document() != m_ownerDocument) { ec = WRONG_DOCUMENT_ERR; return 0; } ec = 0; checkNodeWOffset(refNode, offset, ec); if (ec) return 0; // compare to start, and point comes before if (compareBoundaryPoints(refNode, offset, m_start.container(), m_start.offset()) < 0) return -1; // compare to end, and point comes after if (compareBoundaryPoints(refNode, offset, m_end.container(), m_end.offset()) > 0) return 1; // point is in the middle of this range, or on the boundary points return 0; } Range::CompareResults Range::compareNode(Node* refNode, ExceptionCode& ec) 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) { ec = NOT_FOUND_ERR; return NODE_BEFORE; } if (!m_start.container() && refNode->attached()) { ec = INVALID_STATE_ERR; return NODE_BEFORE; } if (m_start.container() && !refNode->attached()) { // 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; } Node* 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 ec = NOT_FOUND_ERR; return NODE_BEFORE; } if (comparePoint(parentNode, nodeIndex, ec) < 0) { // starts before if (comparePoint(parentNode, nodeIndex + 1, ec) > 0) // ends after the range return NODE_BEFORE_AND_AFTER; return NODE_BEFORE; // ends before or in the range } else { // starts at or after the range start if (comparePoint(parentNode, nodeIndex + 1, ec) > 0) // ends after the range return NODE_AFTER; return NODE_INSIDE; // ends inside the range } } short Range::compareBoundaryPoints(CompareHow how, const Range* sourceRange, ExceptionCode& ec) const { if (!m_start.container()) { ec = INVALID_STATE_ERR; return 0; } if (!sourceRange) { ec = NOT_FOUND_ERR; return 0; } ec = 0; Node* thisCont = commonAncestorContainer(ec); if (ec) return 0; Node* sourceCont = sourceRange->commonAncestorContainer(ec); if (ec) return 0; if (thisCont->document() != sourceCont->document()) { ec = WRONG_DOCUMENT_ERR; 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 ec = WRONG_DOCUMENT_ERR; return 0; } switch (how) { case START_TO_START: return compareBoundaryPoints(m_start, sourceRange->m_start); case START_TO_END: return compareBoundaryPoints(m_end, sourceRange->m_start); case END_TO_END: return compareBoundaryPoints(m_end, sourceRange->m_end); case END_TO_START: return compareBoundaryPoints(m_start, sourceRange->m_end); } ec = SYNTAX_ERR; return 0; } short Range::compareBoundaryPoints(Node* containerA, int offsetA, Node* containerB, int offsetB) { 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) 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) { return compareBoundaryPoints(boundaryA.container(), boundaryA.offset(), boundaryB.container(), boundaryB.offset()); } bool Range::boundaryPointsValid() const { return m_start.container() && compareBoundaryPoints(m_start, m_end) <= 0; } void Range::deleteContents(ExceptionCode& ec) { checkDeleteExtract(ec); if (ec) return; processContents(DELETE_CONTENTS, ec); } bool Range::intersectsNode(Node* refNode, ExceptionCode& ec) { // http://developer.mozilla.org/en/docs/DOM:range.intersectsNode // Returns a bool if the node intersects the range. if (!refNode) { ec = NOT_FOUND_ERR; return false; } if ((!m_start.container() && refNode->attached()) || (m_start.container() && !refNode->attached()) || refNode->document() != m_ownerDocument) { // Firefox doesn't throw an exception for these cases; it returns false. return false; } Node* 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 ec = NOT_FOUND_ERR; return false; } if (comparePoint(parentNode, nodeIndex, ec) < 0 && // starts before start comparePoint(parentNode, nodeIndex + 1, ec) < 0) { // ends before start return false; } else if (comparePoint(parentNode, nodeIndex, ec) > 0 && // starts after end comparePoint(parentNode, nodeIndex + 1, ec) > 0) { // ends after end return false; } return true; // all other cases } PassRefPtr<DocumentFragment> Range::processContents(ActionType action, ExceptionCode& ec) { // FIXME: To work properly with mutation events, we will have to take into account // situations where the tree is being transformed while we work on it - ugh! RefPtr<DocumentFragment> fragment; if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) fragment = DocumentFragment::create(m_ownerDocument.get()); ec = 0; if (collapsed(ec)) return fragment.release(); if (ec) return 0; Node* commonRoot = commonAncestorContainer(ec); if (ec) return 0; ASSERT(commonRoot); // what is the highest node that partially selects the start of the range? Node* partialStart = 0; if (m_start.container() != commonRoot) { partialStart = m_start.container(); while (partialStart->parentNode() != commonRoot) partialStart = partialStart->parentNode(); } // what is the highest node that partially selects the end of the range? Node* partialEnd = 0; if (m_end.container() != commonRoot) { partialEnd = m_end.container(); while (partialEnd->parentNode() != commonRoot) partialEnd = partialEnd->parentNode(); } // Simple case: the start and end containers are the same. We just grab // everything >= start offset and < end offset if (m_start.container() == m_end.container()) { Node::NodeType startNodeType = m_start.container()->nodeType(); if (startNodeType == Node::TEXT_NODE || startNodeType == Node::CDATA_SECTION_NODE || startNodeType == Node::COMMENT_NODE) { if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) { RefPtr<CharacterData> c = static_pointer_cast<CharacterData>(m_start.container()->cloneNode(true)); c->deleteData(m_end.offset(), c->length() - m_end.offset(), ec); c->deleteData(0, m_start.offset(), ec); fragment->appendChild(c.release(), ec); } if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) static_cast<CharacterData*>(m_start.container())->deleteData(m_start.offset(), m_end.offset() - m_start.offset(), ec); } else if (startNodeType == Node::PROCESSING_INSTRUCTION_NODE) { if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) { RefPtr<ProcessingInstruction> c = static_pointer_cast<ProcessingInstruction>(m_start.container()->cloneNode(true)); c->setData(c->data().substring(m_start.offset(), m_end.offset() - m_start.offset()), ec); fragment->appendChild(c.release(), ec); } if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) { ProcessingInstruction* pi = static_cast<ProcessingInstruction*>(m_start.container()); String data(pi->data()); data.remove(m_start.offset(), m_end.offset() - m_start.offset()); pi->setData(data, ec); } } else { Node* n = m_start.container()->firstChild(); int i; for (i = 0; n && i < m_start.offset(); i++) // skip until start offset n = n->nextSibling(); int endOffset = m_end.offset(); while (n && i < endOffset) { // delete until end offset Node* next = n->nextSibling(); if (action == EXTRACT_CONTENTS) fragment->appendChild(n, ec); // will remove n from its parent else if (action == CLONE_CONTENTS) fragment->appendChild(n->cloneNode(true), ec); else m_start.container()->removeChild(n, ec); n = next; i++; } } return fragment.release(); } // Complex case: 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. RefPtr<Node> leftContents; if (m_start.container() != commonRoot) { // process the left-hand side of the range, up until the last ancestor of // start container before commonRoot Node::NodeType startNodeType = m_start.container()->nodeType(); if (startNodeType == Node::TEXT_NODE || startNodeType == Node::CDATA_SECTION_NODE || startNodeType == Node::COMMENT_NODE) { if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) { RefPtr<CharacterData> c = static_pointer_cast<CharacterData>(m_start.container()->cloneNode(true)); c->deleteData(0, m_start.offset(), ec); leftContents = c.release(); } if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) static_cast<CharacterData*>(m_start.container())->deleteData( m_start.offset(), static_cast<CharacterData*>(m_start.container())->length() - m_start.offset(), ec); } else if (startNodeType == Node::PROCESSING_INSTRUCTION_NODE) { if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) { RefPtr<ProcessingInstruction> c = static_pointer_cast<ProcessingInstruction>(m_start.container()->cloneNode(true)); c->setData(c->data().substring(m_start.offset()), ec); leftContents = c.release(); } if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) { ProcessingInstruction* pi = static_cast<ProcessingInstruction*>(m_start.container()); String data(pi->data()); pi->setData(data.left(m_start.offset()), ec); } } else { if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) leftContents = m_start.container()->cloneNode(false); Node* n = m_start.container()->firstChild(); for (int i = 0; n && i < m_start.offset(); i++) // skip until start offset n = n->nextSibling(); while (n) { // process until end Node* next = n->nextSibling(); if (action == EXTRACT_CONTENTS) leftContents->appendChild(n, ec); // will remove n from start container else if (action == CLONE_CONTENTS) leftContents->appendChild(n->cloneNode(true), ec); else m_start.container()->removeChild(n, ec); n = next; } } Node* leftParent = m_start.container()->parentNode(); Node* n = m_start.container()->nextSibling(); for (; leftParent != commonRoot; leftParent = leftParent->parentNode()) { if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) { RefPtr<Node> leftContentsParent = leftParent->cloneNode(false); leftContentsParent->appendChild(leftContents, ec); leftContents = leftContentsParent; } Node* next; for (; n; n = next) { next = n->nextSibling(); if (action == EXTRACT_CONTENTS) leftContents->appendChild(n, ec); // will remove n from leftParent else if (action == CLONE_CONTENTS) leftContents->appendChild(n->cloneNode(true), ec); else leftParent->removeChild(n, ec); } n = leftParent->nextSibling(); } } RefPtr<Node> rightContents; if (m_end.container() != commonRoot) { // delete the right-hand side of the range, up until the last ancestor of // end container before commonRoot Node::NodeType endNodeType = m_end.container()->nodeType(); if (endNodeType == Node::TEXT_NODE || endNodeType == Node::CDATA_SECTION_NODE || endNodeType == Node::COMMENT_NODE) { if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) { RefPtr<CharacterData> c = static_pointer_cast<CharacterData>(m_end.container()->cloneNode(true)); c->deleteData(m_end.offset(), static_cast<CharacterData*>(m_end.container())->length() - m_end.offset(), ec); rightContents = c; } if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) static_cast<CharacterData*>(m_end.container())->deleteData(0, m_end.offset(), ec); } else if (endNodeType == Node::PROCESSING_INSTRUCTION_NODE) { if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) { RefPtr<ProcessingInstruction> c = static_pointer_cast<ProcessingInstruction>(m_end.container()->cloneNode(true)); c->setData(c->data().left(m_end.offset()), ec); rightContents = c.release(); } if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) { ProcessingInstruction* pi = static_cast<ProcessingInstruction*>(m_end.container()); pi->setData(pi->data().substring(m_end.offset()), ec); } } else { if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) rightContents = m_end.container()->cloneNode(false); Node* n = m_end.container()->firstChild(); if (n && m_end.offset()) { for (int i = 0; i + 1 < m_end.offset(); i++) { // skip to end.offset() Node* next = n->nextSibling(); if (!next) break; n = next; } Node* prev; for (; n; n = prev) { prev = n->previousSibling(); if (action == EXTRACT_CONTENTS) rightContents->insertBefore(n, rightContents->firstChild(), ec); // will remove n from its parent else if (action == CLONE_CONTENTS) rightContents->insertBefore(n->cloneNode(true), rightContents->firstChild(), ec); else m_end.container()->removeChild(n, ec); } } } Node* rightParent = m_end.container()->parentNode(); Node* n = m_end.container()->previousSibling(); for (; rightParent != commonRoot; rightParent = rightParent->parentNode()) { if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) { RefPtr<Node> rightContentsParent = rightParent->cloneNode(false); rightContentsParent->appendChild(rightContents, ec); rightContents = rightContentsParent; } Node* prev; for (; n; n = prev) { prev = n->previousSibling(); if (action == EXTRACT_CONTENTS) rightContents->insertBefore(n, rightContents->firstChild(), ec); // will remove n from its parent else if (action == CLONE_CONTENTS) rightContents->insertBefore(n->cloneNode(true), rightContents->firstChild(), ec); else rightParent->removeChild(n, ec); } n = rightParent->previousSibling(); } } // delete all children of commonRoot between the start and end container Node* processStart; // child of commonRoot if (m_start.container() == commonRoot) { processStart = m_start.container()->firstChild(); for (int i = 0; i < m_start.offset(); i++) processStart = processStart->nextSibling(); } else { processStart = m_start.container(); while (processStart->parentNode() != commonRoot) processStart = processStart->parentNode(); processStart = processStart->nextSibling(); } Node* processEnd; // child of commonRoot if (m_end.container() == commonRoot) { processEnd = m_end.container()->firstChild(); for (int i = 0; i < m_end.offset(); i++) processEnd = processEnd->nextSibling(); } else { processEnd = m_end.container(); while (processEnd->parentNode() != commonRoot) processEnd = processEnd->parentNode(); } // 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) setStart(partialStart->parentNode(), partialStart->nodeIndex() + 1, ec); else if (partialEnd) setStart(partialEnd->parentNode(), partialEnd->nodeIndex(), ec); if (ec) return 0; m_end = m_start; } // 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, ec); Node* next; Node* n; if (processStart) { for (n = processStart; n && n != processEnd; n = next) { next = n->nextSibling(); if (action == EXTRACT_CONTENTS) fragment->appendChild(n, ec); // will remove from commonRoot else if (action == CLONE_CONTENTS) fragment->appendChild(n->cloneNode(true), ec); else commonRoot->removeChild(n, ec); } } if ((action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) && rightContents) fragment->appendChild(rightContents, ec); return fragment.release(); } PassRefPtr<DocumentFragment> Range::extractContents(ExceptionCode& ec) { checkDeleteExtract(ec); if (ec) return 0; return processContents(EXTRACT_CONTENTS, ec); } PassRefPtr<DocumentFragment> Range::cloneContents(ExceptionCode& ec) { if (!m_start.container()) { ec = INVALID_STATE_ERR; return 0; } return processContents(CLONE_CONTENTS, ec); } void Range::insertNode(PassRefPtr<Node> prpNewNode, ExceptionCode& ec) { RefPtr<Node> newNode = prpNewNode; ec = 0; if (!m_start.container()) { ec = INVALID_STATE_ERR; return; } if (!newNode) { ec = NOT_FOUND_ERR; return; } // NO_MODIFICATION_ALLOWED_ERR: Raised if an ancestor container of either boundary-point of // the Range is read-only. if (containedByReadOnly()) { ec = NO_MODIFICATION_ALLOWED_ERR; return; } // WRONG_DOCUMENT_ERR: Raised if newParent and the container of the start of the Range were // not created from the same document. if (newNode->document() != m_start.container()->document()) { ec = WRONG_DOCUMENT_ERR; return; } // HIERARCHY_REQUEST_ERR: 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()) { ec = HIERARCHY_REQUEST_ERR; 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 = newNode->firstChild(); c; c = c->nextSibling()) { if (!checkAgainst->childTypeAllowed(c->nodeType())) { ec = HIERARCHY_REQUEST_ERR; return; } ++numNewChildren; } } else { numNewChildren = 1; if (!checkAgainst->childTypeAllowed(newNodeType)) { ec = HIERARCHY_REQUEST_ERR; return; } } for (Node* n = m_start.container(); n; n = n->parentNode()) { if (n == newNode) { ec = HIERARCHY_REQUEST_ERR; return; } } // INVALID_NODE_TYPE_ERR: Raised if newNode is an Attr, Entity, Notation, or Document node. if (newNodeType == Node::ATTRIBUTE_NODE || newNodeType == Node::ENTITY_NODE || newNodeType == Node::NOTATION_NODE || newNodeType == Node::DOCUMENT_NODE) { ec = RangeException::INVALID_NODE_TYPE_ERR; return; } bool collapsed = m_start == m_end; if (startIsText) { RefPtr<Text> newText = static_cast<Text*>(m_start.container())->splitText(m_start.offset(), ec); if (ec) return; m_start.container()->parentNode()->insertBefore(newNode.release(), newText.get(), ec); if (ec) return; // This special case doesn't seem to match the DOM specification, but it's currently required // to pass Acid3. We might later decide to remove this. if (collapsed) m_end.setToBeforeChild(newText.get()); } else { RefPtr<Node> lastChild; if (collapsed) lastChild = (newNodeType == Node::DOCUMENT_FRAGMENT_NODE) ? newNode->lastChild() : newNode; int startOffset = m_start.offset(); m_start.container()->insertBefore(newNode.release(), m_start.container()->childNode(startOffset), ec); if (ec) return; // This special case doesn't seem to match the DOM specification, but it's currently required // to pass Acid3. We might later decide to remove this. if (collapsed) m_end.set(m_start.container(), startOffset + numNewChildren, lastChild.get()); } } String Range::toString(ExceptionCode& ec) const { if (!m_start.container()) { ec = INVALID_STATE_ERR; return String(); } Vector<UChar> result; Node* pastLast = pastLastNode(); for (Node* n = firstNode(); n != pastLast; n = n->traverseNextNode()) { if (n->nodeType() == Node::TEXT_NODE || n->nodeType() == Node::CDATA_SECTION_NODE) { String data = static_cast<CharacterData*>(n)->data(); int length = data.length(); int start = (n == m_start.container()) ? min(max(0, m_start.offset()), length) : 0; int end = (n == m_end.container()) ? min(max(start, m_end.offset()), length) : length; result.append(data.characters() + start, end - start); } } return String::adopt(result); } String Range::toHTML() const { return createMarkup(this); } String Range::text() const { if (!m_start.container()) return String(); // We need to update layout, since plainText uses line boxes in the render tree. // FIXME: As with innerText, we'd like this to work even if there are no render objects. m_start.container()->document()->updateLayout(); return plainText(this); } PassRefPtr<DocumentFragment> Range::createContextualFragment(const String& markup, ExceptionCode& ec) const { if (!m_start.container()) { ec = INVALID_STATE_ERR; return 0; } Node* element = m_start.container()->isElementNode() ? m_start.container() : m_start.container()->parentNode(); if (!element || !element->isHTMLElement()) { ec = NOT_SUPPORTED_ERR; return 0; } RefPtr<DocumentFragment> fragment = static_cast<HTMLElement*>(element)->createContextualFragment(markup); if (!fragment) { ec = NOT_SUPPORTED_ERR; return 0; } return fragment.release(); } void Range::detach(ExceptionCode& ec) { // Check first to see if we've already detached: if (!m_start.container()) { ec = INVALID_STATE_ERR; return; } m_ownerDocument->detachRange(this); m_start.clear(); m_end.clear(); } Node* Range::checkNodeWOffset(Node* n, int offset, ExceptionCode& ec) const { switch (n->nodeType()) { case Node::DOCUMENT_TYPE_NODE: case Node::ENTITY_NODE: case Node::NOTATION_NODE: ec = RangeException::INVALID_NODE_TYPE_ERR; return 0; case Node::CDATA_SECTION_NODE: case Node::COMMENT_NODE: case Node::TEXT_NODE: if (static_cast<unsigned>(offset) > static_cast<CharacterData*>(n)->length()) ec = INDEX_SIZE_ERR; return 0; case Node::PROCESSING_INSTRUCTION_NODE: if (static_cast<unsigned>(offset) > static_cast<ProcessingInstruction*>(n)->data().length()) ec = INDEX_SIZE_ERR; return 0; case Node::ATTRIBUTE_NODE: case Node::DOCUMENT_FRAGMENT_NODE: case Node::DOCUMENT_NODE: case Node::ELEMENT_NODE: case Node::ENTITY_REFERENCE_NODE: case Node::XPATH_NAMESPACE_NODE: { if (!offset) return 0; Node* childBefore = n->childNode(offset - 1); if (!childBefore) ec = INDEX_SIZE_ERR; return childBefore; } } ASSERT_NOT_REACHED(); return 0; } void Range::checkNodeBA(Node* n, ExceptionCode& ec) const { // INVALID_NODE_TYPE_ERR: Raised if the root container of refNode is not an // Attr, Document or DocumentFragment node or part of a shadow DOM tree // or if refNode is a Document, DocumentFragment, Attr, Entity, or Notation node. switch (n->nodeType()) { case Node::ATTRIBUTE_NODE: case Node::DOCUMENT_FRAGMENT_NODE: case Node::DOCUMENT_NODE: case Node::ENTITY_NODE: case Node::NOTATION_NODE: ec = RangeException::INVALID_NODE_TYPE_ERR; return; case Node::CDATA_SECTION_NODE: case Node::COMMENT_NODE: case Node::DOCUMENT_TYPE_NODE: case Node::ELEMENT_NODE: case Node::ENTITY_REFERENCE_NODE: case Node::PROCESSING_INSTRUCTION_NODE: case Node::TEXT_NODE: case Node::XPATH_NAMESPACE_NODE: break; } Node* root = n; while (Node* parent = root->parentNode()) root = parent; switch (root->nodeType()) { case Node::ATTRIBUTE_NODE: case Node::DOCUMENT_NODE: case Node::DOCUMENT_FRAGMENT_NODE: break; case Node::CDATA_SECTION_NODE: case Node::COMMENT_NODE: case Node::DOCUMENT_TYPE_NODE: case Node::ELEMENT_NODE: case Node::ENTITY_NODE: case Node::ENTITY_REFERENCE_NODE: case Node::NOTATION_NODE: case Node::PROCESSING_INSTRUCTION_NODE: case Node::TEXT_NODE: case Node::XPATH_NAMESPACE_NODE: if (root->isShadowNode()) break; ec = RangeException::INVALID_NODE_TYPE_ERR; return; } } PassRefPtr<Range> Range::cloneRange(ExceptionCode& ec) const { if (!m_start.container()) { ec = INVALID_STATE_ERR; return 0; } return Range::create(m_ownerDocument, m_start.container(), m_start.offset(), m_end.container(), m_end.offset()); } void Range::setStartAfter(Node* refNode, ExceptionCode& ec) { if (!m_start.container()) { ec = INVALID_STATE_ERR; return; } if (!refNode) { ec = NOT_FOUND_ERR; return; } if (refNode->document() != m_ownerDocument) { ec = WRONG_DOCUMENT_ERR; return; } ec = 0; checkNodeBA(refNode, ec); if (ec) return; setStart(refNode->parentNode(), refNode->nodeIndex() + 1, ec); } void Range::setEndBefore(Node* refNode, ExceptionCode& ec) { if (!m_start.container()) { ec = INVALID_STATE_ERR; return; } if (!refNode) { ec = NOT_FOUND_ERR; return; } if (refNode->document() != m_ownerDocument) { ec = WRONG_DOCUMENT_ERR; return; } ec = 0; checkNodeBA(refNode, ec); if (ec) return; setEnd(refNode->parentNode(), refNode->nodeIndex(), ec); } void Range::setEndAfter(Node* refNode, ExceptionCode& ec) { if (!m_start.container()) { ec = INVALID_STATE_ERR; return; } if (!refNode) { ec = NOT_FOUND_ERR; return; } if (refNode->document() != m_ownerDocument) { ec = WRONG_DOCUMENT_ERR; return; } ec = 0; checkNodeBA(refNode, ec); if (ec) return; setEnd(refNode->parentNode(), refNode->nodeIndex() + 1, ec); } void Range::selectNode(Node* refNode, ExceptionCode& ec) { if (!m_start.container()) { ec = INVALID_STATE_ERR; return; } if (!refNode) { ec = NOT_FOUND_ERR; return; } // INVALID_NODE_TYPE_ERR: 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. for (Node* anc = refNode->parentNode(); anc; anc = anc->parentNode()) { switch (anc->nodeType()) { case Node::ATTRIBUTE_NODE: case Node::CDATA_SECTION_NODE: case Node::COMMENT_NODE: case Node::DOCUMENT_FRAGMENT_NODE: case Node::DOCUMENT_NODE: case Node::ELEMENT_NODE: case Node::ENTITY_REFERENCE_NODE: case Node::PROCESSING_INSTRUCTION_NODE: case Node::TEXT_NODE: case Node::XPATH_NAMESPACE_NODE: break; case Node::DOCUMENT_TYPE_NODE: case Node::ENTITY_NODE: case Node::NOTATION_NODE: ec = RangeException::INVALID_NODE_TYPE_ERR; return; } } switch (refNode->nodeType()) { case Node::CDATA_SECTION_NODE: case Node::COMMENT_NODE: case Node::DOCUMENT_TYPE_NODE: case Node::ELEMENT_NODE: case Node::ENTITY_REFERENCE_NODE: case Node::PROCESSING_INSTRUCTION_NODE: case Node::TEXT_NODE: case Node::XPATH_NAMESPACE_NODE: break; case Node::ATTRIBUTE_NODE: case Node::DOCUMENT_FRAGMENT_NODE: case Node::DOCUMENT_NODE: case Node::ENTITY_NODE: case Node::NOTATION_NODE: ec = RangeException::INVALID_NODE_TYPE_ERR; return; } ec = 0; setStartBefore(refNode, ec); if (ec) return; setEndAfter(refNode, ec); } void Range::selectNodeContents(Node* refNode, ExceptionCode& ec) { if (!m_start.container()) { ec = INVALID_STATE_ERR; return; } if (!refNode) { ec = NOT_FOUND_ERR; return; } // INVALID_NODE_TYPE_ERR: Raised if refNode or an ancestor of refNode is an Entity, Notation // or DocumentType node. for (Node* n = refNode; n; n = n->parentNode()) { switch (n->nodeType()) { case Node::ATTRIBUTE_NODE: case Node::CDATA_SECTION_NODE: case Node::COMMENT_NODE: case Node::DOCUMENT_FRAGMENT_NODE: case Node::DOCUMENT_NODE: case Node::ELEMENT_NODE: case Node::ENTITY_REFERENCE_NODE: case Node::PROCESSING_INSTRUCTION_NODE: case Node::TEXT_NODE: case Node::XPATH_NAMESPACE_NODE: break; case Node::DOCUMENT_TYPE_NODE: case Node::ENTITY_NODE: case Node::NOTATION_NODE: ec = RangeException::INVALID_NODE_TYPE_ERR; return; } } m_start.setToStartOfNode(refNode); m_end.setToEndOfNode(refNode); } void Range::surroundContents(PassRefPtr<Node> passNewParent, ExceptionCode& ec) { RefPtr<Node> newParent = passNewParent; if (!m_start.container()) { ec = INVALID_STATE_ERR; return; } if (!newParent) { ec = NOT_FOUND_ERR; return; } // INVALID_NODE_TYPE_ERR: Raised if node is an Attr, Entity, DocumentType, Notation, // Document, or DocumentFragment node. switch (newParent->nodeType()) { case Node::ATTRIBUTE_NODE: case Node::DOCUMENT_FRAGMENT_NODE: case Node::DOCUMENT_NODE: case Node::DOCUMENT_TYPE_NODE: case Node::ENTITY_NODE: case Node::NOTATION_NODE: ec = RangeException::INVALID_NODE_TYPE_ERR; return; case Node::CDATA_SECTION_NODE: case Node::COMMENT_NODE: case Node::ELEMENT_NODE: case Node::ENTITY_REFERENCE_NODE: case Node::PROCESSING_INSTRUCTION_NODE: case Node::TEXT_NODE: case Node::XPATH_NAMESPACE_NODE: break; } // NO_MODIFICATION_ALLOWED_ERR: Raised if an ancestor container of either boundary-point of // the Range is read-only. if (containedByReadOnly()) { ec = NO_MODIFICATION_ALLOWED_ERR; return; } // WRONG_DOCUMENT_ERR: Raised if newParent and the container of the start of the Range were // not created from the same document. if (newParent->document() != m_start.container()->document()) { ec = WRONG_DOCUMENT_ERR; return; } // Raise a HIERARCHY_REQUEST_ERR 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->isCharacterDataNode()) parentOfNewParent = parentOfNewParent->parentNode(); if (!parentOfNewParent || !parentOfNewParent->childTypeAllowed(newParent->nodeType())) { ec = HIERARCHY_REQUEST_ERR; return; } if (m_start.container() == newParent || m_start.container()->isDescendantOf(newParent.get())) { ec = HIERARCHY_REQUEST_ERR; 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? // BAD_BOUNDARYPOINTS_ERR: 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) { ec = RangeException::BAD_BOUNDARYPOINTS_ERR; return; } ec = 0; while (Node* n = newParent->firstChild()) { newParent->removeChild(n, ec); if (ec) return; } RefPtr<DocumentFragment> fragment = extractContents(ec); if (ec) return; insertNode(newParent, ec); if (ec) return; newParent->appendChild(fragment.release(), ec); if (ec) return; selectNode(newParent.get(), ec); } void Range::setStartBefore(Node* refNode, ExceptionCode& ec) { if (!m_start.container()) { ec = INVALID_STATE_ERR; return; } if (!refNode) { ec = NOT_FOUND_ERR; return; } if (refNode->document() != m_ownerDocument) { ec = WRONG_DOCUMENT_ERR; return; } ec = 0; checkNodeBA(refNode, ec); if (ec) return; setStart(refNode->parentNode(), refNode->nodeIndex(), ec); } void Range::checkDeleteExtract(ExceptionCode& ec) { if (!m_start.container()) { ec = INVALID_STATE_ERR; return; } ec = 0; if (!commonAncestorContainer(ec) || ec) return; Node* pastLast = pastLastNode(); for (Node* n = firstNode(); n != pastLast; n = n->traverseNextNode()) { if (n->isReadOnlyNode()) { ec = NO_MODIFICATION_ALLOWED_ERR; return; } if (n->nodeType() == Node::DOCUMENT_TYPE_NODE) { ec = HIERARCHY_REQUEST_ERR; return; } } if (containedByReadOnly()) { ec = NO_MODIFICATION_ALLOWED_ERR; return; } } bool Range::containedByReadOnly() const { for (Node* n = m_start.container(); n; n = n->parentNode()) { if (n->isReadOnlyNode()) return true; } for (Node* n = m_end.container(); n; n = n->parentNode()) { if (n->isReadOnlyNode()) return true; } return false; } Node* Range::firstNode() const { if (!m_start.container()) return 0; if (m_start.container()->offsetInCharacters()) return m_start.container(); if (Node* child = m_start.container()->childNode(m_start.offset())) return child; if (!m_start.offset()) return m_start.container(); return m_start.container()->traverseNextSibling(); } Position Range::editingStartPosition() const { // This function is used by range style computations to avoid bugs like: // <rdar://problem/4017641> REGRESSION (Mail): you can only bold/unbold a selection starting from end of line once // It is important to skip certain irrelevant content at the start of the selection, so we do not wind up // with a spurious "mixed" style. VisiblePosition visiblePosition(m_start.container(), m_start.offset(), VP_DEFAULT_AFFINITY); if (visiblePosition.isNull()) return Position(); ExceptionCode ec = 0; // if the selection is a caret, just return the position, since the style // behind us is relevant if (collapsed(ec)) return visiblePosition.deepEquivalent(); // if the selection starts just before a paragraph break, skip over it if (isEndOfParagraph(visiblePosition)) return visiblePosition.next().deepEquivalent().downstream(); // otherwise, make sure to be at the start of the first selected node, // instead of possibly at the end of the last node before the selection return visiblePosition.deepEquivalent().downstream(); } Node* Range::shadowTreeRootNode() const { return startContainer() ? startContainer()->shadowTreeRootNode() : 0; } Node* Range::pastLastNode() const { if (!m_start.container() || !m_end.container()) return 0; if (m_end.container()->offsetInCharacters()) return m_end.container()->traverseNextSibling(); if (Node* child = m_end.container()->childNode(m_end.offset())) return child; return m_end.container()->traverseNextSibling(); } IntRect Range::boundingBox() { IntRect result; Vector<IntRect> 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<IntRect>& rects, bool useSelectionHeight) { Node* startContainer = m_start.container(); Node* endContainer = m_end.container(); if (!startContainer || !endContainer) return; Node* stopNode = pastLastNode(); for (Node* node = firstNode(); node != stopNode; node = node->traverseNextNode()) { 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() : numeric_limits<int>::max(); renderText->absoluteRectsForRange(rects, startOffset, endOffset, useSelectionHeight); } } void Range::textQuads(Vector<FloatQuad>& quads, bool useSelectionHeight) { Node* startContainer = m_start.container(); Node* endContainer = m_end.container(); if (!startContainer || !endContainer) return; Node* stopNode = pastLastNode(); for (Node* node = firstNode(); node != stopNode; node = node->traverseNextNode()) { 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() : numeric_limits<int>::max(); renderText->absoluteQuadsForRange(quads, startOffset, endOffset, useSelectionHeight); } } #ifndef NDEBUG #define FormatBufferSize 1024 void Range::formatForDebugger(char* buffer, unsigned length) const { String result; String s; if (!m_start.container() || !m_end.container()) result = "<empty>"; else { char s[FormatBufferSize]; result += "from offset "; result += String::number(m_start.offset()); result += " of "; m_start.container()->formatForDebugger(s, FormatBufferSize); result += s; result += " to offset "; result += String::number(m_end.offset()); result += " of "; m_end.container()->formatForDebugger(s, FormatBufferSize); result += s; } strncpy(buffer, result.utf8().data(), length - 1); } #undef FormatBufferSize #endif bool operator==(const Range& a, const Range& b) { if (&a == &b) return true; // Not strictly legal C++, but in practice this can happen, and this check works // fine with GCC to detect such cases and return false rather than crashing. if (!&a || !&b) return false; return a.startPosition() == b.startPosition() && a.endPosition() == b.endPosition(); } PassRefPtr<Range> rangeOfContents(Node* node) { ASSERT(node); RefPtr<Range> range = Range::create(node->document()); int exception = 0; range->selectNodeContents(node, exception); return range.release(); } int Range::maxStartOffset() const { if (!m_start.container()) return 0; if (!m_start.container()->offsetInCharacters()) return m_start.container()->childNodeCount(); return m_start.container()->maxCharacterOffset(); } int Range::maxEndOffset() const { if (!m_end.container()) return 0; if (!m_end.container()->offsetInCharacters()) return m_end.container()->childNodeCount(); return m_end.container()->maxCharacterOffset(); } 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 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); ASSERT(node->document() == m_ownerDocument); ASSERT(node != m_ownerDocument); ASSERT(node->parentNode()); 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::textInserted(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::textRemoved(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, 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::textNodesMerged(NodeWithIndex& oldNode, unsigned offset) { ASSERT(oldNode.node()); 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); } static inline void boundaryTextNodesSplit(RangeBoundaryPoint& boundary, Text* oldNode) { if (boundary.container() != oldNode) return; unsigned boundaryOffset = boundary.offset(); if (boundaryOffset <= oldNode->length()) return; boundary.set(oldNode->nextSibling(), boundaryOffset - oldNode->length(), 0); } void Range::textNodeSplit(Text* oldNode) { ASSERT(oldNode); ASSERT(oldNode->document() == m_ownerDocument); ASSERT(oldNode->parentNode()); ASSERT(oldNode->isTextNode()); ASSERT(oldNode->nextSibling()); ASSERT(oldNode->nextSibling()->isTextNode()); boundaryTextNodesSplit(m_start, oldNode); boundaryTextNodesSplit(m_end, oldNode); } void Range::expand(const String& unit, ExceptionCode& ec) { 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(), ec); setEnd(end.deepEquivalent().containerNode(), end.deepEquivalent().computeOffsetInContainerNode(), ec); } PassRefPtr<ClientRectList> Range::getClientRects() const { if (!m_start.container()) return 0; m_ownerDocument->updateLayoutIgnorePendingStylesheets(); Vector<FloatQuad> quads; getBorderAndTextQuads(quads); return ClientRectList::create(quads); } PassRefPtr<ClientRect> Range::getBoundingClientRect() const { if (!m_start.container()) return 0; m_ownerDocument->updateLayoutIgnorePendingStylesheets(); Vector<FloatQuad> quads; getBorderAndTextQuads(quads); if (quads.isEmpty()) return ClientRect::create(); IntRect result; for (size_t i = 0; i < quads.size(); ++i) result.unite(quads[i].enclosingBoundingBox()); return ClientRect::create(result); } static void adjustFloatQuadsForScrollAndAbsoluteZoom(Vector<FloatQuad>& quads, Document* document, RenderObject* renderer) { FrameView* view = document->view(); if (!view) return; IntRect visibleContentRect = view->visibleContentRect(); for (size_t i = 0; i < quads.size(); ++i) { quads[i].move(-visibleContentRect.x(), -visibleContentRect.y()); adjustFloatQuadForAbsoluteZoom(quads[i], renderer); } } void Range::getBorderAndTextQuads(Vector<FloatQuad>& quads) const { Node* startContainer = m_start.container(); Node* endContainer = m_end.container(); Node* stopNode = pastLastNode(); HashSet<Node*> nodeSet; for (Node* node = firstNode(); node != stopNode; node = node->traverseNextNode()) { if (node->isElementNode()) nodeSet.add(node); } for (Node* node = firstNode(); node != stopNode; node = node->traverseNextNode()) { if (node->isElementNode()) { if (!nodeSet.contains(node->parentNode())) { if (RenderBoxModelObject* renderBoxModelObject = static_cast<Element*>(node)->renderBoxModelObject()) { Vector<FloatQuad> elementQuads; renderBoxModelObject->absoluteQuads(elementQuads); adjustFloatQuadsForScrollAndAbsoluteZoom(elementQuads, m_ownerDocument.get(), renderBoxModelObject); quads.append(elementQuads); } } } else if (node->isTextNode()) { if (RenderObject* renderer = static_cast<Text*>(node)->renderer()) { RenderText* renderText = toRenderText(renderer); int startOffset = (node == startContainer) ? m_start.offset() : 0; int endOffset = (node == endContainer) ? m_end.offset() : INT_MAX; Vector<FloatQuad> textQuads; renderText->absoluteQuadsForRange(textQuads, startOffset, endOffset); adjustFloatQuadsForScrollAndAbsoluteZoom(textQuads, m_ownerDocument.get(), renderText); quads.append(textQuads); } } } } } // namespace WebCore #ifndef NDEBUG void showTree(const WebCore::Range* range) { if (range && range->boundaryPointsValid()) { WebCore::Position start = range->startPosition(); WebCore::Position end = range->endPosition(); start.node()->showTreeAndMark(start.node(), "S", end.node(), "E"); fprintf(stderr, "start offset: %d, end offset: %d\n", start.deprecatedEditingOffset(), end.deprecatedEditingOffset()); } } #endif