1 /* 2 * Copyright (C) 2007, 2009 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 14 * its contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 30 #include "config.h" 31 #include "DOMSelection.h" 32 33 #include "ExceptionCode.h" 34 #include "Frame.h" 35 #include "Node.h" 36 #include "PlatformString.h" 37 #include "Range.h" 38 #include "SelectionController.h" 39 #include "TextIterator.h" 40 #include "htmlediting.h" 41 42 namespace WebCore { 43 44 static Node* selectionShadowAncestor(Frame* frame) 45 { 46 Node* node = frame->selection()->selection().base().anchorNode(); 47 if (!node) 48 return 0; 49 Node* shadowAncestor = node->shadowAncestorNode(); 50 if (shadowAncestor == node) 51 return 0; 52 return shadowAncestor; 53 } 54 55 DOMSelection::DOMSelection(Frame* frame) 56 : m_frame(frame) 57 { 58 } 59 60 Frame* DOMSelection::frame() const 61 { 62 return m_frame; 63 } 64 65 void DOMSelection::disconnectFrame() 66 { 67 m_frame = 0; 68 } 69 70 const VisibleSelection& DOMSelection::visibleSelection() const 71 { 72 ASSERT(m_frame); 73 return m_frame->selection()->selection(); 74 } 75 76 static Position anchorPosition(const VisibleSelection& selection) 77 { 78 Position anchor = selection.isBaseFirst() ? selection.start() : selection.end(); 79 return anchor.parentAnchoredEquivalent(); 80 } 81 82 static Position focusPosition(const VisibleSelection& selection) 83 { 84 Position focus = selection.isBaseFirst() ? selection.end() : selection.start(); 85 return focus.parentAnchoredEquivalent(); 86 } 87 88 static Position basePosition(const VisibleSelection& selection) 89 { 90 return selection.base().parentAnchoredEquivalent(); 91 } 92 93 static Position extentPosition(const VisibleSelection& selection) 94 { 95 return selection.extent().parentAnchoredEquivalent(); 96 } 97 98 Node* DOMSelection::anchorNode() const 99 { 100 if (!m_frame) 101 return 0; 102 if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) 103 return shadowAncestor->parentNodeGuaranteedHostFree(); 104 return anchorPosition(visibleSelection()).containerNode(); 105 } 106 107 int DOMSelection::anchorOffset() const 108 { 109 if (!m_frame) 110 return 0; 111 if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) 112 return shadowAncestor->nodeIndex(); 113 return anchorPosition(visibleSelection()).offsetInContainerNode(); 114 } 115 116 Node* DOMSelection::focusNode() const 117 { 118 if (!m_frame) 119 return 0; 120 if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) 121 return shadowAncestor->parentNodeGuaranteedHostFree(); 122 return focusPosition(visibleSelection()).containerNode(); 123 } 124 125 int DOMSelection::focusOffset() const 126 { 127 if (!m_frame) 128 return 0; 129 if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) 130 return shadowAncestor->nodeIndex(); 131 return focusPosition(visibleSelection()).offsetInContainerNode(); 132 } 133 134 Node* DOMSelection::baseNode() const 135 { 136 if (!m_frame) 137 return 0; 138 if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) 139 return shadowAncestor->parentNodeGuaranteedHostFree(); 140 return basePosition(visibleSelection()).containerNode(); 141 } 142 143 int DOMSelection::baseOffset() const 144 { 145 if (!m_frame) 146 return 0; 147 if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) 148 return shadowAncestor->nodeIndex(); 149 return basePosition(visibleSelection()).offsetInContainerNode(); 150 } 151 152 Node* DOMSelection::extentNode() const 153 { 154 if (!m_frame) 155 return 0; 156 if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) 157 return shadowAncestor->parentNodeGuaranteedHostFree(); 158 return extentPosition(visibleSelection()).containerNode(); 159 } 160 161 int DOMSelection::extentOffset() const 162 { 163 if (!m_frame) 164 return 0; 165 if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) 166 return shadowAncestor->nodeIndex(); 167 return extentPosition(visibleSelection()).offsetInContainerNode(); 168 } 169 170 bool DOMSelection::isCollapsed() const 171 { 172 if (!m_frame || selectionShadowAncestor(m_frame)) 173 return true; 174 return !m_frame->selection()->isRange(); 175 } 176 177 String DOMSelection::type() const 178 { 179 if (!m_frame) 180 return String(); 181 182 SelectionController* selection = m_frame->selection(); 183 184 // This is a WebKit DOM extension, incompatible with an IE extension 185 // IE has this same attribute, but returns "none", "text" and "control" 186 // http://msdn.microsoft.com/en-us/library/ms534692(VS.85).aspx 187 if (selection->isNone()) 188 return "None"; 189 if (selection->isCaret()) 190 return "Caret"; 191 return "Range"; 192 } 193 194 int DOMSelection::rangeCount() const 195 { 196 if (!m_frame) 197 return 0; 198 return m_frame->selection()->isNone() ? 0 : 1; 199 } 200 201 void DOMSelection::collapse(Node* node, int offset, ExceptionCode& ec) 202 { 203 if (!m_frame) 204 return; 205 206 if (offset < 0) { 207 ec = INDEX_SIZE_ERR; 208 return; 209 } 210 211 if (!isValidForPosition(node)) 212 return; 213 214 // FIXME: Eliminate legacy editing positions 215 m_frame->selection()->moveTo(VisiblePosition(Position(node, offset), DOWNSTREAM)); 216 } 217 218 void DOMSelection::collapseToEnd(ExceptionCode& ec) 219 { 220 if (!m_frame) 221 return; 222 223 const VisibleSelection& selection = m_frame->selection()->selection(); 224 225 if (selection.isNone()) { 226 ec = INVALID_STATE_ERR; 227 return; 228 } 229 230 m_frame->selection()->moveTo(VisiblePosition(selection.end(), DOWNSTREAM)); 231 } 232 233 void DOMSelection::collapseToStart(ExceptionCode& ec) 234 { 235 if (!m_frame) 236 return; 237 238 const VisibleSelection& selection = m_frame->selection()->selection(); 239 240 if (selection.isNone()) { 241 ec = INVALID_STATE_ERR; 242 return; 243 } 244 245 m_frame->selection()->moveTo(VisiblePosition(selection.start(), DOWNSTREAM)); 246 } 247 248 void DOMSelection::empty() 249 { 250 if (!m_frame) 251 return; 252 m_frame->selection()->clear(); 253 } 254 255 void DOMSelection::setBaseAndExtent(Node* baseNode, int baseOffset, Node* extentNode, int extentOffset, ExceptionCode& ec) 256 { 257 if (!m_frame) 258 return; 259 260 if (baseOffset < 0 || extentOffset < 0) { 261 ec = INDEX_SIZE_ERR; 262 return; 263 } 264 265 if (!isValidForPosition(baseNode) || !isValidForPosition(extentNode)) 266 return; 267 268 // FIXME: Eliminate legacy editing positions 269 VisiblePosition visibleBase = VisiblePosition(Position(baseNode, baseOffset), DOWNSTREAM); 270 VisiblePosition visibleExtent = VisiblePosition(Position(extentNode, extentOffset), DOWNSTREAM); 271 272 m_frame->selection()->moveTo(visibleBase, visibleExtent); 273 } 274 275 void DOMSelection::setPosition(Node* node, int offset, ExceptionCode& ec) 276 { 277 if (!m_frame) 278 return; 279 if (offset < 0) { 280 ec = INDEX_SIZE_ERR; 281 return; 282 } 283 284 if (!isValidForPosition(node)) 285 return; 286 287 // FIXME: Eliminate legacy editing positions 288 m_frame->selection()->moveTo(VisiblePosition(Position(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 SelectionController::EAlteration alter; 297 if (equalIgnoringCase(alterString, "extend")) 298 alter = SelectionController::AlterationExtend; 299 else if (equalIgnoringCase(alterString, "move")) 300 alter = SelectionController::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 if (equalIgnoringCase(granularityString, "-webkit-visual-word")) 336 granularity = WebKitVisualWordGranularity; 337 else 338 return; 339 340 m_frame->selection()->modify(alter, direction, granularity, false); 341 } 342 343 void DOMSelection::extend(Node* node, int offset, ExceptionCode& ec) 344 { 345 if (!m_frame) 346 return; 347 348 if (!node) { 349 ec = TYPE_MISMATCH_ERR; 350 return; 351 } 352 353 if (offset < 0 || offset > (node->offsetInCharacters() ? caretMaxOffset(node) : (int)node->childNodeCount())) { 354 ec = INDEX_SIZE_ERR; 355 return; 356 } 357 358 if (!isValidForPosition(node)) 359 return; 360 361 // FIXME: Eliminate legacy editing positions 362 m_frame->selection()->setExtent(VisiblePosition(Position(node, offset), DOWNSTREAM)); 363 } 364 365 PassRefPtr<Range> DOMSelection::getRangeAt(int index, ExceptionCode& ec) 366 { 367 if (!m_frame) 368 return 0; 369 370 if (index < 0 || index >= rangeCount()) { 371 ec = INDEX_SIZE_ERR; 372 return 0; 373 } 374 375 // If you're hitting this, you've added broken multi-range selection support 376 ASSERT(rangeCount() == 1); 377 378 if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) { 379 ContainerNode* container = shadowAncestor->parentNodeGuaranteedHostFree(); 380 int offset = shadowAncestor->nodeIndex(); 381 return Range::create(shadowAncestor->document(), container, offset, container, offset); 382 } 383 384 const VisibleSelection& selection = m_frame->selection()->selection(); 385 return selection.firstRange(); 386 } 387 388 void DOMSelection::removeAllRanges() 389 { 390 if (!m_frame) 391 return; 392 m_frame->selection()->clear(); 393 } 394 395 void DOMSelection::addRange(Range* r) 396 { 397 if (!m_frame) 398 return; 399 if (!r) 400 return; 401 402 SelectionController* selection = m_frame->selection(); 403 404 if (selection->isNone()) { 405 selection->setSelection(VisibleSelection(r)); 406 return; 407 } 408 409 RefPtr<Range> range = selection->selection().toNormalizedRange(); 410 ExceptionCode ec = 0; 411 if (r->compareBoundaryPoints(Range::START_TO_START, range.get(), ec) == -1) { 412 // We don't support discontiguous selection. We don't do anything if r and range don't intersect. 413 if (r->compareBoundaryPoints(Range::START_TO_END, range.get(), ec) > -1) { 414 if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), ec) == -1) 415 // The original range and r intersect. 416 selection->setSelection(VisibleSelection(r->startPosition(), range->endPosition(), DOWNSTREAM)); 417 else 418 // r contains the original range. 419 selection->setSelection(VisibleSelection(r)); 420 } 421 } else { 422 // We don't support discontiguous selection. We don't do anything if r and range don't intersect. 423 if (r->compareBoundaryPoints(Range::END_TO_START, range.get(), ec) < 1) { 424 if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), ec) == -1) 425 // The original range contains r. 426 selection->setSelection(VisibleSelection(range.get())); 427 else 428 // The original range and r intersect. 429 selection->setSelection(VisibleSelection(range->startPosition(), r->endPosition(), DOWNSTREAM)); 430 } 431 } 432 } 433 434 void DOMSelection::deleteFromDocument() 435 { 436 if (!m_frame) 437 return; 438 439 SelectionController* selection = m_frame->selection(); 440 441 if (selection->isNone()) 442 return; 443 444 if (isCollapsed()) 445 selection->modify(SelectionController::AlterationExtend, DirectionBackward, CharacterGranularity); 446 447 RefPtr<Range> selectedRange = selection->selection().toNormalizedRange(); 448 if (!selectedRange) 449 return; 450 451 ExceptionCode ec = 0; 452 selectedRange->deleteContents(ec); 453 ASSERT(!ec); 454 455 setBaseAndExtent(selectedRange->startContainer(ec), selectedRange->startOffset(ec), selectedRange->startContainer(ec), selectedRange->startOffset(ec), ec); 456 ASSERT(!ec); 457 } 458 459 bool DOMSelection::containsNode(const Node* n, bool allowPartial) const 460 { 461 if (!m_frame) 462 return false; 463 464 SelectionController* selection = m_frame->selection(); 465 466 if (!n || m_frame->document() != n->document() || selection->isNone()) 467 return false; 468 469 ContainerNode* parentNode = n->parentNode(); 470 unsigned nodeIndex = n->nodeIndex(); 471 RefPtr<Range> selectedRange = selection->selection().toNormalizedRange(); 472 473 if (!parentNode) 474 return false; 475 476 ExceptionCode ec = 0; 477 bool nodeFullySelected = Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->startContainer(ec), selectedRange->startOffset(ec), ec) >= 0 && !ec 478 && Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->endContainer(ec), selectedRange->endOffset(ec), ec) <= 0 && !ec; 479 ASSERT(!ec); 480 if (nodeFullySelected) 481 return true; 482 483 bool nodeFullyUnselected = (Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->endContainer(ec), selectedRange->endOffset(ec), ec) > 0 && !ec) 484 || (Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->startContainer(ec), selectedRange->startOffset(ec), ec) < 0 && !ec); 485 ASSERT(!ec); 486 if (nodeFullyUnselected) 487 return false; 488 489 return allowPartial || n->isTextNode(); 490 } 491 492 void DOMSelection::selectAllChildren(Node* n, ExceptionCode& ec) 493 { 494 if (!n) 495 return; 496 497 // This doesn't (and shouldn't) select text node characters. 498 setBaseAndExtent(n, 0, n, n->childNodeCount(), ec); 499 } 500 501 String DOMSelection::toString() 502 { 503 if (!m_frame) 504 return String(); 505 506 return plainText(m_frame->selection()->selection().toNormalizedRange().get()); 507 } 508 509 bool DOMSelection::isValidForPosition(Node* node) const 510 { 511 ASSERT(m_frame); 512 if (!node) 513 return true; 514 return node->document() == m_frame->document(); 515 } 516 517 } // namespace WebCore 518