1/*
2 * Copyright (C) 2006 Apple Computer, 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 Computer, 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/RunLoop.h>
65#import <WebCore/Settings.h>
66#import <WebCore/SpellChecker.h>
67#import <WebCore/StylePropertySet.h>
68#import <WebCore/UndoStep.h>
69#import <WebCore/UserTypingGestureIndicator.h>
70#import <WebCore/WebCoreObjCExtras.h>
71#import <runtime/InitializeThreading.h>
72#import <wtf/MainThread.h>
73#import <wtf/PassRefPtr.h>
74#import <wtf/text/WTFString.h>
75
76using namespace WebCore;
77
78using namespace HTMLNames;
79
80#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
81@interface NSSpellChecker (WebNSSpellCheckerDetails)
82- (NSString *)languageForWordRange:(NSRange)range inString:(NSString *)string orthography:(NSOrthography *)orthography;
83@end
84#endif
85
86@interface NSAttributedString (WebNSAttributedStringDetails)
87- (id)_initWithDOMRange:(DOMRange*)range;
88- (DOMDocumentFragment*)_documentFromRange:(NSRange)range document:(DOMDocument*)document documentAttributes:(NSDictionary *)dict subresources:(NSArray **)subresources;
89@end
90
91static WebViewInsertAction kit(EditorInsertAction coreAction)
92{
93    return static_cast<WebViewInsertAction>(coreAction);
94}
95
96static const int InvalidCorrectionPanelTag = 0;
97
98
99@interface WebUndoStep : NSObject
100{
101    RefPtr<UndoStep> m_step;
102}
103
104+ (WebUndoStep *)stepWithUndoStep:(PassRefPtr<UndoStep>)step;
105- (UndoStep *)step;
106
107@end
108
109@implementation WebUndoStep
110
111+ (void)initialize
112{
113    JSC::initializeThreading();
114    WTF::initializeMainThreadToProcessMainThread();
115    WebCore::RunLoop::initializeMainRunLoop();
116    WebCoreObjCFinalizeOnMainThread(self);
117}
118
119- (id)initWithUndoStep:(PassRefPtr<UndoStep>)step
120{
121    ASSERT(step);
122    self = [super init];
123    if (!self)
124        return nil;
125    m_step = step;
126    return self;
127}
128
129- (void)dealloc
130{
131    if (WebCoreObjCScheduleDeallocateOnMainThread([WebUndoStep class], self))
132        return;
133
134    [super dealloc];
135}
136
137- (void)finalize
138{
139    ASSERT_MAIN_THREAD();
140
141    [super finalize];
142}
143
144+ (WebUndoStep *)stepWithUndoStep:(PassRefPtr<UndoStep>)step
145{
146    return [[[WebUndoStep alloc] initWithUndoStep:step] autorelease];
147}
148
149- (UndoStep *)step
150{
151    return m_step.get();
152}
153
154@end
155
156@interface WebEditorUndoTarget : NSObject
157{
158}
159
160- (void)undoEditing:(id)arg;
161- (void)redoEditing:(id)arg;
162
163@end
164
165@implementation WebEditorUndoTarget
166
167- (void)undoEditing:(id)arg
168{
169    ASSERT([arg isKindOfClass:[WebUndoStep class]]);
170    [arg step]->unapply();
171}
172
173- (void)redoEditing:(id)arg
174{
175    ASSERT([arg isKindOfClass:[WebUndoStep class]]);
176    [arg step]->reapply();
177}
178
179@end
180
181void WebEditorClient::pageDestroyed()
182{
183    delete this;
184}
185
186WebEditorClient::WebEditorClient(WebView *webView)
187    : m_webView(webView)
188    , m_undoTarget([[[WebEditorUndoTarget alloc] init] autorelease])
189    , m_haveUndoRedoOperations(false)
190{
191}
192
193WebEditorClient::~WebEditorClient()
194{
195}
196
197bool WebEditorClient::isContinuousSpellCheckingEnabled()
198{
199    return [m_webView isContinuousSpellCheckingEnabled];
200}
201
202void WebEditorClient::toggleContinuousSpellChecking()
203{
204    [m_webView toggleContinuousSpellChecking:nil];
205}
206
207bool WebEditorClient::isGrammarCheckingEnabled()
208{
209    return [m_webView isGrammarCheckingEnabled];
210}
211
212void WebEditorClient::toggleGrammarChecking()
213{
214    [m_webView toggleGrammarChecking:nil];
215}
216
217int WebEditorClient::spellCheckerDocumentTag()
218{
219    return [m_webView spellCheckerDocumentTag];
220}
221
222bool WebEditorClient::shouldDeleteRange(Range* range)
223{
224    return [[m_webView _editingDelegateForwarder] webView:m_webView
225        shouldDeleteDOMRange:kit(range)];
226}
227
228#if ENABLE(DELETION_UI)
229bool WebEditorClient::shouldShowDeleteInterface(HTMLElement* element)
230{
231    return [[m_webView _editingDelegateForwarder] webView:m_webView
232        shouldShowDeleteInterfaceForElement:kit(element)];
233}
234#endif
235
236bool WebEditorClient::smartInsertDeleteEnabled()
237{
238    Page* page = [m_webView page];
239    if (!page)
240        return false;
241    return page->settings()->smartInsertDeleteEnabled();
242}
243
244bool WebEditorClient::isSelectTrailingWhitespaceEnabled()
245{
246    Page* page = [m_webView page];
247    if (!page)
248        return false;
249    return page->settings()->selectTrailingWhitespaceEnabled();
250}
251
252bool WebEditorClient::shouldApplyStyle(StylePropertySet* style, Range* range)
253{
254    RefPtr<MutableStylePropertySet> mutableStyle = style->isMutable() ? static_cast<MutableStylePropertySet*>(style) : style->mutableCopy();
255    return [[m_webView _editingDelegateForwarder] webView:m_webView
256        shouldApplyStyle:kit(mutableStyle->ensureCSSStyleDeclaration()) toElementsInDOMRange:kit(range)];
257}
258
259bool WebEditorClient::shouldMoveRangeAfterDelete(Range* range, Range* rangeToBeReplaced)
260{
261    return [[m_webView _editingDelegateForwarder] webView:m_webView
262        shouldMoveRangeAfterDelete:kit(range) replacingRange:kit(rangeToBeReplaced)];
263}
264
265bool WebEditorClient::shouldBeginEditing(Range* range)
266{
267    return [[m_webView _editingDelegateForwarder] webView:m_webView
268        shouldBeginEditingInDOMRange:kit(range)];
269
270    return false;
271}
272
273bool WebEditorClient::shouldEndEditing(Range* range)
274{
275    return [[m_webView _editingDelegateForwarder] webView:m_webView
276                             shouldEndEditingInDOMRange:kit(range)];
277}
278
279bool WebEditorClient::shouldInsertText(const String& text, Range* range, EditorInsertAction action)
280{
281    WebView* webView = m_webView;
282    return [[webView _editingDelegateForwarder] webView:webView shouldInsertText:text replacingDOMRange:kit(range) givenAction:kit(action)];
283}
284
285bool WebEditorClient::shouldChangeSelectedRange(Range* fromRange, Range* toRange, EAffinity selectionAffinity, bool stillSelecting)
286{
287    return [m_webView _shouldChangeSelectedDOMRange:kit(fromRange) toDOMRange:kit(toRange) affinity:kit(selectionAffinity) stillSelecting:stillSelecting];
288}
289
290void WebEditorClient::didBeginEditing()
291{
292    [[NSNotificationCenter defaultCenter] postNotificationName:WebViewDidBeginEditingNotification object:m_webView];
293}
294
295void WebEditorClient::respondToChangedContents()
296{
297    NSView <WebDocumentView> *view = [[[m_webView selectedFrame] frameView] documentView];
298    if ([view isKindOfClass:[WebHTMLView class]])
299        [(WebHTMLView *)view _updateFontPanel];
300    [[NSNotificationCenter defaultCenter] postNotificationName:WebViewDidChangeNotification object:m_webView];
301}
302
303void WebEditorClient::respondToChangedSelection(Frame* frame)
304{
305    NSView<WebDocumentView> *documentView = [[kit(frame) frameView] documentView];
306    if ([documentView isKindOfClass:[WebHTMLView class]])
307        [(WebHTMLView *)documentView _selectionChanged];
308
309    // 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
310    if (!WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITHOUT_APERTURE_QUIRK) && [[[NSBundle mainBundle] bundleIdentifier] isEqualToString:@"com.apple.Aperture"])
311        return;
312
313    [[NSNotificationCenter defaultCenter] postNotificationName:WebViewDidChangeSelectionNotification object:m_webView];
314}
315
316void WebEditorClient::didEndEditing()
317{
318    [[NSNotificationCenter defaultCenter] postNotificationName:WebViewDidEndEditingNotification object:m_webView];
319}
320
321void WebEditorClient::didWriteSelectionToPasteboard()
322{
323    [[m_webView _editingDelegateForwarder] webView:m_webView didWriteSelectionToPasteboard:[NSPasteboard generalPasteboard]];
324}
325
326void WebEditorClient::willWriteSelectionToPasteboard(WebCore::Range*)
327{
328    // Not implemented WebKit, only WebKit2.
329}
330
331void WebEditorClient::getClientPasteboardDataForRange(WebCore::Range*, Vector<String>& pasteboardTypes, Vector<RefPtr<WebCore::SharedBuffer> >& pasteboardData)
332{
333    // Not implemented WebKit, only WebKit2.
334}
335
336void WebEditorClient::didSetSelectionTypesForPasteboard()
337{
338    [[m_webView _editingDelegateForwarder] webView:m_webView didSetSelectionTypesForPasteboard:[NSPasteboard generalPasteboard]];
339}
340
341NSString *WebEditorClient::userVisibleString(NSURL *URL)
342{
343    return [URL _web_userVisibleString];
344}
345
346NSURL *WebEditorClient::canonicalizeURL(NSURL *URL)
347{
348    return [URL _webkit_canonicalize];
349}
350
351NSURL *WebEditorClient::canonicalizeURLString(NSString *URLString)
352{
353    NSURL *URL = nil;
354    if ([URLString _webkit_looksLikeAbsoluteURL])
355        URL = [[NSURL _web_URLWithUserTypedString:URLString] _webkit_canonicalize];
356    return URL;
357}
358
359static NSArray *createExcludedElementsForAttributedStringConversion()
360{
361    NSArray *elements = [[NSArray alloc] initWithObjects:
362        // Omit style since we want style to be inline so the fragment can be easily inserted.
363        @"style",
364        // Omit xml so the result is not XHTML.
365        @"xml",
366        // Omit tags that will get stripped when converted to a fragment anyway.
367        @"doctype", @"html", @"head", @"body",
368        // Omit deprecated tags.
369        @"applet", @"basefont", @"center", @"dir", @"font", @"isindex", @"menu", @"s", @"strike", @"u",
370        // Omit object so no file attachments are part of the fragment.
371        @"object", nil];
372    CFRetain(elements);
373    return elements;
374}
375
376DocumentFragment* WebEditorClient::documentFragmentFromAttributedString(NSAttributedString *string, Vector<RefPtr<ArchiveResource> >& resources)
377{
378    static NSArray *excludedElements = createExcludedElementsForAttributedStringConversion();
379
380    NSDictionary *dictionary = [[NSDictionary alloc] initWithObjectsAndKeys: excludedElements, NSExcludedElementsDocumentAttribute,
381        nil, @"WebResourceHandler", nil];
382
383    NSArray *subResources;
384    DOMDocumentFragment* fragment = [string _documentFromRange:NSMakeRange(0, [string length])
385                                                      document:[[m_webView mainFrame] DOMDocument]
386                                            documentAttributes:dictionary
387                                                  subresources:&subResources];
388    for (WebResource* resource in subResources)
389        resources.append([resource _coreResource]);
390
391    [dictionary release];
392    return core(fragment);
393}
394
395void WebEditorClient::setInsertionPasteboard(const String& pasteboardName)
396{
397    NSPasteboard *pasteboard = pasteboardName.isEmpty() ? nil : [NSPasteboard pasteboardWithName:pasteboardName];
398    [m_webView _setInsertionPasteboard:pasteboard];
399}
400
401#if USE(APPKIT)
402void WebEditorClient::uppercaseWord()
403{
404    [m_webView uppercaseWord:nil];
405}
406
407void WebEditorClient::lowercaseWord()
408{
409    [m_webView lowercaseWord:nil];
410}
411
412void WebEditorClient::capitalizeWord()
413{
414    [m_webView capitalizeWord:nil];
415}
416#endif
417
418#if USE(AUTOMATIC_TEXT_REPLACEMENT)
419void WebEditorClient::showSubstitutionsPanel(bool show)
420{
421    NSPanel *spellingPanel = [[NSSpellChecker sharedSpellChecker] substitutionsPanel];
422    if (show)
423        [spellingPanel orderFront:nil];
424    else
425        [spellingPanel orderOut:nil];
426}
427
428bool WebEditorClient::substitutionsPanelIsShowing()
429{
430    return [[[NSSpellChecker sharedSpellChecker] substitutionsPanel] isVisible];
431}
432
433void WebEditorClient::toggleSmartInsertDelete()
434{
435    [m_webView toggleSmartInsertDelete:nil];
436}
437
438bool WebEditorClient::isAutomaticQuoteSubstitutionEnabled()
439{
440    return [m_webView isAutomaticQuoteSubstitutionEnabled];
441}
442
443void WebEditorClient::toggleAutomaticQuoteSubstitution()
444{
445    [m_webView toggleAutomaticQuoteSubstitution:nil];
446}
447
448bool WebEditorClient::isAutomaticLinkDetectionEnabled()
449{
450    return [m_webView isAutomaticLinkDetectionEnabled];
451}
452
453void WebEditorClient::toggleAutomaticLinkDetection()
454{
455    [m_webView toggleAutomaticLinkDetection:nil];
456}
457
458bool WebEditorClient::isAutomaticDashSubstitutionEnabled()
459{
460    return [m_webView isAutomaticDashSubstitutionEnabled];
461}
462
463void WebEditorClient::toggleAutomaticDashSubstitution()
464{
465    [m_webView toggleAutomaticDashSubstitution:nil];
466}
467
468bool WebEditorClient::isAutomaticTextReplacementEnabled()
469{
470    return [m_webView isAutomaticTextReplacementEnabled];
471}
472
473void WebEditorClient::toggleAutomaticTextReplacement()
474{
475    [m_webView toggleAutomaticTextReplacement:nil];
476}
477
478bool WebEditorClient::isAutomaticSpellingCorrectionEnabled()
479{
480    return [m_webView isAutomaticSpellingCorrectionEnabled];
481}
482
483void WebEditorClient::toggleAutomaticSpellingCorrection()
484{
485    [m_webView toggleAutomaticSpellingCorrection:nil];
486}
487#endif // USE(AUTOMATIC_TEXT_REPLACEMENT)
488
489bool WebEditorClient::shouldInsertNode(Node *node, Range* replacingRange, EditorInsertAction givenAction)
490{
491    return [[m_webView _editingDelegateForwarder] webView:m_webView shouldInsertNode:kit(node) replacingDOMRange:kit(replacingRange) givenAction:(WebViewInsertAction)givenAction];
492}
493
494static NSString* undoNameForEditAction(EditAction editAction)
495{
496    switch (editAction) {
497        case EditActionUnspecified: return nil;
498        case EditActionSetColor: return UI_STRING_KEY_INTERNAL("Set Color", "Set Color (Undo action name)", "Undo action name");
499        case EditActionSetBackgroundColor: return UI_STRING_KEY_INTERNAL("Set Background Color", "Set Background Color (Undo action name)", "Undo action name");
500        case EditActionTurnOffKerning: return UI_STRING_KEY_INTERNAL("Turn Off Kerning", "Turn Off Kerning (Undo action name)", "Undo action name");
501        case EditActionTightenKerning: return UI_STRING_KEY_INTERNAL("Tighten Kerning", "Tighten Kerning (Undo action name)", "Undo action name");
502        case EditActionLoosenKerning: return UI_STRING_KEY_INTERNAL("Loosen Kerning", "Loosen Kerning (Undo action name)", "Undo action name");
503        case EditActionUseStandardKerning: return UI_STRING_KEY_INTERNAL("Use Standard Kerning", "Use Standard Kerning (Undo action name)", "Undo action name");
504        case EditActionTurnOffLigatures: return UI_STRING_KEY_INTERNAL("Turn Off Ligatures", "Turn Off Ligatures (Undo action name)", "Undo action name");
505        case EditActionUseStandardLigatures: return UI_STRING_KEY_INTERNAL("Use Standard Ligatures", "Use Standard Ligatures (Undo action name)", "Undo action name");
506        case EditActionUseAllLigatures: return UI_STRING_KEY_INTERNAL("Use All Ligatures", "Use All Ligatures (Undo action name)", "Undo action name");
507        case EditActionRaiseBaseline: return UI_STRING_KEY_INTERNAL("Raise Baseline", "Raise Baseline (Undo action name)", "Undo action name");
508        case EditActionLowerBaseline: return UI_STRING_KEY_INTERNAL("Lower Baseline", "Lower Baseline (Undo action name)", "Undo action name");
509        case EditActionSetTraditionalCharacterShape: return UI_STRING_KEY_INTERNAL("Set Traditional Character Shape", "Set Traditional Character Shape (Undo action name)", "Undo action name");
510        case EditActionSetFont: return UI_STRING_KEY_INTERNAL("Set Font", "Set Font (Undo action name)", "Undo action name");
511        case EditActionChangeAttributes: return UI_STRING_KEY_INTERNAL("Change Attributes", "Change Attributes (Undo action name)", "Undo action name");
512        case EditActionAlignLeft: return UI_STRING_KEY_INTERNAL("Align Left", "Align Left (Undo action name)", "Undo action name");
513        case EditActionAlignRight: return UI_STRING_KEY_INTERNAL("Align Right", "Align Right (Undo action name)", "Undo action name");
514        case EditActionCenter: return UI_STRING_KEY_INTERNAL("Center", "Center (Undo action name)", "Undo action name");
515        case EditActionJustify: return UI_STRING_KEY_INTERNAL("Justify", "Justify (Undo action name)", "Undo action name");
516        case EditActionSetWritingDirection: return UI_STRING_KEY_INTERNAL("Set Writing Direction", "Set Writing Direction (Undo action name)", "Undo action name");
517        case EditActionSubscript: return UI_STRING_KEY_INTERNAL("Subscript", "Subscript (Undo action name)", "Undo action name");
518        case EditActionSuperscript: return UI_STRING_KEY_INTERNAL("Superscript", "Superscript (Undo action name)", "Undo action name");
519        case EditActionUnderline: return UI_STRING_KEY_INTERNAL("Underline", "Underline (Undo action name)", "Undo action name");
520        case EditActionOutline: return UI_STRING_KEY_INTERNAL("Outline", "Outline (Undo action name)", "Undo action name");
521        case EditActionUnscript: return UI_STRING_KEY_INTERNAL("Unscript", "Unscript (Undo action name)", "Undo action name");
522        case EditActionDrag: return UI_STRING_KEY_INTERNAL("Drag", "Drag (Undo action name)", "Undo action name");
523        case EditActionCut: return UI_STRING_KEY_INTERNAL("Cut", "Cut (Undo action name)", "Undo action name");
524        case EditActionPaste: return UI_STRING_KEY_INTERNAL("Paste", "Paste (Undo action name)", "Undo action name");
525        case EditActionPasteFont: return UI_STRING_KEY_INTERNAL("Paste Font", "Paste Font (Undo action name)", "Undo action name");
526        case EditActionPasteRuler: return UI_STRING_KEY_INTERNAL("Paste Ruler", "Paste Ruler (Undo action name)", "Undo action name");
527        case EditActionTyping: return UI_STRING_KEY_INTERNAL("Typing", "Typing (Undo action name)", "Undo action name");
528        case EditActionCreateLink: return UI_STRING_KEY_INTERNAL("Create Link", "Create Link (Undo action name)", "Undo action name");
529        case EditActionUnlink: return UI_STRING_KEY_INTERNAL("Unlink", "Unlink (Undo action name)", "Undo action name");
530        case EditActionInsertList: return UI_STRING_KEY_INTERNAL("Insert List", "Insert List (Undo action name)", "Undo action name");
531        case EditActionFormatBlock: return UI_STRING_KEY_INTERNAL("Formatting", "Format Block (Undo action name)", "Undo action name");
532        case EditActionIndent: return UI_STRING_KEY_INTERNAL("Indent", "Indent (Undo action name)", "Undo action name");
533        case EditActionOutdent: return UI_STRING_KEY_INTERNAL("Outdent", "Outdent (Undo action name)", "Undo action name");
534        case EditActionBold: return UI_STRING_KEY_INTERNAL("Bold", "Bold (Undo action name)", "Undo action name");
535        case EditActionItalics: return UI_STRING_KEY_INTERNAL("Italics", "Italics (Undo action name)", "Undo action name");
536    }
537    return nil;
538}
539
540void WebEditorClient::registerUndoOrRedoStep(PassRefPtr<UndoStep> step, bool isRedo)
541{
542    ASSERT(step);
543
544    NSUndoManager *undoManager = [m_webView undoManager];
545    NSString *actionName = undoNameForEditAction(step->editingAction());
546    WebUndoStep *webEntry = [WebUndoStep stepWithUndoStep:step];
547    [undoManager registerUndoWithTarget:m_undoTarget.get() selector:(isRedo ? @selector(redoEditing:) : @selector(undoEditing:)) object:webEntry];
548    if (actionName)
549        [undoManager setActionName:actionName];
550    m_haveUndoRedoOperations = YES;
551}
552
553void WebEditorClient::registerUndoStep(PassRefPtr<UndoStep> cmd)
554{
555    registerUndoOrRedoStep(cmd, false);
556}
557
558void WebEditorClient::registerRedoStep(PassRefPtr<UndoStep> cmd)
559{
560    registerUndoOrRedoStep(cmd, true);
561}
562
563void WebEditorClient::clearUndoRedoOperations()
564{
565    if (m_haveUndoRedoOperations) {
566        // workaround for <rdar://problem/4645507> NSUndoManager dies
567        // with uncaught exception when undo items cleared while
568        // groups are open
569        NSUndoManager *undoManager = [m_webView undoManager];
570        int groupingLevel = [undoManager groupingLevel];
571        for (int i = 0; i < groupingLevel; ++i)
572            [undoManager endUndoGrouping];
573
574        [undoManager removeAllActionsWithTarget:m_undoTarget.get()];
575
576        for (int i = 0; i < groupingLevel; ++i)
577            [undoManager beginUndoGrouping];
578
579        m_haveUndoRedoOperations = NO;
580    }
581}
582
583bool WebEditorClient::canCopyCut(Frame*, bool defaultValue) const
584{
585    return defaultValue;
586}
587
588bool WebEditorClient::canPaste(Frame*, bool defaultValue) const
589{
590    return defaultValue;
591}
592
593bool WebEditorClient::canUndo() const
594{
595    return [[m_webView undoManager] canUndo];
596}
597
598bool WebEditorClient::canRedo() const
599{
600    return [[m_webView undoManager] canRedo];
601}
602
603void WebEditorClient::undo()
604{
605    if (canUndo())
606        [[m_webView undoManager] undo];
607}
608
609void WebEditorClient::redo()
610{
611    if (canRedo())
612        [[m_webView undoManager] redo];
613}
614
615void WebEditorClient::handleKeyboardEvent(KeyboardEvent* event)
616{
617    Frame* frame = event->target()->toNode()->document()->frame();
618    WebHTMLView *webHTMLView = [[kit(frame) frameView] documentView];
619    if ([webHTMLView _interpretKeyEvent:event savingCommands:NO])
620        event->setDefaultHandled();
621}
622
623void WebEditorClient::handleInputMethodKeydown(KeyboardEvent* event)
624{
625    Frame* frame = event->target()->toNode()->document()->frame();
626    WebHTMLView *webHTMLView = [[kit(frame) frameView] documentView];
627    if ([webHTMLView _interpretKeyEvent:event savingCommands:YES])
628        event->setDefaultHandled();
629}
630
631#define FormDelegateLog(ctrl)  LOG(FormDelegate, "control=%@", ctrl)
632
633void WebEditorClient::textFieldDidBeginEditing(Element* element)
634{
635    if (!element->hasTagName(inputTag))
636        return;
637
638    DOMHTMLInputElement* inputElement = kit(static_cast<HTMLInputElement*>(element));
639    FormDelegateLog(inputElement);
640    CallFormDelegate(m_webView, @selector(textFieldDidBeginEditing:inFrame:), inputElement, kit(element->document()->frame()));
641}
642
643void WebEditorClient::textFieldDidEndEditing(Element* element)
644{
645    if (!element->hasTagName(inputTag))
646        return;
647
648    DOMHTMLInputElement* inputElement = kit(static_cast<HTMLInputElement*>(element));
649    FormDelegateLog(inputElement);
650    CallFormDelegate(m_webView, @selector(textFieldDidEndEditing:inFrame:), inputElement, kit(element->document()->frame()));
651}
652
653void WebEditorClient::textDidChangeInTextField(Element* element)
654{
655    if (!element->hasTagName(inputTag))
656        return;
657
658    if (!UserTypingGestureIndicator::processingUserTypingGesture() || UserTypingGestureIndicator::focusedElementAtGestureStart() != element)
659        return;
660
661    DOMHTMLInputElement* inputElement = kit(static_cast<HTMLInputElement*>(element));
662    FormDelegateLog(inputElement);
663    CallFormDelegate(m_webView, @selector(textDidChangeInTextField:inFrame:), inputElement, kit(element->document()->frame()));
664}
665
666static SEL selectorForKeyEvent(KeyboardEvent* event)
667{
668    // FIXME: This helper function is for the auto-fill code so we can pass a selector to the form delegate.
669    // Eventually, we should move all of the auto-fill code down to WebKit and remove the need for this function by
670    // not relying on the selector in the new implementation.
671    // The key identifiers are from <http://www.w3.org/TR/DOM-Level-3-Events/keyset.html#KeySet-Set>
672    const String& key = event->keyIdentifier();
673    if (key == "Up")
674        return @selector(moveUp:);
675    if (key == "Down")
676        return @selector(moveDown:);
677    if (key == "U+001B")
678        return @selector(cancel:);
679    if (key == "U+0009") {
680        if (event->shiftKey())
681            return @selector(insertBacktab:);
682        return @selector(insertTab:);
683    }
684    if (key == "Enter")
685        return @selector(insertNewline:);
686    return 0;
687}
688
689bool WebEditorClient::doTextFieldCommandFromEvent(Element* element, KeyboardEvent* event)
690{
691    if (!element->hasTagName(inputTag))
692        return NO;
693
694    DOMHTMLInputElement* inputElement = kit(static_cast<HTMLInputElement*>(element));
695    FormDelegateLog(inputElement);
696    if (SEL commandSelector = selectorForKeyEvent(event))
697        return CallFormDelegateReturningBoolean(NO, m_webView, @selector(textField:doCommandBySelector:inFrame:), inputElement, commandSelector, kit(element->document()->frame()));
698    return NO;
699}
700
701void WebEditorClient::textWillBeDeletedInTextField(Element* element)
702{
703    if (!element->hasTagName(inputTag))
704        return;
705
706    DOMHTMLInputElement* inputElement = kit(static_cast<HTMLInputElement*>(element));
707    FormDelegateLog(inputElement);
708    // We're using the deleteBackward selector for all deletion operations since the autofill code treats all deletions the same way.
709    CallFormDelegateReturningBoolean(NO, m_webView, @selector(textField:doCommandBySelector:inFrame:), inputElement, @selector(deleteBackward:), kit(element->document()->frame()));
710}
711
712void WebEditorClient::textDidChangeInTextArea(Element* element)
713{
714    if (!element->hasTagName(textareaTag))
715        return;
716
717    DOMHTMLTextAreaElement* textAreaElement = kit(static_cast<HTMLTextAreaElement*>(element));
718    FormDelegateLog(textAreaElement);
719    CallFormDelegate(m_webView, @selector(textDidChangeInTextArea:inFrame:), textAreaElement, kit(element->document()->frame()));
720}
721
722bool WebEditorClient::shouldEraseMarkersAfterChangeSelection(TextCheckingType type) const
723{
724    // This prevents erasing spelling markers on OS X Lion or later to match AppKit on these Mac OS X versions.
725#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
726    return type != TextCheckingTypeSpelling;
727#else
728    return true;
729#endif
730}
731
732void WebEditorClient::ignoreWordInSpellDocument(const String& text)
733{
734    [[NSSpellChecker sharedSpellChecker] ignoreWord:text
735                             inSpellDocumentWithTag:spellCheckerDocumentTag()];
736}
737
738void WebEditorClient::learnWord(const String& text)
739{
740    [[NSSpellChecker sharedSpellChecker] learnWord:text];
741}
742
743void WebEditorClient::checkSpellingOfString(const UChar* text, int length, int* misspellingLocation, int* misspellingLength)
744{
745    NSString* textString = [[NSString alloc] initWithCharactersNoCopy:const_cast<UChar*>(text) length:length freeWhenDone:NO];
746    NSRange range = [[NSSpellChecker sharedSpellChecker] checkSpellingOfString:textString startingAt:0 language:nil wrap:NO inSpellDocumentWithTag:spellCheckerDocumentTag() wordCount:NULL];
747    [textString release];
748    if (misspellingLocation) {
749        // WebCore expects -1 to represent "not found"
750        if (range.location == NSNotFound)
751            *misspellingLocation = -1;
752        else
753            *misspellingLocation = range.location;
754    }
755
756    if (misspellingLength)
757        *misspellingLength = range.length;
758}
759
760String WebEditorClient::getAutoCorrectSuggestionForMisspelledWord(const String& inputWord)
761{
762    // This method can be implemented using customized algorithms for the particular browser.
763    // Currently, it computes an empty string.
764    return String();
765}
766
767void WebEditorClient::checkGrammarOfString(const UChar* text, int length, Vector<GrammarDetail>& details, int* badGrammarLocation, int* badGrammarLength)
768{
769    NSArray *grammarDetails;
770    NSString* textString = [[NSString alloc] initWithCharactersNoCopy:const_cast<UChar*>(text) length:length freeWhenDone:NO];
771    NSRange range = [[NSSpellChecker sharedSpellChecker] checkGrammarOfString:textString startingAt:0 language:nil wrap:NO inSpellDocumentWithTag:spellCheckerDocumentTag() details:&grammarDetails];
772    [textString release];
773    if (badGrammarLocation)
774        // WebCore expects -1 to represent "not found"
775        *badGrammarLocation = (range.location == NSNotFound) ? -1 : static_cast<int>(range.location);
776    if (badGrammarLength)
777        *badGrammarLength = range.length;
778    for (NSDictionary *detail in grammarDetails) {
779        ASSERT(detail);
780        GrammarDetail grammarDetail;
781        NSValue *detailRangeAsNSValue = [detail objectForKey:NSGrammarRange];
782        ASSERT(detailRangeAsNSValue);
783        NSRange detailNSRange = [detailRangeAsNSValue rangeValue];
784        ASSERT(detailNSRange.location != NSNotFound);
785        ASSERT(detailNSRange.length > 0);
786        grammarDetail.location = detailNSRange.location;
787        grammarDetail.length = detailNSRange.length;
788        grammarDetail.userDescription = [detail objectForKey:NSGrammarUserDescription];
789        NSArray *guesses = [detail objectForKey:NSGrammarCorrections];
790        for (NSString *guess in guesses)
791            grammarDetail.guesses.append(String(guess));
792        details.append(grammarDetail);
793    }
794}
795
796static Vector<TextCheckingResult> core(NSArray *incomingResults, TextCheckingTypeMask checkingTypes)
797{
798    Vector<TextCheckingResult> results;
799
800    for (NSTextCheckingResult *incomingResult in incomingResults) {
801        NSRange resultRange = [incomingResult range];
802        NSTextCheckingType resultType = [incomingResult resultType];
803        ASSERT(resultRange.location != NSNotFound);
804        ASSERT(resultRange.length > 0);
805        if (NSTextCheckingTypeSpelling == resultType && 0 != (checkingTypes & NSTextCheckingTypeSpelling)) {
806            TextCheckingResult result;
807            result.type = TextCheckingTypeSpelling;
808            result.location = resultRange.location;
809            result.length = resultRange.length;
810            results.append(result);
811        } else if (NSTextCheckingTypeGrammar == resultType && 0 != (checkingTypes & NSTextCheckingTypeGrammar)) {
812            TextCheckingResult result;
813            NSArray *details = [incomingResult grammarDetails];
814            result.type = TextCheckingTypeGrammar;
815            result.location = resultRange.location;
816            result.length = resultRange.length;
817            for (NSDictionary *incomingDetail in details) {
818                ASSERT(incomingDetail);
819                GrammarDetail detail;
820                NSValue *detailRangeAsNSValue = [incomingDetail objectForKey:NSGrammarRange];
821                ASSERT(detailRangeAsNSValue);
822                NSRange detailNSRange = [detailRangeAsNSValue rangeValue];
823                ASSERT(detailNSRange.location != NSNotFound);
824                ASSERT(detailNSRange.length > 0);
825                detail.location = detailNSRange.location;
826                detail.length = detailNSRange.length;
827                detail.userDescription = [incomingDetail objectForKey:NSGrammarUserDescription];
828                NSArray *guesses = [incomingDetail objectForKey:NSGrammarCorrections];
829                for (NSString *guess in guesses)
830                    detail.guesses.append(String(guess));
831                result.details.append(detail);
832            }
833            results.append(result);
834        } else if (NSTextCheckingTypeLink == resultType && 0 != (checkingTypes & NSTextCheckingTypeLink)) {
835            TextCheckingResult result;
836            result.type = TextCheckingTypeLink;
837            result.location = resultRange.location;
838            result.length = resultRange.length;
839            result.replacement = [[incomingResult URL] absoluteString];
840            results.append(result);
841        } else if (NSTextCheckingTypeQuote == resultType && 0 != (checkingTypes & NSTextCheckingTypeQuote)) {
842            TextCheckingResult result;
843            result.type = TextCheckingTypeQuote;
844            result.location = resultRange.location;
845            result.length = resultRange.length;
846            result.replacement = [incomingResult replacementString];
847            results.append(result);
848        } else if (NSTextCheckingTypeDash == resultType && 0 != (checkingTypes & NSTextCheckingTypeDash)) {
849            TextCheckingResult result;
850            result.type = TextCheckingTypeDash;
851            result.location = resultRange.location;
852            result.length = resultRange.length;
853            result.replacement = [incomingResult replacementString];
854            results.append(result);
855        } else if (NSTextCheckingTypeReplacement == resultType && 0 != (checkingTypes & NSTextCheckingTypeReplacement)) {
856            TextCheckingResult result;
857            result.type = TextCheckingTypeReplacement;
858            result.location = resultRange.location;
859            result.length = resultRange.length;
860            result.replacement = [incomingResult replacementString];
861            results.append(result);
862        } else if (NSTextCheckingTypeCorrection == resultType && 0 != (checkingTypes & NSTextCheckingTypeCorrection)) {
863            TextCheckingResult result;
864            result.type = TextCheckingTypeCorrection;
865            result.location = resultRange.location;
866            result.length = resultRange.length;
867            result.replacement = [incomingResult replacementString];
868            results.append(result);
869        }
870    }
871
872    return results;
873}
874
875void WebEditorClient::checkTextOfParagraph(const UChar* text, int length, TextCheckingTypeMask checkingTypes, Vector<TextCheckingResult>& results)
876{
877    NSString *textString = [[NSString alloc] initWithCharactersNoCopy:const_cast<UChar*>(text) length:length freeWhenDone:NO];
878    NSArray *incomingResults = [[NSSpellChecker sharedSpellChecker] checkString:textString range:NSMakeRange(0, [textString length]) types:(checkingTypes|NSTextCheckingTypeOrthography) options:nil inSpellDocumentWithTag:spellCheckerDocumentTag() orthography:NULL wordCount:NULL];
879    [textString release];
880    results = core(incomingResults, checkingTypes);
881}
882
883void WebEditorClient::updateSpellingUIWithGrammarString(const String& badGrammarPhrase, const GrammarDetail& grammarDetail)
884{
885    NSMutableArray* corrections = [NSMutableArray array];
886    for (unsigned i = 0; i < grammarDetail.guesses.size(); i++) {
887        NSString* guess = grammarDetail.guesses[i];
888        [corrections addObject:guess];
889    }
890    NSRange grammarRange = NSMakeRange(grammarDetail.location, grammarDetail.length);
891    NSString* grammarUserDescription = grammarDetail.userDescription;
892    NSDictionary* grammarDetailDict = [NSDictionary dictionaryWithObjectsAndKeys:[NSValue valueWithRange:grammarRange], NSGrammarRange, grammarUserDescription, NSGrammarUserDescription, corrections, NSGrammarCorrections, nil];
893
894    [[NSSpellChecker sharedSpellChecker] updateSpellingPanelWithGrammarString:badGrammarPhrase detail:grammarDetailDict];
895}
896
897void WebEditorClient::updateSpellingUIWithMisspelledWord(const String& misspelledWord)
898{
899    [[NSSpellChecker sharedSpellChecker] updateSpellingPanelWithMisspelledWord:misspelledWord];
900}
901
902void WebEditorClient::showSpellingUI(bool show)
903{
904    NSPanel *spellingPanel = [[NSSpellChecker sharedSpellChecker] spellingPanel];
905    if (show)
906        [spellingPanel orderFront:nil];
907    else
908        [spellingPanel orderOut:nil];
909}
910
911bool WebEditorClient::spellingUIIsShowing()
912{
913    return [[[NSSpellChecker sharedSpellChecker] spellingPanel] isVisible];
914}
915
916void WebEditorClient::getGuessesForWord(const String& word, const String& context, Vector<String>& guesses) {
917    guesses.clear();
918#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
919    NSString* language = nil;
920    NSOrthography* orthography = nil;
921    NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
922    if (context.length()) {
923        [checker checkString:context range:NSMakeRange(0, context.length()) types:NSTextCheckingTypeOrthography options:0 inSpellDocumentWithTag:spellCheckerDocumentTag() orthography:&orthography wordCount:0];
924        language = [checker languageForWordRange:NSMakeRange(0, context.length()) inString:context orthography:orthography];
925    }
926    NSArray* stringsArray = [checker guessesForWordRange:NSMakeRange(0, word.length()) inString:word language:language inSpellDocumentWithTag:spellCheckerDocumentTag()];
927#else
928    NSArray* stringsArray = [[NSSpellChecker sharedSpellChecker] guessesForWord:word];
929#endif
930    unsigned count = [stringsArray count];
931
932    if (count > 0) {
933        NSEnumerator* enumerator = [stringsArray objectEnumerator];
934        NSString* string;
935        while ((string = [enumerator nextObject]) != nil)
936            guesses.append(string);
937    }
938}
939
940void WebEditorClient::willSetInputMethodState()
941{
942}
943
944void WebEditorClient::setInputMethodState(bool)
945{
946}
947
948@interface WebEditorSpellCheckResponder : NSObject
949{
950    WebEditorClient* _client;
951    int _sequence;
952    RetainPtr<NSArray> _results;
953}
954- (id)initWithClient:(WebEditorClient*)client sequence:(int)sequence results:(NSArray*)results;
955- (void)perform;
956@end
957
958@implementation WebEditorSpellCheckResponder
959- (id)initWithClient:(WebEditorClient*)client sequence:(int)sequence results:(NSArray*)results
960{
961    self = [super init];
962    if (!self)
963        return nil;
964    _client = client;
965    _sequence = sequence;
966    _results = results;
967    return self;
968}
969
970- (void)perform
971{
972    _client->didCheckSucceed(_sequence, _results.get());
973}
974
975@end
976
977void WebEditorClient::didCheckSucceed(int sequence, NSArray* results)
978{
979    ASSERT_UNUSED(sequence, sequence == m_textCheckingRequest->data().sequence());
980    m_textCheckingRequest->didSucceed(core(results, m_textCheckingRequest->data().mask()));
981    m_textCheckingRequest.clear();
982}
983
984void WebEditorClient::requestCheckingOfString(PassRefPtr<WebCore::TextCheckingRequest> request)
985{
986    ASSERT(!m_textCheckingRequest);
987    m_textCheckingRequest = request;
988
989    int sequence = m_textCheckingRequest->data().sequence();
990    NSRange range = NSMakeRange(0, m_textCheckingRequest->data().text().length());
991    NSRunLoop* currentLoop = [NSRunLoop currentRunLoop];
992    [[NSSpellChecker sharedSpellChecker] requestCheckingOfString:m_textCheckingRequest->data().text() range:range types:NSTextCheckingAllSystemTypes options:0 inSpellDocumentWithTag:0
993                                         completionHandler:^(NSInteger, NSArray* results, NSOrthography*, NSInteger) {
994            [currentLoop performSelector:@selector(perform)
995                                  target:[[[WebEditorSpellCheckResponder alloc] initWithClient:this sequence:sequence results:results] autorelease]
996                                argument:nil order:0 modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]];
997        }];
998}
999