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