1 /* 2 * (C) 1999 Lars Knoll (knoll (at) kde.org) 3 * (C) 2000 Gunnstein Lye (gunnstein (at) netcom.no) 4 * (C) 2000 Frederik Holljen (frederik.holljen (at) hig.no) 5 * (C) 2001 Peter Kelly (pmk (at) post.com) 6 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved. 7 * Copyright (C) 2011 Motorola Mobility. All rights reserved. 8 * 9 * This library is free software; you can redistribute it and/or 10 * modify it under the terms of the GNU Library General Public 11 * License as published by the Free Software Foundation; either 12 * version 2 of the License, or (at your option) any later version. 13 * 14 * This library is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 * Library General Public License for more details. 18 * 19 * You should have received a copy of the GNU Library General Public License 20 * along with this library; see the file COPYING.LIB. If not, write to 21 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 22 * Boston, MA 02110-1301, USA. 23 */ 24 25 #include "config.h" 26 #include "core/dom/Range.h" 27 28 #include "bindings/core/v8/ExceptionState.h" 29 #include "core/dom/ClientRect.h" 30 #include "core/dom/ClientRectList.h" 31 #include "core/dom/DocumentFragment.h" 32 #include "core/dom/ExceptionCode.h" 33 #include "core/dom/Node.h" 34 #include "core/dom/NodeTraversal.h" 35 #include "core/dom/NodeWithIndex.h" 36 #include "core/dom/ProcessingInstruction.h" 37 #include "core/dom/Text.h" 38 #include "core/editing/TextIterator.h" 39 #include "core/editing/VisiblePosition.h" 40 #include "core/editing/VisibleUnits.h" 41 #include "core/editing/markup.h" 42 #include "core/events/ScopedEventQueue.h" 43 #include "core/html/HTMLBodyElement.h" 44 #include "core/html/HTMLElement.h" 45 #include "core/rendering/RenderBoxModelObject.h" 46 #include "core/rendering/RenderText.h" 47 #include "core/svg/SVGSVGElement.h" 48 #include "platform/geometry/FloatQuad.h" 49 #include "wtf/RefCountedLeakCounter.h" 50 #include "wtf/text/CString.h" 51 #include "wtf/text/StringBuilder.h" 52 #ifndef NDEBUG 53 #include <stdio.h> 54 #endif 55 56 namespace blink { 57 58 DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, rangeCounter, ("Range")); 59 60 inline Range::Range(Document& ownerDocument) 61 : m_ownerDocument(&ownerDocument) 62 , m_start(m_ownerDocument) 63 , m_end(m_ownerDocument) 64 { 65 #ifndef NDEBUG 66 rangeCounter.increment(); 67 #endif 68 69 m_ownerDocument->attachRange(this); 70 } 71 72 PassRefPtrWillBeRawPtr<Range> Range::create(Document& ownerDocument) 73 { 74 return adoptRefWillBeNoop(new Range(ownerDocument)); 75 } 76 77 inline Range::Range(Document& ownerDocument, Node* startContainer, int startOffset, Node* endContainer, int endOffset) 78 : m_ownerDocument(&ownerDocument) 79 , m_start(m_ownerDocument) 80 , m_end(m_ownerDocument) 81 { 82 #ifndef NDEBUG 83 rangeCounter.increment(); 84 #endif 85 86 m_ownerDocument->attachRange(this); 87 88 // Simply setting the containers and offsets directly would not do any of the checking 89 // that setStart and setEnd do, so we call those functions. 90 setStart(startContainer, startOffset); 91 setEnd(endContainer, endOffset); 92 } 93 94 PassRefPtrWillBeRawPtr<Range> Range::create(Document& ownerDocument, Node* startContainer, int startOffset, Node* endContainer, int endOffset) 95 { 96 return adoptRefWillBeNoop(new Range(ownerDocument, startContainer, startOffset, endContainer, endOffset)); 97 } 98 99 PassRefPtrWillBeRawPtr<Range> Range::create(Document& ownerDocument, const Position& start, const Position& end) 100 { 101 return adoptRefWillBeNoop(new Range(ownerDocument, start.containerNode(), start.computeOffsetInContainerNode(), end.containerNode(), end.computeOffsetInContainerNode())); 102 } 103 104 Range::~Range() 105 { 106 #if !ENABLE(OILPAN) 107 // Always detach (even if we've already detached) to fix https://bugs.webkit.org/show_bug.cgi?id=26044 108 m_ownerDocument->detachRange(this); 109 #endif 110 111 #ifndef NDEBUG 112 rangeCounter.decrement(); 113 #endif 114 } 115 116 void Range::setDocument(Document& document) 117 { 118 ASSERT(m_ownerDocument != document); 119 ASSERT(m_ownerDocument); 120 m_ownerDocument->detachRange(this); 121 m_ownerDocument = &document; 122 m_start.setToStartOfNode(document); 123 m_end.setToStartOfNode(document); 124 m_ownerDocument->attachRange(this); 125 } 126 127 Node* Range::commonAncestorContainer() const 128 { 129 return commonAncestorContainer(m_start.container(), m_end.container()); 130 } 131 132 Node* Range::commonAncestorContainer(Node* containerA, Node* containerB) 133 { 134 for (Node* parentA = containerA; parentA; parentA = parentA->parentNode()) { 135 for (Node* parentB = containerB; parentB; parentB = parentB->parentNode()) { 136 if (parentA == parentB) 137 return parentA; 138 } 139 } 140 return 0; 141 } 142 143 static inline bool checkForDifferentRootContainer(const RangeBoundaryPoint& start, const RangeBoundaryPoint& end) 144 { 145 Node* endRootContainer = end.container(); 146 while (endRootContainer->parentNode()) 147 endRootContainer = endRootContainer->parentNode(); 148 Node* startRootContainer = start.container(); 149 while (startRootContainer->parentNode()) 150 startRootContainer = startRootContainer->parentNode(); 151 152 return startRootContainer != endRootContainer || (Range::compareBoundaryPoints(start, end, ASSERT_NO_EXCEPTION) > 0); 153 } 154 155 void Range::setStart(PassRefPtrWillBeRawPtr<Node> refNode, int offset, ExceptionState& exceptionState) 156 { 157 if (!refNode) { 158 exceptionState.throwDOMException(NotFoundError, "The node provided was null."); 159 return; 160 } 161 162 bool didMoveDocument = false; 163 if (refNode->document() != m_ownerDocument) { 164 setDocument(refNode->document()); 165 didMoveDocument = true; 166 } 167 168 Node* childNode = checkNodeWOffset(refNode.get(), offset, exceptionState); 169 if (exceptionState.hadException()) 170 return; 171 172 m_start.set(refNode, offset, childNode); 173 174 if (didMoveDocument || checkForDifferentRootContainer(m_start, m_end)) 175 collapse(true); 176 } 177 178 void Range::setEnd(PassRefPtrWillBeRawPtr<Node> refNode, int offset, ExceptionState& exceptionState) 179 { 180 if (!refNode) { 181 exceptionState.throwDOMException(NotFoundError, "The node provided was null."); 182 return; 183 } 184 185 bool didMoveDocument = false; 186 if (refNode->document() != m_ownerDocument) { 187 setDocument(refNode->document()); 188 didMoveDocument = true; 189 } 190 191 Node* childNode = checkNodeWOffset(refNode.get(), offset, exceptionState); 192 if (exceptionState.hadException()) 193 return; 194 195 m_end.set(refNode, offset, childNode); 196 197 if (didMoveDocument || checkForDifferentRootContainer(m_start, m_end)) 198 collapse(false); 199 } 200 201 void Range::setStart(const Position& start, ExceptionState& exceptionState) 202 { 203 Position parentAnchored = start.parentAnchoredEquivalent(); 204 setStart(parentAnchored.containerNode(), parentAnchored.offsetInContainerNode(), exceptionState); 205 } 206 207 void Range::setEnd(const Position& end, ExceptionState& exceptionState) 208 { 209 Position parentAnchored = end.parentAnchoredEquivalent(); 210 setEnd(parentAnchored.containerNode(), parentAnchored.offsetInContainerNode(), exceptionState); 211 } 212 213 void Range::collapse(bool toStart) 214 { 215 if (toStart) 216 m_end = m_start; 217 else 218 m_start = m_end; 219 } 220 221 bool Range::isPointInRange(Node* refNode, int offset, ExceptionState& exceptionState) 222 { 223 if (!refNode) { 224 exceptionState.throwDOMException(HierarchyRequestError, "The node provided was null."); 225 return false; 226 } 227 228 if (!refNode->inActiveDocument() || refNode->document() != m_ownerDocument) { 229 return false; 230 } 231 232 checkNodeWOffset(refNode, offset, exceptionState); 233 if (exceptionState.hadException()) 234 return false; 235 236 return compareBoundaryPoints(refNode, offset, m_start.container(), m_start.offset(), exceptionState) >= 0 && !exceptionState.hadException() 237 && compareBoundaryPoints(refNode, offset, m_end.container(), m_end.offset(), exceptionState) <= 0 && !exceptionState.hadException(); 238 } 239 240 short Range::comparePoint(Node* refNode, int offset, ExceptionState& exceptionState) const 241 { 242 // http://developer.mozilla.org/en/docs/DOM:range.comparePoint 243 // This method returns -1, 0 or 1 depending on if the point described by the 244 // refNode node and an offset within the node is before, same as, or after the range respectively. 245 246 if (!refNode->inActiveDocument()) { 247 exceptionState.throwDOMException(WrongDocumentError, "The node provided is not in an active document."); 248 return 0; 249 } 250 251 if (refNode->document() != m_ownerDocument) { 252 exceptionState.throwDOMException(WrongDocumentError, "The node provided is not in this Range's Document."); 253 return 0; 254 } 255 256 checkNodeWOffset(refNode, offset, exceptionState); 257 if (exceptionState.hadException()) 258 return 0; 259 260 // compare to start, and point comes before 261 if (compareBoundaryPoints(refNode, offset, m_start.container(), m_start.offset(), exceptionState) < 0) 262 return -1; 263 264 if (exceptionState.hadException()) 265 return 0; 266 267 // compare to end, and point comes after 268 if (compareBoundaryPoints(refNode, offset, m_end.container(), m_end.offset(), exceptionState) > 0 && !exceptionState.hadException()) 269 return 1; 270 271 // point is in the middle of this range, or on the boundary points 272 return 0; 273 } 274 275 Range::CompareResults Range::compareNode(Node* refNode, ExceptionState& exceptionState) const 276 { 277 // http://developer.mozilla.org/en/docs/DOM:range.compareNode 278 // This method returns 0, 1, 2, or 3 based on if the node is before, after, 279 // before and after(surrounds), or inside the range, respectively 280 281 if (!refNode) { 282 exceptionState.throwDOMException(NotFoundError, "The node provided was null."); 283 return NODE_BEFORE; 284 } 285 286 if (!refNode->inActiveDocument()) { 287 // Firefox doesn't throw an exception for this case; it returns 0. 288 return NODE_BEFORE; 289 } 290 291 if (refNode->document() != m_ownerDocument) { 292 // Firefox doesn't throw an exception for this case; it returns 0. 293 return NODE_BEFORE; 294 } 295 296 ContainerNode* parentNode = refNode->parentNode(); 297 int nodeIndex = refNode->nodeIndex(); 298 299 if (!parentNode) { 300 // if the node is the top document we should return NODE_BEFORE_AND_AFTER 301 // but we throw to match firefox behavior 302 exceptionState.throwDOMException(NotFoundError, "The provided node has no parent."); 303 return NODE_BEFORE; 304 } 305 306 if (comparePoint(parentNode, nodeIndex, exceptionState) < 0) { // starts before 307 if (comparePoint(parentNode, nodeIndex + 1, exceptionState) > 0) // ends after the range 308 return NODE_BEFORE_AND_AFTER; 309 return NODE_BEFORE; // ends before or in the range 310 } 311 // starts at or after the range start 312 if (comparePoint(parentNode, nodeIndex + 1, exceptionState) > 0) // ends after the range 313 return NODE_AFTER; 314 return NODE_INSIDE; // ends inside the range 315 } 316 317 short Range::compareBoundaryPoints(unsigned how, const Range* sourceRange, ExceptionState& exceptionState) const 318 { 319 if (!(how == START_TO_START || how == START_TO_END || how == END_TO_END || how == END_TO_START)) { 320 exceptionState.throwDOMException(NotSupportedError, "The comparison method provided must be one of 'START_TO_START', 'START_TO_END', 'END_TO_END', or 'END_TO_START'."); 321 return 0; 322 } 323 324 Node* thisCont = commonAncestorContainer(); 325 Node* sourceCont = sourceRange->commonAncestorContainer(); 326 if (thisCont->document() != sourceCont->document()) { 327 exceptionState.throwDOMException(WrongDocumentError, "The source range is in a different document than this range."); 328 return 0; 329 } 330 331 Node* thisTop = thisCont; 332 Node* sourceTop = sourceCont; 333 while (thisTop->parentNode()) 334 thisTop = thisTop->parentNode(); 335 while (sourceTop->parentNode()) 336 sourceTop = sourceTop->parentNode(); 337 if (thisTop != sourceTop) { // in different DocumentFragments 338 exceptionState.throwDOMException(WrongDocumentError, "The source range is in a different document than this range."); 339 return 0; 340 } 341 342 switch (how) { 343 case START_TO_START: 344 return compareBoundaryPoints(m_start, sourceRange->m_start, exceptionState); 345 case START_TO_END: 346 return compareBoundaryPoints(m_end, sourceRange->m_start, exceptionState); 347 case END_TO_END: 348 return compareBoundaryPoints(m_end, sourceRange->m_end, exceptionState); 349 case END_TO_START: 350 return compareBoundaryPoints(m_start, sourceRange->m_end, exceptionState); 351 } 352 353 ASSERT_NOT_REACHED(); 354 return 0; 355 } 356 357 short Range::compareBoundaryPoints(Node* containerA, int offsetA, Node* containerB, int offsetB, ExceptionState& exceptionState) 358 { 359 ASSERT(containerA); 360 ASSERT(containerB); 361 362 if (!containerA) 363 return -1; 364 if (!containerB) 365 return 1; 366 367 // see DOM2 traversal & range section 2.5 368 369 // case 1: both points have the same container 370 if (containerA == containerB) { 371 if (offsetA == offsetB) 372 return 0; // A is equal to B 373 if (offsetA < offsetB) 374 return -1; // A is before B 375 else 376 return 1; // A is after B 377 } 378 379 // case 2: node C (container B or an ancestor) is a child node of A 380 Node* c = containerB; 381 while (c && c->parentNode() != containerA) 382 c = c->parentNode(); 383 if (c) { 384 int offsetC = 0; 385 Node* n = containerA->firstChild(); 386 while (n != c && offsetC < offsetA) { 387 offsetC++; 388 n = n->nextSibling(); 389 } 390 391 if (offsetA <= offsetC) 392 return -1; // A is before B 393 else 394 return 1; // A is after B 395 } 396 397 // case 3: node C (container A or an ancestor) is a child node of B 398 c = containerA; 399 while (c && c->parentNode() != containerB) 400 c = c->parentNode(); 401 if (c) { 402 int offsetC = 0; 403 Node* n = containerB->firstChild(); 404 while (n != c && offsetC < offsetB) { 405 offsetC++; 406 n = n->nextSibling(); 407 } 408 409 if (offsetC < offsetB) 410 return -1; // A is before B 411 else 412 return 1; // A is after B 413 } 414 415 // case 4: containers A & B are siblings, or children of siblings 416 // ### we need to do a traversal here instead 417 Node* commonAncestor = commonAncestorContainer(containerA, containerB); 418 if (!commonAncestor) { 419 exceptionState.throwDOMException(WrongDocumentError, "The two ranges are in separate documents."); 420 return 0; 421 } 422 Node* childA = containerA; 423 while (childA && childA->parentNode() != commonAncestor) 424 childA = childA->parentNode(); 425 if (!childA) 426 childA = commonAncestor; 427 Node* childB = containerB; 428 while (childB && childB->parentNode() != commonAncestor) 429 childB = childB->parentNode(); 430 if (!childB) 431 childB = commonAncestor; 432 433 if (childA == childB) 434 return 0; // A is equal to B 435 436 Node* n = commonAncestor->firstChild(); 437 while (n) { 438 if (n == childA) 439 return -1; // A is before B 440 if (n == childB) 441 return 1; // A is after B 442 n = n->nextSibling(); 443 } 444 445 // Should never reach this point. 446 ASSERT_NOT_REACHED(); 447 return 0; 448 } 449 450 short Range::compareBoundaryPoints(const RangeBoundaryPoint& boundaryA, const RangeBoundaryPoint& boundaryB, ExceptionState& exceptionState) 451 { 452 return compareBoundaryPoints(boundaryA.container(), boundaryA.offset(), boundaryB.container(), boundaryB.offset(), exceptionState); 453 } 454 455 bool Range::boundaryPointsValid() const 456 { 457 TrackExceptionState exceptionState; 458 return compareBoundaryPoints(m_start, m_end, exceptionState) <= 0 && !exceptionState.hadException(); 459 } 460 461 void Range::deleteContents(ExceptionState& exceptionState) 462 { 463 ASSERT(boundaryPointsValid()); 464 465 { 466 EventQueueScope eventQueueScope; 467 processContents(DELETE_CONTENTS, exceptionState); 468 } 469 } 470 471 static bool nodeValidForIntersects(Node* refNode, Document* expectedDocument, ExceptionState& exceptionState) 472 { 473 if (!refNode) { 474 exceptionState.throwDOMException(NotFoundError, "The node provided is null."); 475 return false; 476 } 477 478 if (!refNode->inActiveDocument() || refNode->document() != expectedDocument) { 479 // Firefox doesn't throw an exception for these cases; it returns false. 480 return false; 481 } 482 483 return true; 484 } 485 486 bool Range::intersectsNode(Node* refNode, ExceptionState& exceptionState) 487 { 488 // http://developer.mozilla.org/en/docs/DOM:range.intersectsNode 489 // Returns a bool if the node intersects the range. 490 if (!nodeValidForIntersects(refNode, m_ownerDocument.get(), exceptionState)) 491 return false; 492 493 ContainerNode* parentNode = refNode->parentNode(); 494 int nodeIndex = refNode->nodeIndex(); 495 496 if (!parentNode) { 497 // if the node is the top document we should return NODE_BEFORE_AND_AFTER 498 // but we throw to match firefox behavior 499 exceptionState.throwDOMException(NotFoundError, "The node provided has no parent."); 500 return false; 501 } 502 503 if (comparePoint(parentNode, nodeIndex, exceptionState) < 0 // starts before start 504 && comparePoint(parentNode, nodeIndex + 1, exceptionState) < 0) { // ends before start 505 return false; 506 } 507 508 if (comparePoint(parentNode, nodeIndex, exceptionState) > 0 // starts after end 509 && comparePoint(parentNode, nodeIndex + 1, exceptionState) > 0) { // ends after end 510 return false; 511 } 512 513 return true; // all other cases 514 } 515 516 bool Range::intersectsNode(Node* refNode, const Position& start, const Position& end, ExceptionState& exceptionState) 517 { 518 // http://developer.mozilla.org/en/docs/DOM:range.intersectsNode 519 // Returns a bool if the node intersects the range. 520 if (!nodeValidForIntersects(refNode, start.document(), exceptionState)) 521 return false; 522 523 ContainerNode* parentNode = refNode->parentNode(); 524 int nodeIndex = refNode->nodeIndex(); 525 526 if (!parentNode) { 527 // if the node is the top document we should return NODE_BEFORE_AND_AFTER 528 // but we throw to match firefox behavior 529 exceptionState.throwDOMException(NotFoundError, "The node provided has no parent."); 530 return false; 531 } 532 533 Node* startContainerNode = start.containerNode(); 534 int startOffset = start.computeOffsetInContainerNode(); 535 536 if (compareBoundaryPoints(parentNode, nodeIndex, startContainerNode, startOffset, exceptionState) < 0 // starts before start 537 && compareBoundaryPoints(parentNode, nodeIndex + 1, startContainerNode, startOffset, exceptionState) < 0) { // ends before start 538 ASSERT(!exceptionState.hadException()); 539 return false; 540 } 541 542 Node* endContainerNode = end.containerNode(); 543 int endOffset = end.computeOffsetInContainerNode(); 544 545 if (compareBoundaryPoints(parentNode, nodeIndex, endContainerNode, endOffset, exceptionState) > 0 // starts after end 546 && compareBoundaryPoints(parentNode, nodeIndex + 1, endContainerNode, endOffset, exceptionState) > 0) { // ends after end 547 ASSERT(!exceptionState.hadException()); 548 return false; 549 } 550 551 return true; // all other cases 552 } 553 554 static inline Node* highestAncestorUnderCommonRoot(Node* node, Node* commonRoot) 555 { 556 if (node == commonRoot) 557 return 0; 558 559 ASSERT(commonRoot->contains(node)); 560 561 while (node->parentNode() != commonRoot) 562 node = node->parentNode(); 563 564 return node; 565 } 566 567 static inline Node* childOfCommonRootBeforeOffset(Node* container, unsigned offset, Node* commonRoot) 568 { 569 ASSERT(container); 570 ASSERT(commonRoot); 571 572 if (!commonRoot->contains(container)) 573 return 0; 574 575 if (container == commonRoot) { 576 container = container->firstChild(); 577 for (unsigned i = 0; container && i < offset; i++) 578 container = container->nextSibling(); 579 } else { 580 while (container->parentNode() != commonRoot) 581 container = container->parentNode(); 582 } 583 584 return container; 585 } 586 587 PassRefPtrWillBeRawPtr<DocumentFragment> Range::processContents(ActionType action, ExceptionState& exceptionState) 588 { 589 typedef WillBeHeapVector<RefPtrWillBeMember<Node> > NodeVector; 590 591 RefPtrWillBeRawPtr<DocumentFragment> fragment = nullptr; 592 if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) 593 fragment = DocumentFragment::create(*m_ownerDocument.get()); 594 595 if (collapsed()) 596 return fragment.release(); 597 598 RefPtrWillBeRawPtr<Node> commonRoot = commonAncestorContainer(); 599 ASSERT(commonRoot); 600 601 if (m_start.container() == m_end.container()) { 602 processContentsBetweenOffsets(action, fragment, m_start.container(), m_start.offset(), m_end.offset(), exceptionState); 603 return fragment; 604 } 605 606 // Since mutation observers can modify the range during the process, the boundary points need to be saved. 607 RangeBoundaryPoint originalStart(m_start); 608 RangeBoundaryPoint originalEnd(m_end); 609 610 // what is the highest node that partially selects the start / end of the range? 611 RefPtrWillBeRawPtr<Node> partialStart = highestAncestorUnderCommonRoot(originalStart.container(), commonRoot.get()); 612 RefPtrWillBeRawPtr<Node> partialEnd = highestAncestorUnderCommonRoot(originalEnd.container(), commonRoot.get()); 613 614 // Start and end containers are different. 615 // There are three possibilities here: 616 // 1. Start container == commonRoot (End container must be a descendant) 617 // 2. End container == commonRoot (Start container must be a descendant) 618 // 3. Neither is commonRoot, they are both descendants 619 // 620 // In case 3, we grab everything after the start (up until a direct child 621 // of commonRoot) into leftContents, and everything before the end (up until 622 // a direct child of commonRoot) into rightContents. Then we process all 623 // commonRoot children between leftContents and rightContents 624 // 625 // In case 1 or 2, we skip either processing of leftContents or rightContents, 626 // in which case the last lot of nodes either goes from the first or last 627 // child of commonRoot. 628 // 629 // These are deleted, cloned, or extracted (i.e. both) depending on action. 630 631 // Note that we are verifying that our common root hierarchy is still intact 632 // after any DOM mutation event, at various stages below. See webkit bug 60350. 633 634 RefPtrWillBeRawPtr<Node> leftContents = nullptr; 635 if (originalStart.container() != commonRoot && commonRoot->contains(originalStart.container())) { 636 leftContents = processContentsBetweenOffsets(action, nullptr, originalStart.container(), originalStart.offset(), originalStart.container()->lengthOfContents(), exceptionState); 637 leftContents = processAncestorsAndTheirSiblings(action, originalStart.container(), ProcessContentsForward, leftContents, commonRoot.get(), exceptionState); 638 } 639 640 RefPtrWillBeRawPtr<Node> rightContents = nullptr; 641 if (m_end.container() != commonRoot && commonRoot->contains(originalEnd.container())) { 642 rightContents = processContentsBetweenOffsets(action, nullptr, originalEnd.container(), 0, originalEnd.offset(), exceptionState); 643 rightContents = processAncestorsAndTheirSiblings(action, originalEnd.container(), ProcessContentsBackward, rightContents, commonRoot.get(), exceptionState); 644 } 645 646 // delete all children of commonRoot between the start and end container 647 RefPtrWillBeRawPtr<Node> processStart = childOfCommonRootBeforeOffset(originalStart.container(), originalStart.offset(), commonRoot.get()); 648 if (processStart && originalStart.container() != commonRoot) // processStart contains nodes before m_start. 649 processStart = processStart->nextSibling(); 650 RefPtrWillBeRawPtr<Node> processEnd = childOfCommonRootBeforeOffset(originalEnd.container(), originalEnd.offset(), commonRoot.get()); 651 652 // Collapse the range, making sure that the result is not within a node that was partially selected. 653 if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) { 654 if (partialStart && commonRoot->contains(partialStart.get())) { 655 // FIXME: We should not continue if we have an earlier error. 656 exceptionState.clearException(); 657 setStart(partialStart->parentNode(), partialStart->nodeIndex() + 1, exceptionState); 658 } else if (partialEnd && commonRoot->contains(partialEnd.get())) { 659 // FIXME: We should not continue if we have an earlier error. 660 exceptionState.clearException(); 661 setStart(partialEnd->parentNode(), partialEnd->nodeIndex(), exceptionState); 662 } 663 if (exceptionState.hadException()) 664 return nullptr; 665 m_end = m_start; 666 } 667 668 originalStart.clear(); 669 originalEnd.clear(); 670 671 // Now add leftContents, stuff in between, and rightContents to the fragment 672 // (or just delete the stuff in between) 673 674 if ((action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) && leftContents) 675 fragment->appendChild(leftContents, exceptionState); 676 677 if (processStart) { 678 NodeVector nodes; 679 for (Node* n = processStart.get(); n && n != processEnd; n = n->nextSibling()) 680 nodes.append(n); 681 processNodes(action, nodes, commonRoot, fragment, exceptionState); 682 } 683 684 if ((action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) && rightContents) 685 fragment->appendChild(rightContents, exceptionState); 686 687 return fragment.release(); 688 } 689 690 static inline void deleteCharacterData(PassRefPtrWillBeRawPtr<CharacterData> data, unsigned startOffset, unsigned endOffset, ExceptionState& exceptionState) 691 { 692 if (data->length() - endOffset) 693 data->deleteData(endOffset, data->length() - endOffset, exceptionState); 694 if (startOffset) 695 data->deleteData(0, startOffset, exceptionState); 696 } 697 698 PassRefPtrWillBeRawPtr<Node> Range::processContentsBetweenOffsets(ActionType action, PassRefPtrWillBeRawPtr<DocumentFragment> fragment, 699 Node* container, unsigned startOffset, unsigned endOffset, ExceptionState& exceptionState) 700 { 701 ASSERT(container); 702 ASSERT(startOffset <= endOffset); 703 704 // This switch statement must be consistent with that of Node::lengthOfContents. 705 RefPtrWillBeRawPtr<Node> result = nullptr; 706 switch (container->nodeType()) { 707 case Node::TEXT_NODE: 708 case Node::CDATA_SECTION_NODE: 709 case Node::COMMENT_NODE: 710 endOffset = std::min(endOffset, toCharacterData(container)->length()); 711 if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) { 712 RefPtrWillBeRawPtr<CharacterData> c = static_pointer_cast<CharacterData>(container->cloneNode(true)); 713 deleteCharacterData(c, startOffset, endOffset, exceptionState); 714 if (fragment) { 715 result = fragment; 716 result->appendChild(c.release(), exceptionState); 717 } else 718 result = c.release(); 719 } 720 if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) 721 toCharacterData(container)->deleteData(startOffset, endOffset - startOffset, exceptionState); 722 break; 723 case Node::PROCESSING_INSTRUCTION_NODE: 724 endOffset = std::min(endOffset, toProcessingInstruction(container)->data().length()); 725 if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) { 726 RefPtrWillBeRawPtr<ProcessingInstruction> c = static_pointer_cast<ProcessingInstruction>(container->cloneNode(true)); 727 c->setData(c->data().substring(startOffset, endOffset - startOffset)); 728 if (fragment) { 729 result = fragment; 730 result->appendChild(c.release(), exceptionState); 731 } else 732 result = c.release(); 733 } 734 if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) { 735 ProcessingInstruction* pi = toProcessingInstruction(container); 736 String data(pi->data()); 737 data.remove(startOffset, endOffset - startOffset); 738 pi->setData(data); 739 } 740 break; 741 case Node::ELEMENT_NODE: 742 case Node::ATTRIBUTE_NODE: 743 case Node::DOCUMENT_NODE: 744 case Node::DOCUMENT_TYPE_NODE: 745 case Node::DOCUMENT_FRAGMENT_NODE: 746 // FIXME: Should we assert that some nodes never appear here? 747 if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) { 748 if (fragment) 749 result = fragment; 750 else 751 result = container->cloneNode(false); 752 } 753 754 Node* n = container->firstChild(); 755 WillBeHeapVector<RefPtrWillBeMember<Node> > nodes; 756 for (unsigned i = startOffset; n && i; i--) 757 n = n->nextSibling(); 758 for (unsigned i = startOffset; n && i < endOffset; i++, n = n->nextSibling()) 759 nodes.append(n); 760 761 processNodes(action, nodes, container, result, exceptionState); 762 break; 763 } 764 765 return result.release(); 766 } 767 768 void Range::processNodes(ActionType action, WillBeHeapVector<RefPtrWillBeMember<Node> >& nodes, PassRefPtrWillBeRawPtr<Node> oldContainer, PassRefPtrWillBeRawPtr<Node> newContainer, ExceptionState& exceptionState) 769 { 770 for (unsigned i = 0; i < nodes.size(); i++) { 771 switch (action) { 772 case DELETE_CONTENTS: 773 oldContainer->removeChild(nodes[i].get(), exceptionState); 774 break; 775 case EXTRACT_CONTENTS: 776 newContainer->appendChild(nodes[i].release(), exceptionState); // will remove n from its parent 777 break; 778 case CLONE_CONTENTS: 779 newContainer->appendChild(nodes[i]->cloneNode(true), exceptionState); 780 break; 781 } 782 } 783 } 784 785 PassRefPtrWillBeRawPtr<Node> Range::processAncestorsAndTheirSiblings(ActionType action, Node* container, ContentsProcessDirection direction, PassRefPtrWillBeRawPtr<Node> passedClonedContainer, Node* commonRoot, ExceptionState& exceptionState) 786 { 787 typedef WillBeHeapVector<RefPtrWillBeMember<Node> > NodeVector; 788 789 RefPtrWillBeRawPtr<Node> clonedContainer = passedClonedContainer; 790 NodeVector ancestors; 791 for (ContainerNode* n = container->parentNode(); n && n != commonRoot; n = n->parentNode()) 792 ancestors.append(n); 793 794 RefPtrWillBeRawPtr<Node> firstChildInAncestorToProcess = direction == ProcessContentsForward ? container->nextSibling() : container->previousSibling(); 795 for (NodeVector::const_iterator it = ancestors.begin(); it != ancestors.end(); ++it) { 796 RefPtrWillBeRawPtr<Node> ancestor = *it; 797 if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) { 798 if (RefPtrWillBeRawPtr<Node> clonedAncestor = ancestor->cloneNode(false)) { // Might have been removed already during mutation event. 799 clonedAncestor->appendChild(clonedContainer, exceptionState); 800 clonedContainer = clonedAncestor; 801 } 802 } 803 804 // Copy siblings of an ancestor of start/end containers 805 // FIXME: This assertion may fail if DOM is modified during mutation event 806 // FIXME: Share code with Range::processNodes 807 ASSERT(!firstChildInAncestorToProcess || firstChildInAncestorToProcess->parentNode() == ancestor); 808 809 NodeVector nodes; 810 for (Node* child = firstChildInAncestorToProcess.get(); child; 811 child = (direction == ProcessContentsForward) ? child->nextSibling() : child->previousSibling()) 812 nodes.append(child); 813 814 for (NodeVector::const_iterator it = nodes.begin(); it != nodes.end(); ++it) { 815 Node* child = it->get(); 816 switch (action) { 817 case DELETE_CONTENTS: 818 // Prior call of ancestor->removeChild() may cause a tree change due to DOMSubtreeModified event. 819 // Therefore, we need to make sure |ancestor| is still |child|'s parent. 820 if (ancestor == child->parentNode()) 821 ancestor->removeChild(child, exceptionState); 822 break; 823 case EXTRACT_CONTENTS: // will remove child from ancestor 824 if (direction == ProcessContentsForward) 825 clonedContainer->appendChild(child, exceptionState); 826 else 827 clonedContainer->insertBefore(child, clonedContainer->firstChild(), exceptionState); 828 break; 829 case CLONE_CONTENTS: 830 if (direction == ProcessContentsForward) 831 clonedContainer->appendChild(child->cloneNode(true), exceptionState); 832 else 833 clonedContainer->insertBefore(child->cloneNode(true), clonedContainer->firstChild(), exceptionState); 834 break; 835 } 836 } 837 firstChildInAncestorToProcess = direction == ProcessContentsForward ? ancestor->nextSibling() : ancestor->previousSibling(); 838 } 839 840 return clonedContainer.release(); 841 } 842 843 PassRefPtrWillBeRawPtr<DocumentFragment> Range::extractContents(ExceptionState& exceptionState) 844 { 845 checkExtractPrecondition(exceptionState); 846 if (exceptionState.hadException()) 847 return nullptr; 848 849 return processContents(EXTRACT_CONTENTS, exceptionState); 850 } 851 852 PassRefPtrWillBeRawPtr<DocumentFragment> Range::cloneContents(ExceptionState& exceptionState) 853 { 854 return processContents(CLONE_CONTENTS, exceptionState); 855 } 856 857 void Range::insertNode(PassRefPtrWillBeRawPtr<Node> prpNewNode, ExceptionState& exceptionState) 858 { 859 RefPtrWillBeRawPtr<Node> newNode = prpNewNode; 860 861 if (!newNode) { 862 exceptionState.throwDOMException(NotFoundError, "The node provided is null."); 863 return; 864 } 865 866 // HierarchyRequestError: Raised if the container of the start of the Range is of a type that 867 // does not allow children of the type of newNode or if newNode is an ancestor of the container. 868 869 // an extra one here - if a text node is going to split, it must have a parent to insert into 870 bool startIsText = m_start.container()->isTextNode(); 871 if (startIsText && !m_start.container()->parentNode()) { 872 exceptionState.throwDOMException(HierarchyRequestError, "This operation would split a text node, but there's no parent into which to insert."); 873 return; 874 } 875 876 // In the case where the container is a text node, we check against the container's parent, because 877 // text nodes get split up upon insertion. 878 Node* checkAgainst; 879 if (startIsText) 880 checkAgainst = m_start.container()->parentNode(); 881 else 882 checkAgainst = m_start.container(); 883 884 Node::NodeType newNodeType = newNode->nodeType(); 885 int numNewChildren; 886 if (newNodeType == Node::DOCUMENT_FRAGMENT_NODE && !newNode->isShadowRoot()) { 887 // check each child node, not the DocumentFragment itself 888 numNewChildren = 0; 889 for (Node* c = toDocumentFragment(newNode)->firstChild(); c; c = c->nextSibling()) { 890 if (!checkAgainst->childTypeAllowed(c->nodeType())) { 891 exceptionState.throwDOMException(HierarchyRequestError, "The node to be inserted contains a '" + c->nodeName() + "' node, which may not be inserted here."); 892 return; 893 } 894 ++numNewChildren; 895 } 896 } else { 897 numNewChildren = 1; 898 if (!checkAgainst->childTypeAllowed(newNodeType)) { 899 exceptionState.throwDOMException(HierarchyRequestError, "The node to be inserted is a '" + newNode->nodeName() + "' node, which may not be inserted here."); 900 return; 901 } 902 } 903 904 for (Node* n = m_start.container(); n; n = n->parentNode()) { 905 if (n == newNode) { 906 exceptionState.throwDOMException(HierarchyRequestError, "The node to be inserted contains the insertion point; it may not be inserted into itself."); 907 return; 908 } 909 } 910 911 // InvalidNodeTypeError: Raised if newNode is an Attr, Entity, Notation, ShadowRoot or Document node. 912 switch (newNodeType) { 913 case Node::ATTRIBUTE_NODE: 914 case Node::DOCUMENT_NODE: 915 exceptionState.throwDOMException(InvalidNodeTypeError, "The node to be inserted is a '" + newNode->nodeName() + "' node, which may not be inserted here."); 916 return; 917 default: 918 if (newNode->isShadowRoot()) { 919 exceptionState.throwDOMException(InvalidNodeTypeError, "The node to be inserted is a shadow root, which may not be inserted here."); 920 return; 921 } 922 break; 923 } 924 925 EventQueueScope scope; 926 bool collapsed = m_start == m_end; 927 RefPtrWillBeRawPtr<Node> container = nullptr; 928 if (startIsText) { 929 container = m_start.container(); 930 RefPtrWillBeRawPtr<Text> newText = toText(container)->splitText(m_start.offset(), exceptionState); 931 if (exceptionState.hadException()) 932 return; 933 934 container = m_start.container(); 935 container->parentNode()->insertBefore(newNode.release(), newText.get(), exceptionState); 936 if (exceptionState.hadException()) 937 return; 938 939 if (collapsed) { 940 // The load event would be fired regardless of EventQueueScope; 941 // e.g. by ContainerNode::updateTreeAfterInsertion 942 // Given circumstance may mutate the tree so newText->parentNode() may become null 943 if (!newText->parentNode()) { 944 exceptionState.throwDOMException(HierarchyRequestError, "This operation would set range's end to parent with new offset, but there's no parent into which to continue."); 945 return; 946 } 947 m_end.setToBeforeChild(*newText); 948 } 949 } else { 950 RefPtrWillBeRawPtr<Node> lastChild = (newNodeType == Node::DOCUMENT_FRAGMENT_NODE) ? toDocumentFragment(newNode)->lastChild() : newNode.get(); 951 if (lastChild && lastChild == m_start.childBefore()) { 952 // The insertion will do nothing, but we need to extend the range to include 953 // the inserted nodes. 954 Node* firstChild = (newNodeType == Node::DOCUMENT_FRAGMENT_NODE) ? toDocumentFragment(newNode)->firstChild() : newNode.get(); 955 ASSERT(firstChild); 956 m_start.setToBeforeChild(*firstChild); 957 return; 958 } 959 960 container = m_start.container(); 961 container->insertBefore(newNode.release(), NodeTraversal::childAt(*container, m_start.offset()), exceptionState); 962 if (exceptionState.hadException()) 963 return; 964 965 // Note that m_start.offset() may have changed as a result of container->insertBefore, 966 // when the node we are inserting comes before the range in the same container. 967 if (collapsed && numNewChildren) 968 m_end.set(m_start.container(), m_start.offset() + numNewChildren, lastChild.get()); 969 } 970 } 971 972 String Range::toString() const 973 { 974 StringBuilder builder; 975 976 Node* pastLast = pastLastNode(); 977 for (Node* n = firstNode(); n != pastLast; n = NodeTraversal::next(*n)) { 978 Node::NodeType type = n->nodeType(); 979 if (type == Node::TEXT_NODE || type == Node::CDATA_SECTION_NODE) { 980 String data = toCharacterData(n)->data(); 981 int length = data.length(); 982 int start = (n == m_start.container()) ? std::min(std::max(0, m_start.offset()), length) : 0; 983 int end = (n == m_end.container()) ? std::min(std::max(start, m_end.offset()), length) : length; 984 builder.append(data, start, end - start); 985 } 986 } 987 988 return builder.toString(); 989 } 990 991 String Range::toHTML() const 992 { 993 return createMarkup(this); 994 } 995 996 String Range::text() const 997 { 998 return plainText(this, TextIteratorEmitsObjectReplacementCharacter); 999 } 1000 1001 PassRefPtrWillBeRawPtr<DocumentFragment> Range::createContextualFragment(const String& markup, ExceptionState& exceptionState) 1002 { 1003 // Algorithm: http://domparsing.spec.whatwg.org/#extensions-to-the-range-interface 1004 1005 Node* node = m_start.container(); 1006 1007 // Step 1. 1008 RefPtrWillBeRawPtr<Element> element; 1009 if (!m_start.offset() && (node->isDocumentNode() || node->isDocumentFragment())) 1010 element = nullptr; 1011 else if (node->isElementNode()) 1012 element = toElement(node); 1013 else 1014 element = node->parentElement(); 1015 1016 // Step 2. 1017 if (!element || isHTMLHtmlElement(element)) { 1018 Document& document = node->document(); 1019 1020 if (document.isHTMLDocument() || document.isXHTMLDocument()) { 1021 // Optimization over spec: try to reuse the existing <body> element, if it is available. 1022 element = document.body(); 1023 if (!element) 1024 element = HTMLBodyElement::create(document); 1025 } else if (document.isSVGDocument()) { 1026 element = document.documentElement(); 1027 if (!element) 1028 element = SVGSVGElement::create(document); 1029 } 1030 } 1031 1032 if (!element || (!element->isHTMLElement() && !element->isSVGElement())) { 1033 exceptionState.throwDOMException(NotSupportedError, "The range's container must be an HTML or SVG Element, Document, or DocumentFragment."); 1034 return nullptr; 1035 } 1036 1037 // Steps 3, 4, 5. 1038 RefPtrWillBeRawPtr<DocumentFragment> fragment = blink::createContextualFragment(markup, element.get(), AllowScriptingContentAndDoNotMarkAlreadyStarted, exceptionState); 1039 if (!fragment) 1040 return nullptr; 1041 1042 return fragment.release(); 1043 } 1044 1045 1046 void Range::detach() 1047 { 1048 // This is now a no-op as per the DOM specification. 1049 } 1050 1051 Node* Range::checkNodeWOffset(Node* n, int offset, ExceptionState& exceptionState) const 1052 { 1053 switch (n->nodeType()) { 1054 case Node::DOCUMENT_TYPE_NODE: 1055 exceptionState.throwDOMException(InvalidNodeTypeError, "The node provided is of type '" + n->nodeName() + "'."); 1056 return 0; 1057 case Node::CDATA_SECTION_NODE: 1058 case Node::COMMENT_NODE: 1059 case Node::TEXT_NODE: 1060 if (static_cast<unsigned>(offset) > toCharacterData(n)->length()) 1061 exceptionState.throwDOMException(IndexSizeError, "The offset " + String::number(offset) + " is larger than or equal to the node's length (" + String::number(toCharacterData(n)->length()) + ")."); 1062 return 0; 1063 case Node::PROCESSING_INSTRUCTION_NODE: 1064 if (static_cast<unsigned>(offset) > toProcessingInstruction(n)->data().length()) 1065 exceptionState.throwDOMException(IndexSizeError, "The offset " + String::number(offset) + " is larger than or equal to than the node's length (" + String::number(toProcessingInstruction(n)->data().length()) + ")."); 1066 return 0; 1067 case Node::ATTRIBUTE_NODE: 1068 case Node::DOCUMENT_FRAGMENT_NODE: 1069 case Node::DOCUMENT_NODE: 1070 case Node::ELEMENT_NODE: { 1071 if (!offset) 1072 return 0; 1073 Node* childBefore = NodeTraversal::childAt(*n, offset - 1); 1074 if (!childBefore) 1075 exceptionState.throwDOMException(IndexSizeError, "There is no child at offset " + String::number(offset) + "."); 1076 return childBefore; 1077 } 1078 } 1079 ASSERT_NOT_REACHED(); 1080 return 0; 1081 } 1082 1083 void Range::checkNodeBA(Node* n, ExceptionState& exceptionState) const 1084 { 1085 if (!n) { 1086 exceptionState.throwDOMException(NotFoundError, "The node provided is null."); 1087 return; 1088 } 1089 1090 // InvalidNodeTypeError: Raised if the root container of refNode is not an 1091 // Attr, Document, DocumentFragment or ShadowRoot node, or part of a SVG shadow DOM tree, 1092 // or if refNode is a Document, DocumentFragment, ShadowRoot, Attr, Entity, or Notation node. 1093 1094 if (!n->parentNode()) { 1095 exceptionState.throwDOMException(InvalidNodeTypeError, "the given Node has no parent."); 1096 return; 1097 } 1098 1099 switch (n->nodeType()) { 1100 case Node::ATTRIBUTE_NODE: 1101 case Node::DOCUMENT_FRAGMENT_NODE: 1102 case Node::DOCUMENT_NODE: 1103 exceptionState.throwDOMException(InvalidNodeTypeError, "The node provided is of type '" + n->nodeName() + "'."); 1104 return; 1105 case Node::CDATA_SECTION_NODE: 1106 case Node::COMMENT_NODE: 1107 case Node::DOCUMENT_TYPE_NODE: 1108 case Node::ELEMENT_NODE: 1109 case Node::PROCESSING_INSTRUCTION_NODE: 1110 case Node::TEXT_NODE: 1111 break; 1112 } 1113 1114 Node* root = n; 1115 while (ContainerNode* parent = root->parentNode()) 1116 root = parent; 1117 1118 switch (root->nodeType()) { 1119 case Node::ATTRIBUTE_NODE: 1120 case Node::DOCUMENT_NODE: 1121 case Node::DOCUMENT_FRAGMENT_NODE: 1122 case Node::ELEMENT_NODE: 1123 break; 1124 case Node::CDATA_SECTION_NODE: 1125 case Node::COMMENT_NODE: 1126 case Node::DOCUMENT_TYPE_NODE: 1127 case Node::PROCESSING_INSTRUCTION_NODE: 1128 case Node::TEXT_NODE: 1129 exceptionState.throwDOMException(InvalidNodeTypeError, "The node provided is of type '" + n->nodeName() + "'."); 1130 return; 1131 } 1132 } 1133 1134 PassRefPtrWillBeRawPtr<Range> Range::cloneRange() const 1135 { 1136 return Range::create(*m_ownerDocument.get(), m_start.container(), m_start.offset(), m_end.container(), m_end.offset()); 1137 } 1138 1139 void Range::setStartAfter(Node* refNode, ExceptionState& exceptionState) 1140 { 1141 checkNodeBA(refNode, exceptionState); 1142 if (exceptionState.hadException()) 1143 return; 1144 1145 setStart(refNode->parentNode(), refNode->nodeIndex() + 1, exceptionState); 1146 } 1147 1148 void Range::setEndBefore(Node* refNode, ExceptionState& exceptionState) 1149 { 1150 checkNodeBA(refNode, exceptionState); 1151 if (exceptionState.hadException()) 1152 return; 1153 1154 setEnd(refNode->parentNode(), refNode->nodeIndex(), exceptionState); 1155 } 1156 1157 void Range::setEndAfter(Node* refNode, ExceptionState& exceptionState) 1158 { 1159 checkNodeBA(refNode, exceptionState); 1160 if (exceptionState.hadException()) 1161 return; 1162 1163 setEnd(refNode->parentNode(), refNode->nodeIndex() + 1, exceptionState); 1164 } 1165 1166 void Range::selectNode(Node* refNode, ExceptionState& exceptionState) 1167 { 1168 if (!refNode) { 1169 exceptionState.throwDOMException(NotFoundError, "The node provided is null."); 1170 return; 1171 } 1172 1173 if (!refNode->parentNode()) { 1174 exceptionState.throwDOMException(InvalidNodeTypeError, "the given Node has no parent."); 1175 return; 1176 } 1177 1178 // InvalidNodeTypeError: Raised if an ancestor of refNode is an Entity, Notation or 1179 // DocumentType node or if refNode is a Document, DocumentFragment, ShadowRoot, Attr, Entity, or Notation 1180 // node. 1181 for (ContainerNode* anc = refNode->parentNode(); anc; anc = anc->parentNode()) { 1182 switch (anc->nodeType()) { 1183 case Node::ATTRIBUTE_NODE: 1184 case Node::CDATA_SECTION_NODE: 1185 case Node::COMMENT_NODE: 1186 case Node::DOCUMENT_FRAGMENT_NODE: 1187 case Node::DOCUMENT_NODE: 1188 case Node::ELEMENT_NODE: 1189 case Node::PROCESSING_INSTRUCTION_NODE: 1190 case Node::TEXT_NODE: 1191 break; 1192 case Node::DOCUMENT_TYPE_NODE: 1193 exceptionState.throwDOMException(InvalidNodeTypeError, "The node provided has an ancestor of type '" + anc->nodeName() + "'."); 1194 return; 1195 } 1196 } 1197 1198 switch (refNode->nodeType()) { 1199 case Node::CDATA_SECTION_NODE: 1200 case Node::COMMENT_NODE: 1201 case Node::DOCUMENT_TYPE_NODE: 1202 case Node::ELEMENT_NODE: 1203 case Node::PROCESSING_INSTRUCTION_NODE: 1204 case Node::TEXT_NODE: 1205 break; 1206 case Node::ATTRIBUTE_NODE: 1207 case Node::DOCUMENT_FRAGMENT_NODE: 1208 case Node::DOCUMENT_NODE: 1209 exceptionState.throwDOMException(InvalidNodeTypeError, "The node provided is of type '" + refNode->nodeName() + "'."); 1210 return; 1211 } 1212 1213 if (m_ownerDocument != refNode->document()) 1214 setDocument(refNode->document()); 1215 1216 setStartBefore(refNode); 1217 setEndAfter(refNode); 1218 } 1219 1220 void Range::selectNodeContents(Node* refNode, ExceptionState& exceptionState) 1221 { 1222 if (!refNode) { 1223 exceptionState.throwDOMException(NotFoundError, "The node provided is null."); 1224 return; 1225 } 1226 1227 // InvalidNodeTypeError: Raised if refNode or an ancestor of refNode is an Entity, Notation 1228 // or DocumentType node. 1229 for (Node* n = refNode; n; n = n->parentNode()) { 1230 switch (n->nodeType()) { 1231 case Node::ATTRIBUTE_NODE: 1232 case Node::CDATA_SECTION_NODE: 1233 case Node::COMMENT_NODE: 1234 case Node::DOCUMENT_FRAGMENT_NODE: 1235 case Node::DOCUMENT_NODE: 1236 case Node::ELEMENT_NODE: 1237 case Node::PROCESSING_INSTRUCTION_NODE: 1238 case Node::TEXT_NODE: 1239 break; 1240 case Node::DOCUMENT_TYPE_NODE: 1241 exceptionState.throwDOMException(InvalidNodeTypeError, "The node provided is of type '" + refNode->nodeName() + "'."); 1242 return; 1243 } 1244 } 1245 1246 if (m_ownerDocument != refNode->document()) 1247 setDocument(refNode->document()); 1248 1249 m_start.setToStartOfNode(*refNode); 1250 m_end.setToEndOfNode(*refNode); 1251 } 1252 1253 bool Range::selectNodeContents(Node* refNode, Position& start, Position& end) 1254 { 1255 if (!refNode) { 1256 return false; 1257 } 1258 1259 for (Node* n = refNode; n; n = n->parentNode()) { 1260 switch (n->nodeType()) { 1261 case Node::ATTRIBUTE_NODE: 1262 case Node::CDATA_SECTION_NODE: 1263 case Node::COMMENT_NODE: 1264 case Node::DOCUMENT_FRAGMENT_NODE: 1265 case Node::DOCUMENT_NODE: 1266 case Node::ELEMENT_NODE: 1267 case Node::PROCESSING_INSTRUCTION_NODE: 1268 case Node::TEXT_NODE: 1269 break; 1270 case Node::DOCUMENT_TYPE_NODE: 1271 return false; 1272 } 1273 } 1274 1275 RangeBoundaryPoint startBoundaryPoint(refNode); 1276 startBoundaryPoint.setToStartOfNode(*refNode); 1277 start = startBoundaryPoint.toPosition(); 1278 RangeBoundaryPoint endBoundaryPoint(refNode); 1279 endBoundaryPoint.setToEndOfNode(*refNode); 1280 end = endBoundaryPoint.toPosition(); 1281 return true; 1282 } 1283 1284 void Range::surroundContents(PassRefPtrWillBeRawPtr<Node> passNewParent, ExceptionState& exceptionState) 1285 { 1286 RefPtrWillBeRawPtr<Node> newParent = passNewParent; 1287 if (!newParent) { 1288 exceptionState.throwDOMException(NotFoundError, "The node provided is null."); 1289 return; 1290 } 1291 1292 // InvalidStateError: Raised if the Range partially selects a non-Text node. 1293 Node* startNonTextContainer = m_start.container(); 1294 if (startNonTextContainer->nodeType() == Node::TEXT_NODE) 1295 startNonTextContainer = startNonTextContainer->parentNode(); 1296 Node* endNonTextContainer = m_end.container(); 1297 if (endNonTextContainer->nodeType() == Node::TEXT_NODE) 1298 endNonTextContainer = endNonTextContainer->parentNode(); 1299 if (startNonTextContainer != endNonTextContainer) { 1300 exceptionState.throwDOMException(InvalidStateError, "The Range has partially selected a non-Text node."); 1301 return; 1302 } 1303 1304 // InvalidNodeTypeError: Raised if node is an Attr, Entity, DocumentType, Notation, 1305 // Document, or DocumentFragment node. 1306 switch (newParent->nodeType()) { 1307 case Node::ATTRIBUTE_NODE: 1308 case Node::DOCUMENT_FRAGMENT_NODE: 1309 case Node::DOCUMENT_NODE: 1310 case Node::DOCUMENT_TYPE_NODE: 1311 exceptionState.throwDOMException(InvalidNodeTypeError, "The node provided is of type '" + newParent->nodeName() + "'."); 1312 return; 1313 case Node::CDATA_SECTION_NODE: 1314 case Node::COMMENT_NODE: 1315 case Node::ELEMENT_NODE: 1316 case Node::PROCESSING_INSTRUCTION_NODE: 1317 case Node::TEXT_NODE: 1318 break; 1319 } 1320 1321 // Raise a HierarchyRequestError if m_start.container() doesn't accept children like newParent. 1322 Node* parentOfNewParent = m_start.container(); 1323 1324 // If m_start.container() is a character data node, it will be split and it will be its parent that will 1325 // need to accept newParent (or in the case of a comment, it logically "would" be inserted into the parent, 1326 // although this will fail below for another reason). 1327 if (parentOfNewParent->isCharacterDataNode()) 1328 parentOfNewParent = parentOfNewParent->parentNode(); 1329 1330 if (!parentOfNewParent) { 1331 exceptionState.throwDOMException(HierarchyRequestError, "The container node is a detached character data node; no parent node is available for insertion."); 1332 return; 1333 } 1334 1335 if (!parentOfNewParent->childTypeAllowed(newParent->nodeType())) { 1336 exceptionState.throwDOMException(HierarchyRequestError, "The node provided is of type '" + newParent->nodeName() + "', which may not be inserted here."); 1337 return; 1338 } 1339 1340 if (newParent->contains(m_start.container())) { 1341 exceptionState.throwDOMException(HierarchyRequestError, "The node provided contains the insertion point; it may not be inserted into itself."); 1342 return; 1343 } 1344 1345 // FIXME: Do we need a check if the node would end up with a child node of a type not 1346 // allowed by the type of node? 1347 1348 while (Node* n = newParent->firstChild()) { 1349 toContainerNode(newParent)->removeChild(n, exceptionState); 1350 if (exceptionState.hadException()) 1351 return; 1352 } 1353 RefPtrWillBeRawPtr<DocumentFragment> fragment = extractContents(exceptionState); 1354 if (exceptionState.hadException()) 1355 return; 1356 insertNode(newParent, exceptionState); 1357 if (exceptionState.hadException()) 1358 return; 1359 newParent->appendChild(fragment.release(), exceptionState); 1360 if (exceptionState.hadException()) 1361 return; 1362 selectNode(newParent.get(), exceptionState); 1363 } 1364 1365 void Range::setStartBefore(Node* refNode, ExceptionState& exceptionState) 1366 { 1367 checkNodeBA(refNode, exceptionState); 1368 if (exceptionState.hadException()) 1369 return; 1370 1371 setStart(refNode->parentNode(), refNode->nodeIndex(), exceptionState); 1372 } 1373 1374 void Range::checkExtractPrecondition(ExceptionState& exceptionState) 1375 { 1376 ASSERT(boundaryPointsValid()); 1377 1378 if (!commonAncestorContainer()) 1379 return; 1380 1381 Node* pastLast = pastLastNode(); 1382 for (Node* n = firstNode(); n != pastLast; n = NodeTraversal::next(*n)) { 1383 if (n->isDocumentTypeNode()) { 1384 exceptionState.throwDOMException(HierarchyRequestError, "The Range contains a doctype node."); 1385 return; 1386 } 1387 } 1388 } 1389 1390 Node* Range::firstNode() const 1391 { 1392 if (m_start.container()->offsetInCharacters()) 1393 return m_start.container(); 1394 if (Node* child = NodeTraversal::childAt(*m_start.container(), m_start.offset())) 1395 return child; 1396 if (!m_start.offset()) 1397 return m_start.container(); 1398 return NodeTraversal::nextSkippingChildren(*m_start.container()); 1399 } 1400 1401 ShadowRoot* Range::shadowRoot() const 1402 { 1403 return startContainer() ? startContainer()->containingShadowRoot() : 0; 1404 } 1405 1406 Node* Range::pastLastNode() const 1407 { 1408 if (m_end.container()->offsetInCharacters()) 1409 return NodeTraversal::nextSkippingChildren(*m_end.container()); 1410 if (Node* child = NodeTraversal::childAt(*m_end.container(), m_end.offset())) 1411 return child; 1412 return NodeTraversal::nextSkippingChildren(*m_end.container()); 1413 } 1414 1415 IntRect Range::boundingBox() const 1416 { 1417 IntRect result; 1418 Vector<IntRect> rects; 1419 textRects(rects); 1420 const size_t n = rects.size(); 1421 for (size_t i = 0; i < n; ++i) 1422 result.unite(rects[i]); 1423 return result; 1424 } 1425 1426 void Range::textRects(Vector<IntRect>& rects, bool useSelectionHeight, RangeInFixedPosition* inFixed) const 1427 { 1428 Node* startContainer = m_start.container(); 1429 ASSERT(startContainer); 1430 Node* endContainer = m_end.container(); 1431 ASSERT(endContainer); 1432 1433 bool allFixed = true; 1434 bool someFixed = false; 1435 1436 Node* stopNode = pastLastNode(); 1437 for (Node* node = firstNode(); node != stopNode; node = NodeTraversal::next(*node)) { 1438 RenderObject* r = node->renderer(); 1439 if (!r || !r->isText()) 1440 continue; 1441 RenderText* renderText = toRenderText(r); 1442 int startOffset = node == startContainer ? m_start.offset() : 0; 1443 int endOffset = node == endContainer ? m_end.offset() : std::numeric_limits<int>::max(); 1444 bool isFixed = false; 1445 renderText->absoluteRectsForRange(rects, startOffset, endOffset, useSelectionHeight, &isFixed); 1446 allFixed &= isFixed; 1447 someFixed |= isFixed; 1448 } 1449 1450 if (inFixed) 1451 *inFixed = allFixed ? EntirelyFixedPosition : (someFixed ? PartiallyFixedPosition : NotFixedPosition); 1452 } 1453 1454 void Range::textQuads(Vector<FloatQuad>& quads, bool useSelectionHeight, RangeInFixedPosition* inFixed) const 1455 { 1456 Node* startContainer = m_start.container(); 1457 ASSERT(startContainer); 1458 Node* endContainer = m_end.container(); 1459 ASSERT(endContainer); 1460 1461 bool allFixed = true; 1462 bool someFixed = false; 1463 1464 Node* stopNode = pastLastNode(); 1465 for (Node* node = firstNode(); node != stopNode; node = NodeTraversal::next(*node)) { 1466 RenderObject* r = node->renderer(); 1467 if (!r || !r->isText()) 1468 continue; 1469 RenderText* renderText = toRenderText(r); 1470 int startOffset = node == startContainer ? m_start.offset() : 0; 1471 int endOffset = node == endContainer ? m_end.offset() : std::numeric_limits<int>::max(); 1472 bool isFixed = false; 1473 renderText->absoluteQuadsForRange(quads, startOffset, endOffset, useSelectionHeight, &isFixed); 1474 allFixed &= isFixed; 1475 someFixed |= isFixed; 1476 } 1477 1478 if (inFixed) 1479 *inFixed = allFixed ? EntirelyFixedPosition : (someFixed ? PartiallyFixedPosition : NotFixedPosition); 1480 } 1481 1482 #ifndef NDEBUG 1483 void Range::formatForDebugger(char* buffer, unsigned length) const 1484 { 1485 StringBuilder result; 1486 1487 const int FormatBufferSize = 1024; 1488 char s[FormatBufferSize]; 1489 result.appendLiteral("from offset "); 1490 result.appendNumber(m_start.offset()); 1491 result.appendLiteral(" of "); 1492 m_start.container()->formatForDebugger(s, FormatBufferSize); 1493 result.append(s); 1494 result.appendLiteral(" to offset "); 1495 result.appendNumber(m_end.offset()); 1496 result.appendLiteral(" of "); 1497 m_end.container()->formatForDebugger(s, FormatBufferSize); 1498 result.append(s); 1499 1500 strncpy(buffer, result.toString().utf8().data(), length - 1); 1501 } 1502 #endif 1503 1504 bool areRangesEqual(const Range* a, const Range* b) 1505 { 1506 if (a == b) 1507 return true; 1508 if (!a || !b) 1509 return false; 1510 return a->startPosition() == b->startPosition() && a->endPosition() == b->endPosition(); 1511 } 1512 1513 PassRefPtrWillBeRawPtr<Range> rangeOfContents(Node* node) 1514 { 1515 ASSERT(node); 1516 RefPtrWillBeRawPtr<Range> range = Range::create(node->document()); 1517 range->selectNodeContents(node, IGNORE_EXCEPTION); 1518 return range.release(); 1519 } 1520 1521 static inline void boundaryNodeChildrenChanged(RangeBoundaryPoint& boundary, ContainerNode* container) 1522 { 1523 if (!boundary.childBefore()) 1524 return; 1525 if (boundary.container() != container) 1526 return; 1527 boundary.invalidateOffset(); 1528 } 1529 1530 void Range::nodeChildrenChanged(ContainerNode* container) 1531 { 1532 ASSERT(container); 1533 ASSERT(container->document() == m_ownerDocument); 1534 boundaryNodeChildrenChanged(m_start, container); 1535 boundaryNodeChildrenChanged(m_end, container); 1536 } 1537 1538 static inline void boundaryNodeChildrenWillBeRemoved(RangeBoundaryPoint& boundary, ContainerNode& container) 1539 { 1540 for (Node* nodeToBeRemoved = container.firstChild(); nodeToBeRemoved; nodeToBeRemoved = nodeToBeRemoved->nextSibling()) { 1541 if (boundary.childBefore() == nodeToBeRemoved) { 1542 boundary.setToStartOfNode(container); 1543 return; 1544 } 1545 1546 for (Node* n = boundary.container(); n; n = n->parentNode()) { 1547 if (n == nodeToBeRemoved) { 1548 boundary.setToStartOfNode(container); 1549 return; 1550 } 1551 } 1552 } 1553 } 1554 1555 void Range::nodeChildrenWillBeRemoved(ContainerNode& container) 1556 { 1557 ASSERT(container.document() == m_ownerDocument); 1558 boundaryNodeChildrenWillBeRemoved(m_start, container); 1559 boundaryNodeChildrenWillBeRemoved(m_end, container); 1560 } 1561 1562 static inline void boundaryNodeWillBeRemoved(RangeBoundaryPoint& boundary, Node& nodeToBeRemoved) 1563 { 1564 if (boundary.childBefore() == nodeToBeRemoved) { 1565 boundary.childBeforeWillBeRemoved(); 1566 return; 1567 } 1568 1569 for (Node* n = boundary.container(); n; n = n->parentNode()) { 1570 if (n == nodeToBeRemoved) { 1571 boundary.setToBeforeChild(nodeToBeRemoved); 1572 return; 1573 } 1574 } 1575 } 1576 1577 void Range::nodeWillBeRemoved(Node& node) 1578 { 1579 ASSERT(node.document() == m_ownerDocument); 1580 ASSERT(node != m_ownerDocument.get()); 1581 1582 // FIXME: Once DOMNodeRemovedFromDocument mutation event removed, we 1583 // should change following if-statement to ASSERT(!node->parentNode). 1584 if (!node.parentNode()) 1585 return; 1586 boundaryNodeWillBeRemoved(m_start, node); 1587 boundaryNodeWillBeRemoved(m_end, node); 1588 } 1589 1590 static inline void boundaryTextInserted(RangeBoundaryPoint& boundary, Node* text, unsigned offset, unsigned length) 1591 { 1592 if (boundary.container() != text) 1593 return; 1594 unsigned boundaryOffset = boundary.offset(); 1595 if (offset >= boundaryOffset) 1596 return; 1597 boundary.setOffset(boundaryOffset + length); 1598 } 1599 1600 void Range::didInsertText(Node* text, unsigned offset, unsigned length) 1601 { 1602 ASSERT(text); 1603 ASSERT(text->document() == m_ownerDocument); 1604 boundaryTextInserted(m_start, text, offset, length); 1605 boundaryTextInserted(m_end, text, offset, length); 1606 } 1607 1608 static inline void boundaryTextRemoved(RangeBoundaryPoint& boundary, Node* text, unsigned offset, unsigned length) 1609 { 1610 if (boundary.container() != text) 1611 return; 1612 unsigned boundaryOffset = boundary.offset(); 1613 if (offset >= boundaryOffset) 1614 return; 1615 if (offset + length >= boundaryOffset) 1616 boundary.setOffset(offset); 1617 else 1618 boundary.setOffset(boundaryOffset - length); 1619 } 1620 1621 void Range::didRemoveText(Node* text, unsigned offset, unsigned length) 1622 { 1623 ASSERT(text); 1624 ASSERT(text->document() == m_ownerDocument); 1625 boundaryTextRemoved(m_start, text, offset, length); 1626 boundaryTextRemoved(m_end, text, offset, length); 1627 } 1628 1629 static inline void boundaryTextNodesMerged(RangeBoundaryPoint& boundary, const NodeWithIndex& oldNode, unsigned offset) 1630 { 1631 if (boundary.container() == oldNode.node()) 1632 boundary.set(oldNode.node().previousSibling(), boundary.offset() + offset, 0); 1633 else if (boundary.container() == oldNode.node().parentNode() && boundary.offset() == oldNode.index()) 1634 boundary.set(oldNode.node().previousSibling(), offset, 0); 1635 } 1636 1637 void Range::didMergeTextNodes(const NodeWithIndex& oldNode, unsigned offset) 1638 { 1639 ASSERT(oldNode.node().document() == m_ownerDocument); 1640 ASSERT(oldNode.node().parentNode()); 1641 ASSERT(oldNode.node().isTextNode()); 1642 ASSERT(oldNode.node().previousSibling()); 1643 ASSERT(oldNode.node().previousSibling()->isTextNode()); 1644 boundaryTextNodesMerged(m_start, oldNode, offset); 1645 boundaryTextNodesMerged(m_end, oldNode, offset); 1646 } 1647 1648 void Range::updateOwnerDocumentIfNeeded() 1649 { 1650 ASSERT(m_start.container()); 1651 ASSERT(m_end.container()); 1652 Document& newDocument = m_start.container()->document(); 1653 ASSERT(newDocument == m_end.container()->document()); 1654 if (newDocument == m_ownerDocument) 1655 return; 1656 m_ownerDocument->detachRange(this); 1657 m_ownerDocument = &newDocument; 1658 m_ownerDocument->attachRange(this); 1659 } 1660 1661 static inline void boundaryTextNodeSplit(RangeBoundaryPoint& boundary, Text& oldNode) 1662 { 1663 Node* boundaryContainer = boundary.container(); 1664 unsigned boundaryOffset = boundary.offset(); 1665 if (boundary.childBefore() == &oldNode) 1666 boundary.set(boundaryContainer, boundaryOffset + 1, oldNode.nextSibling()); 1667 else if (boundary.container() == &oldNode && boundaryOffset > oldNode.length()) 1668 boundary.set(oldNode.nextSibling(), boundaryOffset - oldNode.length(), 0); 1669 } 1670 1671 void Range::didSplitTextNode(Text& oldNode) 1672 { 1673 ASSERT(oldNode.document() == m_ownerDocument); 1674 ASSERT(oldNode.parentNode()); 1675 ASSERT(oldNode.nextSibling()); 1676 ASSERT(oldNode.nextSibling()->isTextNode()); 1677 boundaryTextNodeSplit(m_start, oldNode); 1678 boundaryTextNodeSplit(m_end, oldNode); 1679 ASSERT(boundaryPointsValid()); 1680 } 1681 1682 void Range::expand(const String& unit, ExceptionState& exceptionState) 1683 { 1684 VisiblePosition start(startPosition()); 1685 VisiblePosition end(endPosition()); 1686 if (unit == "word") { 1687 start = startOfWord(start); 1688 end = endOfWord(end); 1689 } else if (unit == "sentence") { 1690 start = startOfSentence(start); 1691 end = endOfSentence(end); 1692 } else if (unit == "block") { 1693 start = startOfParagraph(start); 1694 end = endOfParagraph(end); 1695 } else if (unit == "document") { 1696 start = startOfDocument(start); 1697 end = endOfDocument(end); 1698 } else 1699 return; 1700 setStart(start.deepEquivalent().containerNode(), start.deepEquivalent().computeOffsetInContainerNode(), exceptionState); 1701 setEnd(end.deepEquivalent().containerNode(), end.deepEquivalent().computeOffsetInContainerNode(), exceptionState); 1702 } 1703 1704 PassRefPtrWillBeRawPtr<ClientRectList> Range::getClientRects() const 1705 { 1706 m_ownerDocument->updateLayoutIgnorePendingStylesheets(); 1707 1708 Vector<FloatQuad> quads; 1709 getBorderAndTextQuads(quads); 1710 1711 return ClientRectList::create(quads); 1712 } 1713 1714 PassRefPtrWillBeRawPtr<ClientRect> Range::getBoundingClientRect() const 1715 { 1716 return ClientRect::create(boundingRect()); 1717 } 1718 1719 void Range::getBorderAndTextQuads(Vector<FloatQuad>& quads) const 1720 { 1721 Node* startContainer = m_start.container(); 1722 Node* endContainer = m_end.container(); 1723 Node* stopNode = pastLastNode(); 1724 1725 WillBeHeapHashSet<RawPtrWillBeMember<Node> > nodeSet; 1726 for (Node* node = firstNode(); node != stopNode; node = NodeTraversal::next(*node)) { 1727 if (node->isElementNode()) 1728 nodeSet.add(node); 1729 } 1730 1731 for (Node* node = firstNode(); node != stopNode; node = NodeTraversal::next(*node)) { 1732 if (node->isElementNode()) { 1733 if (!nodeSet.contains(node->parentNode())) { 1734 if (RenderBoxModelObject* renderBoxModelObject = toElement(node)->renderBoxModelObject()) { 1735 Vector<FloatQuad> elementQuads; 1736 renderBoxModelObject->absoluteQuads(elementQuads); 1737 m_ownerDocument->adjustFloatQuadsForScrollAndAbsoluteZoom(elementQuads, *renderBoxModelObject); 1738 1739 quads.appendVector(elementQuads); 1740 } 1741 } 1742 } else if (node->isTextNode()) { 1743 if (RenderText* renderText = toText(node)->renderer()) { 1744 int startOffset = (node == startContainer) ? m_start.offset() : 0; 1745 int endOffset = (node == endContainer) ? m_end.offset() : INT_MAX; 1746 1747 Vector<FloatQuad> textQuads; 1748 renderText->absoluteQuadsForRange(textQuads, startOffset, endOffset); 1749 m_ownerDocument->adjustFloatQuadsForScrollAndAbsoluteZoom(textQuads, *renderText); 1750 1751 quads.appendVector(textQuads); 1752 } 1753 } 1754 } 1755 } 1756 1757 FloatRect Range::boundingRect() const 1758 { 1759 m_ownerDocument->updateLayoutIgnorePendingStylesheets(); 1760 1761 Vector<FloatQuad> quads; 1762 getBorderAndTextQuads(quads); 1763 if (quads.isEmpty()) 1764 return FloatRect(); 1765 1766 FloatRect result; 1767 for (size_t i = 0; i < quads.size(); ++i) 1768 result.unite(quads[i].boundingBox()); 1769 1770 return result; 1771 } 1772 1773 void Range::trace(Visitor* visitor) 1774 { 1775 visitor->trace(m_ownerDocument); 1776 visitor->trace(m_start); 1777 visitor->trace(m_end); 1778 } 1779 1780 } // namespace blink 1781 1782 #ifndef NDEBUG 1783 1784 void showTree(const blink::Range* range) 1785 { 1786 if (range && range->boundaryPointsValid()) { 1787 range->startContainer()->showTreeAndMark(range->startContainer(), "S", range->endContainer(), "E"); 1788 fprintf(stderr, "start offset: %d, end offset: %d\n", range->startOffset(), range->endOffset()); 1789 } 1790 } 1791 1792 #endif 1793