mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Remove all code relating to shadow trees, insertion points, shadow boundaries, traversing composed trees, distribution, template documents, custom elements, registering elements, element registries, element factories, shadow roots, etc. Remove the following features from the IDLs and from the binding generators: CustomElementCallbacks, Reflect*, EventHandler. Remove the CSS custom pseudo-element concept, since we no longer have a UA style sheet worth talking about, no longer have shadow trees or custom elements, no longer use pseudo-elements, and generally therefore don't use this code at all.
1513 lines
54 KiB
C++
1513 lines
54 KiB
C++
/*
|
|
* (C) 1999 Lars Knoll (knoll@kde.org)
|
|
* (C) 2000 Gunnstein Lye (gunnstein@netcom.no)
|
|
* (C) 2000 Frederik Holljen (frederik.holljen@hig.no)
|
|
* (C) 2001 Peter Kelly (pmk@post.com)
|
|
* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved.
|
|
* Copyright (C) 2011 Motorola Mobility. All rights reserved.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public License
|
|
* along with this library; see the file COPYING.LIB. If not, write to
|
|
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "sky/engine/core/dom/Range.h"
|
|
|
|
#include "sky/engine/bindings/exception_state.h"
|
|
#include "sky/engine/core/dom/ClientRect.h"
|
|
#include "sky/engine/core/dom/ClientRectList.h"
|
|
#include "sky/engine/core/dom/DocumentFragment.h"
|
|
#include "sky/engine/core/dom/ExceptionCode.h"
|
|
#include "sky/engine/core/dom/Node.h"
|
|
#include "sky/engine/core/dom/NodeTraversal.h"
|
|
#include "sky/engine/core/dom/NodeWithIndex.h"
|
|
#include "sky/engine/core/dom/Text.h"
|
|
#include "sky/engine/core/editing/TextIterator.h"
|
|
#include "sky/engine/core/editing/VisiblePosition.h"
|
|
#include "sky/engine/core/editing/VisibleUnits.h"
|
|
#include "sky/engine/core/html/HTMLElement.h"
|
|
#include "sky/engine/core/rendering/RenderBoxModelObject.h"
|
|
#include "sky/engine/core/rendering/RenderText.h"
|
|
#include "sky/engine/platform/geometry/FloatQuad.h"
|
|
#include "sky/engine/wtf/RefCountedLeakCounter.h"
|
|
#include "sky/engine/wtf/text/CString.h"
|
|
#include "sky/engine/wtf/text/StringBuilder.h"
|
|
#ifndef NDEBUG
|
|
#include <stdio.h>
|
|
#endif
|
|
|
|
namespace blink {
|
|
|
|
DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, rangeCounter, ("Range"));
|
|
|
|
inline Range::Range(Document& ownerDocument)
|
|
: m_ownerDocument(&ownerDocument)
|
|
, m_start(m_ownerDocument)
|
|
, m_end(m_ownerDocument)
|
|
{
|
|
#ifndef NDEBUG
|
|
rangeCounter.increment();
|
|
#endif
|
|
|
|
m_ownerDocument->attachRange(this);
|
|
}
|
|
|
|
PassRefPtr<Range> Range::create(Document& ownerDocument)
|
|
{
|
|
return adoptRef(new Range(ownerDocument));
|
|
}
|
|
|
|
PassRefPtr<Range> Range::create(Document* ownerDocument)
|
|
{
|
|
return adoptRef(new Range(*ownerDocument));
|
|
}
|
|
|
|
inline Range::Range(Document& ownerDocument, Node* startContainer, int startOffset, Node* endContainer, int endOffset)
|
|
: m_ownerDocument(&ownerDocument)
|
|
, m_start(m_ownerDocument)
|
|
, m_end(m_ownerDocument)
|
|
{
|
|
#ifndef NDEBUG
|
|
rangeCounter.increment();
|
|
#endif
|
|
|
|
m_ownerDocument->attachRange(this);
|
|
|
|
// Simply setting the containers and offsets directly would not do any of the checking
|
|
// that setStart and setEnd do, so we call those functions.
|
|
setStart(startContainer, startOffset);
|
|
setEnd(endContainer, endOffset);
|
|
}
|
|
|
|
PassRefPtr<Range> Range::create(Document& ownerDocument, Node* startContainer, int startOffset, Node* endContainer, int endOffset)
|
|
{
|
|
return adoptRef(new Range(ownerDocument, startContainer, startOffset, endContainer, endOffset));
|
|
}
|
|
|
|
PassRefPtr<Range> Range::create(Document& ownerDocument, const Position& start, const Position& end)
|
|
{
|
|
return adoptRef(new Range(ownerDocument, start.containerNode(), start.computeOffsetInContainerNode(), end.containerNode(), end.computeOffsetInContainerNode()));
|
|
}
|
|
|
|
Range::~Range()
|
|
{
|
|
#if !ENABLE(OILPAN)
|
|
// Always detach (even if we've already detached) to fix https://bugs.webkit.org/show_bug.cgi?id=26044
|
|
m_ownerDocument->detachRange(this);
|
|
#endif
|
|
|
|
#ifndef NDEBUG
|
|
rangeCounter.decrement();
|
|
#endif
|
|
}
|
|
|
|
void Range::setDocument(Document& document)
|
|
{
|
|
ASSERT(m_ownerDocument != document);
|
|
ASSERT(m_ownerDocument);
|
|
m_ownerDocument->detachRange(this);
|
|
m_ownerDocument = &document;
|
|
m_start.setToStartOfNode(document);
|
|
m_end.setToStartOfNode(document);
|
|
m_ownerDocument->attachRange(this);
|
|
}
|
|
|
|
Node* Range::commonAncestorContainer() const
|
|
{
|
|
return commonAncestorContainer(m_start.container(), m_end.container());
|
|
}
|
|
|
|
Node* Range::commonAncestorContainer(Node* containerA, Node* containerB)
|
|
{
|
|
for (Node* parentA = containerA; parentA; parentA = parentA->parentNode()) {
|
|
for (Node* parentB = containerB; parentB; parentB = parentB->parentNode()) {
|
|
if (parentA == parentB)
|
|
return parentA;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline bool checkForDifferentRootContainer(const RangeBoundaryPoint& start, const RangeBoundaryPoint& end)
|
|
{
|
|
Node* endRootContainer = end.container();
|
|
while (endRootContainer->parentNode())
|
|
endRootContainer = endRootContainer->parentNode();
|
|
Node* startRootContainer = start.container();
|
|
while (startRootContainer->parentNode())
|
|
startRootContainer = startRootContainer->parentNode();
|
|
|
|
return startRootContainer != endRootContainer || (Range::compareBoundaryPoints(start, end, ASSERT_NO_EXCEPTION) > 0);
|
|
}
|
|
|
|
void Range::setStart(PassRefPtr<Node> refNode, int offset, ExceptionState& exceptionState)
|
|
{
|
|
if (!refNode) {
|
|
exceptionState.ThrowDOMException(NotFoundError, "The node provided was null.");
|
|
return;
|
|
}
|
|
|
|
bool didMoveDocument = false;
|
|
if (refNode->document() != m_ownerDocument) {
|
|
setDocument(refNode->document());
|
|
didMoveDocument = true;
|
|
}
|
|
|
|
Node* childNode = checkNodeWOffset(refNode.get(), offset, exceptionState);
|
|
if (exceptionState.had_exception())
|
|
return;
|
|
|
|
m_start.set(refNode, offset, childNode);
|
|
|
|
if (didMoveDocument || checkForDifferentRootContainer(m_start, m_end))
|
|
collapse(true);
|
|
}
|
|
|
|
void Range::setEnd(PassRefPtr<Node> refNode, int offset, ExceptionState& exceptionState)
|
|
{
|
|
if (!refNode) {
|
|
exceptionState.ThrowDOMException(NotFoundError, "The node provided was null.");
|
|
return;
|
|
}
|
|
|
|
bool didMoveDocument = false;
|
|
if (refNode->document() != m_ownerDocument) {
|
|
setDocument(refNode->document());
|
|
didMoveDocument = true;
|
|
}
|
|
|
|
Node* childNode = checkNodeWOffset(refNode.get(), offset, exceptionState);
|
|
if (exceptionState.had_exception())
|
|
return;
|
|
|
|
m_end.set(refNode, offset, childNode);
|
|
|
|
if (didMoveDocument || checkForDifferentRootContainer(m_start, m_end))
|
|
collapse(false);
|
|
}
|
|
|
|
void Range::setStart(const Position& start, ExceptionState& exceptionState)
|
|
{
|
|
Position parentAnchored = start.parentAnchoredEquivalent();
|
|
setStart(parentAnchored.containerNode(), parentAnchored.offsetInContainerNode(), exceptionState);
|
|
}
|
|
|
|
void Range::setEnd(const Position& end, ExceptionState& exceptionState)
|
|
{
|
|
Position parentAnchored = end.parentAnchoredEquivalent();
|
|
setEnd(parentAnchored.containerNode(), parentAnchored.offsetInContainerNode(), exceptionState);
|
|
}
|
|
|
|
void Range::collapse(bool toStart)
|
|
{
|
|
if (toStart)
|
|
m_end = m_start;
|
|
else
|
|
m_start = m_end;
|
|
}
|
|
|
|
bool Range::isPointInRange(Node* refNode, int offset, ExceptionState& exceptionState)
|
|
{
|
|
if (!refNode) {
|
|
exceptionState.ThrowDOMException(HierarchyRequestError, "The node provided was null.");
|
|
return false;
|
|
}
|
|
|
|
if (!refNode->inActiveDocument() || refNode->document() != m_ownerDocument) {
|
|
return false;
|
|
}
|
|
|
|
checkNodeWOffset(refNode, offset, exceptionState);
|
|
if (exceptionState.had_exception())
|
|
return false;
|
|
|
|
return compareBoundaryPoints(refNode, offset, m_start.container(), m_start.offset(), exceptionState) >= 0 && !exceptionState.had_exception()
|
|
&& compareBoundaryPoints(refNode, offset, m_end.container(), m_end.offset(), exceptionState) <= 0 && !exceptionState.had_exception();
|
|
}
|
|
|
|
short Range::comparePoint(Node* refNode, int offset, ExceptionState& exceptionState) const
|
|
{
|
|
// http://developer.mozilla.org/en/docs/DOM:range.comparePoint
|
|
// This method returns -1, 0 or 1 depending on if the point described by the
|
|
// refNode node and an offset within the node is before, same as, or after the range respectively.
|
|
|
|
if (!refNode->inActiveDocument()) {
|
|
exceptionState.ThrowDOMException(WrongDocumentError, "The node provided is not in an active document.");
|
|
return 0;
|
|
}
|
|
|
|
if (refNode->document() != m_ownerDocument) {
|
|
exceptionState.ThrowDOMException(WrongDocumentError, "The node provided is not in this Range's Document.");
|
|
return 0;
|
|
}
|
|
|
|
checkNodeWOffset(refNode, offset, exceptionState);
|
|
if (exceptionState.had_exception())
|
|
return 0;
|
|
|
|
// compare to start, and point comes before
|
|
if (compareBoundaryPoints(refNode, offset, m_start.container(), m_start.offset(), exceptionState) < 0)
|
|
return -1;
|
|
|
|
if (exceptionState.had_exception())
|
|
return 0;
|
|
|
|
// compare to end, and point comes after
|
|
if (compareBoundaryPoints(refNode, offset, m_end.container(), m_end.offset(), exceptionState) > 0 && !exceptionState.had_exception())
|
|
return 1;
|
|
|
|
// point is in the middle of this range, or on the boundary points
|
|
return 0;
|
|
}
|
|
|
|
Range::CompareResults Range::compareNode(Node* refNode, ExceptionState& exceptionState) const
|
|
{
|
|
// http://developer.mozilla.org/en/docs/DOM:range.compareNode
|
|
// This method returns 0, 1, 2, or 3 based on if the node is before, after,
|
|
// before and after(surrounds), or inside the range, respectively
|
|
|
|
if (!refNode) {
|
|
exceptionState.ThrowDOMException(NotFoundError, "The node provided was null.");
|
|
return NODE_BEFORE;
|
|
}
|
|
|
|
if (!refNode->inActiveDocument()) {
|
|
// Firefox doesn't throw an exception for this case; it returns 0.
|
|
return NODE_BEFORE;
|
|
}
|
|
|
|
if (refNode->document() != m_ownerDocument) {
|
|
// Firefox doesn't throw an exception for this case; it returns 0.
|
|
return NODE_BEFORE;
|
|
}
|
|
|
|
ContainerNode* parentNode = refNode->parentNode();
|
|
int nodeIndex = refNode->nodeIndex();
|
|
|
|
if (!parentNode) {
|
|
// if the node is the top document we should return NODE_BEFORE_AND_AFTER
|
|
// but we throw to match firefox behavior
|
|
exceptionState.ThrowDOMException(NotFoundError, "The provided node has no parent.");
|
|
return NODE_BEFORE;
|
|
}
|
|
|
|
if (comparePoint(parentNode, nodeIndex, exceptionState) < 0) { // starts before
|
|
if (comparePoint(parentNode, nodeIndex + 1, exceptionState) > 0) // ends after the range
|
|
return NODE_BEFORE_AND_AFTER;
|
|
return NODE_BEFORE; // ends before or in the range
|
|
}
|
|
// starts at or after the range start
|
|
if (comparePoint(parentNode, nodeIndex + 1, exceptionState) > 0) // ends after the range
|
|
return NODE_AFTER;
|
|
return NODE_INSIDE; // ends inside the range
|
|
}
|
|
|
|
short Range::compareBoundaryPoints(CompareHow how, const Range* sourceRange, ExceptionState& exceptionState) const
|
|
{
|
|
if (!(how == START_TO_START || how == START_TO_END || how == END_TO_END || how == END_TO_START)) {
|
|
exceptionState.ThrowDOMException(NotSupportedError, "The comparison method provided must be one of 'START_TO_START', 'START_TO_END', 'END_TO_END', or 'END_TO_START'.");
|
|
return 0;
|
|
}
|
|
|
|
Node* thisCont = commonAncestorContainer();
|
|
Node* sourceCont = sourceRange->commonAncestorContainer();
|
|
if (thisCont->document() != sourceCont->document()) {
|
|
exceptionState.ThrowDOMException(WrongDocumentError, "The source range is in a different document than this range.");
|
|
return 0;
|
|
}
|
|
|
|
Node* thisTop = thisCont;
|
|
Node* sourceTop = sourceCont;
|
|
while (thisTop->parentNode())
|
|
thisTop = thisTop->parentNode();
|
|
while (sourceTop->parentNode())
|
|
sourceTop = sourceTop->parentNode();
|
|
if (thisTop != sourceTop) { // in different DocumentFragments
|
|
exceptionState.ThrowDOMException(WrongDocumentError, "The source range is in a different document than this range.");
|
|
return 0;
|
|
}
|
|
|
|
switch (how) {
|
|
case START_TO_START:
|
|
return compareBoundaryPoints(m_start, sourceRange->m_start, exceptionState);
|
|
case START_TO_END:
|
|
return compareBoundaryPoints(m_end, sourceRange->m_start, exceptionState);
|
|
case END_TO_END:
|
|
return compareBoundaryPoints(m_end, sourceRange->m_end, exceptionState);
|
|
case END_TO_START:
|
|
return compareBoundaryPoints(m_start, sourceRange->m_end, exceptionState);
|
|
}
|
|
|
|
ASSERT_NOT_REACHED();
|
|
return 0;
|
|
}
|
|
|
|
short Range::compareBoundaryPoints(Node* containerA, int offsetA, Node* containerB, int offsetB, ExceptionState& exceptionState)
|
|
{
|
|
ASSERT(containerA);
|
|
ASSERT(containerB);
|
|
|
|
if (!containerA)
|
|
return -1;
|
|
if (!containerB)
|
|
return 1;
|
|
|
|
// see DOM2 traversal & range section 2.5
|
|
|
|
// case 1: both points have the same container
|
|
if (containerA == containerB) {
|
|
if (offsetA == offsetB)
|
|
return 0; // A is equal to B
|
|
if (offsetA < offsetB)
|
|
return -1; // A is before B
|
|
else
|
|
return 1; // A is after B
|
|
}
|
|
|
|
// case 2: node C (container B or an ancestor) is a child node of A
|
|
Node* c = containerB;
|
|
while (c && c->parentNode() != containerA)
|
|
c = c->parentNode();
|
|
if (c) {
|
|
int offsetC = 0;
|
|
Node* n = containerA->firstChild();
|
|
while (n != c && offsetC < offsetA) {
|
|
offsetC++;
|
|
n = n->nextSibling();
|
|
}
|
|
|
|
if (offsetA <= offsetC)
|
|
return -1; // A is before B
|
|
else
|
|
return 1; // A is after B
|
|
}
|
|
|
|
// case 3: node C (container A or an ancestor) is a child node of B
|
|
c = containerA;
|
|
while (c && c->parentNode() != containerB)
|
|
c = c->parentNode();
|
|
if (c) {
|
|
int offsetC = 0;
|
|
Node* n = containerB->firstChild();
|
|
while (n != c && offsetC < offsetB) {
|
|
offsetC++;
|
|
n = n->nextSibling();
|
|
}
|
|
|
|
if (offsetC < offsetB)
|
|
return -1; // A is before B
|
|
else
|
|
return 1; // A is after B
|
|
}
|
|
|
|
// case 4: containers A & B are siblings, or children of siblings
|
|
// ### we need to do a traversal here instead
|
|
Node* commonAncestor = commonAncestorContainer(containerA, containerB);
|
|
if (!commonAncestor) {
|
|
exceptionState.ThrowDOMException(WrongDocumentError, "The two ranges are in separate documents.");
|
|
return 0;
|
|
}
|
|
Node* childA = containerA;
|
|
while (childA && childA->parentNode() != commonAncestor)
|
|
childA = childA->parentNode();
|
|
if (!childA)
|
|
childA = commonAncestor;
|
|
Node* childB = containerB;
|
|
while (childB && childB->parentNode() != commonAncestor)
|
|
childB = childB->parentNode();
|
|
if (!childB)
|
|
childB = commonAncestor;
|
|
|
|
if (childA == childB)
|
|
return 0; // A is equal to B
|
|
|
|
Node* n = commonAncestor->firstChild();
|
|
while (n) {
|
|
if (n == childA)
|
|
return -1; // A is before B
|
|
if (n == childB)
|
|
return 1; // A is after B
|
|
n = n->nextSibling();
|
|
}
|
|
|
|
// Should never reach this point.
|
|
ASSERT_NOT_REACHED();
|
|
return 0;
|
|
}
|
|
|
|
short Range::compareBoundaryPoints(const RangeBoundaryPoint& boundaryA, const RangeBoundaryPoint& boundaryB, ExceptionState& exceptionState)
|
|
{
|
|
return compareBoundaryPoints(boundaryA.container(), boundaryA.offset(), boundaryB.container(), boundaryB.offset(), exceptionState);
|
|
}
|
|
|
|
bool Range::boundaryPointsValid() const
|
|
{
|
|
TrackExceptionState exceptionState;
|
|
return compareBoundaryPoints(m_start, m_end, exceptionState) <= 0 && !exceptionState.had_exception();
|
|
}
|
|
|
|
void Range::deleteContents(ExceptionState& exceptionState)
|
|
{
|
|
ASSERT(boundaryPointsValid());
|
|
|
|
{
|
|
processContents(DELETE_CONTENTS, exceptionState);
|
|
}
|
|
}
|
|
|
|
bool Range::intersectsNode(Node* refNode, ExceptionState& exceptionState)
|
|
{
|
|
// http://developer.mozilla.org/en/docs/DOM:range.intersectsNode
|
|
// Returns a bool if the node intersects the range.
|
|
if (!refNode) {
|
|
exceptionState.ThrowDOMException(NotFoundError, "The node provided is null.");
|
|
return false;
|
|
}
|
|
|
|
if (!refNode->inActiveDocument() || refNode->document() != m_ownerDocument) {
|
|
// Firefox doesn't throw an exception for these cases; it returns false.
|
|
return false;
|
|
}
|
|
|
|
ContainerNode* parentNode = refNode->parentNode();
|
|
int nodeIndex = refNode->nodeIndex();
|
|
|
|
if (!parentNode) {
|
|
// if the node is the top document we should return NODE_BEFORE_AND_AFTER
|
|
// but we throw to match firefox behavior
|
|
exceptionState.ThrowDOMException(NotFoundError, "The node provided has no parent.");
|
|
return false;
|
|
}
|
|
|
|
if (comparePoint(parentNode, nodeIndex, exceptionState) < 0 // starts before start
|
|
&& comparePoint(parentNode, nodeIndex + 1, exceptionState) < 0) { // ends before start
|
|
return false;
|
|
}
|
|
|
|
if (comparePoint(parentNode, nodeIndex, exceptionState) > 0 // starts after end
|
|
&& comparePoint(parentNode, nodeIndex + 1, exceptionState) > 0) { // ends after end
|
|
return false;
|
|
}
|
|
|
|
return true; // all other cases
|
|
}
|
|
|
|
static inline Node* highestAncestorUnderCommonRoot(Node* node, Node* commonRoot)
|
|
{
|
|
if (node == commonRoot)
|
|
return 0;
|
|
|
|
ASSERT(commonRoot->contains(node));
|
|
|
|
while (node->parentNode() != commonRoot)
|
|
node = node->parentNode();
|
|
|
|
return node;
|
|
}
|
|
|
|
static inline Node* childOfCommonRootBeforeOffset(Node* container, unsigned offset, Node* commonRoot)
|
|
{
|
|
ASSERT(container);
|
|
ASSERT(commonRoot);
|
|
|
|
if (!commonRoot->contains(container))
|
|
return 0;
|
|
|
|
if (container == commonRoot) {
|
|
container = container->firstChild();
|
|
for (unsigned i = 0; container && i < offset; i++)
|
|
container = container->nextSibling();
|
|
} else {
|
|
while (container->parentNode() != commonRoot)
|
|
container = container->parentNode();
|
|
}
|
|
|
|
return container;
|
|
}
|
|
|
|
PassRefPtr<DocumentFragment> Range::processContents(ActionType action, ExceptionState& exceptionState)
|
|
{
|
|
typedef Vector<RefPtr<Node> > NodeVector;
|
|
|
|
RefPtr<DocumentFragment> fragment = nullptr;
|
|
if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS)
|
|
fragment = DocumentFragment::create(*m_ownerDocument.get());
|
|
|
|
if (collapsed())
|
|
return fragment.release();
|
|
|
|
RefPtr<Node> commonRoot = commonAncestorContainer();
|
|
ASSERT(commonRoot);
|
|
|
|
if (m_start.container() == m_end.container()) {
|
|
processContentsBetweenOffsets(action, fragment, m_start.container(), m_start.offset(), m_end.offset(), exceptionState);
|
|
return fragment;
|
|
}
|
|
|
|
// Since mutation observers can modify the range during the process, the boundary points need to be saved.
|
|
RangeBoundaryPoint originalStart(m_start);
|
|
RangeBoundaryPoint originalEnd(m_end);
|
|
|
|
// what is the highest node that partially selects the start / end of the range?
|
|
RefPtr<Node> partialStart = highestAncestorUnderCommonRoot(originalStart.container(), commonRoot.get());
|
|
RefPtr<Node> partialEnd = highestAncestorUnderCommonRoot(originalEnd.container(), commonRoot.get());
|
|
|
|
// Start and end containers are different.
|
|
// There are three possibilities here:
|
|
// 1. Start container == commonRoot (End container must be a descendant)
|
|
// 2. End container == commonRoot (Start container must be a descendant)
|
|
// 3. Neither is commonRoot, they are both descendants
|
|
//
|
|
// In case 3, we grab everything after the start (up until a direct child
|
|
// of commonRoot) into leftContents, and everything before the end (up until
|
|
// a direct child of commonRoot) into rightContents. Then we process all
|
|
// commonRoot children between leftContents and rightContents
|
|
//
|
|
// In case 1 or 2, we skip either processing of leftContents or rightContents,
|
|
// in which case the last lot of nodes either goes from the first or last
|
|
// child of commonRoot.
|
|
//
|
|
// These are deleted, cloned, or extracted (i.e. both) depending on action.
|
|
|
|
// Note that we are verifying that our common root hierarchy is still intact
|
|
// after any DOM mutation event, at various stages below. See webkit bug 60350.
|
|
|
|
RefPtr<Node> leftContents = nullptr;
|
|
if (originalStart.container() != commonRoot && commonRoot->contains(originalStart.container())) {
|
|
leftContents = processContentsBetweenOffsets(action, nullptr, originalStart.container(), originalStart.offset(), originalStart.container()->lengthOfContents(), exceptionState);
|
|
leftContents = processAncestorsAndTheirSiblings(action, originalStart.container(), ProcessContentsForward, leftContents, commonRoot.get(), exceptionState);
|
|
}
|
|
|
|
RefPtr<Node> rightContents = nullptr;
|
|
if (m_end.container() != commonRoot && commonRoot->contains(originalEnd.container())) {
|
|
rightContents = processContentsBetweenOffsets(action, nullptr, originalEnd.container(), 0, originalEnd.offset(), exceptionState);
|
|
rightContents = processAncestorsAndTheirSiblings(action, originalEnd.container(), ProcessContentsBackward, rightContents, commonRoot.get(), exceptionState);
|
|
}
|
|
|
|
// delete all children of commonRoot between the start and end container
|
|
RefPtr<Node> processStart = childOfCommonRootBeforeOffset(originalStart.container(), originalStart.offset(), commonRoot.get());
|
|
if (processStart && originalStart.container() != commonRoot) // processStart contains nodes before m_start.
|
|
processStart = processStart->nextSibling();
|
|
RefPtr<Node> processEnd = childOfCommonRootBeforeOffset(originalEnd.container(), originalEnd.offset(), commonRoot.get());
|
|
|
|
// Collapse the range, making sure that the result is not within a node that was partially selected.
|
|
if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) {
|
|
if (partialStart && commonRoot->contains(partialStart.get())) {
|
|
// FIXME: We should not continue if we have an earlier error.
|
|
exceptionState.ClearException();
|
|
setStart(partialStart->parentNode(), partialStart->nodeIndex() + 1, exceptionState);
|
|
} else if (partialEnd && commonRoot->contains(partialEnd.get())) {
|
|
// FIXME: We should not continue if we have an earlier error.
|
|
exceptionState.ClearException();
|
|
setStart(partialEnd->parentNode(), partialEnd->nodeIndex(), exceptionState);
|
|
}
|
|
if (exceptionState.had_exception())
|
|
return nullptr;
|
|
m_end = m_start;
|
|
}
|
|
|
|
originalStart.clear();
|
|
originalEnd.clear();
|
|
|
|
// Now add leftContents, stuff in between, and rightContents to the fragment
|
|
// (or just delete the stuff in between)
|
|
|
|
if ((action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) && leftContents)
|
|
fragment->appendChild(leftContents, exceptionState);
|
|
|
|
if (processStart) {
|
|
NodeVector nodes;
|
|
for (Node* n = processStart.get(); n && n != processEnd; n = n->nextSibling())
|
|
nodes.append(n);
|
|
processNodes(action, nodes, commonRoot, fragment, exceptionState);
|
|
}
|
|
|
|
if ((action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) && rightContents)
|
|
fragment->appendChild(rightContents, exceptionState);
|
|
|
|
return fragment.release();
|
|
}
|
|
|
|
static inline void deleteCharacterData(PassRefPtr<CharacterData> data, unsigned startOffset, unsigned endOffset, ExceptionState& exceptionState)
|
|
{
|
|
if (data->length() - endOffset)
|
|
data->deleteData(endOffset, data->length() - endOffset, exceptionState);
|
|
if (startOffset)
|
|
data->deleteData(0, startOffset, exceptionState);
|
|
}
|
|
|
|
PassRefPtr<Node> Range::processContentsBetweenOffsets(ActionType action, PassRefPtr<DocumentFragment> fragment,
|
|
Node* container, unsigned startOffset, unsigned endOffset, ExceptionState& exceptionState)
|
|
{
|
|
ASSERT(container);
|
|
ASSERT(startOffset <= endOffset);
|
|
|
|
// This switch statement must be consistent with that of Node::lengthOfContents.
|
|
RefPtr<Node> result = nullptr;
|
|
switch (container->nodeType()) {
|
|
case Node::TEXT_NODE:
|
|
endOffset = std::min(endOffset, toCharacterData(container)->length());
|
|
if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) {
|
|
RefPtr<CharacterData> c = static_pointer_cast<CharacterData>(container->cloneNode(true));
|
|
deleteCharacterData(c, startOffset, endOffset, exceptionState);
|
|
if (fragment) {
|
|
result = fragment;
|
|
result->appendChild(c.release(), exceptionState);
|
|
} else
|
|
result = c.release();
|
|
}
|
|
if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS)
|
|
toCharacterData(container)->deleteData(startOffset, endOffset - startOffset, exceptionState);
|
|
break;
|
|
case Node::ELEMENT_NODE:
|
|
case Node::DOCUMENT_NODE:
|
|
case Node::DOCUMENT_FRAGMENT_NODE:
|
|
// FIXME: Should we assert that some nodes never appear here?
|
|
if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) {
|
|
if (fragment)
|
|
result = fragment;
|
|
else
|
|
result = container->cloneNode(false);
|
|
}
|
|
|
|
Node* n = container->firstChild();
|
|
Vector<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, exceptionState);
|
|
break;
|
|
}
|
|
|
|
return result.release();
|
|
}
|
|
|
|
void Range::processNodes(ActionType action, Vector<RefPtr<Node> >& nodes, PassRefPtr<Node> oldContainer, PassRefPtr<Node> newContainer, ExceptionState& exceptionState)
|
|
{
|
|
for (unsigned i = 0; i < nodes.size(); i++) {
|
|
switch (action) {
|
|
case DELETE_CONTENTS:
|
|
oldContainer->removeChild(nodes[i].get(), exceptionState);
|
|
break;
|
|
case EXTRACT_CONTENTS:
|
|
newContainer->appendChild(nodes[i].release(), exceptionState); // will remove n from its parent
|
|
break;
|
|
case CLONE_CONTENTS:
|
|
newContainer->appendChild(nodes[i]->cloneNode(true), exceptionState);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
PassRefPtr<Node> Range::processAncestorsAndTheirSiblings(ActionType action, Node* container, ContentsProcessDirection direction, PassRefPtr<Node> passedClonedContainer, Node* commonRoot, ExceptionState& exceptionState)
|
|
{
|
|
typedef Vector<RefPtr<Node> > NodeVector;
|
|
|
|
RefPtr<Node> clonedContainer = passedClonedContainer;
|
|
NodeVector 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 (NodeVector::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, exceptionState);
|
|
clonedContainer = clonedAncestor;
|
|
}
|
|
}
|
|
|
|
// Copy siblings of an ancestor of start/end containers
|
|
// FIXME: This assertion may fail if DOM is modified during mutation event
|
|
// FIXME: Share code with Range::processNodes
|
|
ASSERT(!firstChildInAncestorToProcess || firstChildInAncestorToProcess->parentNode() == ancestor);
|
|
|
|
NodeVector nodes;
|
|
for (Node* child = firstChildInAncestorToProcess.get(); child;
|
|
child = (direction == ProcessContentsForward) ? child->nextSibling() : child->previousSibling())
|
|
nodes.append(child);
|
|
|
|
for (NodeVector::const_iterator it = nodes.begin(); it != nodes.end(); ++it) {
|
|
Node* child = it->get();
|
|
switch (action) {
|
|
case DELETE_CONTENTS:
|
|
// Prior call of ancestor->removeChild() may cause a tree change due to DOMSubtreeModified event.
|
|
// Therefore, we need to make sure |ancestor| is still |child|'s parent.
|
|
if (ancestor == child->parentNode())
|
|
ancestor->removeChild(child, exceptionState);
|
|
break;
|
|
case EXTRACT_CONTENTS: // will remove child from ancestor
|
|
if (direction == ProcessContentsForward)
|
|
clonedContainer->appendChild(child, exceptionState);
|
|
else
|
|
clonedContainer->insertBefore(child, clonedContainer->firstChild(), exceptionState);
|
|
break;
|
|
case CLONE_CONTENTS:
|
|
if (direction == ProcessContentsForward)
|
|
clonedContainer->appendChild(child->cloneNode(true), exceptionState);
|
|
else
|
|
clonedContainer->insertBefore(child->cloneNode(true), clonedContainer->firstChild(), exceptionState);
|
|
break;
|
|
}
|
|
}
|
|
firstChildInAncestorToProcess = direction == ProcessContentsForward ? ancestor->nextSibling() : ancestor->previousSibling();
|
|
}
|
|
|
|
return clonedContainer.release();
|
|
}
|
|
|
|
PassRefPtr<DocumentFragment> Range::extractContents(ExceptionState& exceptionState)
|
|
{
|
|
checkExtractPrecondition(exceptionState);
|
|
if (exceptionState.had_exception())
|
|
return nullptr;
|
|
|
|
return processContents(EXTRACT_CONTENTS, exceptionState);
|
|
}
|
|
|
|
PassRefPtr<DocumentFragment> Range::cloneContents(ExceptionState& exceptionState)
|
|
{
|
|
return processContents(CLONE_CONTENTS, exceptionState);
|
|
}
|
|
|
|
void Range::insertNode(PassRefPtr<Node> prpNewNode, ExceptionState& exceptionState)
|
|
{
|
|
RefPtr<Node> newNode = prpNewNode;
|
|
|
|
if (!newNode) {
|
|
exceptionState.ThrowDOMException(NotFoundError, "The node provided is null.");
|
|
return;
|
|
}
|
|
|
|
// HierarchyRequestError: Raised if the container of the start of the Range is of a type that
|
|
// does not allow children of the type of newNode or if newNode is an ancestor of the container.
|
|
|
|
// an extra one here - if a text node is going to split, it must have a parent to insert into
|
|
bool startIsText = m_start.container()->isTextNode();
|
|
if (startIsText && !m_start.container()->parentNode()) {
|
|
exceptionState.ThrowDOMException(HierarchyRequestError, "This operation would split a text node, but there's no parent into which to insert.");
|
|
return;
|
|
}
|
|
|
|
// In the case where the container is a text node, we check against the container's parent, because
|
|
// text nodes get split up upon insertion.
|
|
Node* checkAgainst;
|
|
if (startIsText)
|
|
checkAgainst = m_start.container()->parentNode();
|
|
else
|
|
checkAgainst = m_start.container();
|
|
|
|
Node::NodeType newNodeType = newNode->nodeType();
|
|
int numNewChildren;
|
|
if (newNodeType == Node::DOCUMENT_FRAGMENT_NODE) {
|
|
// check each child node, not the DocumentFragment itself
|
|
numNewChildren = 0;
|
|
for (Node* c = toDocumentFragment(newNode)->firstChild(); c; c = c->nextSibling()) {
|
|
++numNewChildren;
|
|
}
|
|
} else {
|
|
numNewChildren = 1;
|
|
}
|
|
|
|
for (Node* n = m_start.container(); n; n = n->parentNode()) {
|
|
if (n == newNode) {
|
|
exceptionState.ThrowDOMException(HierarchyRequestError, "The node to be inserted contains the insertion point; it may not be inserted into itself.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// InvalidNodeTypeError: Raised if newNode is an Attr, Entity, Notation, or Document node.
|
|
switch (newNodeType) {
|
|
case Node::DOCUMENT_NODE:
|
|
exceptionState.ThrowDOMException(InvalidNodeTypeError, "The node to be inserted is a '" + newNode->nodeName() + "' node, which may not be inserted here.");
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
bool collapsed = m_start == m_end;
|
|
RefPtr<Node> container = nullptr;
|
|
if (startIsText) {
|
|
container = m_start.container();
|
|
RefPtr<Text> newText = toText(container)->splitText(m_start.offset(), exceptionState);
|
|
if (exceptionState.had_exception())
|
|
return;
|
|
|
|
container = m_start.container();
|
|
container->parentNode()->insertBefore(newNode.release(), newText.get(), exceptionState);
|
|
if (exceptionState.had_exception())
|
|
return;
|
|
|
|
if (collapsed) {
|
|
// The load event would be fired e.g. by ContainerNode::updateTreeAfterInsertion
|
|
// Given circumstance may mutate the tree so newText->parentNode() may become null
|
|
if (!newText->parentNode()) {
|
|
exceptionState.ThrowDOMException(HierarchyRequestError, "This operation would set range's end to parent with new offset, but there's no parent into which to continue.");
|
|
return;
|
|
}
|
|
m_end.setToBeforeChild(*newText);
|
|
}
|
|
} else {
|
|
RefPtr<Node> lastChild = (newNodeType == Node::DOCUMENT_FRAGMENT_NODE) ? toDocumentFragment(newNode)->lastChild() : newNode.get();
|
|
if (lastChild && lastChild == m_start.childBefore()) {
|
|
// The insertion will do nothing, but we need to extend the range to include
|
|
// the inserted nodes.
|
|
Node* firstChild = (newNodeType == Node::DOCUMENT_FRAGMENT_NODE) ? toDocumentFragment(newNode)->firstChild() : newNode.get();
|
|
ASSERT(firstChild);
|
|
m_start.setToBeforeChild(*firstChild);
|
|
return;
|
|
}
|
|
|
|
container = m_start.container();
|
|
container->insertBefore(newNode.release(), NodeTraversal::childAt(*container, m_start.offset()), exceptionState);
|
|
if (exceptionState.had_exception())
|
|
return;
|
|
|
|
// Note that m_start.offset() may have changed as a result of container->insertBefore,
|
|
// when the node we are inserting comes before the range in the same container.
|
|
if (collapsed && numNewChildren)
|
|
m_end.set(m_start.container(), m_start.offset() + numNewChildren, lastChild.get());
|
|
}
|
|
}
|
|
|
|
String Range::toString() const
|
|
{
|
|
StringBuilder builder;
|
|
|
|
Node* pastLast = pastLastNode();
|
|
for (Node* n = firstNode(); n != pastLast; n = NodeTraversal::next(*n)) {
|
|
Node::NodeType type = n->nodeType();
|
|
if (type == Node::TEXT_NODE) {
|
|
String data = toCharacterData(n)->data();
|
|
int length = data.length();
|
|
int start = (n == m_start.container()) ? std::min(std::max(0, m_start.offset()), length) : 0;
|
|
int end = (n == m_end.container()) ? std::min(std::max(start, m_end.offset()), length) : length;
|
|
builder.append(data, start, end - start);
|
|
}
|
|
}
|
|
|
|
return builder.toString();
|
|
}
|
|
|
|
String Range::text() const
|
|
{
|
|
return plainText(this, TextIteratorEmitsObjectReplacementCharacter);
|
|
}
|
|
|
|
void Range::detach()
|
|
{
|
|
// This is now a no-op as per the DOM specification.
|
|
}
|
|
|
|
Node* Range::checkNodeWOffset(Node* n, int offset, ExceptionState& exceptionState) const
|
|
{
|
|
switch (n->nodeType()) {
|
|
case Node::TEXT_NODE:
|
|
if (static_cast<unsigned>(offset) > toCharacterData(n)->length())
|
|
exceptionState.ThrowDOMException(IndexSizeError, "The offset " + String::number(offset) + " is larger than or equal to the node's length (" + String::number(toCharacterData(n)->length()) + ").");
|
|
return 0;
|
|
case Node::DOCUMENT_FRAGMENT_NODE:
|
|
case Node::DOCUMENT_NODE:
|
|
case Node::ELEMENT_NODE: {
|
|
if (!offset)
|
|
return 0;
|
|
Node* childBefore = NodeTraversal::childAt(*n, offset - 1);
|
|
if (!childBefore)
|
|
exceptionState.ThrowDOMException(IndexSizeError, "There is no child at offset " + String::number(offset) + ".");
|
|
return childBefore;
|
|
}
|
|
}
|
|
ASSERT_NOT_REACHED();
|
|
return 0;
|
|
}
|
|
|
|
void Range::checkNodeBA(Node* n, ExceptionState& exceptionState) const
|
|
{
|
|
if (!n) {
|
|
exceptionState.ThrowDOMException(NotFoundError, "The node provided is null.");
|
|
return;
|
|
}
|
|
|
|
// InvalidNodeTypeError: Raised if the root container of refNode is not an
|
|
// Attr, Document, DocumentFragment node,
|
|
// or if refNode is a Document, DocumentFragment, Attr, Entity, or Notation node.
|
|
|
|
if (!n->parentNode()) {
|
|
exceptionState.ThrowDOMException(InvalidNodeTypeError, "the given Node has no parent.");
|
|
return;
|
|
}
|
|
|
|
switch (n->nodeType()) {
|
|
case Node::DOCUMENT_FRAGMENT_NODE:
|
|
case Node::DOCUMENT_NODE:
|
|
exceptionState.ThrowDOMException(InvalidNodeTypeError, "The node provided is of type '" + n->nodeName() + "'.");
|
|
return;
|
|
case Node::ELEMENT_NODE:
|
|
case Node::TEXT_NODE:
|
|
break;
|
|
}
|
|
|
|
Node* root = n;
|
|
while (ContainerNode* parent = root->parentNode())
|
|
root = parent;
|
|
|
|
switch (root->nodeType()) {
|
|
case Node::DOCUMENT_NODE:
|
|
case Node::DOCUMENT_FRAGMENT_NODE:
|
|
case Node::ELEMENT_NODE:
|
|
break;
|
|
case Node::TEXT_NODE:
|
|
exceptionState.ThrowDOMException(InvalidNodeTypeError, "The node provided is of type '" + n->nodeName() + "'.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
PassRefPtr<Range> Range::cloneRange() const
|
|
{
|
|
return Range::create(*m_ownerDocument.get(), m_start.container(), m_start.offset(), m_end.container(), m_end.offset());
|
|
}
|
|
|
|
void Range::setStartAfter(Node* refNode, ExceptionState& exceptionState)
|
|
{
|
|
checkNodeBA(refNode, exceptionState);
|
|
if (exceptionState.had_exception())
|
|
return;
|
|
|
|
setStart(refNode->parentNode(), refNode->nodeIndex() + 1, exceptionState);
|
|
}
|
|
|
|
void Range::setEndBefore(Node* refNode, ExceptionState& exceptionState)
|
|
{
|
|
checkNodeBA(refNode, exceptionState);
|
|
if (exceptionState.had_exception())
|
|
return;
|
|
|
|
setEnd(refNode->parentNode(), refNode->nodeIndex(), exceptionState);
|
|
}
|
|
|
|
void Range::setEndAfter(Node* refNode, ExceptionState& exceptionState)
|
|
{
|
|
checkNodeBA(refNode, exceptionState);
|
|
if (exceptionState.had_exception())
|
|
return;
|
|
|
|
setEnd(refNode->parentNode(), refNode->nodeIndex() + 1, exceptionState);
|
|
}
|
|
|
|
void Range::selectNode(Node* refNode, ExceptionState& exceptionState)
|
|
{
|
|
if (!refNode) {
|
|
exceptionState.ThrowDOMException(NotFoundError, "The node provided is null.");
|
|
return;
|
|
}
|
|
|
|
if (!refNode->parentNode()) {
|
|
exceptionState.ThrowDOMException(InvalidNodeTypeError, "the given Node has no parent.");
|
|
return;
|
|
}
|
|
|
|
// InvalidNodeTypeError: Raised if an ancestor of refNode is an Entity, Notation or
|
|
// DocumentType node or if refNode is a Document, DocumentFragment, Attr, Entity, or Notation
|
|
// node.
|
|
switch (refNode->nodeType()) {
|
|
case Node::ELEMENT_NODE:
|
|
case Node::TEXT_NODE:
|
|
break;
|
|
case Node::DOCUMENT_FRAGMENT_NODE:
|
|
case Node::DOCUMENT_NODE:
|
|
exceptionState.ThrowDOMException(InvalidNodeTypeError, "The node provided is of type '" + refNode->nodeName() + "'.");
|
|
return;
|
|
}
|
|
|
|
if (m_ownerDocument != refNode->document())
|
|
setDocument(refNode->document());
|
|
|
|
setStartBefore(refNode);
|
|
setEndAfter(refNode);
|
|
}
|
|
|
|
void Range::selectNodeContents(Node* refNode, ExceptionState& exceptionState)
|
|
{
|
|
if (!refNode) {
|
|
exceptionState.ThrowDOMException(NotFoundError, "The node provided is null.");
|
|
return;
|
|
}
|
|
|
|
if (m_ownerDocument != refNode->document())
|
|
setDocument(refNode->document());
|
|
|
|
m_start.setToStartOfNode(*refNode);
|
|
m_end.setToEndOfNode(*refNode);
|
|
}
|
|
|
|
void Range::surroundContents(PassRefPtr<Node> passNewParent, ExceptionState& exceptionState)
|
|
{
|
|
RefPtr<Node> newParent = passNewParent;
|
|
if (!newParent) {
|
|
exceptionState.ThrowDOMException(NotFoundError, "The node provided is null.");
|
|
return;
|
|
}
|
|
|
|
// InvalidStateError: Raised if the Range partially selects a non-Text node.
|
|
Node* startNonTextContainer = m_start.container();
|
|
if (startNonTextContainer->nodeType() == Node::TEXT_NODE)
|
|
startNonTextContainer = startNonTextContainer->parentNode();
|
|
Node* endNonTextContainer = m_end.container();
|
|
if (endNonTextContainer->nodeType() == Node::TEXT_NODE)
|
|
endNonTextContainer = endNonTextContainer->parentNode();
|
|
if (startNonTextContainer != endNonTextContainer) {
|
|
exceptionState.ThrowDOMException(InvalidStateError, "The Range has partially selected a non-Text node.");
|
|
return;
|
|
}
|
|
|
|
// InvalidNodeTypeError: Raised if node is an Attr, Entity, DocumentType, Notation,
|
|
// Document, or DocumentFragment node.
|
|
switch (newParent->nodeType()) {
|
|
case Node::DOCUMENT_FRAGMENT_NODE:
|
|
case Node::DOCUMENT_NODE:
|
|
exceptionState.ThrowDOMException(InvalidNodeTypeError, "The node provided is of type '" + newParent->nodeName() + "'.");
|
|
return;
|
|
case Node::ELEMENT_NODE:
|
|
case Node::TEXT_NODE:
|
|
break;
|
|
}
|
|
|
|
// Raise a HierarchyRequestError if m_start.container() doesn't accept children like newParent.
|
|
Node* parentOfNewParent = m_start.container();
|
|
|
|
// If m_start.container() is a character data node, it will be split and it will be its parent that will
|
|
// need to accept newParent (or in the case of a comment, it logically "would" be inserted into the parent,
|
|
// although this will fail below for another reason).
|
|
if (parentOfNewParent->isTextNode())
|
|
parentOfNewParent = parentOfNewParent->parentNode();
|
|
|
|
if (!parentOfNewParent) {
|
|
exceptionState.ThrowDOMException(HierarchyRequestError, "The container node is a detached character data node; no parent node is available for insertion.");
|
|
return;
|
|
}
|
|
|
|
if (newParent->contains(m_start.container())) {
|
|
exceptionState.ThrowDOMException(HierarchyRequestError, "The node provided contains the insertion point; it may not be inserted into itself.");
|
|
return;
|
|
}
|
|
|
|
// FIXME: Do we need a check if the node would end up with a child node of a type not
|
|
// allowed by the type of node?
|
|
|
|
while (Node* n = newParent->firstChild()) {
|
|
toContainerNode(newParent)->removeChild(n, exceptionState);
|
|
if (exceptionState.had_exception())
|
|
return;
|
|
}
|
|
RefPtr<DocumentFragment> fragment = extractContents(exceptionState);
|
|
if (exceptionState.had_exception())
|
|
return;
|
|
insertNode(newParent, exceptionState);
|
|
if (exceptionState.had_exception())
|
|
return;
|
|
newParent->appendChild(fragment.release(), exceptionState);
|
|
if (exceptionState.had_exception())
|
|
return;
|
|
selectNode(newParent.get(), exceptionState);
|
|
}
|
|
|
|
void Range::setStartBefore(Node* refNode, ExceptionState& exceptionState)
|
|
{
|
|
checkNodeBA(refNode, exceptionState);
|
|
if (exceptionState.had_exception())
|
|
return;
|
|
|
|
setStart(refNode->parentNode(), refNode->nodeIndex(), exceptionState);
|
|
}
|
|
|
|
void Range::checkExtractPrecondition(ExceptionState&)
|
|
{
|
|
// FIXME: Remove.
|
|
}
|
|
|
|
Node* Range::firstNode() const
|
|
{
|
|
if (m_start.container()->offsetInCharacters())
|
|
return m_start.container();
|
|
if (Node* child = NodeTraversal::childAt(*m_start.container(), m_start.offset()))
|
|
return child;
|
|
if (!m_start.offset())
|
|
return m_start.container();
|
|
return NodeTraversal::nextSkippingChildren(*m_start.container());
|
|
}
|
|
|
|
Node* Range::pastLastNode() const
|
|
{
|
|
if (m_end.container()->offsetInCharacters())
|
|
return NodeTraversal::nextSkippingChildren(*m_end.container());
|
|
if (Node* child = NodeTraversal::childAt(*m_end.container(), m_end.offset()))
|
|
return child;
|
|
return NodeTraversal::nextSkippingChildren(*m_end.container());
|
|
}
|
|
|
|
IntRect Range::boundingBox() const
|
|
{
|
|
IntRect result;
|
|
Vector<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) const
|
|
{
|
|
Node* startContainer = m_start.container();
|
|
ASSERT(startContainer);
|
|
Node* endContainer = m_end.container();
|
|
ASSERT(endContainer);
|
|
|
|
Node* stopNode = pastLastNode();
|
|
for (Node* node = firstNode(); node != stopNode; node = NodeTraversal::next(*node)) {
|
|
RenderObject* r = node->renderer();
|
|
if (!r || !r->isText())
|
|
continue;
|
|
RenderText* renderText = toRenderText(r);
|
|
int startOffset = node == startContainer ? m_start.offset() : 0;
|
|
int endOffset = node == endContainer ? m_end.offset() : std::numeric_limits<int>::max();
|
|
renderText->absoluteRectsForRange(rects, startOffset, endOffset, useSelectionHeight);
|
|
}
|
|
}
|
|
|
|
void Range::textQuads(Vector<FloatQuad>& quads, bool useSelectionHeight) const
|
|
{
|
|
Node* startContainer = m_start.container();
|
|
ASSERT(startContainer);
|
|
Node* endContainer = m_end.container();
|
|
ASSERT(endContainer);
|
|
|
|
Node* stopNode = pastLastNode();
|
|
for (Node* node = firstNode(); node != stopNode; node = NodeTraversal::next(*node)) {
|
|
RenderObject* r = node->renderer();
|
|
if (!r || !r->isText())
|
|
continue;
|
|
RenderText* renderText = toRenderText(r);
|
|
int startOffset = node == startContainer ? m_start.offset() : 0;
|
|
int endOffset = node == endContainer ? m_end.offset() : std::numeric_limits<int>::max();
|
|
renderText->absoluteQuadsForRange(quads, startOffset, endOffset, useSelectionHeight);
|
|
}
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
void Range::formatForDebugger(char* buffer, unsigned length) const
|
|
{
|
|
StringBuilder result;
|
|
|
|
const int FormatBufferSize = 1024;
|
|
char s[FormatBufferSize];
|
|
result.appendLiteral("from offset ");
|
|
result.appendNumber(m_start.offset());
|
|
result.appendLiteral(" of ");
|
|
m_start.container()->formatForDebugger(s, FormatBufferSize);
|
|
result.append(s);
|
|
result.appendLiteral(" to offset ");
|
|
result.appendNumber(m_end.offset());
|
|
result.appendLiteral(" of ");
|
|
m_end.container()->formatForDebugger(s, FormatBufferSize);
|
|
result.append(s);
|
|
|
|
strncpy(buffer, result.toString().utf8().data(), length - 1);
|
|
}
|
|
#endif
|
|
|
|
bool areRangesEqual(const Range* a, const Range* b)
|
|
{
|
|
if (a == b)
|
|
return true;
|
|
if (!a || !b)
|
|
return false;
|
|
return a->startPosition() == b->startPosition() && a->endPosition() == b->endPosition();
|
|
}
|
|
|
|
PassRefPtr<Range> rangeOfContents(Node* node)
|
|
{
|
|
ASSERT(node);
|
|
RefPtr<Range> range = Range::create(node->document());
|
|
range->selectNodeContents(node, IGNORE_EXCEPTION);
|
|
return range.release();
|
|
}
|
|
|
|
static inline void boundaryNodeChildrenChanged(RangeBoundaryPoint& boundary, ContainerNode* container)
|
|
{
|
|
if (!boundary.childBefore())
|
|
return;
|
|
if (boundary.container() != container)
|
|
return;
|
|
boundary.invalidateOffset();
|
|
}
|
|
|
|
void Range::nodeChildrenChanged(ContainerNode* container)
|
|
{
|
|
ASSERT(container);
|
|
ASSERT(container->document() == m_ownerDocument);
|
|
boundaryNodeChildrenChanged(m_start, container);
|
|
boundaryNodeChildrenChanged(m_end, container);
|
|
}
|
|
|
|
static inline void boundaryNodeChildrenWillBeRemoved(RangeBoundaryPoint& boundary, ContainerNode& container)
|
|
{
|
|
for (Node* nodeToBeRemoved = container.firstChild(); nodeToBeRemoved; nodeToBeRemoved = nodeToBeRemoved->nextSibling()) {
|
|
if (boundary.childBefore() == nodeToBeRemoved) {
|
|
boundary.setToStartOfNode(container);
|
|
return;
|
|
}
|
|
|
|
for (Node* n = boundary.container(); n; n = n->parentNode()) {
|
|
if (n == nodeToBeRemoved) {
|
|
boundary.setToStartOfNode(container);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Range::nodeChildrenWillBeRemoved(ContainerNode& container)
|
|
{
|
|
ASSERT(container.document() == m_ownerDocument);
|
|
boundaryNodeChildrenWillBeRemoved(m_start, container);
|
|
boundaryNodeChildrenWillBeRemoved(m_end, container);
|
|
}
|
|
|
|
static inline void boundaryNodeWillBeRemoved(RangeBoundaryPoint& boundary, Node& nodeToBeRemoved)
|
|
{
|
|
if (boundary.childBefore() == nodeToBeRemoved) {
|
|
boundary.childBeforeWillBeRemoved();
|
|
return;
|
|
}
|
|
|
|
for (Node* n = boundary.container(); n; n = n->parentNode()) {
|
|
if (n == nodeToBeRemoved) {
|
|
boundary.setToBeforeChild(nodeToBeRemoved);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Range::nodeWillBeRemoved(Node& node)
|
|
{
|
|
ASSERT(node.document() == m_ownerDocument);
|
|
ASSERT(node != m_ownerDocument.get());
|
|
|
|
// FIXME: Once DOMNodeRemovedFromDocument mutation event removed, we
|
|
// should change following if-statement to ASSERT(!node->parentNode).
|
|
if (!node.parentNode())
|
|
return;
|
|
boundaryNodeWillBeRemoved(m_start, node);
|
|
boundaryNodeWillBeRemoved(m_end, node);
|
|
}
|
|
|
|
static inline void boundaryTextInserted(RangeBoundaryPoint& boundary, Node* text, unsigned offset, unsigned length)
|
|
{
|
|
if (boundary.container() != text)
|
|
return;
|
|
unsigned boundaryOffset = boundary.offset();
|
|
if (offset >= boundaryOffset)
|
|
return;
|
|
boundary.setOffset(boundaryOffset + length);
|
|
}
|
|
|
|
void Range::didInsertText(Node* text, unsigned offset, unsigned length)
|
|
{
|
|
ASSERT(text);
|
|
ASSERT(text->document() == m_ownerDocument);
|
|
boundaryTextInserted(m_start, text, offset, length);
|
|
boundaryTextInserted(m_end, text, offset, length);
|
|
}
|
|
|
|
static inline void boundaryTextRemoved(RangeBoundaryPoint& boundary, Node* text, unsigned offset, unsigned length)
|
|
{
|
|
if (boundary.container() != text)
|
|
return;
|
|
unsigned boundaryOffset = boundary.offset();
|
|
if (offset >= boundaryOffset)
|
|
return;
|
|
if (offset + length >= boundaryOffset)
|
|
boundary.setOffset(offset);
|
|
else
|
|
boundary.setOffset(boundaryOffset - length);
|
|
}
|
|
|
|
void Range::didRemoveText(Node* text, unsigned offset, unsigned length)
|
|
{
|
|
ASSERT(text);
|
|
ASSERT(text->document() == m_ownerDocument);
|
|
boundaryTextRemoved(m_start, text, offset, length);
|
|
boundaryTextRemoved(m_end, text, offset, length);
|
|
}
|
|
|
|
static inline void boundaryTextNodesMerged(RangeBoundaryPoint& boundary, const NodeWithIndex& oldNode, unsigned offset)
|
|
{
|
|
if (boundary.container() == oldNode.node())
|
|
boundary.set(oldNode.node().previousSibling(), boundary.offset() + offset, 0);
|
|
else if (boundary.container() == oldNode.node().parentNode() && boundary.offset() == oldNode.index())
|
|
boundary.set(oldNode.node().previousSibling(), offset, 0);
|
|
}
|
|
|
|
void Range::didMergeTextNodes(const NodeWithIndex& oldNode, unsigned offset)
|
|
{
|
|
ASSERT(oldNode.node().document() == m_ownerDocument);
|
|
ASSERT(oldNode.node().parentNode());
|
|
ASSERT(oldNode.node().isTextNode());
|
|
ASSERT(oldNode.node().previousSibling());
|
|
ASSERT(oldNode.node().previousSibling()->isTextNode());
|
|
boundaryTextNodesMerged(m_start, oldNode, offset);
|
|
boundaryTextNodesMerged(m_end, oldNode, offset);
|
|
}
|
|
|
|
void Range::updateOwnerDocumentIfNeeded()
|
|
{
|
|
ASSERT(m_start.container());
|
|
ASSERT(m_end.container());
|
|
Document& newDocument = m_start.container()->document();
|
|
ASSERT(newDocument == m_end.container()->document());
|
|
if (newDocument == m_ownerDocument)
|
|
return;
|
|
m_ownerDocument->detachRange(this);
|
|
m_ownerDocument = &newDocument;
|
|
m_ownerDocument->attachRange(this);
|
|
}
|
|
|
|
static inline void boundaryTextNodeSplit(RangeBoundaryPoint& boundary, Text& oldNode)
|
|
{
|
|
Node* boundaryContainer = boundary.container();
|
|
unsigned boundaryOffset = boundary.offset();
|
|
if (boundary.childBefore() == &oldNode)
|
|
boundary.set(boundaryContainer, boundaryOffset + 1, oldNode.nextSibling());
|
|
else if (boundary.container() == &oldNode && boundaryOffset > oldNode.length())
|
|
boundary.set(oldNode.nextSibling(), boundaryOffset - oldNode.length(), 0);
|
|
}
|
|
|
|
void Range::didSplitTextNode(Text& oldNode)
|
|
{
|
|
ASSERT(oldNode.document() == m_ownerDocument);
|
|
ASSERT(oldNode.parentNode());
|
|
ASSERT(oldNode.nextSibling());
|
|
ASSERT(oldNode.nextSibling()->isTextNode());
|
|
boundaryTextNodeSplit(m_start, oldNode);
|
|
boundaryTextNodeSplit(m_end, oldNode);
|
|
ASSERT(boundaryPointsValid());
|
|
}
|
|
|
|
void Range::expand(const String& unit, ExceptionState& exceptionState)
|
|
{
|
|
VisiblePosition start(startPosition());
|
|
VisiblePosition end(endPosition());
|
|
if (unit == "word") {
|
|
start = startOfWord(start);
|
|
end = endOfWord(end);
|
|
} else if (unit == "sentence") {
|
|
start = startOfSentence(start);
|
|
end = endOfSentence(end);
|
|
} else if (unit == "block") {
|
|
start = startOfParagraph(start);
|
|
end = endOfParagraph(end);
|
|
} else if (unit == "document") {
|
|
start = startOfDocument(start);
|
|
end = endOfDocument(end);
|
|
} else
|
|
return;
|
|
setStart(start.deepEquivalent().containerNode(), start.deepEquivalent().computeOffsetInContainerNode(), exceptionState);
|
|
setEnd(end.deepEquivalent().containerNode(), end.deepEquivalent().computeOffsetInContainerNode(), exceptionState);
|
|
}
|
|
|
|
PassRefPtr<ClientRectList> Range::getClientRects() const
|
|
{
|
|
m_ownerDocument->updateLayout();
|
|
|
|
Vector<FloatQuad> quads;
|
|
getBorderAndTextQuads(quads);
|
|
|
|
return ClientRectList::create(quads);
|
|
}
|
|
|
|
PassRefPtr<ClientRect> Range::getBoundingClientRect() const
|
|
{
|
|
return ClientRect::create(boundingRect());
|
|
}
|
|
|
|
void Range::getBorderAndTextQuads(Vector<FloatQuad>& quads) const
|
|
{
|
|
Node* startContainer = m_start.container();
|
|
Node* endContainer = m_end.container();
|
|
Node* stopNode = pastLastNode();
|
|
|
|
HashSet<RawPtr<Node> > nodeSet;
|
|
for (Node* node = firstNode(); node != stopNode; node = NodeTraversal::next(*node)) {
|
|
if (node->isElementNode())
|
|
nodeSet.add(node);
|
|
}
|
|
|
|
for (Node* node = firstNode(); node != stopNode; node = NodeTraversal::next(*node)) {
|
|
if (node->isElementNode()) {
|
|
if (!nodeSet.contains(node->parentNode())) {
|
|
if (RenderBoxModelObject* renderBoxModelObject = toElement(node)->renderBoxModelObject()) {
|
|
Vector<FloatQuad> elementQuads;
|
|
renderBoxModelObject->absoluteQuads(elementQuads);
|
|
|
|
quads.appendVector(elementQuads);
|
|
}
|
|
}
|
|
} else if (node->isTextNode()) {
|
|
if (RenderText* renderText = toText(node)->renderer()) {
|
|
int startOffset = (node == startContainer) ? m_start.offset() : 0;
|
|
int endOffset = (node == endContainer) ? m_end.offset() : INT_MAX;
|
|
|
|
Vector<FloatQuad> textQuads;
|
|
renderText->absoluteQuadsForRange(textQuads, startOffset, endOffset);
|
|
|
|
quads.appendVector(textQuads);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FloatRect Range::boundingRect() const
|
|
{
|
|
m_ownerDocument->updateLayout();
|
|
|
|
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 blink
|
|
|
|
#ifndef NDEBUG
|
|
|
|
void showTree(const blink::Range* range)
|
|
{
|
|
if (range && range->boundaryPointsValid()) {
|
|
range->startContainer()->showTreeAndMark(range->startContainer(), "S", range->endContainer(), "E");
|
|
fprintf(stderr, "start offset: %d, end offset: %d\n", range->startOffset(), range->endOffset());
|
|
}
|
|
}
|
|
|
|
#endif
|