2 * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved.
3 * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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.
30 #include "AXObjectCache.h"
31 #include "ApplyStyleCommand.h"
32 #include "CSSComputedStyleDeclaration.h"
33 #include "CSSProperty.h"
34 #include "CSSPropertyNames.h"
35 #include "CSSValueKeywords.h"
36 #include "ClipboardEvent.h"
37 #include "DeleteButtonController.h"
38 #include "DeleteSelectionCommand.h"
39 #include "DocLoader.h"
40 #include "DocumentFragment.h"
41 #include "EditorClient.h"
42 #include "EventHandler.h"
43 #include "EventNames.h"
44 #include "FocusController.h"
46 #include "FrameTree.h"
47 #include "FrameView.h"
48 #include "HTMLInputElement.h"
49 #include "HTMLTextAreaElement.h"
50 #include "HitTestResult.h"
51 #include "IndentOutdentCommand.h"
52 #include "InsertListCommand.h"
53 #include "KeyboardEvent.h"
54 #include "ModifySelectionListLevel.h"
56 #include "Pasteboard.h"
57 #include "RemoveFormatCommand.h"
58 #include "RenderBlock.h"
59 #include "RenderPart.h"
60 #include "ReplaceSelectionCommand.h"
63 #include "TextIterator.h"
64 #include "TypingCommand.h"
65 #include "htmlediting.h"
67 #include "visible_units.h"
68 #include <wtf/UnusedParam.h>
73 using namespace HTMLNames;
75 // When an event handler has moved the selection outside of a text control
76 // we should use the target control's selection for this editing operation.
77 VisibleSelection Editor::selectionForCommand(Event* event)
79 VisibleSelection selection = m_frame->selection()->selection();
82 // If the target is a text control, and the current selection is outside of its shadow tree,
83 // then use the saved selection for that text control.
84 Node* target = event->target()->toNode();
85 Node* selectionStart = selection.start().node();
86 if (target && (!selectionStart || target->shadowAncestorNode() != selectionStart->shadowAncestorNode())) {
87 if (target->hasTagName(inputTag) && static_cast<HTMLInputElement*>(target)->isTextField())
88 return static_cast<HTMLInputElement*>(target)->selection();
89 if (target->hasTagName(textareaTag))
90 return static_cast<HTMLTextAreaElement*>(target)->selection();
95 EditorClient* Editor::client() const
97 if (Page* page = m_frame->page())
98 return page->editorClient();
102 void Editor::handleKeyboardEvent(KeyboardEvent* event)
104 if (EditorClient* c = client())
105 c->handleKeyboardEvent(event);
108 void Editor::handleInputMethodKeydown(KeyboardEvent* event)
110 if (EditorClient* c = client())
111 c->handleInputMethodKeydown(event);
114 bool Editor::canEdit() const
116 return m_frame->selection()->isContentEditable();
119 bool Editor::canEditRichly() const
121 return m_frame->selection()->isContentRichlyEditable();
124 // WinIE uses onbeforecut and onbeforepaste to enables the cut and paste menu items. They
125 // also send onbeforecopy, apparently for symmetry, but it doesn't affect the menu items.
126 // We need to use onbeforecopy as a real menu enabler because we allow elements that are not
127 // normally selectable to implement copy/paste (like divs, or a document body).
129 bool Editor::canDHTMLCut()
131 return !m_frame->selection()->isInPasswordField() && !dispatchCPPEvent(eventNames().beforecutEvent, ClipboardNumb);
134 bool Editor::canDHTMLCopy()
136 return !m_frame->selection()->isInPasswordField() && !dispatchCPPEvent(eventNames().beforecopyEvent, ClipboardNumb);
139 bool Editor::canDHTMLPaste()
141 return !dispatchCPPEvent(eventNames().beforepasteEvent, ClipboardNumb);
144 bool Editor::canCut() const
146 return canCopy() && canDelete();
149 static HTMLImageElement* imageElementFromImageDocument(Document* document)
153 if (!document->isImageDocument())
156 HTMLElement* body = document->body();
160 Node* node = body->firstChild();
163 if (!node->hasTagName(imgTag))
165 return static_cast<HTMLImageElement*>(node);
168 bool Editor::canCopy() const
170 if (imageElementFromImageDocument(m_frame->document()))
172 SelectionController* selection = m_frame->selection();
173 return selection->isRange() && !selection->isInPasswordField();
176 bool Editor::canPaste() const
181 bool Editor::canDelete() const
183 SelectionController* selection = m_frame->selection();
184 return selection->isRange() && selection->isContentEditable();
187 bool Editor::canDeleteRange(Range* range) const
189 ExceptionCode ec = 0;
190 Node* startContainer = range->startContainer(ec);
191 Node* endContainer = range->endContainer(ec);
192 if (!startContainer || !endContainer)
195 if (!startContainer->isContentEditable() || !endContainer->isContentEditable())
198 if (range->collapsed(ec)) {
199 VisiblePosition start(startContainer, range->startOffset(ec), DOWNSTREAM);
200 VisiblePosition previous = start.previous();
201 // FIXME: We sometimes allow deletions at the start of editable roots, like when the caret is in an empty list item.
202 if (previous.isNull() || previous.deepEquivalent().node()->rootEditableElement() != startContainer->rootEditableElement())
208 bool Editor::smartInsertDeleteEnabled()
210 return client() && client()->smartInsertDeleteEnabled();
213 bool Editor::canSmartCopyOrDelete()
215 return client() && client()->smartInsertDeleteEnabled() && m_frame->selectionGranularity() == WordGranularity;
218 bool Editor::isSelectTrailingWhitespaceEnabled()
220 return client() && client()->isSelectTrailingWhitespaceEnabled();
223 bool Editor::deleteWithDirection(SelectionController::EDirection direction, TextGranularity granularity, bool killRing, bool isTypingAction)
228 if (m_frame->selection()->isRange()) {
229 if (isTypingAction) {
230 TypingCommand::deleteKeyPressed(m_frame->document(), canSmartCopyOrDelete(), granularity);
231 revealSelectionAfterEditingOperation();
234 addToKillRing(selectedRange().get(), false);
235 deleteSelectionWithSmartDelete(canSmartCopyOrDelete());
236 // Implicitly calls revealSelectionAfterEditingOperation().
240 case SelectionController::FORWARD:
241 case SelectionController::RIGHT:
242 TypingCommand::forwardDeleteKeyPressed(m_frame->document(), canSmartCopyOrDelete(), granularity, killRing);
244 case SelectionController::BACKWARD:
245 case SelectionController::LEFT:
246 TypingCommand::deleteKeyPressed(m_frame->document(), canSmartCopyOrDelete(), granularity, killRing);
249 revealSelectionAfterEditingOperation();
252 // FIXME: We should to move this down into deleteKeyPressed.
253 // clear the "start new kill ring sequence" setting, because it was set to true
254 // when the selection was updated by deleting the range
256 setStartNewKillRingSequence(false);
261 void Editor::deleteSelectionWithSmartDelete(bool smartDelete)
263 if (m_frame->selection()->isNone())
266 applyCommand(DeleteSelectionCommand::create(m_frame->document(), smartDelete));
269 void Editor::pasteAsPlainTextWithPasteboard(Pasteboard* pasteboard)
271 String text = pasteboard->plainText(m_frame);
272 if (client() && client()->shouldInsertText(text, selectedRange().get(), EditorInsertActionPasted))
273 replaceSelectionWithText(text, false, canSmartReplaceWithPasteboard(pasteboard));
276 void Editor::pasteWithPasteboard(Pasteboard* pasteboard, bool allowPlainText)
278 RefPtr<Range> range = selectedRange();
280 RefPtr<DocumentFragment> fragment = pasteboard->documentFragment(m_frame, range, allowPlainText, chosePlainText);
281 if (fragment && shouldInsertFragment(fragment, range, EditorInsertActionPasted))
282 replaceSelectionWithFragment(fragment, false, canSmartReplaceWithPasteboard(pasteboard), chosePlainText);
285 bool Editor::canSmartReplaceWithPasteboard(Pasteboard* pasteboard)
287 return client() && client()->smartInsertDeleteEnabled() && pasteboard->canSmartReplace();
290 bool Editor::shouldInsertFragment(PassRefPtr<DocumentFragment> fragment, PassRefPtr<Range> replacingDOMRange, EditorInsertAction givenAction)
295 Node* child = fragment->firstChild();
296 if (child && fragment->lastChild() == child && child->isCharacterDataNode())
297 return client()->shouldInsertText(static_cast<CharacterData*>(child)->data(), replacingDOMRange.get(), givenAction);
299 return client()->shouldInsertNode(fragment.get(), replacingDOMRange.get(), givenAction);
302 void Editor::replaceSelectionWithFragment(PassRefPtr<DocumentFragment> fragment, bool selectReplacement, bool smartReplace, bool matchStyle)
304 if (m_frame->selection()->isNone() || !fragment)
307 applyCommand(ReplaceSelectionCommand::create(m_frame->document(), fragment, selectReplacement, smartReplace, matchStyle));
308 revealSelectionAfterEditingOperation();
311 void Editor::replaceSelectionWithText(const String& text, bool selectReplacement, bool smartReplace)
313 replaceSelectionWithFragment(createFragmentFromText(selectedRange().get(), text), selectReplacement, smartReplace, true);
316 PassRefPtr<Range> Editor::selectedRange()
320 return m_frame->selection()->toNormalizedRange();
323 bool Editor::shouldDeleteRange(Range* range) const
326 if (!range || range->collapsed(ec))
329 if (!canDeleteRange(range))
332 return client() && client()->shouldDeleteRange(range);
335 bool Editor::tryDHTMLCopy()
337 if (m_frame->selection()->isInPasswordField())
340 // Must be done before oncopy adds types and data to the pboard,
341 // also done for security, as it erases data from the last copy/paste.
342 Pasteboard::generalPasteboard()->clear();
344 return !dispatchCPPEvent(eventNames().copyEvent, ClipboardWritable);
347 bool Editor::tryDHTMLCut()
349 if (m_frame->selection()->isInPasswordField())
352 // Must be done before oncut adds types and data to the pboard,
353 // also done for security, as it erases data from the last copy/paste.
354 Pasteboard::generalPasteboard()->clear();
356 return !dispatchCPPEvent(eventNames().cutEvent, ClipboardWritable);
359 bool Editor::tryDHTMLPaste()
361 return !dispatchCPPEvent(eventNames().pasteEvent, ClipboardReadable);
364 void Editor::writeSelectionToPasteboard(Pasteboard* pasteboard)
366 pasteboard->writeSelection(selectedRange().get(), canSmartCopyOrDelete(), m_frame);
369 bool Editor::shouldInsertText(const String& text, Range* range, EditorInsertAction action) const
371 return client() && client()->shouldInsertText(text, range, action);
374 bool Editor::shouldShowDeleteInterface(HTMLElement* element) const
376 return client() && client()->shouldShowDeleteInterface(element);
379 void Editor::respondToChangedSelection(const VisibleSelection& oldSelection)
382 client()->respondToChangedSelection();
383 m_deleteButtonController->respondToChangedSelection(oldSelection);
386 void Editor::respondToChangedContents(const VisibleSelection& endingSelection)
388 if (AXObjectCache::accessibilityEnabled()) {
389 Node* node = endingSelection.start().node();
391 m_frame->document()->axObjectCache()->postNotification(node->renderer(), "AXValueChanged");
395 client()->respondToChangedContents();
398 const SimpleFontData* Editor::fontForSelection(bool& hasMultipleFonts) const
401 hasMultipleFonts = false;
403 if (!m_frame->selection()->isRange()) {
405 RenderStyle* style = m_frame->styleForSelectionStart(nodeToRemove); // sets nodeToRemove
407 const SimpleFontData* result = 0;
409 result = style->font().primaryFont();
413 nodeToRemove->remove(ec);
420 const SimpleFontData* font = 0;
422 RefPtr<Range> range = m_frame->selection()->toNormalizedRange();
423 Node* startNode = range->editingStartPosition().node();
425 Node* pastEnd = range->pastLastNode();
426 // In the loop below, n should eventually match pastEnd and not become nil, but we've seen at least one
427 // unreproducible case where this didn't happen, so check for nil also.
428 for (Node* n = startNode; n && n != pastEnd; n = n->traverseNextNode()) {
429 RenderObject *renderer = n->renderer();
432 // FIXME: Are there any node types that have renderers, but that we should be skipping?
433 const SimpleFontData* f = renderer->style()->font().primaryFont();
436 else if (font != f) {
437 hasMultipleFonts = true;
449 WritingDirection Editor::textDirectionForSelection(bool& hasNestedOrMultipleEmbeddings) const
451 hasNestedOrMultipleEmbeddings = true;
453 if (m_frame->selection()->isNone())
454 return NaturalWritingDirection;
456 Position pos = m_frame->selection()->selection().start().downstream();
458 Node* node = pos.node();
460 return NaturalWritingDirection;
463 if (m_frame->selection()->isRange()) {
464 end = m_frame->selection()->selection().end().upstream();
466 Node* pastLast = Range::create(m_frame->document(), rangeCompliantEquivalent(pos), rangeCompliantEquivalent(end))->pastLastNode();
467 for (Node* n = node; n && n != pastLast; n = n->traverseNextNode()) {
468 if (!n->isStyledElement())
471 RefPtr<CSSComputedStyleDeclaration> style = computedStyle(n);
472 RefPtr<CSSValue> unicodeBidi = style->getPropertyCSSValue(CSSPropertyUnicodeBidi);
476 ASSERT(unicodeBidi->isPrimitiveValue());
477 int unicodeBidiValue = static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent();
478 if (unicodeBidiValue == CSSValueEmbed || unicodeBidiValue == CSSValueBidiOverride)
479 return NaturalWritingDirection;
483 if (m_frame->selection()->isCaret()) {
484 if (CSSMutableStyleDeclaration *typingStyle = m_frame->typingStyle()) {
485 RefPtr<CSSValue> unicodeBidi = typingStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi);
487 ASSERT(unicodeBidi->isPrimitiveValue());
488 int unicodeBidiValue = static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent();
489 if (unicodeBidiValue == CSSValueEmbed) {
490 RefPtr<CSSValue> direction = typingStyle->getPropertyCSSValue(CSSPropertyDirection);
491 ASSERT(!direction || direction->isPrimitiveValue());
493 hasNestedOrMultipleEmbeddings = false;
494 return static_cast<CSSPrimitiveValue*>(direction.get())->getIdent() == CSSValueLtr ? LeftToRightWritingDirection : RightToLeftWritingDirection;
496 } else if (unicodeBidiValue == CSSValueNormal) {
497 hasNestedOrMultipleEmbeddings = false;
498 return NaturalWritingDirection;
502 node = m_frame->selection()->selection().visibleStart().deepEquivalent().node();
505 // 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
507 Node* block = enclosingBlock(node);
508 WritingDirection foundDirection = NaturalWritingDirection;
510 for (; node != block; node = node->parent()) {
511 if (!node->isStyledElement())
514 RefPtr<CSSComputedStyleDeclaration> style = computedStyle(node);
515 RefPtr<CSSValue> unicodeBidi = style->getPropertyCSSValue(CSSPropertyUnicodeBidi);
519 ASSERT(unicodeBidi->isPrimitiveValue());
520 int unicodeBidiValue = static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent();
521 if (unicodeBidiValue == CSSValueNormal)
524 if (unicodeBidiValue == CSSValueBidiOverride)
525 return NaturalWritingDirection;
527 ASSERT(unicodeBidiValue == CSSValueEmbed);
528 RefPtr<CSSValue> direction = style->getPropertyCSSValue(CSSPropertyDirection);
532 ASSERT(direction->isPrimitiveValue());
533 int directionValue = static_cast<CSSPrimitiveValue*>(direction.get())->getIdent();
534 if (directionValue != CSSValueLtr && directionValue != CSSValueRtl)
537 if (foundDirection != NaturalWritingDirection)
538 return NaturalWritingDirection;
540 // In the range case, make sure that the embedding element persists until the end of the range.
541 if (m_frame->selection()->isRange() && !end.node()->isDescendantOf(node))
542 return NaturalWritingDirection;
544 foundDirection = directionValue == CSSValueLtr ? LeftToRightWritingDirection : RightToLeftWritingDirection;
546 hasNestedOrMultipleEmbeddings = false;
547 return foundDirection;
550 bool Editor::hasBidiSelection() const
552 if (m_frame->selection()->isNone())
556 if (m_frame->selection()->isRange()) {
557 startNode = m_frame->selection()->selection().start().downstream().node();
558 Node* endNode = m_frame->selection()->selection().end().upstream().node();
559 if (enclosingBlock(startNode) != enclosingBlock(endNode))
562 startNode = m_frame->selection()->selection().visibleStart().deepEquivalent().node();
564 RenderObject* renderer = startNode->renderer();
565 while (renderer && !renderer->isRenderBlock())
566 renderer = renderer->parent();
571 RenderStyle* style = renderer->style();
572 if (style->direction() == RTL)
575 return toRenderBlock(renderer)->containsNonZeroBidiLevel();
578 TriState Editor::selectionUnorderedListState() const
580 if (m_frame->selection()->isCaret()) {
581 if (enclosingNodeWithTag(m_frame->selection()->selection().start(), ulTag))
583 } else if (m_frame->selection()->isRange()) {
584 Node* startNode = enclosingNodeWithTag(m_frame->selection()->selection().start(), ulTag);
585 Node* endNode = enclosingNodeWithTag(m_frame->selection()->selection().end(), ulTag);
586 if (startNode && endNode && startNode == endNode)
590 return FalseTriState;
593 TriState Editor::selectionOrderedListState() const
595 if (m_frame->selection()->isCaret()) {
596 if (enclosingNodeWithTag(m_frame->selection()->selection().start(), olTag))
598 } else if (m_frame->selection()->isRange()) {
599 Node* startNode = enclosingNodeWithTag(m_frame->selection()->selection().start(), olTag);
600 Node* endNode = enclosingNodeWithTag(m_frame->selection()->selection().end(), olTag);
601 if (startNode && endNode && startNode == endNode)
605 return FalseTriState;
608 PassRefPtr<Node> Editor::insertOrderedList()
610 if (!canEditRichly())
613 RefPtr<Node> newList = InsertListCommand::insertList(m_frame->document(), InsertListCommand::OrderedList);
614 revealSelectionAfterEditingOperation();
618 PassRefPtr<Node> Editor::insertUnorderedList()
620 if (!canEditRichly())
623 RefPtr<Node> newList = InsertListCommand::insertList(m_frame->document(), InsertListCommand::UnorderedList);
624 revealSelectionAfterEditingOperation();
628 bool Editor::canIncreaseSelectionListLevel()
630 return canEditRichly() && IncreaseSelectionListLevelCommand::canIncreaseSelectionListLevel(m_frame->document());
633 bool Editor::canDecreaseSelectionListLevel()
635 return canEditRichly() && DecreaseSelectionListLevelCommand::canDecreaseSelectionListLevel(m_frame->document());
638 PassRefPtr<Node> Editor::increaseSelectionListLevel()
640 if (!canEditRichly() || m_frame->selection()->isNone())
643 RefPtr<Node> newList = IncreaseSelectionListLevelCommand::increaseSelectionListLevel(m_frame->document());
644 revealSelectionAfterEditingOperation();
648 PassRefPtr<Node> Editor::increaseSelectionListLevelOrdered()
650 if (!canEditRichly() || m_frame->selection()->isNone())
653 PassRefPtr<Node> newList = IncreaseSelectionListLevelCommand::increaseSelectionListLevelOrdered(m_frame->document());
654 revealSelectionAfterEditingOperation();
658 PassRefPtr<Node> Editor::increaseSelectionListLevelUnordered()
660 if (!canEditRichly() || m_frame->selection()->isNone())
663 PassRefPtr<Node> newList = IncreaseSelectionListLevelCommand::increaseSelectionListLevelUnordered(m_frame->document());
664 revealSelectionAfterEditingOperation();
668 void Editor::decreaseSelectionListLevel()
670 if (!canEditRichly() || m_frame->selection()->isNone())
673 DecreaseSelectionListLevelCommand::decreaseSelectionListLevel(m_frame->document());
674 revealSelectionAfterEditingOperation();
677 void Editor::removeFormattingAndStyle()
679 applyCommand(RemoveFormatCommand::create(m_frame->document()));
682 void Editor::clearLastEditCommand()
684 m_lastEditCommand.clear();
687 // Returns whether caller should continue with "the default processing", which is the same as
688 // the event handler NOT setting the return value to false
689 bool Editor::dispatchCPPEvent(const AtomicString &eventType, ClipboardAccessPolicy policy)
691 Node* target = m_frame->selection()->start().element();
693 target = m_frame->document()->body();
696 target = target->shadowAncestorNode();
698 RefPtr<Clipboard> clipboard = newGeneralClipboard(policy);
700 ExceptionCode ec = 0;
701 RefPtr<Event> evt = ClipboardEvent::create(eventType, true, true, clipboard);
702 target->dispatchEvent(evt, ec);
703 bool noDefaultProcessing = evt->defaultPrevented();
705 // invalidate clipboard here for security
706 clipboard->setAccessPolicy(ClipboardNumb);
708 return !noDefaultProcessing;
711 void Editor::applyStyle(CSSStyleDeclaration* style, EditAction editingAction)
713 switch (m_frame->selection()->selectionType()) {
714 case VisibleSelection::NoSelection:
717 case VisibleSelection::CaretSelection:
718 m_frame->computeAndSetTypingStyle(style, editingAction);
720 case VisibleSelection::RangeSelection:
722 applyCommand(ApplyStyleCommand::create(m_frame->document(), style, editingAction));
727 bool Editor::shouldApplyStyle(CSSStyleDeclaration* style, Range* range)
729 return client()->shouldApplyStyle(style, range);
732 void Editor::applyParagraphStyle(CSSStyleDeclaration* style, EditAction editingAction)
734 switch (m_frame->selection()->selectionType()) {
735 case VisibleSelection::NoSelection:
738 case VisibleSelection::CaretSelection:
739 case VisibleSelection::RangeSelection:
741 applyCommand(ApplyStyleCommand::create(m_frame->document(), style, editingAction, ApplyStyleCommand::ForceBlockProperties));
746 void Editor::applyStyleToSelection(CSSStyleDeclaration* style, EditAction editingAction)
748 if (!style || style->length() == 0 || !canEditRichly())
751 if (client() && client()->shouldApplyStyle(style, m_frame->selection()->toNormalizedRange().get()))
752 applyStyle(style, editingAction);
755 void Editor::applyParagraphStyleToSelection(CSSStyleDeclaration* style, EditAction editingAction)
757 if (!style || style->length() == 0 || !canEditRichly())
760 if (client() && client()->shouldApplyStyle(style, m_frame->selection()->toNormalizedRange().get()))
761 applyParagraphStyle(style, editingAction);
764 bool Editor::clientIsEditable() const
766 return client() && client()->isEditable();
769 bool Editor::selectionStartHasStyle(CSSStyleDeclaration* style) const
772 RefPtr<CSSComputedStyleDeclaration> selectionStyle = m_frame->selectionComputedStyle(nodeToRemove);
776 RefPtr<CSSMutableStyleDeclaration> mutableStyle = style->makeMutable();
779 CSSMutableStyleDeclaration::const_iterator end = mutableStyle->end();
780 for (CSSMutableStyleDeclaration::const_iterator it = mutableStyle->begin(); it != end; ++it) {
781 int propertyID = (*it).id();
782 if (!equalIgnoringCase(mutableStyle->getPropertyValue(propertyID), selectionStyle->getPropertyValue(propertyID))) {
789 ExceptionCode ec = 0;
790 nodeToRemove->remove(ec);
797 static void updateState(CSSMutableStyleDeclaration* desiredStyle, CSSComputedStyleDeclaration* computedStyle, bool& atStart, TriState& state)
799 CSSMutableStyleDeclaration::const_iterator end = desiredStyle->end();
800 for (CSSMutableStyleDeclaration::const_iterator it = desiredStyle->begin(); it != end; ++it) {
801 int propertyID = (*it).id();
802 String desiredProperty = desiredStyle->getPropertyValue(propertyID);
803 String computedProperty = computedStyle->getPropertyValue(propertyID);
804 TriState propertyState = equalIgnoringCase(desiredProperty, computedProperty)
805 ? TrueTriState : FalseTriState;
807 state = propertyState;
809 } else if (state != propertyState) {
810 state = MixedTriState;
816 TriState Editor::selectionHasStyle(CSSStyleDeclaration* style) const
819 TriState state = FalseTriState;
821 RefPtr<CSSMutableStyleDeclaration> mutableStyle = style->makeMutable();
823 if (!m_frame->selection()->isRange()) {
825 RefPtr<CSSComputedStyleDeclaration> selectionStyle = m_frame->selectionComputedStyle(nodeToRemove);
827 return FalseTriState;
828 updateState(mutableStyle.get(), selectionStyle.get(), atStart, state);
830 ExceptionCode ec = 0;
831 nodeToRemove->remove(ec);
835 for (Node* node = m_frame->selection()->start().node(); node; node = node->traverseNextNode()) {
836 RefPtr<CSSComputedStyleDeclaration> nodeStyle = computedStyle(node);
838 updateState(mutableStyle.get(), nodeStyle.get(), atStart, state);
839 if (state == MixedTriState)
841 if (node == m_frame->selection()->end().node())
848 void Editor::indent()
850 applyCommand(IndentOutdentCommand::create(m_frame->document(), IndentOutdentCommand::Indent));
853 void Editor::outdent()
855 applyCommand(IndentOutdentCommand::create(m_frame->document(), IndentOutdentCommand::Outdent));
858 static void dispatchEditableContentChangedEvents(const EditCommand& command)
860 Element* startRoot = command.startingRootEditableElement();
861 Element* endRoot = command.endingRootEditableElement();
864 startRoot->dispatchEvent(Event::create(eventNames().webkitEditableContentChangedEvent, false, false), ec);
865 if (endRoot && endRoot != startRoot)
866 endRoot->dispatchEvent(Event::create(eventNames().webkitEditableContentChangedEvent, false, false), ec);
869 void Editor::appliedEditing(PassRefPtr<EditCommand> cmd)
871 dispatchEditableContentChangedEvents(*cmd);
873 VisibleSelection newSelection(cmd->endingSelection());
874 // Don't clear the typing style with this selection change. We do those things elsewhere if necessary.
875 changeSelectionAfterCommand(newSelection, false, false, cmd.get());
877 if (!cmd->preservesTypingStyle())
878 m_frame->setTypingStyle(0);
880 // Command will be equal to last edit command only in the case of typing
881 if (m_lastEditCommand.get() == cmd)
882 ASSERT(cmd->isTypingCommand());
884 // Only register a new undo command if the command passed in is
885 // different from the last command
886 m_lastEditCommand = cmd;
888 client()->registerCommandForUndo(m_lastEditCommand);
890 respondToChangedContents(newSelection);
893 void Editor::unappliedEditing(PassRefPtr<EditCommand> cmd)
895 dispatchEditableContentChangedEvents(*cmd);
897 VisibleSelection newSelection(cmd->startingSelection());
898 changeSelectionAfterCommand(newSelection, true, true, cmd.get());
900 m_lastEditCommand = 0;
902 client()->registerCommandForRedo(cmd);
903 respondToChangedContents(newSelection);
906 void Editor::reappliedEditing(PassRefPtr<EditCommand> cmd)
908 dispatchEditableContentChangedEvents(*cmd);
910 VisibleSelection newSelection(cmd->endingSelection());
911 changeSelectionAfterCommand(newSelection, true, true, cmd.get());
913 m_lastEditCommand = 0;
915 client()->registerCommandForUndo(cmd);
916 respondToChangedContents(newSelection);
919 Editor::Editor(Frame* frame)
921 , m_deleteButtonController(new DeleteButtonController(frame))
922 , m_ignoreCompositionSelectionChange(false)
923 , m_shouldStartNewKillRingSequence(false)
924 // This is off by default, since most editors want this behavior (this matches IE but not FF).
925 , m_shouldStyleWithCSS(false)
935 m_compositionNode = 0;
936 m_customCompositionUnderlines.clear();
937 m_shouldStyleWithCSS = false;
940 bool Editor::insertText(const String& text, Event* triggeringEvent)
942 return m_frame->eventHandler()->handleTextInputEvent(text, triggeringEvent);
945 bool Editor::insertTextWithoutSendingTextEvent(const String& text, bool selectInsertedText, Event* triggeringEvent)
950 VisibleSelection selection = selectionForCommand(triggeringEvent);
951 if (!selection.isContentEditable())
953 RefPtr<Range> range = selection.toNormalizedRange();
955 if (!shouldInsertText(text, range.get(), EditorInsertActionTyped))
958 // Get the selection to use for the event that triggered this insertText.
959 // If the event handler changed the selection, we may want to use a different selection
960 // that is contained in the event target.
961 selection = selectionForCommand(triggeringEvent);
962 if (selection.isContentEditable()) {
963 if (Node* selectionStart = selection.start().node()) {
964 RefPtr<Document> document = selectionStart->document();
967 TypingCommand::insertText(document.get(), text, selection, selectInsertedText);
969 // Reveal the current selection
970 if (Frame* editedFrame = document->frame())
971 if (Page* page = editedFrame->page())
972 page->focusController()->focusedOrMainFrame()->revealSelection(ScrollAlignment::alignToEdgeIfNeeded);
979 bool Editor::insertLineBreak()
984 if (!shouldInsertText("\n", m_frame->selection()->toNormalizedRange().get(), EditorInsertActionTyped))
987 TypingCommand::insertLineBreak(m_frame->document());
988 revealSelectionAfterEditingOperation();
992 bool Editor::insertParagraphSeparator()
997 if (!canEditRichly())
998 return insertLineBreak();
1000 if (!shouldInsertText("\n", m_frame->selection()->toNormalizedRange().get(), EditorInsertActionTyped))
1003 TypingCommand::insertParagraphSeparator(m_frame->document());
1004 revealSelectionAfterEditingOperation();
1011 return; // DHTML did the whole operation
1016 RefPtr<Range> selection = selectedRange();
1017 if (shouldDeleteRange(selection.get())) {
1018 Pasteboard::generalPasteboard()->writeSelection(selection.get(), canSmartCopyOrDelete(), m_frame);
1019 didWriteSelectionToPasteboard();
1020 deleteSelectionWithSmartDelete(canSmartCopyOrDelete());
1027 return; // DHTML did the whole operation
1033 Document* document = m_frame->document();
1034 if (HTMLImageElement* imageElement = imageElementFromImageDocument(document))
1035 Pasteboard::generalPasteboard()->writeImage(imageElement, document->url(), document->title());
1037 Pasteboard::generalPasteboard()->writeSelection(selectedRange().get(), canSmartCopyOrDelete(), m_frame);
1039 didWriteSelectionToPasteboard();
1044 void Editor::paste()
1046 ASSERT(m_frame->document());
1047 if (tryDHTMLPaste())
1048 return; // DHTML did the whole operation
1051 DocLoader* loader = m_frame->document()->docLoader();
1052 loader->setAllowStaleResources(true);
1053 if (m_frame->selection()->isContentRichlyEditable())
1054 pasteWithPasteboard(Pasteboard::generalPasteboard(), true);
1056 pasteAsPlainTextWithPasteboard(Pasteboard::generalPasteboard());
1057 loader->setAllowStaleResources(false);
1062 void Editor::pasteAsPlainText()
1066 pasteAsPlainTextWithPasteboard(Pasteboard::generalPasteboard());
1069 void Editor::performDelete()
1076 addToKillRing(selectedRange().get(), false);
1077 deleteSelectionWithSmartDelete(canSmartCopyOrDelete());
1079 // clear the "start new kill ring sequence" setting, because it was set to true
1080 // when the selection was updated by deleting the range
1081 setStartNewKillRingSequence(false);
1084 void Editor::copyURL(const KURL& url, const String& title)
1086 Pasteboard::generalPasteboard()->writeURL(url, title, m_frame);
1089 void Editor::copyImage(const HitTestResult& result)
1091 KURL url = result.absoluteLinkURL();
1093 url = result.absoluteImageURL();
1095 Pasteboard::generalPasteboard()->writeImage(result.innerNonSharedNode(), url, result.altDisplayString());
1098 bool Editor::isContinuousSpellCheckingEnabled()
1100 return client() && client()->isContinuousSpellCheckingEnabled();
1103 void Editor::toggleContinuousSpellChecking()
1106 client()->toggleContinuousSpellChecking();
1109 bool Editor::isGrammarCheckingEnabled()
1111 return client() && client()->isGrammarCheckingEnabled();
1114 void Editor::toggleGrammarChecking()
1117 client()->toggleGrammarChecking();
1120 int Editor::spellCheckerDocumentTag()
1122 return client() ? client()->spellCheckerDocumentTag() : 0;
1125 bool Editor::shouldEndEditing(Range* range)
1127 return client() && client()->shouldEndEditing(range);
1130 bool Editor::shouldBeginEditing(Range* range)
1132 return client() && client()->shouldBeginEditing(range);
1135 void Editor::clearUndoRedoOperations()
1138 client()->clearUndoRedoOperations();
1141 bool Editor::canUndo()
1143 return client() && client()->canUndo();
1152 bool Editor::canRedo()
1154 return client() && client()->canRedo();
1163 void Editor::didBeginEditing()
1166 client()->didBeginEditing();
1169 void Editor::didEndEditing()
1172 client()->didEndEditing();
1175 void Editor::didWriteSelectionToPasteboard()
1178 client()->didWriteSelectionToPasteboard();
1181 void Editor::toggleBold()
1183 command("ToggleBold").execute();
1186 void Editor::toggleUnderline()
1188 command("ToggleUnderline").execute();
1191 void Editor::setBaseWritingDirection(WritingDirection direction)
1193 Node* focusedNode = frame()->document()->focusedNode();
1194 if (focusedNode && (focusedNode->hasTagName(textareaTag)
1195 || focusedNode->hasTagName(inputTag) && (static_cast<HTMLInputElement*>(focusedNode)->inputType() == HTMLInputElement::TEXT
1196 || static_cast<HTMLInputElement*>(focusedNode)->inputType() == HTMLInputElement::SEARCH))) {
1197 if (direction == NaturalWritingDirection)
1199 static_cast<HTMLElement*>(focusedNode)->setAttribute(dirAttr, direction == LeftToRightWritingDirection ? "ltr" : "rtl");
1200 frame()->document()->updateRendering();
1204 RefPtr<CSSMutableStyleDeclaration> style = CSSMutableStyleDeclaration::create();
1205 style->setProperty(CSSPropertyDirection, direction == LeftToRightWritingDirection ? "ltr" : direction == RightToLeftWritingDirection ? "rtl" : "inherit", false);
1206 applyParagraphStyleToSelection(style.get(), EditActionSetWritingDirection);
1209 void Editor::selectComposition()
1211 RefPtr<Range> range = compositionRange();
1215 // The composition can start inside a composed character sequence, so we have to override checks.
1216 // See <http://bugs.webkit.org/show_bug.cgi?id=15781>
1217 VisibleSelection selection;
1218 selection.setWithoutValidation(range->startPosition(), range->endPosition());
1219 m_frame->selection()->setSelection(selection, false, false);
1222 void Editor::confirmComposition()
1224 if (!m_compositionNode)
1226 confirmComposition(m_compositionNode->data().substring(m_compositionStart, m_compositionEnd - m_compositionStart), false);
1229 void Editor::confirmCompositionWithoutDisturbingSelection()
1231 if (!m_compositionNode)
1233 confirmComposition(m_compositionNode->data().substring(m_compositionStart, m_compositionEnd - m_compositionStart), true);
1236 void Editor::confirmComposition(const String& text)
1238 confirmComposition(text, false);
1241 void Editor::confirmComposition(const String& text, bool preserveSelection)
1243 setIgnoreCompositionSelectionChange(true);
1245 VisibleSelection oldSelection = m_frame->selection()->selection();
1247 selectComposition();
1249 if (m_frame->selection()->isNone()) {
1250 setIgnoreCompositionSelectionChange(false);
1254 // If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input
1255 // will delete the old composition with an optimized replace operation.
1257 TypingCommand::deleteSelection(m_frame->document(), false);
1259 m_compositionNode = 0;
1260 m_customCompositionUnderlines.clear();
1262 insertText(text, 0);
1264 if (preserveSelection)
1265 m_frame->selection()->setSelection(oldSelection, false, false);
1267 setIgnoreCompositionSelectionChange(false);
1270 void Editor::setComposition(const String& text, const Vector<CompositionUnderline>& underlines, unsigned selectionStart, unsigned selectionEnd)
1272 setIgnoreCompositionSelectionChange(true);
1274 selectComposition();
1276 if (m_frame->selection()->isNone()) {
1277 setIgnoreCompositionSelectionChange(false);
1281 // If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input
1282 // will delete the old composition with an optimized replace operation.
1284 TypingCommand::deleteSelection(m_frame->document(), false);
1286 m_compositionNode = 0;
1287 m_customCompositionUnderlines.clear();
1289 if (!text.isEmpty()) {
1290 TypingCommand::insertText(m_frame->document(), text, true, true);
1292 Node* baseNode = m_frame->selection()->base().node();
1293 unsigned baseOffset = m_frame->selection()->base().m_offset;
1294 Node* extentNode = m_frame->selection()->extent().node();
1295 unsigned extentOffset = m_frame->selection()->extent().m_offset;
1297 if (baseNode && baseNode == extentNode && baseNode->isTextNode() && baseOffset + text.length() == extentOffset) {
1298 m_compositionNode = static_cast<Text*>(baseNode);
1299 m_compositionStart = baseOffset;
1300 m_compositionEnd = extentOffset;
1301 m_customCompositionUnderlines = underlines;
1302 size_t numUnderlines = m_customCompositionUnderlines.size();
1303 for (size_t i = 0; i < numUnderlines; ++i) {
1304 m_customCompositionUnderlines[i].startOffset += baseOffset;
1305 m_customCompositionUnderlines[i].endOffset += baseOffset;
1307 if (baseNode->renderer())
1308 baseNode->renderer()->repaint();
1310 unsigned start = min(baseOffset + selectionStart, extentOffset);
1311 unsigned end = min(max(start, baseOffset + selectionEnd), extentOffset);
1312 RefPtr<Range> selectedRange = Range::create(baseNode->document(), baseNode, start, baseNode, end);
1313 m_frame->selection()->setSelectedRange(selectedRange.get(), DOWNSTREAM, false);
1317 setIgnoreCompositionSelectionChange(false);
1320 void Editor::ignoreSpelling()
1325 RefPtr<Range> selectedRange = frame()->selection()->toNormalizedRange();
1327 frame()->document()->removeMarkers(selectedRange.get(), DocumentMarker::Spelling);
1329 String text = frame()->selectedText();
1330 ASSERT(text.length() != 0);
1331 client()->ignoreWordInSpellDocument(text);
1334 void Editor::learnSpelling()
1339 // FIXME: We don't call this on the Mac, and it should remove misppelling markers around the
1340 // learned word, see <rdar://problem/5396072>.
1342 String text = frame()->selectedText();
1343 ASSERT(text.length() != 0);
1344 client()->learnWord(text);
1347 static String findFirstMisspellingInRange(EditorClient* client, Range* searchRange, int& firstMisspellingOffset, bool markAll)
1349 ASSERT_ARG(client, client);
1350 ASSERT_ARG(searchRange, searchRange);
1352 WordAwareIterator it(searchRange);
1353 firstMisspellingOffset = 0;
1355 String firstMisspelling;
1356 int currentChunkOffset = 0;
1358 while (!it.atEnd()) {
1359 const UChar* chars = it.characters();
1360 int len = it.length();
1362 // Skip some work for one-space-char hunks
1363 if (!(len == 1 && chars[0] == ' ')) {
1365 int misspellingLocation = -1;
1366 int misspellingLength = 0;
1367 client->checkSpellingOfString(chars, len, &misspellingLocation, &misspellingLength);
1369 // 5490627 shows that there was some code path here where the String constructor below crashes.
1370 // We don't know exactly what combination of bad input caused this, so we're making this much
1371 // more robust against bad input on release builds.
1372 ASSERT(misspellingLength >= 0);
1373 ASSERT(misspellingLocation >= -1);
1374 ASSERT(misspellingLength == 0 || misspellingLocation >= 0);
1375 ASSERT(misspellingLocation < len);
1376 ASSERT(misspellingLength <= len);
1377 ASSERT(misspellingLocation + misspellingLength <= len);
1379 if (misspellingLocation >= 0 && misspellingLength > 0 && misspellingLocation < len && misspellingLength <= len && misspellingLocation + misspellingLength <= len) {
1381 // Remember first-encountered misspelling and its offset
1382 if (!firstMisspelling) {
1383 firstMisspellingOffset = currentChunkOffset + misspellingLocation;
1384 firstMisspelling = String(chars + misspellingLocation, misspellingLength);
1387 // Mark this instance if we're marking all instances. Otherwise bail out because we found the first one.
1391 // Compute range of misspelled word
1392 RefPtr<Range> misspellingRange = TextIterator::subrange(searchRange, currentChunkOffset + misspellingLocation, misspellingLength);
1394 // Store marker for misspelled word
1395 ExceptionCode ec = 0;
1396 misspellingRange->startContainer(ec)->document()->addMarker(misspellingRange.get(), DocumentMarker::Spelling);
1401 currentChunkOffset += len;
1405 return firstMisspelling;
1408 #ifndef BUILDING_ON_TIGER
1410 static PassRefPtr<Range> paragraphAlignedRangeForRange(Range* arbitraryRange, int& offsetIntoParagraphAlignedRange, String& paragraphString)
1412 ASSERT_ARG(arbitraryRange, arbitraryRange);
1414 ExceptionCode ec = 0;
1416 // Expand range to paragraph boundaries
1417 RefPtr<Range> paragraphRange = arbitraryRange->cloneRange(ec);
1418 setStart(paragraphRange.get(), startOfParagraph(arbitraryRange->startPosition()));
1419 setEnd(paragraphRange.get(), endOfParagraph(arbitraryRange->endPosition()));
1421 // Compute offset from start of expanded range to start of original range
1422 RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), arbitraryRange->startPosition());
1423 offsetIntoParagraphAlignedRange = TextIterator::rangeLength(offsetAsRange.get());
1425 // Fill in out parameter with string representing entire paragraph range.
1426 // Someday we might have a caller that doesn't use this, but for now all callers do.
1427 paragraphString = plainText(paragraphRange.get());
1429 return paragraphRange;
1432 static int findFirstGrammarDetailInRange(const Vector<GrammarDetail>& grammarDetails, int badGrammarPhraseLocation, int /*badGrammarPhraseLength*/, Range *searchRange, int startOffset, int endOffset, bool markAll)
1434 // Found some bad grammar. Find the earliest detail range that starts in our search range (if any).
1435 // Optionally add a DocumentMarker for each detail in the range.
1436 int earliestDetailLocationSoFar = -1;
1437 int earliestDetailIndex = -1;
1438 for (unsigned i = 0; i < grammarDetails.size(); i++) {
1439 const GrammarDetail* detail = &grammarDetails[i];
1440 ASSERT(detail->length > 0 && detail->location >= 0);
1442 int detailStartOffsetInParagraph = badGrammarPhraseLocation + detail->location;
1444 // Skip this detail if it starts before the original search range
1445 if (detailStartOffsetInParagraph < startOffset)
1448 // Skip this detail if it starts after the original search range
1449 if (detailStartOffsetInParagraph >= endOffset)
1453 RefPtr<Range> badGrammarRange = TextIterator::subrange(searchRange, badGrammarPhraseLocation - startOffset + detail->location, detail->length);
1454 ExceptionCode ec = 0;
1455 badGrammarRange->startContainer(ec)->document()->addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail->userDescription);
1459 // Remember this detail only if it's earlier than our current candidate (the details aren't in a guaranteed order)
1460 if (earliestDetailIndex < 0 || earliestDetailLocationSoFar > detail->location) {
1461 earliestDetailIndex = i;
1462 earliestDetailLocationSoFar = detail->location;
1466 return earliestDetailIndex;
1469 static String findFirstBadGrammarInRange(EditorClient* client, Range* searchRange, GrammarDetail& outGrammarDetail, int& outGrammarPhraseOffset, bool markAll)
1471 ASSERT_ARG(client, client);
1472 ASSERT_ARG(searchRange, searchRange);
1474 // Initialize out parameters; these will be updated if we find something to return.
1475 outGrammarDetail.location = -1;
1476 outGrammarDetail.length = 0;
1477 outGrammarDetail.guesses.clear();
1478 outGrammarDetail.userDescription = "";
1479 outGrammarPhraseOffset = 0;
1481 String firstBadGrammarPhrase;
1483 // Expand the search range to encompass entire paragraphs, since grammar checking needs that much context.
1484 // Determine the character offset from the start of the paragraph to the start of the original search range,
1485 // since we will want to ignore results in this area.
1486 int searchRangeStartOffset;
1487 String paragraphString;
1488 RefPtr<Range> paragraphRange = paragraphAlignedRangeForRange(searchRange, searchRangeStartOffset, paragraphString);
1490 // Determine the character offset from the start of the paragraph to the end of the original search range,
1491 // since we will want to ignore results in this area also.
1492 int searchRangeEndOffset = searchRangeStartOffset + TextIterator::rangeLength(searchRange);
1494 // Start checking from beginning of paragraph, but skip past results that occur before the start of the original search range.
1495 int startOffset = 0;
1496 while (startOffset < searchRangeEndOffset) {
1497 Vector<GrammarDetail> grammarDetails;
1498 int badGrammarPhraseLocation = -1;
1499 int badGrammarPhraseLength = 0;
1500 client->checkGrammarOfString(paragraphString.characters() + startOffset, paragraphString.length() - startOffset, grammarDetails, &badGrammarPhraseLocation, &badGrammarPhraseLength);
1502 if (badGrammarPhraseLength == 0) {
1503 ASSERT(badGrammarPhraseLocation == -1);
1507 ASSERT(badGrammarPhraseLocation >= 0);
1508 badGrammarPhraseLocation += startOffset;
1511 // Found some bad grammar. Find the earliest detail range that starts in our search range (if any).
1512 int badGrammarIndex = findFirstGrammarDetailInRange(grammarDetails, badGrammarPhraseLocation, badGrammarPhraseLength, searchRange, searchRangeStartOffset, searchRangeEndOffset, markAll);
1513 if (badGrammarIndex >= 0) {
1514 ASSERT(static_cast<unsigned>(badGrammarIndex) < grammarDetails.size());
1515 outGrammarDetail = grammarDetails[badGrammarIndex];
1518 // If we found a detail in range, then we have found the first bad phrase (unless we found one earlier but
1519 // kept going so we could mark all instances).
1520 if (badGrammarIndex >= 0 && firstBadGrammarPhrase.isEmpty()) {
1521 outGrammarPhraseOffset = badGrammarPhraseLocation - searchRangeStartOffset;
1522 firstBadGrammarPhrase = paragraphString.substring(badGrammarPhraseLocation, badGrammarPhraseLength);
1524 // Found one. We're done now, unless we're marking each instance.
1529 // These results were all between the start of the paragraph and the start of the search range; look
1530 // beyond this phrase.
1531 startOffset = badGrammarPhraseLocation + badGrammarPhraseLength;
1534 return firstBadGrammarPhrase;
1537 #endif /* not BUILDING_ON_TIGER */
1539 #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
1541 static String findFirstMisspellingOrBadGrammarInRange(EditorClient* client, Range* searchRange, bool checkGrammar, bool& outIsSpelling, int& outFirstFoundOffset, GrammarDetail& outGrammarDetail)
1543 ASSERT_ARG(client, client);
1544 ASSERT_ARG(searchRange, searchRange);
1546 String firstFoundItem;
1547 String misspelledWord;
1548 String badGrammarPhrase;
1549 ExceptionCode ec = 0;
1551 // Initialize out parameters; these will be updated if we find something to return.
1552 outIsSpelling = true;
1553 outFirstFoundOffset = 0;
1554 outGrammarDetail.location = -1;
1555 outGrammarDetail.length = 0;
1556 outGrammarDetail.guesses.clear();
1557 outGrammarDetail.userDescription = "";
1559 // Expand the search range to encompass entire paragraphs, since text checking needs that much context.
1560 // Determine the character offset from the start of the paragraph to the start of the original search range,
1561 // since we will want to ignore results in this area.
1562 RefPtr<Range> paragraphRange = searchRange->cloneRange(ec);
1563 setStart(paragraphRange.get(), startOfParagraph(searchRange->startPosition()));
1564 int totalRangeLength = TextIterator::rangeLength(paragraphRange.get());
1565 setEnd(paragraphRange.get(), endOfParagraph(searchRange->startPosition()));
1567 RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), searchRange->startPosition());
1568 int searchRangeStartOffset = TextIterator::rangeLength(offsetAsRange.get());
1569 int totalLengthProcessed = 0;
1571 bool firstIteration = true;
1572 bool lastIteration = false;
1573 while (totalLengthProcessed < totalRangeLength) {
1574 // Iterate through the search range by paragraphs, checking each one for spelling and grammar.
1575 int currentLength = TextIterator::rangeLength(paragraphRange.get());
1576 int currentStartOffset = firstIteration ? searchRangeStartOffset : 0;
1577 int currentEndOffset = currentLength;
1578 if (inSameParagraph(paragraphRange->startPosition(), searchRange->endPosition())) {
1579 // Determine the character offset from the end of the original search range to the end of the paragraph,
1580 // since we will want to ignore results in this area.
1581 RefPtr<Range> endOffsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), searchRange->endPosition());
1582 currentEndOffset = TextIterator::rangeLength(endOffsetAsRange.get());
1583 lastIteration = true;
1585 if (currentStartOffset < currentEndOffset) {
1586 String paragraphString = plainText(paragraphRange.get());
1587 if (paragraphString.length() > 0) {
1588 bool foundGrammar = false;
1589 int spellingLocation = 0;
1590 int grammarPhraseLocation = 0;
1591 int grammarDetailLocation = 0;
1592 unsigned grammarDetailIndex = 0;
1594 Vector<TextCheckingResult> results;
1595 client->checkSpellingAndGrammarOfParagraph(paragraphString.characters(), paragraphString.length(), checkGrammar, results);
1597 for (unsigned i = 0; i < results.size(); i++) {
1598 const TextCheckingResult* result = &results[i];
1599 if (result->resultType == 1 && result->location >= currentStartOffset && result->location + result->length <= currentEndOffset) {
1600 ASSERT(result->length > 0 && result->location >= 0);
1601 spellingLocation = result->location;
1602 misspelledWord = paragraphString.substring(result->location, result->length);
1603 ASSERT(misspelledWord.length() != 0);
1605 } else if (checkGrammar && result->resultType == 2 && result->location < currentEndOffset && result->location + result->length > currentStartOffset) {
1606 ASSERT(result->length > 0 && result->location >= 0);
1607 // We can't stop after the first grammar result, since there might still be a spelling result after
1608 // it begins but before the first detail in it, but we can stop if we find a second grammar result.
1609 if (foundGrammar) break;
1610 for (unsigned j = 0; j < result->details.size(); j++) {
1611 const GrammarDetail* detail = &result->details[j];
1612 ASSERT(detail->length > 0 && detail->location >= 0);
1613 if (result->location + detail->location >= currentStartOffset && result->location + detail->location + detail->length <= currentEndOffset && (!foundGrammar || result->location + detail->location < grammarDetailLocation)) {
1614 grammarDetailIndex = j;
1615 grammarDetailLocation = result->location + detail->location;
1616 foundGrammar = true;
1620 grammarPhraseLocation = result->location;
1621 outGrammarDetail = result->details[grammarDetailIndex];
1622 badGrammarPhrase = paragraphString.substring(result->location, result->length);
1623 ASSERT(badGrammarPhrase.length() != 0);
1628 if (!misspelledWord.isEmpty() && (!checkGrammar || badGrammarPhrase.isEmpty() || spellingLocation <= grammarDetailLocation)) {
1629 int spellingOffset = spellingLocation - currentStartOffset;
1630 if (!firstIteration) {
1631 RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), searchRange->startPosition(), paragraphRange->startPosition());
1632 spellingOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get());
1634 outIsSpelling = true;
1635 outFirstFoundOffset = spellingOffset;
1636 firstFoundItem = misspelledWord;
1638 } else if (checkGrammar && !badGrammarPhrase.isEmpty()) {
1639 int grammarPhraseOffset = grammarPhraseLocation - currentStartOffset;
1640 if (!firstIteration) {
1641 RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), searchRange->startPosition(), paragraphRange->startPosition());
1642 grammarPhraseOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get());
1644 outIsSpelling = false;
1645 outFirstFoundOffset = grammarPhraseOffset;
1646 firstFoundItem = badGrammarPhrase;
1651 if (lastIteration || totalLengthProcessed + currentLength >= totalRangeLength)
1653 setStart(paragraphRange.get(), startOfNextParagraph(paragraphRange->endPosition()));
1654 setEnd(paragraphRange.get(), endOfParagraph(paragraphRange->startPosition()));
1655 firstIteration = false;
1656 totalLengthProcessed += currentLength;
1658 return firstFoundItem;
1663 void Editor::advanceToNextMisspelling(bool startBeforeSelection)
1665 ExceptionCode ec = 0;
1667 // The basic approach is to search in two phases - from the selection end to the end of the doc, and
1668 // then we wrap and search from the doc start to (approximately) where we started.
1670 // Start at the end of the selection, search to edge of document. Starting at the selection end makes
1671 // repeated "check spelling" commands work.
1672 VisibleSelection selection(frame()->selection()->selection());
1673 RefPtr<Range> spellingSearchRange(rangeOfContents(frame()->document()));
1674 bool startedWithSelection = false;
1675 if (selection.start().node()) {
1676 startedWithSelection = true;
1677 if (startBeforeSelection) {
1678 VisiblePosition start(selection.visibleStart());
1679 // We match AppKit's rule: Start 1 character before the selection.
1680 VisiblePosition oneBeforeStart = start.previous();
1681 setStart(spellingSearchRange.get(), oneBeforeStart.isNotNull() ? oneBeforeStart : start);
1683 setStart(spellingSearchRange.get(), selection.visibleEnd());
1686 Position position = spellingSearchRange->startPosition();
1687 if (!isEditablePosition(position)) {
1688 // This shouldn't happen in very often because the Spelling menu items aren't enabled unless the
1689 // selection is editable.
1690 // This can happen in Mail for a mix of non-editable and editable content (like Stationary),
1691 // when spell checking the whole document before sending the message.
1692 // In that case the document might not be editable, but there are editable pockets that need to be spell checked.
1694 position = firstEditablePositionAfterPositionInRoot(position, frame()->document()->documentElement()).deepEquivalent();
1695 if (position.isNull())
1698 Position rangeCompliantPosition = rangeCompliantEquivalent(position);
1699 spellingSearchRange->setStart(rangeCompliantPosition.node(), rangeCompliantPosition.m_offset, ec);
1700 startedWithSelection = false; // won't need to wrap
1703 // topNode defines the whole range we want to operate on
1704 Node* topNode = highestEditableRoot(position);
1705 spellingSearchRange->setEnd(topNode, maxDeepOffset(topNode), ec);
1707 // If spellingSearchRange starts in the middle of a word, advance to the next word so we start checking
1708 // at a word boundary. Going back by one char and then forward by a word does the trick.
1709 if (startedWithSelection) {
1710 VisiblePosition oneBeforeStart = startVisiblePosition(spellingSearchRange.get(), DOWNSTREAM).previous();
1711 if (oneBeforeStart.isNotNull()) {
1712 setStart(spellingSearchRange.get(), endOfWord(oneBeforeStart));
1713 } // else we were already at the start of the editable node
1716 if (spellingSearchRange->collapsed(ec))
1717 return; // nothing to search in
1719 // Get the spell checker if it is available
1723 // We go to the end of our first range instead of the start of it, just to be sure
1724 // we don't get foiled by any word boundary problems at the start. It means we might
1725 // do a tiny bit more searching.
1726 Node *searchEndNodeAfterWrap = spellingSearchRange->endContainer(ec);
1727 int searchEndOffsetAfterWrap = spellingSearchRange->endOffset(ec);
1729 int misspellingOffset = 0;
1730 #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
1731 RefPtr<Range> grammarSearchRange = spellingSearchRange->cloneRange(ec);
1732 String misspelledWord;
1733 String badGrammarPhrase;
1734 int grammarPhraseOffset = 0;
1735 bool isSpelling = true;
1736 int foundOffset = 0;
1737 GrammarDetail grammarDetail;
1738 String foundItem = findFirstMisspellingOrBadGrammarInRange(client(), spellingSearchRange.get(), isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail);
1740 misspelledWord = foundItem;
1741 misspellingOffset = foundOffset;
1743 badGrammarPhrase = foundItem;
1744 grammarPhraseOffset = foundOffset;
1747 String misspelledWord = findFirstMisspellingInRange(client(), spellingSearchRange.get(), misspellingOffset, false);
1749 String badGrammarPhrase;
1751 #ifndef BUILDING_ON_TIGER
1752 int grammarPhraseOffset = 0;
1753 GrammarDetail grammarDetail;
1755 // Search for bad grammar that occurs prior to the next misspelled word (if any)
1756 RefPtr<Range> grammarSearchRange = spellingSearchRange->cloneRange(ec);
1757 if (!misspelledWord.isEmpty()) {
1758 // Stop looking at start of next misspelled word
1759 CharacterIterator chars(grammarSearchRange.get());
1760 chars.advance(misspellingOffset);
1761 grammarSearchRange->setEnd(chars.range()->startContainer(ec), chars.range()->startOffset(ec), ec);
1764 if (isGrammarCheckingEnabled())
1765 badGrammarPhrase = findFirstBadGrammarInRange(client(), grammarSearchRange.get(), grammarDetail, grammarPhraseOffset, false);
1769 // 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
1770 // block rather than at a selection).
1771 if (startedWithSelection && !misspelledWord && !badGrammarPhrase) {
1772 spellingSearchRange->setStart(topNode, 0, ec);
1773 // going until the end of the very first chunk we tested is far enough
1774 spellingSearchRange->setEnd(searchEndNodeAfterWrap, searchEndOffsetAfterWrap, ec);
1776 #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
1777 grammarSearchRange = spellingSearchRange->cloneRange(ec);
1778 foundItem = findFirstMisspellingOrBadGrammarInRange(client(), spellingSearchRange.get(), isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail);
1780 misspelledWord = foundItem;
1781 misspellingOffset = foundOffset;
1783 badGrammarPhrase = foundItem;
1784 grammarPhraseOffset = foundOffset;
1787 misspelledWord = findFirstMisspellingInRange(client(), spellingSearchRange.get(), misspellingOffset, false);
1789 #ifndef BUILDING_ON_TIGER
1790 grammarSearchRange = spellingSearchRange->cloneRange(ec);
1791 if (!misspelledWord.isEmpty()) {
1792 // Stop looking at start of next misspelled word
1793 CharacterIterator chars(grammarSearchRange.get());
1794 chars.advance(misspellingOffset);
1795 grammarSearchRange->setEnd(chars.range()->startContainer(ec), chars.range()->startOffset(ec), ec);
1797 if (isGrammarCheckingEnabled())
1798 badGrammarPhrase = findFirstBadGrammarInRange(client(), grammarSearchRange.get(), grammarDetail, grammarPhraseOffset, false);
1803 if (!badGrammarPhrase.isEmpty()) {
1804 #ifdef BUILDING_ON_TIGER
1805 ASSERT_NOT_REACHED();
1807 // We found bad grammar. Since we only searched for bad grammar up to the first misspelled word, the bad grammar
1808 // takes precedence and we ignore any potential misspelled word. Select the grammar detail, update the spelling
1809 // panel, and store a marker so we draw the green squiggle later.
1811 ASSERT(badGrammarPhrase.length() > 0);
1812 ASSERT(grammarDetail.location != -1 && grammarDetail.length > 0);
1814 // FIXME 4859190: This gets confused with doubled punctuation at the end of a paragraph
1815 RefPtr<Range> badGrammarRange = TextIterator::subrange(grammarSearchRange.get(), grammarPhraseOffset + grammarDetail.location, grammarDetail.length);
1816 frame()->selection()->setSelection(VisibleSelection(badGrammarRange.get(), SEL_DEFAULT_AFFINITY));
1817 frame()->revealSelection();
1819 client()->updateSpellingUIWithGrammarString(badGrammarPhrase, grammarDetail);
1820 frame()->document()->addMarker(badGrammarRange.get(), DocumentMarker::Grammar, grammarDetail.userDescription);
1822 } else if (!misspelledWord.isEmpty()) {
1823 // We found a misspelling, but not any earlier bad grammar. Select the misspelling, update the spelling panel, and store
1824 // a marker so we draw the red squiggle later.
1826 RefPtr<Range> misspellingRange = TextIterator::subrange(spellingSearchRange.get(), misspellingOffset, misspelledWord.length());
1827 frame()->selection()->setSelection(VisibleSelection(misspellingRange.get(), DOWNSTREAM));
1828 frame()->revealSelection();
1830 client()->updateSpellingUIWithMisspelledWord(misspelledWord);
1831 frame()->document()->addMarker(misspellingRange.get(), DocumentMarker::Spelling);
1835 bool Editor::isSelectionMisspelled()
1837 String selectedString = frame()->selectedText();
1838 int length = selectedString.length();
1845 int misspellingLocation = -1;
1846 int misspellingLength = 0;
1847 client()->checkSpellingOfString(selectedString.characters(), length, &misspellingLocation, &misspellingLength);
1849 // The selection only counts as misspelled if the selected text is exactly one misspelled word
1850 if (misspellingLength != length)
1853 // Update the spelling panel to be displaying this error (whether or not the spelling panel is on screen).
1854 // This is necessary to make a subsequent call to [NSSpellChecker ignoreWord:inSpellDocumentWithTag:] work
1855 // correctly; that call behaves differently based on whether the spelling panel is displaying a misspelling
1856 // or a grammar error.
1857 client()->updateSpellingUIWithMisspelledWord(selectedString);
1862 #ifndef BUILDING_ON_TIGER
1863 static bool isRangeUngrammatical(EditorClient* client, Range *range, Vector<String>& guessesVector)
1869 if (!range || range->collapsed(ec))
1872 // Returns true only if the passed range exactly corresponds to a bad grammar detail range. This is analogous
1873 // to isSelectionMisspelled. It's not good enough for there to be some bad grammar somewhere in the range,
1874 // or overlapping the range; the ranges must exactly match.
1875 guessesVector.clear();
1876 int grammarPhraseOffset;
1878 GrammarDetail grammarDetail;
1879 String badGrammarPhrase = findFirstBadGrammarInRange(client, range, grammarDetail, grammarPhraseOffset, false);
1881 // No bad grammar in these parts at all.
1882 if (badGrammarPhrase.isEmpty())
1885 // Bad grammar, but phrase (e.g. sentence) starts beyond start of range.
1886 if (grammarPhraseOffset > 0)
1889 ASSERT(grammarDetail.location >= 0 && grammarDetail.length > 0);
1891 // Bad grammar, but start of detail (e.g. ungrammatical word) doesn't match start of range
1892 if (grammarDetail.location + grammarPhraseOffset != 0)
1895 // Bad grammar at start of range, but end of bad grammar is before or after end of range
1896 if (grammarDetail.length != TextIterator::rangeLength(range))
1899 // Update the spelling panel to be displaying this error (whether or not the spelling panel is on screen).
1900 // This is necessary to make a subsequent call to [NSSpellChecker ignoreWord:inSpellDocumentWithTag:] work
1901 // correctly; that call behaves differently based on whether the spelling panel is displaying a misspelling
1902 // or a grammar error.
1903 client->updateSpellingUIWithGrammarString(badGrammarPhrase, grammarDetail);
1909 bool Editor::isSelectionUngrammatical()
1911 #ifdef BUILDING_ON_TIGER
1914 Vector<String> ignoredGuesses;
1915 return isRangeUngrammatical(client(), frame()->selection()->toNormalizedRange().get(), ignoredGuesses);
1919 Vector<String> Editor::guessesForUngrammaticalSelection()
1921 #ifdef BUILDING_ON_TIGER
1922 return Vector<String>();
1924 Vector<String> guesses;
1925 // Ignore the result of isRangeUngrammatical; we just want the guesses, whether or not there are any
1926 isRangeUngrammatical(client(), frame()->selection()->toNormalizedRange().get(), guesses);
1931 Vector<String> Editor::guessesForMisspelledSelection()
1933 String selectedString = frame()->selectedText();
1934 ASSERT(selectedString.length() != 0);
1936 Vector<String> guesses;
1938 client()->getGuessesForWord(selectedString, guesses);
1942 #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
1944 static Vector<String> guessesForMisspelledOrUngrammaticalRange(EditorClient* client, Range *range, bool checkGrammar, bool& misspelled, bool& ungrammatical)
1946 Vector<String> guesses;
1949 ungrammatical = false;
1951 if (!client || !range || range->collapsed(ec))
1954 // Expand the range to encompass entire paragraphs, since text checking needs that much context.
1955 int rangeStartOffset;
1956 String paragraphString;
1957 RefPtr<Range> paragraphRange = paragraphAlignedRangeForRange(range, rangeStartOffset, paragraphString);
1958 int rangeLength = TextIterator::rangeLength(range);
1959 if (rangeLength == 0 || paragraphString.length() == 0)
1962 Vector<TextCheckingResult> results;
1963 client->checkSpellingAndGrammarOfParagraph(paragraphString.characters(), paragraphString.length(), checkGrammar, results);
1965 for (unsigned i = 0; i < results.size(); i++) {
1966 const TextCheckingResult* result = &results[i];
1967 if (result->resultType == 1 && result->location == rangeStartOffset && result->length == rangeLength) {
1968 String misspelledWord = paragraphString.substring(rangeStartOffset, rangeLength);
1969 ASSERT(misspelledWord.length() != 0);
1970 client->getGuessesForWord(misspelledWord, guesses);
1971 client->updateSpellingUIWithMisspelledWord(misspelledWord);
1980 for (unsigned i = 0; i < results.size(); i++) {
1981 const TextCheckingResult* result = &results[i];
1982 if (result->resultType == 2 && result->location <= rangeStartOffset && result->location + result->length >= rangeStartOffset + rangeLength) {
1983 for (unsigned j = 0; j < result->details.size(); j++) {
1984 const GrammarDetail* detail = &result->details[j];
1985 ASSERT(detail->length > 0 && detail->location >= 0);
1986 if (result->location + detail->location == rangeStartOffset && detail->length == rangeLength) {
1987 String badGrammarPhrase = paragraphString.substring(result->location, result->length);
1988 ASSERT(badGrammarPhrase.length() != 0);
1989 for (unsigned k = 0; k < detail->guesses.size(); k++)
1990 guesses.append(detail->guesses[k]);
1991 client->updateSpellingUIWithGrammarString(badGrammarPhrase, *detail);
1992 ungrammatical = true;
2003 Vector<String> Editor::guessesForMisspelledOrUngrammaticalSelection(bool& misspelled, bool& ungrammatical)
2005 #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
2006 return guessesForMisspelledOrUngrammaticalRange(client(), frame()->selection()->toNormalizedRange().get(), isGrammarCheckingEnabled(), misspelled, ungrammatical);
2008 misspelled = isSelectionMisspelled();
2010 ungrammatical = false;
2011 return guessesForMisspelledSelection();
2013 if (isGrammarCheckingEnabled() && isSelectionUngrammatical()) {
2014 ungrammatical = true;
2015 return guessesForUngrammaticalSelection();
2017 ungrammatical = false;
2018 return Vector<String>();
2022 void Editor::showSpellingGuessPanel()
2025 LOG_ERROR("No NSSpellChecker");
2029 #ifndef BUILDING_ON_TIGER
2030 // Post-Tiger, this menu item is a show/hide toggle, to match AppKit. Leave Tiger behavior alone
2031 // to match rest of OS X.
2032 if (client()->spellingUIIsShowing()) {
2033 client()->showSpellingUI(false);
2038 advanceToNextMisspelling(true);
2039 client()->showSpellingUI(true);
2042 bool Editor::spellingPanelIsShowing()
2046 return client()->spellingUIIsShowing();
2049 void Editor::markMisspellingsAfterTypingToPosition(const VisiblePosition &p)
2051 if (!isContinuousSpellCheckingEnabled())
2054 #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
2055 VisibleSelection adjacentWords = VisibleSelection(startOfWord(p, LeftWordIfOnBoundary), endOfWord(p, RightWordIfOnBoundary));
2056 if (isGrammarCheckingEnabled()) {
2057 VisibleSelection selectedSentence = VisibleSelection(startOfSentence(p), endOfSentence(p));
2058 markMisspellingsAndBadGrammar(adjacentWords, true, selectedSentence);
2060 markMisspellingsAndBadGrammar(adjacentWords, false, adjacentWords);
2063 // Check spelling of one word
2064 markMisspellings(VisibleSelection(startOfWord(p, LeftWordIfOnBoundary), endOfWord(p, RightWordIfOnBoundary)));
2066 if (!isGrammarCheckingEnabled())
2069 // Check grammar of entire sentence
2070 markBadGrammar(VisibleSelection(startOfSentence(p), endOfSentence(p)));
2074 static void markAllMisspellingsInRange(EditorClient* client, Range* searchRange)
2076 // Use the "markAll" feature of findFirstMisspellingInRange. Ignore the return value and the "out parameter";
2077 // all we need to do is mark every instance.
2079 findFirstMisspellingInRange(client, searchRange, ignoredOffset, true);
2082 #ifndef BUILDING_ON_TIGER
2083 static void markAllBadGrammarInRange(EditorClient* client, Range* searchRange)
2085 // Use the "markAll" feature of findFirstBadGrammarInRange. Ignore the return value and "out parameters"; all we need to
2086 // do is mark every instance.
2087 GrammarDetail ignoredGrammarDetail;
2089 findFirstBadGrammarInRange(client, searchRange, ignoredGrammarDetail, ignoredOffset, true);
2093 static void markMisspellingsOrBadGrammar(Editor* editor, const VisibleSelection& selection, bool checkSpelling)
2095 // This function is called with a selection already expanded to word boundaries.
2096 // Might be nice to assert that here.
2098 // This function is used only for as-you-type checking, so if that's off we do nothing. Note that
2099 // grammar checking can only be on if spell checking is also on.
2100 if (!editor->isContinuousSpellCheckingEnabled())
2103 RefPtr<Range> searchRange(selection.toNormalizedRange());
2107 // If we're not in an editable node, bail.
2108 Node* editableNode = searchRange->startContainer();
2109 if (!editableNode || !editableNode->isContentEditable())
2112 // Get the spell checker if it is available
2113 if (!editor->client())
2117 markAllMisspellingsInRange(editor->client(), searchRange.get());
2119 #ifdef BUILDING_ON_TIGER
2120 ASSERT_NOT_REACHED();
2122 if (editor->isGrammarCheckingEnabled())
2123 markAllBadGrammarInRange(editor->client(), searchRange.get());
2128 void Editor::markMisspellings(const VisibleSelection& selection)
2130 markMisspellingsOrBadGrammar(this, selection, true);
2133 void Editor::markBadGrammar(const VisibleSelection& selection)
2135 #ifndef BUILDING_ON_TIGER
2136 markMisspellingsOrBadGrammar(this, selection, false);
2138 UNUSED_PARAM(selection);
2142 #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
2144 static void markAllMisspellingsAndBadGrammarInRanges(EditorClient* client, Range *spellingRange, bool markGrammar, Range *grammarRange)
2146 // This function is called with selections already expanded to word boundaries.
2148 if (!client || !spellingRange || (markGrammar && !grammarRange))
2151 // If we're not in an editable node, bail.
2152 Node* editableNode = spellingRange->startContainer();
2153 if (!editableNode || !editableNode->isContentEditable())
2156 // Expand the range to encompass entire paragraphs, since text checking needs that much context.
2157 int spellingRangeStartOffset = 0;
2158 int spellingRangeEndOffset = 0;
2159 int grammarRangeStartOffset = 0;
2160 int grammarRangeEndOffset = 0;
2161 String paragraphString;
2164 // The spelling range should be contained in the paragraph-aligned extension of the grammar range.
2165 RefPtr<Range> paragraphRange = paragraphAlignedRangeForRange(grammarRange, grammarRangeStartOffset, paragraphString);
2166 RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), spellingRange->startPosition());
2167 spellingRangeStartOffset = TextIterator::rangeLength(offsetAsRange.get());
2168 grammarRangeEndOffset = grammarRangeStartOffset + TextIterator::rangeLength(grammarRange);
2170 RefPtr<Range> paragraphRange = paragraphAlignedRangeForRange(spellingRange, spellingRangeStartOffset, paragraphString);
2172 spellingRangeEndOffset = spellingRangeStartOffset + TextIterator::rangeLength(spellingRange);
2173 if (paragraphString.length() == 0 || (spellingRangeStartOffset >= spellingRangeEndOffset && (!markGrammar || grammarRangeStartOffset >= grammarRangeEndOffset)))
2176 Vector<TextCheckingResult> results;
2177 client->checkSpellingAndGrammarOfParagraph(paragraphString.characters(), paragraphString.length(), markGrammar, results);
2179 for (unsigned i = 0; i < results.size(); i++) {
2180 const TextCheckingResult* result = &results[i];
2181 if (result->resultType == 1 && result->location >= spellingRangeStartOffset && result->location + result->length <= spellingRangeEndOffset) {
2182 ASSERT(result->length > 0 && result->location >= 0);
2183 RefPtr<Range> misspellingRange = TextIterator::subrange(spellingRange, result->location - spellingRangeStartOffset, result->length);
2184 misspellingRange->startContainer(ec)->document()->addMarker(misspellingRange.get(), DocumentMarker::Spelling);
2185 } else if (markGrammar && result->resultType == 2 && result->location < grammarRangeEndOffset && result->location + result->length > grammarRangeStartOffset) {
2186 ASSERT(result->length > 0 && result->location >= 0);
2187 for (unsigned j = 0; j < result->details.size(); j++) {
2188 const GrammarDetail* detail = &result->details[j];
2189 ASSERT(detail->length > 0 && detail->location >= 0);
2190 if (result->location + detail->location >= grammarRangeStartOffset && result->location + detail->location + detail->length <= grammarRangeEndOffset) {
2191 RefPtr<Range> badGrammarRange = TextIterator::subrange(grammarRange, result->location + detail->location - grammarRangeStartOffset, detail->length);
2192 grammarRange->startContainer(ec)->document()->addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail->userDescription);
2201 void Editor::markMisspellingsAndBadGrammar(const VisibleSelection& spellingSelection, bool markGrammar, const VisibleSelection& grammarSelection)
2203 #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
2204 if (!isContinuousSpellCheckingEnabled())
2206 markAllMisspellingsAndBadGrammarInRanges(client(), spellingSelection.toNormalizedRange().get(), markGrammar && isGrammarCheckingEnabled(), grammarSelection.toNormalizedRange().get());
2208 markMisspellings(spellingSelection);
2210 markBadGrammar(grammarSelection);
2214 PassRefPtr<Range> Editor::rangeForPoint(const IntPoint& windowPoint)
2216 Document* document = m_frame->documentAtPoint(windowPoint);
2220 Frame* frame = document->frame();
2222 FrameView* frameView = frame->view();
2225 IntPoint framePoint = frameView->windowToContents(windowPoint);
2226 VisibleSelection selection(frame->visiblePositionForPoint(framePoint));
2227 return avoidIntersectionWithNode(selection.toNormalizedRange().get(), deleteButtonController() ? deleteButtonController()->containerElement() : 0);
2230 void Editor::revealSelectionAfterEditingOperation()
2232 if (m_ignoreCompositionSelectionChange)
2235 m_frame->revealSelection(ScrollAlignment::alignToEdgeIfNeeded);
2238 void Editor::setIgnoreCompositionSelectionChange(bool ignore)
2240 if (m_ignoreCompositionSelectionChange == ignore)
2243 m_ignoreCompositionSelectionChange = ignore;
2245 revealSelectionAfterEditingOperation();
2248 PassRefPtr<Range> Editor::compositionRange() const
2250 if (!m_compositionNode)
2252 unsigned length = m_compositionNode->length();
2253 unsigned start = min(m_compositionStart, length);
2254 unsigned end = min(max(start, m_compositionEnd), length);
2257 return Range::create(m_compositionNode->document(), m_compositionNode.get(), start, m_compositionNode.get(), end);
2260 bool Editor::getCompositionSelection(unsigned& selectionStart, unsigned& selectionEnd) const
2262 if (!m_compositionNode)
2264 Position start = m_frame->selection()->start();
2265 if (start.node() != m_compositionNode)
2267 Position end = m_frame->selection()->end();
2268 if (end.node() != m_compositionNode)
2271 if (static_cast<unsigned>(start.m_offset) < m_compositionStart)
2273 if (static_cast<unsigned>(end.m_offset) > m_compositionEnd)
2276 selectionStart = start.m_offset - m_compositionStart;
2277 selectionEnd = start.m_offset - m_compositionEnd;
2281 void Editor::transpose()
2286 VisibleSelection selection = m_frame->selection()->selection();
2287 if (!selection.isCaret())
2290 // Make a selection that goes back one character and forward two characters.
2291 VisiblePosition caret = selection.visibleStart();
2292 VisiblePosition next = isEndOfParagraph(caret) ? caret : caret.next();
2293 VisiblePosition previous = next.previous();
2294 if (next == previous)
2296 previous = previous.previous();
2297 if (!inSameParagraph(next, previous))
2299 RefPtr<Range> range = makeRange(previous, next);
2302 VisibleSelection newSelection(range.get(), DOWNSTREAM);
2304 // Transpose the two characters.
2305 String text = plainText(range.get());
2306 if (text.length() != 2)
2308 String transposed = text.right(1) + text.left(1);
2310 // Select the two characters.
2311 if (newSelection != m_frame->selection()->selection()) {
2312 if (!m_frame->shouldChangeSelection(newSelection))
2314 m_frame->selection()->setSelection(newSelection);
2317 // Insert the transposed characters.
2318 if (!shouldInsertText(transposed, range.get(), EditorInsertActionTyped))
2320 replaceSelectionWithText(transposed, false, false);
2323 void Editor::addToKillRing(Range* range, bool prepend)
2325 if (m_shouldStartNewKillRingSequence)
2326 startNewKillRingSequence();
2328 String text = m_frame->displayStringModifiedByEncoding(plainText(range));
2330 prependToKillRing(text);
2332 appendToKillRing(text);
2333 m_shouldStartNewKillRingSequence = false;
2338 void Editor::appendToKillRing(const String&)
2342 void Editor::prependToKillRing(const String&)
2346 String Editor::yankFromKillRing()
2351 void Editor::startNewKillRingSequence()
2355 void Editor::setKillRingToYankedState()
2361 bool Editor::insideVisibleArea(const IntPoint& point) const
2363 if (m_frame->excludeFromTextSearch())
2366 // Right now, we only check the visibility of a point for disconnected frames. For all other
2367 // frames, we assume visibility.
2368 Frame* frame = m_frame->isDisconnected() ? m_frame : m_frame->tree()->top(true);
2369 if (!frame->isDisconnected())
2372 RenderPart* renderer = frame->ownerRenderer();
2373 RenderBlock* container = renderer->containingBlock();
2374 if (!(container->style()->overflowX() == OHIDDEN || container->style()->overflowY() == OHIDDEN))
2377 IntRect rectInPageCoords = container->overflowClipRect(0, 0);
2378 IntRect rectInFrameCoords = IntRect(renderer->x() * -1, renderer->y() * -1,
2379 rectInPageCoords.width(), rectInPageCoords.height());
2381 return rectInFrameCoords.contains(point);
2384 bool Editor::insideVisibleArea(Range* range) const
2389 if (m_frame->excludeFromTextSearch())
2392 // Right now, we only check the visibility of a range for disconnected frames. For all other
2393 // frames, we assume visibility.
2394 Frame* frame = m_frame->isDisconnected() ? m_frame : m_frame->tree()->top(true);
2395 if (!frame->isDisconnected())
2398 RenderPart* renderer = frame->ownerRenderer();
2399 RenderBlock* container = renderer->containingBlock();
2400 if (!(container->style()->overflowX() == OHIDDEN || container->style()->overflowY() == OHIDDEN))
2403 IntRect rectInPageCoords = container->overflowClipRect(0, 0);
2404 IntRect rectInFrameCoords = IntRect(renderer->x() * -1, renderer->y() * -1,
2405 rectInPageCoords.width(), rectInPageCoords.height());
2406 IntRect resultRect = range->boundingBox();
2408 return rectInFrameCoords.contains(resultRect);
2411 PassRefPtr<Range> Editor::firstVisibleRange(const String& target, bool caseFlag)
2413 RefPtr<Range> searchRange(rangeOfContents(m_frame->document()));
2414 RefPtr<Range> resultRange = findPlainText(searchRange.get(), target, true, caseFlag);
2415 ExceptionCode ec = 0;
2417 while (!insideVisibleArea(resultRange.get())) {
2418 searchRange->setStartAfter(resultRange->endContainer(), ec);
2419 if (searchRange->startContainer() == searchRange->endContainer())
2420 return Range::create(m_frame->document());
2421 resultRange = findPlainText(searchRange.get(), target, true, caseFlag);
2427 PassRefPtr<Range> Editor::lastVisibleRange(const String& target, bool caseFlag)
2429 RefPtr<Range> searchRange(rangeOfContents(m_frame->document()));
2430 RefPtr<Range> resultRange = findPlainText(searchRange.get(), target, false, caseFlag);
2431 ExceptionCode ec = 0;
2433 while (!insideVisibleArea(resultRange.get())) {
2434 searchRange->setEndBefore(resultRange->startContainer(), ec);
2435 if (searchRange->startContainer() == searchRange->endContainer())
2436 return Range::create(m_frame->document());
2437 resultRange = findPlainText(searchRange.get(), target, false, caseFlag);
2443 PassRefPtr<Range> Editor::nextVisibleRange(Range* currentRange, const String& target, bool forward, bool caseFlag, bool wrapFlag)
2445 if (m_frame->excludeFromTextSearch())
2446 return Range::create(m_frame->document());
2448 RefPtr<Range> resultRange = currentRange;
2449 RefPtr<Range> searchRange(rangeOfContents(m_frame->document()));
2450 ExceptionCode ec = 0;
2452 for ( ; !insideVisibleArea(resultRange.get()); resultRange = findPlainText(searchRange.get(), target, forward, caseFlag)) {
2453 if (resultRange->collapsed(ec)) {
2454 if (!resultRange->startContainer()->isInShadowTree())
2456 searchRange = rangeOfContents(m_frame->document());
2458 searchRange->setStartAfter(resultRange->startContainer()->shadowAncestorNode(), ec);
2460 searchRange->setEndBefore(resultRange->startContainer()->shadowAncestorNode(), ec);
2465 searchRange->setStartAfter(resultRange->endContainer(), ec);
2467 searchRange->setEndBefore(resultRange->startContainer(), ec);
2469 Node* shadowTreeRoot = searchRange->shadowTreeRootNode();
2470 if (searchRange->collapsed(ec) && shadowTreeRoot) {
2472 searchRange->setEnd(shadowTreeRoot, shadowTreeRoot->childNodeCount(), ec);
2474 searchRange->setStartBefore(shadowTreeRoot, ec);
2477 if (searchRange->startContainer()->isDocumentNode() && searchRange->endContainer()->isDocumentNode())
2481 if (insideVisibleArea(resultRange.get()))
2485 return Range::create(m_frame->document());
2488 return firstVisibleRange(target, caseFlag);
2490 return lastVisibleRange(target, caseFlag);
2493 void Editor::changeSelectionAfterCommand(const VisibleSelection& newSelection, bool closeTyping, bool clearTypingStyle, EditCommand* cmd)
2495 // If there is no selection change, don't bother sending shouldChangeSelection, but still call setSelection,
2496 // because there is work that it must do in this situation.
2497 // The old selection can be invalid here and calling shouldChangeSelection can produce some strange calls.
2498 // See <rdar://problem/5729315> Some shouldChangeSelectedDOMRange contain Ranges for selections that are no longer valid
2499 bool selectionDidNotChangeDOMPosition = newSelection == m_frame->selection()->selection();
2500 if (selectionDidNotChangeDOMPosition || m_frame->shouldChangeSelection(newSelection))
2501 m_frame->selection()->setSelection(newSelection, closeTyping, clearTypingStyle);
2503 // Some kinds of deletes and line break insertions change the selection's position within the document without
2504 // changing its position within the DOM. For example when you press return in the following (the caret is marked by ^):
2505 // <div contentEditable="true"><div>^Hello</div></div>
2506 // WebCore inserts <div><br></div> *before* the current block, which correctly moves the paragraph down but which doesn't
2507 // change the caret's DOM position (["hello", 0]). In these situations the above SelectionController::setSelection call
2508 // does not call EditorClient::respondToChangedSelection(), which, on the Mac, sends selection change notifications and
2509 // starts a new kill ring sequence, but we want to do these things (matches AppKit).
2510 if (selectionDidNotChangeDOMPosition && cmd->isTypingCommand())
2511 client()->respondToChangedSelection();
2514 } // namespace WebCore