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/page/DOMSelection.h" 33 34 #include "bindings/v8/ExceptionState.h" 35 #include "bindings/v8/ExceptionStatePlaceholder.h" 36 #include "core/dom/Document.h" 37 #include "core/dom/ExceptionCode.h" 38 #include "core/dom/Node.h" 39 #include "core/dom/Range.h" 40 #include "core/dom/TreeScope.h" 41 #include "core/editing/FrameSelection.h" 42 #include "core/editing/TextIterator.h" 43 #include "core/editing/htmlediting.h" 44 #include "core/frame/Frame.h" 45 #include "wtf/text/WTFString.h" 46 47 namespace WebCore { 48 49 static Node* selectionShadowAncestor(Frame* frame) 50 { 51 Node* node = frame->selection().selection().base().anchorNode(); 52 if (!node) 53 return 0; 54 55 if (!node->isInShadowTree()) 56 return 0; 57 58 return frame->document()->ancestorInThisScope(node); 59 } 60 61 DOMSelection::DOMSelection(const TreeScope* treeScope) 62 : DOMWindowProperty(treeScope->rootNode()->document().frame()) 63 , m_treeScope(treeScope) 64 { 65 ScriptWrappable::init(this); 66 } 67 68 void DOMSelection::clearTreeScope() 69 { 70 m_treeScope = 0; 71 } 72 73 const VisibleSelection& DOMSelection::visibleSelection() const 74 { 75 ASSERT(m_frame); 76 return m_frame->selection().selection(); 77 } 78 79 static Position anchorPosition(const VisibleSelection& selection) 80 { 81 Position anchor = selection.isBaseFirst() ? selection.start() : selection.end(); 82 return anchor.parentAnchoredEquivalent(); 83 } 84 85 static Position focusPosition(const VisibleSelection& selection) 86 { 87 Position focus = selection.isBaseFirst() ? selection.end() : selection.start(); 88 return focus.parentAnchoredEquivalent(); 89 } 90 91 static Position basePosition(const VisibleSelection& selection) 92 { 93 return selection.base().parentAnchoredEquivalent(); 94 } 95 96 static Position extentPosition(const VisibleSelection& selection) 97 { 98 return selection.extent().parentAnchoredEquivalent(); 99 } 100 101 Node* DOMSelection::anchorNode() const 102 { 103 if (!m_frame) 104 return 0; 105 106 return shadowAdjustedNode(anchorPosition(visibleSelection())); 107 } 108 109 int DOMSelection::anchorOffset() const 110 { 111 if (!m_frame) 112 return 0; 113 114 return shadowAdjustedOffset(anchorPosition(visibleSelection())); 115 } 116 117 Node* DOMSelection::focusNode() const 118 { 119 if (!m_frame) 120 return 0; 121 122 return shadowAdjustedNode(focusPosition(visibleSelection())); 123 } 124 125 int DOMSelection::focusOffset() const 126 { 127 if (!m_frame) 128 return 0; 129 130 return shadowAdjustedOffset(focusPosition(visibleSelection())); 131 } 132 133 Node* DOMSelection::baseNode() const 134 { 135 if (!m_frame) 136 return 0; 137 138 return shadowAdjustedNode(basePosition(visibleSelection())); 139 } 140 141 int DOMSelection::baseOffset() const 142 { 143 if (!m_frame) 144 return 0; 145 146 return shadowAdjustedOffset(basePosition(visibleSelection())); 147 } 148 149 Node* DOMSelection::extentNode() const 150 { 151 if (!m_frame) 152 return 0; 153 154 return shadowAdjustedNode(extentPosition(visibleSelection())); 155 } 156 157 int DOMSelection::extentOffset() const 158 { 159 if (!m_frame) 160 return 0; 161 162 return shadowAdjustedOffset(extentPosition(visibleSelection())); 163 } 164 165 bool DOMSelection::isCollapsed() const 166 { 167 if (!m_frame || selectionShadowAncestor(m_frame)) 168 return true; 169 return !m_frame->selection().isRange(); 170 } 171 172 String DOMSelection::type() const 173 { 174 if (!m_frame) 175 return String(); 176 177 FrameSelection& selection = m_frame->selection(); 178 179 // This is a WebKit DOM extension, incompatible with an IE extension 180 // IE has this same attribute, but returns "none", "text" and "control" 181 // http://msdn.microsoft.com/en-us/library/ms534692(VS.85).aspx 182 if (selection.isNone()) 183 return "None"; 184 if (selection.isCaret()) 185 return "Caret"; 186 return "Range"; 187 } 188 189 int DOMSelection::rangeCount() const 190 { 191 if (!m_frame) 192 return 0; 193 return m_frame->selection().isNone() ? 0 : 1; 194 } 195 196 void DOMSelection::collapse(Node* node, int offset, ExceptionState& exceptionState) 197 { 198 if (!m_frame) 199 return; 200 201 if (offset < 0) { 202 exceptionState.throwDOMException(IndexSizeError, String::number(offset) + " is not a valid offset."); 203 return; 204 } 205 206 if (!isValidForPosition(node)) 207 return; 208 209 // FIXME: Eliminate legacy editing positions 210 m_frame->selection().moveTo(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM)); 211 } 212 213 void DOMSelection::collapseToEnd(ExceptionState& exceptionState) 214 { 215 if (!m_frame) 216 return; 217 218 const VisibleSelection& selection = m_frame->selection().selection(); 219 220 if (selection.isNone()) { 221 exceptionState.throwDOMException(InvalidStateError, "there is no selection."); 222 return; 223 } 224 225 m_frame->selection().moveTo(VisiblePosition(selection.end(), DOWNSTREAM)); 226 } 227 228 void DOMSelection::collapseToStart(ExceptionState& exceptionState) 229 { 230 if (!m_frame) 231 return; 232 233 const VisibleSelection& selection = m_frame->selection().selection(); 234 235 if (selection.isNone()) { 236 exceptionState.throwDOMException(InvalidStateError, "there is no selection."); 237 return; 238 } 239 240 m_frame->selection().moveTo(VisiblePosition(selection.start(), DOWNSTREAM)); 241 } 242 243 void DOMSelection::empty() 244 { 245 if (!m_frame) 246 return; 247 m_frame->selection().clear(); 248 } 249 250 void DOMSelection::setBaseAndExtent(Node* baseNode, int baseOffset, Node* extentNode, int extentOffset, ExceptionState& exceptionState) 251 { 252 if (!m_frame) 253 return; 254 255 if (baseOffset < 0) { 256 exceptionState.throwDOMException(IndexSizeError, String::number(baseOffset) + " is not a valid base offset."); 257 return; 258 } 259 260 if (extentOffset < 0) { 261 exceptionState.throwDOMException(IndexSizeError, String::number(extentOffset) + " is not a valid extent offset."); 262 return; 263 } 264 265 if (!isValidForPosition(baseNode) || !isValidForPosition(extentNode)) 266 return; 267 268 // FIXME: Eliminate legacy editing positions 269 VisiblePosition visibleBase = VisiblePosition(createLegacyEditingPosition(baseNode, baseOffset), DOWNSTREAM); 270 VisiblePosition visibleExtent = VisiblePosition(createLegacyEditingPosition(extentNode, extentOffset), DOWNSTREAM); 271 272 m_frame->selection().moveTo(visibleBase, visibleExtent); 273 } 274 275 void DOMSelection::setPosition(Node* node, int offset, ExceptionState& exceptionState) 276 { 277 if (!m_frame) 278 return; 279 if (offset < 0) { 280 exceptionState.throwDOMException(IndexSizeError, String::number(offset) + " is not a valid offset."); 281 return; 282 } 283 284 if (!isValidForPosition(node)) 285 return; 286 287 // FIXME: Eliminate legacy editing positions 288 m_frame->selection().moveTo(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM)); 289 } 290 291 void DOMSelection::modify(const String& alterString, const String& directionString, const String& granularityString) 292 { 293 if (!m_frame) 294 return; 295 296 FrameSelection::EAlteration alter; 297 if (equalIgnoringCase(alterString, "extend")) 298 alter = FrameSelection::AlterationExtend; 299 else if (equalIgnoringCase(alterString, "move")) 300 alter = FrameSelection::AlterationMove; 301 else 302 return; 303 304 SelectionDirection direction; 305 if (equalIgnoringCase(directionString, "forward")) 306 direction = DirectionForward; 307 else if (equalIgnoringCase(directionString, "backward")) 308 direction = DirectionBackward; 309 else if (equalIgnoringCase(directionString, "left")) 310 direction = DirectionLeft; 311 else if (equalIgnoringCase(directionString, "right")) 312 direction = DirectionRight; 313 else 314 return; 315 316 TextGranularity granularity; 317 if (equalIgnoringCase(granularityString, "character")) 318 granularity = CharacterGranularity; 319 else if (equalIgnoringCase(granularityString, "word")) 320 granularity = WordGranularity; 321 else if (equalIgnoringCase(granularityString, "sentence")) 322 granularity = SentenceGranularity; 323 else if (equalIgnoringCase(granularityString, "line")) 324 granularity = LineGranularity; 325 else if (equalIgnoringCase(granularityString, "paragraph")) 326 granularity = ParagraphGranularity; 327 else if (equalIgnoringCase(granularityString, "lineboundary")) 328 granularity = LineBoundary; 329 else if (equalIgnoringCase(granularityString, "sentenceboundary")) 330 granularity = SentenceBoundary; 331 else if (equalIgnoringCase(granularityString, "paragraphboundary")) 332 granularity = ParagraphBoundary; 333 else if (equalIgnoringCase(granularityString, "documentboundary")) 334 granularity = DocumentBoundary; 335 else 336 return; 337 338 m_frame->selection().modify(alter, direction, granularity); 339 } 340 341 void DOMSelection::extend(Node* node, int offset, ExceptionState& exceptionState) 342 { 343 if (!m_frame) 344 return; 345 346 if (!node) { 347 exceptionState.throwDOMException(TypeMismatchError, "The node provided is invalid."); 348 return; 349 } 350 351 if (offset < 0) { 352 exceptionState.throwDOMException(IndexSizeError, String::number(offset) + " is not a valid offset."); 353 return; 354 } 355 if (offset > (node->offsetInCharacters() ? caretMaxOffset(node) : (int)node->childNodeCount())) { 356 exceptionState.throwDOMException(IndexSizeError, String::number(offset) + " is larger than the given node's length."); 357 return; 358 } 359 360 if (!isValidForPosition(node)) 361 return; 362 363 // FIXME: Eliminate legacy editing positions 364 m_frame->selection().setExtent(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM)); 365 } 366 367 PassRefPtr<Range> DOMSelection::getRangeAt(int index, ExceptionState& exceptionState) 368 { 369 if (!m_frame) 370 return 0; 371 372 if (index < 0 || index >= rangeCount()) { 373 exceptionState.throwDOMException(IndexSizeError, String::number(index) + " is not a valid index."); 374 return 0; 375 } 376 377 // If you're hitting this, you've added broken multi-range selection support 378 ASSERT(rangeCount() == 1); 379 380 if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) { 381 ASSERT(!shadowAncestor->isShadowRoot()); 382 ContainerNode* container = shadowAncestor->parentOrShadowHostNode(); 383 int offset = shadowAncestor->nodeIndex(); 384 return Range::create(shadowAncestor->document(), container, offset, container, offset); 385 } 386 387 const VisibleSelection& selection = m_frame->selection().selection(); 388 return selection.firstRange(); 389 } 390 391 void DOMSelection::removeAllRanges() 392 { 393 if (!m_frame) 394 return; 395 m_frame->selection().clear(); 396 } 397 398 void DOMSelection::addRange(Range* r) 399 { 400 if (!m_frame) 401 return; 402 if (!r) 403 return; 404 405 FrameSelection& selection = m_frame->selection(); 406 407 if (selection.isNone()) { 408 selection.setSelection(VisibleSelection(r)); 409 return; 410 } 411 412 RefPtr<Range> range = selection.selection().toNormalizedRange(); 413 if (r->compareBoundaryPoints(Range::START_TO_START, range.get(), IGNORE_EXCEPTION) == -1) { 414 // We don't support discontiguous selection. We don't do anything if r and range don't intersect. 415 if (r->compareBoundaryPoints(Range::START_TO_END, range.get(), IGNORE_EXCEPTION) > -1) { 416 if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), IGNORE_EXCEPTION) == -1) { 417 // The original range and r intersect. 418 selection.setSelection(VisibleSelection(r->startPosition(), range->endPosition(), DOWNSTREAM)); 419 } else { 420 // r contains the original range. 421 selection.setSelection(VisibleSelection(r)); 422 } 423 } 424 } else { 425 // We don't support discontiguous selection. We don't do anything if r and range don't intersect. 426 TrackExceptionState exceptionState; 427 if (r->compareBoundaryPoints(Range::END_TO_START, range.get(), exceptionState) < 1 && !exceptionState.hadException()) { 428 if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), IGNORE_EXCEPTION) == -1) { 429 // The original range contains r. 430 selection.setSelection(VisibleSelection(range.get())); 431 } else { 432 // The original range and r intersect. 433 selection.setSelection(VisibleSelection(range->startPosition(), r->endPosition(), DOWNSTREAM)); 434 } 435 } 436 } 437 } 438 439 void DOMSelection::deleteFromDocument() 440 { 441 if (!m_frame) 442 return; 443 444 FrameSelection& selection = m_frame->selection(); 445 446 if (selection.isNone()) 447 return; 448 449 if (isCollapsed()) 450 selection.modify(FrameSelection::AlterationExtend, DirectionBackward, CharacterGranularity); 451 452 RefPtr<Range> selectedRange = selection.selection().toNormalizedRange(); 453 if (!selectedRange) 454 return; 455 456 selectedRange->deleteContents(ASSERT_NO_EXCEPTION); 457 458 setBaseAndExtent(selectedRange->startContainer(ASSERT_NO_EXCEPTION), selectedRange->startOffset(), selectedRange->startContainer(), selectedRange->startOffset(), ASSERT_NO_EXCEPTION); 459 } 460 461 bool DOMSelection::containsNode(const Node* n, bool allowPartial) const 462 { 463 if (!m_frame) 464 return false; 465 466 FrameSelection& selection = m_frame->selection(); 467 468 if (!n || m_frame->document() != n->document() || selection.isNone()) 469 return false; 470 471 unsigned nodeIndex = n->nodeIndex(); 472 RefPtr<Range> selectedRange = selection.selection().toNormalizedRange(); 473 474 ContainerNode* parentNode = n->parentNode(); 475 if (!parentNode) 476 return false; 477 478 TrackExceptionState exceptionState; 479 bool nodeFullySelected = Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->startContainer(), selectedRange->startOffset(), exceptionState) >= 0 && !exceptionState.hadException() 480 && Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->endContainer(), selectedRange->endOffset(), exceptionState) <= 0 && !exceptionState.hadException(); 481 ASSERT(!exceptionState.hadException()); 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->childNodeCount(), exceptionState); 501 } 502 503 String DOMSelection::toString() 504 { 505 if (!m_frame) 506 return String(); 507 508 return plainText(m_frame->selection().selection().toNormalizedRange().get()); 509 } 510 511 Node* DOMSelection::shadowAdjustedNode(const Position& position) const 512 { 513 if (position.isNull()) 514 return 0; 515 516 Node* containerNode = position.containerNode(); 517 Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode); 518 519 if (!adjustedNode) 520 return 0; 521 522 if (containerNode == adjustedNode) 523 return containerNode; 524 525 ASSERT(!adjustedNode->isShadowRoot()); 526 return adjustedNode->parentOrShadowHostNode(); 527 } 528 529 int DOMSelection::shadowAdjustedOffset(const Position& position) const 530 { 531 if (position.isNull()) 532 return 0; 533 534 Node* containerNode = position.containerNode(); 535 Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode); 536 537 if (!adjustedNode) 538 return 0; 539 540 if (containerNode == adjustedNode) 541 return position.computeOffsetInContainerNode(); 542 543 return adjustedNode->nodeIndex(); 544 } 545 546 bool DOMSelection::isValidForPosition(Node* node) const 547 { 548 ASSERT(m_frame); 549 if (!node) 550 return true; 551 return node->document() == m_frame->document(); 552 } 553 554 } // namespace WebCore 555