From 8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Tue, 3 Mar 2009 19:30:52 -0800 Subject: auto import from //depot/cupcake/@135843 --- WebCore/dom/Range.cpp | 1802 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1802 insertions(+) create mode 100644 WebCore/dom/Range.cpp (limited to 'WebCore/dom/Range.cpp') diff --git a/WebCore/dom/Range.cpp b/WebCore/dom/Range.cpp new file mode 100644 index 0000000..afd563e --- /dev/null +++ b/WebCore/dom/Range.cpp @@ -0,0 +1,1802 @@ +/* + * (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 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 "Document.h" +#include "DocumentFragment.h" +#include "ExceptionCode.h" +#include "HTMLElement.h" +#include "HTMLNames.h" +#include "NodeWithIndex.h" +#include "ProcessingInstruction.h" +#include "RenderBlock.h" +#include "Text.h" +#include "TextIterator.h" +#include "markup.h" +#include "visible_units.h" +#include +#include + +namespace WebCore { + +using namespace std; +using namespace HTMLNames; + +#ifndef NDEBUG +static WTF::RefCountedLeakCounter rangeCounter("Range"); +#endif + +inline Range::Range(PassRefPtr ownerDocument) + : m_ownerDocument(ownerDocument) + , m_start(m_ownerDocument) + , m_end(m_ownerDocument) +{ +#ifndef NDEBUG + rangeCounter.increment(); +#endif + + m_ownerDocument->attachRange(this); +} + +PassRefPtr Range::create(PassRefPtr ownerDocument) +{ + return adoptRef(new Range(ownerDocument)); +} + +inline Range::Range(PassRefPtr ownerDocument, PassRefPtr startContainer, int startOffset, PassRefPtr 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::create(PassRefPtr ownerDocument, PassRefPtr startContainer, int startOffset, PassRefPtr endContainer, int endOffset) +{ + return adoptRef(new Range(ownerDocument, startContainer, startOffset, endContainer, endOffset)); +} + +PassRefPtr Range::create(PassRefPtr ownerDocument, const Position& start, const Position& end) +{ + return adoptRef(new Range(ownerDocument, start.container.get(), start.posOffset, end.container.get(), end.posOffset)); +} + +Range::~Range() +{ + if (m_start.container()) + 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 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.container(), m_start.offset(), m_end.container(), m_end.offset()) > 0) + collapse(true, ec); +} + +void Range::setEnd(PassRefPtr 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.container(), m_start.offset(), m_end.container(), m_end.offset()) > 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 (!refNode) { + ec = NOT_FOUND_ERR; + return false; + } + + if (!m_start.container() && refNode->attached()) { + ec = INVALID_STATE_ERR; + return false; + } + + if (m_start.container() && !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) +{ + // 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) { + ec = NOT_FOUND_ERR; + return 0; + } + + if (!m_start.container() && refNode->attached()) { + ec = INVALID_STATE_ERR; + return 0; + } + + if (m_start.container() && !refNode->attached()) { + // Firefox doesn't throw an exception for this case; it returns -1. + return -1; + } + + if (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) +{ + // 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.container(), m_start.offset(), + sourceRange->m_start.container(), sourceRange->m_start.offset()); + case START_TO_END: + return compareBoundaryPoints(m_end.container(), m_end.offset(), + sourceRange->m_start.container(), sourceRange->m_start.offset()); + case END_TO_END: + return compareBoundaryPoints(m_end.container(), m_end.offset(), + sourceRange->m_end.container(), sourceRange->m_end.offset()); + case END_TO_START: + return compareBoundaryPoints(m_start.container(), m_start.offset(), + sourceRange->m_end.container(), sourceRange->m_end.offset()); + } + + ec = SYNTAX_ERR; + return 0; +} + +short Range::compareBoundaryPoints(Node* containerA, int offsetA, Node* containerB, int offsetB) +{ + ASSERT(containerA && 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 Position& a, const Position& b) +{ + return compareBoundaryPoints(a.container.get(), a.posOffset, b.container.get(), b.posOffset); +} + +bool Range::boundaryPointsValid() const +{ + return m_start.container() && compareBoundaryPoints(m_start.container(), m_start.offset(), m_end.container(), m_end.offset()) <= 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 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 fragment; + if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) + fragment = new DocumentFragment(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 c = static_pointer_cast(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(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 c = static_pointer_cast(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(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 possiblities 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 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 c = static_pointer_cast(m_start.container()->cloneNode(true)); + c->deleteData(0, m_start.offset(), ec); + leftContents = c.release(); + } + if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) + static_cast(m_start.container())->deleteData( + m_start.offset(), static_cast(m_start.container())->length() - m_start.offset(), ec); + } else if (startNodeType == Node::PROCESSING_INSTRUCTION_NODE) { + if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) { + RefPtr c = static_pointer_cast(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(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 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 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 c = static_pointer_cast(m_end.container()->cloneNode(true)); + c->deleteData(m_end.offset(), static_cast(m_end.container())->length() - m_end.offset(), ec); + rightContents = c; + } + if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) + static_cast(m_end.container())->deleteData(0, m_end.offset(), ec); + } else if (endNodeType == Node::PROCESSING_INSTRUCTION_NODE) { + if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) { + RefPtr c = static_pointer_cast(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(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 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(); + } + + // 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 Range::extractContents(ExceptionCode& ec) +{ + checkDeleteExtract(ec); + if (ec) + return 0; + + return processContents(EXTRACT_CONTENTS, ec); +} + +PassRefPtr Range::cloneContents(ExceptionCode& ec) +{ + if (!m_start.container()) { + ec = INVALID_STATE_ERR; + return 0; + } + + return processContents(CLONE_CONTENTS, ec); +} + +void Range::insertNode(PassRefPtr prpNewNode, ExceptionCode& ec) +{ + RefPtr 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 newText = static_cast(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.setToChild(newText.get()); + } else { + RefPtr 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 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(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 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 fragment = static_cast(element)->createContextualFragment(markup); + if (!fragment) { + ec = NOT_SUPPORTED_ERR; + return 0; + } + + return fragment.release(); +} + + +void Range::detach(ExceptionCode& ec) +{ + 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(offset) > static_cast(n)->length()) + ec = INDEX_SIZE_ERR; + return 0; + case Node::PROCESSING_INSTRUCTION_NODE: + if (static_cast(offset) > static_cast(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::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.setToStart(refNode); + m_end.setToEnd(refNode); +} + +void Range::surroundContents(PassRefPtr passNewParent, ExceptionCode& ec) +{ + RefPtr 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->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. + if (m_start.container()->nodeType() != Node::TEXT_NODE) { + if (m_start.offset() > 0 && m_start.offset() < maxStartOffset()) { + ec = RangeException::BAD_BOUNDARYPOINTS_ERR; + return; + } + } + if (m_end.container()->nodeType() != Node::TEXT_NODE) { + if (m_end.offset() > 0 && m_end.offset() < maxEndOffset()) { + ec = RangeException::BAD_BOUNDARYPOINTS_ERR; + return; + } + } + + ec = 0; + while (Node* n = newParent->firstChild()) { + newParent->removeChild(n, ec); + if (ec) + return; + } + RefPtr 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: + // 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 rects; + addLineBoxRects(rects); + const size_t n = rects.size(); + for (size_t i = 0; i < n; ++i) + result.unite(rects[i]); + return result; +} + +void Range::addLineBoxRects(Vector& rects, bool useSelectionHeight) +{ + if (!m_start.container() || !m_end.container()) + return; + + RenderObject* start = m_start.container()->renderer(); + RenderObject* end = m_end.container()->renderer(); + if (!start || !end) + return; + + RenderObject* stop = end->nextInPreOrderAfterChildren(); + for (RenderObject* r = start; r && r != stop; r = r->nextInPreOrder()) { + // only ask leaf render objects for their line box rects + if (!r->firstChild()) { + int startOffset = r == start ? m_start.offset() : 0; + int endOffset = r == end ? m_end.offset() : INT_MAX; + r->addLineBoxRects(rects, 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 = ""; + 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 rangeOfContents(Node* node) +{ + ASSERT(node); + RefPtr 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.setToChild(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); +} + +} -- cgit v1.1