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/page/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& es) 197 { 198 if (!m_frame) 199 return; 200 201 if (offset < 0) { 202 es.throwDOMException(IndexSizeError); 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& es) 214 { 215 if (!m_frame) 216 return; 217 218 const VisibleSelection& selection = m_frame->selection()->selection(); 219 220 if (selection.isNone()) { 221 es.throwDOMException(InvalidStateError); 222 return; 223 } 224 225 m_frame->selection()->moveTo(VisiblePosition(selection.end(), DOWNSTREAM)); 226 } 227 228 void DOMSelection::collapseToStart(ExceptionState& es) 229 { 230 if (!m_frame) 231 return; 232 233 const VisibleSelection& selection = m_frame->selection()->selection(); 234 235 if (selection.isNone()) { 236 es.throwDOMException(InvalidStateError); 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& es) 251 { 252 if (!m_frame) 253 return; 254 255 if (baseOffset < 0 || extentOffset < 0) { 256 es.throwDOMException(IndexSizeError); 257 return; 258 } 259 260 if (!isValidForPosition(baseNode) || !isValidForPosition(extentNode)) 261 return; 262 263 // FIXME: Eliminate legacy editing positions 264 VisiblePosition visibleBase = VisiblePosition(createLegacyEditingPosition(baseNode, baseOffset), DOWNSTREAM); 265 VisiblePosition visibleExtent = VisiblePosition(createLegacyEditingPosition(extentNode, extentOffset), DOWNSTREAM); 266 267 m_frame->selection()->moveTo(visibleBase, visibleExtent); 268 } 269 270 void DOMSelection::setPosition(Node* node, int offset, ExceptionState& es) 271 { 272 if (!m_frame) 273 return; 274 if (offset < 0) { 275 es.throwDOMException(IndexSizeError); 276 return; 277 } 278 279 if (!isValidForPosition(node)) 280 return; 281 282 // FIXME: Eliminate legacy editing positions 283 m_frame->selection()->moveTo(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM)); 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& es) 337 { 338 if (!m_frame) 339 return; 340 341 if (!node) { 342 es.throwDOMException(TypeMismatchError); 343 return; 344 } 345 346 if (offset < 0 || offset > (node->offsetInCharacters() ? caretMaxOffset(node) : (int)node->childNodeCount())) { 347 es.throwDOMException(IndexSizeError); 348 return; 349 } 350 351 if (!isValidForPosition(node)) 352 return; 353 354 // FIXME: Eliminate legacy editing positions 355 m_frame->selection()->setExtent(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM)); 356 } 357 358 PassRefPtr<Range> DOMSelection::getRangeAt(int index, ExceptionState& es) 359 { 360 if (!m_frame) 361 return 0; 362 363 if (index < 0 || index >= rangeCount()) { 364 es.throwDOMException(IndexSizeError); 365 return 0; 366 } 367 368 // If you're hitting this, you've added broken multi-range selection support 369 ASSERT(rangeCount() == 1); 370 371 if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) { 372 ContainerNode* container = shadowAncestor->parentNodeGuaranteedHostFree(); 373 int offset = shadowAncestor->nodeIndex(); 374 return Range::create(shadowAncestor->document(), container, offset, container, offset); 375 } 376 377 const VisibleSelection& selection = m_frame->selection()->selection(); 378 return selection.firstRange(); 379 } 380 381 void DOMSelection::removeAllRanges() 382 { 383 if (!m_frame) 384 return; 385 m_frame->selection()->clear(); 386 } 387 388 void DOMSelection::addRange(Range* r) 389 { 390 if (!m_frame) 391 return; 392 if (!r) 393 return; 394 395 FrameSelection* selection = m_frame->selection(); 396 397 if (selection->isNone()) { 398 selection->setSelection(VisibleSelection(r)); 399 return; 400 } 401 402 RefPtr<Range> range = selection->selection().toNormalizedRange(); 403 if (r->compareBoundaryPoints(Range::START_TO_START, range.get(), IGNORE_EXCEPTION) == -1) { 404 // We don't support discontiguous selection. We don't do anything if r and range don't intersect. 405 if (r->compareBoundaryPoints(Range::START_TO_END, range.get(), IGNORE_EXCEPTION) > -1) { 406 if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), IGNORE_EXCEPTION) == -1) 407 // The original range and r intersect. 408 selection->setSelection(VisibleSelection(r->startPosition(), range->endPosition(), DOWNSTREAM)); 409 else 410 // r contains the original range. 411 selection->setSelection(VisibleSelection(r)); 412 } 413 } else { 414 // We don't support discontiguous selection. We don't do anything if r and range don't intersect. 415 TrackExceptionState es; 416 if (r->compareBoundaryPoints(Range::END_TO_START, range.get(), es) < 1 && !es.hadException()) { 417 if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), IGNORE_EXCEPTION) == -1) 418 // The original range contains r. 419 selection->setSelection(VisibleSelection(range.get())); 420 else 421 // The original range and r intersect. 422 selection->setSelection(VisibleSelection(range->startPosition(), r->endPosition(), DOWNSTREAM)); 423 } 424 } 425 } 426 427 void DOMSelection::deleteFromDocument() 428 { 429 if (!m_frame) 430 return; 431 432 FrameSelection* selection = m_frame->selection(); 433 434 if (selection->isNone()) 435 return; 436 437 if (isCollapsed()) 438 selection->modify(FrameSelection::AlterationExtend, DirectionBackward, CharacterGranularity); 439 440 RefPtr<Range> selectedRange = selection->selection().toNormalizedRange(); 441 if (!selectedRange) 442 return; 443 444 selectedRange->deleteContents(ASSERT_NO_EXCEPTION); 445 446 setBaseAndExtent(selectedRange->startContainer(ASSERT_NO_EXCEPTION), selectedRange->startOffset(), selectedRange->startContainer(), selectedRange->startOffset(), ASSERT_NO_EXCEPTION); 447 } 448 449 bool DOMSelection::containsNode(const Node* n, bool allowPartial) const 450 { 451 if (!m_frame) 452 return false; 453 454 FrameSelection* selection = m_frame->selection(); 455 456 if (!n || m_frame->document() != n->document() || selection->isNone()) 457 return false; 458 459 unsigned nodeIndex = n->nodeIndex(); 460 RefPtr<Range> selectedRange = selection->selection().toNormalizedRange(); 461 462 ContainerNode* parentNode = n->parentNode(); 463 if (!parentNode) 464 return false; 465 466 TrackExceptionState es; 467 bool nodeFullySelected = Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->startContainer(), selectedRange->startOffset(), es) >= 0 && !es.hadException() 468 && Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->endContainer(), selectedRange->endOffset(), es) <= 0 && !es.hadException(); 469 ASSERT(!es.hadException()); 470 if (nodeFullySelected) 471 return true; 472 473 bool nodeFullyUnselected = (Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->endContainer(), selectedRange->endOffset(), es) > 0 && !es.hadException()) 474 || (Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->startContainer(), selectedRange->startOffset(), es) < 0 && !es.hadException()); 475 ASSERT(!es.hadException()); 476 if (nodeFullyUnselected) 477 return false; 478 479 return allowPartial || n->isTextNode(); 480 } 481 482 void DOMSelection::selectAllChildren(Node* n, ExceptionState& es) 483 { 484 if (!n) 485 return; 486 487 // This doesn't (and shouldn't) select text node characters. 488 setBaseAndExtent(n, 0, n, n->childNodeCount(), es); 489 } 490 491 String DOMSelection::toString() 492 { 493 if (!m_frame) 494 return String(); 495 496 return plainText(m_frame->selection()->selection().toNormalizedRange().get()); 497 } 498 499 Node* DOMSelection::shadowAdjustedNode(const Position& position) const 500 { 501 if (position.isNull()) 502 return 0; 503 504 Node* containerNode = position.containerNode(); 505 Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode); 506 507 if (!adjustedNode) 508 return 0; 509 510 if (containerNode == adjustedNode) 511 return containerNode; 512 513 return adjustedNode->parentNodeGuaranteedHostFree(); 514 } 515 516 int DOMSelection::shadowAdjustedOffset(const Position& position) const 517 { 518 if (position.isNull()) 519 return 0; 520 521 Node* containerNode = position.containerNode(); 522 Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode); 523 524 if (!adjustedNode) 525 return 0; 526 527 if (containerNode == adjustedNode) 528 return position.computeOffsetInContainerNode(); 529 530 return adjustedNode->nodeIndex(); 531 } 532 533 bool DOMSelection::isValidForPosition(Node* node) const 534 { 535 ASSERT(m_frame); 536 if (!node) 537 return true; 538 return node->document() == m_frame->document(); 539 } 540 541 } // namespace WebCore 542