2 * Copyright (C) 2006 Apple Computer, Inc. All rights reserved.
3 * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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.
30 #import "WebEditorClient.h"
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 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
73 #import <AppKit/NSTextChecker.h>
76 using namespace WebCore;
79 using namespace HTMLNames;
81 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
82 static inline NSCorrectionBubbleType correctionBubbleType(CorrectionPanelInfo::PanelType panelType)
85 case CorrectionPanelInfo::PanelTypeCorrection:
86 return NSCorrectionBubbleTypeCorrection;
87 case CorrectionPanelInfo::PanelTypeReversion:
88 return NSCorrectionBubbleTypeReversion;
89 case CorrectionPanelInfo::PanelTypeSpellingSuggestions:
90 return NSCorrectionBubbleTypeGuesses;
93 return NSCorrectionBubbleTypeCorrection;
97 @interface NSAttributedString (WebNSAttributedStringDetails)
98 - (id)_initWithDOMRange:(DOMRange*)range;
99 - (DOMDocumentFragment*)_documentFromRange:(NSRange)range document:(DOMDocument*)document documentAttributes:(NSDictionary *)dict subresources:(NSArray **)subresources;
102 static WebViewInsertAction kit(EditorInsertAction coreAction)
104 return static_cast<WebViewInsertAction>(coreAction);
107 static const int InvalidCorrectionPanelTag = 0;
109 #ifdef BUILDING_ON_TIGER
110 @interface NSSpellChecker (NotYetPublicMethods)
111 - (void)learnWord:(NSString *)word;
115 @interface WebEditCommand : NSObject
117 RefPtr<EditCommand> m_command;
120 + (WebEditCommand *)commandWithEditCommand:(PassRefPtr<EditCommand>)command;
121 - (EditCommand *)command;
125 @implementation WebEditCommand
129 JSC::initializeThreading();
130 WTF::initializeMainThreadToProcessMainThread();
131 #ifndef BUILDING_ON_TIGER
132 WebCoreObjCFinalizeOnMainThread(self);
136 - (id)initWithEditCommand:(PassRefPtr<EditCommand>)command
146 if (WebCoreObjCScheduleDeallocateOnMainThread([WebEditCommand class], self))
154 ASSERT_MAIN_THREAD();
159 + (WebEditCommand *)commandWithEditCommand:(PassRefPtr<EditCommand>)command
161 return [[[WebEditCommand alloc] initWithEditCommand:command] autorelease];
164 - (EditCommand *)command
166 return m_command.get();
171 @interface WebEditorUndoTarget : NSObject
175 - (void)undoEditing:(id)arg;
176 - (void)redoEditing:(id)arg;
180 @implementation WebEditorUndoTarget
182 - (void)undoEditing:(id)arg
184 ASSERT([arg isKindOfClass:[WebEditCommand class]]);
185 [arg command]->unapply();
188 - (void)redoEditing:(id)arg
190 ASSERT([arg isKindOfClass:[WebEditCommand class]]);
191 [arg command]->reapply();
196 void WebEditorClient::pageDestroyed()
201 WebEditorClient::WebEditorClient(WebView *webView)
203 , m_undoTarget([[[WebEditorUndoTarget alloc] init] autorelease])
204 , m_haveUndoRedoOperations(false)
205 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
206 , m_correctionPanelIsShown(false)
211 WebEditorClient::~WebEditorClient()
213 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
214 dismissCorrectionPanel(ReasonForDismissingCorrectionPanelIgnored);
218 bool WebEditorClient::isContinuousSpellCheckingEnabled()
220 return [m_webView isContinuousSpellCheckingEnabled];
223 void WebEditorClient::toggleContinuousSpellChecking()
225 [m_webView toggleContinuousSpellChecking:nil];
228 bool WebEditorClient::isGrammarCheckingEnabled()
230 #ifdef BUILDING_ON_TIGER
233 return [m_webView isGrammarCheckingEnabled];
237 void WebEditorClient::toggleGrammarChecking()
239 #ifndef BUILDING_ON_TIGER
240 [m_webView toggleGrammarChecking:nil];
244 int WebEditorClient::spellCheckerDocumentTag()
246 return [m_webView spellCheckerDocumentTag];
249 bool WebEditorClient::shouldDeleteRange(Range* range)
251 return [[m_webView _editingDelegateForwarder] webView:m_webView
252 shouldDeleteDOMRange:kit(range)];
255 bool WebEditorClient::shouldShowDeleteInterface(HTMLElement* element)
257 return [[m_webView _editingDelegateForwarder] webView:m_webView
258 shouldShowDeleteInterfaceForElement:kit(element)];
261 bool WebEditorClient::smartInsertDeleteEnabled()
263 return [m_webView smartInsertDeleteEnabled];
266 bool WebEditorClient::isSelectTrailingWhitespaceEnabled()
268 return [m_webView isSelectTrailingWhitespaceEnabled];
271 bool WebEditorClient::shouldApplyStyle(CSSStyleDeclaration* style, Range* range)
273 return [[m_webView _editingDelegateForwarder] webView:m_webView
274 shouldApplyStyle:kit(style) toElementsInDOMRange:kit(range)];
277 bool WebEditorClient::shouldMoveRangeAfterDelete(Range* range, Range* rangeToBeReplaced)
279 return [[m_webView _editingDelegateForwarder] webView:m_webView
280 shouldMoveRangeAfterDelete:kit(range) replacingRange:kit(rangeToBeReplaced)];
283 bool WebEditorClient::shouldBeginEditing(Range* range)
285 return [[m_webView _editingDelegateForwarder] webView:m_webView
286 shouldBeginEditingInDOMRange:kit(range)];
291 bool WebEditorClient::shouldEndEditing(Range* range)
293 return [[m_webView _editingDelegateForwarder] webView:m_webView
294 shouldEndEditingInDOMRange:kit(range)];
297 bool WebEditorClient::shouldInsertText(const String& text, Range* range, EditorInsertAction action)
299 WebView* webView = m_webView;
300 return [[webView _editingDelegateForwarder] webView:webView shouldInsertText:text replacingDOMRange:kit(range) givenAction:kit(action)];
303 bool WebEditorClient::shouldChangeSelectedRange(Range* fromRange, Range* toRange, EAffinity selectionAffinity, bool stillSelecting)
305 return [m_webView _shouldChangeSelectedDOMRange:kit(fromRange) toDOMRange:kit(toRange) affinity:kit(selectionAffinity) stillSelecting:stillSelecting];
308 void WebEditorClient::didBeginEditing()
310 [[NSNotificationCenter defaultCenter] postNotificationName:WebViewDidBeginEditingNotification object:m_webView];
313 void WebEditorClient::respondToChangedContents()
315 NSView <WebDocumentView> *view = [[[m_webView selectedFrame] frameView] documentView];
316 if ([view isKindOfClass:[WebHTMLView class]])
317 [(WebHTMLView *)view _updateFontPanel];
318 [[NSNotificationCenter defaultCenter] postNotificationName:WebViewDidChangeNotification object:m_webView];
321 void WebEditorClient::respondToChangedSelection()
323 [m_webView _selectionChanged];
325 // 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
326 if (!WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITHOUT_APERTURE_QUIRK) && [[[NSBundle mainBundle] bundleIdentifier] isEqualToString:@"com.apple.Aperture"])
329 [[NSNotificationCenter defaultCenter] postNotificationName:WebViewDidChangeSelectionNotification object:m_webView];
332 void WebEditorClient::didEndEditing()
334 [[NSNotificationCenter defaultCenter] postNotificationName:WebViewDidEndEditingNotification object:m_webView];
337 void WebEditorClient::didWriteSelectionToPasteboard()
339 [[m_webView _editingDelegateForwarder] webView:m_webView didWriteSelectionToPasteboard:[NSPasteboard generalPasteboard]];
342 void WebEditorClient::didSetSelectionTypesForPasteboard()
344 [[m_webView _editingDelegateForwarder] webView:m_webView didSetSelectionTypesForPasteboard:[NSPasteboard generalPasteboard]];
347 NSString *WebEditorClient::userVisibleString(NSURL *URL)
349 return [URL _web_userVisibleString];
352 NSURL *WebEditorClient::canonicalizeURL(NSURL *URL)
354 return [URL _webkit_canonicalize];
357 NSURL *WebEditorClient::canonicalizeURLString(NSString *URLString)
360 if ([URLString _webkit_looksLikeAbsoluteURL])
361 URL = [[NSURL _web_URLWithUserTypedString:URLString] _webkit_canonicalize];
365 static NSArray *createExcludedElementsForAttributedStringConversion()
367 NSArray *elements = [[NSArray alloc] initWithObjects:
368 // Omit style since we want style to be inline so the fragment can be easily inserted.
370 // Omit xml so the result is not XHTML.
372 // Omit tags that will get stripped when converted to a fragment anyway.
373 @"doctype", @"html", @"head", @"body",
374 // Omit deprecated tags.
375 @"applet", @"basefont", @"center", @"dir", @"font", @"isindex", @"menu", @"s", @"strike", @"u",
376 // Omit object so no file attachments are part of the fragment.
382 DocumentFragment* WebEditorClient::documentFragmentFromAttributedString(NSAttributedString *string, Vector<RefPtr<ArchiveResource> >& resources)
384 static NSArray *excludedElements = createExcludedElementsForAttributedStringConversion();
386 NSDictionary *dictionary = [[NSDictionary alloc] initWithObjectsAndKeys: excludedElements, NSExcludedElementsDocumentAttribute,
387 nil, @"WebResourceHandler", nil];
389 NSArray *subResources;
390 DOMDocumentFragment* fragment = [string _documentFromRange:NSMakeRange(0, [string length])
391 document:[[m_webView mainFrame] DOMDocument]
392 documentAttributes:dictionary
393 subresources:&subResources];
394 for (WebResource* resource in subResources)
395 resources.append([resource _coreResource]);
397 [dictionary release];
398 return core(fragment);
401 void WebEditorClient::setInsertionPasteboard(NSPasteboard *pasteboard)
403 [m_webView _setInsertionPasteboard:pasteboard];
406 #ifdef BUILDING_ON_TIGER
407 NSArray *WebEditorClient::pasteboardTypesForSelection(Frame* selectedFrame)
409 WebFrame* frame = kit(selectedFrame);
410 return [[[frame frameView] documentView] pasteboardTypesForSelection];
414 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
415 void WebEditorClient::uppercaseWord()
417 [m_webView uppercaseWord:nil];
420 void WebEditorClient::lowercaseWord()
422 [m_webView lowercaseWord:nil];
425 void WebEditorClient::capitalizeWord()
427 [m_webView capitalizeWord:nil];
430 void WebEditorClient::showSubstitutionsPanel(bool show)
432 NSPanel *spellingPanel = [[NSSpellChecker sharedSpellChecker] substitutionsPanel];
434 [spellingPanel orderFront:nil];
436 [spellingPanel orderOut:nil];
439 bool WebEditorClient::substitutionsPanelIsShowing()
441 return [[[NSSpellChecker sharedSpellChecker] substitutionsPanel] isVisible];
444 void WebEditorClient::toggleSmartInsertDelete()
446 [m_webView toggleSmartInsertDelete:nil];
449 bool WebEditorClient::isAutomaticQuoteSubstitutionEnabled()
451 return [m_webView isAutomaticQuoteSubstitutionEnabled];
454 void WebEditorClient::toggleAutomaticQuoteSubstitution()
456 [m_webView toggleAutomaticQuoteSubstitution:nil];
459 bool WebEditorClient::isAutomaticLinkDetectionEnabled()
461 return [m_webView isAutomaticLinkDetectionEnabled];
464 void WebEditorClient::toggleAutomaticLinkDetection()
466 [m_webView toggleAutomaticLinkDetection:nil];
469 bool WebEditorClient::isAutomaticDashSubstitutionEnabled()
471 return [m_webView isAutomaticDashSubstitutionEnabled];
474 void WebEditorClient::toggleAutomaticDashSubstitution()
476 [m_webView toggleAutomaticDashSubstitution:nil];
479 bool WebEditorClient::isAutomaticTextReplacementEnabled()
481 return [m_webView isAutomaticTextReplacementEnabled];
484 void WebEditorClient::toggleAutomaticTextReplacement()
486 [m_webView toggleAutomaticTextReplacement:nil];
489 bool WebEditorClient::isAutomaticSpellingCorrectionEnabled()
491 return [m_webView isAutomaticSpellingCorrectionEnabled];
494 void WebEditorClient::toggleAutomaticSpellingCorrection()
496 [m_webView toggleAutomaticSpellingCorrection:nil];
500 bool WebEditorClient::shouldInsertNode(Node *node, Range* replacingRange, EditorInsertAction givenAction)
502 return [[m_webView _editingDelegateForwarder] webView:m_webView shouldInsertNode:kit(node) replacingDOMRange:kit(replacingRange) givenAction:(WebViewInsertAction)givenAction];
505 static NSString* undoNameForEditAction(EditAction editAction)
507 switch (editAction) {
508 case EditActionUnspecified: return nil;
509 case EditActionSetColor: return UI_STRING_KEY_INTERNAL("Set Color", "Set Color (Undo action name)", "Undo action name");
510 case EditActionSetBackgroundColor: return UI_STRING_KEY_INTERNAL("Set Background Color", "Set Background Color (Undo action name)", "Undo action name");
511 case EditActionTurnOffKerning: return UI_STRING_KEY_INTERNAL("Turn Off Kerning", "Turn Off Kerning (Undo action name)", "Undo action name");
512 case EditActionTightenKerning: return UI_STRING_KEY_INTERNAL("Tighten Kerning", "Tighten Kerning (Undo action name)", "Undo action name");
513 case EditActionLoosenKerning: return UI_STRING_KEY_INTERNAL("Loosen Kerning", "Loosen Kerning (Undo action name)", "Undo action name");
514 case EditActionUseStandardKerning: return UI_STRING_KEY_INTERNAL("Use Standard Kerning", "Use Standard Kerning (Undo action name)", "Undo action name");
515 case EditActionTurnOffLigatures: return UI_STRING_KEY_INTERNAL("Turn Off Ligatures", "Turn Off Ligatures (Undo action name)", "Undo action name");
516 case EditActionUseStandardLigatures: return UI_STRING_KEY_INTERNAL("Use Standard Ligatures", "Use Standard Ligatures (Undo action name)", "Undo action name");
517 case EditActionUseAllLigatures: return UI_STRING_KEY_INTERNAL("Use All Ligatures", "Use All Ligatures (Undo action name)", "Undo action name");
518 case EditActionRaiseBaseline: return UI_STRING_KEY_INTERNAL("Raise Baseline", "Raise Baseline (Undo action name)", "Undo action name");
519 case EditActionLowerBaseline: return UI_STRING_KEY_INTERNAL("Lower Baseline", "Lower Baseline (Undo action name)", "Undo action name");
520 case EditActionSetTraditionalCharacterShape: return UI_STRING_KEY_INTERNAL("Set Traditional Character Shape", "Set Traditional Character Shape (Undo action name)", "Undo action name");
521 case EditActionSetFont: return UI_STRING_KEY_INTERNAL("Set Font", "Set Font (Undo action name)", "Undo action name");
522 case EditActionChangeAttributes: return UI_STRING_KEY_INTERNAL("Change Attributes", "Change Attributes (Undo action name)", "Undo action name");
523 case EditActionAlignLeft: return UI_STRING_KEY_INTERNAL("Align Left", "Align Left (Undo action name)", "Undo action name");
524 case EditActionAlignRight: return UI_STRING_KEY_INTERNAL("Align Right", "Align Right (Undo action name)", "Undo action name");
525 case EditActionCenter: return UI_STRING_KEY_INTERNAL("Center", "Center (Undo action name)", "Undo action name");
526 case EditActionJustify: return UI_STRING_KEY_INTERNAL("Justify", "Justify (Undo action name)", "Undo action name");
527 case EditActionSetWritingDirection: return UI_STRING_KEY_INTERNAL("Set Writing Direction", "Set Writing Direction (Undo action name)", "Undo action name");
528 case EditActionSubscript: return UI_STRING_KEY_INTERNAL("Subscript", "Subscript (Undo action name)", "Undo action name");
529 case EditActionSuperscript: return UI_STRING_KEY_INTERNAL("Superscript", "Superscript (Undo action name)", "Undo action name");
530 case EditActionUnderline: return UI_STRING_KEY_INTERNAL("Underline", "Underline (Undo action name)", "Undo action name");
531 case EditActionOutline: return UI_STRING_KEY_INTERNAL("Outline", "Outline (Undo action name)", "Undo action name");
532 case EditActionUnscript: return UI_STRING_KEY_INTERNAL("Unscript", "Unscript (Undo action name)", "Undo action name");
533 case EditActionDrag: return UI_STRING_KEY_INTERNAL("Drag", "Drag (Undo action name)", "Undo action name");
534 case EditActionCut: return UI_STRING_KEY_INTERNAL("Cut", "Cut (Undo action name)", "Undo action name");
535 case EditActionPaste: return UI_STRING_KEY_INTERNAL("Paste", "Paste (Undo action name)", "Undo action name");
536 case EditActionPasteFont: return UI_STRING_KEY_INTERNAL("Paste Font", "Paste Font (Undo action name)", "Undo action name");
537 case EditActionPasteRuler: return UI_STRING_KEY_INTERNAL("Paste Ruler", "Paste Ruler (Undo action name)", "Undo action name");
538 case EditActionTyping: return UI_STRING_KEY_INTERNAL("Typing", "Typing (Undo action name)", "Undo action name");
539 case EditActionCreateLink: return UI_STRING_KEY_INTERNAL("Create Link", "Create Link (Undo action name)", "Undo action name");
540 case EditActionUnlink: return UI_STRING_KEY_INTERNAL("Unlink", "Unlink (Undo action name)", "Undo action name");
541 case EditActionInsertList: return UI_STRING_KEY_INTERNAL("Insert List", "Insert List (Undo action name)", "Undo action name");
542 case EditActionFormatBlock: return UI_STRING_KEY_INTERNAL("Formatting", "Format Block (Undo action name)", "Undo action name");
543 case EditActionIndent: return UI_STRING_KEY_INTERNAL("Indent", "Indent (Undo action name)", "Undo action name");
544 case EditActionOutdent: return UI_STRING_KEY_INTERNAL("Outdent", "Outdent (Undo action name)", "Undo action name");
549 void WebEditorClient::registerCommandForUndoOrRedo(PassRefPtr<EditCommand> cmd, bool isRedo)
553 NSUndoManager *undoManager = [m_webView undoManager];
554 NSString *actionName = undoNameForEditAction(cmd->editingAction());
555 WebEditCommand *command = [WebEditCommand commandWithEditCommand:cmd];
556 [undoManager registerUndoWithTarget:m_undoTarget.get() selector:(isRedo ? @selector(redoEditing:) : @selector(undoEditing:)) object:command];
558 [undoManager setActionName:actionName];
559 m_haveUndoRedoOperations = YES;
562 void WebEditorClient::registerCommandForUndo(PassRefPtr<EditCommand> cmd)
564 registerCommandForUndoOrRedo(cmd, false);
567 void WebEditorClient::registerCommandForRedo(PassRefPtr<EditCommand> cmd)
569 registerCommandForUndoOrRedo(cmd, true);
572 void WebEditorClient::clearUndoRedoOperations()
574 if (m_haveUndoRedoOperations) {
575 // workaround for <rdar://problem/4645507> NSUndoManager dies
576 // with uncaught exception when undo items cleared while
578 NSUndoManager *undoManager = [m_webView undoManager];
579 int groupingLevel = [undoManager groupingLevel];
580 for (int i = 0; i < groupingLevel; ++i)
581 [undoManager endUndoGrouping];
583 [undoManager removeAllActionsWithTarget:m_undoTarget.get()];
585 for (int i = 0; i < groupingLevel; ++i)
586 [undoManager beginUndoGrouping];
588 m_haveUndoRedoOperations = NO;
592 bool WebEditorClient::canCopyCut(bool defaultValue) const
597 bool WebEditorClient::canPaste(bool defaultValue) const
602 bool WebEditorClient::canUndo() const
604 return [[m_webView undoManager] canUndo];
607 bool WebEditorClient::canRedo() const
609 return [[m_webView undoManager] canRedo];
612 void WebEditorClient::undo()
615 [[m_webView undoManager] undo];
618 void WebEditorClient::redo()
621 [[m_webView undoManager] redo];
624 void WebEditorClient::handleKeyboardEvent(KeyboardEvent* event)
626 Frame* frame = event->target()->toNode()->document()->frame();
627 WebHTMLView *webHTMLView = [[kit(frame) frameView] documentView];
628 if ([webHTMLView _interceptEditingKeyEvent:event shouldSaveCommand:NO])
629 event->setDefaultHandled();
632 void WebEditorClient::handleInputMethodKeydown(KeyboardEvent* event)
634 Frame* frame = event->target()->toNode()->document()->frame();
635 WebHTMLView *webHTMLView = [[kit(frame) frameView] documentView];
636 if ([webHTMLView _interceptEditingKeyEvent:event shouldSaveCommand:YES])
637 event->setDefaultHandled();
640 #define FormDelegateLog(ctrl) LOG(FormDelegate, "control=%@", ctrl)
642 void WebEditorClient::textFieldDidBeginEditing(Element* element)
644 if (!element->hasTagName(inputTag))
647 DOMHTMLInputElement* inputElement = kit(static_cast<HTMLInputElement*>(element));
648 FormDelegateLog(inputElement);
649 CallFormDelegate(m_webView, @selector(textFieldDidBeginEditing:inFrame:), inputElement, kit(element->document()->frame()));
652 void WebEditorClient::textFieldDidEndEditing(Element* element)
654 if (!element->hasTagName(inputTag))
657 DOMHTMLInputElement* inputElement = kit(static_cast<HTMLInputElement*>(element));
658 FormDelegateLog(inputElement);
659 CallFormDelegate(m_webView, @selector(textFieldDidEndEditing:inFrame:), inputElement, kit(element->document()->frame()));
662 void WebEditorClient::textDidChangeInTextField(Element* element)
664 if (!element->hasTagName(inputTag))
667 if (!UserTypingGestureIndicator::processingUserTypingGesture() || UserTypingGestureIndicator::focusedElementAtGestureStart() != element)
670 DOMHTMLInputElement* inputElement = kit(static_cast<HTMLInputElement*>(element));
671 FormDelegateLog(inputElement);
672 CallFormDelegate(m_webView, @selector(textDidChangeInTextField:inFrame:), inputElement, kit(element->document()->frame()));
675 static SEL selectorForKeyEvent(KeyboardEvent* event)
677 // FIXME: This helper function is for the auto-fill code so we can pass a selector to the form delegate.
678 // Eventually, we should move all of the auto-fill code down to WebKit and remove the need for this function by
679 // not relying on the selector in the new implementation.
680 // The key identifiers are from <http://www.w3.org/TR/DOM-Level-3-Events/keyset.html#KeySet-Set>
681 const String& key = event->keyIdentifier();
683 return @selector(moveUp:);
685 return @selector(moveDown:);
687 return @selector(cancel:);
688 if (key == "U+0009") {
689 if (event->shiftKey())
690 return @selector(insertBacktab:);
691 return @selector(insertTab:);
694 return @selector(insertNewline:);
698 bool WebEditorClient::doTextFieldCommandFromEvent(Element* element, KeyboardEvent* event)
700 if (!element->hasTagName(inputTag))
703 DOMHTMLInputElement* inputElement = kit(static_cast<HTMLInputElement*>(element));
704 FormDelegateLog(inputElement);
705 if (SEL commandSelector = selectorForKeyEvent(event))
706 return CallFormDelegateReturningBoolean(NO, m_webView, @selector(textField:doCommandBySelector:inFrame:), inputElement, commandSelector, kit(element->document()->frame()));
710 void WebEditorClient::textWillBeDeletedInTextField(Element* element)
712 if (!element->hasTagName(inputTag))
715 DOMHTMLInputElement* inputElement = kit(static_cast<HTMLInputElement*>(element));
716 FormDelegateLog(inputElement);
717 // We're using the deleteBackward selector for all deletion operations since the autofill code treats all deletions the same way.
718 CallFormDelegateReturningBoolean(NO, m_webView, @selector(textField:doCommandBySelector:inFrame:), inputElement, @selector(deleteBackward:), kit(element->document()->frame()));
721 void WebEditorClient::textDidChangeInTextArea(Element* element)
723 if (!element->hasTagName(textareaTag))
726 DOMHTMLTextAreaElement* textAreaElement = kit(static_cast<HTMLTextAreaElement*>(element));
727 FormDelegateLog(textAreaElement);
728 CallFormDelegate(m_webView, @selector(textDidChangeInTextArea:inFrame:), textAreaElement, kit(element->document()->frame()));
731 void WebEditorClient::ignoreWordInSpellDocument(const String& text)
733 [[NSSpellChecker sharedSpellChecker] ignoreWord:text
734 inSpellDocumentWithTag:spellCheckerDocumentTag()];
737 void WebEditorClient::learnWord(const String& text)
739 [[NSSpellChecker sharedSpellChecker] learnWord:text];
742 void WebEditorClient::checkSpellingOfString(const UChar* text, int length, int* misspellingLocation, int* misspellingLength)
744 NSString* textString = [[NSString alloc] initWithCharactersNoCopy:const_cast<UChar*>(text) length:length freeWhenDone:NO];
745 NSRange range = [[NSSpellChecker sharedSpellChecker] checkSpellingOfString:textString startingAt:0 language:nil wrap:NO inSpellDocumentWithTag:spellCheckerDocumentTag() wordCount:NULL];
746 [textString release];
747 if (misspellingLocation) {
748 // WebCore expects -1 to represent "not found"
749 if (range.location == NSNotFound)
750 *misspellingLocation = -1;
752 *misspellingLocation = range.location;
755 if (misspellingLength)
756 *misspellingLength = range.length;
759 String WebEditorClient::getAutoCorrectSuggestionForMisspelledWord(const String& inputWord)
761 // This method can be implemented using customized algorithms for the particular browser.
762 // Currently, it computes an empty string.
766 void WebEditorClient::checkGrammarOfString(const UChar* text, int length, Vector<GrammarDetail>& details, int* badGrammarLocation, int* badGrammarLength)
768 #ifndef BUILDING_ON_TIGER
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) {
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);
797 void WebEditorClient::checkTextOfParagraph(const UChar* text, int length, uint64_t checkingTypes, Vector<TextCheckingResult>& results)
799 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
800 NSString *textString = [[NSString alloc] initWithCharactersNoCopy:const_cast<UChar*>(text) length:length freeWhenDone:NO];
801 NSArray *incomingResults = [[NSSpellChecker sharedSpellChecker] checkString:textString range:NSMakeRange(0, [textString length]) types:(checkingTypes|NSTextCheckingTypeOrthography) options:nil inSpellDocumentWithTag:spellCheckerDocumentTag() orthography:NULL wordCount:NULL];
802 [textString release];
803 for (NSTextCheckingResult *incomingResult in incomingResults) {
804 NSRange resultRange = [incomingResult range];
805 NSTextCheckingType resultType = [incomingResult resultType];
806 ASSERT(resultRange.location != NSNotFound);
807 ASSERT(resultRange.length > 0);
808 if (NSTextCheckingTypeSpelling == resultType && 0 != (checkingTypes & NSTextCheckingTypeSpelling)) {
809 TextCheckingResult result;
810 result.type = TextCheckingTypeSpelling;
811 result.location = resultRange.location;
812 result.length = resultRange.length;
813 results.append(result);
814 } else if (NSTextCheckingTypeGrammar == resultType && 0 != (checkingTypes & NSTextCheckingTypeGrammar)) {
815 TextCheckingResult result;
816 NSArray *details = [incomingResult grammarDetails];
817 result.type = TextCheckingTypeGrammar;
818 result.location = resultRange.location;
819 result.length = resultRange.length;
820 for (NSDictionary *incomingDetail in details) {
821 ASSERT(incomingDetail);
822 GrammarDetail detail;
823 NSValue *detailRangeAsNSValue = [incomingDetail objectForKey:NSGrammarRange];
824 ASSERT(detailRangeAsNSValue);
825 NSRange detailNSRange = [detailRangeAsNSValue rangeValue];
826 ASSERT(detailNSRange.location != NSNotFound);
827 ASSERT(detailNSRange.length > 0);
828 detail.location = detailNSRange.location;
829 detail.length = detailNSRange.length;
830 detail.userDescription = [incomingDetail objectForKey:NSGrammarUserDescription];
831 NSArray *guesses = [incomingDetail objectForKey:NSGrammarCorrections];
832 for (NSString *guess in guesses)
833 detail.guesses.append(String(guess));
834 result.details.append(detail);
836 results.append(result);
837 } else if (NSTextCheckingTypeLink == resultType && 0 != (checkingTypes & NSTextCheckingTypeLink)) {
838 TextCheckingResult result;
839 result.type = TextCheckingTypeLink;
840 result.location = resultRange.location;
841 result.length = resultRange.length;
842 result.replacement = [[incomingResult URL] absoluteString];
843 results.append(result);
844 } else if (NSTextCheckingTypeQuote == resultType && 0 != (checkingTypes & NSTextCheckingTypeQuote)) {
845 TextCheckingResult result;
846 result.type = TextCheckingTypeQuote;
847 result.location = resultRange.location;
848 result.length = resultRange.length;
849 result.replacement = [incomingResult replacementString];
850 results.append(result);
851 } else if (NSTextCheckingTypeDash == resultType && 0 != (checkingTypes & NSTextCheckingTypeDash)) {
852 TextCheckingResult result;
853 result.type = TextCheckingTypeDash;
854 result.location = resultRange.location;
855 result.length = resultRange.length;
856 result.replacement = [incomingResult replacementString];
857 results.append(result);
858 } else if (NSTextCheckingTypeReplacement == resultType && 0 != (checkingTypes & NSTextCheckingTypeReplacement)) {
859 TextCheckingResult result;
860 result.type = TextCheckingTypeReplacement;
861 result.location = resultRange.location;
862 result.length = resultRange.length;
863 result.replacement = [incomingResult replacementString];
864 results.append(result);
865 } else if (NSTextCheckingTypeCorrection == resultType && 0 != (checkingTypes & NSTextCheckingTypeCorrection)) {
866 TextCheckingResult result;
867 result.type = TextCheckingTypeCorrection;
868 result.location = resultRange.location;
869 result.length = resultRange.length;
870 result.replacement = [incomingResult replacementString];
871 results.append(result);
877 void WebEditorClient::updateSpellingUIWithGrammarString(const String& badGrammarPhrase, const GrammarDetail& grammarDetail)
879 #ifndef BUILDING_ON_TIGER
880 NSMutableArray* corrections = [NSMutableArray array];
881 for (unsigned i = 0; i < grammarDetail.guesses.size(); i++) {
882 NSString* guess = grammarDetail.guesses[i];
883 [corrections addObject:guess];
885 NSRange grammarRange = NSMakeRange(grammarDetail.location, grammarDetail.length);
886 NSString* grammarUserDescription = grammarDetail.userDescription;
887 NSMutableDictionary* grammarDetailDict = [NSDictionary dictionaryWithObjectsAndKeys:[NSValue valueWithRange:grammarRange], NSGrammarRange, grammarUserDescription, NSGrammarUserDescription, corrections, NSGrammarCorrections, nil];
889 [[NSSpellChecker sharedSpellChecker] updateSpellingPanelWithGrammarString:badGrammarPhrase detail:grammarDetailDict];
893 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
894 void WebEditorClient::showCorrectionPanel(WebCore::CorrectionPanelInfo::PanelType panelType, const FloatRect& boundingBoxOfReplacedString, const String& replacedString, const String& replacementString, const Vector<String>& alternativeReplacementStrings, Editor* editor) {
895 dismissCorrectionPanel(ReasonForDismissingCorrectionPanelIgnored);
897 // Need to explicitly use these local NSString objects, because the C++ references may be invalidated by the time the block below is executed.
898 NSString *replacedStringAsNSString = replacedString;
899 NSString *replacementStringAsNSString = replacementString;
901 m_correctionPanelIsShown = YES;
902 m_correctionPanelIsDismissedExternally = NO;
903 m_reasonForDismissingCorrectionPanel = ReasonForDismissingCorrectionPanelIgnored;
905 NSCorrectionBubbleType bubbleType = correctionBubbleType(panelType);
906 NSMutableArray *alternativeStrings = nil;
907 if (!alternativeReplacementStrings.isEmpty()) {
908 size_t size = alternativeReplacementStrings.size();
909 alternativeStrings = [NSMutableArray arrayWithCapacity:size];
910 for (size_t i = 0; i < size; ++i)
911 [alternativeStrings addObject:(NSString*)alternativeReplacementStrings[i]];
913 NSSpellChecker *spellChecker = [NSSpellChecker sharedSpellChecker];
914 [[NSSpellChecker sharedSpellChecker] showCorrectionBubbleOfType:bubbleType primaryString:replacementStringAsNSString alternativeStrings:alternativeStrings forStringInRect:boundingBoxOfReplacedString view:m_webView completionHandler:^(NSString *acceptedString) {
915 switch (bubbleType) {
916 case NSCorrectionBubbleTypeCorrection:
918 [spellChecker recordResponse:NSCorrectionResponseAccepted toCorrection:acceptedString forWord:replacedStringAsNSString language:nil inSpellDocumentWithTag:spellCheckerDocumentTag()];
920 if (!m_correctionPanelIsDismissedExternally || m_reasonForDismissingCorrectionPanel == ReasonForDismissingCorrectionPanelCancelled)
921 [spellChecker recordResponse:NSCorrectionResponseRejected toCorrection:replacementStringAsNSString forWord:replacedStringAsNSString language:nil inSpellDocumentWithTag:spellCheckerDocumentTag()];
923 [spellChecker recordResponse:NSCorrectionResponseIgnored toCorrection:replacementStringAsNSString forWord:replacedStringAsNSString language:nil inSpellDocumentWithTag:spellCheckerDocumentTag()];
926 case NSCorrectionBubbleTypeReversion:
928 [spellChecker recordResponse:NSCorrectionResponseReverted toCorrection:replacedStringAsNSString forWord:acceptedString language:nil inSpellDocumentWithTag:spellCheckerDocumentTag()];
930 case NSCorrectionBubbleTypeGuesses:
932 [[NSSpellChecker sharedSpellChecker] recordResponse:NSCorrectionResponseAccepted toCorrection:acceptedString forWord:replacedStringAsNSString language:nil inSpellDocumentWithTag:[m_webView spellCheckerDocumentTag]];
935 editor->handleCorrectionPanelResult(String(acceptedString));
939 void WebEditorClient::dismissCorrectionPanel(ReasonForDismissingCorrectionPanel reasonForDismissing)
941 if (isShowingCorrectionPanel()) {
942 m_correctionPanelIsDismissedExternally = YES;
943 m_reasonForDismissingCorrectionPanel = reasonForDismissing;
944 if (reasonForDismissing == ReasonForDismissingCorrectionPanelAccepted)
945 [[NSSpellChecker sharedSpellChecker] dismissCorrectionBubbleForView:m_webView];
947 [[NSSpellChecker sharedSpellChecker] cancelCorrectionBubbleForView:m_webView];
948 m_correctionPanelIsShown = NO;
952 bool WebEditorClient::isShowingCorrectionPanel()
954 return m_correctionPanelIsShown;
957 void WebEditorClient::recordAutocorrectionResponse(EditorClient::AutocorrectionResponseType responseType, const String& replacedString, const String& replacementString)
959 NSCorrectionResponse spellCheckerResponse = responseType == EditorClient::AutocorrectionReverted ? NSCorrectionResponseReverted : NSCorrectionResponseEdited;
960 [[NSSpellChecker sharedSpellChecker] recordResponse:spellCheckerResponse toCorrection:replacementString forWord:replacedString language:nil inSpellDocumentWithTag:[m_webView spellCheckerDocumentTag]];
964 void WebEditorClient::updateSpellingUIWithMisspelledWord(const String& misspelledWord)
966 [[NSSpellChecker sharedSpellChecker] updateSpellingPanelWithMisspelledWord:misspelledWord];
969 void WebEditorClient::showSpellingUI(bool show)
971 NSPanel *spellingPanel = [[NSSpellChecker sharedSpellChecker] spellingPanel];
973 [spellingPanel orderFront:nil];
975 [spellingPanel orderOut:nil];
978 bool WebEditorClient::spellingUIIsShowing()
980 return [[[NSSpellChecker sharedSpellChecker] spellingPanel] isVisible];
983 void WebEditorClient::getGuessesForWord(const String& word, const String& context, Vector<String>& guesses) {
985 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
986 NSString* language = nil;
987 NSOrthography* orthography = nil;
988 NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
989 if (context.length()) {
990 [checker checkString:context range:NSMakeRange(0, context.length()) types:NSTextCheckingTypeOrthography options:0 inSpellDocumentWithTag:spellCheckerDocumentTag() orthography:&orthography wordCount:0];
991 language = [checker languageForWordRange:NSMakeRange(0, context.length()) inString:context orthography:orthography];
993 NSArray* stringsArray = [checker guessesForWordRange:NSMakeRange(0, word.length()) inString:word language:language inSpellDocumentWithTag:spellCheckerDocumentTag()];
995 NSArray* stringsArray = [[NSSpellChecker sharedSpellChecker] guessesForWord:word];
997 unsigned count = [stringsArray count];
1000 NSEnumerator* enumerator = [stringsArray objectEnumerator];
1002 while ((string = [enumerator nextObject]) != nil)
1003 guesses.append(string);
1007 void WebEditorClient::willSetInputMethodState()
1011 void WebEditorClient::setInputMethodState(bool)
1015 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
1016 @interface WebEditorSpellCheckResponder : NSObject
1018 WebCore::SpellChecker* _sender;
1020 RetainPtr<NSArray> _results;
1022 - (id)initWithSender:(WebCore::SpellChecker*)sender sequence:(int)sequence results:(NSArray*)results;
1024 - (WTF::Vector<WebCore::SpellCheckingResult>) _coreResults;
1027 @implementation WebEditorSpellCheckResponder
1028 - (id)initWithSender:(WebCore::SpellChecker*)sender sequence:(int)sequence results:(NSArray*)results
1030 self = [super init];
1034 _sequence = sequence;
1041 _sender->didCheck(_sequence, [self _coreResults]);
1044 static SpellCheckingResult toCoreSpellingResult(NSTextCheckingResult* result)
1046 NSTextCheckingType type = [result resultType];
1047 NSRange range = [result range];
1048 DocumentMarker::MarkerType coreType;
1049 if (type & NSTextCheckingTypeSpelling)
1050 coreType = DocumentMarker::Spelling;
1051 else if (type & NSTextCheckingTypeGrammar)
1052 coreType = DocumentMarker::Grammar;
1054 coreType = DocumentMarker::AllMarkers;
1056 return SpellCheckingResult(coreType, range.location, range.length);
1059 - (WTF::Vector<WebCore::SpellCheckingResult>)_coreResults
1061 WTF::Vector<WebCore::SpellCheckingResult> coreResults;
1062 coreResults.reserveCapacity([_results.get() count]);
1063 for (NSTextCheckingResult* result in _results.get())
1064 coreResults.append(toCoreSpellingResult(result));
1071 void WebEditorClient::requestCheckingOfString(WebCore::SpellChecker* sender, int sequence, const String& text)
1073 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
1074 NSRange range = NSMakeRange(0, text.length());
1075 NSRunLoop* currentLoop = [NSRunLoop currentRunLoop];
1076 [[NSSpellChecker sharedSpellChecker] requestCheckingOfString:text range:range types:NSTextCheckingAllSystemTypes options:0 inSpellDocumentWithTag:0
1077 completionHandler:^(NSInteger, NSArray* results, NSOrthography*, NSInteger) {
1078 [currentLoop performSelector:@selector(perform)
1079 target:[[[WebEditorSpellCheckResponder alloc] initWithSender:sender sequence:sequence results:results] autorelease]
1080 argument:nil order:0 modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]];