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/MainThread.h>
71 #import <wtf/PassRefPtr.h>
73 using namespace WebCore;
75 using namespace HTMLNames;
77 #if !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
78 @interface NSSpellChecker (WebNSSpellCheckerDetails)
79 - (NSString *)languageForWordRange:(NSRange)range inString:(NSString *)string orthography:(NSOrthography *)orthography;
83 @interface NSAttributedString (WebNSAttributedStringDetails)
84 - (id)_initWithDOMRange:(DOMRange*)range;
85 - (DOMDocumentFragment*)_documentFromRange:(NSRange)range document:(DOMDocument*)document documentAttributes:(NSDictionary *)dict subresources:(NSArray **)subresources;
88 static WebViewInsertAction kit(EditorInsertAction coreAction)
90 return static_cast<WebViewInsertAction>(coreAction);
93 static const int InvalidCorrectionPanelTag = 0;
96 @interface WebEditCommand : NSObject
98 RefPtr<EditCommand> m_command;
101 + (WebEditCommand *)commandWithEditCommand:(PassRefPtr<EditCommand>)command;
102 - (EditCommand *)command;
106 @implementation WebEditCommand
110 JSC::initializeThreading();
111 WTF::initializeMainThreadToProcessMainThread();
112 WebCoreObjCFinalizeOnMainThread(self);
115 - (id)initWithEditCommand:(PassRefPtr<EditCommand>)command
127 if (WebCoreObjCScheduleDeallocateOnMainThread([WebEditCommand class], self))
135 ASSERT_MAIN_THREAD();
140 + (WebEditCommand *)commandWithEditCommand:(PassRefPtr<EditCommand>)command
142 return [[[WebEditCommand alloc] initWithEditCommand:command] autorelease];
145 - (EditCommand *)command
147 return m_command.get();
152 @interface WebEditorUndoTarget : NSObject
156 - (void)undoEditing:(id)arg;
157 - (void)redoEditing:(id)arg;
161 @implementation WebEditorUndoTarget
163 - (void)undoEditing:(id)arg
165 ASSERT([arg isKindOfClass:[WebEditCommand class]]);
166 [arg command]->unapply();
169 - (void)redoEditing:(id)arg
171 ASSERT([arg isKindOfClass:[WebEditCommand class]]);
172 [arg command]->reapply();
177 void WebEditorClient::pageDestroyed()
182 WebEditorClient::WebEditorClient(WebView *webView)
184 , m_undoTarget([[[WebEditorUndoTarget alloc] init] autorelease])
185 , m_haveUndoRedoOperations(false)
189 WebEditorClient::~WebEditorClient()
191 #if !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
192 dismissCorrectionPanel(ReasonForDismissingCorrectionPanelIgnored);
196 bool WebEditorClient::isContinuousSpellCheckingEnabled()
198 return [m_webView isContinuousSpellCheckingEnabled];
201 void WebEditorClient::toggleContinuousSpellChecking()
203 [m_webView toggleContinuousSpellChecking:nil];
206 bool WebEditorClient::isGrammarCheckingEnabled()
208 return [m_webView isGrammarCheckingEnabled];
211 void WebEditorClient::toggleGrammarChecking()
213 [m_webView toggleGrammarChecking:nil];
216 int WebEditorClient::spellCheckerDocumentTag()
218 return [m_webView spellCheckerDocumentTag];
221 bool WebEditorClient::shouldDeleteRange(Range* range)
223 return [[m_webView _editingDelegateForwarder] webView:m_webView
224 shouldDeleteDOMRange:kit(range)];
227 bool WebEditorClient::shouldShowDeleteInterface(HTMLElement* element)
229 return [[m_webView _editingDelegateForwarder] webView:m_webView
230 shouldShowDeleteInterfaceForElement:kit(element)];
233 bool WebEditorClient::smartInsertDeleteEnabled()
235 return [m_webView smartInsertDeleteEnabled];
238 bool WebEditorClient::isSelectTrailingWhitespaceEnabled()
240 return [m_webView isSelectTrailingWhitespaceEnabled];
243 bool WebEditorClient::shouldApplyStyle(CSSStyleDeclaration* style, Range* range)
245 return [[m_webView _editingDelegateForwarder] webView:m_webView
246 shouldApplyStyle:kit(style) toElementsInDOMRange:kit(range)];
249 bool WebEditorClient::shouldMoveRangeAfterDelete(Range* range, Range* rangeToBeReplaced)
251 return [[m_webView _editingDelegateForwarder] webView:m_webView
252 shouldMoveRangeAfterDelete:kit(range) replacingRange:kit(rangeToBeReplaced)];
255 bool WebEditorClient::shouldBeginEditing(Range* range)
257 return [[m_webView _editingDelegateForwarder] webView:m_webView
258 shouldBeginEditingInDOMRange:kit(range)];
263 bool WebEditorClient::shouldEndEditing(Range* range)
265 return [[m_webView _editingDelegateForwarder] webView:m_webView
266 shouldEndEditingInDOMRange:kit(range)];
269 bool WebEditorClient::shouldInsertText(const String& text, Range* range, EditorInsertAction action)
271 WebView* webView = m_webView;
272 return [[webView _editingDelegateForwarder] webView:webView shouldInsertText:text replacingDOMRange:kit(range) givenAction:kit(action)];
275 bool WebEditorClient::shouldChangeSelectedRange(Range* fromRange, Range* toRange, EAffinity selectionAffinity, bool stillSelecting)
277 return [m_webView _shouldChangeSelectedDOMRange:kit(fromRange) toDOMRange:kit(toRange) affinity:kit(selectionAffinity) stillSelecting:stillSelecting];
280 void WebEditorClient::didBeginEditing()
282 [[NSNotificationCenter defaultCenter] postNotificationName:WebViewDidBeginEditingNotification object:m_webView];
285 void WebEditorClient::respondToChangedContents()
287 NSView <WebDocumentView> *view = [[[m_webView selectedFrame] frameView] documentView];
288 if ([view isKindOfClass:[WebHTMLView class]])
289 [(WebHTMLView *)view _updateFontPanel];
290 [[NSNotificationCenter defaultCenter] postNotificationName:WebViewDidChangeNotification object:m_webView];
293 void WebEditorClient::respondToChangedSelection()
295 [m_webView _selectionChanged];
297 // 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
298 if (!WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITHOUT_APERTURE_QUIRK) && [[[NSBundle mainBundle] bundleIdentifier] isEqualToString:@"com.apple.Aperture"])
301 [[NSNotificationCenter defaultCenter] postNotificationName:WebViewDidChangeSelectionNotification object:m_webView];
304 void WebEditorClient::didEndEditing()
306 [[NSNotificationCenter defaultCenter] postNotificationName:WebViewDidEndEditingNotification object:m_webView];
309 void WebEditorClient::didWriteSelectionToPasteboard()
311 [[m_webView _editingDelegateForwarder] webView:m_webView didWriteSelectionToPasteboard:[NSPasteboard generalPasteboard]];
314 void WebEditorClient::didSetSelectionTypesForPasteboard()
316 [[m_webView _editingDelegateForwarder] webView:m_webView didSetSelectionTypesForPasteboard:[NSPasteboard generalPasteboard]];
319 NSString *WebEditorClient::userVisibleString(NSURL *URL)
321 return [URL _web_userVisibleString];
324 NSURL *WebEditorClient::canonicalizeURL(NSURL *URL)
326 return [URL _webkit_canonicalize];
329 NSURL *WebEditorClient::canonicalizeURLString(NSString *URLString)
332 if ([URLString _webkit_looksLikeAbsoluteURL])
333 URL = [[NSURL _web_URLWithUserTypedString:URLString] _webkit_canonicalize];
337 static NSArray *createExcludedElementsForAttributedStringConversion()
339 NSArray *elements = [[NSArray alloc] initWithObjects:
340 // Omit style since we want style to be inline so the fragment can be easily inserted.
342 // Omit xml so the result is not XHTML.
344 // Omit tags that will get stripped when converted to a fragment anyway.
345 @"doctype", @"html", @"head", @"body",
346 // Omit deprecated tags.
347 @"applet", @"basefont", @"center", @"dir", @"font", @"isindex", @"menu", @"s", @"strike", @"u",
348 // Omit object so no file attachments are part of the fragment.
354 DocumentFragment* WebEditorClient::documentFragmentFromAttributedString(NSAttributedString *string, Vector<RefPtr<ArchiveResource> >& resources)
356 static NSArray *excludedElements = createExcludedElementsForAttributedStringConversion();
358 NSDictionary *dictionary = [[NSDictionary alloc] initWithObjectsAndKeys: excludedElements, NSExcludedElementsDocumentAttribute,
359 nil, @"WebResourceHandler", nil];
361 NSArray *subResources;
362 DOMDocumentFragment* fragment = [string _documentFromRange:NSMakeRange(0, [string length])
363 document:[[m_webView mainFrame] DOMDocument]
364 documentAttributes:dictionary
365 subresources:&subResources];
366 for (WebResource* resource in subResources)
367 resources.append([resource _coreResource]);
369 [dictionary release];
370 return core(fragment);
373 void WebEditorClient::setInsertionPasteboard(NSPasteboard *pasteboard)
375 [m_webView _setInsertionPasteboard:pasteboard];
379 #ifndef BUILDING_ON_LEOPARD
380 void WebEditorClient::uppercaseWord()
382 [m_webView uppercaseWord:nil];
385 void WebEditorClient::lowercaseWord()
387 [m_webView lowercaseWord:nil];
390 void WebEditorClient::capitalizeWord()
392 [m_webView capitalizeWord:nil];
395 void WebEditorClient::showSubstitutionsPanel(bool show)
397 NSPanel *spellingPanel = [[NSSpellChecker sharedSpellChecker] substitutionsPanel];
399 [spellingPanel orderFront:nil];
401 [spellingPanel orderOut:nil];
404 bool WebEditorClient::substitutionsPanelIsShowing()
406 return [[[NSSpellChecker sharedSpellChecker] substitutionsPanel] isVisible];
409 void WebEditorClient::toggleSmartInsertDelete()
411 [m_webView toggleSmartInsertDelete:nil];
414 bool WebEditorClient::isAutomaticQuoteSubstitutionEnabled()
416 return [m_webView isAutomaticQuoteSubstitutionEnabled];
419 void WebEditorClient::toggleAutomaticQuoteSubstitution()
421 [m_webView toggleAutomaticQuoteSubstitution:nil];
424 bool WebEditorClient::isAutomaticLinkDetectionEnabled()
426 return [m_webView isAutomaticLinkDetectionEnabled];
429 void WebEditorClient::toggleAutomaticLinkDetection()
431 [m_webView toggleAutomaticLinkDetection:nil];
434 bool WebEditorClient::isAutomaticDashSubstitutionEnabled()
436 return [m_webView isAutomaticDashSubstitutionEnabled];
439 void WebEditorClient::toggleAutomaticDashSubstitution()
441 [m_webView toggleAutomaticDashSubstitution:nil];
444 bool WebEditorClient::isAutomaticTextReplacementEnabled()
446 return [m_webView isAutomaticTextReplacementEnabled];
449 void WebEditorClient::toggleAutomaticTextReplacement()
451 [m_webView toggleAutomaticTextReplacement:nil];
454 bool WebEditorClient::isAutomaticSpellingCorrectionEnabled()
456 return [m_webView isAutomaticSpellingCorrectionEnabled];
459 void WebEditorClient::toggleAutomaticSpellingCorrection()
461 [m_webView toggleAutomaticSpellingCorrection:nil];
465 bool WebEditorClient::shouldInsertNode(Node *node, Range* replacingRange, EditorInsertAction givenAction)
467 return [[m_webView _editingDelegateForwarder] webView:m_webView shouldInsertNode:kit(node) replacingDOMRange:kit(replacingRange) givenAction:(WebViewInsertAction)givenAction];
470 static NSString* undoNameForEditAction(EditAction editAction)
472 switch (editAction) {
473 case EditActionUnspecified: return nil;
474 case EditActionSetColor: return UI_STRING_KEY_INTERNAL("Set Color", "Set Color (Undo action name)", "Undo action name");
475 case EditActionSetBackgroundColor: return UI_STRING_KEY_INTERNAL("Set Background Color", "Set Background Color (Undo action name)", "Undo action name");
476 case EditActionTurnOffKerning: return UI_STRING_KEY_INTERNAL("Turn Off Kerning", "Turn Off Kerning (Undo action name)", "Undo action name");
477 case EditActionTightenKerning: return UI_STRING_KEY_INTERNAL("Tighten Kerning", "Tighten Kerning (Undo action name)", "Undo action name");
478 case EditActionLoosenKerning: return UI_STRING_KEY_INTERNAL("Loosen Kerning", "Loosen Kerning (Undo action name)", "Undo action name");
479 case EditActionUseStandardKerning: return UI_STRING_KEY_INTERNAL("Use Standard Kerning", "Use Standard Kerning (Undo action name)", "Undo action name");
480 case EditActionTurnOffLigatures: return UI_STRING_KEY_INTERNAL("Turn Off Ligatures", "Turn Off Ligatures (Undo action name)", "Undo action name");
481 case EditActionUseStandardLigatures: return UI_STRING_KEY_INTERNAL("Use Standard Ligatures", "Use Standard Ligatures (Undo action name)", "Undo action name");
482 case EditActionUseAllLigatures: return UI_STRING_KEY_INTERNAL("Use All Ligatures", "Use All Ligatures (Undo action name)", "Undo action name");
483 case EditActionRaiseBaseline: return UI_STRING_KEY_INTERNAL("Raise Baseline", "Raise Baseline (Undo action name)", "Undo action name");
484 case EditActionLowerBaseline: return UI_STRING_KEY_INTERNAL("Lower Baseline", "Lower Baseline (Undo action name)", "Undo action name");
485 case EditActionSetTraditionalCharacterShape: return UI_STRING_KEY_INTERNAL("Set Traditional Character Shape", "Set Traditional Character Shape (Undo action name)", "Undo action name");
486 case EditActionSetFont: return UI_STRING_KEY_INTERNAL("Set Font", "Set Font (Undo action name)", "Undo action name");
487 case EditActionChangeAttributes: return UI_STRING_KEY_INTERNAL("Change Attributes", "Change Attributes (Undo action name)", "Undo action name");
488 case EditActionAlignLeft: return UI_STRING_KEY_INTERNAL("Align Left", "Align Left (Undo action name)", "Undo action name");
489 case EditActionAlignRight: return UI_STRING_KEY_INTERNAL("Align Right", "Align Right (Undo action name)", "Undo action name");
490 case EditActionCenter: return UI_STRING_KEY_INTERNAL("Center", "Center (Undo action name)", "Undo action name");
491 case EditActionJustify: return UI_STRING_KEY_INTERNAL("Justify", "Justify (Undo action name)", "Undo action name");
492 case EditActionSetWritingDirection: return UI_STRING_KEY_INTERNAL("Set Writing Direction", "Set Writing Direction (Undo action name)", "Undo action name");
493 case EditActionSubscript: return UI_STRING_KEY_INTERNAL("Subscript", "Subscript (Undo action name)", "Undo action name");
494 case EditActionSuperscript: return UI_STRING_KEY_INTERNAL("Superscript", "Superscript (Undo action name)", "Undo action name");
495 case EditActionUnderline: return UI_STRING_KEY_INTERNAL("Underline", "Underline (Undo action name)", "Undo action name");
496 case EditActionOutline: return UI_STRING_KEY_INTERNAL("Outline", "Outline (Undo action name)", "Undo action name");
497 case EditActionUnscript: return UI_STRING_KEY_INTERNAL("Unscript", "Unscript (Undo action name)", "Undo action name");
498 case EditActionDrag: return UI_STRING_KEY_INTERNAL("Drag", "Drag (Undo action name)", "Undo action name");
499 case EditActionCut: return UI_STRING_KEY_INTERNAL("Cut", "Cut (Undo action name)", "Undo action name");
500 case EditActionPaste: return UI_STRING_KEY_INTERNAL("Paste", "Paste (Undo action name)", "Undo action name");
501 case EditActionPasteFont: return UI_STRING_KEY_INTERNAL("Paste Font", "Paste Font (Undo action name)", "Undo action name");
502 case EditActionPasteRuler: return UI_STRING_KEY_INTERNAL("Paste Ruler", "Paste Ruler (Undo action name)", "Undo action name");
503 case EditActionTyping: return UI_STRING_KEY_INTERNAL("Typing", "Typing (Undo action name)", "Undo action name");
504 case EditActionCreateLink: return UI_STRING_KEY_INTERNAL("Create Link", "Create Link (Undo action name)", "Undo action name");
505 case EditActionUnlink: return UI_STRING_KEY_INTERNAL("Unlink", "Unlink (Undo action name)", "Undo action name");
506 case EditActionInsertList: return UI_STRING_KEY_INTERNAL("Insert List", "Insert List (Undo action name)", "Undo action name");
507 case EditActionFormatBlock: return UI_STRING_KEY_INTERNAL("Formatting", "Format Block (Undo action name)", "Undo action name");
508 case EditActionIndent: return UI_STRING_KEY_INTERNAL("Indent", "Indent (Undo action name)", "Undo action name");
509 case EditActionOutdent: return UI_STRING_KEY_INTERNAL("Outdent", "Outdent (Undo action name)", "Undo action name");
514 void WebEditorClient::registerCommandForUndoOrRedo(PassRefPtr<EditCommand> cmd, bool isRedo)
518 NSUndoManager *undoManager = [m_webView undoManager];
519 NSString *actionName = undoNameForEditAction(cmd->editingAction());
520 WebEditCommand *command = [WebEditCommand commandWithEditCommand:cmd];
521 [undoManager registerUndoWithTarget:m_undoTarget.get() selector:(isRedo ? @selector(redoEditing:) : @selector(undoEditing:)) object:command];
523 [undoManager setActionName:actionName];
524 m_haveUndoRedoOperations = YES;
527 void WebEditorClient::registerCommandForUndo(PassRefPtr<EditCommand> cmd)
529 registerCommandForUndoOrRedo(cmd, false);
532 void WebEditorClient::registerCommandForRedo(PassRefPtr<EditCommand> cmd)
534 registerCommandForUndoOrRedo(cmd, true);
537 void WebEditorClient::clearUndoRedoOperations()
539 if (m_haveUndoRedoOperations) {
540 // workaround for <rdar://problem/4645507> NSUndoManager dies
541 // with uncaught exception when undo items cleared while
543 NSUndoManager *undoManager = [m_webView undoManager];
544 int groupingLevel = [undoManager groupingLevel];
545 for (int i = 0; i < groupingLevel; ++i)
546 [undoManager endUndoGrouping];
548 [undoManager removeAllActionsWithTarget:m_undoTarget.get()];
550 for (int i = 0; i < groupingLevel; ++i)
551 [undoManager beginUndoGrouping];
553 m_haveUndoRedoOperations = NO;
557 bool WebEditorClient::canCopyCut(Frame*, bool defaultValue) const
562 bool WebEditorClient::canPaste(Frame*, bool defaultValue) const
567 bool WebEditorClient::canUndo() const
569 return [[m_webView undoManager] canUndo];
572 bool WebEditorClient::canRedo() const
574 return [[m_webView undoManager] canRedo];
577 void WebEditorClient::undo()
580 [[m_webView undoManager] undo];
583 void WebEditorClient::redo()
586 [[m_webView undoManager] redo];
589 void WebEditorClient::handleKeyboardEvent(KeyboardEvent* event)
591 Frame* frame = event->target()->toNode()->document()->frame();
592 WebHTMLView *webHTMLView = [[kit(frame) frameView] documentView];
593 if ([webHTMLView _interpretKeyEvent:event savingCommands:NO])
594 event->setDefaultHandled();
597 void WebEditorClient::handleInputMethodKeydown(KeyboardEvent* event)
599 Frame* frame = event->target()->toNode()->document()->frame();
600 WebHTMLView *webHTMLView = [[kit(frame) frameView] documentView];
601 if ([webHTMLView _interpretKeyEvent:event savingCommands:YES])
602 event->setDefaultHandled();
605 #define FormDelegateLog(ctrl) LOG(FormDelegate, "control=%@", ctrl)
607 void WebEditorClient::textFieldDidBeginEditing(Element* element)
609 if (!element->hasTagName(inputTag))
612 DOMHTMLInputElement* inputElement = kit(static_cast<HTMLInputElement*>(element));
613 FormDelegateLog(inputElement);
614 CallFormDelegate(m_webView, @selector(textFieldDidBeginEditing:inFrame:), inputElement, kit(element->document()->frame()));
617 void WebEditorClient::textFieldDidEndEditing(Element* element)
619 if (!element->hasTagName(inputTag))
622 DOMHTMLInputElement* inputElement = kit(static_cast<HTMLInputElement*>(element));
623 FormDelegateLog(inputElement);
624 CallFormDelegate(m_webView, @selector(textFieldDidEndEditing:inFrame:), inputElement, kit(element->document()->frame()));
627 void WebEditorClient::textDidChangeInTextField(Element* element)
629 if (!element->hasTagName(inputTag))
632 if (!UserTypingGestureIndicator::processingUserTypingGesture() || UserTypingGestureIndicator::focusedElementAtGestureStart() != element)
635 DOMHTMLInputElement* inputElement = kit(static_cast<HTMLInputElement*>(element));
636 FormDelegateLog(inputElement);
637 CallFormDelegate(m_webView, @selector(textDidChangeInTextField:inFrame:), inputElement, kit(element->document()->frame()));
640 static SEL selectorForKeyEvent(KeyboardEvent* event)
642 // FIXME: This helper function is for the auto-fill code so we can pass a selector to the form delegate.
643 // Eventually, we should move all of the auto-fill code down to WebKit and remove the need for this function by
644 // not relying on the selector in the new implementation.
645 // The key identifiers are from <http://www.w3.org/TR/DOM-Level-3-Events/keyset.html#KeySet-Set>
646 const String& key = event->keyIdentifier();
648 return @selector(moveUp:);
650 return @selector(moveDown:);
652 return @selector(cancel:);
653 if (key == "U+0009") {
654 if (event->shiftKey())
655 return @selector(insertBacktab:);
656 return @selector(insertTab:);
659 return @selector(insertNewline:);
663 bool WebEditorClient::doTextFieldCommandFromEvent(Element* element, KeyboardEvent* event)
665 if (!element->hasTagName(inputTag))
668 DOMHTMLInputElement* inputElement = kit(static_cast<HTMLInputElement*>(element));
669 FormDelegateLog(inputElement);
670 if (SEL commandSelector = selectorForKeyEvent(event))
671 return CallFormDelegateReturningBoolean(NO, m_webView, @selector(textField:doCommandBySelector:inFrame:), inputElement, commandSelector, kit(element->document()->frame()));
675 void WebEditorClient::textWillBeDeletedInTextField(Element* element)
677 if (!element->hasTagName(inputTag))
680 DOMHTMLInputElement* inputElement = kit(static_cast<HTMLInputElement*>(element));
681 FormDelegateLog(inputElement);
682 // We're using the deleteBackward selector for all deletion operations since the autofill code treats all deletions the same way.
683 CallFormDelegateReturningBoolean(NO, m_webView, @selector(textField:doCommandBySelector:inFrame:), inputElement, @selector(deleteBackward:), kit(element->document()->frame()));
686 void WebEditorClient::textDidChangeInTextArea(Element* element)
688 if (!element->hasTagName(textareaTag))
691 DOMHTMLTextAreaElement* textAreaElement = kit(static_cast<HTMLTextAreaElement*>(element));
692 FormDelegateLog(textAreaElement);
693 CallFormDelegate(m_webView, @selector(textDidChangeInTextArea:inFrame:), textAreaElement, kit(element->document()->frame()));
696 void WebEditorClient::ignoreWordInSpellDocument(const String& text)
698 [[NSSpellChecker sharedSpellChecker] ignoreWord:text
699 inSpellDocumentWithTag:spellCheckerDocumentTag()];
702 void WebEditorClient::learnWord(const String& text)
704 [[NSSpellChecker sharedSpellChecker] learnWord:text];
707 void WebEditorClient::checkSpellingOfString(const UChar* text, int length, int* misspellingLocation, int* misspellingLength)
709 NSString* textString = [[NSString alloc] initWithCharactersNoCopy:const_cast<UChar*>(text) length:length freeWhenDone:NO];
710 NSRange range = [[NSSpellChecker sharedSpellChecker] checkSpellingOfString:textString startingAt:0 language:nil wrap:NO inSpellDocumentWithTag:spellCheckerDocumentTag() wordCount:NULL];
711 [textString release];
712 if (misspellingLocation) {
713 // WebCore expects -1 to represent "not found"
714 if (range.location == NSNotFound)
715 *misspellingLocation = -1;
717 *misspellingLocation = range.location;
720 if (misspellingLength)
721 *misspellingLength = range.length;
724 String WebEditorClient::getAutoCorrectSuggestionForMisspelledWord(const String& inputWord)
726 // This method can be implemented using customized algorithms for the particular browser.
727 // Currently, it computes an empty string.
731 void WebEditorClient::checkGrammarOfString(const UChar* text, int length, Vector<GrammarDetail>& details, int* badGrammarLocation, int* badGrammarLength)
733 NSArray *grammarDetails;
734 NSString* textString = [[NSString alloc] initWithCharactersNoCopy:const_cast<UChar*>(text) length:length freeWhenDone:NO];
735 NSRange range = [[NSSpellChecker sharedSpellChecker] checkGrammarOfString:textString startingAt:0 language:nil wrap:NO inSpellDocumentWithTag:spellCheckerDocumentTag() details:&grammarDetails];
736 [textString release];
737 if (badGrammarLocation)
738 // WebCore expects -1 to represent "not found"
739 *badGrammarLocation = (range.location == NSNotFound) ? -1 : static_cast<int>(range.location);
740 if (badGrammarLength)
741 *badGrammarLength = range.length;
742 for (NSDictionary *detail in grammarDetails) {
744 GrammarDetail grammarDetail;
745 NSValue *detailRangeAsNSValue = [detail objectForKey:NSGrammarRange];
746 ASSERT(detailRangeAsNSValue);
747 NSRange detailNSRange = [detailRangeAsNSValue rangeValue];
748 ASSERT(detailNSRange.location != NSNotFound);
749 ASSERT(detailNSRange.length > 0);
750 grammarDetail.location = detailNSRange.location;
751 grammarDetail.length = detailNSRange.length;
752 grammarDetail.userDescription = [detail objectForKey:NSGrammarUserDescription];
753 NSArray *guesses = [detail objectForKey:NSGrammarCorrections];
754 for (NSString *guess in guesses)
755 grammarDetail.guesses.append(String(guess));
756 details.append(grammarDetail);
760 #ifndef BUILDING_ON_LEOPARD
761 static Vector<TextCheckingResult> core(NSArray *incomingResults, TextCheckingTypeMask checkingTypes)
763 Vector<TextCheckingResult> results;
765 for (NSTextCheckingResult *incomingResult in incomingResults) {
766 NSRange resultRange = [incomingResult range];
767 NSTextCheckingType resultType = [incomingResult resultType];
768 ASSERT(resultRange.location != NSNotFound);
769 ASSERT(resultRange.length > 0);
770 if (NSTextCheckingTypeSpelling == resultType && 0 != (checkingTypes & NSTextCheckingTypeSpelling)) {
771 TextCheckingResult result;
772 result.type = TextCheckingTypeSpelling;
773 result.location = resultRange.location;
774 result.length = resultRange.length;
775 results.append(result);
776 } else if (NSTextCheckingTypeGrammar == resultType && 0 != (checkingTypes & NSTextCheckingTypeGrammar)) {
777 TextCheckingResult result;
778 NSArray *details = [incomingResult grammarDetails];
779 result.type = TextCheckingTypeGrammar;
780 result.location = resultRange.location;
781 result.length = resultRange.length;
782 for (NSDictionary *incomingDetail in details) {
783 ASSERT(incomingDetail);
784 GrammarDetail detail;
785 NSValue *detailRangeAsNSValue = [incomingDetail objectForKey:NSGrammarRange];
786 ASSERT(detailRangeAsNSValue);
787 NSRange detailNSRange = [detailRangeAsNSValue rangeValue];
788 ASSERT(detailNSRange.location != NSNotFound);
789 ASSERT(detailNSRange.length > 0);
790 detail.location = detailNSRange.location;
791 detail.length = detailNSRange.length;
792 detail.userDescription = [incomingDetail objectForKey:NSGrammarUserDescription];
793 NSArray *guesses = [incomingDetail objectForKey:NSGrammarCorrections];
794 for (NSString *guess in guesses)
795 detail.guesses.append(String(guess));
796 result.details.append(detail);
798 results.append(result);
799 } else if (NSTextCheckingTypeLink == resultType && 0 != (checkingTypes & NSTextCheckingTypeLink)) {
800 TextCheckingResult result;
801 result.type = TextCheckingTypeLink;
802 result.location = resultRange.location;
803 result.length = resultRange.length;
804 result.replacement = [[incomingResult URL] absoluteString];
805 results.append(result);
806 } else if (NSTextCheckingTypeQuote == resultType && 0 != (checkingTypes & NSTextCheckingTypeQuote)) {
807 TextCheckingResult result;
808 result.type = TextCheckingTypeQuote;
809 result.location = resultRange.location;
810 result.length = resultRange.length;
811 result.replacement = [incomingResult replacementString];
812 results.append(result);
813 } else if (NSTextCheckingTypeDash == resultType && 0 != (checkingTypes & NSTextCheckingTypeDash)) {
814 TextCheckingResult result;
815 result.type = TextCheckingTypeDash;
816 result.location = resultRange.location;
817 result.length = resultRange.length;
818 result.replacement = [incomingResult replacementString];
819 results.append(result);
820 } else if (NSTextCheckingTypeReplacement == resultType && 0 != (checkingTypes & NSTextCheckingTypeReplacement)) {
821 TextCheckingResult result;
822 result.type = TextCheckingTypeReplacement;
823 result.location = resultRange.location;
824 result.length = resultRange.length;
825 result.replacement = [incomingResult replacementString];
826 results.append(result);
827 } else if (NSTextCheckingTypeCorrection == resultType && 0 != (checkingTypes & NSTextCheckingTypeCorrection)) {
828 TextCheckingResult result;
829 result.type = TextCheckingTypeCorrection;
830 result.location = resultRange.location;
831 result.length = resultRange.length;
832 result.replacement = [incomingResult replacementString];
833 results.append(result);
841 void WebEditorClient::checkTextOfParagraph(const UChar* text, int length, TextCheckingTypeMask checkingTypes, Vector<TextCheckingResult>& results)
843 #ifndef BUILDING_ON_LEOPARD
844 NSString *textString = [[NSString alloc] initWithCharactersNoCopy:const_cast<UChar*>(text) length:length freeWhenDone:NO];
845 NSArray *incomingResults = [[NSSpellChecker sharedSpellChecker] checkString:textString range:NSMakeRange(0, [textString length]) types:(checkingTypes|NSTextCheckingTypeOrthography) options:nil inSpellDocumentWithTag:spellCheckerDocumentTag() orthography:NULL wordCount:NULL];
846 [textString release];
847 results = core(incomingResults, checkingTypes);
851 void WebEditorClient::updateSpellingUIWithGrammarString(const String& badGrammarPhrase, const GrammarDetail& grammarDetail)
853 NSMutableArray* corrections = [NSMutableArray array];
854 for (unsigned i = 0; i < grammarDetail.guesses.size(); i++) {
855 NSString* guess = grammarDetail.guesses[i];
856 [corrections addObject:guess];
858 NSRange grammarRange = NSMakeRange(grammarDetail.location, grammarDetail.length);
859 NSString* grammarUserDescription = grammarDetail.userDescription;
860 NSMutableDictionary* grammarDetailDict = [NSDictionary dictionaryWithObjectsAndKeys:[NSValue valueWithRange:grammarRange], NSGrammarRange, grammarUserDescription, NSGrammarUserDescription, corrections, NSGrammarCorrections, nil];
862 [[NSSpellChecker sharedSpellChecker] updateSpellingPanelWithGrammarString:badGrammarPhrase detail:grammarDetailDict];
865 #if !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
866 void WebEditorClient::showCorrectionPanel(CorrectionPanelInfo::PanelType panelType, const FloatRect& boundingBoxOfReplacedString, const String& replacedString, const String& replacementString, const Vector<String>& alternativeReplacementStrings)
868 m_correctionPanel.show(m_webView, panelType, boundingBoxOfReplacedString, replacedString, replacementString, alternativeReplacementStrings);
871 void WebEditorClient::dismissCorrectionPanel(ReasonForDismissingCorrectionPanel reasonForDismissing)
873 m_correctionPanel.dismiss(reasonForDismissing);
876 String WebEditorClient::dismissCorrectionPanelSoon(ReasonForDismissingCorrectionPanel reasonForDismissing)
878 return m_correctionPanel.dismiss(reasonForDismissing);
881 void WebEditorClient::recordAutocorrectionResponse(EditorClient::AutocorrectionResponseType responseType, const String& replacedString, const String& replacementString)
883 NSCorrectionResponse response = responseType == EditorClient::AutocorrectionReverted ? NSCorrectionResponseReverted : NSCorrectionResponseEdited;
884 CorrectionPanel::recordAutocorrectionResponse(m_webView, response, replacedString, replacementString);
888 void WebEditorClient::updateSpellingUIWithMisspelledWord(const String& misspelledWord)
890 [[NSSpellChecker sharedSpellChecker] updateSpellingPanelWithMisspelledWord:misspelledWord];
893 void WebEditorClient::showSpellingUI(bool show)
895 NSPanel *spellingPanel = [[NSSpellChecker sharedSpellChecker] spellingPanel];
897 [spellingPanel orderFront:nil];
899 [spellingPanel orderOut:nil];
902 bool WebEditorClient::spellingUIIsShowing()
904 return [[[NSSpellChecker sharedSpellChecker] spellingPanel] isVisible];
907 void WebEditorClient::getGuessesForWord(const String& word, const String& context, Vector<String>& guesses) {
909 #if !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
910 NSString* language = nil;
911 NSOrthography* orthography = nil;
912 NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
913 if (context.length()) {
914 [checker checkString:context range:NSMakeRange(0, context.length()) types:NSTextCheckingTypeOrthography options:0 inSpellDocumentWithTag:spellCheckerDocumentTag() orthography:&orthography wordCount:0];
915 language = [checker languageForWordRange:NSMakeRange(0, context.length()) inString:context orthography:orthography];
917 NSArray* stringsArray = [checker guessesForWordRange:NSMakeRange(0, word.length()) inString:word language:language inSpellDocumentWithTag:spellCheckerDocumentTag()];
919 NSArray* stringsArray = [[NSSpellChecker sharedSpellChecker] guessesForWord:word];
921 unsigned count = [stringsArray count];
924 NSEnumerator* enumerator = [stringsArray objectEnumerator];
926 while ((string = [enumerator nextObject]) != nil)
927 guesses.append(string);
931 void WebEditorClient::willSetInputMethodState()
935 void WebEditorClient::setInputMethodState(bool)
939 #ifndef BUILDING_ON_LEOPARD
940 @interface WebEditorSpellCheckResponder : NSObject
942 WebCore::SpellChecker* _sender;
944 TextCheckingTypeMask _types;
945 RetainPtr<NSArray> _results;
947 - (id)initWithSender:(WebCore::SpellChecker*)sender sequence:(int)sequence types:(WebCore::TextCheckingTypeMask)types results:(NSArray*)results;
951 @implementation WebEditorSpellCheckResponder
952 - (id)initWithSender:(WebCore::SpellChecker*)sender sequence:(int)sequence types:(WebCore::TextCheckingTypeMask)types results:(NSArray*)results
958 _sequence = sequence;
966 _sender->didCheck(_sequence, core(_results.get(), _types));
972 void WebEditorClient::requestCheckingOfString(WebCore::SpellChecker* sender, int sequence, WebCore::TextCheckingTypeMask checkingTypes, const String& text)
974 #ifndef BUILDING_ON_LEOPARD
975 NSRange range = NSMakeRange(0, text.length());
976 NSRunLoop* currentLoop = [NSRunLoop currentRunLoop];
977 [[NSSpellChecker sharedSpellChecker] requestCheckingOfString:text range:range types:NSTextCheckingAllSystemTypes options:0 inSpellDocumentWithTag:0
978 completionHandler:^(NSInteger, NSArray* results, NSOrthography*, NSInteger) {
979 [currentLoop performSelector:@selector(perform)
980 target:[[[WebEditorSpellCheckResponder alloc] initWithSender:sender sequence:sequence types:checkingTypes results:results] autorelease]
981 argument:nil order:0 modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]];