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