diff options
author | Simon Hausmann <simon.hausmann@nokia.com> | 2012-01-06 14:44:00 +0100 |
---|---|---|
committer | Simon Hausmann <simon.hausmann@nokia.com> | 2012-01-06 14:44:00 +0100 |
commit | 40736c5763bf61337c8c14e16d8587db021a87d4 (patch) | |
tree | b17a9c00042ad89cb1308e2484491799aa14e9f8 /Source/WebCore/dom/Range.cpp | |
download | qtwebkit-40736c5763bf61337c8c14e16d8587db021a87d4.tar.gz |
Imported WebKit commit 2ea9d364d0f6efa8fa64acf19f451504c59be0e4 (http://svn.webkit.org/repository/webkit/trunk@104285)
Diffstat (limited to 'Source/WebCore/dom/Range.cpp')
-rw-r--r-- | Source/WebCore/dom/Range.cpp | 2087 |
1 files changed, 2087 insertions, 0 deletions
diff --git a/Source/WebCore/dom/Range.cpp b/Source/WebCore/dom/Range.cpp new file mode 100644 index 000000000..298220e4a --- /dev/null +++ b/Source/WebCore/dom/Range.cpp @@ -0,0 +1,2087 @@ +/* + * (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 2000 Gunnstein Lye (gunnstein@netcom.no) + * (C) 2000 Frederik Holljen (frederik.holljen@hig.no) + * (C) 2001 Peter Kelly (pmk@post.com) + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved. + * Copyright (C) 2011 Motorola Mobility. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "Range.h" + +#include "ClientRect.h" +#include "ClientRectList.h" +#include "DocumentFragment.h" +#include "ExceptionCode.h" +#include "FloatQuad.h" +#include "Frame.h" +#include "FrameView.h" +#include "HTMLElement.h" +#include "HTMLNames.h" +#include "Node.h" +#include "NodeWithIndex.h" +#include "Page.h" +#include "ProcessingInstruction.h" +#include "RangeException.h" +#include "RenderBoxModelObject.h" +#include "RenderText.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> +#include <wtf/Vector.h> +#include <wtf/text/CString.h> +#include <wtf/text/StringBuilder.h> + +namespace WebCore { + +using namespace std; +using namespace HTMLNames; + +DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, rangeCounter, ("Range")); + +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. + setStart(startContainer, startOffset); + setEnd(endContainer, endOffset); +} + +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) +{ + return adoptRef(new Range(ownerDocument, start.containerNode(), start.computeOffsetInContainerNode(), end.containerNode(), end.computeOffsetInContainerNode())); +} + +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 +} + +void Range::setDocument(Document* document) +{ + ASSERT(m_ownerDocument != document); + if (m_ownerDocument) + m_ownerDocument->detachRange(this); + m_ownerDocument = document; + m_start.setToStartOfNode(document); + m_end.setToStartOfNode(document); + m_ownerDocument->attachRange(this); +} + +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, ec) > 0) { + ASSERT(!ec); + 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, ec) > 0) { + ASSERT(!ec); + collapse(false, ec); + } +} + +void Range::setStart(const Position& start, ExceptionCode& ec) +{ + Position parentAnchored = start.parentAnchoredEquivalent(); + setStart(parentAnchored.containerNode(), parentAnchored.offsetInContainerNode(), ec); +} + +void Range::setEnd(const Position& end, ExceptionCode& ec) +{ + Position parentAnchored = end.parentAnchoredEquivalent(); + setEnd(parentAnchored.containerNode(), parentAnchored.offsetInContainerNode(), 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(), ec) >= 0 && !ec + && compareBoundaryPoints(refNode, offset, m_end.container(), m_end.offset(), ec) <= 0 && !ec; +} + +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(), ec) < 0) + return -1; + + if (ec) + return 0; + + // compare to end, and point comes after + if (compareBoundaryPoints(refNode, offset, m_end.container(), m_end.offset(), ec) > 0 && !ec) + 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; + } + + ContainerNode* parentNode = refNode->parentNode(); + int nodeIndex = refNode->nodeIndex(); + + if (!parentNode) { + // if the node is the top document we should return NODE_BEFORE_AND_AFTER + // but we throw to match firefox behavior + 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, ec); + case START_TO_END: + return compareBoundaryPoints(m_end, sourceRange->m_start, ec); + case END_TO_END: + return compareBoundaryPoints(m_end, sourceRange->m_end, ec); + case END_TO_START: + return compareBoundaryPoints(m_start, sourceRange->m_end, ec); + } + + ec = SYNTAX_ERR; + return 0; +} + +short Range::compareBoundaryPoints(Node* containerA, int offsetA, Node* containerB, int offsetB, ExceptionCode& ec) +{ + 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) { + ec = WRONG_DOCUMENT_ERR; + 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, ExceptionCode& ec) +{ + return compareBoundaryPoints(boundaryA.container(), boundaryA.offset(), boundaryB.container(), boundaryB.offset(), ec); +} + +bool Range::boundaryPointsValid() const +{ + ExceptionCode ec = 0; + return m_start.container() && compareBoundaryPoints(m_start, m_end, ec) <= 0 && !ec; +} + +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; + } + + ContainerNode* parentNode = refNode->parentNode(); + int nodeIndex = refNode->nodeIndex(); + + if (!parentNode) { + // if the node is the top document we should return NODE_BEFORE_AND_AFTER + // but we throw to match firefox behavior + 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 +} + +static inline Node* highestAncestorUnderCommonRoot(Node* node, Node* commonRoot) +{ + if (node == commonRoot) + return 0; + + ASSERT(commonRoot->contains(node)); + + while (node->parentNode() != commonRoot) + node = node->parentNode(); + + return node; +} + +static inline Node* childOfCommonRootBeforeOffset(Node* container, unsigned offset, Node* commonRoot) +{ + ASSERT(container); + ASSERT(commonRoot); + + if (!commonRoot->contains(container)) + return 0; + + if (container == commonRoot) { + container = container->firstChild(); + for (unsigned i = 0; container && i < offset; i++) + container = container->nextSibling(); + } else { + while (container->parentNode() != commonRoot) + container = container->parentNode(); + } + + return container; +} + +static inline unsigned lengthOfContentsInNode(Node* node) +{ + // This switch statement must be consistent with that of Range::processContentsBetweenOffsets. + switch (node->nodeType()) { + case Node::TEXT_NODE: + case Node::CDATA_SECTION_NODE: + case Node::COMMENT_NODE: + return static_cast<CharacterData*>(node)->length(); + case Node::PROCESSING_INSTRUCTION_NODE: + return static_cast<ProcessingInstruction*>(node)->data().length(); + case Node::ELEMENT_NODE: + case Node::ATTRIBUTE_NODE: + case Node::ENTITY_REFERENCE_NODE: + case Node::ENTITY_NODE: + case Node::DOCUMENT_NODE: + case Node::DOCUMENT_TYPE_NODE: + case Node::DOCUMENT_FRAGMENT_NODE: + case Node::NOTATION_NODE: + case Node::XPATH_NAMESPACE_NODE: + case Node::SHADOW_ROOT_NODE: + return node->childNodeCount(); + } + ASSERT_NOT_REACHED(); + return 0; +} + +PassRefPtr<DocumentFragment> Range::processContents(ActionType action, ExceptionCode& ec) +{ + typedef Vector<RefPtr<Node> > NodeVector; + + 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; + + RefPtr<Node> commonRoot = commonAncestorContainer(ec); + if (ec) + return 0; + ASSERT(commonRoot); + + if (m_start.container() == m_end.container()) { + processContentsBetweenOffsets(action, fragment, m_start.container(), m_start.offset(), m_end.offset(), ec); + return fragment; + } + + // what is the highest node that partially selects the start / end of the range? + RefPtr<Node> partialStart = highestAncestorUnderCommonRoot(m_start.container(), commonRoot.get()); + RefPtr<Node> partialEnd = highestAncestorUnderCommonRoot(m_end.container(), commonRoot.get()); + + // Start and end containers are different. + // There are three possibilities here: + // 1. Start container == commonRoot (End container must be a descendant) + // 2. End container == commonRoot (Start container must be a descendant) + // 3. Neither is commonRoot, they are both descendants + // + // In case 3, we grab everything after the start (up until a direct child + // of commonRoot) into leftContents, and everything before the end (up until + // a direct child of commonRoot) into rightContents. Then we process all + // commonRoot children between leftContents and rightContents + // + // In case 1 or 2, we skip either processing of leftContents or rightContents, + // in which case the last lot of nodes either goes from the first or last + // child of commonRoot. + // + // These are deleted, cloned, or extracted (i.e. both) depending on action. + + // Note that we are verifying that our common root hierarchy is still intact + // after any DOM mutation event, at various stages below. See webkit bug 60350. + + RefPtr<Node> leftContents; + if (m_start.container() != commonRoot && commonRoot->contains(m_start.container())) { + leftContents = processContentsBetweenOffsets(action, 0, m_start.container(), m_start.offset(), lengthOfContentsInNode(m_start.container()), ec); + leftContents = processAncestorsAndTheirSiblings(action, m_start.container(), ProcessContentsForward, leftContents, commonRoot.get(), ec); + } + + RefPtr<Node> rightContents; + if (m_end.container() != commonRoot && commonRoot->contains(m_end.container())) { + rightContents = processContentsBetweenOffsets(action, 0, m_end.container(), 0, m_end.offset(), ec); + rightContents = processAncestorsAndTheirSiblings(action, m_end.container(), ProcessContentsBackward, rightContents, commonRoot.get(), ec); + } + + // delete all children of commonRoot between the start and end container + RefPtr<Node> processStart = childOfCommonRootBeforeOffset(m_start.container(), m_start.offset(), commonRoot.get()); + if (processStart && m_start.container() != commonRoot) // processStart contains nodes before m_start. + processStart = processStart->nextSibling(); + RefPtr<Node> processEnd = childOfCommonRootBeforeOffset(m_end.container(), m_end.offset(), commonRoot.get()); + + // Collapse the range, making sure that the result is not within a node that was partially selected. + if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) { + if (partialStart && commonRoot->contains(partialStart.get())) + setStart(partialStart->parentNode(), partialStart->nodeIndex() + 1, ec); + else if (partialEnd && commonRoot->contains(partialEnd.get())) + 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); + + if (processStart) { + NodeVector nodes; + for (Node* n = processStart.get(); n && n != processEnd; n = n->nextSibling()) + nodes.append(n); + processNodes(action, nodes, commonRoot, fragment, ec); + } + + if ((action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) && rightContents) + fragment->appendChild(rightContents, ec); + + return fragment.release(); +} + +static inline void deleteCharacterData(PassRefPtr<CharacterData> data, unsigned startOffset, unsigned endOffset, ExceptionCode& ec) +{ + if (data->length() - endOffset) + data->deleteData(endOffset, data->length() - endOffset, ec); + if (startOffset) + data->deleteData(0, startOffset, ec); +} + +PassRefPtr<Node> Range::processContentsBetweenOffsets(ActionType action, PassRefPtr<DocumentFragment> fragment, + Node* container, unsigned startOffset, unsigned endOffset, ExceptionCode& ec) +{ + ASSERT(container); + ASSERT(startOffset <= endOffset); + + // This switch statement must be consistent with that of lengthOfContentsInNode. + RefPtr<Node> result; + switch (container->nodeType()) { + case Node::TEXT_NODE: + case Node::CDATA_SECTION_NODE: + case Node::COMMENT_NODE: + ASSERT(endOffset <= static_cast<CharacterData*>(container)->length()); + if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) { + RefPtr<CharacterData> c = static_pointer_cast<CharacterData>(container->cloneNode(true)); + deleteCharacterData(c, startOffset, endOffset, ec); + if (fragment) { + result = fragment; + result->appendChild(c.release(), ec); + } else + result = c.release(); + } + if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) + static_cast<CharacterData*>(container)->deleteData(startOffset, endOffset - startOffset, ec); + break; + case Node::PROCESSING_INSTRUCTION_NODE: + ASSERT(endOffset <= static_cast<ProcessingInstruction*>(container)->data().length()); + if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) { + RefPtr<ProcessingInstruction> c = static_pointer_cast<ProcessingInstruction>(container->cloneNode(true)); + c->setData(c->data().substring(startOffset, endOffset - startOffset), ec); + if (fragment) { + result = fragment; + result->appendChild(c.release(), ec); + } else + result = c.release(); + } + if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) { + ProcessingInstruction* pi = static_cast<ProcessingInstruction*>(container); + String data(pi->data()); + data.remove(startOffset, endOffset - startOffset); + pi->setData(data, ec); + } + break; + case Node::ELEMENT_NODE: + case Node::ATTRIBUTE_NODE: + case Node::ENTITY_REFERENCE_NODE: + case Node::ENTITY_NODE: + case Node::DOCUMENT_NODE: + case Node::DOCUMENT_TYPE_NODE: + case Node::DOCUMENT_FRAGMENT_NODE: + case Node::NOTATION_NODE: + case Node::XPATH_NAMESPACE_NODE: + case Node::SHADOW_ROOT_NODE: + // FIXME: Should we assert that some nodes never appear here? + if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) { + if (fragment) + result = fragment; + else + result = container->cloneNode(false); + } + + Node* n = container->firstChild(); + Vector<RefPtr<Node> > nodes; + for (unsigned i = startOffset; n && i; i--) + n = n->nextSibling(); + for (unsigned i = startOffset; n && i < endOffset; i++, n = n->nextSibling()) + nodes.append(n); + + processNodes(action, nodes, container, result, ec); + break; + } + + return result.release(); +} + +void Range::processNodes(ActionType action, Vector<RefPtr<Node> >& nodes, PassRefPtr<Node> oldContainer, PassRefPtr<Node> newContainer, ExceptionCode& ec) +{ + for (unsigned i = 0; i < nodes.size(); i++) { + switch (action) { + case DELETE_CONTENTS: + oldContainer->removeChild(nodes[i].get(), ec); + break; + case EXTRACT_CONTENTS: + newContainer->appendChild(nodes[i].release(), ec); // will remove n from its parent + break; + case CLONE_CONTENTS: + newContainer->appendChild(nodes[i]->cloneNode(true), ec); + break; + } + } +} + +PassRefPtr<Node> Range::processAncestorsAndTheirSiblings(ActionType action, Node* container, ContentsProcessDirection direction, PassRefPtr<Node> passedClonedContainer, Node* commonRoot, ExceptionCode& ec) +{ + typedef Vector<RefPtr<Node> > NodeVector; + + RefPtr<Node> clonedContainer = passedClonedContainer; + Vector<RefPtr<Node> > ancestors; + for (ContainerNode* n = container->parentNode(); n && n != commonRoot; n = n->parentNode()) + ancestors.append(n); + + RefPtr<Node> firstChildInAncestorToProcess = direction == ProcessContentsForward ? container->nextSibling() : container->previousSibling(); + for (Vector<RefPtr<Node> >::const_iterator it = ancestors.begin(); it != ancestors.end(); it++) { + RefPtr<Node> ancestor = *it; + if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) { + if (RefPtr<Node> clonedAncestor = ancestor->cloneNode(false)) { // Might have been removed already during mutation event. + clonedAncestor->appendChild(clonedContainer, ec); + clonedContainer = clonedAncestor; + } + } + + // Copy siblings of an ancestor of start/end containers + // FIXME: This assertion may fail if DOM is modified during mutation event + // FIXME: Share code with Range::processNodes + ASSERT(!firstChildInAncestorToProcess || firstChildInAncestorToProcess->parentNode() == ancestor); + + NodeVector nodes; + for (Node* child = firstChildInAncestorToProcess.get(); child; + child = (direction == ProcessContentsForward) ? child->nextSibling() : child->previousSibling()) + nodes.append(child); + + for (NodeVector::const_iterator it = nodes.begin(); it != nodes.end(); it++) { + Node* child = it->get(); + switch (action) { + case DELETE_CONTENTS: + ancestor->removeChild(child, ec); + break; + case EXTRACT_CONTENTS: // will remove child from ancestor + if (direction == ProcessContentsForward) + clonedContainer->appendChild(child, ec); + else + clonedContainer->insertBefore(child, clonedContainer->firstChild(), ec); + break; + case CLONE_CONTENTS: + if (direction == ProcessContentsForward) + clonedContainer->appendChild(child->cloneNode(true), ec); + else + clonedContainer->insertBefore(child->cloneNode(true), clonedContainer->firstChild(), ec); + break; + } + } + firstChildInAncestorToProcess = direction == ProcessContentsForward ? ancestor->nextSibling() : ancestor->previousSibling(); + } + + return clonedContainer.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; + } + + // 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, ShadowRoot or Document node. + switch (newNodeType) { + case Node::ATTRIBUTE_NODE: + case Node::ENTITY_NODE: + case Node::NOTATION_NODE: + case Node::DOCUMENT_NODE: + case Node::SHADOW_ROOT_NODE: + ec = RangeException::INVALID_NODE_TYPE_ERR; + return; + default: + break; + } + + 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 && numNewChildren) + 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(); + } + + StringBuilder builder; + + 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; + builder.append(data.characters() + start, end - start); + } + } + + return builder.toString(); +} + +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); +} + +static inline void removeElementPreservingChildren(PassRefPtr<DocumentFragment> fragment, HTMLElement* element) +{ + ExceptionCode ignoredExceptionCode; + + RefPtr<Node> nextChild; + for (RefPtr<Node> child = element->firstChild(); child; child = nextChild) { + nextChild = child->nextSibling(); + element->removeChild(child.get(), ignoredExceptionCode); + ASSERT(!ignoredExceptionCode); + fragment->insertBefore(child, element, ignoredExceptionCode); + ASSERT(!ignoredExceptionCode); + } + fragment->removeChild(element, ignoredExceptionCode); + ASSERT(!ignoredExceptionCode); +} + +PassRefPtr<DocumentFragment> Range::createDocumentFragmentForElement(const String& markup, Element* element, FragmentScriptingPermission scriptingPermission) +{ + ASSERT(element); + HTMLElement* htmlElement = toHTMLElement(element); + if (htmlElement->ieForbidsInsertHTML()) + return 0; + + if (htmlElement->hasLocalName(colTag) || htmlElement->hasLocalName(colgroupTag) || htmlElement->hasLocalName(framesetTag) + || htmlElement->hasLocalName(headTag) || htmlElement->hasLocalName(styleTag) || htmlElement->hasLocalName(titleTag)) + return 0; + + RefPtr<DocumentFragment> fragment = element->document()->createDocumentFragment(); + + if (element->document()->isHTMLDocument()) + fragment->parseHTML(markup, element, scriptingPermission); + else if (!fragment->parseXML(markup, element, scriptingPermission)) + return 0; // FIXME: We should propagate a syntax error exception out here. + + // We need to pop <html> and <body> elements and remove <head> to + // accommodate folks passing complete HTML documents to make the + // child of an element. + + RefPtr<Node> nextNode; + for (RefPtr<Node> node = fragment->firstChild(); node; node = nextNode) { + nextNode = node->nextSibling(); + if (node->hasTagName(htmlTag) || node->hasTagName(headTag) || node->hasTagName(bodyTag)) { + HTMLElement* element = toHTMLElement(node.get()); + if (Node* firstChild = element->firstChild()) + nextNode = firstChild; + removeElementPreservingChildren(fragment, element); + } + } + return fragment.release(); +} + +PassRefPtr<DocumentFragment> Range::createContextualFragment(const String& markup, ExceptionCode& ec, FragmentScriptingPermission scriptingPermission) +{ + 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 = createDocumentFragmentForElement(markup, toElement(element), scriptingPermission); + + 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: + case Node::SHADOW_ROOT_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, DocumentFragment or ShadowRoot node, or part of a SVG shadow DOM tree, + // or if refNode is a Document, DocumentFragment, ShadowRoot, 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: + case Node::SHADOW_ROOT_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 (ContainerNode* parent = root->parentNode()) + root = parent; + + switch (root->nodeType()) { + case Node::ATTRIBUTE_NODE: + case Node::DOCUMENT_NODE: + case Node::DOCUMENT_FRAGMENT_NODE: + case Node::SHADOW_ROOT_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->isSVGShadowRoot()) + 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, ShadowRoot, Attr, Entity, or Notation + // node. + for (ContainerNode* 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: + case Node::SHADOW_ROOT_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: + case Node::SHADOW_ROOT_NODE: + ec = RangeException::INVALID_NODE_TYPE_ERR; + return; + } + + if (m_ownerDocument != refNode->document()) + setDocument(refNode->document()); + + 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: + case Node::SHADOW_ROOT_NODE: + break; + case Node::DOCUMENT_TYPE_NODE: + case Node::ENTITY_NODE: + case Node::NOTATION_NODE: + ec = RangeException::INVALID_NODE_TYPE_ERR; + return; + } + } + + if (m_ownerDocument != refNode->document()) + setDocument(refNode->document()); + + 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: + case Node::SHADOW_ROOT_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; + } + + // 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()) { + toContainerNode(newParent.get())->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(); +} + +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, RangeInFixedPosition* inFixed) +{ + Node* startContainer = m_start.container(); + Node* endContainer = m_end.container(); + + if (!startContainer || !endContainer) { + if (inFixed) + *inFixed = NotFixedPosition; + return; + } + + bool allFixed = true; + bool someFixed = false; + + 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(); + bool isFixed = false; + renderText->absoluteRectsForRange(rects, startOffset, endOffset, useSelectionHeight, &isFixed); + allFixed &= isFixed; + someFixed |= isFixed; + } + + if (inFixed) + *inFixed = allFixed ? EntirelyFixedPosition : (someFixed ? PartiallyFixedPosition : NotFixedPosition); +} + +void Range::textQuads(Vector<FloatQuad>& quads, bool useSelectionHeight, RangeInFixedPosition* inFixed) const +{ + Node* startContainer = m_start.container(); + Node* endContainer = m_end.container(); + + if (!startContainer || !endContainer) { + if (inFixed) + *inFixed = NotFixedPosition; + return; + } + + bool allFixed = true; + bool someFixed = false; + + 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(); + bool isFixed = false; + renderText->absoluteQuadsForRange(quads, startOffset, endOffset, useSelectionHeight, &isFixed); + allFixed &= isFixed; + someFixed |= isFixed; + } + + if (inFixed) + *inFixed = allFixed ? EntirelyFixedPosition : (someFixed ? PartiallyFixedPosition : NotFixedPosition); +} + +#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 areRangesEqual(const Range* a, const Range* b) +{ + if (a == b) + return true; + if (!a || !b) + return false; + return a->startPosition() == b->startPosition() && a->endPosition() == b->endPosition(); +} + +PassRefPtr<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 boundaryNodeChildrenWillBeRemoved(RangeBoundaryPoint& boundary, ContainerNode* container) +{ + for (Node* nodeToBeRemoved = container->firstChild(); nodeToBeRemoved; nodeToBeRemoved = nodeToBeRemoved->nextSibling()) { + if (boundary.childBefore() == nodeToBeRemoved) { + boundary.setToStartOfNode(container); + return; + } + + for (Node* n = boundary.container(); n; n = n->parentNode()) { + if (n == nodeToBeRemoved) { + boundary.setToStartOfNode(container); + return; + } + } + } +} + +void Range::nodeChildrenWillBeRemoved(ContainerNode* container) +{ + ASSERT(container); + ASSERT(container->document() == m_ownerDocument); + boundaryNodeChildrenWillBeRemoved(m_start, container); + boundaryNodeChildrenWillBeRemoved(m_end, container); +} + +static inline void boundaryNodeWillBeRemoved(RangeBoundaryPoint& boundary, Node* nodeToBeRemoved) +{ + if (boundary.childBefore() == nodeToBeRemoved) { + boundary.childBeforeWillBeRemoved(); + return; + } + + for (Node* n = boundary.container(); n; n = n->parentNode()) { + if (n == nodeToBeRemoved) { + boundary.setToBeforeChild(nodeToBeRemoved); + return; + } + } +} + +void Range::nodeWillBeRemoved(Node* node) +{ + ASSERT(node); + 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 ClientRectList::create(); + + m_ownerDocument->updateLayoutIgnorePendingStylesheets(); + + Vector<FloatQuad> quads; + getBorderAndTextQuads(quads); + + return ClientRectList::create(quads); +} + +PassRefPtr<ClientRect> Range::getBoundingClientRect() const +{ + return ClientRect::create(boundingRect()); +} + +static void adjustFloatQuadsForScrollAndAbsoluteZoomAndPageScale(Vector<FloatQuad>& quads, Document* document, RenderObject* renderer) +{ + FrameView* view = document->view(); + if (!view) + return; + + float pageScale = 1; + if (Page* page = document->page()) + pageScale = page->pageScaleFactor(); + + LayoutRect visibleContentRect = view->visibleContentRect(); + for (size_t i = 0; i < quads.size(); ++i) { + quads[i].move(-visibleContentRect.x(), -visibleContentRect.y()); + adjustFloatQuadForAbsoluteZoom(quads[i], renderer); + if (pageScale != 1) + adjustFloatQuadForPageScale(quads[i], pageScale); + } +} + +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); + adjustFloatQuadsForScrollAndAbsoluteZoomAndPageScale(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); + adjustFloatQuadsForScrollAndAbsoluteZoomAndPageScale(textQuads, m_ownerDocument.get(), renderText); + + quads.append(textQuads); + } + } + } +} + + +FloatRect Range::boundingRect() const +{ + if (!m_start.container()) + return FloatRect(); + + m_ownerDocument->updateLayoutIgnorePendingStylesheets(); + + Vector<FloatQuad> quads; + getBorderAndTextQuads(quads); + if (quads.isEmpty()) + return FloatRect(); + + FloatRect result; + for (size_t i = 0; i < quads.size(); ++i) + result.unite(quads[i].boundingBox()); + + return result; +} + +} // namespace WebCore + +#ifndef NDEBUG + +void showTree(const WebCore::Range* range) +{ + if (range && range->boundaryPointsValid()) { + range->startContainer()->showTreeAndMark(range->startContainer(), "S", range->endContainer(), "E"); + fprintf(stderr, "start offset: %d, end offset: %d\n", range->startOffset(), range->endOffset()); + } +} + +#endif |