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 "CSSComputedStyleDeclaration.h"
33 #include "CSSProperty.h"
34 #include "CSSPropertyNames.h"
35 #include "CSSValueKeywords.h"
36 #include "ClipboardEvent.h"
37 #include "DeleteButtonController.h"
38 #include "DeleteSelectionCommand.h"
39 #include "DocLoader.h"
40 #include "DocumentFragment.h"
41 #include "EditorClient.h"
42 #include "EventHandler.h"
43 #include "EventNames.h"
44 #include "FocusController.h"
45 #include "Frame.h"
46 #include "FrameTree.h"
47 #include "FrameView.h"
48 #include "HTMLInputElement.h"
49 #include "HTMLTextAreaElement.h"
50 #include "HitTestResult.h"
51 #include "IndentOutdentCommand.h"
52 #include "InsertListCommand.h"
53 #include "KeyboardEvent.h"
54 #include "ModifySelectionListLevel.h"
55 #include "Page.h"
56 #include "Pasteboard.h"
57 #include "RemoveFormatCommand.h"
58 #include "RenderBlock.h"
59 #include "RenderPart.h"
60 #include "ReplaceSelectionCommand.h"
61 #include "Sound.h"
62 #include "Text.h"
63 #include "TextIterator.h"
64 #include "TypingCommand.h"
65 #include "htmlediting.h"
66 #include "markup.h"
67 #include "visible_units.h"
68 #include <wtf/UnusedParam.h>
69
70 namespace WebCore {
71
72 using namespace std;
73 using namespace HTMLNames;
74
75 // When an event handler has moved the selection outside of a text control
76 // we should use the target control's selection for this editing operation.
selectionForCommand(Event * event)77 Selection Editor::selectionForCommand(Event* event)
78 {
79 Selection selection = m_frame->selection()->selection();
80 if (!event)
81 return selection;
82 // If the target is a text control, and the current selection is outside of its shadow tree,
83 // then use the saved selection for that text control.
84 Node* target = event->target()->toNode();
85 Node* selectionStart = selection.start().node();
86 if (target && (!selectionStart || target->shadowAncestorNode() != selectionStart->shadowAncestorNode())) {
87 if (target->hasTagName(inputTag) && static_cast<HTMLInputElement*>(target)->isTextField())
88 return static_cast<HTMLInputElement*>(target)->selection();
89 if (target->hasTagName(textareaTag))
90 return static_cast<HTMLTextAreaElement*>(target)->selection();
91 }
92 return selection;
93 }
94
client() const95 EditorClient* Editor::client() const
96 {
97 if (Page* page = m_frame->page())
98 return page->editorClient();
99 return 0;
100 }
101
handleKeyboardEvent(KeyboardEvent * event)102 void Editor::handleKeyboardEvent(KeyboardEvent* event)
103 {
104 if (EditorClient* c = client())
105 c->handleKeyboardEvent(event);
106 }
107
handleInputMethodKeydown(KeyboardEvent * event)108 void Editor::handleInputMethodKeydown(KeyboardEvent* event)
109 {
110 if (EditorClient* c = client())
111 c->handleInputMethodKeydown(event);
112 }
113
canEdit() const114 bool Editor::canEdit() const
115 {
116 return m_frame->selection()->isContentEditable();
117 }
118
canEditRichly() const119 bool Editor::canEditRichly() const
120 {
121 return m_frame->selection()->isContentRichlyEditable();
122 }
123
124 // WinIE uses onbeforecut and onbeforepaste to enables the cut and paste menu items. They
125 // also send onbeforecopy, apparently for symmetry, but it doesn't affect the menu items.
126 // We need to use onbeforecopy as a real menu enabler because we allow elements that are not
127 // normally selectable to implement copy/paste (like divs, or a document body).
128
canDHTMLCut()129 bool Editor::canDHTMLCut()
130 {
131 return !m_frame->selection()->isInPasswordField() && !dispatchCPPEvent(eventNames().beforecutEvent, ClipboardNumb);
132 }
133
canDHTMLCopy()134 bool Editor::canDHTMLCopy()
135 {
136 return !m_frame->selection()->isInPasswordField() && !dispatchCPPEvent(eventNames().beforecopyEvent, ClipboardNumb);
137 }
138
canDHTMLPaste()139 bool Editor::canDHTMLPaste()
140 {
141 return !dispatchCPPEvent(eventNames().beforepasteEvent, ClipboardNumb);
142 }
143
canCut() const144 bool Editor::canCut() const
145 {
146 return canCopy() && canDelete();
147 }
148
imageElementFromImageDocument(Document * document)149 static HTMLImageElement* imageElementFromImageDocument(Document* document)
150 {
151 if (!document)
152 return 0;
153 if (!document->isImageDocument())
154 return 0;
155
156 HTMLElement* body = document->body();
157 if (!body)
158 return 0;
159
160 Node* node = body->firstChild();
161 if (!node)
162 return 0;
163 if (!node->hasTagName(imgTag))
164 return 0;
165 return static_cast<HTMLImageElement*>(node);
166 }
167
canCopy() const168 bool Editor::canCopy() const
169 {
170 if (imageElementFromImageDocument(m_frame->document()))
171 return true;
172 SelectionController* selection = m_frame->selection();
173 return selection->isRange() && !selection->isInPasswordField();
174 }
175
canPaste() const176 bool Editor::canPaste() const
177 {
178 return canEdit();
179 }
180
canDelete() const181 bool Editor::canDelete() const
182 {
183 SelectionController* selection = m_frame->selection();
184 return selection->isRange() && selection->isContentEditable();
185 }
186
canDeleteRange(Range * range) const187 bool Editor::canDeleteRange(Range* range) const
188 {
189 ExceptionCode ec = 0;
190 Node* startContainer = range->startContainer(ec);
191 Node* endContainer = range->endContainer(ec);
192 if (!startContainer || !endContainer)
193 return false;
194
195 if (!startContainer->isContentEditable() || !endContainer->isContentEditable())
196 return false;
197
198 if (range->collapsed(ec)) {
199 VisiblePosition start(startContainer, range->startOffset(ec), DOWNSTREAM);
200 VisiblePosition previous = start.previous();
201 // FIXME: We sometimes allow deletions at the start of editable roots, like when the caret is in an empty list item.
202 if (previous.isNull() || previous.deepEquivalent().node()->rootEditableElement() != startContainer->rootEditableElement())
203 return false;
204 }
205 return true;
206 }
207
smartInsertDeleteEnabled()208 bool Editor::smartInsertDeleteEnabled()
209 {
210 return client() && client()->smartInsertDeleteEnabled();
211 }
212
canSmartCopyOrDelete()213 bool Editor::canSmartCopyOrDelete()
214 {
215 return client() && client()->smartInsertDeleteEnabled() && m_frame->selectionGranularity() == WordGranularity;
216 }
217
isSelectTrailingWhitespaceEnabled()218 bool Editor::isSelectTrailingWhitespaceEnabled()
219 {
220 return client() && client()->isSelectTrailingWhitespaceEnabled();
221 }
222
deleteWithDirection(SelectionController::EDirection direction,TextGranularity granularity,bool killRing,bool isTypingAction)223 bool Editor::deleteWithDirection(SelectionController::EDirection direction, TextGranularity granularity, bool killRing, bool isTypingAction)
224 {
225 if (!canEdit() || !m_frame->document())
226 return false;
227
228 if (m_frame->selection()->isRange()) {
229 if (isTypingAction) {
230 TypingCommand::deleteKeyPressed(m_frame->document(), canSmartCopyOrDelete(), granularity);
231 revealSelectionAfterEditingOperation();
232 } else {
233 if (killRing)
234 addToKillRing(selectedRange().get(), false);
235 deleteSelectionWithSmartDelete(canSmartCopyOrDelete());
236 // Implicitly calls revealSelectionAfterEditingOperation().
237 }
238 } else {
239 switch (direction) {
240 case SelectionController::FORWARD:
241 case SelectionController::RIGHT:
242 TypingCommand::forwardDeleteKeyPressed(m_frame->document(), canSmartCopyOrDelete(), granularity, killRing);
243 break;
244 case SelectionController::BACKWARD:
245 case SelectionController::LEFT:
246 TypingCommand::deleteKeyPressed(m_frame->document(), canSmartCopyOrDelete(), granularity, killRing);
247 break;
248 }
249 revealSelectionAfterEditingOperation();
250 }
251
252 // FIXME: We should to move this down into deleteKeyPressed.
253 // clear the "start new kill ring sequence" setting, because it was set to true
254 // when the selection was updated by deleting the range
255 if (killRing)
256 setStartNewKillRingSequence(false);
257
258 return true;
259 }
260
deleteSelectionWithSmartDelete(bool smartDelete)261 void Editor::deleteSelectionWithSmartDelete(bool smartDelete)
262 {
263 if (m_frame->selection()->isNone())
264 return;
265
266 applyCommand(DeleteSelectionCommand::create(m_frame->document(), smartDelete));
267 }
268
pasteAsPlainTextWithPasteboard(Pasteboard * pasteboard)269 void Editor::pasteAsPlainTextWithPasteboard(Pasteboard* pasteboard)
270 {
271 String text = pasteboard->plainText(m_frame);
272 if (client() && client()->shouldInsertText(text, selectedRange().get(), EditorInsertActionPasted))
273 replaceSelectionWithText(text, false, canSmartReplaceWithPasteboard(pasteboard));
274 }
275
pasteWithPasteboard(Pasteboard * pasteboard,bool allowPlainText)276 void Editor::pasteWithPasteboard(Pasteboard* pasteboard, bool allowPlainText)
277 {
278 RefPtr<Range> range = selectedRange();
279 bool chosePlainText;
280 RefPtr<DocumentFragment> fragment = pasteboard->documentFragment(m_frame, range, allowPlainText, chosePlainText);
281 if (fragment && shouldInsertFragment(fragment, range, EditorInsertActionPasted))
282 replaceSelectionWithFragment(fragment, false, canSmartReplaceWithPasteboard(pasteboard), chosePlainText);
283 }
284
canSmartReplaceWithPasteboard(Pasteboard * pasteboard)285 bool Editor::canSmartReplaceWithPasteboard(Pasteboard* pasteboard)
286 {
287 return client() && client()->smartInsertDeleteEnabled() && pasteboard->canSmartReplace();
288 }
289
shouldInsertFragment(PassRefPtr<DocumentFragment> fragment,PassRefPtr<Range> replacingDOMRange,EditorInsertAction givenAction)290 bool Editor::shouldInsertFragment(PassRefPtr<DocumentFragment> fragment, PassRefPtr<Range> replacingDOMRange, EditorInsertAction givenAction)
291 {
292 if (!client())
293 return false;
294
295 Node* child = fragment->firstChild();
296 if (child && fragment->lastChild() == child && child->isCharacterDataNode())
297 return client()->shouldInsertText(static_cast<CharacterData*>(child)->data(), replacingDOMRange.get(), givenAction);
298
299 return client()->shouldInsertNode(fragment.get(), replacingDOMRange.get(), givenAction);
300 }
301
replaceSelectionWithFragment(PassRefPtr<DocumentFragment> fragment,bool selectReplacement,bool smartReplace,bool matchStyle)302 void Editor::replaceSelectionWithFragment(PassRefPtr<DocumentFragment> fragment, bool selectReplacement, bool smartReplace, bool matchStyle)
303 {
304 if (m_frame->selection()->isNone() || !fragment)
305 return;
306
307 applyCommand(ReplaceSelectionCommand::create(m_frame->document(), fragment, selectReplacement, smartReplace, matchStyle));
308 revealSelectionAfterEditingOperation();
309 }
310
replaceSelectionWithText(const String & text,bool selectReplacement,bool smartReplace)311 void Editor::replaceSelectionWithText(const String& text, bool selectReplacement, bool smartReplace)
312 {
313 replaceSelectionWithFragment(createFragmentFromText(selectedRange().get(), text), selectReplacement, smartReplace, true);
314 }
315
selectedRange()316 PassRefPtr<Range> Editor::selectedRange()
317 {
318 if (!m_frame)
319 return 0;
320 return m_frame->selection()->toRange();
321 }
322
shouldDeleteRange(Range * range) const323 bool Editor::shouldDeleteRange(Range* range) const
324 {
325 ExceptionCode ec;
326 if (!range || range->collapsed(ec))
327 return false;
328
329 if (!canDeleteRange(range))
330 return false;
331
332 return client() && client()->shouldDeleteRange(range);
333 }
334
tryDHTMLCopy()335 bool Editor::tryDHTMLCopy()
336 {
337 if (m_frame->selection()->isInPasswordField())
338 return false;
339
340 // Must be done before oncopy adds types and data to the pboard,
341 // also done for security, as it erases data from the last copy/paste.
342 Pasteboard::generalPasteboard()->clear();
343
344 return !dispatchCPPEvent(eventNames().copyEvent, ClipboardWritable);
345 }
346
tryDHTMLCut()347 bool Editor::tryDHTMLCut()
348 {
349 if (m_frame->selection()->isInPasswordField())
350 return false;
351
352 // Must be done before oncut adds types and data to the pboard,
353 // also done for security, as it erases data from the last copy/paste.
354 Pasteboard::generalPasteboard()->clear();
355
356 return !dispatchCPPEvent(eventNames().cutEvent, ClipboardWritable);
357 }
358
tryDHTMLPaste()359 bool Editor::tryDHTMLPaste()
360 {
361 return !dispatchCPPEvent(eventNames().pasteEvent, ClipboardReadable);
362 }
363
writeSelectionToPasteboard(Pasteboard * pasteboard)364 void Editor::writeSelectionToPasteboard(Pasteboard* pasteboard)
365 {
366 pasteboard->writeSelection(selectedRange().get(), canSmartCopyOrDelete(), m_frame);
367 }
368
shouldInsertText(const String & text,Range * range,EditorInsertAction action) const369 bool Editor::shouldInsertText(const String& text, Range* range, EditorInsertAction action) const
370 {
371 return client() && client()->shouldInsertText(text, range, action);
372 }
373
shouldShowDeleteInterface(HTMLElement * element) const374 bool Editor::shouldShowDeleteInterface(HTMLElement* element) const
375 {
376 return client() && client()->shouldShowDeleteInterface(element);
377 }
378
respondToChangedSelection(const Selection & oldSelection)379 void Editor::respondToChangedSelection(const Selection& oldSelection)
380 {
381 if (client())
382 client()->respondToChangedSelection();
383 m_deleteButtonController->respondToChangedSelection(oldSelection);
384 }
385
respondToChangedContents(const Selection & endingSelection)386 void Editor::respondToChangedContents(const Selection& endingSelection)
387 {
388 if (AXObjectCache::accessibilityEnabled()) {
389 Node* node = endingSelection.start().node();
390 if (node)
391 m_frame->document()->axObjectCache()->postNotification(node->renderer(), "AXValueChanged");
392 }
393
394 if (client())
395 client()->respondToChangedContents();
396 }
397
fontForSelection(bool & hasMultipleFonts) const398 const SimpleFontData* Editor::fontForSelection(bool& hasMultipleFonts) const
399 {
400 #if !PLATFORM(QT)
401 hasMultipleFonts = false;
402
403 if (!m_frame->selection()->isRange()) {
404 Node* nodeToRemove;
405 RenderStyle* style = m_frame->styleForSelectionStart(nodeToRemove); // sets nodeToRemove
406
407 const SimpleFontData* result = 0;
408 if (style)
409 result = style->font().primaryFont();
410
411 if (nodeToRemove) {
412 ExceptionCode ec;
413 nodeToRemove->remove(ec);
414 ASSERT(ec == 0);
415 }
416
417 return result;
418 }
419
420 const SimpleFontData* font = 0;
421
422 RefPtr<Range> range = m_frame->selection()->toRange();
423 Node* startNode = range->editingStartPosition().node();
424 if (startNode) {
425 Node* pastEnd = range->pastLastNode();
426 // In the loop below, n should eventually match pastEnd and not become nil, but we've seen at least one
427 // unreproducible case where this didn't happen, so check for nil also.
428 for (Node* n = startNode; n && n != pastEnd; n = n->traverseNextNode()) {
429 RenderObject *renderer = n->renderer();
430 if (!renderer)
431 continue;
432 // FIXME: Are there any node types that have renderers, but that we should be skipping?
433 const SimpleFontData* f = renderer->style()->font().primaryFont();
434 if (!font)
435 font = f;
436 else if (font != f) {
437 hasMultipleFonts = true;
438 break;
439 }
440 }
441 }
442
443 return font;
444 #else
445 return 0;
446 #endif
447 }
448
textDirectionForSelection(bool & hasNestedOrMultipleEmbeddings) const449 WritingDirection Editor::textDirectionForSelection(bool& hasNestedOrMultipleEmbeddings) const
450 {
451 hasNestedOrMultipleEmbeddings = true;
452
453 if (m_frame->selection()->isNone())
454 return NaturalWritingDirection;
455
456 Position pos = m_frame->selection()->selection().start().downstream();
457
458 Node* node = pos.node();
459 if (!node)
460 return NaturalWritingDirection;
461
462 Position end;
463 if (m_frame->selection()->isRange()) {
464 end = m_frame->selection()->selection().end().upstream();
465
466 Node* pastLast = Range::create(m_frame->document(), rangeCompliantEquivalent(pos), rangeCompliantEquivalent(end))->pastLastNode();
467 for (Node* n = node; n && n != pastLast; n = n->traverseNextNode()) {
468 if (!n->isStyledElement())
469 continue;
470
471 RefPtr<CSSComputedStyleDeclaration> style = computedStyle(n);
472 RefPtr<CSSValue> unicodeBidi = style->getPropertyCSSValue(CSSPropertyUnicodeBidi);
473 if (!unicodeBidi)
474 continue;
475
476 ASSERT(unicodeBidi->isPrimitiveValue());
477 int unicodeBidiValue = static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent();
478 if (unicodeBidiValue == CSSValueEmbed || unicodeBidiValue == CSSValueBidiOverride)
479 return NaturalWritingDirection;
480 }
481 }
482
483 if (m_frame->selection()->isCaret()) {
484 if (CSSMutableStyleDeclaration *typingStyle = m_frame->typingStyle()) {
485 RefPtr<CSSValue> unicodeBidi = typingStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi);
486 if (unicodeBidi) {
487 ASSERT(unicodeBidi->isPrimitiveValue());
488 int unicodeBidiValue = static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent();
489 if (unicodeBidiValue == CSSValueEmbed) {
490 RefPtr<CSSValue> direction = typingStyle->getPropertyCSSValue(CSSPropertyDirection);
491 ASSERT(!direction || direction->isPrimitiveValue());
492 if (direction) {
493 hasNestedOrMultipleEmbeddings = false;
494 return static_cast<CSSPrimitiveValue*>(direction.get())->getIdent() == CSSValueLtr ? LeftToRightWritingDirection : RightToLeftWritingDirection;
495 }
496 } else if (unicodeBidiValue == CSSValueNormal) {
497 hasNestedOrMultipleEmbeddings = false;
498 return NaturalWritingDirection;
499 }
500 }
501 }
502 node = m_frame->selection()->selection().visibleStart().deepEquivalent().node();
503 }
504
505 // The selection is either a caret with no typing attributes or a range in which no embedding is added, so just use the start position
506 // to decide.
507 Node* block = enclosingBlock(node);
508 WritingDirection foundDirection = NaturalWritingDirection;
509
510 for (; node != block; node = node->parent()) {
511 if (!node->isStyledElement())
512 continue;
513
514 RefPtr<CSSComputedStyleDeclaration> style = computedStyle(node);
515 RefPtr<CSSValue> unicodeBidi = style->getPropertyCSSValue(CSSPropertyUnicodeBidi);
516 if (!unicodeBidi)
517 continue;
518
519 ASSERT(unicodeBidi->isPrimitiveValue());
520 int unicodeBidiValue = static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent();
521 if (unicodeBidiValue == CSSValueNormal)
522 continue;
523
524 if (unicodeBidiValue == CSSValueBidiOverride)
525 return NaturalWritingDirection;
526
527 ASSERT(unicodeBidiValue == CSSValueEmbed);
528 RefPtr<CSSValue> direction = style->getPropertyCSSValue(CSSPropertyDirection);
529 if (!direction)
530 continue;
531
532 ASSERT(direction->isPrimitiveValue());
533 int directionValue = static_cast<CSSPrimitiveValue*>(direction.get())->getIdent();
534 if (directionValue != CSSValueLtr && directionValue != CSSValueRtl)
535 continue;
536
537 if (foundDirection != NaturalWritingDirection)
538 return NaturalWritingDirection;
539
540 // In the range case, make sure that the embedding element persists until the end of the range.
541 if (m_frame->selection()->isRange() && !end.node()->isDescendantOf(node))
542 return NaturalWritingDirection;
543
544 foundDirection = directionValue == CSSValueLtr ? LeftToRightWritingDirection : RightToLeftWritingDirection;
545 }
546 hasNestedOrMultipleEmbeddings = false;
547 return foundDirection;
548 }
549
hasBidiSelection() const550 bool Editor::hasBidiSelection() const
551 {
552 if (m_frame->selection()->isNone())
553 return false;
554
555 Node* startNode;
556 if (m_frame->selection()->isRange()) {
557 startNode = m_frame->selection()->selection().start().downstream().node();
558 Node* endNode = m_frame->selection()->selection().end().upstream().node();
559 if (enclosingBlock(startNode) != enclosingBlock(endNode))
560 return false;
561 } else
562 startNode = m_frame->selection()->selection().visibleStart().deepEquivalent().node();
563
564 RenderObject* renderer = startNode->renderer();
565 while (renderer && !renderer->isRenderBlock())
566 renderer = renderer->parent();
567
568 if (!renderer)
569 return false;
570
571 RenderStyle* style = renderer->style();
572 if (style->direction() == RTL)
573 return true;
574
575 return static_cast<RenderBlock*>(renderer)->containsNonZeroBidiLevel();
576 }
577
selectionUnorderedListState() const578 TriState Editor::selectionUnorderedListState() const
579 {
580 if (m_frame->selection()->isCaret()) {
581 if (enclosingNodeWithTag(m_frame->selection()->selection().start(), ulTag))
582 return TrueTriState;
583 } else if (m_frame->selection()->isRange()) {
584 Node* startNode = enclosingNodeWithTag(m_frame->selection()->selection().start(), ulTag);
585 Node* endNode = enclosingNodeWithTag(m_frame->selection()->selection().end(), ulTag);
586 if (startNode && endNode && startNode == endNode)
587 return TrueTriState;
588 }
589
590 return FalseTriState;
591 }
592
selectionOrderedListState() const593 TriState Editor::selectionOrderedListState() const
594 {
595 if (m_frame->selection()->isCaret()) {
596 if (enclosingNodeWithTag(m_frame->selection()->selection().start(), olTag))
597 return TrueTriState;
598 } else if (m_frame->selection()->isRange()) {
599 Node* startNode = enclosingNodeWithTag(m_frame->selection()->selection().start(), olTag);
600 Node* endNode = enclosingNodeWithTag(m_frame->selection()->selection().end(), olTag);
601 if (startNode && endNode && startNode == endNode)
602 return TrueTriState;
603 }
604
605 return FalseTriState;
606 }
607
insertOrderedList()608 PassRefPtr<Node> Editor::insertOrderedList()
609 {
610 if (!canEditRichly())
611 return 0;
612
613 RefPtr<Node> newList = InsertListCommand::insertList(m_frame->document(), InsertListCommand::OrderedList);
614 revealSelectionAfterEditingOperation();
615 return newList;
616 }
617
insertUnorderedList()618 PassRefPtr<Node> Editor::insertUnorderedList()
619 {
620 if (!canEditRichly())
621 return 0;
622
623 RefPtr<Node> newList = InsertListCommand::insertList(m_frame->document(), InsertListCommand::UnorderedList);
624 revealSelectionAfterEditingOperation();
625 return newList;
626 }
627
canIncreaseSelectionListLevel()628 bool Editor::canIncreaseSelectionListLevel()
629 {
630 return canEditRichly() && IncreaseSelectionListLevelCommand::canIncreaseSelectionListLevel(m_frame->document());
631 }
632
canDecreaseSelectionListLevel()633 bool Editor::canDecreaseSelectionListLevel()
634 {
635 return canEditRichly() && DecreaseSelectionListLevelCommand::canDecreaseSelectionListLevel(m_frame->document());
636 }
637
increaseSelectionListLevel()638 PassRefPtr<Node> Editor::increaseSelectionListLevel()
639 {
640 if (!canEditRichly() || m_frame->selection()->isNone())
641 return 0;
642
643 RefPtr<Node> newList = IncreaseSelectionListLevelCommand::increaseSelectionListLevel(m_frame->document());
644 revealSelectionAfterEditingOperation();
645 return newList;
646 }
647
increaseSelectionListLevelOrdered()648 PassRefPtr<Node> Editor::increaseSelectionListLevelOrdered()
649 {
650 if (!canEditRichly() || m_frame->selection()->isNone())
651 return 0;
652
653 PassRefPtr<Node> newList = IncreaseSelectionListLevelCommand::increaseSelectionListLevelOrdered(m_frame->document());
654 revealSelectionAfterEditingOperation();
655 return newList;
656 }
657
increaseSelectionListLevelUnordered()658 PassRefPtr<Node> Editor::increaseSelectionListLevelUnordered()
659 {
660 if (!canEditRichly() || m_frame->selection()->isNone())
661 return 0;
662
663 PassRefPtr<Node> newList = IncreaseSelectionListLevelCommand::increaseSelectionListLevelUnordered(m_frame->document());
664 revealSelectionAfterEditingOperation();
665 return newList;
666 }
667
decreaseSelectionListLevel()668 void Editor::decreaseSelectionListLevel()
669 {
670 if (!canEditRichly() || m_frame->selection()->isNone())
671 return;
672
673 DecreaseSelectionListLevelCommand::decreaseSelectionListLevel(m_frame->document());
674 revealSelectionAfterEditingOperation();
675 }
676
removeFormattingAndStyle()677 void Editor::removeFormattingAndStyle()
678 {
679 applyCommand(RemoveFormatCommand::create(m_frame->document()));
680 }
681
clearLastEditCommand()682 void Editor::clearLastEditCommand()
683 {
684 m_lastEditCommand.clear();
685 }
686
687 // Returns whether caller should continue with "the default processing", which is the same as
688 // the event handler NOT setting the return value to false
dispatchCPPEvent(const AtomicString & eventType,ClipboardAccessPolicy policy)689 bool Editor::dispatchCPPEvent(const AtomicString &eventType, ClipboardAccessPolicy policy)
690 {
691 Node* target = m_frame->selection()->start().element();
692 if (!target && m_frame->document())
693 target = m_frame->document()->body();
694 if (!target)
695 return true;
696 target = target->shadowAncestorNode();
697
698 RefPtr<Clipboard> clipboard = newGeneralClipboard(policy);
699
700 ExceptionCode ec = 0;
701 RefPtr<Event> evt = ClipboardEvent::create(eventType, true, true, clipboard);
702 EventTargetNodeCast(target)->dispatchEvent(evt, ec);
703 bool noDefaultProcessing = evt->defaultPrevented();
704
705 // invalidate clipboard here for security
706 clipboard->setAccessPolicy(ClipboardNumb);
707
708 return !noDefaultProcessing;
709 }
710
applyStyle(CSSStyleDeclaration * style,EditAction editingAction)711 void Editor::applyStyle(CSSStyleDeclaration* style, EditAction editingAction)
712 {
713 switch (m_frame->selection()->state()) {
714 case Selection::NONE:
715 // do nothing
716 break;
717 case Selection::CARET:
718 m_frame->computeAndSetTypingStyle(style, editingAction);
719 break;
720 case Selection::RANGE:
721 if (m_frame->document() && style)
722 applyCommand(ApplyStyleCommand::create(m_frame->document(), style, editingAction));
723 break;
724 }
725 }
726
shouldApplyStyle(CSSStyleDeclaration * style,Range * range)727 bool Editor::shouldApplyStyle(CSSStyleDeclaration* style, Range* range)
728 {
729 return client()->shouldApplyStyle(style, range);
730 }
731
applyParagraphStyle(CSSStyleDeclaration * style,EditAction editingAction)732 void Editor::applyParagraphStyle(CSSStyleDeclaration* style, EditAction editingAction)
733 {
734 switch (m_frame->selection()->state()) {
735 case Selection::NONE:
736 // do nothing
737 break;
738 case Selection::CARET:
739 case Selection::RANGE:
740 if (m_frame->document() && style)
741 applyCommand(ApplyStyleCommand::create(m_frame->document(), style, editingAction, ApplyStyleCommand::ForceBlockProperties));
742 break;
743 }
744 }
745
applyStyleToSelection(CSSStyleDeclaration * style,EditAction editingAction)746 void Editor::applyStyleToSelection(CSSStyleDeclaration* style, EditAction editingAction)
747 {
748 if (!style || style->length() == 0 || !canEditRichly())
749 return;
750
751 if (client() && client()->shouldApplyStyle(style, m_frame->selection()->toRange().get()))
752 applyStyle(style, editingAction);
753 }
754
applyParagraphStyleToSelection(CSSStyleDeclaration * style,EditAction editingAction)755 void Editor::applyParagraphStyleToSelection(CSSStyleDeclaration* style, EditAction editingAction)
756 {
757 if (!style || style->length() == 0 || !canEditRichly())
758 return;
759
760 if (client() && client()->shouldApplyStyle(style, m_frame->selection()->toRange().get()))
761 applyParagraphStyle(style, editingAction);
762 }
763
clientIsEditable() const764 bool Editor::clientIsEditable() const
765 {
766 return client() && client()->isEditable();
767 }
768
selectionStartHasStyle(CSSStyleDeclaration * style) const769 bool Editor::selectionStartHasStyle(CSSStyleDeclaration* style) const
770 {
771 Node* nodeToRemove;
772 RefPtr<CSSComputedStyleDeclaration> selectionStyle = m_frame->selectionComputedStyle(nodeToRemove);
773 if (!selectionStyle)
774 return false;
775
776 RefPtr<CSSMutableStyleDeclaration> mutableStyle = style->makeMutable();
777
778 bool match = true;
779 CSSMutableStyleDeclaration::const_iterator end = mutableStyle->end();
780 for (CSSMutableStyleDeclaration::const_iterator it = mutableStyle->begin(); it != end; ++it) {
781 int propertyID = (*it).id();
782 if (!equalIgnoringCase(mutableStyle->getPropertyValue(propertyID), selectionStyle->getPropertyValue(propertyID))) {
783 match = false;
784 break;
785 }
786 }
787
788 if (nodeToRemove) {
789 ExceptionCode ec = 0;
790 nodeToRemove->remove(ec);
791 ASSERT(ec == 0);
792 }
793
794 return match;
795 }
796
updateState(CSSMutableStyleDeclaration * desiredStyle,CSSComputedStyleDeclaration * computedStyle,bool & atStart,TriState & state)797 static void updateState(CSSMutableStyleDeclaration* desiredStyle, CSSComputedStyleDeclaration* computedStyle, bool& atStart, TriState& state)
798 {
799 CSSMutableStyleDeclaration::const_iterator end = desiredStyle->end();
800 for (CSSMutableStyleDeclaration::const_iterator it = desiredStyle->begin(); it != end; ++it) {
801 int propertyID = (*it).id();
802 String desiredProperty = desiredStyle->getPropertyValue(propertyID);
803 String computedProperty = computedStyle->getPropertyValue(propertyID);
804 TriState propertyState = equalIgnoringCase(desiredProperty, computedProperty)
805 ? TrueTriState : FalseTriState;
806 if (atStart) {
807 state = propertyState;
808 atStart = false;
809 } else if (state != propertyState) {
810 state = MixedTriState;
811 break;
812 }
813 }
814 }
815
selectionHasStyle(CSSStyleDeclaration * style) const816 TriState Editor::selectionHasStyle(CSSStyleDeclaration* style) const
817 {
818 bool atStart = true;
819 TriState state = FalseTriState;
820
821 RefPtr<CSSMutableStyleDeclaration> mutableStyle = style->makeMutable();
822
823 if (!m_frame->selection()->isRange()) {
824 Node* nodeToRemove;
825 RefPtr<CSSComputedStyleDeclaration> selectionStyle = m_frame->selectionComputedStyle(nodeToRemove);
826 if (!selectionStyle)
827 return FalseTriState;
828 updateState(mutableStyle.get(), selectionStyle.get(), atStart, state);
829 if (nodeToRemove) {
830 ExceptionCode ec = 0;
831 nodeToRemove->remove(ec);
832 ASSERT(ec == 0);
833 }
834 } else {
835 for (Node* node = m_frame->selection()->start().node(); node; node = node->traverseNextNode()) {
836 RefPtr<CSSComputedStyleDeclaration> nodeStyle = computedStyle(node);
837 if (nodeStyle)
838 updateState(mutableStyle.get(), nodeStyle.get(), atStart, state);
839 if (state == MixedTriState)
840 break;
841 if (node == m_frame->selection()->end().node())
842 break;
843 }
844 }
845
846 return state;
847 }
indent()848 void Editor::indent()
849 {
850 applyCommand(IndentOutdentCommand::create(m_frame->document(), IndentOutdentCommand::Indent));
851 }
852
outdent()853 void Editor::outdent()
854 {
855 applyCommand(IndentOutdentCommand::create(m_frame->document(), IndentOutdentCommand::Outdent));
856 }
857
dispatchEditableContentChangedEvents(const EditCommand & command)858 static void dispatchEditableContentChangedEvents(const EditCommand& command)
859 {
860 Element* startRoot = command.startingRootEditableElement();
861 Element* endRoot = command.endingRootEditableElement();
862 ExceptionCode ec;
863 if (startRoot)
864 startRoot->dispatchEvent(Event::create(eventNames().webkitEditableContentChangedEvent, false, false), ec);
865 if (endRoot && endRoot != startRoot)
866 endRoot->dispatchEvent(Event::create(eventNames().webkitEditableContentChangedEvent, false, false), ec);
867 }
868
appliedEditing(PassRefPtr<EditCommand> cmd)869 void Editor::appliedEditing(PassRefPtr<EditCommand> cmd)
870 {
871 dispatchEditableContentChangedEvents(*cmd);
872
873 Selection newSelection(cmd->endingSelection());
874 // If there is no selection change, don't bother sending shouldChangeSelection, but still call setSelection,
875 // because there is work that it must do in this situation.
876 // The old selection can be invalid here and calling shouldChangeSelection can produce some strange calls.
877 // See <rdar://problem/5729315> Some shouldChangeSelectedDOMRange contain Ranges for selections that are no longer valid
878 // Don't clear the typing style with this selection change. We do those things elsewhere if necessary.
879 if (newSelection == m_frame->selection()->selection() || m_frame->shouldChangeSelection(newSelection))
880 m_frame->selection()->setSelection(newSelection, false, false);
881
882 if (!cmd->preservesTypingStyle())
883 m_frame->setTypingStyle(0);
884
885 // Command will be equal to last edit command only in the case of typing
886 if (m_lastEditCommand.get() == cmd)
887 ASSERT(cmd->isTypingCommand());
888 else {
889 // Only register a new undo command if the command passed in is
890 // different from the last command
891 m_lastEditCommand = cmd;
892 if (client())
893 client()->registerCommandForUndo(m_lastEditCommand);
894 }
895 respondToChangedContents(newSelection);
896 }
897
unappliedEditing(PassRefPtr<EditCommand> cmd)898 void Editor::unappliedEditing(PassRefPtr<EditCommand> cmd)
899 {
900 dispatchEditableContentChangedEvents(*cmd);
901
902 Selection newSelection(cmd->startingSelection());
903 // If there is no selection change, don't bother sending shouldChangeSelection, but still call setSelection,
904 // because there is work that it must do in this situation.
905 // The old selection can be invalid here and calling shouldChangeSelection can produce some strange calls.
906 // See <rdar://problem/5729315> Some shouldChangeSelectedDOMRange contain Ranges for selections that are no longer valid
907 if (newSelection == m_frame->selection()->selection() || m_frame->shouldChangeSelection(newSelection))
908 m_frame->selection()->setSelection(newSelection, true);
909
910 m_lastEditCommand = 0;
911 if (client())
912 client()->registerCommandForRedo(cmd);
913 respondToChangedContents(newSelection);
914 }
915
reappliedEditing(PassRefPtr<EditCommand> cmd)916 void Editor::reappliedEditing(PassRefPtr<EditCommand> cmd)
917 {
918 dispatchEditableContentChangedEvents(*cmd);
919
920 Selection newSelection(cmd->endingSelection());
921 // If there is no selection change, don't bother sending shouldChangeSelection, but still call setSelection,
922 // because there is work that it must do in this situation.
923 // The old selection can be invalid here and calling shouldChangeSelection can produce some strange calls.
924 // See <rdar://problem/5729315> Some shouldChangeSelectedDOMRange contain Ranges for selections that are no longer valid
925 if (newSelection == m_frame->selection()->selection() || m_frame->shouldChangeSelection(newSelection))
926 m_frame->selection()->setSelection(newSelection, true);
927
928 m_lastEditCommand = 0;
929 if (client())
930 client()->registerCommandForUndo(cmd);
931 respondToChangedContents(newSelection);
932 }
933
Editor(Frame * frame)934 Editor::Editor(Frame* frame)
935 : m_frame(frame)
936 , m_deleteButtonController(new DeleteButtonController(frame))
937 , m_ignoreCompositionSelectionChange(false)
938 , m_shouldStartNewKillRingSequence(false)
939 {
940 }
941
~Editor()942 Editor::~Editor()
943 {
944 }
945
clear()946 void Editor::clear()
947 {
948 m_compositionNode = 0;
949 m_customCompositionUnderlines.clear();
950 }
951
insertText(const String & text,Event * triggeringEvent)952 bool Editor::insertText(const String& text, Event* triggeringEvent)
953 {
954 return m_frame->eventHandler()->handleTextInputEvent(text, triggeringEvent);
955 }
956
insertTextWithoutSendingTextEvent(const String & text,bool selectInsertedText,Event * triggeringEvent)957 bool Editor::insertTextWithoutSendingTextEvent(const String& text, bool selectInsertedText, Event* triggeringEvent)
958 {
959 if (text.isEmpty())
960 return false;
961
962 Selection selection = selectionForCommand(triggeringEvent);
963 if (!selection.isContentEditable())
964 return false;
965 RefPtr<Range> range = selection.toRange();
966
967 if (!shouldInsertText(text, range.get(), EditorInsertActionTyped))
968 return true;
969
970 // Get the selection to use for the event that triggered this insertText.
971 // If the event handler changed the selection, we may want to use a different selection
972 // that is contained in the event target.
973 selection = selectionForCommand(triggeringEvent);
974 if (selection.isContentEditable()) {
975 if (Node* selectionStart = selection.start().node()) {
976 RefPtr<Document> document = selectionStart->document();
977
978 // Insert the text
979 TypingCommand::insertText(document.get(), text, selection, selectInsertedText);
980
981 // Reveal the current selection
982 if (Frame* editedFrame = document->frame())
983 if (Page* page = editedFrame->page())
984 page->focusController()->focusedOrMainFrame()->revealSelection(RenderLayer::gAlignToEdgeIfNeeded);
985 }
986 }
987
988 return true;
989 }
990
insertLineBreak()991 bool Editor::insertLineBreak()
992 {
993 if (!canEdit())
994 return false;
995
996 if (!shouldInsertText("\n", m_frame->selection()->toRange().get(), EditorInsertActionTyped))
997 return true;
998
999 TypingCommand::insertLineBreak(m_frame->document());
1000 revealSelectionAfterEditingOperation();
1001 return true;
1002 }
1003
insertParagraphSeparator()1004 bool Editor::insertParagraphSeparator()
1005 {
1006 if (!canEdit())
1007 return false;
1008
1009 if (!canEditRichly())
1010 return insertLineBreak();
1011
1012 if (!shouldInsertText("\n", m_frame->selection()->toRange().get(), EditorInsertActionTyped))
1013 return true;
1014
1015 TypingCommand::insertParagraphSeparator(m_frame->document());
1016 revealSelectionAfterEditingOperation();
1017 return true;
1018 }
1019
cut()1020 void Editor::cut()
1021 {
1022 if (tryDHTMLCut())
1023 return; // DHTML did the whole operation
1024 if (!canCut()) {
1025 systemBeep();
1026 return;
1027 }
1028 RefPtr<Range> selection = selectedRange();
1029 if (shouldDeleteRange(selection.get())) {
1030 Pasteboard::generalPasteboard()->writeSelection(selection.get(), canSmartCopyOrDelete(), m_frame);
1031 didWriteSelectionToPasteboard();
1032 deleteSelectionWithSmartDelete(canSmartCopyOrDelete());
1033 }
1034 }
1035
copy()1036 void Editor::copy()
1037 {
1038 if (tryDHTMLCopy())
1039 return; // DHTML did the whole operation
1040 if (!canCopy()) {
1041 systemBeep();
1042 return;
1043 }
1044
1045 Document* document = m_frame->document();
1046 if (HTMLImageElement* imageElement = imageElementFromImageDocument(document))
1047 Pasteboard::generalPasteboard()->writeImage(imageElement, document->url(), document->title());
1048 else
1049 Pasteboard::generalPasteboard()->writeSelection(selectedRange().get(), canSmartCopyOrDelete(), m_frame);
1050
1051 didWriteSelectionToPasteboard();
1052 }
1053
1054 #if !PLATFORM(MAC)
1055
paste()1056 void Editor::paste()
1057 {
1058 ASSERT(m_frame->document());
1059 if (tryDHTMLPaste())
1060 return; // DHTML did the whole operation
1061 if (!canPaste())
1062 return;
1063 DocLoader* loader = m_frame->document()->docLoader();
1064 loader->setAllowStaleResources(true);
1065 if (m_frame->selection()->isContentRichlyEditable())
1066 pasteWithPasteboard(Pasteboard::generalPasteboard(), true);
1067 else
1068 pasteAsPlainTextWithPasteboard(Pasteboard::generalPasteboard());
1069 loader->setAllowStaleResources(false);
1070 }
1071
1072 #endif
1073
pasteAsPlainText()1074 void Editor::pasteAsPlainText()
1075 {
1076 if (!canPaste())
1077 return;
1078 pasteAsPlainTextWithPasteboard(Pasteboard::generalPasteboard());
1079 }
1080
performDelete()1081 void Editor::performDelete()
1082 {
1083 if (!canDelete()) {
1084 systemBeep();
1085 return;
1086 }
1087
1088 addToKillRing(selectedRange().get(), false);
1089 deleteSelectionWithSmartDelete(canSmartCopyOrDelete());
1090
1091 // clear the "start new kill ring sequence" setting, because it was set to true
1092 // when the selection was updated by deleting the range
1093 setStartNewKillRingSequence(false);
1094 }
1095
copyURL(const KURL & url,const String & title)1096 void Editor::copyURL(const KURL& url, const String& title)
1097 {
1098 Pasteboard::generalPasteboard()->writeURL(url, title, m_frame);
1099 }
1100
copyImage(const HitTestResult & result)1101 void Editor::copyImage(const HitTestResult& result)
1102 {
1103 KURL url = result.absoluteLinkURL();
1104 if (url.isEmpty())
1105 url = result.absoluteImageURL();
1106
1107 Pasteboard::generalPasteboard()->writeImage(result.innerNonSharedNode(), url, result.altDisplayString());
1108 }
1109
isContinuousSpellCheckingEnabled()1110 bool Editor::isContinuousSpellCheckingEnabled()
1111 {
1112 return client() && client()->isContinuousSpellCheckingEnabled();
1113 }
1114
toggleContinuousSpellChecking()1115 void Editor::toggleContinuousSpellChecking()
1116 {
1117 if (client())
1118 client()->toggleContinuousSpellChecking();
1119 }
1120
isGrammarCheckingEnabled()1121 bool Editor::isGrammarCheckingEnabled()
1122 {
1123 return client() && client()->isGrammarCheckingEnabled();
1124 }
1125
toggleGrammarChecking()1126 void Editor::toggleGrammarChecking()
1127 {
1128 if (client())
1129 client()->toggleGrammarChecking();
1130 }
1131
spellCheckerDocumentTag()1132 int Editor::spellCheckerDocumentTag()
1133 {
1134 return client() ? client()->spellCheckerDocumentTag() : 0;
1135 }
1136
shouldEndEditing(Range * range)1137 bool Editor::shouldEndEditing(Range* range)
1138 {
1139 return client() && client()->shouldEndEditing(range);
1140 }
1141
shouldBeginEditing(Range * range)1142 bool Editor::shouldBeginEditing(Range* range)
1143 {
1144 return client() && client()->shouldBeginEditing(range);
1145 }
1146
clearUndoRedoOperations()1147 void Editor::clearUndoRedoOperations()
1148 {
1149 if (client())
1150 client()->clearUndoRedoOperations();
1151 }
1152
canUndo()1153 bool Editor::canUndo()
1154 {
1155 return client() && client()->canUndo();
1156 }
1157
undo()1158 void Editor::undo()
1159 {
1160 if (client())
1161 client()->undo();
1162 }
1163
canRedo()1164 bool Editor::canRedo()
1165 {
1166 return client() && client()->canRedo();
1167 }
1168
redo()1169 void Editor::redo()
1170 {
1171 if (client())
1172 client()->redo();
1173 }
1174
didBeginEditing()1175 void Editor::didBeginEditing()
1176 {
1177 if (client())
1178 client()->didBeginEditing();
1179 }
1180
didEndEditing()1181 void Editor::didEndEditing()
1182 {
1183 if (client())
1184 client()->didEndEditing();
1185 }
1186
didWriteSelectionToPasteboard()1187 void Editor::didWriteSelectionToPasteboard()
1188 {
1189 if (client())
1190 client()->didWriteSelectionToPasteboard();
1191 }
1192
toggleBold()1193 void Editor::toggleBold()
1194 {
1195 command("ToggleBold").execute();
1196 }
1197
toggleUnderline()1198 void Editor::toggleUnderline()
1199 {
1200 command("ToggleUnderline").execute();
1201 }
1202
setBaseWritingDirection(WritingDirection direction)1203 void Editor::setBaseWritingDirection(WritingDirection direction)
1204 {
1205 Node* focusedNode = frame()->document()->focusedNode();
1206 if (focusedNode && (focusedNode->hasTagName(textareaTag)
1207 || focusedNode->hasTagName(inputTag) && (static_cast<HTMLInputElement*>(focusedNode)->inputType() == HTMLInputElement::TEXT
1208 || static_cast<HTMLInputElement*>(focusedNode)->inputType() == HTMLInputElement::SEARCH))) {
1209 if (direction == NaturalWritingDirection)
1210 return;
1211 static_cast<HTMLElement*>(focusedNode)->setAttribute(dirAttr, direction == LeftToRightWritingDirection ? "ltr" : "rtl");
1212 frame()->document()->updateRendering();
1213 return;
1214 }
1215
1216 RefPtr<CSSMutableStyleDeclaration> style = CSSMutableStyleDeclaration::create();
1217 style->setProperty(CSSPropertyDirection, direction == LeftToRightWritingDirection ? "ltr" : direction == RightToLeftWritingDirection ? "rtl" : "inherit", false);
1218 applyParagraphStyleToSelection(style.get(), EditActionSetWritingDirection);
1219 }
1220
selectComposition()1221 void Editor::selectComposition()
1222 {
1223 RefPtr<Range> range = compositionRange();
1224 if (!range)
1225 return;
1226
1227 // The composition can start inside a composed character sequence, so we have to override checks.
1228 // See <http://bugs.webkit.org/show_bug.cgi?id=15781>
1229 Selection selection;
1230 selection.setWithoutValidation(range->startPosition(), range->endPosition());
1231 m_frame->selection()->setSelection(selection, false, false);
1232 }
1233
confirmComposition()1234 void Editor::confirmComposition()
1235 {
1236 if (!m_compositionNode)
1237 return;
1238 confirmComposition(m_compositionNode->data().substring(m_compositionStart, m_compositionEnd - m_compositionStart), false);
1239 }
1240
confirmCompositionWithoutDisturbingSelection()1241 void Editor::confirmCompositionWithoutDisturbingSelection()
1242 {
1243 if (!m_compositionNode)
1244 return;
1245 confirmComposition(m_compositionNode->data().substring(m_compositionStart, m_compositionEnd - m_compositionStart), true);
1246 }
1247
confirmComposition(const String & text)1248 void Editor::confirmComposition(const String& text)
1249 {
1250 confirmComposition(text, false);
1251 }
1252
confirmComposition(const String & text,bool preserveSelection)1253 void Editor::confirmComposition(const String& text, bool preserveSelection)
1254 {
1255 setIgnoreCompositionSelectionChange(true);
1256
1257 Selection oldSelection = m_frame->selection()->selection();
1258
1259 selectComposition();
1260
1261 if (m_frame->selection()->isNone()) {
1262 setIgnoreCompositionSelectionChange(false);
1263 return;
1264 }
1265
1266 // If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input
1267 // will delete the old composition with an optimized replace operation.
1268 if (text.isEmpty())
1269 TypingCommand::deleteSelection(m_frame->document(), false);
1270
1271 m_compositionNode = 0;
1272 m_customCompositionUnderlines.clear();
1273
1274 insertText(text, 0);
1275
1276 if (preserveSelection)
1277 m_frame->selection()->setSelection(oldSelection, false, false);
1278
1279 setIgnoreCompositionSelectionChange(false);
1280 }
1281
setComposition(const String & text,const Vector<CompositionUnderline> & underlines,unsigned selectionStart,unsigned selectionEnd)1282 void Editor::setComposition(const String& text, const Vector<CompositionUnderline>& underlines, unsigned selectionStart, unsigned selectionEnd)
1283 {
1284 setIgnoreCompositionSelectionChange(true);
1285
1286 selectComposition();
1287
1288 if (m_frame->selection()->isNone()) {
1289 setIgnoreCompositionSelectionChange(false);
1290 return;
1291 }
1292
1293 // If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input
1294 // will delete the old composition with an optimized replace operation.
1295 if (text.isEmpty())
1296 TypingCommand::deleteSelection(m_frame->document(), false);
1297
1298 m_compositionNode = 0;
1299 m_customCompositionUnderlines.clear();
1300
1301 if (!text.isEmpty()) {
1302 TypingCommand::insertText(m_frame->document(), text, true, true);
1303
1304 Node* baseNode = m_frame->selection()->base().node();
1305 unsigned baseOffset = m_frame->selection()->base().offset();
1306 Node* extentNode = m_frame->selection()->extent().node();
1307 unsigned extentOffset = m_frame->selection()->extent().offset();
1308
1309 if (baseNode && baseNode == extentNode && baseNode->isTextNode() && baseOffset + text.length() == extentOffset) {
1310 m_compositionNode = static_cast<Text*>(baseNode);
1311 m_compositionStart = baseOffset;
1312 m_compositionEnd = extentOffset;
1313 m_customCompositionUnderlines = underlines;
1314 size_t numUnderlines = m_customCompositionUnderlines.size();
1315 for (size_t i = 0; i < numUnderlines; ++i) {
1316 m_customCompositionUnderlines[i].startOffset += baseOffset;
1317 m_customCompositionUnderlines[i].endOffset += baseOffset;
1318 }
1319 if (baseNode->renderer())
1320 baseNode->renderer()->repaint();
1321
1322 unsigned start = min(baseOffset + selectionStart, extentOffset);
1323 unsigned end = min(max(start, baseOffset + selectionEnd), extentOffset);
1324 RefPtr<Range> selectedRange = Range::create(baseNode->document(), baseNode, start, baseNode, end);
1325 m_frame->selection()->setSelectedRange(selectedRange.get(), DOWNSTREAM, false);
1326 }
1327 }
1328
1329 setIgnoreCompositionSelectionChange(false);
1330 }
1331
ignoreSpelling()1332 void Editor::ignoreSpelling()
1333 {
1334 if (!client())
1335 return;
1336
1337 RefPtr<Range> selectedRange = frame()->selection()->toRange();
1338 if (selectedRange)
1339 frame()->document()->removeMarkers(selectedRange.get(), DocumentMarker::Spelling);
1340
1341 String text = frame()->selectedText();
1342 ASSERT(text.length() != 0);
1343 client()->ignoreWordInSpellDocument(text);
1344 }
1345
learnSpelling()1346 void Editor::learnSpelling()
1347 {
1348 if (!client())
1349 return;
1350
1351 // FIXME: We don't call this on the Mac, and it should remove misppelling markers around the
1352 // learned word, see <rdar://problem/5396072>.
1353
1354 String text = frame()->selectedText();
1355 ASSERT(text.length() != 0);
1356 client()->learnWord(text);
1357 }
1358
findFirstMisspellingInRange(EditorClient * client,Range * searchRange,int & firstMisspellingOffset,bool markAll)1359 static String findFirstMisspellingInRange(EditorClient* client, Range* searchRange, int& firstMisspellingOffset, bool markAll)
1360 {
1361 ASSERT_ARG(client, client);
1362 ASSERT_ARG(searchRange, searchRange);
1363
1364 WordAwareIterator it(searchRange);
1365 firstMisspellingOffset = 0;
1366
1367 String firstMisspelling;
1368 int currentChunkOffset = 0;
1369
1370 while (!it.atEnd()) {
1371 const UChar* chars = it.characters();
1372 int len = it.length();
1373
1374 // Skip some work for one-space-char hunks
1375 if (!(len == 1 && chars[0] == ' ')) {
1376
1377 int misspellingLocation = -1;
1378 int misspellingLength = 0;
1379 client->checkSpellingOfString(chars, len, &misspellingLocation, &misspellingLength);
1380
1381 // 5490627 shows that there was some code path here where the String constructor below crashes.
1382 // We don't know exactly what combination of bad input caused this, so we're making this much
1383 // more robust against bad input on release builds.
1384 ASSERT(misspellingLength >= 0);
1385 ASSERT(misspellingLocation >= -1);
1386 ASSERT(misspellingLength == 0 || misspellingLocation >= 0);
1387 ASSERT(misspellingLocation < len);
1388 ASSERT(misspellingLength <= len);
1389 ASSERT(misspellingLocation + misspellingLength <= len);
1390
1391 if (misspellingLocation >= 0 && misspellingLength > 0 && misspellingLocation < len && misspellingLength <= len && misspellingLocation + misspellingLength <= len) {
1392
1393 // Remember first-encountered misspelling and its offset
1394 if (!firstMisspelling) {
1395 firstMisspellingOffset = currentChunkOffset + misspellingLocation;
1396 firstMisspelling = String(chars + misspellingLocation, misspellingLength);
1397 }
1398
1399 // Mark this instance if we're marking all instances. Otherwise bail out because we found the first one.
1400 if (!markAll)
1401 break;
1402
1403 // Compute range of misspelled word
1404 RefPtr<Range> misspellingRange = TextIterator::subrange(searchRange, currentChunkOffset + misspellingLocation, misspellingLength);
1405
1406 // Store marker for misspelled word
1407 ExceptionCode ec = 0;
1408 misspellingRange->startContainer(ec)->document()->addMarker(misspellingRange.get(), DocumentMarker::Spelling);
1409 ASSERT(ec == 0);
1410 }
1411 }
1412
1413 currentChunkOffset += len;
1414 it.advance();
1415 }
1416
1417 return firstMisspelling;
1418 }
1419
1420 #ifndef BUILDING_ON_TIGER
1421
paragraphAlignedRangeForRange(Range * arbitraryRange,int & offsetIntoParagraphAlignedRange,String & paragraphString)1422 static PassRefPtr<Range> paragraphAlignedRangeForRange(Range* arbitraryRange, int& offsetIntoParagraphAlignedRange, String& paragraphString)
1423 {
1424 ASSERT_ARG(arbitraryRange, arbitraryRange);
1425
1426 ExceptionCode ec = 0;
1427
1428 // Expand range to paragraph boundaries
1429 RefPtr<Range> paragraphRange = arbitraryRange->cloneRange(ec);
1430 setStart(paragraphRange.get(), startOfParagraph(arbitraryRange->startPosition()));
1431 setEnd(paragraphRange.get(), endOfParagraph(arbitraryRange->endPosition()));
1432
1433 // Compute offset from start of expanded range to start of original range
1434 RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), arbitraryRange->startPosition());
1435 offsetIntoParagraphAlignedRange = TextIterator::rangeLength(offsetAsRange.get());
1436
1437 // Fill in out parameter with string representing entire paragraph range.
1438 // Someday we might have a caller that doesn't use this, but for now all callers do.
1439 paragraphString = plainText(paragraphRange.get());
1440
1441 return paragraphRange;
1442 }
1443
findFirstGrammarDetailInRange(const Vector<GrammarDetail> & grammarDetails,int badGrammarPhraseLocation,int,Range * searchRange,int startOffset,int endOffset,bool markAll)1444 static int findFirstGrammarDetailInRange(const Vector<GrammarDetail>& grammarDetails, int badGrammarPhraseLocation, int /*badGrammarPhraseLength*/, Range *searchRange, int startOffset, int endOffset, bool markAll)
1445 {
1446 // Found some bad grammar. Find the earliest detail range that starts in our search range (if any).
1447 // Optionally add a DocumentMarker for each detail in the range.
1448 int earliestDetailLocationSoFar = -1;
1449 int earliestDetailIndex = -1;
1450 for (unsigned i = 0; i < grammarDetails.size(); i++) {
1451 const GrammarDetail* detail = &grammarDetails[i];
1452 ASSERT(detail->length > 0 && detail->location >= 0);
1453
1454 int detailStartOffsetInParagraph = badGrammarPhraseLocation + detail->location;
1455
1456 // Skip this detail if it starts before the original search range
1457 if (detailStartOffsetInParagraph < startOffset)
1458 continue;
1459
1460 // Skip this detail if it starts after the original search range
1461 if (detailStartOffsetInParagraph >= endOffset)
1462 continue;
1463
1464 if (markAll) {
1465 RefPtr<Range> badGrammarRange = TextIterator::subrange(searchRange, badGrammarPhraseLocation - startOffset + detail->location, detail->length);
1466 ExceptionCode ec = 0;
1467 badGrammarRange->startContainer(ec)->document()->addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail->userDescription);
1468 ASSERT(ec == 0);
1469 }
1470
1471 // Remember this detail only if it's earlier than our current candidate (the details aren't in a guaranteed order)
1472 if (earliestDetailIndex < 0 || earliestDetailLocationSoFar > detail->location) {
1473 earliestDetailIndex = i;
1474 earliestDetailLocationSoFar = detail->location;
1475 }
1476 }
1477
1478 return earliestDetailIndex;
1479 }
1480
findFirstBadGrammarInRange(EditorClient * client,Range * searchRange,GrammarDetail & outGrammarDetail,int & outGrammarPhraseOffset,bool markAll)1481 static String findFirstBadGrammarInRange(EditorClient* client, Range* searchRange, GrammarDetail& outGrammarDetail, int& outGrammarPhraseOffset, bool markAll)
1482 {
1483 ASSERT_ARG(client, client);
1484 ASSERT_ARG(searchRange, searchRange);
1485
1486 // Initialize out parameters; these will be updated if we find something to return.
1487 outGrammarDetail.location = -1;
1488 outGrammarDetail.length = 0;
1489 outGrammarDetail.guesses.clear();
1490 outGrammarDetail.userDescription = "";
1491 outGrammarPhraseOffset = 0;
1492
1493 String firstBadGrammarPhrase;
1494
1495 // Expand the search range to encompass entire paragraphs, since grammar checking needs that much context.
1496 // Determine the character offset from the start of the paragraph to the start of the original search range,
1497 // since we will want to ignore results in this area.
1498 int searchRangeStartOffset;
1499 String paragraphString;
1500 RefPtr<Range> paragraphRange = paragraphAlignedRangeForRange(searchRange, searchRangeStartOffset, paragraphString);
1501
1502 // Determine the character offset from the start of the paragraph to the end of the original search range,
1503 // since we will want to ignore results in this area also.
1504 int searchRangeEndOffset = searchRangeStartOffset + TextIterator::rangeLength(searchRange);
1505
1506 // Start checking from beginning of paragraph, but skip past results that occur before the start of the original search range.
1507 int startOffset = 0;
1508 while (startOffset < searchRangeEndOffset) {
1509 Vector<GrammarDetail> grammarDetails;
1510 int badGrammarPhraseLocation = -1;
1511 int badGrammarPhraseLength = 0;
1512 client->checkGrammarOfString(paragraphString.characters() + startOffset, paragraphString.length() - startOffset, grammarDetails, &badGrammarPhraseLocation, &badGrammarPhraseLength);
1513
1514 if (badGrammarPhraseLength == 0) {
1515 ASSERT(badGrammarPhraseLocation == -1);
1516 return String();
1517 }
1518
1519 ASSERT(badGrammarPhraseLocation >= 0);
1520 badGrammarPhraseLocation += startOffset;
1521
1522
1523 // Found some bad grammar. Find the earliest detail range that starts in our search range (if any).
1524 int badGrammarIndex = findFirstGrammarDetailInRange(grammarDetails, badGrammarPhraseLocation, badGrammarPhraseLength, searchRange, searchRangeStartOffset, searchRangeEndOffset, markAll);
1525 if (badGrammarIndex >= 0) {
1526 ASSERT(static_cast<unsigned>(badGrammarIndex) < grammarDetails.size());
1527 outGrammarDetail = grammarDetails[badGrammarIndex];
1528 }
1529
1530 // If we found a detail in range, then we have found the first bad phrase (unless we found one earlier but
1531 // kept going so we could mark all instances).
1532 if (badGrammarIndex >= 0 && firstBadGrammarPhrase.isEmpty()) {
1533 outGrammarPhraseOffset = badGrammarPhraseLocation - searchRangeStartOffset;
1534 firstBadGrammarPhrase = paragraphString.substring(badGrammarPhraseLocation, badGrammarPhraseLength);
1535
1536 // Found one. We're done now, unless we're marking each instance.
1537 if (!markAll)
1538 break;
1539 }
1540
1541 // These results were all between the start of the paragraph and the start of the search range; look
1542 // beyond this phrase.
1543 startOffset = badGrammarPhraseLocation + badGrammarPhraseLength;
1544 }
1545
1546 return firstBadGrammarPhrase;
1547 }
1548
1549 #endif /* not BUILDING_ON_TIGER */
1550
advanceToNextMisspelling(bool startBeforeSelection)1551 void Editor::advanceToNextMisspelling(bool startBeforeSelection)
1552 {
1553 ExceptionCode ec = 0;
1554
1555 // The basic approach is to search in two phases - from the selection end to the end of the doc, and
1556 // then we wrap and search from the doc start to (approximately) where we started.
1557
1558 // Start at the end of the selection, search to edge of document. Starting at the selection end makes
1559 // repeated "check spelling" commands work.
1560 Selection selection(frame()->selection()->selection());
1561 RefPtr<Range> spellingSearchRange(rangeOfContents(frame()->document()));
1562 bool startedWithSelection = false;
1563 if (selection.start().node()) {
1564 startedWithSelection = true;
1565 if (startBeforeSelection) {
1566 VisiblePosition start(selection.visibleStart());
1567 // We match AppKit's rule: Start 1 character before the selection.
1568 VisiblePosition oneBeforeStart = start.previous();
1569 setStart(spellingSearchRange.get(), oneBeforeStart.isNotNull() ? oneBeforeStart : start);
1570 } else
1571 setStart(spellingSearchRange.get(), selection.visibleEnd());
1572 }
1573
1574 Position position = spellingSearchRange->startPosition();
1575 if (!isEditablePosition(position)) {
1576 // This shouldn't happen in very often because the Spelling menu items aren't enabled unless the
1577 // selection is editable.
1578 // This can happen in Mail for a mix of non-editable and editable content (like Stationary),
1579 // when spell checking the whole document before sending the message.
1580 // In that case the document might not be editable, but there are editable pockets that need to be spell checked.
1581
1582 position = firstEditablePositionAfterPositionInRoot(position, frame()->document()->documentElement()).deepEquivalent();
1583 if (position.isNull())
1584 return;
1585
1586 Position rangeCompliantPosition = rangeCompliantEquivalent(position);
1587 spellingSearchRange->setStart(rangeCompliantPosition.node(), rangeCompliantPosition.offset(), ec);
1588 startedWithSelection = false; // won't need to wrap
1589 }
1590
1591 // topNode defines the whole range we want to operate on
1592 Node* topNode = highestEditableRoot(position);
1593 spellingSearchRange->setEnd(topNode, maxDeepOffset(topNode), ec);
1594
1595 // If spellingSearchRange starts in the middle of a word, advance to the next word so we start checking
1596 // at a word boundary. Going back by one char and then forward by a word does the trick.
1597 if (startedWithSelection) {
1598 VisiblePosition oneBeforeStart = startVisiblePosition(spellingSearchRange.get(), DOWNSTREAM).previous();
1599 if (oneBeforeStart.isNotNull()) {
1600 setStart(spellingSearchRange.get(), endOfWord(oneBeforeStart));
1601 } // else we were already at the start of the editable node
1602 }
1603
1604 if (spellingSearchRange->collapsed(ec))
1605 return; // nothing to search in
1606
1607 // Get the spell checker if it is available
1608 if (!client())
1609 return;
1610
1611 // We go to the end of our first range instead of the start of it, just to be sure
1612 // we don't get foiled by any word boundary problems at the start. It means we might
1613 // do a tiny bit more searching.
1614 Node *searchEndNodeAfterWrap = spellingSearchRange->endContainer(ec);
1615 int searchEndOffsetAfterWrap = spellingSearchRange->endOffset(ec);
1616
1617 int misspellingOffset;
1618 String misspelledWord = findFirstMisspellingInRange(client(), spellingSearchRange.get(), misspellingOffset, false);
1619
1620 String badGrammarPhrase;
1621
1622 #ifndef BUILDING_ON_TIGER
1623 int grammarPhraseOffset = 0;
1624 GrammarDetail grammarDetail;
1625
1626 // Search for bad grammar that occurs prior to the next misspelled word (if any)
1627 RefPtr<Range> grammarSearchRange = spellingSearchRange->cloneRange(ec);
1628 if (!misspelledWord.isEmpty()) {
1629 // Stop looking at start of next misspelled word
1630 CharacterIterator chars(grammarSearchRange.get());
1631 chars.advance(misspellingOffset);
1632 grammarSearchRange->setEnd(chars.range()->startContainer(ec), chars.range()->startOffset(ec), ec);
1633 }
1634
1635 if (isGrammarCheckingEnabled())
1636 badGrammarPhrase = findFirstBadGrammarInRange(client(), grammarSearchRange.get(), grammarDetail, grammarPhraseOffset, false);
1637 #endif
1638
1639 // 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
1640 // block rather than at a selection).
1641 if (startedWithSelection && !misspelledWord && !badGrammarPhrase) {
1642 spellingSearchRange->setStart(topNode, 0, ec);
1643 // going until the end of the very first chunk we tested is far enough
1644 spellingSearchRange->setEnd(searchEndNodeAfterWrap, searchEndOffsetAfterWrap, ec);
1645
1646 misspelledWord = findFirstMisspellingInRange(client(), spellingSearchRange.get(), misspellingOffset, false);
1647
1648 #ifndef BUILDING_ON_TIGER
1649 grammarSearchRange = spellingSearchRange->cloneRange(ec);
1650 if (!misspelledWord.isEmpty()) {
1651 // Stop looking at start of next misspelled word
1652 CharacterIterator chars(grammarSearchRange.get());
1653 chars.advance(misspellingOffset);
1654 grammarSearchRange->setEnd(chars.range()->startContainer(ec), chars.range()->startOffset(ec), ec);
1655 }
1656 if (isGrammarCheckingEnabled())
1657 badGrammarPhrase = findFirstBadGrammarInRange(client(), grammarSearchRange.get(), grammarDetail, grammarPhraseOffset, false);
1658 #endif
1659 }
1660
1661 if (!badGrammarPhrase.isEmpty()) {
1662 #ifdef BUILDING_ON_TIGER
1663 ASSERT_NOT_REACHED();
1664 #else
1665 // We found bad grammar. Since we only searched for bad grammar up to the first misspelled word, the bad grammar
1666 // takes precedence and we ignore any potential misspelled word. Select the grammar detail, update the spelling
1667 // panel, and store a marker so we draw the green squiggle later.
1668
1669 ASSERT(badGrammarPhrase.length() > 0);
1670 ASSERT(grammarDetail.location != -1 && grammarDetail.length > 0);
1671
1672 // FIXME 4859190: This gets confused with doubled punctuation at the end of a paragraph
1673 RefPtr<Range> badGrammarRange = TextIterator::subrange(grammarSearchRange.get(), grammarPhraseOffset + grammarDetail.location, grammarDetail.length);
1674 frame()->selection()->setSelection(Selection(badGrammarRange.get(), SEL_DEFAULT_AFFINITY));
1675 frame()->revealSelection();
1676
1677 client()->updateSpellingUIWithGrammarString(badGrammarPhrase, grammarDetail);
1678 frame()->document()->addMarker(badGrammarRange.get(), DocumentMarker::Grammar, grammarDetail.userDescription);
1679 #endif
1680 } else if (!misspelledWord.isEmpty()) {
1681 // We found a misspelling, but not any earlier bad grammar. Select the misspelling, update the spelling panel, and store
1682 // a marker so we draw the red squiggle later.
1683
1684 RefPtr<Range> misspellingRange = TextIterator::subrange(spellingSearchRange.get(), misspellingOffset, misspelledWord.length());
1685 frame()->selection()->setSelection(Selection(misspellingRange.get(), DOWNSTREAM));
1686 frame()->revealSelection();
1687
1688 client()->updateSpellingUIWithMisspelledWord(misspelledWord);
1689 frame()->document()->addMarker(misspellingRange.get(), DocumentMarker::Spelling);
1690 }
1691 }
1692
isSelectionMisspelled()1693 bool Editor::isSelectionMisspelled()
1694 {
1695 String selectedString = frame()->selectedText();
1696 int length = selectedString.length();
1697 if (length == 0)
1698 return false;
1699
1700 if (!client())
1701 return false;
1702
1703 int misspellingLocation = -1;
1704 int misspellingLength = 0;
1705 client()->checkSpellingOfString(selectedString.characters(), length, &misspellingLocation, &misspellingLength);
1706
1707 // The selection only counts as misspelled if the selected text is exactly one misspelled word
1708 if (misspellingLength != length)
1709 return false;
1710
1711 // Update the spelling panel to be displaying this error (whether or not the spelling panel is on screen).
1712 // This is necessary to make a subsequent call to [NSSpellChecker ignoreWord:inSpellDocumentWithTag:] work
1713 // correctly; that call behaves differently based on whether the spelling panel is displaying a misspelling
1714 // or a grammar error.
1715 client()->updateSpellingUIWithMisspelledWord(selectedString);
1716
1717 return true;
1718 }
1719
1720 #ifndef BUILDING_ON_TIGER
isRangeUngrammatical(EditorClient * client,Range * range,Vector<String> & guessesVector)1721 static bool isRangeUngrammatical(EditorClient* client, Range *range, Vector<String>& guessesVector)
1722 {
1723 if (!client)
1724 return false;
1725
1726 ExceptionCode ec;
1727 if (!range || range->collapsed(ec))
1728 return false;
1729
1730 // Returns true only if the passed range exactly corresponds to a bad grammar detail range. This is analogous
1731 // to isSelectionMisspelled. It's not good enough for there to be some bad grammar somewhere in the range,
1732 // or overlapping the range; the ranges must exactly match.
1733 guessesVector.clear();
1734 int grammarPhraseOffset;
1735
1736 GrammarDetail grammarDetail;
1737 String badGrammarPhrase = findFirstBadGrammarInRange(client, range, grammarDetail, grammarPhraseOffset, false);
1738
1739 // No bad grammar in these parts at all.
1740 if (badGrammarPhrase.isEmpty())
1741 return false;
1742
1743 // Bad grammar, but phrase (e.g. sentence) starts beyond start of range.
1744 if (grammarPhraseOffset > 0)
1745 return false;
1746
1747 ASSERT(grammarDetail.location >= 0 && grammarDetail.length > 0);
1748
1749 // Bad grammar, but start of detail (e.g. ungrammatical word) doesn't match start of range
1750 if (grammarDetail.location + grammarPhraseOffset != 0)
1751 return false;
1752
1753 // Bad grammar at start of range, but end of bad grammar is before or after end of range
1754 if (grammarDetail.length != TextIterator::rangeLength(range))
1755 return false;
1756
1757 // Update the spelling panel to be displaying this error (whether or not the spelling panel is on screen).
1758 // This is necessary to make a subsequent call to [NSSpellChecker ignoreWord:inSpellDocumentWithTag:] work
1759 // correctly; that call behaves differently based on whether the spelling panel is displaying a misspelling
1760 // or a grammar error.
1761 client->updateSpellingUIWithGrammarString(badGrammarPhrase, grammarDetail);
1762
1763 return true;
1764 }
1765 #endif
1766
isSelectionUngrammatical()1767 bool Editor::isSelectionUngrammatical()
1768 {
1769 #ifdef BUILDING_ON_TIGER
1770 return false;
1771 #else
1772 Vector<String> ignoredGuesses;
1773 return isRangeUngrammatical(client(), frame()->selection()->toRange().get(), ignoredGuesses);
1774 #endif
1775 }
1776
guessesForUngrammaticalSelection()1777 Vector<String> Editor::guessesForUngrammaticalSelection()
1778 {
1779 #ifdef BUILDING_ON_TIGER
1780 return Vector<String>();
1781 #else
1782 Vector<String> guesses;
1783 // Ignore the result of isRangeUngrammatical; we just want the guesses, whether or not there are any
1784 isRangeUngrammatical(client(), frame()->selection()->toRange().get(), guesses);
1785 return guesses;
1786 #endif
1787 }
1788
guessesForMisspelledSelection()1789 Vector<String> Editor::guessesForMisspelledSelection()
1790 {
1791 String selectedString = frame()->selectedText();
1792 ASSERT(selectedString.length() != 0);
1793
1794 Vector<String> guesses;
1795 if (client())
1796 client()->getGuessesForWord(selectedString, guesses);
1797 return guesses;
1798 }
1799
showSpellingGuessPanel()1800 void Editor::showSpellingGuessPanel()
1801 {
1802 if (!client()) {
1803 LOG_ERROR("No NSSpellChecker");
1804 return;
1805 }
1806
1807 #ifndef BUILDING_ON_TIGER
1808 // Post-Tiger, this menu item is a show/hide toggle, to match AppKit. Leave Tiger behavior alone
1809 // to match rest of OS X.
1810 if (client()->spellingUIIsShowing()) {
1811 client()->showSpellingUI(false);
1812 return;
1813 }
1814 #endif
1815
1816 advanceToNextMisspelling(true);
1817 client()->showSpellingUI(true);
1818 }
1819
spellingPanelIsShowing()1820 bool Editor::spellingPanelIsShowing()
1821 {
1822 if (!client())
1823 return false;
1824 return client()->spellingUIIsShowing();
1825 }
1826
markMisspellingsAfterTypingToPosition(const VisiblePosition & p)1827 void Editor::markMisspellingsAfterTypingToPosition(const VisiblePosition &p)
1828 {
1829 if (!isContinuousSpellCheckingEnabled())
1830 return;
1831
1832 // Check spelling of one word
1833 markMisspellings(Selection(startOfWord(p, LeftWordIfOnBoundary), endOfWord(p, RightWordIfOnBoundary)));
1834
1835 if (!isGrammarCheckingEnabled())
1836 return;
1837
1838 // Check grammar of entire sentence
1839 markBadGrammar(Selection(startOfSentence(p), endOfSentence(p)));
1840 }
1841
markAllMisspellingsInRange(EditorClient * client,Range * searchRange)1842 static void markAllMisspellingsInRange(EditorClient* client, Range* searchRange)
1843 {
1844 // Use the "markAll" feature of findFirstMisspellingInRange. Ignore the return value and the "out parameter";
1845 // all we need to do is mark every instance.
1846 int ignoredOffset;
1847 findFirstMisspellingInRange(client, searchRange, ignoredOffset, true);
1848 }
1849
1850 #ifndef BUILDING_ON_TIGER
markAllBadGrammarInRange(EditorClient * client,Range * searchRange)1851 static void markAllBadGrammarInRange(EditorClient* client, Range* searchRange)
1852 {
1853 // Use the "markAll" feature of findFirstBadGrammarInRange. Ignore the return value and "out parameters"; all we need to
1854 // do is mark every instance.
1855 GrammarDetail ignoredGrammarDetail;
1856 int ignoredOffset;
1857 findFirstBadGrammarInRange(client, searchRange, ignoredGrammarDetail, ignoredOffset, true);
1858 }
1859 #endif
1860
markMisspellingsOrBadGrammar(Editor * editor,const Selection & selection,bool checkSpelling)1861 static void markMisspellingsOrBadGrammar(Editor* editor, const Selection& selection, bool checkSpelling)
1862 {
1863 // This function is called with a selection already expanded to word boundaries.
1864 // Might be nice to assert that here.
1865
1866 // This function is used only for as-you-type checking, so if that's off we do nothing. Note that
1867 // grammar checking can only be on if spell checking is also on.
1868 if (!editor->isContinuousSpellCheckingEnabled())
1869 return;
1870
1871 RefPtr<Range> searchRange(selection.toRange());
1872 if (!searchRange)
1873 return;
1874
1875 // If we're not in an editable node, bail.
1876 Node* editableNode = searchRange->startContainer();
1877 if (!editableNode || !editableNode->isContentEditable())
1878 return;
1879
1880 // Get the spell checker if it is available
1881 if (!editor->client())
1882 return;
1883
1884 if (checkSpelling)
1885 markAllMisspellingsInRange(editor->client(), searchRange.get());
1886 else {
1887 #ifdef BUILDING_ON_TIGER
1888 ASSERT_NOT_REACHED();
1889 #else
1890 if (editor->isGrammarCheckingEnabled())
1891 markAllBadGrammarInRange(editor->client(), searchRange.get());
1892 #endif
1893 }
1894 }
1895
markMisspellings(const Selection & selection)1896 void Editor::markMisspellings(const Selection& selection)
1897 {
1898 markMisspellingsOrBadGrammar(this, selection, true);
1899 }
1900
markBadGrammar(const Selection & selection)1901 void Editor::markBadGrammar(const Selection& selection)
1902 {
1903 #ifndef BUILDING_ON_TIGER
1904 markMisspellingsOrBadGrammar(this, selection, false);
1905 #else
1906 UNUSED_PARAM(selection);
1907 #endif
1908 }
1909
rangeForPoint(const IntPoint & windowPoint)1910 PassRefPtr<Range> Editor::rangeForPoint(const IntPoint& windowPoint)
1911 {
1912 Document* document = m_frame->documentAtPoint(windowPoint);
1913 if (!document)
1914 return 0;
1915
1916 Frame* frame = document->frame();
1917 ASSERT(frame);
1918 FrameView* frameView = frame->view();
1919 if (!frameView)
1920 return 0;
1921 IntPoint framePoint = frameView->windowToContents(windowPoint);
1922 Selection selection(frame->visiblePositionForPoint(framePoint));
1923 return avoidIntersectionWithNode(selection.toRange().get(), deleteButtonController() ? deleteButtonController()->containerElement() : 0);
1924 }
1925
revealSelectionAfterEditingOperation()1926 void Editor::revealSelectionAfterEditingOperation()
1927 {
1928 if (m_ignoreCompositionSelectionChange)
1929 return;
1930
1931 m_frame->revealSelection(RenderLayer::gAlignToEdgeIfNeeded);
1932 }
1933
setIgnoreCompositionSelectionChange(bool ignore)1934 void Editor::setIgnoreCompositionSelectionChange(bool ignore)
1935 {
1936 if (m_ignoreCompositionSelectionChange == ignore)
1937 return;
1938
1939 m_ignoreCompositionSelectionChange = ignore;
1940 if (!ignore)
1941 revealSelectionAfterEditingOperation();
1942 }
1943
compositionRange() const1944 PassRefPtr<Range> Editor::compositionRange() const
1945 {
1946 if (!m_compositionNode)
1947 return 0;
1948 unsigned length = m_compositionNode->length();
1949 unsigned start = min(m_compositionStart, length);
1950 unsigned end = min(max(start, m_compositionEnd), length);
1951 if (start >= end)
1952 return 0;
1953 return Range::create(m_compositionNode->document(), m_compositionNode.get(), start, m_compositionNode.get(), end);
1954 }
1955
getCompositionSelection(unsigned & selectionStart,unsigned & selectionEnd) const1956 bool Editor::getCompositionSelection(unsigned& selectionStart, unsigned& selectionEnd) const
1957 {
1958 if (!m_compositionNode)
1959 return false;
1960 Position start = m_frame->selection()->start();
1961 if (start.node() != m_compositionNode)
1962 return false;
1963 Position end = m_frame->selection()->end();
1964 if (end.node() != m_compositionNode)
1965 return false;
1966
1967 if (static_cast<unsigned>(start.offset()) < m_compositionStart)
1968 return false;
1969 if (static_cast<unsigned>(end.offset()) > m_compositionEnd)
1970 return false;
1971
1972 selectionStart = start.offset() - m_compositionStart;
1973 selectionEnd = start.offset() - m_compositionEnd;
1974 return true;
1975 }
1976
transpose()1977 void Editor::transpose()
1978 {
1979 if (!canEdit())
1980 return;
1981
1982 Selection selection = m_frame->selection()->selection();
1983 if (!selection.isCaret())
1984 return;
1985
1986 // Make a selection that goes back one character and forward two characters.
1987 VisiblePosition caret = selection.visibleStart();
1988 VisiblePosition next = isEndOfParagraph(caret) ? caret : caret.next();
1989 VisiblePosition previous = next.previous();
1990 if (next == previous)
1991 return;
1992 previous = previous.previous();
1993 if (!inSameParagraph(next, previous))
1994 return;
1995 RefPtr<Range> range = makeRange(previous, next);
1996 if (!range)
1997 return;
1998 Selection newSelection(range.get(), DOWNSTREAM);
1999
2000 // Transpose the two characters.
2001 String text = plainText(range.get());
2002 if (text.length() != 2)
2003 return;
2004 String transposed = text.right(1) + text.left(1);
2005
2006 // Select the two characters.
2007 if (newSelection != m_frame->selection()->selection()) {
2008 if (!m_frame->shouldChangeSelection(newSelection))
2009 return;
2010 m_frame->selection()->setSelection(newSelection);
2011 }
2012
2013 // Insert the transposed characters.
2014 if (!shouldInsertText(transposed, range.get(), EditorInsertActionTyped))
2015 return;
2016 replaceSelectionWithText(transposed, false, false);
2017 }
2018
addToKillRing(Range * range,bool prepend)2019 void Editor::addToKillRing(Range* range, bool prepend)
2020 {
2021 if (m_shouldStartNewKillRingSequence)
2022 startNewKillRingSequence();
2023
2024 String text = m_frame->displayStringModifiedByEncoding(plainText(range));
2025 if (prepend)
2026 prependToKillRing(text);
2027 else
2028 appendToKillRing(text);
2029 m_shouldStartNewKillRingSequence = false;
2030 }
2031
2032 #if !PLATFORM(MAC)
2033
appendToKillRing(const String &)2034 void Editor::appendToKillRing(const String&)
2035 {
2036 }
2037
prependToKillRing(const String &)2038 void Editor::prependToKillRing(const String&)
2039 {
2040 }
2041
yankFromKillRing()2042 String Editor::yankFromKillRing()
2043 {
2044 return String();
2045 }
2046
startNewKillRingSequence()2047 void Editor::startNewKillRingSequence()
2048 {
2049 }
2050
setKillRingToYankedState()2051 void Editor::setKillRingToYankedState()
2052 {
2053 }
2054
2055 #endif
2056
insideVisibleArea(const IntPoint & point) const2057 bool Editor::insideVisibleArea(const IntPoint& point) const
2058 {
2059 if (m_frame->excludeFromTextSearch())
2060 return false;
2061
2062 // Right now, we only check the visibility of a point for disconnected frames. For all other
2063 // frames, we assume visibility.
2064 Frame* frame = m_frame->isDisconnected() ? m_frame : m_frame->tree()->top(true);
2065 if (!frame->isDisconnected())
2066 return true;
2067
2068 RenderPart* renderer = frame->ownerRenderer();
2069 RenderBlock* container = renderer->containingBlock();
2070 if (!(container->style()->overflowX() == OHIDDEN || container->style()->overflowY() == OHIDDEN))
2071 return true;
2072
2073 IntRect rectInPageCoords = container->getOverflowClipRect(0, 0);
2074 IntRect rectInFrameCoords = IntRect(renderer->x() * -1, renderer->y() * -1,
2075 rectInPageCoords.width(), rectInPageCoords.height());
2076
2077 return rectInFrameCoords.contains(point);
2078 }
2079
insideVisibleArea(Range * range) const2080 bool Editor::insideVisibleArea(Range* range) const
2081 {
2082 if (!range)
2083 return true;
2084
2085 if (m_frame->excludeFromTextSearch())
2086 return false;
2087
2088 // Right now, we only check the visibility of a range for disconnected frames. For all other
2089 // frames, we assume visibility.
2090 Frame* frame = m_frame->isDisconnected() ? m_frame : m_frame->tree()->top(true);
2091 if (!frame->isDisconnected())
2092 return true;
2093
2094 RenderPart* renderer = frame->ownerRenderer();
2095 RenderBlock* container = renderer->containingBlock();
2096 if (!(container->style()->overflowX() == OHIDDEN || container->style()->overflowY() == OHIDDEN))
2097 return true;
2098
2099 IntRect rectInPageCoords = container->getOverflowClipRect(0, 0);
2100 IntRect rectInFrameCoords = IntRect(renderer->x() * -1, renderer->y() * -1,
2101 rectInPageCoords.width(), rectInPageCoords.height());
2102 IntRect resultRect = range->boundingBox();
2103
2104 return rectInFrameCoords.contains(resultRect);
2105 }
2106
firstVisibleRange(const String & target,bool caseFlag)2107 PassRefPtr<Range> Editor::firstVisibleRange(const String& target, bool caseFlag)
2108 {
2109 RefPtr<Range> searchRange(rangeOfContents(m_frame->document()));
2110 RefPtr<Range> resultRange = findPlainText(searchRange.get(), target, true, caseFlag);
2111 ExceptionCode ec = 0;
2112
2113 while (!insideVisibleArea(resultRange.get())) {
2114 searchRange->setStartAfter(resultRange->endContainer(), ec);
2115 if (searchRange->startContainer() == searchRange->endContainer())
2116 return Range::create(m_frame->document());
2117 resultRange = findPlainText(searchRange.get(), target, true, caseFlag);
2118 }
2119
2120 return resultRange;
2121 }
2122
lastVisibleRange(const String & target,bool caseFlag)2123 PassRefPtr<Range> Editor::lastVisibleRange(const String& target, bool caseFlag)
2124 {
2125 RefPtr<Range> searchRange(rangeOfContents(m_frame->document()));
2126 RefPtr<Range> resultRange = findPlainText(searchRange.get(), target, false, caseFlag);
2127 ExceptionCode ec = 0;
2128
2129 while (!insideVisibleArea(resultRange.get())) {
2130 searchRange->setEndBefore(resultRange->startContainer(), ec);
2131 if (searchRange->startContainer() == searchRange->endContainer())
2132 return Range::create(m_frame->document());
2133 resultRange = findPlainText(searchRange.get(), target, false, caseFlag);
2134 }
2135
2136 return resultRange;
2137 }
2138
nextVisibleRange(Range * currentRange,const String & target,bool forward,bool caseFlag,bool wrapFlag)2139 PassRefPtr<Range> Editor::nextVisibleRange(Range* currentRange, const String& target, bool forward, bool caseFlag, bool wrapFlag)
2140 {
2141 if (m_frame->excludeFromTextSearch())
2142 return Range::create(m_frame->document());
2143
2144 RefPtr<Range> resultRange = currentRange;
2145 RefPtr<Range> searchRange(rangeOfContents(m_frame->document()));
2146 ExceptionCode ec = 0;
2147
2148 for ( ; !insideVisibleArea(resultRange.get()); resultRange = findPlainText(searchRange.get(), target, forward, caseFlag)) {
2149 if (resultRange->collapsed(ec)) {
2150 if (!resultRange->startContainer()->isInShadowTree())
2151 break;
2152 searchRange = rangeOfContents(m_frame->document());
2153 if (forward)
2154 searchRange->setStartAfter(resultRange->startContainer()->shadowAncestorNode(), ec);
2155 else
2156 searchRange->setEndBefore(resultRange->startContainer()->shadowAncestorNode(), ec);
2157 continue;
2158 }
2159
2160 if (forward)
2161 searchRange->setStartAfter(resultRange->endContainer(), ec);
2162 else
2163 searchRange->setEndBefore(resultRange->startContainer(), ec);
2164
2165 Node* shadowTreeRoot = searchRange->shadowTreeRootNode();
2166 if (searchRange->collapsed(ec) && shadowTreeRoot) {
2167 if (forward)
2168 searchRange->setEnd(shadowTreeRoot, shadowTreeRoot->childNodeCount(), ec);
2169 else
2170 searchRange->setStartBefore(shadowTreeRoot, ec);
2171 }
2172
2173 if (searchRange->startContainer()->isDocumentNode() && searchRange->endContainer()->isDocumentNode())
2174 break;
2175 }
2176
2177 if (insideVisibleArea(resultRange.get()))
2178 return resultRange;
2179
2180 if (!wrapFlag)
2181 return Range::create(m_frame->document());
2182
2183 if (forward)
2184 return firstVisibleRange(target, caseFlag);
2185
2186 return lastVisibleRange(target, caseFlag);
2187 }
2188
2189 } // namespace WebCore
2190