1 /* 2 * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. 3 * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) 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 * 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 * 14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27 #include "config.h" 28 #include "Editor.h" 29 30 #include "AXObjectCache.h" 31 #include "ApplyStyleCommand.h" 32 #include "CharacterNames.h" 33 #include "CompositionEvent.h" 34 #include "CreateLinkCommand.h" 35 #include "CSSComputedStyleDeclaration.h" 36 #include "CSSMutableStyleDeclaration.h" 37 #include "CSSProperty.h" 38 #include "CSSPropertyNames.h" 39 #include "CSSValueKeywords.h" 40 #include "ClipboardEvent.h" 41 #include "DeleteButtonController.h" 42 #include "DeleteSelectionCommand.h" 43 #include "DocLoader.h" 44 #include "DocumentFragment.h" 45 #include "EditorClient.h" 46 #include "EventHandler.h" 47 #include "EventNames.h" 48 #include "FocusController.h" 49 #include "Frame.h" 50 #include "FrameTree.h" 51 #include "FrameView.h" 52 #include "HTMLInputElement.h" 53 #include "HTMLTextAreaElement.h" 54 #include "HitTestResult.h" 55 #include "IndentOutdentCommand.h" 56 #include "InsertListCommand.h" 57 #include "KeyboardEvent.h" 58 #include "ModifySelectionListLevel.h" 59 #include "Page.h" 60 #include "Pasteboard.h" 61 #include "RemoveFormatCommand.h" 62 #include "RenderBlock.h" 63 #include "RenderPart.h" 64 #include "ReplaceSelectionCommand.h" 65 #include "Sound.h" 66 #include "Text.h" 67 #include "TextIterator.h" 68 #include "TypingCommand.h" 69 #include "htmlediting.h" 70 #include "markup.h" 71 #include "visible_units.h" 72 #include <wtf/UnusedParam.h> 73 74 namespace WebCore { 75 76 using namespace std; 77 using namespace HTMLNames; 78 79 // When an event handler has moved the selection outside of a text control 80 // we should use the target control's selection for this editing operation. 81 VisibleSelection Editor::selectionForCommand(Event* event) 82 { 83 VisibleSelection selection = m_frame->selection()->selection(); 84 if (!event) 85 return selection; 86 // If the target is a text control, and the current selection is outside of its shadow tree, 87 // then use the saved selection for that text control. 88 Node* target = event->target()->toNode(); 89 Node* selectionStart = selection.start().node(); 90 if (target && (!selectionStart || target->shadowAncestorNode() != selectionStart->shadowAncestorNode())) { 91 if (target->hasTagName(inputTag) && static_cast<HTMLInputElement*>(target)->isTextField()) 92 return static_cast<HTMLInputElement*>(target)->selection(); 93 if (target->hasTagName(textareaTag)) 94 return static_cast<HTMLTextAreaElement*>(target)->selection(); 95 } 96 return selection; 97 } 98 99 EditorClient* Editor::client() const 100 { 101 if (Page* page = m_frame->page()) 102 return page->editorClient(); 103 return 0; 104 } 105 106 void Editor::handleKeyboardEvent(KeyboardEvent* event) 107 { 108 if (EditorClient* c = client()) 109 c->handleKeyboardEvent(event); 110 } 111 112 void Editor::handleInputMethodKeydown(KeyboardEvent* event) 113 { 114 if (EditorClient* c = client()) 115 c->handleInputMethodKeydown(event); 116 } 117 118 bool Editor::canEdit() const 119 { 120 return m_frame->selection()->isContentEditable(); 121 } 122 123 bool Editor::canEditRichly() const 124 { 125 return m_frame->selection()->isContentRichlyEditable(); 126 } 127 128 // WinIE uses onbeforecut and onbeforepaste to enables the cut and paste menu items. They 129 // also send onbeforecopy, apparently for symmetry, but it doesn't affect the menu items. 130 // We need to use onbeforecopy as a real menu enabler because we allow elements that are not 131 // normally selectable to implement copy/paste (like divs, or a document body). 132 133 bool Editor::canDHTMLCut() 134 { 135 return !m_frame->selection()->isInPasswordField() && !dispatchCPPEvent(eventNames().beforecutEvent, ClipboardNumb); 136 } 137 138 bool Editor::canDHTMLCopy() 139 { 140 return !m_frame->selection()->isInPasswordField() && !dispatchCPPEvent(eventNames().beforecopyEvent, ClipboardNumb); 141 } 142 143 bool Editor::canDHTMLPaste() 144 { 145 return !dispatchCPPEvent(eventNames().beforepasteEvent, ClipboardNumb); 146 } 147 148 bool Editor::canCut() const 149 { 150 return canCopy() && canDelete(); 151 } 152 153 static HTMLImageElement* imageElementFromImageDocument(Document* document) 154 { 155 if (!document) 156 return 0; 157 if (!document->isImageDocument()) 158 return 0; 159 160 HTMLElement* body = document->body(); 161 if (!body) 162 return 0; 163 164 Node* node = body->firstChild(); 165 if (!node) 166 return 0; 167 if (!node->hasTagName(imgTag)) 168 return 0; 169 return static_cast<HTMLImageElement*>(node); 170 } 171 172 bool Editor::canCopy() const 173 { 174 if (imageElementFromImageDocument(m_frame->document())) 175 return true; 176 SelectionController* selection = m_frame->selection(); 177 return selection->isRange() && !selection->isInPasswordField(); 178 } 179 180 bool Editor::canPaste() const 181 { 182 return canEdit(); 183 } 184 185 bool Editor::canDelete() const 186 { 187 SelectionController* selection = m_frame->selection(); 188 return selection->isRange() && selection->isContentEditable(); 189 } 190 191 bool Editor::canDeleteRange(Range* range) const 192 { 193 ExceptionCode ec = 0; 194 Node* startContainer = range->startContainer(ec); 195 Node* endContainer = range->endContainer(ec); 196 if (!startContainer || !endContainer) 197 return false; 198 199 if (!startContainer->isContentEditable() || !endContainer->isContentEditable()) 200 return false; 201 202 if (range->collapsed(ec)) { 203 VisiblePosition start(startContainer, range->startOffset(ec), DOWNSTREAM); 204 VisiblePosition previous = start.previous(); 205 // FIXME: We sometimes allow deletions at the start of editable roots, like when the caret is in an empty list item. 206 if (previous.isNull() || previous.deepEquivalent().node()->rootEditableElement() != startContainer->rootEditableElement()) 207 return false; 208 } 209 return true; 210 } 211 212 bool Editor::smartInsertDeleteEnabled() 213 { 214 return client() && client()->smartInsertDeleteEnabled(); 215 } 216 217 bool Editor::canSmartCopyOrDelete() 218 { 219 return client() && client()->smartInsertDeleteEnabled() && m_frame->selectionGranularity() == WordGranularity; 220 } 221 222 bool Editor::isSelectTrailingWhitespaceEnabled() 223 { 224 return client() && client()->isSelectTrailingWhitespaceEnabled(); 225 } 226 227 bool Editor::deleteWithDirection(SelectionController::EDirection direction, TextGranularity granularity, bool killRing, bool isTypingAction) 228 { 229 if (!canEdit()) 230 return false; 231 232 if (m_frame->selection()->isRange()) { 233 if (isTypingAction) { 234 TypingCommand::deleteKeyPressed(m_frame->document(), canSmartCopyOrDelete(), granularity); 235 revealSelectionAfterEditingOperation(); 236 } else { 237 if (killRing) 238 addToKillRing(selectedRange().get(), false); 239 deleteSelectionWithSmartDelete(canSmartCopyOrDelete()); 240 // Implicitly calls revealSelectionAfterEditingOperation(). 241 } 242 } else { 243 switch (direction) { 244 case SelectionController::FORWARD: 245 case SelectionController::RIGHT: 246 TypingCommand::forwardDeleteKeyPressed(m_frame->document(), canSmartCopyOrDelete(), granularity, killRing); 247 break; 248 case SelectionController::BACKWARD: 249 case SelectionController::LEFT: 250 TypingCommand::deleteKeyPressed(m_frame->document(), canSmartCopyOrDelete(), granularity, killRing); 251 break; 252 } 253 revealSelectionAfterEditingOperation(); 254 } 255 256 // FIXME: We should to move this down into deleteKeyPressed. 257 // clear the "start new kill ring sequence" setting, because it was set to true 258 // when the selection was updated by deleting the range 259 if (killRing) 260 setStartNewKillRingSequence(false); 261 262 return true; 263 } 264 265 void Editor::deleteSelectionWithSmartDelete(bool smartDelete) 266 { 267 if (m_frame->selection()->isNone()) 268 return; 269 270 applyCommand(DeleteSelectionCommand::create(m_frame->document(), smartDelete)); 271 } 272 273 void Editor::pasteAsPlainTextWithPasteboard(Pasteboard* pasteboard) 274 { 275 String text = pasteboard->plainText(m_frame); 276 if (client() && client()->shouldInsertText(text, selectedRange().get(), EditorInsertActionPasted)) 277 replaceSelectionWithText(text, false, canSmartReplaceWithPasteboard(pasteboard)); 278 } 279 280 void Editor::pasteWithPasteboard(Pasteboard* pasteboard, bool allowPlainText) 281 { 282 RefPtr<Range> range = selectedRange(); 283 bool chosePlainText; 284 RefPtr<DocumentFragment> fragment = pasteboard->documentFragment(m_frame, range, allowPlainText, chosePlainText); 285 if (fragment && shouldInsertFragment(fragment, range, EditorInsertActionPasted)) 286 replaceSelectionWithFragment(fragment, false, canSmartReplaceWithPasteboard(pasteboard), chosePlainText); 287 } 288 289 bool Editor::canSmartReplaceWithPasteboard(Pasteboard* pasteboard) 290 { 291 return client() && client()->smartInsertDeleteEnabled() && pasteboard->canSmartReplace(); 292 } 293 294 bool Editor::shouldInsertFragment(PassRefPtr<DocumentFragment> fragment, PassRefPtr<Range> replacingDOMRange, EditorInsertAction givenAction) 295 { 296 if (!client()) 297 return false; 298 299 Node* child = fragment->firstChild(); 300 if (child && fragment->lastChild() == child && child->isCharacterDataNode()) 301 return client()->shouldInsertText(static_cast<CharacterData*>(child)->data(), replacingDOMRange.get(), givenAction); 302 303 return client()->shouldInsertNode(fragment.get(), replacingDOMRange.get(), givenAction); 304 } 305 306 void Editor::replaceSelectionWithFragment(PassRefPtr<DocumentFragment> fragment, bool selectReplacement, bool smartReplace, bool matchStyle) 307 { 308 if (m_frame->selection()->isNone() || !fragment) 309 return; 310 311 applyCommand(ReplaceSelectionCommand::create(m_frame->document(), fragment, selectReplacement, smartReplace, matchStyle)); 312 revealSelectionAfterEditingOperation(); 313 } 314 315 void Editor::replaceSelectionWithText(const String& text, bool selectReplacement, bool smartReplace) 316 { 317 replaceSelectionWithFragment(createFragmentFromText(selectedRange().get(), text), selectReplacement, smartReplace, true); 318 } 319 320 PassRefPtr<Range> Editor::selectedRange() 321 { 322 if (!m_frame) 323 return 0; 324 return m_frame->selection()->toNormalizedRange(); 325 } 326 327 bool Editor::shouldDeleteRange(Range* range) const 328 { 329 ExceptionCode ec; 330 if (!range || range->collapsed(ec)) 331 return false; 332 333 if (!canDeleteRange(range)) 334 return false; 335 336 return client() && client()->shouldDeleteRange(range); 337 } 338 339 bool Editor::tryDHTMLCopy() 340 { 341 if (m_frame->selection()->isInPasswordField()) 342 return false; 343 344 if (canCopy()) 345 // Must be done before oncopy adds types and data to the pboard, 346 // also done for security, as it erases data from the last copy/paste. 347 Pasteboard::generalPasteboard()->clear(); 348 349 return !dispatchCPPEvent(eventNames().copyEvent, ClipboardWritable); 350 } 351 352 bool Editor::tryDHTMLCut() 353 { 354 if (m_frame->selection()->isInPasswordField()) 355 return false; 356 357 if (canCut()) 358 // Must be done before oncut adds types and data to the pboard, 359 // also done for security, as it erases data from the last copy/paste. 360 Pasteboard::generalPasteboard()->clear(); 361 362 return !dispatchCPPEvent(eventNames().cutEvent, ClipboardWritable); 363 } 364 365 bool Editor::tryDHTMLPaste() 366 { 367 return !dispatchCPPEvent(eventNames().pasteEvent, ClipboardReadable); 368 } 369 370 void Editor::writeSelectionToPasteboard(Pasteboard* pasteboard) 371 { 372 pasteboard->writeSelection(selectedRange().get(), canSmartCopyOrDelete(), m_frame); 373 } 374 375 bool Editor::shouldInsertText(const String& text, Range* range, EditorInsertAction action) const 376 { 377 return client() && client()->shouldInsertText(text, range, action); 378 } 379 380 bool Editor::shouldShowDeleteInterface(HTMLElement* element) const 381 { 382 return client() && client()->shouldShowDeleteInterface(element); 383 } 384 385 void Editor::respondToChangedSelection(const VisibleSelection& oldSelection) 386 { 387 if (client()) 388 client()->respondToChangedSelection(); 389 m_deleteButtonController->respondToChangedSelection(oldSelection); 390 } 391 392 void Editor::respondToChangedContents(const VisibleSelection& endingSelection) 393 { 394 if (AXObjectCache::accessibilityEnabled()) { 395 Node* node = endingSelection.start().node(); 396 if (node) 397 m_frame->document()->axObjectCache()->postNotification(node->renderer(), AXObjectCache::AXValueChanged, false); 398 } 399 400 if (client()) 401 client()->respondToChangedContents(); 402 } 403 404 const SimpleFontData* Editor::fontForSelection(bool& hasMultipleFonts) const 405 { 406 #if !PLATFORM(QT) 407 hasMultipleFonts = false; 408 409 if (!m_frame->selection()->isRange()) { 410 Node* nodeToRemove; 411 RenderStyle* style = m_frame->styleForSelectionStart(nodeToRemove); // sets nodeToRemove 412 413 const SimpleFontData* result = 0; 414 if (style) 415 result = style->font().primaryFont(); 416 417 if (nodeToRemove) { 418 ExceptionCode ec; 419 nodeToRemove->remove(ec); 420 ASSERT(ec == 0); 421 } 422 423 return result; 424 } 425 426 const SimpleFontData* font = 0; 427 428 RefPtr<Range> range = m_frame->selection()->toNormalizedRange(); 429 Node* startNode = range->editingStartPosition().node(); 430 if (startNode) { 431 Node* pastEnd = range->pastLastNode(); 432 // In the loop below, n should eventually match pastEnd and not become nil, but we've seen at least one 433 // unreproducible case where this didn't happen, so check for nil also. 434 for (Node* n = startNode; n && n != pastEnd; n = n->traverseNextNode()) { 435 RenderObject *renderer = n->renderer(); 436 if (!renderer) 437 continue; 438 // FIXME: Are there any node types that have renderers, but that we should be skipping? 439 const SimpleFontData* f = renderer->style()->font().primaryFont(); 440 if (!font) 441 font = f; 442 else if (font != f) { 443 hasMultipleFonts = true; 444 break; 445 } 446 } 447 } 448 449 return font; 450 #else 451 return 0; 452 #endif 453 } 454 455 WritingDirection Editor::textDirectionForSelection(bool& hasNestedOrMultipleEmbeddings) const 456 { 457 hasNestedOrMultipleEmbeddings = true; 458 459 if (m_frame->selection()->isNone()) 460 return NaturalWritingDirection; 461 462 Position pos = m_frame->selection()->selection().start().downstream(); 463 464 Node* node = pos.node(); 465 if (!node) 466 return NaturalWritingDirection; 467 468 Position end; 469 if (m_frame->selection()->isRange()) { 470 end = m_frame->selection()->selection().end().upstream(); 471 472 Node* pastLast = Range::create(m_frame->document(), rangeCompliantEquivalent(pos), rangeCompliantEquivalent(end))->pastLastNode(); 473 for (Node* n = node; n && n != pastLast; n = n->traverseNextNode()) { 474 if (!n->isStyledElement()) 475 continue; 476 477 RefPtr<CSSComputedStyleDeclaration> style = computedStyle(n); 478 RefPtr<CSSValue> unicodeBidi = style->getPropertyCSSValue(CSSPropertyUnicodeBidi); 479 if (!unicodeBidi) 480 continue; 481 482 ASSERT(unicodeBidi->isPrimitiveValue()); 483 int unicodeBidiValue = static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent(); 484 if (unicodeBidiValue == CSSValueEmbed || unicodeBidiValue == CSSValueBidiOverride) 485 return NaturalWritingDirection; 486 } 487 } 488 489 if (m_frame->selection()->isCaret()) { 490 if (CSSMutableStyleDeclaration *typingStyle = m_frame->typingStyle()) { 491 RefPtr<CSSValue> unicodeBidi = typingStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi); 492 if (unicodeBidi) { 493 ASSERT(unicodeBidi->isPrimitiveValue()); 494 int unicodeBidiValue = static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent(); 495 if (unicodeBidiValue == CSSValueEmbed) { 496 RefPtr<CSSValue> direction = typingStyle->getPropertyCSSValue(CSSPropertyDirection); 497 ASSERT(!direction || direction->isPrimitiveValue()); 498 if (direction) { 499 hasNestedOrMultipleEmbeddings = false; 500 return static_cast<CSSPrimitiveValue*>(direction.get())->getIdent() == CSSValueLtr ? LeftToRightWritingDirection : RightToLeftWritingDirection; 501 } 502 } else if (unicodeBidiValue == CSSValueNormal) { 503 hasNestedOrMultipleEmbeddings = false; 504 return NaturalWritingDirection; 505 } 506 } 507 } 508 node = m_frame->selection()->selection().visibleStart().deepEquivalent().node(); 509 } 510 511 // The selection is either a caret with no typing attributes or a range in which no embedding is added, so just use the start position 512 // to decide. 513 Node* block = enclosingBlock(node); 514 WritingDirection foundDirection = NaturalWritingDirection; 515 516 for (; node != block; node = node->parent()) { 517 if (!node->isStyledElement()) 518 continue; 519 520 RefPtr<CSSComputedStyleDeclaration> style = computedStyle(node); 521 RefPtr<CSSValue> unicodeBidi = style->getPropertyCSSValue(CSSPropertyUnicodeBidi); 522 if (!unicodeBidi) 523 continue; 524 525 ASSERT(unicodeBidi->isPrimitiveValue()); 526 int unicodeBidiValue = static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent(); 527 if (unicodeBidiValue == CSSValueNormal) 528 continue; 529 530 if (unicodeBidiValue == CSSValueBidiOverride) 531 return NaturalWritingDirection; 532 533 ASSERT(unicodeBidiValue == CSSValueEmbed); 534 RefPtr<CSSValue> direction = style->getPropertyCSSValue(CSSPropertyDirection); 535 if (!direction) 536 continue; 537 538 ASSERT(direction->isPrimitiveValue()); 539 int directionValue = static_cast<CSSPrimitiveValue*>(direction.get())->getIdent(); 540 if (directionValue != CSSValueLtr && directionValue != CSSValueRtl) 541 continue; 542 543 if (foundDirection != NaturalWritingDirection) 544 return NaturalWritingDirection; 545 546 // In the range case, make sure that the embedding element persists until the end of the range. 547 if (m_frame->selection()->isRange() && !end.node()->isDescendantOf(node)) 548 return NaturalWritingDirection; 549 550 foundDirection = directionValue == CSSValueLtr ? LeftToRightWritingDirection : RightToLeftWritingDirection; 551 } 552 hasNestedOrMultipleEmbeddings = false; 553 return foundDirection; 554 } 555 556 bool Editor::hasBidiSelection() const 557 { 558 if (m_frame->selection()->isNone()) 559 return false; 560 561 Node* startNode; 562 if (m_frame->selection()->isRange()) { 563 startNode = m_frame->selection()->selection().start().downstream().node(); 564 Node* endNode = m_frame->selection()->selection().end().upstream().node(); 565 if (enclosingBlock(startNode) != enclosingBlock(endNode)) 566 return false; 567 } else 568 startNode = m_frame->selection()->selection().visibleStart().deepEquivalent().node(); 569 570 RenderObject* renderer = startNode->renderer(); 571 while (renderer && !renderer->isRenderBlock()) 572 renderer = renderer->parent(); 573 574 if (!renderer) 575 return false; 576 577 RenderStyle* style = renderer->style(); 578 if (style->direction() == RTL) 579 return true; 580 581 return toRenderBlock(renderer)->containsNonZeroBidiLevel(); 582 } 583 584 TriState Editor::selectionUnorderedListState() const 585 { 586 if (m_frame->selection()->isCaret()) { 587 if (enclosingNodeWithTag(m_frame->selection()->selection().start(), ulTag)) 588 return TrueTriState; 589 } else if (m_frame->selection()->isRange()) { 590 Node* startNode = enclosingNodeWithTag(m_frame->selection()->selection().start(), ulTag); 591 Node* endNode = enclosingNodeWithTag(m_frame->selection()->selection().end(), ulTag); 592 if (startNode && endNode && startNode == endNode) 593 return TrueTriState; 594 } 595 596 return FalseTriState; 597 } 598 599 TriState Editor::selectionOrderedListState() const 600 { 601 if (m_frame->selection()->isCaret()) { 602 if (enclosingNodeWithTag(m_frame->selection()->selection().start(), olTag)) 603 return TrueTriState; 604 } else if (m_frame->selection()->isRange()) { 605 Node* startNode = enclosingNodeWithTag(m_frame->selection()->selection().start(), olTag); 606 Node* endNode = enclosingNodeWithTag(m_frame->selection()->selection().end(), olTag); 607 if (startNode && endNode && startNode == endNode) 608 return TrueTriState; 609 } 610 611 return FalseTriState; 612 } 613 614 PassRefPtr<Node> Editor::insertOrderedList() 615 { 616 if (!canEditRichly()) 617 return 0; 618 619 RefPtr<Node> newList = InsertListCommand::insertList(m_frame->document(), InsertListCommand::OrderedList); 620 revealSelectionAfterEditingOperation(); 621 return newList; 622 } 623 624 PassRefPtr<Node> Editor::insertUnorderedList() 625 { 626 if (!canEditRichly()) 627 return 0; 628 629 RefPtr<Node> newList = InsertListCommand::insertList(m_frame->document(), InsertListCommand::UnorderedList); 630 revealSelectionAfterEditingOperation(); 631 return newList; 632 } 633 634 bool Editor::canIncreaseSelectionListLevel() 635 { 636 return canEditRichly() && IncreaseSelectionListLevelCommand::canIncreaseSelectionListLevel(m_frame->document()); 637 } 638 639 bool Editor::canDecreaseSelectionListLevel() 640 { 641 return canEditRichly() && DecreaseSelectionListLevelCommand::canDecreaseSelectionListLevel(m_frame->document()); 642 } 643 644 PassRefPtr<Node> Editor::increaseSelectionListLevel() 645 { 646 if (!canEditRichly() || m_frame->selection()->isNone()) 647 return 0; 648 649 RefPtr<Node> newList = IncreaseSelectionListLevelCommand::increaseSelectionListLevel(m_frame->document()); 650 revealSelectionAfterEditingOperation(); 651 return newList; 652 } 653 654 PassRefPtr<Node> Editor::increaseSelectionListLevelOrdered() 655 { 656 if (!canEditRichly() || m_frame->selection()->isNone()) 657 return 0; 658 659 RefPtr<Node> newList = IncreaseSelectionListLevelCommand::increaseSelectionListLevelOrdered(m_frame->document()); 660 revealSelectionAfterEditingOperation(); 661 return newList.release(); 662 } 663 664 PassRefPtr<Node> Editor::increaseSelectionListLevelUnordered() 665 { 666 if (!canEditRichly() || m_frame->selection()->isNone()) 667 return 0; 668 669 RefPtr<Node> newList = IncreaseSelectionListLevelCommand::increaseSelectionListLevelUnordered(m_frame->document()); 670 revealSelectionAfterEditingOperation(); 671 return newList.release(); 672 } 673 674 void Editor::decreaseSelectionListLevel() 675 { 676 if (!canEditRichly() || m_frame->selection()->isNone()) 677 return; 678 679 DecreaseSelectionListLevelCommand::decreaseSelectionListLevel(m_frame->document()); 680 revealSelectionAfterEditingOperation(); 681 } 682 683 void Editor::removeFormattingAndStyle() 684 { 685 applyCommand(RemoveFormatCommand::create(m_frame->document())); 686 } 687 688 void Editor::clearLastEditCommand() 689 { 690 m_lastEditCommand.clear(); 691 } 692 693 // Returns whether caller should continue with "the default processing", which is the same as 694 // the event handler NOT setting the return value to false 695 bool Editor::dispatchCPPEvent(const AtomicString &eventType, ClipboardAccessPolicy policy) 696 { 697 Node* target = m_frame->selection()->start().element(); 698 if (!target) 699 target = m_frame->document()->body(); 700 if (!target) 701 return true; 702 target = target->shadowAncestorNode(); 703 704 RefPtr<Clipboard> clipboard = newGeneralClipboard(policy); 705 706 ExceptionCode ec = 0; 707 RefPtr<Event> evt = ClipboardEvent::create(eventType, true, true, clipboard); 708 target->dispatchEvent(evt, ec); 709 bool noDefaultProcessing = evt->defaultPrevented(); 710 711 // invalidate clipboard here for security 712 clipboard->setAccessPolicy(ClipboardNumb); 713 714 return !noDefaultProcessing; 715 } 716 717 void Editor::applyStyle(CSSStyleDeclaration* style, EditAction editingAction) 718 { 719 switch (m_frame->selection()->selectionType()) { 720 case VisibleSelection::NoSelection: 721 // do nothing 722 break; 723 case VisibleSelection::CaretSelection: 724 m_frame->computeAndSetTypingStyle(style, editingAction); 725 break; 726 case VisibleSelection::RangeSelection: 727 if (style) 728 applyCommand(ApplyStyleCommand::create(m_frame->document(), style, editingAction)); 729 break; 730 } 731 } 732 733 bool Editor::shouldApplyStyle(CSSStyleDeclaration* style, Range* range) 734 { 735 return client()->shouldApplyStyle(style, range); 736 } 737 738 void Editor::applyParagraphStyle(CSSStyleDeclaration* style, EditAction editingAction) 739 { 740 switch (m_frame->selection()->selectionType()) { 741 case VisibleSelection::NoSelection: 742 // do nothing 743 break; 744 case VisibleSelection::CaretSelection: 745 case VisibleSelection::RangeSelection: 746 if (style) 747 applyCommand(ApplyStyleCommand::create(m_frame->document(), style, editingAction, ApplyStyleCommand::ForceBlockProperties)); 748 break; 749 } 750 } 751 752 void Editor::applyStyleToSelection(CSSStyleDeclaration* style, EditAction editingAction) 753 { 754 if (!style || style->length() == 0 || !canEditRichly()) 755 return; 756 757 if (client() && client()->shouldApplyStyle(style, m_frame->selection()->toNormalizedRange().get())) 758 applyStyle(style, editingAction); 759 } 760 761 void Editor::applyParagraphStyleToSelection(CSSStyleDeclaration* style, EditAction editingAction) 762 { 763 if (!style || style->length() == 0 || !canEditRichly()) 764 return; 765 766 if (client() && client()->shouldApplyStyle(style, m_frame->selection()->toNormalizedRange().get())) 767 applyParagraphStyle(style, editingAction); 768 } 769 770 bool Editor::clientIsEditable() const 771 { 772 return client() && client()->isEditable(); 773 } 774 775 // CSS properties that only has a visual difference when applied to text. 776 static const int textOnlyProperties[] = { 777 CSSPropertyTextDecoration, 778 CSSPropertyWebkitTextDecorationsInEffect, 779 CSSPropertyFontStyle, 780 CSSPropertyFontWeight, 781 CSSPropertyColor, 782 }; 783 784 static TriState triStateOfStyleInComputedStyle(CSSStyleDeclaration* desiredStyle, CSSComputedStyleDeclaration* computedStyle, bool ignoreTextOnlyProperties = false) 785 { 786 RefPtr<CSSMutableStyleDeclaration> diff = getPropertiesNotInComputedStyle(desiredStyle, computedStyle); 787 788 if (ignoreTextOnlyProperties) 789 diff->removePropertiesInSet(textOnlyProperties, sizeof(textOnlyProperties)/sizeof(textOnlyProperties[0])); 790 791 if (!diff->length()) 792 return TrueTriState; 793 else if (diff->length() == desiredStyle->length()) 794 return FalseTriState; 795 return MixedTriState; 796 } 797 798 bool Editor::selectionStartHasStyle(CSSStyleDeclaration* style) const 799 { 800 Node* nodeToRemove; 801 RefPtr<CSSComputedStyleDeclaration> selectionStyle = m_frame->selectionComputedStyle(nodeToRemove); 802 if (!selectionStyle) 803 return false; 804 TriState state = triStateOfStyleInComputedStyle(style, selectionStyle.get()); 805 if (nodeToRemove) { 806 ExceptionCode ec = 0; 807 nodeToRemove->remove(ec); 808 ASSERT(ec == 0); 809 } 810 return state == TrueTriState; 811 } 812 813 TriState Editor::selectionHasStyle(CSSStyleDeclaration* style) const 814 { 815 TriState state = FalseTriState; 816 817 if (!m_frame->selection()->isRange()) { 818 Node* nodeToRemove; 819 RefPtr<CSSComputedStyleDeclaration> selectionStyle = m_frame->selectionComputedStyle(nodeToRemove); 820 if (!selectionStyle) 821 return FalseTriState; 822 state = triStateOfStyleInComputedStyle(style, selectionStyle.get()); 823 if (nodeToRemove) { 824 ExceptionCode ec = 0; 825 nodeToRemove->remove(ec); 826 ASSERT(ec == 0); 827 } 828 } else { 829 for (Node* node = m_frame->selection()->start().node(); node; node = node->traverseNextNode()) { 830 RefPtr<CSSComputedStyleDeclaration> nodeStyle = computedStyle(node); 831 if (nodeStyle) { 832 TriState nodeState = triStateOfStyleInComputedStyle(style, nodeStyle.get(), !node->isTextNode()); 833 if (node == m_frame->selection()->start().node()) 834 state = nodeState; 835 else if (state != nodeState && node->isTextNode()) { 836 state = MixedTriState; 837 break; 838 } 839 } 840 if (node == m_frame->selection()->end().node()) 841 break; 842 } 843 } 844 845 return state; 846 } 847 void Editor::indent() 848 { 849 applyCommand(IndentOutdentCommand::create(m_frame->document(), IndentOutdentCommand::Indent)); 850 } 851 852 void Editor::outdent() 853 { 854 applyCommand(IndentOutdentCommand::create(m_frame->document(), IndentOutdentCommand::Outdent)); 855 } 856 857 static void dispatchEditableContentChangedEvents(const EditCommand& command) 858 { 859 Element* startRoot = command.startingRootEditableElement(); 860 Element* endRoot = command.endingRootEditableElement(); 861 ExceptionCode ec; 862 if (startRoot) 863 startRoot->dispatchEvent(Event::create(eventNames().webkitEditableContentChangedEvent, false, false), ec); 864 if (endRoot && endRoot != startRoot) 865 endRoot->dispatchEvent(Event::create(eventNames().webkitEditableContentChangedEvent, false, false), ec); 866 } 867 868 void Editor::appliedEditing(PassRefPtr<EditCommand> cmd) 869 { 870 dispatchEditableContentChangedEvents(*cmd); 871 872 VisibleSelection newSelection(cmd->endingSelection()); 873 // Don't clear the typing style with this selection change. We do those things elsewhere if necessary. 874 changeSelectionAfterCommand(newSelection, false, false, cmd.get()); 875 876 if (!cmd->preservesTypingStyle()) 877 m_frame->setTypingStyle(0); 878 879 // Command will be equal to last edit command only in the case of typing 880 if (m_lastEditCommand.get() == cmd) 881 ASSERT(cmd->isTypingCommand()); 882 else { 883 // Only register a new undo command if the command passed in is 884 // different from the last command 885 m_lastEditCommand = cmd; 886 if (client()) 887 client()->registerCommandForUndo(m_lastEditCommand); 888 } 889 respondToChangedContents(newSelection); 890 } 891 892 void Editor::unappliedEditing(PassRefPtr<EditCommand> cmd) 893 { 894 dispatchEditableContentChangedEvents(*cmd); 895 896 VisibleSelection newSelection(cmd->startingSelection()); 897 changeSelectionAfterCommand(newSelection, true, true, cmd.get()); 898 899 m_lastEditCommand = 0; 900 if (client()) 901 client()->registerCommandForRedo(cmd); 902 respondToChangedContents(newSelection); 903 } 904 905 void Editor::reappliedEditing(PassRefPtr<EditCommand> cmd) 906 { 907 dispatchEditableContentChangedEvents(*cmd); 908 909 VisibleSelection newSelection(cmd->endingSelection()); 910 changeSelectionAfterCommand(newSelection, true, true, cmd.get()); 911 912 m_lastEditCommand = 0; 913 if (client()) 914 client()->registerCommandForUndo(cmd); 915 respondToChangedContents(newSelection); 916 } 917 918 Editor::Editor(Frame* frame) 919 : m_frame(frame) 920 , m_deleteButtonController(new DeleteButtonController(frame)) 921 , m_ignoreCompositionSelectionChange(false) 922 , m_shouldStartNewKillRingSequence(false) 923 // This is off by default, since most editors want this behavior (this matches IE but not FF). 924 , m_shouldStyleWithCSS(false) 925 { 926 } 927 928 Editor::~Editor() 929 { 930 } 931 932 void Editor::clear() 933 { 934 m_compositionNode = 0; 935 m_customCompositionUnderlines.clear(); 936 m_shouldStyleWithCSS = false; 937 } 938 939 bool Editor::insertText(const String& text, Event* triggeringEvent) 940 { 941 return m_frame->eventHandler()->handleTextInputEvent(text, triggeringEvent); 942 } 943 944 bool Editor::insertTextWithoutSendingTextEvent(const String& text, bool selectInsertedText, Event* triggeringEvent) 945 { 946 if (text.isEmpty()) 947 return false; 948 949 VisibleSelection selection = selectionForCommand(triggeringEvent); 950 if (!selection.isContentEditable()) 951 return false; 952 RefPtr<Range> range = selection.toNormalizedRange(); 953 954 if (!shouldInsertText(text, range.get(), EditorInsertActionTyped)) 955 return true; 956 957 // Get the selection to use for the event that triggered this insertText. 958 // If the event handler changed the selection, we may want to use a different selection 959 // that is contained in the event target. 960 selection = selectionForCommand(triggeringEvent); 961 if (selection.isContentEditable()) { 962 if (Node* selectionStart = selection.start().node()) { 963 RefPtr<Document> document = selectionStart->document(); 964 965 // Insert the text 966 TypingCommand::insertText(document.get(), text, selection, selectInsertedText); 967 968 // Reveal the current selection 969 if (Frame* editedFrame = document->frame()) 970 if (Page* page = editedFrame->page()) 971 page->focusController()->focusedOrMainFrame()->revealSelection(ScrollAlignment::alignToEdgeIfNeeded); 972 } 973 } 974 975 return true; 976 } 977 978 bool Editor::insertLineBreak() 979 { 980 if (!canEdit()) 981 return false; 982 983 if (!shouldInsertText("\n", m_frame->selection()->toNormalizedRange().get(), EditorInsertActionTyped)) 984 return true; 985 986 TypingCommand::insertLineBreak(m_frame->document()); 987 revealSelectionAfterEditingOperation(); 988 return true; 989 } 990 991 bool Editor::insertParagraphSeparator() 992 { 993 if (!canEdit()) 994 return false; 995 996 if (!canEditRichly()) 997 return insertLineBreak(); 998 999 if (!shouldInsertText("\n", m_frame->selection()->toNormalizedRange().get(), EditorInsertActionTyped)) 1000 return true; 1001 1002 TypingCommand::insertParagraphSeparator(m_frame->document()); 1003 revealSelectionAfterEditingOperation(); 1004 return true; 1005 } 1006 1007 void Editor::cut() 1008 { 1009 if (tryDHTMLCut()) 1010 return; // DHTML did the whole operation 1011 if (!canCut()) { 1012 systemBeep(); 1013 return; 1014 } 1015 RefPtr<Range> selection = selectedRange(); 1016 if (shouldDeleteRange(selection.get())) { 1017 if (isNodeInTextFormControl(m_frame->selection()->start().node())) 1018 Pasteboard::generalPasteboard()->writePlainText(m_frame->selectedText()); 1019 else 1020 Pasteboard::generalPasteboard()->writeSelection(selection.get(), canSmartCopyOrDelete(), m_frame); 1021 didWriteSelectionToPasteboard(); 1022 deleteSelectionWithSmartDelete(canSmartCopyOrDelete()); 1023 } 1024 } 1025 1026 void Editor::copy() 1027 { 1028 if (tryDHTMLCopy()) 1029 return; // DHTML did the whole operation 1030 if (!canCopy()) { 1031 systemBeep(); 1032 return; 1033 } 1034 1035 if (isNodeInTextFormControl(m_frame->selection()->start().node())) 1036 Pasteboard::generalPasteboard()->writePlainText(m_frame->selectedText()); 1037 else { 1038 Document* document = m_frame->document(); 1039 if (HTMLImageElement* imageElement = imageElementFromImageDocument(document)) 1040 Pasteboard::generalPasteboard()->writeImage(imageElement, document->url(), document->title()); 1041 else 1042 Pasteboard::generalPasteboard()->writeSelection(selectedRange().get(), canSmartCopyOrDelete(), m_frame); 1043 } 1044 1045 didWriteSelectionToPasteboard(); 1046 } 1047 1048 #if !PLATFORM(MAC) 1049 1050 void Editor::paste() 1051 { 1052 ASSERT(m_frame->document()); 1053 if (tryDHTMLPaste()) 1054 return; // DHTML did the whole operation 1055 if (!canPaste()) 1056 return; 1057 DocLoader* loader = m_frame->document()->docLoader(); 1058 loader->setAllowStaleResources(true); 1059 if (m_frame->selection()->isContentRichlyEditable()) 1060 pasteWithPasteboard(Pasteboard::generalPasteboard(), true); 1061 else 1062 pasteAsPlainTextWithPasteboard(Pasteboard::generalPasteboard()); 1063 loader->setAllowStaleResources(false); 1064 } 1065 1066 #endif 1067 1068 void Editor::pasteAsPlainText() 1069 { 1070 if (tryDHTMLPaste()) 1071 return; 1072 if (!canPaste()) 1073 return; 1074 pasteAsPlainTextWithPasteboard(Pasteboard::generalPasteboard()); 1075 } 1076 1077 void Editor::performDelete() 1078 { 1079 if (!canDelete()) { 1080 systemBeep(); 1081 return; 1082 } 1083 1084 addToKillRing(selectedRange().get(), false); 1085 deleteSelectionWithSmartDelete(canSmartCopyOrDelete()); 1086 1087 // clear the "start new kill ring sequence" setting, because it was set to true 1088 // when the selection was updated by deleting the range 1089 setStartNewKillRingSequence(false); 1090 } 1091 1092 void Editor::copyURL(const KURL& url, const String& title) 1093 { 1094 Pasteboard::generalPasteboard()->writeURL(url, title, m_frame); 1095 } 1096 1097 void Editor::copyImage(const HitTestResult& result) 1098 { 1099 KURL url = result.absoluteLinkURL(); 1100 if (url.isEmpty()) 1101 url = result.absoluteImageURL(); 1102 1103 Pasteboard::generalPasteboard()->writeImage(result.innerNonSharedNode(), url, result.altDisplayString()); 1104 } 1105 1106 bool Editor::isContinuousSpellCheckingEnabled() 1107 { 1108 return client() && client()->isContinuousSpellCheckingEnabled(); 1109 } 1110 1111 void Editor::toggleContinuousSpellChecking() 1112 { 1113 if (client()) 1114 client()->toggleContinuousSpellChecking(); 1115 } 1116 1117 bool Editor::isGrammarCheckingEnabled() 1118 { 1119 return client() && client()->isGrammarCheckingEnabled(); 1120 } 1121 1122 void Editor::toggleGrammarChecking() 1123 { 1124 if (client()) 1125 client()->toggleGrammarChecking(); 1126 } 1127 1128 int Editor::spellCheckerDocumentTag() 1129 { 1130 return client() ? client()->spellCheckerDocumentTag() : 0; 1131 } 1132 1133 #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 1134 1135 void Editor::uppercaseWord() 1136 { 1137 if (client()) 1138 client()->uppercaseWord(); 1139 } 1140 1141 void Editor::lowercaseWord() 1142 { 1143 if (client()) 1144 client()->lowercaseWord(); 1145 } 1146 1147 void Editor::capitalizeWord() 1148 { 1149 if (client()) 1150 client()->capitalizeWord(); 1151 } 1152 1153 void Editor::showSubstitutionsPanel() 1154 { 1155 if (!client()) { 1156 LOG_ERROR("No NSSpellChecker"); 1157 return; 1158 } 1159 1160 if (client()->substitutionsPanelIsShowing()) { 1161 client()->showSubstitutionsPanel(false); 1162 return; 1163 } 1164 client()->showSubstitutionsPanel(true); 1165 } 1166 1167 bool Editor::substitutionsPanelIsShowing() 1168 { 1169 if (!client()) 1170 return false; 1171 return client()->substitutionsPanelIsShowing(); 1172 } 1173 1174 void Editor::toggleSmartInsertDelete() 1175 { 1176 if (client()) 1177 client()->toggleSmartInsertDelete(); 1178 } 1179 1180 bool Editor::isAutomaticQuoteSubstitutionEnabled() 1181 { 1182 return client() && client()->isAutomaticQuoteSubstitutionEnabled(); 1183 } 1184 1185 void Editor::toggleAutomaticQuoteSubstitution() 1186 { 1187 if (client()) 1188 client()->toggleAutomaticQuoteSubstitution(); 1189 } 1190 1191 bool Editor::isAutomaticLinkDetectionEnabled() 1192 { 1193 return client() && client()->isAutomaticLinkDetectionEnabled(); 1194 } 1195 1196 void Editor::toggleAutomaticLinkDetection() 1197 { 1198 if (client()) 1199 client()->toggleAutomaticLinkDetection(); 1200 } 1201 1202 bool Editor::isAutomaticDashSubstitutionEnabled() 1203 { 1204 return client() && client()->isAutomaticDashSubstitutionEnabled(); 1205 } 1206 1207 void Editor::toggleAutomaticDashSubstitution() 1208 { 1209 if (client()) 1210 client()->toggleAutomaticDashSubstitution(); 1211 } 1212 1213 bool Editor::isAutomaticTextReplacementEnabled() 1214 { 1215 return client() && client()->isAutomaticTextReplacementEnabled(); 1216 } 1217 1218 void Editor::toggleAutomaticTextReplacement() 1219 { 1220 if (client()) 1221 client()->toggleAutomaticTextReplacement(); 1222 } 1223 1224 bool Editor::isAutomaticSpellingCorrectionEnabled() 1225 { 1226 return client() && client()->isAutomaticSpellingCorrectionEnabled(); 1227 } 1228 1229 void Editor::toggleAutomaticSpellingCorrection() 1230 { 1231 if (client()) 1232 client()->toggleAutomaticSpellingCorrection(); 1233 } 1234 1235 #endif 1236 1237 bool Editor::shouldEndEditing(Range* range) 1238 { 1239 return client() && client()->shouldEndEditing(range); 1240 } 1241 1242 bool Editor::shouldBeginEditing(Range* range) 1243 { 1244 return client() && client()->shouldBeginEditing(range); 1245 } 1246 1247 void Editor::clearUndoRedoOperations() 1248 { 1249 if (client()) 1250 client()->clearUndoRedoOperations(); 1251 } 1252 1253 bool Editor::canUndo() 1254 { 1255 return client() && client()->canUndo(); 1256 } 1257 1258 void Editor::undo() 1259 { 1260 if (client()) 1261 client()->undo(); 1262 } 1263 1264 bool Editor::canRedo() 1265 { 1266 return client() && client()->canRedo(); 1267 } 1268 1269 void Editor::redo() 1270 { 1271 if (client()) 1272 client()->redo(); 1273 } 1274 1275 void Editor::didBeginEditing() 1276 { 1277 if (client()) 1278 client()->didBeginEditing(); 1279 } 1280 1281 void Editor::didEndEditing() 1282 { 1283 if (client()) 1284 client()->didEndEditing(); 1285 } 1286 1287 void Editor::didWriteSelectionToPasteboard() 1288 { 1289 if (client()) 1290 client()->didWriteSelectionToPasteboard(); 1291 } 1292 1293 void Editor::toggleBold() 1294 { 1295 command("ToggleBold").execute(); 1296 } 1297 1298 void Editor::toggleUnderline() 1299 { 1300 command("ToggleUnderline").execute(); 1301 } 1302 1303 void Editor::setBaseWritingDirection(WritingDirection direction) 1304 { 1305 Node* focusedNode = frame()->document()->focusedNode(); 1306 if (focusedNode && (focusedNode->hasTagName(textareaTag) 1307 || (focusedNode->hasTagName(inputTag) && (static_cast<HTMLInputElement*>(focusedNode)->inputType() == HTMLInputElement::TEXT 1308 || static_cast<HTMLInputElement*>(focusedNode)->inputType() == HTMLInputElement::SEARCH)))) { 1309 if (direction == NaturalWritingDirection) 1310 return; 1311 static_cast<HTMLElement*>(focusedNode)->setAttribute(dirAttr, direction == LeftToRightWritingDirection ? "ltr" : "rtl"); 1312 frame()->document()->updateStyleIfNeeded(); 1313 return; 1314 } 1315 1316 RefPtr<CSSMutableStyleDeclaration> style = CSSMutableStyleDeclaration::create(); 1317 style->setProperty(CSSPropertyDirection, direction == LeftToRightWritingDirection ? "ltr" : direction == RightToLeftWritingDirection ? "rtl" : "inherit", false); 1318 applyParagraphStyleToSelection(style.get(), EditActionSetWritingDirection); 1319 } 1320 1321 void Editor::selectComposition() 1322 { 1323 RefPtr<Range> range = compositionRange(); 1324 if (!range) 1325 return; 1326 1327 // The composition can start inside a composed character sequence, so we have to override checks. 1328 // See <http://bugs.webkit.org/show_bug.cgi?id=15781> 1329 VisibleSelection selection; 1330 selection.setWithoutValidation(range->startPosition(), range->endPosition()); 1331 m_frame->selection()->setSelection(selection, false, false); 1332 } 1333 1334 void Editor::confirmComposition() 1335 { 1336 if (!m_compositionNode) 1337 return; 1338 confirmComposition(m_compositionNode->data().substring(m_compositionStart, m_compositionEnd - m_compositionStart), false); 1339 } 1340 1341 void Editor::confirmCompositionWithoutDisturbingSelection() 1342 { 1343 if (!m_compositionNode) 1344 return; 1345 confirmComposition(m_compositionNode->data().substring(m_compositionStart, m_compositionEnd - m_compositionStart), true); 1346 } 1347 1348 void Editor::confirmComposition(const String& text) 1349 { 1350 confirmComposition(text, false); 1351 } 1352 1353 void Editor::confirmComposition(const String& text, bool preserveSelection) 1354 { 1355 setIgnoreCompositionSelectionChange(true); 1356 1357 VisibleSelection oldSelection = m_frame->selection()->selection(); 1358 1359 selectComposition(); 1360 1361 if (m_frame->selection()->isNone()) { 1362 setIgnoreCompositionSelectionChange(false); 1363 return; 1364 } 1365 1366 // Dispatch a compositionend event to the focused node. 1367 // We should send this event before sending a TextEvent as written in Section 6.2.2 and 6.2.3 of 1368 // the DOM Event specification. 1369 Node* target = m_frame->document()->focusedNode(); 1370 if (target) { 1371 RefPtr<CompositionEvent> event = CompositionEvent::create(eventNames().compositionendEvent, m_frame->domWindow(), text); 1372 ExceptionCode ec = 0; 1373 target->dispatchEvent(event, ec); 1374 } 1375 1376 // If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input 1377 // will delete the old composition with an optimized replace operation. 1378 if (text.isEmpty()) 1379 TypingCommand::deleteSelection(m_frame->document(), false); 1380 1381 m_compositionNode = 0; 1382 m_customCompositionUnderlines.clear(); 1383 1384 insertText(text, 0); 1385 1386 if (preserveSelection) { 1387 m_frame->selection()->setSelection(oldSelection, false, false); 1388 // An open typing command that disagrees about current selection would cause issues with typing later on. 1389 TypingCommand::closeTyping(m_lastEditCommand.get()); 1390 } 1391 1392 setIgnoreCompositionSelectionChange(false); 1393 } 1394 1395 void Editor::setComposition(const String& text, const Vector<CompositionUnderline>& underlines, unsigned selectionStart, unsigned selectionEnd) 1396 { 1397 setIgnoreCompositionSelectionChange(true); 1398 1399 selectComposition(); 1400 1401 if (m_frame->selection()->isNone()) { 1402 setIgnoreCompositionSelectionChange(false); 1403 return; 1404 } 1405 1406 Node* target = m_frame->document()->focusedNode(); 1407 if (target) { 1408 // Dispatch an appropriate composition event to the focused node. 1409 // We check the composition status and choose an appropriate composition event since this 1410 // function is used for three purposes: 1411 // 1. Starting a new composition. 1412 // Send a compositionstart event when this function creates a new composition node, i.e. 1413 // m_compositionNode == 0 && !text.isEmpty(). 1414 // 2. Updating the existing composition node. 1415 // Send a compositionupdate event when this function updates the existing composition 1416 // node, i.e. m_compositionNode != 0 && !text.isEmpty(). 1417 // 3. Canceling the ongoing composition. 1418 // Send a compositionend event when function deletes the existing composition node, i.e. 1419 // m_compositionNode != 0 && test.isEmpty(). 1420 RefPtr<CompositionEvent> event; 1421 if (!m_compositionNode) { 1422 // We should send a compositionstart event only when the given text is not empty because this 1423 // function doesn't create a composition node when the text is empty. 1424 if (!text.isEmpty()) 1425 event = CompositionEvent::create(eventNames().compositionstartEvent, m_frame->domWindow(), text); 1426 } else { 1427 if (!text.isEmpty()) 1428 event = CompositionEvent::create(eventNames().compositionupdateEvent, m_frame->domWindow(), text); 1429 else 1430 event = CompositionEvent::create(eventNames().compositionendEvent, m_frame->domWindow(), text); 1431 } 1432 ExceptionCode ec = 0; 1433 if (event.get()) 1434 target->dispatchEvent(event, ec); 1435 } 1436 1437 // If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input 1438 // will delete the old composition with an optimized replace operation. 1439 if (text.isEmpty()) 1440 TypingCommand::deleteSelection(m_frame->document(), false); 1441 1442 m_compositionNode = 0; 1443 m_customCompositionUnderlines.clear(); 1444 1445 if (!text.isEmpty()) { 1446 TypingCommand::insertText(m_frame->document(), text, true, true); 1447 1448 Node* baseNode = m_frame->selection()->base().node(); 1449 unsigned baseOffset = m_frame->selection()->base().deprecatedEditingOffset(); 1450 Node* extentNode = m_frame->selection()->extent().node(); 1451 unsigned extentOffset = m_frame->selection()->extent().deprecatedEditingOffset(); 1452 1453 if (baseNode && baseNode == extentNode && baseNode->isTextNode() && baseOffset + text.length() == extentOffset) { 1454 m_compositionNode = static_cast<Text*>(baseNode); 1455 m_compositionStart = baseOffset; 1456 m_compositionEnd = extentOffset; 1457 m_customCompositionUnderlines = underlines; 1458 size_t numUnderlines = m_customCompositionUnderlines.size(); 1459 for (size_t i = 0; i < numUnderlines; ++i) { 1460 m_customCompositionUnderlines[i].startOffset += baseOffset; 1461 m_customCompositionUnderlines[i].endOffset += baseOffset; 1462 } 1463 if (baseNode->renderer()) 1464 baseNode->renderer()->repaint(); 1465 1466 unsigned start = min(baseOffset + selectionStart, extentOffset); 1467 unsigned end = min(max(start, baseOffset + selectionEnd), extentOffset); 1468 RefPtr<Range> selectedRange = Range::create(baseNode->document(), baseNode, start, baseNode, end); 1469 m_frame->selection()->setSelectedRange(selectedRange.get(), DOWNSTREAM, false); 1470 } 1471 } 1472 1473 setIgnoreCompositionSelectionChange(false); 1474 } 1475 1476 void Editor::ignoreSpelling() 1477 { 1478 if (!client()) 1479 return; 1480 1481 RefPtr<Range> selectedRange = frame()->selection()->toNormalizedRange(); 1482 if (selectedRange) 1483 frame()->document()->removeMarkers(selectedRange.get(), DocumentMarker::Spelling); 1484 1485 String text = frame()->selectedText(); 1486 ASSERT(text.length() != 0); 1487 client()->ignoreWordInSpellDocument(text); 1488 } 1489 1490 void Editor::learnSpelling() 1491 { 1492 if (!client()) 1493 return; 1494 1495 // FIXME: We don't call this on the Mac, and it should remove misspelling markers around the 1496 // learned word, see <rdar://problem/5396072>. 1497 1498 String text = frame()->selectedText(); 1499 ASSERT(text.length() != 0); 1500 client()->learnWord(text); 1501 } 1502 1503 static String findFirstMisspellingInRange(EditorClient* client, Range* searchRange, int& firstMisspellingOffset, bool markAll, RefPtr<Range>& firstMisspellingRange) 1504 { 1505 ASSERT_ARG(client, client); 1506 ASSERT_ARG(searchRange, searchRange); 1507 1508 WordAwareIterator it(searchRange); 1509 firstMisspellingOffset = 0; 1510 1511 String firstMisspelling; 1512 int currentChunkOffset = 0; 1513 1514 while (!it.atEnd()) { 1515 const UChar* chars = it.characters(); 1516 int len = it.length(); 1517 1518 // Skip some work for one-space-char hunks 1519 if (!(len == 1 && chars[0] == ' ')) { 1520 1521 int misspellingLocation = -1; 1522 int misspellingLength = 0; 1523 client->checkSpellingOfString(chars, len, &misspellingLocation, &misspellingLength); 1524 1525 // 5490627 shows that there was some code path here where the String constructor below crashes. 1526 // We don't know exactly what combination of bad input caused this, so we're making this much 1527 // more robust against bad input on release builds. 1528 ASSERT(misspellingLength >= 0); 1529 ASSERT(misspellingLocation >= -1); 1530 ASSERT(misspellingLength == 0 || misspellingLocation >= 0); 1531 ASSERT(misspellingLocation < len); 1532 ASSERT(misspellingLength <= len); 1533 ASSERT(misspellingLocation + misspellingLength <= len); 1534 1535 if (misspellingLocation >= 0 && misspellingLength > 0 && misspellingLocation < len && misspellingLength <= len && misspellingLocation + misspellingLength <= len) { 1536 1537 // Compute range of misspelled word 1538 RefPtr<Range> misspellingRange = TextIterator::subrange(searchRange, currentChunkOffset + misspellingLocation, misspellingLength); 1539 1540 // Remember first-encountered misspelling and its offset. 1541 if (!firstMisspelling) { 1542 firstMisspellingOffset = currentChunkOffset + misspellingLocation; 1543 firstMisspelling = String(chars + misspellingLocation, misspellingLength); 1544 firstMisspellingRange = misspellingRange; 1545 } 1546 1547 // Store marker for misspelled word. 1548 ExceptionCode ec = 0; 1549 misspellingRange->startContainer(ec)->document()->addMarker(misspellingRange.get(), DocumentMarker::Spelling); 1550 ASSERT(ec == 0); 1551 1552 // Bail out if we're marking only the first misspelling, and not all instances. 1553 if (!markAll) 1554 break; 1555 } 1556 } 1557 1558 currentChunkOffset += len; 1559 it.advance(); 1560 } 1561 1562 return firstMisspelling; 1563 } 1564 1565 #ifndef BUILDING_ON_TIGER 1566 1567 static PassRefPtr<Range> paragraphAlignedRangeForRange(Range* arbitraryRange, int& offsetIntoParagraphAlignedRange, String& paragraphString) 1568 { 1569 ASSERT_ARG(arbitraryRange, arbitraryRange); 1570 1571 ExceptionCode ec = 0; 1572 1573 // Expand range to paragraph boundaries 1574 RefPtr<Range> paragraphRange = arbitraryRange->cloneRange(ec); 1575 setStart(paragraphRange.get(), startOfParagraph(arbitraryRange->startPosition())); 1576 setEnd(paragraphRange.get(), endOfParagraph(arbitraryRange->endPosition())); 1577 1578 // Compute offset from start of expanded range to start of original range 1579 RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), arbitraryRange->startPosition()); 1580 offsetIntoParagraphAlignedRange = TextIterator::rangeLength(offsetAsRange.get()); 1581 1582 // Fill in out parameter with string representing entire paragraph range. 1583 // Someday we might have a caller that doesn't use this, but for now all callers do. 1584 paragraphString = plainText(paragraphRange.get()); 1585 1586 return paragraphRange; 1587 } 1588 1589 static int findFirstGrammarDetailInRange(const Vector<GrammarDetail>& grammarDetails, int badGrammarPhraseLocation, int /*badGrammarPhraseLength*/, Range *searchRange, int startOffset, int endOffset, bool markAll) 1590 { 1591 // Found some bad grammar. Find the earliest detail range that starts in our search range (if any). 1592 // Optionally add a DocumentMarker for each detail in the range. 1593 int earliestDetailLocationSoFar = -1; 1594 int earliestDetailIndex = -1; 1595 for (unsigned i = 0; i < grammarDetails.size(); i++) { 1596 const GrammarDetail* detail = &grammarDetails[i]; 1597 ASSERT(detail->length > 0 && detail->location >= 0); 1598 1599 int detailStartOffsetInParagraph = badGrammarPhraseLocation + detail->location; 1600 1601 // Skip this detail if it starts before the original search range 1602 if (detailStartOffsetInParagraph < startOffset) 1603 continue; 1604 1605 // Skip this detail if it starts after the original search range 1606 if (detailStartOffsetInParagraph >= endOffset) 1607 continue; 1608 1609 if (markAll) { 1610 RefPtr<Range> badGrammarRange = TextIterator::subrange(searchRange, badGrammarPhraseLocation - startOffset + detail->location, detail->length); 1611 ExceptionCode ec = 0; 1612 badGrammarRange->startContainer(ec)->document()->addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail->userDescription); 1613 ASSERT(ec == 0); 1614 } 1615 1616 // Remember this detail only if it's earlier than our current candidate (the details aren't in a guaranteed order) 1617 if (earliestDetailIndex < 0 || earliestDetailLocationSoFar > detail->location) { 1618 earliestDetailIndex = i; 1619 earliestDetailLocationSoFar = detail->location; 1620 } 1621 } 1622 1623 return earliestDetailIndex; 1624 } 1625 1626 static String findFirstBadGrammarInRange(EditorClient* client, Range* searchRange, GrammarDetail& outGrammarDetail, int& outGrammarPhraseOffset, bool markAll) 1627 { 1628 ASSERT_ARG(client, client); 1629 ASSERT_ARG(searchRange, searchRange); 1630 1631 // Initialize out parameters; these will be updated if we find something to return. 1632 outGrammarDetail.location = -1; 1633 outGrammarDetail.length = 0; 1634 outGrammarDetail.guesses.clear(); 1635 outGrammarDetail.userDescription = ""; 1636 outGrammarPhraseOffset = 0; 1637 1638 String firstBadGrammarPhrase; 1639 1640 // Expand the search range to encompass entire paragraphs, since grammar checking needs that much context. 1641 // Determine the character offset from the start of the paragraph to the start of the original search range, 1642 // since we will want to ignore results in this area. 1643 int searchRangeStartOffset; 1644 String paragraphString; 1645 RefPtr<Range> paragraphRange = paragraphAlignedRangeForRange(searchRange, searchRangeStartOffset, paragraphString); 1646 1647 // Determine the character offset from the start of the paragraph to the end of the original search range, 1648 // since we will want to ignore results in this area also. 1649 int searchRangeEndOffset = searchRangeStartOffset + TextIterator::rangeLength(searchRange); 1650 1651 // Start checking from beginning of paragraph, but skip past results that occur before the start of the original search range. 1652 int startOffset = 0; 1653 while (startOffset < searchRangeEndOffset) { 1654 Vector<GrammarDetail> grammarDetails; 1655 int badGrammarPhraseLocation = -1; 1656 int badGrammarPhraseLength = 0; 1657 client->checkGrammarOfString(paragraphString.characters() + startOffset, paragraphString.length() - startOffset, grammarDetails, &badGrammarPhraseLocation, &badGrammarPhraseLength); 1658 1659 if (badGrammarPhraseLength == 0) { 1660 ASSERT(badGrammarPhraseLocation == -1); 1661 return String(); 1662 } 1663 1664 ASSERT(badGrammarPhraseLocation >= 0); 1665 badGrammarPhraseLocation += startOffset; 1666 1667 1668 // Found some bad grammar. Find the earliest detail range that starts in our search range (if any). 1669 int badGrammarIndex = findFirstGrammarDetailInRange(grammarDetails, badGrammarPhraseLocation, badGrammarPhraseLength, searchRange, searchRangeStartOffset, searchRangeEndOffset, markAll); 1670 if (badGrammarIndex >= 0) { 1671 ASSERT(static_cast<unsigned>(badGrammarIndex) < grammarDetails.size()); 1672 outGrammarDetail = grammarDetails[badGrammarIndex]; 1673 } 1674 1675 // If we found a detail in range, then we have found the first bad phrase (unless we found one earlier but 1676 // kept going so we could mark all instances). 1677 if (badGrammarIndex >= 0 && firstBadGrammarPhrase.isEmpty()) { 1678 outGrammarPhraseOffset = badGrammarPhraseLocation - searchRangeStartOffset; 1679 firstBadGrammarPhrase = paragraphString.substring(badGrammarPhraseLocation, badGrammarPhraseLength); 1680 1681 // Found one. We're done now, unless we're marking each instance. 1682 if (!markAll) 1683 break; 1684 } 1685 1686 // These results were all between the start of the paragraph and the start of the search range; look 1687 // beyond this phrase. 1688 startOffset = badGrammarPhraseLocation + badGrammarPhraseLength; 1689 } 1690 1691 return firstBadGrammarPhrase; 1692 } 1693 1694 #endif /* not BUILDING_ON_TIGER */ 1695 1696 #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 1697 1698 static String findFirstMisspellingOrBadGrammarInRange(EditorClient* client, Range* searchRange, bool checkGrammar, bool& outIsSpelling, int& outFirstFoundOffset, GrammarDetail& outGrammarDetail) 1699 { 1700 ASSERT_ARG(client, client); 1701 ASSERT_ARG(searchRange, searchRange); 1702 1703 String firstFoundItem; 1704 String misspelledWord; 1705 String badGrammarPhrase; 1706 ExceptionCode ec = 0; 1707 1708 // Initialize out parameters; these will be updated if we find something to return. 1709 outIsSpelling = true; 1710 outFirstFoundOffset = 0; 1711 outGrammarDetail.location = -1; 1712 outGrammarDetail.length = 0; 1713 outGrammarDetail.guesses.clear(); 1714 outGrammarDetail.userDescription = ""; 1715 1716 // Expand the search range to encompass entire paragraphs, since text checking needs that much context. 1717 // Determine the character offset from the start of the paragraph to the start of the original search range, 1718 // since we will want to ignore results in this area. 1719 RefPtr<Range> paragraphRange = searchRange->cloneRange(ec); 1720 setStart(paragraphRange.get(), startOfParagraph(searchRange->startPosition())); 1721 int totalRangeLength = TextIterator::rangeLength(paragraphRange.get()); 1722 setEnd(paragraphRange.get(), endOfParagraph(searchRange->startPosition())); 1723 1724 RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), searchRange->startPosition()); 1725 int searchRangeStartOffset = TextIterator::rangeLength(offsetAsRange.get()); 1726 int totalLengthProcessed = 0; 1727 1728 bool firstIteration = true; 1729 bool lastIteration = false; 1730 while (totalLengthProcessed < totalRangeLength) { 1731 // Iterate through the search range by paragraphs, checking each one for spelling and grammar. 1732 int currentLength = TextIterator::rangeLength(paragraphRange.get()); 1733 int currentStartOffset = firstIteration ? searchRangeStartOffset : 0; 1734 int currentEndOffset = currentLength; 1735 if (inSameParagraph(paragraphRange->startPosition(), searchRange->endPosition())) { 1736 // Determine the character offset from the end of the original search range to the end of the paragraph, 1737 // since we will want to ignore results in this area. 1738 RefPtr<Range> endOffsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), searchRange->endPosition()); 1739 currentEndOffset = TextIterator::rangeLength(endOffsetAsRange.get()); 1740 lastIteration = true; 1741 } 1742 if (currentStartOffset < currentEndOffset) { 1743 String paragraphString = plainText(paragraphRange.get()); 1744 if (paragraphString.length() > 0) { 1745 bool foundGrammar = false; 1746 int spellingLocation = 0; 1747 int grammarPhraseLocation = 0; 1748 int grammarDetailLocation = 0; 1749 unsigned grammarDetailIndex = 0; 1750 1751 Vector<TextCheckingResult> results; 1752 uint64_t checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling; 1753 client->checkTextOfParagraph(paragraphString.characters(), paragraphString.length(), checkingTypes, results); 1754 1755 for (unsigned i = 0; i < results.size(); i++) { 1756 const TextCheckingResult* result = &results[i]; 1757 if (result->type == TextCheckingTypeSpelling && result->location >= currentStartOffset && result->location + result->length <= currentEndOffset) { 1758 ASSERT(result->length > 0 && result->location >= 0); 1759 spellingLocation = result->location; 1760 misspelledWord = paragraphString.substring(result->location, result->length); 1761 ASSERT(misspelledWord.length() != 0); 1762 break; 1763 } else if (checkGrammar && result->type == TextCheckingTypeGrammar && result->location < currentEndOffset && result->location + result->length > currentStartOffset) { 1764 ASSERT(result->length > 0 && result->location >= 0); 1765 // We can't stop after the first grammar result, since there might still be a spelling result after 1766 // it begins but before the first detail in it, but we can stop if we find a second grammar result. 1767 if (foundGrammar) break; 1768 for (unsigned j = 0; j < result->details.size(); j++) { 1769 const GrammarDetail* detail = &result->details[j]; 1770 ASSERT(detail->length > 0 && detail->location >= 0); 1771 if (result->location + detail->location >= currentStartOffset && result->location + detail->location + detail->length <= currentEndOffset && (!foundGrammar || result->location + detail->location < grammarDetailLocation)) { 1772 grammarDetailIndex = j; 1773 grammarDetailLocation = result->location + detail->location; 1774 foundGrammar = true; 1775 } 1776 } 1777 if (foundGrammar) { 1778 grammarPhraseLocation = result->location; 1779 outGrammarDetail = result->details[grammarDetailIndex]; 1780 badGrammarPhrase = paragraphString.substring(result->location, result->length); 1781 ASSERT(badGrammarPhrase.length() != 0); 1782 } 1783 } 1784 } 1785 1786 if (!misspelledWord.isEmpty() && (!checkGrammar || badGrammarPhrase.isEmpty() || spellingLocation <= grammarDetailLocation)) { 1787 int spellingOffset = spellingLocation - currentStartOffset; 1788 if (!firstIteration) { 1789 RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), searchRange->startPosition(), paragraphRange->startPosition()); 1790 spellingOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get()); 1791 } 1792 outIsSpelling = true; 1793 outFirstFoundOffset = spellingOffset; 1794 firstFoundItem = misspelledWord; 1795 break; 1796 } else if (checkGrammar && !badGrammarPhrase.isEmpty()) { 1797 int grammarPhraseOffset = grammarPhraseLocation - currentStartOffset; 1798 if (!firstIteration) { 1799 RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), searchRange->startPosition(), paragraphRange->startPosition()); 1800 grammarPhraseOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get()); 1801 } 1802 outIsSpelling = false; 1803 outFirstFoundOffset = grammarPhraseOffset; 1804 firstFoundItem = badGrammarPhrase; 1805 break; 1806 } 1807 } 1808 } 1809 if (lastIteration || totalLengthProcessed + currentLength >= totalRangeLength) 1810 break; 1811 VisiblePosition newParagraphStart = startOfNextParagraph(paragraphRange->endPosition()); 1812 setStart(paragraphRange.get(), newParagraphStart); 1813 setEnd(paragraphRange.get(), endOfParagraph(newParagraphStart)); 1814 firstIteration = false; 1815 totalLengthProcessed += currentLength; 1816 } 1817 return firstFoundItem; 1818 } 1819 1820 #endif 1821 1822 void Editor::advanceToNextMisspelling(bool startBeforeSelection) 1823 { 1824 ExceptionCode ec = 0; 1825 1826 // The basic approach is to search in two phases - from the selection end to the end of the doc, and 1827 // then we wrap and search from the doc start to (approximately) where we started. 1828 1829 // Start at the end of the selection, search to edge of document. Starting at the selection end makes 1830 // repeated "check spelling" commands work. 1831 VisibleSelection selection(frame()->selection()->selection()); 1832 RefPtr<Range> spellingSearchRange(rangeOfContents(frame()->document())); 1833 bool startedWithSelection = false; 1834 if (selection.start().node()) { 1835 startedWithSelection = true; 1836 if (startBeforeSelection) { 1837 VisiblePosition start(selection.visibleStart()); 1838 // We match AppKit's rule: Start 1 character before the selection. 1839 VisiblePosition oneBeforeStart = start.previous(); 1840 setStart(spellingSearchRange.get(), oneBeforeStart.isNotNull() ? oneBeforeStart : start); 1841 } else 1842 setStart(spellingSearchRange.get(), selection.visibleEnd()); 1843 } 1844 1845 Position position = spellingSearchRange->startPosition(); 1846 if (!isEditablePosition(position)) { 1847 // This shouldn't happen in very often because the Spelling menu items aren't enabled unless the 1848 // selection is editable. 1849 // This can happen in Mail for a mix of non-editable and editable content (like Stationary), 1850 // when spell checking the whole document before sending the message. 1851 // In that case the document might not be editable, but there are editable pockets that need to be spell checked. 1852 1853 position = firstEditablePositionAfterPositionInRoot(position, frame()->document()->documentElement()).deepEquivalent(); 1854 if (position.isNull()) 1855 return; 1856 1857 Position rangeCompliantPosition = rangeCompliantEquivalent(position); 1858 spellingSearchRange->setStart(rangeCompliantPosition.node(), rangeCompliantPosition.deprecatedEditingOffset(), ec); 1859 startedWithSelection = false; // won't need to wrap 1860 } 1861 1862 // topNode defines the whole range we want to operate on 1863 Node* topNode = highestEditableRoot(position); 1864 // FIXME: lastOffsetForEditing() is wrong here if editingIgnoresContent(highestEditableRoot()) returns true (e.g. a <table>) 1865 spellingSearchRange->setEnd(topNode, lastOffsetForEditing(topNode), ec); 1866 1867 // If spellingSearchRange starts in the middle of a word, advance to the next word so we start checking 1868 // at a word boundary. Going back by one char and then forward by a word does the trick. 1869 if (startedWithSelection) { 1870 VisiblePosition oneBeforeStart = startVisiblePosition(spellingSearchRange.get(), DOWNSTREAM).previous(); 1871 if (oneBeforeStart.isNotNull()) { 1872 setStart(spellingSearchRange.get(), endOfWord(oneBeforeStart)); 1873 } // else we were already at the start of the editable node 1874 } 1875 1876 if (spellingSearchRange->collapsed(ec)) 1877 return; // nothing to search in 1878 1879 // Get the spell checker if it is available 1880 if (!client()) 1881 return; 1882 1883 // We go to the end of our first range instead of the start of it, just to be sure 1884 // we don't get foiled by any word boundary problems at the start. It means we might 1885 // do a tiny bit more searching. 1886 Node *searchEndNodeAfterWrap = spellingSearchRange->endContainer(ec); 1887 int searchEndOffsetAfterWrap = spellingSearchRange->endOffset(ec); 1888 1889 int misspellingOffset = 0; 1890 #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 1891 RefPtr<Range> grammarSearchRange = spellingSearchRange->cloneRange(ec); 1892 String misspelledWord; 1893 String badGrammarPhrase; 1894 int grammarPhraseOffset = 0; 1895 bool isSpelling = true; 1896 int foundOffset = 0; 1897 GrammarDetail grammarDetail; 1898 String foundItem = findFirstMisspellingOrBadGrammarInRange(client(), spellingSearchRange.get(), isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail); 1899 if (isSpelling) { 1900 misspelledWord = foundItem; 1901 misspellingOffset = foundOffset; 1902 } else { 1903 badGrammarPhrase = foundItem; 1904 grammarPhraseOffset = foundOffset; 1905 } 1906 #else 1907 RefPtr<Range> firstMisspellingRange; 1908 String misspelledWord = findFirstMisspellingInRange(client(), spellingSearchRange.get(), misspellingOffset, false, firstMisspellingRange); 1909 String badGrammarPhrase; 1910 1911 #ifndef BUILDING_ON_TIGER 1912 int grammarPhraseOffset = 0; 1913 GrammarDetail grammarDetail; 1914 1915 // Search for bad grammar that occurs prior to the next misspelled word (if any) 1916 RefPtr<Range> grammarSearchRange = spellingSearchRange->cloneRange(ec); 1917 if (!misspelledWord.isEmpty()) { 1918 // Stop looking at start of next misspelled word 1919 CharacterIterator chars(grammarSearchRange.get()); 1920 chars.advance(misspellingOffset); 1921 grammarSearchRange->setEnd(chars.range()->startContainer(ec), chars.range()->startOffset(ec), ec); 1922 } 1923 1924 if (isGrammarCheckingEnabled()) 1925 badGrammarPhrase = findFirstBadGrammarInRange(client(), grammarSearchRange.get(), grammarDetail, grammarPhraseOffset, false); 1926 #endif 1927 #endif 1928 1929 // If we found neither bad grammar nor a misspelled word, wrap and try again (but don't bother if we started at the beginning of the 1930 // block rather than at a selection). 1931 if (startedWithSelection && !misspelledWord && !badGrammarPhrase) { 1932 spellingSearchRange->setStart(topNode, 0, ec); 1933 // going until the end of the very first chunk we tested is far enough 1934 spellingSearchRange->setEnd(searchEndNodeAfterWrap, searchEndOffsetAfterWrap, ec); 1935 1936 #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 1937 grammarSearchRange = spellingSearchRange->cloneRange(ec); 1938 foundItem = findFirstMisspellingOrBadGrammarInRange(client(), spellingSearchRange.get(), isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail); 1939 if (isSpelling) { 1940 misspelledWord = foundItem; 1941 misspellingOffset = foundOffset; 1942 } else { 1943 badGrammarPhrase = foundItem; 1944 grammarPhraseOffset = foundOffset; 1945 } 1946 #else 1947 misspelledWord = findFirstMisspellingInRange(client(), spellingSearchRange.get(), misspellingOffset, false, firstMisspellingRange); 1948 1949 #ifndef BUILDING_ON_TIGER 1950 grammarSearchRange = spellingSearchRange->cloneRange(ec); 1951 if (!misspelledWord.isEmpty()) { 1952 // Stop looking at start of next misspelled word 1953 CharacterIterator chars(grammarSearchRange.get()); 1954 chars.advance(misspellingOffset); 1955 grammarSearchRange->setEnd(chars.range()->startContainer(ec), chars.range()->startOffset(ec), ec); 1956 } 1957 if (isGrammarCheckingEnabled()) 1958 badGrammarPhrase = findFirstBadGrammarInRange(client(), grammarSearchRange.get(), grammarDetail, grammarPhraseOffset, false); 1959 #endif 1960 #endif 1961 } 1962 1963 if (!badGrammarPhrase.isEmpty()) { 1964 #ifdef BUILDING_ON_TIGER 1965 ASSERT_NOT_REACHED(); 1966 #else 1967 // We found bad grammar. Since we only searched for bad grammar up to the first misspelled word, the bad grammar 1968 // takes precedence and we ignore any potential misspelled word. Select the grammar detail, update the spelling 1969 // panel, and store a marker so we draw the green squiggle later. 1970 1971 ASSERT(badGrammarPhrase.length() > 0); 1972 ASSERT(grammarDetail.location != -1 && grammarDetail.length > 0); 1973 1974 // FIXME 4859190: This gets confused with doubled punctuation at the end of a paragraph 1975 RefPtr<Range> badGrammarRange = TextIterator::subrange(grammarSearchRange.get(), grammarPhraseOffset + grammarDetail.location, grammarDetail.length); 1976 frame()->selection()->setSelection(VisibleSelection(badGrammarRange.get(), SEL_DEFAULT_AFFINITY)); 1977 frame()->revealSelection(); 1978 1979 client()->updateSpellingUIWithGrammarString(badGrammarPhrase, grammarDetail); 1980 frame()->document()->addMarker(badGrammarRange.get(), DocumentMarker::Grammar, grammarDetail.userDescription); 1981 #endif 1982 } else if (!misspelledWord.isEmpty()) { 1983 // We found a misspelling, but not any earlier bad grammar. Select the misspelling, update the spelling panel, and store 1984 // a marker so we draw the red squiggle later. 1985 1986 RefPtr<Range> misspellingRange = TextIterator::subrange(spellingSearchRange.get(), misspellingOffset, misspelledWord.length()); 1987 frame()->selection()->setSelection(VisibleSelection(misspellingRange.get(), DOWNSTREAM)); 1988 frame()->revealSelection(); 1989 1990 client()->updateSpellingUIWithMisspelledWord(misspelledWord); 1991 frame()->document()->addMarker(misspellingRange.get(), DocumentMarker::Spelling); 1992 } 1993 } 1994 1995 bool Editor::isSelectionMisspelled() 1996 { 1997 String selectedString = frame()->selectedText(); 1998 int length = selectedString.length(); 1999 if (length == 0) 2000 return false; 2001 2002 if (!client()) 2003 return false; 2004 2005 int misspellingLocation = -1; 2006 int misspellingLength = 0; 2007 client()->checkSpellingOfString(selectedString.characters(), length, &misspellingLocation, &misspellingLength); 2008 2009 // The selection only counts as misspelled if the selected text is exactly one misspelled word 2010 if (misspellingLength != length) 2011 return false; 2012 2013 // Update the spelling panel to be displaying this error (whether or not the spelling panel is on screen). 2014 // This is necessary to make a subsequent call to [NSSpellChecker ignoreWord:inSpellDocumentWithTag:] work 2015 // correctly; that call behaves differently based on whether the spelling panel is displaying a misspelling 2016 // or a grammar error. 2017 client()->updateSpellingUIWithMisspelledWord(selectedString); 2018 2019 return true; 2020 } 2021 2022 #ifndef BUILDING_ON_TIGER 2023 static bool isRangeUngrammatical(EditorClient* client, Range *range, Vector<String>& guessesVector) 2024 { 2025 if (!client) 2026 return false; 2027 2028 ExceptionCode ec; 2029 if (!range || range->collapsed(ec)) 2030 return false; 2031 2032 // Returns true only if the passed range exactly corresponds to a bad grammar detail range. This is analogous 2033 // to isSelectionMisspelled. It's not good enough for there to be some bad grammar somewhere in the range, 2034 // or overlapping the range; the ranges must exactly match. 2035 guessesVector.clear(); 2036 int grammarPhraseOffset; 2037 2038 GrammarDetail grammarDetail; 2039 String badGrammarPhrase = findFirstBadGrammarInRange(client, range, grammarDetail, grammarPhraseOffset, false); 2040 2041 // No bad grammar in these parts at all. 2042 if (badGrammarPhrase.isEmpty()) 2043 return false; 2044 2045 // Bad grammar, but phrase (e.g. sentence) starts beyond start of range. 2046 if (grammarPhraseOffset > 0) 2047 return false; 2048 2049 ASSERT(grammarDetail.location >= 0 && grammarDetail.length > 0); 2050 2051 // Bad grammar, but start of detail (e.g. ungrammatical word) doesn't match start of range 2052 if (grammarDetail.location + grammarPhraseOffset != 0) 2053 return false; 2054 2055 // Bad grammar at start of range, but end of bad grammar is before or after end of range 2056 if (grammarDetail.length != TextIterator::rangeLength(range)) 2057 return false; 2058 2059 // Update the spelling panel to be displaying this error (whether or not the spelling panel is on screen). 2060 // This is necessary to make a subsequent call to [NSSpellChecker ignoreWord:inSpellDocumentWithTag:] work 2061 // correctly; that call behaves differently based on whether the spelling panel is displaying a misspelling 2062 // or a grammar error. 2063 client->updateSpellingUIWithGrammarString(badGrammarPhrase, grammarDetail); 2064 2065 return true; 2066 } 2067 #endif 2068 2069 bool Editor::isSelectionUngrammatical() 2070 { 2071 #ifdef BUILDING_ON_TIGER 2072 return false; 2073 #else 2074 Vector<String> ignoredGuesses; 2075 return isRangeUngrammatical(client(), frame()->selection()->toNormalizedRange().get(), ignoredGuesses); 2076 #endif 2077 } 2078 2079 Vector<String> Editor::guessesForUngrammaticalSelection() 2080 { 2081 #ifdef BUILDING_ON_TIGER 2082 return Vector<String>(); 2083 #else 2084 Vector<String> guesses; 2085 // Ignore the result of isRangeUngrammatical; we just want the guesses, whether or not there are any 2086 isRangeUngrammatical(client(), frame()->selection()->toNormalizedRange().get(), guesses); 2087 return guesses; 2088 #endif 2089 } 2090 2091 Vector<String> Editor::guessesForMisspelledSelection() 2092 { 2093 String selectedString = frame()->selectedText(); 2094 ASSERT(selectedString.length() != 0); 2095 2096 Vector<String> guesses; 2097 if (client()) 2098 client()->getGuessesForWord(selectedString, guesses); 2099 return guesses; 2100 } 2101 2102 #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 2103 2104 static Vector<String> guessesForMisspelledOrUngrammaticalRange(EditorClient* client, Range *range, bool checkGrammar, bool& misspelled, bool& ungrammatical) 2105 { 2106 Vector<String> guesses; 2107 ExceptionCode ec; 2108 misspelled = false; 2109 ungrammatical = false; 2110 2111 if (!client || !range || range->collapsed(ec)) 2112 return guesses; 2113 2114 // Expand the range to encompass entire paragraphs, since text checking needs that much context. 2115 int rangeStartOffset; 2116 String paragraphString; 2117 RefPtr<Range> paragraphRange = paragraphAlignedRangeForRange(range, rangeStartOffset, paragraphString); 2118 int rangeLength = TextIterator::rangeLength(range); 2119 if (rangeLength == 0 || paragraphString.length() == 0) 2120 return guesses; 2121 2122 Vector<TextCheckingResult> results; 2123 uint64_t checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling; 2124 client->checkTextOfParagraph(paragraphString.characters(), paragraphString.length(), checkingTypes, results); 2125 2126 for (unsigned i = 0; i < results.size(); i++) { 2127 const TextCheckingResult* result = &results[i]; 2128 if (result->type == TextCheckingTypeSpelling && result->location == rangeStartOffset && result->length == rangeLength) { 2129 String misspelledWord = paragraphString.substring(rangeStartOffset, rangeLength); 2130 ASSERT(misspelledWord.length() != 0); 2131 client->getGuessesForWord(misspelledWord, guesses); 2132 client->updateSpellingUIWithMisspelledWord(misspelledWord); 2133 misspelled = true; 2134 return guesses; 2135 } 2136 } 2137 2138 if (!checkGrammar) 2139 return guesses; 2140 2141 for (unsigned i = 0; i < results.size(); i++) { 2142 const TextCheckingResult* result = &results[i]; 2143 if (result->type == TextCheckingTypeGrammar && result->location <= rangeStartOffset && result->location + result->length >= rangeStartOffset + rangeLength) { 2144 for (unsigned j = 0; j < result->details.size(); j++) { 2145 const GrammarDetail* detail = &result->details[j]; 2146 ASSERT(detail->length > 0 && detail->location >= 0); 2147 if (result->location + detail->location == rangeStartOffset && detail->length == rangeLength) { 2148 String badGrammarPhrase = paragraphString.substring(result->location, result->length); 2149 ASSERT(badGrammarPhrase.length() != 0); 2150 for (unsigned k = 0; k < detail->guesses.size(); k++) 2151 guesses.append(detail->guesses[k]); 2152 client->updateSpellingUIWithGrammarString(badGrammarPhrase, *detail); 2153 ungrammatical = true; 2154 return guesses; 2155 } 2156 } 2157 } 2158 } 2159 return guesses; 2160 } 2161 2162 #endif 2163 2164 Vector<String> Editor::guessesForMisspelledOrUngrammaticalSelection(bool& misspelled, bool& ungrammatical) 2165 { 2166 #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 2167 return guessesForMisspelledOrUngrammaticalRange(client(), frame()->selection()->toNormalizedRange().get(), isGrammarCheckingEnabled(), misspelled, ungrammatical); 2168 #else 2169 misspelled = isSelectionMisspelled(); 2170 if (misspelled) { 2171 ungrammatical = false; 2172 return guessesForMisspelledSelection(); 2173 } 2174 if (isGrammarCheckingEnabled() && isSelectionUngrammatical()) { 2175 ungrammatical = true; 2176 return guessesForUngrammaticalSelection(); 2177 } 2178 ungrammatical = false; 2179 return Vector<String>(); 2180 #endif 2181 } 2182 2183 void Editor::showSpellingGuessPanel() 2184 { 2185 if (!client()) { 2186 LOG_ERROR("No NSSpellChecker"); 2187 return; 2188 } 2189 2190 #ifndef BUILDING_ON_TIGER 2191 // Post-Tiger, this menu item is a show/hide toggle, to match AppKit. Leave Tiger behavior alone 2192 // to match rest of OS X. 2193 if (client()->spellingUIIsShowing()) { 2194 client()->showSpellingUI(false); 2195 return; 2196 } 2197 #endif 2198 2199 advanceToNextMisspelling(true); 2200 client()->showSpellingUI(true); 2201 } 2202 2203 bool Editor::spellingPanelIsShowing() 2204 { 2205 if (!client()) 2206 return false; 2207 return client()->spellingUIIsShowing(); 2208 } 2209 2210 void Editor::markMisspellingsAfterTypingToPosition(const VisiblePosition &p) 2211 { 2212 #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 2213 bool markSpelling = isContinuousSpellCheckingEnabled(); 2214 bool markGrammar = markSpelling && isGrammarCheckingEnabled(); 2215 bool performTextCheckingReplacements = isAutomaticQuoteSubstitutionEnabled() 2216 || isAutomaticLinkDetectionEnabled() 2217 || isAutomaticDashSubstitutionEnabled() 2218 || isAutomaticTextReplacementEnabled() 2219 || (markSpelling && isAutomaticSpellingCorrectionEnabled()); 2220 if (!markSpelling && !performTextCheckingReplacements) 2221 return; 2222 2223 VisibleSelection adjacentWords = VisibleSelection(startOfWord(p, LeftWordIfOnBoundary), endOfWord(p, RightWordIfOnBoundary)); 2224 if (markGrammar) { 2225 VisibleSelection selectedSentence = VisibleSelection(startOfSentence(p), endOfSentence(p)); 2226 markAllMisspellingsAndBadGrammarInRanges(true, adjacentWords.toNormalizedRange().get(), true, selectedSentence.toNormalizedRange().get(), performTextCheckingReplacements); 2227 } else { 2228 markAllMisspellingsAndBadGrammarInRanges(markSpelling, adjacentWords.toNormalizedRange().get(), false, adjacentWords.toNormalizedRange().get(), performTextCheckingReplacements); 2229 } 2230 #else 2231 if (!isContinuousSpellCheckingEnabled()) 2232 return; 2233 2234 // Check spelling of one word 2235 RefPtr<Range> misspellingRange; 2236 markMisspellings(VisibleSelection(startOfWord(p, LeftWordIfOnBoundary), endOfWord(p, RightWordIfOnBoundary)), misspellingRange); 2237 2238 // Autocorrect the misspelled word. 2239 if (misspellingRange == 0) 2240 return; 2241 2242 // Get the misspelled word. 2243 const String misspelledWord = plainText(misspellingRange.get()); 2244 String autocorrectedString = client()->getAutoCorrectSuggestionForMisspelledWord(misspelledWord); 2245 2246 // If autocorrected word is non empty, replace the misspelled word by this word. 2247 if (!autocorrectedString.isEmpty()) { 2248 VisibleSelection newSelection(misspellingRange.get(), DOWNSTREAM); 2249 if (newSelection != frame()->selection()->selection()) { 2250 if (!frame()->shouldChangeSelection(newSelection)) 2251 return; 2252 frame()->selection()->setSelection(newSelection); 2253 } 2254 2255 if (!frame()->editor()->shouldInsertText(autocorrectedString, misspellingRange.get(), EditorInsertActionTyped)) 2256 return; 2257 frame()->editor()->replaceSelectionWithText(autocorrectedString, false, false); 2258 2259 // Reset the charet one character further. 2260 frame()->selection()->moveTo(frame()->selection()->end()); 2261 frame()->selection()->modify(SelectionController::MOVE, SelectionController::FORWARD, CharacterGranularity); 2262 } 2263 2264 if (!isGrammarCheckingEnabled()) 2265 return; 2266 2267 // Check grammar of entire sentence 2268 markBadGrammar(VisibleSelection(startOfSentence(p), endOfSentence(p))); 2269 #endif 2270 } 2271 2272 static void markAllMisspellingsInRange(EditorClient* client, Range* searchRange, RefPtr<Range>& firstMisspellingRange) 2273 { 2274 // Use the "markAll" feature of findFirstMisspellingInRange. Ignore the return value and the "out parameter"; 2275 // all we need to do is mark every instance. 2276 int ignoredOffset; 2277 findFirstMisspellingInRange(client, searchRange, ignoredOffset, true, firstMisspellingRange); 2278 } 2279 2280 #ifndef BUILDING_ON_TIGER 2281 static void markAllBadGrammarInRange(EditorClient* client, Range* searchRange) 2282 { 2283 // Use the "markAll" feature of findFirstBadGrammarInRange. Ignore the return value and "out parameters"; all we need to 2284 // do is mark every instance. 2285 GrammarDetail ignoredGrammarDetail; 2286 int ignoredOffset; 2287 findFirstBadGrammarInRange(client, searchRange, ignoredGrammarDetail, ignoredOffset, true); 2288 } 2289 #endif 2290 2291 static void markMisspellingsOrBadGrammar(Editor* editor, const VisibleSelection& selection, bool checkSpelling, RefPtr<Range>& firstMisspellingRange) 2292 { 2293 // This function is called with a selection already expanded to word boundaries. 2294 // Might be nice to assert that here. 2295 2296 // This function is used only for as-you-type checking, so if that's off we do nothing. Note that 2297 // grammar checking can only be on if spell checking is also on. 2298 if (!editor->isContinuousSpellCheckingEnabled()) 2299 return; 2300 2301 RefPtr<Range> searchRange(selection.toNormalizedRange()); 2302 if (!searchRange) 2303 return; 2304 2305 // If we're not in an editable node, bail. 2306 Node* editableNode = searchRange->startContainer(); 2307 if (!editableNode || !editableNode->isContentEditable()) 2308 return; 2309 2310 if (!editor->spellCheckingEnabledInFocusedNode()) 2311 return; 2312 2313 // Get the spell checker if it is available 2314 if (!editor->client()) 2315 return; 2316 2317 if (checkSpelling) 2318 markAllMisspellingsInRange(editor->client(), searchRange.get(), firstMisspellingRange); 2319 else { 2320 #ifdef BUILDING_ON_TIGER 2321 ASSERT_NOT_REACHED(); 2322 #else 2323 if (editor->isGrammarCheckingEnabled()) 2324 markAllBadGrammarInRange(editor->client(), searchRange.get()); 2325 #endif 2326 } 2327 } 2328 2329 bool Editor::spellCheckingEnabledInFocusedNode() const 2330 { 2331 // Ascend the DOM tree to find a "spellcheck" attribute. 2332 // When we find a "spellcheck" attribute, retrieve its value and return false if its value is "false". 2333 const Node* node = frame()->document()->focusedNode(); 2334 while (node) { 2335 if (node->isElementNode()) { 2336 const WebCore::AtomicString& value = static_cast<const Element*>(node)->getAttribute(spellcheckAttr); 2337 if (equalIgnoringCase(value, "true")) 2338 return true; 2339 if (equalIgnoringCase(value, "false")) 2340 return false; 2341 } 2342 node = node->parent(); 2343 } 2344 return true; 2345 } 2346 2347 void Editor::markMisspellings(const VisibleSelection& selection, RefPtr<Range>& firstMisspellingRange) 2348 { 2349 markMisspellingsOrBadGrammar(this, selection, true, firstMisspellingRange); 2350 } 2351 2352 void Editor::markBadGrammar(const VisibleSelection& selection) 2353 { 2354 #ifndef BUILDING_ON_TIGER 2355 RefPtr<Range> firstMisspellingRange; 2356 markMisspellingsOrBadGrammar(this, selection, false, firstMisspellingRange); 2357 #else 2358 UNUSED_PARAM(selection); 2359 #endif 2360 } 2361 2362 #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 2363 2364 static inline bool isAmbiguousBoundaryCharacter(UChar character) 2365 { 2366 // These are characters that can behave as word boundaries, but can appear within words. 2367 // If they are just typed, i.e. if they are immediately followed by a caret, we want to delay text checking until the next character has been typed. 2368 // FIXME: this is required until 6853027 is fixed and text checking can do this for us. 2369 return character == '\'' || character == rightSingleQuotationMark || character == hebrewPunctuationGershayim; 2370 } 2371 2372 void Editor::markAllMisspellingsAndBadGrammarInRanges(bool markSpelling, Range* spellingRange, bool markGrammar, Range* grammarRange, bool performTextCheckingReplacements) 2373 { 2374 // This function is called with selections already expanded to word boundaries. 2375 ExceptionCode ec = 0; 2376 if (!client() || !spellingRange || (markGrammar && !grammarRange)) 2377 return; 2378 2379 // If we're not in an editable node, bail. 2380 Node* editableNode = spellingRange->startContainer(); 2381 if (!editableNode || !editableNode->isContentEditable()) 2382 return; 2383 2384 if (!spellCheckingEnabledInFocusedNode()) 2385 return; 2386 2387 // Expand the range to encompass entire paragraphs, since text checking needs that much context. 2388 int spellingRangeStartOffset = 0; 2389 int spellingRangeEndOffset = 0; 2390 int grammarRangeStartOffset = 0; 2391 int grammarRangeEndOffset = 0; 2392 int offsetDueToReplacement = 0; 2393 int paragraphLength = 0; 2394 int selectionOffset = 0; 2395 int ambiguousBoundaryOffset = -1; 2396 bool selectionChanged = false; 2397 bool restoreSelectionAfterChange = false; 2398 bool adjustSelectionForParagraphBoundaries = false; 2399 String paragraphString; 2400 RefPtr<Range> paragraphRange; 2401 2402 if (markGrammar) { 2403 // The spelling range should be contained in the paragraph-aligned extension of the grammar range. 2404 paragraphRange = paragraphAlignedRangeForRange(grammarRange, grammarRangeStartOffset, paragraphString); 2405 RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), spellingRange->startPosition()); 2406 spellingRangeStartOffset = TextIterator::rangeLength(offsetAsRange.get()); 2407 grammarRangeEndOffset = grammarRangeStartOffset + TextIterator::rangeLength(grammarRange); 2408 } else { 2409 paragraphRange = paragraphAlignedRangeForRange(spellingRange, spellingRangeStartOffset, paragraphString); 2410 } 2411 spellingRangeEndOffset = spellingRangeStartOffset + TextIterator::rangeLength(spellingRange); 2412 paragraphLength = paragraphString.length(); 2413 if (paragraphLength <= 0 || (spellingRangeStartOffset >= spellingRangeEndOffset && (!markGrammar || grammarRangeStartOffset >= grammarRangeEndOffset))) 2414 return; 2415 2416 if (performTextCheckingReplacements) { 2417 if (m_frame->selection()->selectionType() == VisibleSelection::CaretSelection) { 2418 // Attempt to save the caret position so we can restore it later if needed 2419 RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), paragraphRange->startPosition()); 2420 Position caretPosition = m_frame->selection()->end(); 2421 offsetAsRange->setEnd(caretPosition.containerNode(), caretPosition.computeOffsetInContainerNode(), ec); 2422 if (!ec) { 2423 selectionOffset = TextIterator::rangeLength(offsetAsRange.get()); 2424 restoreSelectionAfterChange = true; 2425 if (selectionOffset > 0 && (selectionOffset > paragraphLength || paragraphString[selectionOffset - 1] == newlineCharacter)) 2426 adjustSelectionForParagraphBoundaries = true; 2427 if (selectionOffset > 0 && selectionOffset <= paragraphLength && isAmbiguousBoundaryCharacter(paragraphString[selectionOffset - 1])) 2428 ambiguousBoundaryOffset = selectionOffset - 1; 2429 } 2430 } 2431 } 2432 2433 Vector<TextCheckingResult> results; 2434 uint64_t checkingTypes = 0; 2435 if (markSpelling) 2436 checkingTypes |= TextCheckingTypeSpelling; 2437 if (markGrammar) 2438 checkingTypes |= TextCheckingTypeGrammar; 2439 if (performTextCheckingReplacements) { 2440 if (isAutomaticLinkDetectionEnabled()) 2441 checkingTypes |= TextCheckingTypeLink; 2442 if (isAutomaticQuoteSubstitutionEnabled()) 2443 checkingTypes |= TextCheckingTypeQuote; 2444 if (isAutomaticDashSubstitutionEnabled()) 2445 checkingTypes |= TextCheckingTypeDash; 2446 if (isAutomaticTextReplacementEnabled()) 2447 checkingTypes |= TextCheckingTypeReplacement; 2448 if (markSpelling && isAutomaticSpellingCorrectionEnabled()) 2449 checkingTypes |= TextCheckingTypeCorrection; 2450 } 2451 client()->checkTextOfParagraph(paragraphString.characters(), paragraphLength, checkingTypes, results); 2452 2453 for (unsigned i = 0; i < results.size(); i++) { 2454 const TextCheckingResult* result = &results[i]; 2455 int resultLocation = result->location + offsetDueToReplacement; 2456 int resultLength = result->length; 2457 if (markSpelling && result->type == TextCheckingTypeSpelling && resultLocation >= spellingRangeStartOffset && resultLocation + resultLength <= spellingRangeEndOffset) { 2458 ASSERT(resultLength > 0 && resultLocation >= 0); 2459 RefPtr<Range> misspellingRange = TextIterator::subrange(spellingRange, resultLocation - spellingRangeStartOffset, resultLength); 2460 misspellingRange->startContainer(ec)->document()->addMarker(misspellingRange.get(), DocumentMarker::Spelling); 2461 } else if (markGrammar && result->type == TextCheckingTypeGrammar && resultLocation < grammarRangeEndOffset && resultLocation + resultLength > grammarRangeStartOffset) { 2462 ASSERT(resultLength > 0 && resultLocation >= 0); 2463 for (unsigned j = 0; j < result->details.size(); j++) { 2464 const GrammarDetail* detail = &result->details[j]; 2465 ASSERT(detail->length > 0 && detail->location >= 0); 2466 if (resultLocation + detail->location >= grammarRangeStartOffset && resultLocation + detail->location + detail->length <= grammarRangeEndOffset) { 2467 RefPtr<Range> badGrammarRange = TextIterator::subrange(grammarRange, resultLocation + detail->location - grammarRangeStartOffset, detail->length); 2468 grammarRange->startContainer(ec)->document()->addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail->userDescription); 2469 } 2470 } 2471 } else if (performTextCheckingReplacements && resultLocation + resultLength <= spellingRangeEndOffset && resultLocation + resultLength >= spellingRangeStartOffset && 2472 (result->type == TextCheckingTypeLink 2473 || result->type == TextCheckingTypeQuote 2474 || result->type == TextCheckingTypeDash 2475 || result->type == TextCheckingTypeReplacement 2476 || result->type == TextCheckingTypeCorrection)) { 2477 // In this case the result range just has to touch the spelling range, so we can handle replacing non-word text such as punctuation. 2478 ASSERT(resultLength > 0 && resultLocation >= 0); 2479 int replacementLength = result->replacement.length(); 2480 bool doReplacement = (replacementLength > 0); 2481 RefPtr<Range> rangeToReplace = TextIterator::subrange(paragraphRange.get(), resultLocation, resultLength); 2482 VisibleSelection selectionToReplace(rangeToReplace.get(), DOWNSTREAM); 2483 2484 // avoid correcting text after an ambiguous boundary character has been typed 2485 // FIXME: this is required until 6853027 is fixed and text checking can do this for us 2486 if (ambiguousBoundaryOffset >= 0 && resultLocation + resultLength == ambiguousBoundaryOffset) 2487 doReplacement = false; 2488 2489 // adding links should be done only immediately after they are typed 2490 if (result->type == TextCheckingTypeLink && selectionOffset > resultLocation + resultLength + 1) 2491 doReplacement = false; 2492 2493 // Don't correct spelling in an already-corrected word. 2494 if (doReplacement && result->type == TextCheckingTypeCorrection) { 2495 Node* node = rangeToReplace->startContainer(); 2496 int startOffset = rangeToReplace->startOffset(); 2497 int endOffset = startOffset + replacementLength; 2498 Vector<DocumentMarker> markers = node->document()->markersForNode(node); 2499 size_t markerCount = markers.size(); 2500 for (size_t i = 0; i < markerCount; ++i) { 2501 const DocumentMarker& marker = markers[i]; 2502 if (marker.type == DocumentMarker::Replacement && static_cast<int>(marker.startOffset) < endOffset && static_cast<int>(marker.endOffset) > startOffset) { 2503 doReplacement = false; 2504 break; 2505 } 2506 if (static_cast<int>(marker.startOffset) >= endOffset) 2507 break; 2508 } 2509 } 2510 if (doReplacement && selectionToReplace != m_frame->selection()->selection()) { 2511 if (m_frame->shouldChangeSelection(selectionToReplace)) { 2512 m_frame->selection()->setSelection(selectionToReplace); 2513 selectionChanged = true; 2514 } else { 2515 doReplacement = false; 2516 } 2517 } 2518 if (doReplacement) { 2519 if (result->type == TextCheckingTypeLink) { 2520 restoreSelectionAfterChange = false; 2521 if (canEditRichly()) 2522 applyCommand(CreateLinkCommand::create(m_frame->document(), result->replacement)); 2523 } else if (canEdit() && shouldInsertText(result->replacement, rangeToReplace.get(), EditorInsertActionTyped)) { 2524 String replacedString; 2525 if (result->type == TextCheckingTypeCorrection) 2526 replacedString = plainText(rangeToReplace.get()); 2527 replaceSelectionWithText(result->replacement, false, false); 2528 spellingRangeEndOffset += replacementLength - resultLength; 2529 offsetDueToReplacement += replacementLength - resultLength; 2530 if (resultLocation < selectionOffset) 2531 selectionOffset += replacementLength - resultLength; 2532 if (result->type == TextCheckingTypeCorrection) { 2533 // Add a marker so that corrections can easily be undone and won't be re-corrected. 2534 RefPtr<Range> replacedRange = TextIterator::subrange(paragraphRange.get(), resultLocation, replacementLength); 2535 replacedRange->startContainer()->document()->addMarker(replacedRange.get(), DocumentMarker::Replacement, replacedString); 2536 } 2537 } 2538 } 2539 } 2540 } 2541 2542 if (selectionChanged) { 2543 // Restore the caret position if we have made any replacements 2544 setEnd(paragraphRange.get(), endOfParagraph(startOfNextParagraph(paragraphRange->startPosition()))); 2545 int newLength = TextIterator::rangeLength(paragraphRange.get()); 2546 if (restoreSelectionAfterChange && selectionOffset >= 0 && selectionOffset <= newLength) { 2547 RefPtr<Range> selectionRange = TextIterator::subrange(paragraphRange.get(), 0, selectionOffset); 2548 m_frame->selection()->moveTo(selectionRange->endPosition(), DOWNSTREAM); 2549 if (adjustSelectionForParagraphBoundaries) 2550 m_frame->selection()->modify(SelectionController::MOVE, SelectionController::FORWARD, CharacterGranularity); 2551 } else { 2552 // If this fails for any reason, the fallback is to go one position beyond the last replacement 2553 m_frame->selection()->moveTo(m_frame->selection()->end()); 2554 m_frame->selection()->modify(SelectionController::MOVE, SelectionController::FORWARD, CharacterGranularity); 2555 } 2556 } 2557 } 2558 2559 void Editor::changeBackToReplacedString(const String& replacedString) 2560 { 2561 if (replacedString.isEmpty()) 2562 return; 2563 2564 RefPtr<Range> selection = selectedRange(); 2565 if (!shouldInsertText(replacedString, selection.get(), EditorInsertActionPasted)) 2566 return; 2567 2568 String paragraphString; 2569 int selectionOffset; 2570 RefPtr<Range> paragraphRange = paragraphAlignedRangeForRange(selection.get(), selectionOffset, paragraphString); 2571 replaceSelectionWithText(replacedString, false, false); 2572 RefPtr<Range> changedRange = TextIterator::subrange(paragraphRange.get(), selectionOffset, replacedString.length()); 2573 changedRange->startContainer()->document()->addMarker(changedRange.get(), DocumentMarker::Replacement, String()); 2574 } 2575 2576 #endif 2577 2578 void Editor::markMisspellingsAndBadGrammar(const VisibleSelection& spellingSelection, bool markGrammar, const VisibleSelection& grammarSelection) 2579 { 2580 #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 2581 if (!isContinuousSpellCheckingEnabled()) 2582 return; 2583 markAllMisspellingsAndBadGrammarInRanges(true, spellingSelection.toNormalizedRange().get(), markGrammar && isGrammarCheckingEnabled(), grammarSelection.toNormalizedRange().get(), false); 2584 #else 2585 RefPtr<Range> firstMisspellingRange; 2586 markMisspellings(spellingSelection, firstMisspellingRange); 2587 if (markGrammar) 2588 markBadGrammar(grammarSelection); 2589 #endif 2590 } 2591 2592 PassRefPtr<Range> Editor::rangeForPoint(const IntPoint& windowPoint) 2593 { 2594 Document* document = m_frame->documentAtPoint(windowPoint); 2595 if (!document) 2596 return 0; 2597 2598 Frame* frame = document->frame(); 2599 ASSERT(frame); 2600 FrameView* frameView = frame->view(); 2601 if (!frameView) 2602 return 0; 2603 IntPoint framePoint = frameView->windowToContents(windowPoint); 2604 VisibleSelection selection(frame->visiblePositionForPoint(framePoint)); 2605 return avoidIntersectionWithNode(selection.toNormalizedRange().get(), m_deleteButtonController->containerElement()); 2606 } 2607 2608 void Editor::revealSelectionAfterEditingOperation() 2609 { 2610 if (m_ignoreCompositionSelectionChange) 2611 return; 2612 2613 m_frame->revealSelection(ScrollAlignment::alignToEdgeIfNeeded); 2614 } 2615 2616 void Editor::setIgnoreCompositionSelectionChange(bool ignore) 2617 { 2618 if (m_ignoreCompositionSelectionChange == ignore) 2619 return; 2620 2621 m_ignoreCompositionSelectionChange = ignore; 2622 if (!ignore) 2623 revealSelectionAfterEditingOperation(); 2624 } 2625 2626 PassRefPtr<Range> Editor::compositionRange() const 2627 { 2628 if (!m_compositionNode) 2629 return 0; 2630 unsigned length = m_compositionNode->length(); 2631 unsigned start = min(m_compositionStart, length); 2632 unsigned end = min(max(start, m_compositionEnd), length); 2633 if (start >= end) 2634 return 0; 2635 return Range::create(m_compositionNode->document(), m_compositionNode.get(), start, m_compositionNode.get(), end); 2636 } 2637 2638 bool Editor::getCompositionSelection(unsigned& selectionStart, unsigned& selectionEnd) const 2639 { 2640 if (!m_compositionNode) 2641 return false; 2642 Position start = m_frame->selection()->start(); 2643 if (start.node() != m_compositionNode) 2644 return false; 2645 Position end = m_frame->selection()->end(); 2646 if (end.node() != m_compositionNode) 2647 return false; 2648 2649 if (static_cast<unsigned>(start.deprecatedEditingOffset()) < m_compositionStart) 2650 return false; 2651 if (static_cast<unsigned>(end.deprecatedEditingOffset()) > m_compositionEnd) 2652 return false; 2653 2654 selectionStart = start.deprecatedEditingOffset() - m_compositionStart; 2655 selectionEnd = start.deprecatedEditingOffset() - m_compositionEnd; 2656 return true; 2657 } 2658 2659 void Editor::transpose() 2660 { 2661 if (!canEdit()) 2662 return; 2663 2664 VisibleSelection selection = m_frame->selection()->selection(); 2665 if (!selection.isCaret()) 2666 return; 2667 2668 // Make a selection that goes back one character and forward two characters. 2669 VisiblePosition caret = selection.visibleStart(); 2670 VisiblePosition next = isEndOfParagraph(caret) ? caret : caret.next(); 2671 VisiblePosition previous = next.previous(); 2672 if (next == previous) 2673 return; 2674 previous = previous.previous(); 2675 if (!inSameParagraph(next, previous)) 2676 return; 2677 RefPtr<Range> range = makeRange(previous, next); 2678 if (!range) 2679 return; 2680 VisibleSelection newSelection(range.get(), DOWNSTREAM); 2681 2682 // Transpose the two characters. 2683 String text = plainText(range.get()); 2684 if (text.length() != 2) 2685 return; 2686 String transposed = text.right(1) + text.left(1); 2687 2688 // Select the two characters. 2689 if (newSelection != m_frame->selection()->selection()) { 2690 if (!m_frame->shouldChangeSelection(newSelection)) 2691 return; 2692 m_frame->selection()->setSelection(newSelection); 2693 } 2694 2695 // Insert the transposed characters. 2696 if (!shouldInsertText(transposed, range.get(), EditorInsertActionTyped)) 2697 return; 2698 replaceSelectionWithText(transposed, false, false); 2699 } 2700 2701 void Editor::addToKillRing(Range* range, bool prepend) 2702 { 2703 if (m_shouldStartNewKillRingSequence) 2704 startNewKillRingSequence(); 2705 2706 String text = m_frame->displayStringModifiedByEncoding(plainText(range)); 2707 if (prepend) 2708 prependToKillRing(text); 2709 else 2710 appendToKillRing(text); 2711 m_shouldStartNewKillRingSequence = false; 2712 } 2713 2714 #if !PLATFORM(MAC) 2715 2716 void Editor::appendToKillRing(const String&) 2717 { 2718 } 2719 2720 void Editor::prependToKillRing(const String&) 2721 { 2722 } 2723 2724 String Editor::yankFromKillRing() 2725 { 2726 return String(); 2727 } 2728 2729 void Editor::startNewKillRingSequence() 2730 { 2731 } 2732 2733 void Editor::setKillRingToYankedState() 2734 { 2735 } 2736 2737 #endif 2738 2739 bool Editor::insideVisibleArea(const IntPoint& point) const 2740 { 2741 if (m_frame->excludeFromTextSearch()) 2742 return false; 2743 2744 // Right now, we only check the visibility of a point for disconnected frames. For all other 2745 // frames, we assume visibility. 2746 Frame* frame = m_frame->isDisconnected() ? m_frame : m_frame->tree()->top(true); 2747 if (!frame->isDisconnected()) 2748 return true; 2749 2750 RenderPart* renderer = frame->ownerRenderer(); 2751 if (!renderer) 2752 return false; 2753 2754 RenderBlock* container = renderer->containingBlock(); 2755 if (!(container->style()->overflowX() == OHIDDEN || container->style()->overflowY() == OHIDDEN)) 2756 return true; 2757 2758 IntRect rectInPageCoords = container->overflowClipRect(0, 0); 2759 IntRect rectInFrameCoords = IntRect(renderer->x() * -1, renderer->y() * -1, 2760 rectInPageCoords.width(), rectInPageCoords.height()); 2761 2762 return rectInFrameCoords.contains(point); 2763 } 2764 2765 bool Editor::insideVisibleArea(Range* range) const 2766 { 2767 if (!range) 2768 return true; 2769 2770 if (m_frame->excludeFromTextSearch()) 2771 return false; 2772 2773 // Right now, we only check the visibility of a range for disconnected frames. For all other 2774 // frames, we assume visibility. 2775 Frame* frame = m_frame->isDisconnected() ? m_frame : m_frame->tree()->top(true); 2776 if (!frame->isDisconnected()) 2777 return true; 2778 2779 RenderPart* renderer = frame->ownerRenderer(); 2780 if (!renderer) 2781 return false; 2782 2783 RenderBlock* container = renderer->containingBlock(); 2784 if (!(container->style()->overflowX() == OHIDDEN || container->style()->overflowY() == OHIDDEN)) 2785 return true; 2786 2787 IntRect rectInPageCoords = container->overflowClipRect(0, 0); 2788 IntRect rectInFrameCoords = IntRect(renderer->x() * -1, renderer->y() * -1, 2789 rectInPageCoords.width(), rectInPageCoords.height()); 2790 IntRect resultRect = range->boundingBox(); 2791 2792 return rectInFrameCoords.contains(resultRect); 2793 } 2794 2795 PassRefPtr<Range> Editor::firstVisibleRange(const String& target, bool caseFlag) 2796 { 2797 RefPtr<Range> searchRange(rangeOfContents(m_frame->document())); 2798 RefPtr<Range> resultRange = findPlainText(searchRange.get(), target, true, caseFlag); 2799 ExceptionCode ec = 0; 2800 2801 while (!insideVisibleArea(resultRange.get())) { 2802 searchRange->setStartAfter(resultRange->endContainer(), ec); 2803 if (searchRange->startContainer() == searchRange->endContainer()) 2804 return Range::create(m_frame->document()); 2805 resultRange = findPlainText(searchRange.get(), target, true, caseFlag); 2806 } 2807 2808 return resultRange; 2809 } 2810 2811 PassRefPtr<Range> Editor::lastVisibleRange(const String& target, bool caseFlag) 2812 { 2813 RefPtr<Range> searchRange(rangeOfContents(m_frame->document())); 2814 RefPtr<Range> resultRange = findPlainText(searchRange.get(), target, false, caseFlag); 2815 ExceptionCode ec = 0; 2816 2817 while (!insideVisibleArea(resultRange.get())) { 2818 searchRange->setEndBefore(resultRange->startContainer(), ec); 2819 if (searchRange->startContainer() == searchRange->endContainer()) 2820 return Range::create(m_frame->document()); 2821 resultRange = findPlainText(searchRange.get(), target, false, caseFlag); 2822 } 2823 2824 return resultRange; 2825 } 2826 2827 PassRefPtr<Range> Editor::nextVisibleRange(Range* currentRange, const String& target, bool forward, bool caseFlag, bool wrapFlag) 2828 { 2829 if (m_frame->excludeFromTextSearch()) 2830 return Range::create(m_frame->document()); 2831 2832 RefPtr<Range> resultRange = currentRange; 2833 RefPtr<Range> searchRange(rangeOfContents(m_frame->document())); 2834 ExceptionCode ec = 0; 2835 2836 for ( ; !insideVisibleArea(resultRange.get()); resultRange = findPlainText(searchRange.get(), target, forward, caseFlag)) { 2837 if (resultRange->collapsed(ec)) { 2838 if (!resultRange->startContainer()->isInShadowTree()) 2839 break; 2840 searchRange = rangeOfContents(m_frame->document()); 2841 if (forward) 2842 searchRange->setStartAfter(resultRange->startContainer()->shadowAncestorNode(), ec); 2843 else 2844 searchRange->setEndBefore(resultRange->startContainer()->shadowAncestorNode(), ec); 2845 continue; 2846 } 2847 2848 if (forward) 2849 searchRange->setStartAfter(resultRange->endContainer(), ec); 2850 else 2851 searchRange->setEndBefore(resultRange->startContainer(), ec); 2852 2853 Node* shadowTreeRoot = searchRange->shadowTreeRootNode(); 2854 if (searchRange->collapsed(ec) && shadowTreeRoot) { 2855 if (forward) 2856 searchRange->setEnd(shadowTreeRoot, shadowTreeRoot->childNodeCount(), ec); 2857 else 2858 searchRange->setStartBefore(shadowTreeRoot, ec); 2859 } 2860 2861 if (searchRange->startContainer()->isDocumentNode() && searchRange->endContainer()->isDocumentNode()) 2862 break; 2863 } 2864 2865 if (insideVisibleArea(resultRange.get())) 2866 return resultRange; 2867 2868 if (!wrapFlag) 2869 return Range::create(m_frame->document()); 2870 2871 if (forward) 2872 return firstVisibleRange(target, caseFlag); 2873 2874 return lastVisibleRange(target, caseFlag); 2875 } 2876 2877 void Editor::changeSelectionAfterCommand(const VisibleSelection& newSelection, bool closeTyping, bool clearTypingStyle, EditCommand* cmd) 2878 { 2879 // If there is no selection change, don't bother sending shouldChangeSelection, but still call setSelection, 2880 // because there is work that it must do in this situation. 2881 // The old selection can be invalid here and calling shouldChangeSelection can produce some strange calls. 2882 // See <rdar://problem/5729315> Some shouldChangeSelectedDOMRange contain Ranges for selections that are no longer valid 2883 bool selectionDidNotChangeDOMPosition = newSelection == m_frame->selection()->selection(); 2884 if (selectionDidNotChangeDOMPosition || m_frame->shouldChangeSelection(newSelection)) 2885 m_frame->selection()->setSelection(newSelection, closeTyping, clearTypingStyle); 2886 2887 // Some kinds of deletes and line break insertions change the selection's position within the document without 2888 // changing its position within the DOM. For example when you press return in the following (the caret is marked by ^): 2889 // <div contentEditable="true"><div>^Hello</div></div> 2890 // WebCore inserts <div><br></div> *before* the current block, which correctly moves the paragraph down but which doesn't 2891 // change the caret's DOM position (["hello", 0]). In these situations the above SelectionController::setSelection call 2892 // does not call EditorClient::respondToChangedSelection(), which, on the Mac, sends selection change notifications and 2893 // starts a new kill ring sequence, but we want to do these things (matches AppKit). 2894 if (selectionDidNotChangeDOMPosition && cmd->isTypingCommand()) 2895 client()->respondToChangedSelection(); 2896 } 2897 2898 } // namespace WebCore 2899