1/*
2 * Copyright (C) 2006 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 *
9 * 1.  Redistributions of source code must retain the above copyright
10 *     notice, this list of conditions and the following disclaimer.
11 * 2.  Redistributions in binary form must reproduce the above copyright
12 *     notice, this list of conditions and the following disclaimer in the
13 *     documentation and/or other materials provided with the distribution.
14 * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
15 *     its contributors may be used to endorse or promote products derived
16 *     from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30#import "WebEditorClient.h"
31
32#import "DOMCSSStyleDeclarationInternal.h"
33#import "DOMDocumentFragmentInternal.h"
34#import "DOMHTMLElementInternal.h"
35#import "DOMHTMLInputElementInternal.h"
36#import "DOMHTMLTextAreaElementInternal.h"
37#import "DOMNodeInternal.h"
38#import "DOMRangeInternal.h"
39#import "WebArchive.h"
40#import "WebDataSourceInternal.h"
41#import "WebDelegateImplementationCaching.h"
42#import "WebDocument.h"
43#import "WebEditingDelegatePrivate.h"
44#import "WebFormDelegate.h"
45#import "WebFrameInternal.h"
46#import "WebHTMLView.h"
47#import "WebHTMLViewInternal.h"
48#import "WebKitLogging.h"
49#import "WebKitVersionChecks.h"
50#import "WebLocalizableStringsInternal.h"
51#import "WebNSURLExtras.h"
52#import "WebResourceInternal.h"
53#import "WebViewInternal.h"
54#import <WebCore/ArchiveResource.h>
55#import <WebCore/Document.h>
56#import <WebCore/DocumentFragment.h>
57#import <WebCore/HTMLInputElement.h>
58#import <WebCore/HTMLNames.h>
59#import <WebCore/HTMLTextAreaElement.h>
60#import <WebCore/KeyboardEvent.h>
61#import <WebCore/LegacyWebArchive.h>
62#import <WebCore/Page.h>
63#import <WebCore/PlatformKeyboardEvent.h>
64#import <WebCore/Settings.h>
65#import <WebCore/SpellChecker.h>
66#import <WebCore/StyleProperties.h>
67#import <WebCore/UndoStep.h>
68#import <WebCore/UserTypingGestureIndicator.h>
69#import <WebCore/WebCoreObjCExtras.h>
70#import <runtime/InitializeThreading.h>
71#import <wtf/MainThread.h>
72#import <wtf/PassRefPtr.h>
73#import <wtf/RunLoop.h>
74#import <wtf/text/WTFString.h>
75
76#if PLATFORM(IOS)
77#import <WebCore/WebCoreThreadMessage.h>
78#import "DOMElementInternal.h"
79#import "WebFrameView.h"
80#import "WebUIKitDelegate.h"
81#endif
82
83using namespace WebCore;
84
85using namespace HTMLNames;
86
87#if !PLATFORM(IOS)
88@interface NSSpellChecker (WebNSSpellCheckerDetails)
89- (NSString *)languageForWordRange:(NSRange)range inString:(NSString *)string orthography:(NSOrthography *)orthography;
90@end
91#endif
92
93@interface NSAttributedString (WebNSAttributedStringDetails)
94- (id)_initWithDOMRange:(DOMRange*)range;
95- (DOMDocumentFragment*)_documentFromRange:(NSRange)range document:(DOMDocument*)document documentAttributes:(NSDictionary *)dict subresources:(NSArray **)subresources;
96@end
97
98static WebViewInsertAction kit(EditorInsertAction coreAction)
99{
100    return static_cast<WebViewInsertAction>(coreAction);
101}
102
103@interface WebUndoStep : NSObject
104{
105    RefPtr<UndoStep> m_step;
106}
107
108+ (WebUndoStep *)stepWithUndoStep:(PassRefPtr<UndoStep>)step;
109- (UndoStep *)step;
110
111@end
112
113@implementation WebUndoStep
114
115+ (void)initialize
116{
117#if !PLATFORM(IOS)
118    JSC::initializeThreading();
119    WTF::initializeMainThreadToProcessMainThread();
120    RunLoop::initializeMainRunLoop();
121#endif
122    WebCoreObjCFinalizeOnMainThread(self);
123}
124
125- (id)initWithUndoStep:(PassRefPtr<UndoStep>)step
126{
127    ASSERT(step);
128    self = [super init];
129    if (!self)
130        return nil;
131    m_step = step;
132    return self;
133}
134
135- (void)dealloc
136{
137    if (WebCoreObjCScheduleDeallocateOnMainThread([WebUndoStep class], self))
138        return;
139
140    [super dealloc];
141}
142
143- (void)finalize
144{
145    ASSERT_MAIN_THREAD();
146
147    [super finalize];
148}
149
150+ (WebUndoStep *)stepWithUndoStep:(PassRefPtr<UndoStep>)step
151{
152    return [[[WebUndoStep alloc] initWithUndoStep:step] autorelease];
153}
154
155- (UndoStep *)step
156{
157    return m_step.get();
158}
159
160@end
161
162@interface WebEditorUndoTarget : NSObject
163{
164}
165
166- (void)undoEditing:(id)arg;
167- (void)redoEditing:(id)arg;
168
169@end
170
171@implementation WebEditorUndoTarget
172
173- (void)undoEditing:(id)arg
174{
175    ASSERT([arg isKindOfClass:[WebUndoStep class]]);
176    [arg step]->unapply();
177}
178
179- (void)redoEditing:(id)arg
180{
181    ASSERT([arg isKindOfClass:[WebUndoStep class]]);
182    [arg step]->reapply();
183}
184
185@end
186
187void WebEditorClient::pageDestroyed()
188{
189    delete this;
190}
191
192WebEditorClient::WebEditorClient(WebView *webView)
193    : m_webView(webView)
194    , m_undoTarget([[[WebEditorUndoTarget alloc] init] autorelease])
195    , m_haveUndoRedoOperations(false)
196#if PLATFORM(IOS)
197    , m_delayingContentChangeNotifications(0)
198    , m_hasDelayedContentChangeNotification(0)
199#endif
200{
201}
202
203WebEditorClient::~WebEditorClient()
204{
205}
206
207bool WebEditorClient::isContinuousSpellCheckingEnabled()
208{
209    return [m_webView isContinuousSpellCheckingEnabled];
210}
211
212#if !PLATFORM(IOS)
213void WebEditorClient::toggleContinuousSpellChecking()
214{
215    [m_webView toggleContinuousSpellChecking:nil];
216}
217
218bool WebEditorClient::isGrammarCheckingEnabled()
219{
220    return [m_webView isGrammarCheckingEnabled];
221}
222
223void WebEditorClient::toggleGrammarChecking()
224{
225    [m_webView toggleGrammarChecking:nil];
226}
227
228int WebEditorClient::spellCheckerDocumentTag()
229{
230    return [m_webView spellCheckerDocumentTag];
231}
232#endif
233
234bool WebEditorClient::shouldDeleteRange(Range* range)
235{
236    return [[m_webView _editingDelegateForwarder] webView:m_webView
237        shouldDeleteDOMRange:kit(range)];
238}
239
240#if ENABLE(DELETION_UI)
241bool WebEditorClient::shouldShowDeleteInterface(HTMLElement* element)
242{
243    return [[m_webView _editingDelegateForwarder] webView:m_webView
244        shouldShowDeleteInterfaceForElement:kit(element)];
245}
246#endif
247
248bool WebEditorClient::smartInsertDeleteEnabled()
249{
250    Page* page = [m_webView page];
251    if (!page)
252        return false;
253    return page->settings().smartInsertDeleteEnabled();
254}
255
256bool WebEditorClient::isSelectTrailingWhitespaceEnabled()
257{
258    Page* page = [m_webView page];
259    if (!page)
260        return false;
261    return page->settings().selectTrailingWhitespaceEnabled();
262}
263
264bool WebEditorClient::shouldApplyStyle(StyleProperties* style, Range* range)
265{
266    Ref<MutableStyleProperties> mutableStyle(style->isMutable() ? static_cast<MutableStyleProperties&>(*style) : style->mutableCopy());
267    return [[m_webView _editingDelegateForwarder] webView:m_webView
268        shouldApplyStyle:kit(mutableStyle->ensureCSSStyleDeclaration()) toElementsInDOMRange:kit(range)];
269}
270
271bool WebEditorClient::shouldMoveRangeAfterDelete(Range* range, Range* rangeToBeReplaced)
272{
273    return [[m_webView _editingDelegateForwarder] webView:m_webView
274        shouldMoveRangeAfterDelete:kit(range) replacingRange:kit(rangeToBeReplaced)];
275}
276
277bool WebEditorClient::shouldBeginEditing(Range* range)
278{
279    return [[m_webView _editingDelegateForwarder] webView:m_webView
280        shouldBeginEditingInDOMRange:kit(range)];
281
282    return false;
283}
284
285bool WebEditorClient::shouldEndEditing(Range* range)
286{
287    return [[m_webView _editingDelegateForwarder] webView:m_webView
288                             shouldEndEditingInDOMRange:kit(range)];
289}
290
291bool WebEditorClient::shouldInsertText(const String& text, Range* range, EditorInsertAction action)
292{
293    WebView* webView = m_webView;
294    return [[webView _editingDelegateForwarder] webView:webView shouldInsertText:text replacingDOMRange:kit(range) givenAction:kit(action)];
295}
296
297bool WebEditorClient::shouldChangeSelectedRange(Range* fromRange, Range* toRange, EAffinity selectionAffinity, bool stillSelecting)
298{
299    return [m_webView _shouldChangeSelectedDOMRange:kit(fromRange) toDOMRange:kit(toRange) affinity:kit(selectionAffinity) stillSelecting:stillSelecting];
300}
301
302void WebEditorClient::didBeginEditing()
303{
304#if !PLATFORM(IOS)
305    [[NSNotificationCenter defaultCenter] postNotificationName:WebViewDidBeginEditingNotification object:m_webView];
306#else
307    WebThreadPostNotification(WebViewDidBeginEditingNotification, m_webView, nil);
308#endif
309}
310
311#if PLATFORM(IOS)
312void WebEditorClient::startDelayingAndCoalescingContentChangeNotifications()
313{
314    m_delayingContentChangeNotifications = true;
315}
316
317void WebEditorClient::stopDelayingAndCoalescingContentChangeNotifications()
318{
319    m_delayingContentChangeNotifications = false;
320
321    if (m_hasDelayedContentChangeNotification)
322        this->respondToChangedContents();
323
324    m_hasDelayedContentChangeNotification = false;
325}
326#endif
327
328void WebEditorClient::respondToChangedContents()
329{
330#if !PLATFORM(IOS)
331    NSView <WebDocumentView> *view = [[[m_webView selectedFrame] frameView] documentView];
332    if ([view isKindOfClass:[WebHTMLView class]])
333        [(WebHTMLView *)view _updateFontPanel];
334    [[NSNotificationCenter defaultCenter] postNotificationName:WebViewDidChangeNotification object:m_webView];
335#else
336    if (m_delayingContentChangeNotifications) {
337        m_hasDelayedContentChangeNotification = true;
338    } else {
339        WebThreadPostNotification(WebViewDidChangeNotification, m_webView, nil);
340    }
341#endif
342}
343
344void WebEditorClient::respondToChangedSelection(Frame* frame)
345{
346    NSView<WebDocumentView> *documentView = [[kit(frame) frameView] documentView];
347    if ([documentView isKindOfClass:[WebHTMLView class]])
348        [(WebHTMLView *)documentView _selectionChanged];
349
350#if !PLATFORM(IOS)
351    // FIXME: This quirk is needed due to <rdar://problem/5009625> - We can phase it out once Aperture can adopt the new behavior on their end
352    if (!WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITHOUT_APERTURE_QUIRK) && [[[NSBundle mainBundle] bundleIdentifier] isEqualToString:@"com.apple.Aperture"])
353        return;
354
355    [[NSNotificationCenter defaultCenter] postNotificationName:WebViewDidChangeSelectionNotification object:m_webView];
356#else
357    // Selection can be changed while deallocating down the WebView / Frame / Editor.  Do not post in that case because it's already too late
358    // for the NSInvocation to retain the WebView.
359    if (![m_webView _isClosing])
360        WebThreadPostNotification(WebViewDidChangeSelectionNotification, m_webView, nil);
361#endif
362}
363
364void WebEditorClient::didEndEditing()
365{
366#if !PLATFORM(IOS)
367    [[NSNotificationCenter defaultCenter] postNotificationName:WebViewDidEndEditingNotification object:m_webView];
368#else
369    WebThreadPostNotification(WebViewDidEndEditingNotification, m_webView, nil);
370#endif
371}
372
373void WebEditorClient::didWriteSelectionToPasteboard()
374{
375#if !PLATFORM(IOS)
376    [[m_webView _editingDelegateForwarder] webView:m_webView didWriteSelectionToPasteboard:[NSPasteboard generalPasteboard]];
377#endif
378}
379
380void WebEditorClient::willWriteSelectionToPasteboard(WebCore::Range*)
381{
382    // Not implemented WebKit, only WebKit2.
383}
384
385void WebEditorClient::getClientPasteboardDataForRange(WebCore::Range*, Vector<String>& pasteboardTypes, Vector<RefPtr<WebCore::SharedBuffer>>& pasteboardData)
386{
387    // Not implemented WebKit, only WebKit2.
388}
389
390NSString *WebEditorClient::userVisibleString(NSURL *URL)
391{
392    return [URL _web_userVisibleString];
393}
394
395NSURL *WebEditorClient::canonicalizeURL(NSURL *URL)
396{
397    return [URL _webkit_canonicalize];
398}
399
400NSURL *WebEditorClient::canonicalizeURLString(NSString *URLString)
401{
402    NSURL *URL = nil;
403    if ([URLString _webkit_looksLikeAbsoluteURL])
404        URL = [[NSURL _web_URLWithUserTypedString:URLString] _webkit_canonicalize];
405    return URL;
406}
407
408static NSArray *createExcludedElementsForAttributedStringConversion()
409{
410    NSArray *elements = [[NSArray alloc] initWithObjects:
411        // Omit style since we want style to be inline so the fragment can be easily inserted.
412        @"style",
413        // Omit xml so the result is not XHTML.
414        @"xml",
415        // Omit tags that will get stripped when converted to a fragment anyway.
416        @"doctype", @"html", @"head", @"body",
417        // Omit deprecated tags.
418        @"applet", @"basefont", @"center", @"dir", @"font", @"isindex", @"menu", @"s", @"strike", @"u",
419        // Omit object so no file attachments are part of the fragment.
420        @"object", nil];
421    CFRetain(elements);
422    return elements;
423}
424
425#if PLATFORM(IOS)
426static NSString *NSExcludedElementsDocumentAttribute = @"ExcludedElements";
427#endif
428
429DocumentFragment* WebEditorClient::documentFragmentFromAttributedString(NSAttributedString *string, Vector<RefPtr<ArchiveResource>>& resources)
430{
431    static NSArray *excludedElements = createExcludedElementsForAttributedStringConversion();
432
433    NSDictionary *dictionary = [NSDictionary dictionaryWithObject:excludedElements forKey:NSExcludedElementsDocumentAttribute];
434
435    NSArray *subResources;
436    DOMDocumentFragment* fragment = [string _documentFromRange:NSMakeRange(0, [string length])
437                                                      document:[[m_webView mainFrame] DOMDocument]
438                                            documentAttributes:dictionary
439                                                  subresources:&subResources];
440    for (WebResource* resource in subResources)
441        resources.append([resource _coreResource]);
442
443    return core(fragment);
444}
445
446void WebEditorClient::setInsertionPasteboard(const String& pasteboardName)
447{
448#if !PLATFORM(IOS)
449    NSPasteboard *pasteboard = pasteboardName.isEmpty() ? nil : [NSPasteboard pasteboardWithName:pasteboardName];
450    [m_webView _setInsertionPasteboard:pasteboard];
451#endif
452}
453
454#if USE(APPKIT)
455void WebEditorClient::uppercaseWord()
456{
457    [m_webView uppercaseWord:nil];
458}
459
460void WebEditorClient::lowercaseWord()
461{
462    [m_webView lowercaseWord:nil];
463}
464
465void WebEditorClient::capitalizeWord()
466{
467    [m_webView capitalizeWord:nil];
468}
469#endif
470
471#if USE(AUTOMATIC_TEXT_REPLACEMENT)
472void WebEditorClient::showSubstitutionsPanel(bool show)
473{
474    NSPanel *spellingPanel = [[NSSpellChecker sharedSpellChecker] substitutionsPanel];
475    if (show)
476        [spellingPanel orderFront:nil];
477    else
478        [spellingPanel orderOut:nil];
479}
480
481bool WebEditorClient::substitutionsPanelIsShowing()
482{
483    return [[[NSSpellChecker sharedSpellChecker] substitutionsPanel] isVisible];
484}
485
486void WebEditorClient::toggleSmartInsertDelete()
487{
488    [m_webView toggleSmartInsertDelete:nil];
489}
490
491bool WebEditorClient::isAutomaticQuoteSubstitutionEnabled()
492{
493    return [m_webView isAutomaticQuoteSubstitutionEnabled];
494}
495
496void WebEditorClient::toggleAutomaticQuoteSubstitution()
497{
498    [m_webView toggleAutomaticQuoteSubstitution:nil];
499}
500
501bool WebEditorClient::isAutomaticLinkDetectionEnabled()
502{
503    return [m_webView isAutomaticLinkDetectionEnabled];
504}
505
506void WebEditorClient::toggleAutomaticLinkDetection()
507{
508    [m_webView toggleAutomaticLinkDetection:nil];
509}
510
511bool WebEditorClient::isAutomaticDashSubstitutionEnabled()
512{
513    return [m_webView isAutomaticDashSubstitutionEnabled];
514}
515
516void WebEditorClient::toggleAutomaticDashSubstitution()
517{
518    [m_webView toggleAutomaticDashSubstitution:nil];
519}
520
521bool WebEditorClient::isAutomaticTextReplacementEnabled()
522{
523    return [m_webView isAutomaticTextReplacementEnabled];
524}
525
526void WebEditorClient::toggleAutomaticTextReplacement()
527{
528    [m_webView toggleAutomaticTextReplacement:nil];
529}
530
531bool WebEditorClient::isAutomaticSpellingCorrectionEnabled()
532{
533    return [m_webView isAutomaticSpellingCorrectionEnabled];
534}
535
536void WebEditorClient::toggleAutomaticSpellingCorrection()
537{
538    [m_webView toggleAutomaticSpellingCorrection:nil];
539}
540#endif // USE(AUTOMATIC_TEXT_REPLACEMENT)
541
542bool WebEditorClient::shouldInsertNode(Node *node, Range* replacingRange, EditorInsertAction givenAction)
543{
544    return [[m_webView _editingDelegateForwarder] webView:m_webView shouldInsertNode:kit(node) replacingDOMRange:kit(replacingRange) givenAction:(WebViewInsertAction)givenAction];
545}
546
547static NSString* undoNameForEditAction(EditAction editAction)
548{
549    switch (editAction) {
550        case EditActionUnspecified: return nil;
551        case EditActionSetColor: return UI_STRING_KEY_INTERNAL("Set Color", "Set Color (Undo action name)", "Undo action name");
552        case EditActionSetBackgroundColor: return UI_STRING_KEY_INTERNAL("Set Background Color", "Set Background Color (Undo action name)", "Undo action name");
553        case EditActionTurnOffKerning: return UI_STRING_KEY_INTERNAL("Turn Off Kerning", "Turn Off Kerning (Undo action name)", "Undo action name");
554        case EditActionTightenKerning: return UI_STRING_KEY_INTERNAL("Tighten Kerning", "Tighten Kerning (Undo action name)", "Undo action name");
555        case EditActionLoosenKerning: return UI_STRING_KEY_INTERNAL("Loosen Kerning", "Loosen Kerning (Undo action name)", "Undo action name");
556        case EditActionUseStandardKerning: return UI_STRING_KEY_INTERNAL("Use Standard Kerning", "Use Standard Kerning (Undo action name)", "Undo action name");
557        case EditActionTurnOffLigatures: return UI_STRING_KEY_INTERNAL("Turn Off Ligatures", "Turn Off Ligatures (Undo action name)", "Undo action name");
558        case EditActionUseStandardLigatures: return UI_STRING_KEY_INTERNAL("Use Standard Ligatures", "Use Standard Ligatures (Undo action name)", "Undo action name");
559        case EditActionUseAllLigatures: return UI_STRING_KEY_INTERNAL("Use All Ligatures", "Use All Ligatures (Undo action name)", "Undo action name");
560        case EditActionRaiseBaseline: return UI_STRING_KEY_INTERNAL("Raise Baseline", "Raise Baseline (Undo action name)", "Undo action name");
561        case EditActionLowerBaseline: return UI_STRING_KEY_INTERNAL("Lower Baseline", "Lower Baseline (Undo action name)", "Undo action name");
562        case EditActionSetTraditionalCharacterShape: return UI_STRING_KEY_INTERNAL("Set Traditional Character Shape", "Set Traditional Character Shape (Undo action name)", "Undo action name");
563        case EditActionSetFont: return UI_STRING_KEY_INTERNAL("Set Font", "Set Font (Undo action name)", "Undo action name");
564        case EditActionChangeAttributes: return UI_STRING_KEY_INTERNAL("Change Attributes", "Change Attributes (Undo action name)", "Undo action name");
565        case EditActionAlignLeft: return UI_STRING_KEY_INTERNAL("Align Left", "Align Left (Undo action name)", "Undo action name");
566        case EditActionAlignRight: return UI_STRING_KEY_INTERNAL("Align Right", "Align Right (Undo action name)", "Undo action name");
567        case EditActionCenter: return UI_STRING_KEY_INTERNAL("Center", "Center (Undo action name)", "Undo action name");
568        case EditActionJustify: return UI_STRING_KEY_INTERNAL("Justify", "Justify (Undo action name)", "Undo action name");
569        case EditActionSetWritingDirection: return UI_STRING_KEY_INTERNAL("Set Writing Direction", "Set Writing Direction (Undo action name)", "Undo action name");
570        case EditActionSubscript: return UI_STRING_KEY_INTERNAL("Subscript", "Subscript (Undo action name)", "Undo action name");
571        case EditActionSuperscript: return UI_STRING_KEY_INTERNAL("Superscript", "Superscript (Undo action name)", "Undo action name");
572        case EditActionUnderline: return UI_STRING_KEY_INTERNAL("Underline", "Underline (Undo action name)", "Undo action name");
573        case EditActionOutline: return UI_STRING_KEY_INTERNAL("Outline", "Outline (Undo action name)", "Undo action name");
574        case EditActionUnscript: return UI_STRING_KEY_INTERNAL("Unscript", "Unscript (Undo action name)", "Undo action name");
575        case EditActionDrag: return UI_STRING_KEY_INTERNAL("Drag", "Drag (Undo action name)", "Undo action name");
576        case EditActionCut: return UI_STRING_KEY_INTERNAL("Cut", "Cut (Undo action name)", "Undo action name");
577        case EditActionPaste: return UI_STRING_KEY_INTERNAL("Paste", "Paste (Undo action name)", "Undo action name");
578        case EditActionPasteFont: return UI_STRING_KEY_INTERNAL("Paste Font", "Paste Font (Undo action name)", "Undo action name");
579        case EditActionPasteRuler: return UI_STRING_KEY_INTERNAL("Paste Ruler", "Paste Ruler (Undo action name)", "Undo action name");
580        case EditActionTyping: return UI_STRING_KEY_INTERNAL("Typing", "Typing (Undo action name)", "Undo action name");
581        case EditActionCreateLink: return UI_STRING_KEY_INTERNAL("Create Link", "Create Link (Undo action name)", "Undo action name");
582        case EditActionUnlink: return UI_STRING_KEY_INTERNAL("Unlink", "Unlink (Undo action name)", "Undo action name");
583        case EditActionInsertList: return UI_STRING_KEY_INTERNAL("Insert List", "Insert List (Undo action name)", "Undo action name");
584        case EditActionFormatBlock: return UI_STRING_KEY_INTERNAL("Formatting", "Format Block (Undo action name)", "Undo action name");
585        case EditActionIndent: return UI_STRING_KEY_INTERNAL("Indent", "Indent (Undo action name)", "Undo action name");
586        case EditActionOutdent: return UI_STRING_KEY_INTERNAL("Outdent", "Outdent (Undo action name)", "Undo action name");
587        case EditActionBold: return UI_STRING_KEY_INTERNAL("Bold", "Bold (Undo action name)", "Undo action name");
588        case EditActionItalics: return UI_STRING_KEY_INTERNAL("Italics", "Italics (Undo action name)", "Undo action name");
589#if PLATFORM(IOS)
590        case EditActionDelete: return UI_STRING_KEY_INTERNAL("Delete", "Delete (Undo action name)", "Undo action name (Used only by PLATFORM(IOS) code)");
591        case EditActionDictation: return UI_STRING_KEY_INTERNAL("Dictation", "Dictation (Undo action name)", "Undo action name (Used only by PLATFORM(IOS) code)");
592#endif
593    }
594    return nil;
595}
596
597void WebEditorClient::registerUndoOrRedoStep(PassRefPtr<UndoStep> step, bool isRedo)
598{
599    ASSERT(step);
600
601    NSUndoManager *undoManager = [m_webView undoManager];
602#if PLATFORM(IOS)
603    // While we are undoing, we shouldn't be asked to register another Undo operation, we shouldn't even be touching the DOM.  But
604    // just in case this happens, return to avoid putting the undo manager into an inconsistent state.  Same for being
605    // asked to register a Redo operation in the midst of another Redo.
606    if (([undoManager isUndoing] && !isRedo) || ([undoManager isRedoing] && isRedo))
607        return;
608#endif
609    NSString *actionName = undoNameForEditAction(step->editingAction());
610    WebUndoStep *webEntry = [WebUndoStep stepWithUndoStep:step];
611    [undoManager registerUndoWithTarget:m_undoTarget.get() selector:(isRedo ? @selector(redoEditing:) : @selector(undoEditing:)) object:webEntry];
612    if (actionName)
613        [undoManager setActionName:actionName];
614    m_haveUndoRedoOperations = YES;
615}
616
617void WebEditorClient::registerUndoStep(PassRefPtr<UndoStep> cmd)
618{
619    registerUndoOrRedoStep(cmd, false);
620}
621
622void WebEditorClient::registerRedoStep(PassRefPtr<UndoStep> cmd)
623{
624    registerUndoOrRedoStep(cmd, true);
625}
626
627void WebEditorClient::clearUndoRedoOperations()
628{
629    if (m_haveUndoRedoOperations) {
630        // workaround for <rdar://problem/4645507> NSUndoManager dies
631        // with uncaught exception when undo items cleared while
632        // groups are open
633        NSUndoManager *undoManager = [m_webView undoManager];
634        int groupingLevel = [undoManager groupingLevel];
635        for (int i = 0; i < groupingLevel; ++i)
636            [undoManager endUndoGrouping];
637
638        [undoManager removeAllActionsWithTarget:m_undoTarget.get()];
639
640        for (int i = 0; i < groupingLevel; ++i)
641            [undoManager beginUndoGrouping];
642
643        m_haveUndoRedoOperations = NO;
644    }
645}
646
647bool WebEditorClient::canCopyCut(Frame*, bool defaultValue) const
648{
649    return defaultValue;
650}
651
652bool WebEditorClient::canPaste(Frame*, bool defaultValue) const
653{
654    return defaultValue;
655}
656
657bool WebEditorClient::canUndo() const
658{
659    return [[m_webView undoManager] canUndo];
660}
661
662bool WebEditorClient::canRedo() const
663{
664    return [[m_webView undoManager] canRedo];
665}
666
667void WebEditorClient::undo()
668{
669    if (canUndo())
670        [[m_webView undoManager] undo];
671}
672
673void WebEditorClient::redo()
674{
675    if (canRedo())
676        [[m_webView undoManager] redo];
677}
678
679void WebEditorClient::handleKeyboardEvent(KeyboardEvent* event)
680{
681    Frame* frame = event->target()->toNode()->document().frame();
682#if !PLATFORM(IOS)
683    WebHTMLView *webHTMLView = [[kit(frame) frameView] documentView];
684    if ([webHTMLView _interpretKeyEvent:event savingCommands:NO])
685        event->setDefaultHandled();
686#else
687    WebHTMLView *webHTMLView = (WebHTMLView *)[[kit(frame) frameView] documentView];
688    if ([webHTMLView _handleEditingKeyEvent:event])
689        event->setDefaultHandled();
690#endif
691}
692
693void WebEditorClient::handleInputMethodKeydown(KeyboardEvent* event)
694{
695#if !PLATFORM(IOS)
696    // FIXME: Switch to WebKit2 model, interpreting the event before it's sent down to WebCore.
697    Frame* frame = event->target()->toNode()->document().frame();
698    WebHTMLView *webHTMLView = [[kit(frame) frameView] documentView];
699    if ([webHTMLView _interpretKeyEvent:event savingCommands:YES])
700        event->setDefaultHandled();
701#else
702    // iOS does not use input manager this way
703#endif
704}
705
706#define FormDelegateLog(ctrl)  LOG(FormDelegate, "control=%@", ctrl)
707
708void WebEditorClient::textFieldDidBeginEditing(Element* element)
709{
710    if (!isHTMLInputElement(element))
711        return;
712
713    DOMHTMLInputElement* inputElement = kit(toHTMLInputElement(element));
714    FormDelegateLog(inputElement);
715    CallFormDelegate(m_webView, @selector(textFieldDidBeginEditing:inFrame:), inputElement, kit(element->document().frame()));
716}
717
718void WebEditorClient::textFieldDidEndEditing(Element* element)
719{
720    if (!isHTMLInputElement(element))
721        return;
722
723    DOMHTMLInputElement* inputElement = kit(toHTMLInputElement(element));
724    FormDelegateLog(inputElement);
725    CallFormDelegate(m_webView, @selector(textFieldDidEndEditing:inFrame:), inputElement, kit(element->document().frame()));
726}
727
728void WebEditorClient::textDidChangeInTextField(Element* element)
729{
730    if (!isHTMLInputElement(element))
731        return;
732
733#if !PLATFORM(IOS)
734    if (!UserTypingGestureIndicator::processingUserTypingGesture() || UserTypingGestureIndicator::focusedElementAtGestureStart() != element)
735        return;
736#endif
737
738    DOMHTMLInputElement* inputElement = kit(toHTMLInputElement(element));
739    FormDelegateLog(inputElement);
740    CallFormDelegate(m_webView, @selector(textDidChangeInTextField:inFrame:), inputElement, kit(element->document().frame()));
741}
742
743static SEL selectorForKeyEvent(KeyboardEvent* event)
744{
745    // FIXME: This helper function is for the auto-fill code so we can pass a selector to the form delegate.
746    // Eventually, we should move all of the auto-fill code down to WebKit and remove the need for this function by
747    // not relying on the selector in the new implementation.
748    // The key identifiers are from <http://www.w3.org/TR/DOM-Level-3-Events/keyset.html#KeySet-Set>
749    const String& key = event->keyIdentifier();
750    if (key == "Up")
751        return @selector(moveUp:);
752    if (key == "Down")
753        return @selector(moveDown:);
754    if (key == "U+001B")
755        return @selector(cancel:);
756    if (key == "U+0009") {
757        if (event->shiftKey())
758            return @selector(insertBacktab:);
759        return @selector(insertTab:);
760    }
761    if (key == "Enter")
762        return @selector(insertNewline:);
763    return 0;
764}
765
766bool WebEditorClient::doTextFieldCommandFromEvent(Element* element, KeyboardEvent* event)
767{
768    if (!isHTMLInputElement(element))
769        return NO;
770
771    DOMHTMLInputElement* inputElement = kit(toHTMLInputElement(element));
772    FormDelegateLog(inputElement);
773    if (SEL commandSelector = selectorForKeyEvent(event))
774        return CallFormDelegateReturningBoolean(NO, m_webView, @selector(textField:doCommandBySelector:inFrame:), inputElement, commandSelector, kit(element->document().frame()));
775    return NO;
776}
777
778void WebEditorClient::textWillBeDeletedInTextField(Element* element)
779{
780    if (!isHTMLInputElement(element))
781        return;
782
783    DOMHTMLInputElement* inputElement = kit(toHTMLInputElement(element));
784    FormDelegateLog(inputElement);
785    // We're using the deleteBackward selector for all deletion operations since the autofill code treats all deletions the same way.
786    CallFormDelegateReturningBoolean(NO, m_webView, @selector(textField:doCommandBySelector:inFrame:), inputElement, @selector(deleteBackward:), kit(element->document().frame()));
787}
788
789void WebEditorClient::textDidChangeInTextArea(Element* element)
790{
791    if (!isHTMLTextAreaElement(element))
792        return;
793
794    DOMHTMLTextAreaElement* textAreaElement = kit(toHTMLTextAreaElement(element));
795    FormDelegateLog(textAreaElement);
796    CallFormDelegate(m_webView, @selector(textDidChangeInTextArea:inFrame:), textAreaElement, kit(element->document().frame()));
797}
798
799#if PLATFORM(IOS)
800
801void WebEditorClient::writeDataToPasteboard(NSDictionary* representation)
802{
803    if ([[m_webView _UIKitDelegateForwarder] respondsToSelector:@selector(writeDataToPasteboard:)])
804        [[m_webView _UIKitDelegateForwarder] writeDataToPasteboard:representation];
805}
806
807NSArray* WebEditorClient::supportedPasteboardTypesForCurrentSelection()
808{
809    if ([[m_webView _UIKitDelegateForwarder] respondsToSelector:@selector(supportedPasteboardTypesForCurrentSelection)])
810        return [[m_webView _UIKitDelegateForwarder] supportedPasteboardTypesForCurrentSelection];
811
812    return nil;
813}
814
815NSArray* WebEditorClient::readDataFromPasteboard(NSString* type, int index)
816{
817    if ([[m_webView _UIKitDelegateForwarder] respondsToSelector:@selector(readDataFromPasteboard:withIndex:)])
818        return [[m_webView _UIKitDelegateForwarder] readDataFromPasteboard:type withIndex:index];
819
820    return nil;
821}
822
823bool WebEditorClient::hasRichlyEditableSelection()
824{
825    if ([[m_webView _UIKitDelegateForwarder] respondsToSelector:@selector(hasRichlyEditableSelection)])
826        return [[m_webView _UIKitDelegateForwarder] hasRichlyEditableSelection];
827
828    return false;
829}
830
831int WebEditorClient::getPasteboardItemsCount()
832{
833    if ([[m_webView _UIKitDelegateForwarder] respondsToSelector:@selector(getPasteboardItemsCount)])
834        return [[m_webView _UIKitDelegateForwarder] getPasteboardItemsCount];
835
836    return 0;
837}
838
839WebCore::DocumentFragment* WebEditorClient::documentFragmentFromDelegate(int index)
840{
841    if ([[m_webView _editingDelegateForwarder] respondsToSelector:@selector(documentFragmentForPasteboardItemAtIndex:)]) {
842        DOMDocumentFragment *fragmentFromDelegate = [[m_webView _editingDelegateForwarder] documentFragmentForPasteboardItemAtIndex:index];
843        if (fragmentFromDelegate)
844            return core(fragmentFromDelegate);
845    }
846
847    return 0;
848}
849
850bool WebEditorClient::performsTwoStepPaste(WebCore::DocumentFragment* fragment)
851{
852    if ([[m_webView _UIKitDelegateForwarder] respondsToSelector:@selector(performsTwoStepPaste:)])
853        return [[m_webView _UIKitDelegateForwarder] performsTwoStepPaste:kit(fragment)];
854
855    return false;
856}
857
858int WebEditorClient::pasteboardChangeCount()
859{
860    if ([[m_webView _UIKitDelegateForwarder] respondsToSelector:@selector(getPasteboardChangeCount)])
861        return [[m_webView _UIKitDelegateForwarder] getPasteboardChangeCount];
862
863    return 0;
864}
865
866Vector<TextCheckingResult> WebEditorClient::checkTextOfParagraph(StringView string, TextCheckingTypeMask checkingTypes)
867{
868    ASSERT(checkingTypes & NSTextCheckingTypeSpelling);
869
870    Vector<TextCheckingResult> results;
871
872    NSArray *incomingResults = [[m_webView _UIKitDelegateForwarder] checkSpellingOfString:string.createNSStringWithoutCopying().get()];
873    for (NSValue *incomingResult in incomingResults) {
874        NSRange resultRange = [incomingResult rangeValue];
875        ASSERT(resultRange.location != NSNotFound && resultRange.length > 0);
876        TextCheckingResult result;
877        result.type = TextCheckingTypeSpelling;
878        result.location = resultRange.location;
879        result.length = resultRange.length;
880        results.append(result);
881    }
882
883    return results;
884}
885
886#endif // PLATFORM(IOS)
887
888#if !PLATFORM(IOS)
889
890bool WebEditorClient::shouldEraseMarkersAfterChangeSelection(TextCheckingType type) const
891{
892    // This prevents erasing spelling markers on OS X Lion or later to match AppKit on these Mac OS X versions.
893    return type != TextCheckingTypeSpelling;
894}
895
896void WebEditorClient::ignoreWordInSpellDocument(const String& text)
897{
898    [[NSSpellChecker sharedSpellChecker] ignoreWord:text inSpellDocumentWithTag:spellCheckerDocumentTag()];
899}
900
901void WebEditorClient::learnWord(const String& text)
902{
903    [[NSSpellChecker sharedSpellChecker] learnWord:text];
904}
905
906void WebEditorClient::checkSpellingOfString(StringView text, int* misspellingLocation, int* misspellingLength)
907{
908    NSRange range = [[NSSpellChecker sharedSpellChecker] checkSpellingOfString:text.createNSStringWithoutCopying().get() startingAt:0 language:nil wrap:NO inSpellDocumentWithTag:spellCheckerDocumentTag() wordCount:NULL];
909
910    if (misspellingLocation) {
911        // WebCore expects -1 to represent "not found"
912        if (range.location == NSNotFound)
913            *misspellingLocation = -1;
914        else
915            *misspellingLocation = range.location;
916    }
917
918    if (misspellingLength)
919        *misspellingLength = range.length;
920}
921
922String WebEditorClient::getAutoCorrectSuggestionForMisspelledWord(const String& inputWord)
923{
924    // This method can be implemented using customized algorithms for the particular browser.
925    // Currently, it computes an empty string.
926    return String();
927}
928
929void WebEditorClient::checkGrammarOfString(StringView text, Vector<GrammarDetail>& details, int* badGrammarLocation, int* badGrammarLength)
930{
931    NSArray *grammarDetails;
932    NSRange range = [[NSSpellChecker sharedSpellChecker] checkGrammarOfString:text.createNSStringWithoutCopying().get() startingAt:0 language:nil wrap:NO inSpellDocumentWithTag:spellCheckerDocumentTag() details:&grammarDetails];
933    if (badGrammarLocation)
934        // WebCore expects -1 to represent "not found"
935        *badGrammarLocation = (range.location == NSNotFound) ? -1 : static_cast<int>(range.location);
936    if (badGrammarLength)
937        *badGrammarLength = range.length;
938    for (NSDictionary *detail in grammarDetails) {
939        ASSERT(detail);
940        GrammarDetail grammarDetail;
941        NSValue *detailRangeAsNSValue = [detail objectForKey:NSGrammarRange];
942        ASSERT(detailRangeAsNSValue);
943        NSRange detailNSRange = [detailRangeAsNSValue rangeValue];
944        ASSERT(detailNSRange.location != NSNotFound);
945        ASSERT(detailNSRange.length > 0);
946        grammarDetail.location = detailNSRange.location;
947        grammarDetail.length = detailNSRange.length;
948        grammarDetail.userDescription = [detail objectForKey:NSGrammarUserDescription];
949        NSArray *guesses = [detail objectForKey:NSGrammarCorrections];
950        for (NSString *guess in guesses)
951            grammarDetail.guesses.append(String(guess));
952        details.append(grammarDetail);
953    }
954}
955
956static Vector<TextCheckingResult> core(NSArray *incomingResults, TextCheckingTypeMask checkingTypes)
957{
958    Vector<TextCheckingResult> results;
959
960    for (NSTextCheckingResult *incomingResult in incomingResults) {
961        NSRange resultRange = [incomingResult range];
962        NSTextCheckingType resultType = [incomingResult resultType];
963        ASSERT(resultRange.location != NSNotFound);
964        ASSERT(resultRange.length > 0);
965        if (NSTextCheckingTypeSpelling == resultType && 0 != (checkingTypes & NSTextCheckingTypeSpelling)) {
966            TextCheckingResult result;
967            result.type = TextCheckingTypeSpelling;
968            result.location = resultRange.location;
969            result.length = resultRange.length;
970            results.append(result);
971        } else if (NSTextCheckingTypeGrammar == resultType && 0 != (checkingTypes & NSTextCheckingTypeGrammar)) {
972            TextCheckingResult result;
973            NSArray *details = [incomingResult grammarDetails];
974            result.type = TextCheckingTypeGrammar;
975            result.location = resultRange.location;
976            result.length = resultRange.length;
977            for (NSDictionary *incomingDetail in details) {
978                ASSERT(incomingDetail);
979                GrammarDetail detail;
980                NSValue *detailRangeAsNSValue = [incomingDetail objectForKey:NSGrammarRange];
981                ASSERT(detailRangeAsNSValue);
982                NSRange detailNSRange = [detailRangeAsNSValue rangeValue];
983                ASSERT(detailNSRange.location != NSNotFound);
984                ASSERT(detailNSRange.length > 0);
985                detail.location = detailNSRange.location;
986                detail.length = detailNSRange.length;
987                detail.userDescription = [incomingDetail objectForKey:NSGrammarUserDescription];
988                NSArray *guesses = [incomingDetail objectForKey:NSGrammarCorrections];
989                for (NSString *guess in guesses)
990                    detail.guesses.append(String(guess));
991                result.details.append(detail);
992            }
993            results.append(result);
994        } else if (NSTextCheckingTypeLink == resultType && 0 != (checkingTypes & NSTextCheckingTypeLink)) {
995            TextCheckingResult result;
996            result.type = TextCheckingTypeLink;
997            result.location = resultRange.location;
998            result.length = resultRange.length;
999            result.replacement = [[incomingResult URL] absoluteString];
1000            results.append(result);
1001        } else if (NSTextCheckingTypeQuote == resultType && 0 != (checkingTypes & NSTextCheckingTypeQuote)) {
1002            TextCheckingResult result;
1003            result.type = TextCheckingTypeQuote;
1004            result.location = resultRange.location;
1005            result.length = resultRange.length;
1006            result.replacement = [incomingResult replacementString];
1007            results.append(result);
1008        } else if (NSTextCheckingTypeDash == resultType && 0 != (checkingTypes & NSTextCheckingTypeDash)) {
1009            TextCheckingResult result;
1010            result.type = TextCheckingTypeDash;
1011            result.location = resultRange.location;
1012            result.length = resultRange.length;
1013            result.replacement = [incomingResult replacementString];
1014            results.append(result);
1015        } else if (NSTextCheckingTypeReplacement == resultType && 0 != (checkingTypes & NSTextCheckingTypeReplacement)) {
1016            TextCheckingResult result;
1017            result.type = TextCheckingTypeReplacement;
1018            result.location = resultRange.location;
1019            result.length = resultRange.length;
1020            result.replacement = [incomingResult replacementString];
1021            results.append(result);
1022        } else if (NSTextCheckingTypeCorrection == resultType && 0 != (checkingTypes & NSTextCheckingTypeCorrection)) {
1023            TextCheckingResult result;
1024            result.type = TextCheckingTypeCorrection;
1025            result.location = resultRange.location;
1026            result.length = resultRange.length;
1027            result.replacement = [incomingResult replacementString];
1028            results.append(result);
1029        }
1030    }
1031
1032    return results;
1033}
1034
1035Vector<TextCheckingResult> WebEditorClient::checkTextOfParagraph(StringView string, TextCheckingTypeMask checkingTypes)
1036{
1037    return core([[NSSpellChecker sharedSpellChecker] checkString:string.createNSStringWithoutCopying().get() range:NSMakeRange(0, string.length()) types:(checkingTypes | NSTextCheckingTypeOrthography) options:nil inSpellDocumentWithTag:spellCheckerDocumentTag() orthography:NULL wordCount:NULL], checkingTypes);
1038}
1039
1040void WebEditorClient::updateSpellingUIWithGrammarString(const String& badGrammarPhrase, const GrammarDetail& grammarDetail)
1041{
1042    NSMutableArray* corrections = [NSMutableArray array];
1043    for (unsigned i = 0; i < grammarDetail.guesses.size(); i++) {
1044        NSString* guess = grammarDetail.guesses[i];
1045        [corrections addObject:guess];
1046    }
1047    NSRange grammarRange = NSMakeRange(grammarDetail.location, grammarDetail.length);
1048    NSString* grammarUserDescription = grammarDetail.userDescription;
1049    NSDictionary* grammarDetailDict = [NSDictionary dictionaryWithObjectsAndKeys:[NSValue valueWithRange:grammarRange], NSGrammarRange, grammarUserDescription, NSGrammarUserDescription, corrections, NSGrammarCorrections, nil];
1050
1051    [[NSSpellChecker sharedSpellChecker] updateSpellingPanelWithGrammarString:badGrammarPhrase detail:grammarDetailDict];
1052}
1053
1054void WebEditorClient::updateSpellingUIWithMisspelledWord(const String& misspelledWord)
1055{
1056    [[NSSpellChecker sharedSpellChecker] updateSpellingPanelWithMisspelledWord:misspelledWord];
1057}
1058
1059void WebEditorClient::showSpellingUI(bool show)
1060{
1061    NSPanel *spellingPanel = [[NSSpellChecker sharedSpellChecker] spellingPanel];
1062    if (show)
1063        [spellingPanel orderFront:nil];
1064    else
1065        [spellingPanel orderOut:nil];
1066}
1067
1068bool WebEditorClient::spellingUIIsShowing()
1069{
1070    return [[[NSSpellChecker sharedSpellChecker] spellingPanel] isVisible];
1071}
1072
1073void WebEditorClient::getGuessesForWord(const String& word, const String& context, Vector<String>& guesses) {
1074    guesses.clear();
1075    NSString* language = nil;
1076    NSOrthography* orthography = nil;
1077    NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
1078    if (context.length()) {
1079        [checker checkString:context range:NSMakeRange(0, context.length()) types:NSTextCheckingTypeOrthography options:0 inSpellDocumentWithTag:spellCheckerDocumentTag() orthography:&orthography wordCount:0];
1080        language = [checker languageForWordRange:NSMakeRange(0, context.length()) inString:context orthography:orthography];
1081    }
1082    NSArray* stringsArray = [checker guessesForWordRange:NSMakeRange(0, word.length()) inString:word language:language inSpellDocumentWithTag:spellCheckerDocumentTag()];
1083    unsigned count = [stringsArray count];
1084
1085    if (count > 0) {
1086        NSEnumerator* enumerator = [stringsArray objectEnumerator];
1087        NSString* string;
1088        while ((string = [enumerator nextObject]) != nil)
1089            guesses.append(string);
1090    }
1091}
1092
1093#endif // !PLATFORM(IOS)
1094
1095void WebEditorClient::willSetInputMethodState()
1096{
1097}
1098
1099void WebEditorClient::setInputMethodState(bool)
1100{
1101}
1102
1103#if !PLATFORM(IOS)
1104@interface WebEditorSpellCheckResponder : NSObject
1105{
1106    WebEditorClient* _client;
1107    int _sequence;
1108    RetainPtr<NSArray> _results;
1109}
1110- (id)initWithClient:(WebEditorClient*)client sequence:(int)sequence results:(NSArray*)results;
1111- (void)perform;
1112@end
1113
1114@implementation WebEditorSpellCheckResponder
1115- (id)initWithClient:(WebEditorClient*)client sequence:(int)sequence results:(NSArray*)results
1116{
1117    self = [super init];
1118    if (!self)
1119        return nil;
1120    _client = client;
1121    _sequence = sequence;
1122    _results = results;
1123    return self;
1124}
1125
1126- (void)perform
1127{
1128    _client->didCheckSucceed(_sequence, _results.get());
1129}
1130
1131@end
1132
1133void WebEditorClient::didCheckSucceed(int sequence, NSArray* results)
1134{
1135    ASSERT_UNUSED(sequence, sequence == m_textCheckingRequest->data().sequence());
1136    m_textCheckingRequest->didSucceed(core(results, m_textCheckingRequest->data().mask()));
1137    m_textCheckingRequest.clear();
1138}
1139#endif
1140
1141void WebEditorClient::requestCheckingOfString(PassRefPtr<WebCore::TextCheckingRequest> request)
1142{
1143#if !PLATFORM(IOS)
1144    ASSERT(!m_textCheckingRequest);
1145    m_textCheckingRequest = request;
1146
1147    int sequence = m_textCheckingRequest->data().sequence();
1148    NSRange range = NSMakeRange(0, m_textCheckingRequest->data().text().length());
1149    NSRunLoop* currentLoop = [NSRunLoop currentRunLoop];
1150    [[NSSpellChecker sharedSpellChecker] requestCheckingOfString:m_textCheckingRequest->data().text() range:range types:NSTextCheckingAllSystemTypes options:0 inSpellDocumentWithTag:0
1151                                         completionHandler:^(NSInteger, NSArray* results, NSOrthography*, NSInteger) {
1152            [currentLoop performSelector:@selector(perform)
1153                                  target:[[[WebEditorSpellCheckResponder alloc] initWithClient:this sequence:sequence results:results] autorelease]
1154                                argument:nil order:0 modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]];
1155        }];
1156#endif
1157}
1158