1 /* 2 * Copyright (C) 2007, 2009 Apple Inc. All rights reserved. 3 * Copyright (C) 2012 Google Inc. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 15 * its contributors may be used to endorse or promote products derived 16 * from this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 31 #include "config.h" 32 #include "core/editing/DOMSelection.h" 33 34 #include "bindings/core/v8/ExceptionMessages.h" 35 #include "bindings/core/v8/ExceptionState.h" 36 #include "bindings/core/v8/ExceptionStatePlaceholder.h" 37 #include "core/dom/Document.h" 38 #include "core/dom/ExceptionCode.h" 39 #include "core/dom/Node.h" 40 #include "core/dom/Range.h" 41 #include "core/dom/TreeScope.h" 42 #include "core/editing/FrameSelection.h" 43 #include "core/editing/TextIterator.h" 44 #include "core/editing/htmlediting.h" 45 #include "core/frame/LocalFrame.h" 46 #include "core/inspector/ConsoleMessage.h" 47 #include "wtf/text/WTFString.h" 48 49 namespace blink { 50 51 static Node* selectionShadowAncestor(LocalFrame* frame) 52 { 53 Node* node = frame->selection().selection().base().anchorNode(); 54 if (!node) 55 return 0; 56 57 if (!node->isInShadowTree()) 58 return 0; 59 60 return frame->document()->ancestorInThisScope(node); 61 } 62 63 DOMSelection::DOMSelection(const TreeScope* treeScope) 64 : DOMWindowProperty(treeScope->rootNode().document().frame()) 65 , m_treeScope(treeScope) 66 { 67 } 68 69 void DOMSelection::clearTreeScope() 70 { 71 m_treeScope = nullptr; 72 } 73 74 const VisibleSelection& DOMSelection::visibleSelection() const 75 { 76 ASSERT(m_frame); 77 return m_frame->selection().selection(); 78 } 79 80 static Position anchorPosition(const VisibleSelection& selection) 81 { 82 Position anchor = selection.isBaseFirst() ? selection.start() : selection.end(); 83 return anchor.parentAnchoredEquivalent(); 84 } 85 86 static Position focusPosition(const VisibleSelection& selection) 87 { 88 Position focus = selection.isBaseFirst() ? selection.end() : selection.start(); 89 return focus.parentAnchoredEquivalent(); 90 } 91 92 static Position basePosition(const VisibleSelection& selection) 93 { 94 return selection.base().parentAnchoredEquivalent(); 95 } 96 97 static Position extentPosition(const VisibleSelection& selection) 98 { 99 return selection.extent().parentAnchoredEquivalent(); 100 } 101 102 Node* DOMSelection::anchorNode() const 103 { 104 if (!m_frame) 105 return 0; 106 107 return shadowAdjustedNode(anchorPosition(visibleSelection())); 108 } 109 110 int DOMSelection::anchorOffset() const 111 { 112 if (!m_frame) 113 return 0; 114 115 return shadowAdjustedOffset(anchorPosition(visibleSelection())); 116 } 117 118 Node* DOMSelection::focusNode() const 119 { 120 if (!m_frame) 121 return 0; 122 123 return shadowAdjustedNode(focusPosition(visibleSelection())); 124 } 125 126 int DOMSelection::focusOffset() const 127 { 128 if (!m_frame) 129 return 0; 130 131 return shadowAdjustedOffset(focusPosition(visibleSelection())); 132 } 133 134 Node* DOMSelection::baseNode() const 135 { 136 if (!m_frame) 137 return 0; 138 139 return shadowAdjustedNode(basePosition(visibleSelection())); 140 } 141 142 int DOMSelection::baseOffset() const 143 { 144 if (!m_frame) 145 return 0; 146 147 return shadowAdjustedOffset(basePosition(visibleSelection())); 148 } 149 150 Node* DOMSelection::extentNode() const 151 { 152 if (!m_frame) 153 return 0; 154 155 return shadowAdjustedNode(extentPosition(visibleSelection())); 156 } 157 158 int DOMSelection::extentOffset() const 159 { 160 if (!m_frame) 161 return 0; 162 163 return shadowAdjustedOffset(extentPosition(visibleSelection())); 164 } 165 166 bool DOMSelection::isCollapsed() const 167 { 168 if (!m_frame || selectionShadowAncestor(m_frame)) 169 return true; 170 return !m_frame->selection().isRange(); 171 } 172 173 String DOMSelection::type() const 174 { 175 if (!m_frame) 176 return String(); 177 178 FrameSelection& selection = m_frame->selection(); 179 180 // This is a WebKit DOM extension, incompatible with an IE extension 181 // IE has this same attribute, but returns "none", "text" and "control" 182 // http://msdn.microsoft.com/en-us/library/ms534692(VS.85).aspx 183 if (selection.isNone()) 184 return "None"; 185 if (selection.isCaret()) 186 return "Caret"; 187 return "Range"; 188 } 189 190 int DOMSelection::rangeCount() const 191 { 192 if (!m_frame) 193 return 0; 194 return m_frame->selection().isNone() ? 0 : 1; 195 } 196 197 void DOMSelection::collapse(Node* node, int offset, ExceptionState& exceptionState) 198 { 199 if (!m_frame) 200 return; 201 202 if (!node) { 203 m_frame->selection().clear(); 204 return; 205 } 206 207 if (offset < 0) { 208 exceptionState.throwDOMException(IndexSizeError, String::number(offset) + " is not a valid offset."); 209 return; 210 } 211 212 if (!isValidForPosition(node)) 213 return; 214 RefPtrWillBeRawPtr<Range> range = Range::create(node->document()); 215 range->setStart(node, offset, exceptionState); 216 if (exceptionState.hadException()) 217 return; 218 range->setEnd(node, offset, exceptionState); 219 if (exceptionState.hadException()) 220 return; 221 m_frame->selection().setSelectedRange(range.get(), DOWNSTREAM, m_frame->selection().isDirectional() ? FrameSelection::Directional : FrameSelection::NonDirectional); 222 } 223 224 void DOMSelection::collapseToEnd(ExceptionState& exceptionState) 225 { 226 if (!m_frame) 227 return; 228 229 const VisibleSelection& selection = m_frame->selection().selection(); 230 231 if (selection.isNone()) { 232 exceptionState.throwDOMException(InvalidStateError, "there is no selection."); 233 return; 234 } 235 236 m_frame->selection().moveTo(VisiblePosition(selection.end(), DOWNSTREAM)); 237 } 238 239 void DOMSelection::collapseToStart(ExceptionState& exceptionState) 240 { 241 if (!m_frame) 242 return; 243 244 const VisibleSelection& selection = m_frame->selection().selection(); 245 246 if (selection.isNone()) { 247 exceptionState.throwDOMException(InvalidStateError, "there is no selection."); 248 return; 249 } 250 251 m_frame->selection().moveTo(VisiblePosition(selection.start(), DOWNSTREAM)); 252 } 253 254 void DOMSelection::empty() 255 { 256 if (!m_frame) 257 return; 258 m_frame->selection().clear(); 259 } 260 261 void DOMSelection::setBaseAndExtent(Node* baseNode, int baseOffset, Node* extentNode, int extentOffset, ExceptionState& exceptionState) 262 { 263 if (!m_frame) 264 return; 265 266 if (baseOffset < 0) { 267 exceptionState.throwDOMException(IndexSizeError, String::number(baseOffset) + " is not a valid base offset."); 268 return; 269 } 270 271 if (extentOffset < 0) { 272 exceptionState.throwDOMException(IndexSizeError, String::number(extentOffset) + " is not a valid extent offset."); 273 return; 274 } 275 276 if (!isValidForPosition(baseNode) || !isValidForPosition(extentNode)) 277 return; 278 279 // FIXME: Eliminate legacy editing positions 280 VisiblePosition visibleBase = VisiblePosition(createLegacyEditingPosition(baseNode, baseOffset), DOWNSTREAM); 281 VisiblePosition visibleExtent = VisiblePosition(createLegacyEditingPosition(extentNode, extentOffset), DOWNSTREAM); 282 283 m_frame->selection().moveTo(visibleBase, visibleExtent); 284 } 285 286 void DOMSelection::modify(const String& alterString, const String& directionString, const String& granularityString) 287 { 288 if (!m_frame) 289 return; 290 291 FrameSelection::EAlteration alter; 292 if (equalIgnoringCase(alterString, "extend")) 293 alter = FrameSelection::AlterationExtend; 294 else if (equalIgnoringCase(alterString, "move")) 295 alter = FrameSelection::AlterationMove; 296 else 297 return; 298 299 SelectionDirection direction; 300 if (equalIgnoringCase(directionString, "forward")) 301 direction = DirectionForward; 302 else if (equalIgnoringCase(directionString, "backward")) 303 direction = DirectionBackward; 304 else if (equalIgnoringCase(directionString, "left")) 305 direction = DirectionLeft; 306 else if (equalIgnoringCase(directionString, "right")) 307 direction = DirectionRight; 308 else 309 return; 310 311 TextGranularity granularity; 312 if (equalIgnoringCase(granularityString, "character")) 313 granularity = CharacterGranularity; 314 else if (equalIgnoringCase(granularityString, "word")) 315 granularity = WordGranularity; 316 else if (equalIgnoringCase(granularityString, "sentence")) 317 granularity = SentenceGranularity; 318 else if (equalIgnoringCase(granularityString, "line")) 319 granularity = LineGranularity; 320 else if (equalIgnoringCase(granularityString, "paragraph")) 321 granularity = ParagraphGranularity; 322 else if (equalIgnoringCase(granularityString, "lineboundary")) 323 granularity = LineBoundary; 324 else if (equalIgnoringCase(granularityString, "sentenceboundary")) 325 granularity = SentenceBoundary; 326 else if (equalIgnoringCase(granularityString, "paragraphboundary")) 327 granularity = ParagraphBoundary; 328 else if (equalIgnoringCase(granularityString, "documentboundary")) 329 granularity = DocumentBoundary; 330 else 331 return; 332 333 m_frame->selection().modify(alter, direction, granularity); 334 } 335 336 void DOMSelection::extend(Node* node, int offset, ExceptionState& exceptionState) 337 { 338 ASSERT(node); 339 340 if (!m_frame) 341 return; 342 343 if (offset < 0) { 344 exceptionState.throwDOMException(IndexSizeError, String::number(offset) + " is not a valid offset."); 345 return; 346 } 347 if (offset > (node->offsetInCharacters() ? caretMaxOffset(node) : (int)node->countChildren())) { 348 exceptionState.throwDOMException(IndexSizeError, String::number(offset) + " is larger than the given node's length."); 349 return; 350 } 351 352 if (!isValidForPosition(node)) 353 return; 354 355 // FIXME: Eliminate legacy editing positions 356 m_frame->selection().setExtent(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM)); 357 } 358 359 PassRefPtrWillBeRawPtr<Range> DOMSelection::getRangeAt(int index, ExceptionState& exceptionState) 360 { 361 if (!m_frame) 362 return nullptr; 363 364 if (index < 0 || index >= rangeCount()) { 365 exceptionState.throwDOMException(IndexSizeError, String::number(index) + " is not a valid index."); 366 return nullptr; 367 } 368 369 // If you're hitting this, you've added broken multi-range selection support 370 ASSERT(rangeCount() == 1); 371 372 if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) { 373 ASSERT(!shadowAncestor->isShadowRoot()); 374 ContainerNode* container = shadowAncestor->parentOrShadowHostNode(); 375 int offset = shadowAncestor->nodeIndex(); 376 return Range::create(shadowAncestor->document(), container, offset, container, offset); 377 } 378 379 return m_frame->selection().firstRange(); 380 } 381 382 void DOMSelection::removeAllRanges() 383 { 384 if (!m_frame) 385 return; 386 m_frame->selection().clear(); 387 } 388 389 void DOMSelection::addRange(Range* newRange) 390 { 391 if (!m_frame) 392 return; 393 394 // FIXME: Should we throw DOMException for error cases below? 395 if (!newRange) { 396 addConsoleError("The given range is null."); 397 return; 398 } 399 400 if (!newRange->startContainer()) { 401 addConsoleError("The given range has no container. Perhaps 'detach()' has been invoked on it?"); 402 return; 403 } 404 405 FrameSelection& selection = m_frame->selection(); 406 407 if (selection.isNone()) { 408 selection.setSelectedRange(newRange, VP_DEFAULT_AFFINITY); 409 return; 410 } 411 412 RefPtrWillBeRawPtr<Range> originalRange = selection.firstRange(); 413 414 if (originalRange->startContainer()->document() != newRange->startContainer()->document()) { 415 addConsoleError("The given range does not belong to the current selection's document."); 416 return; 417 } 418 if (originalRange->startContainer()->treeScope() != newRange->startContainer()->treeScope()) { 419 addConsoleError("The given range and the current selection belong to two different document fragments."); 420 return; 421 } 422 423 if (originalRange->compareBoundaryPoints(Range::START_TO_END, newRange, ASSERT_NO_EXCEPTION) < 0 424 || newRange->compareBoundaryPoints(Range::START_TO_END, originalRange.get(), ASSERT_NO_EXCEPTION) < 0) { 425 addConsoleError("Discontiguous selection is not supported."); 426 return; 427 } 428 429 // FIXME: "Merge the ranges if they intersect" is Blink-specific behavior; other browsers supporting discontiguous 430 // selection (obviously) keep each Range added and return it in getRangeAt(). But it's unclear if we can really 431 // do the same, since we don't support discontiguous selection. Further discussions at 432 // <https://code.google.com/p/chromium/issues/detail?id=353069>. 433 434 Range* start = originalRange->compareBoundaryPoints(Range::START_TO_START, newRange, ASSERT_NO_EXCEPTION) < 0 ? originalRange.get() : newRange; 435 Range* end = originalRange->compareBoundaryPoints(Range::END_TO_END, newRange, ASSERT_NO_EXCEPTION) < 0 ? newRange : originalRange.get(); 436 RefPtrWillBeRawPtr<Range> merged = Range::create(originalRange->startContainer()->document(), start->startContainer(), start->startOffset(), end->endContainer(), end->endOffset()); 437 EAffinity affinity = selection.selection().affinity(); 438 selection.setSelectedRange(merged.get(), affinity); 439 } 440 441 void DOMSelection::deleteFromDocument() 442 { 443 if (!m_frame) 444 return; 445 446 FrameSelection& selection = m_frame->selection(); 447 448 if (selection.isNone()) 449 return; 450 451 RefPtrWillBeRawPtr<Range> selectedRange = selection.selection().toNormalizedRange(); 452 if (!selectedRange) 453 return; 454 455 selectedRange->deleteContents(ASSERT_NO_EXCEPTION); 456 457 setBaseAndExtent(selectedRange->startContainer(), selectedRange->startOffset(), selectedRange->startContainer(), selectedRange->startOffset(), ASSERT_NO_EXCEPTION); 458 } 459 460 bool DOMSelection::containsNode(const Node* n, bool allowPartial) const 461 { 462 if (!m_frame) 463 return false; 464 465 FrameSelection& selection = m_frame->selection(); 466 467 if (!n || m_frame->document() != n->document() || selection.isNone()) 468 return false; 469 470 unsigned nodeIndex = n->nodeIndex(); 471 RefPtrWillBeRawPtr<Range> selectedRange = selection.selection().toNormalizedRange(); 472 473 ContainerNode* parentNode = n->parentNode(); 474 if (!parentNode) 475 return false; 476 477 TrackExceptionState exceptionState; 478 bool nodeFullySelected = Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->startContainer(), selectedRange->startOffset(), exceptionState) >= 0 && !exceptionState.hadException() 479 && Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->endContainer(), selectedRange->endOffset(), exceptionState) <= 0 && !exceptionState.hadException(); 480 if (exceptionState.hadException()) 481 return false; 482 if (nodeFullySelected) 483 return true; 484 485 bool nodeFullyUnselected = (Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->endContainer(), selectedRange->endOffset(), exceptionState) > 0 && !exceptionState.hadException()) 486 || (Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->startContainer(), selectedRange->startOffset(), exceptionState) < 0 && !exceptionState.hadException()); 487 ASSERT(!exceptionState.hadException()); 488 if (nodeFullyUnselected) 489 return false; 490 491 return allowPartial || n->isTextNode(); 492 } 493 494 void DOMSelection::selectAllChildren(Node* n, ExceptionState& exceptionState) 495 { 496 if (!n) 497 return; 498 499 // This doesn't (and shouldn't) select text node characters. 500 setBaseAndExtent(n, 0, n, n->countChildren(), exceptionState); 501 } 502 503 String DOMSelection::toString() 504 { 505 if (!m_frame) 506 return String(); 507 508 Position start, end; 509 if (m_frame->selection().selection().toNormalizedPositions(start, end)) 510 return plainText(start, end); 511 return emptyString(); 512 } 513 514 Node* DOMSelection::shadowAdjustedNode(const Position& position) const 515 { 516 if (position.isNull()) 517 return 0; 518 519 Node* containerNode = position.containerNode(); 520 Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode); 521 522 if (!adjustedNode) 523 return 0; 524 525 if (containerNode == adjustedNode) 526 return containerNode; 527 528 ASSERT(!adjustedNode->isShadowRoot()); 529 return adjustedNode->parentOrShadowHostNode(); 530 } 531 532 int DOMSelection::shadowAdjustedOffset(const Position& position) const 533 { 534 if (position.isNull()) 535 return 0; 536 537 Node* containerNode = position.containerNode(); 538 Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode); 539 540 if (!adjustedNode) 541 return 0; 542 543 if (containerNode == adjustedNode) 544 return position.computeOffsetInContainerNode(); 545 546 return adjustedNode->nodeIndex(); 547 } 548 549 bool DOMSelection::isValidForPosition(Node* node) const 550 { 551 ASSERT(m_frame); 552 if (!node) 553 return true; 554 return node->document() == m_frame->document(); 555 } 556 557 void DOMSelection::addConsoleError(const String& message) 558 { 559 if (m_treeScope) 560 m_treeScope->document().addConsoleMessage(ConsoleMessage::create(JSMessageSource, ErrorMessageLevel, message)); 561 } 562 563 void DOMSelection::trace(Visitor* visitor) 564 { 565 visitor->trace(m_treeScope); 566 DOMWindowProperty::trace(visitor); 567 } 568 569 } // namespace blink 570