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