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/HTMLInputElement.h>
58 #import <WebCore/HTMLNames.h>
59 #import <WebCore/HTMLTextAreaElement.h>
60 #import <WebCore/KeyboardEvent.h>
61 #import <WebCore/LegacyWebArchive.h>
62 #import <WebCore/Page.h>
63 #import <WebCore/PlatformKeyboardEvent.h>
64 #import <WebCore/RunLoop.h>
65 #import <WebCore/Settings.h>
66 #import <WebCore/SpellChecker.h>
67 #import <WebCore/StylePropertySet.h>
68 #import <WebCore/UndoStep.h>
69 #import <WebCore/UserTypingGestureIndicator.h>
70 #import <WebCore/WebCoreObjCExtras.h>
71 #import <runtime/InitializeThreading.h>
72 #import <wtf/MainThread.h>
73 #import <wtf/PassRefPtr.h>
74 #import <wtf/text/WTFString.h>
76 using namespace WebCore;
78 using namespace HTMLNames;
80 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
81 @interface NSSpellChecker (WebNSSpellCheckerDetails)
82 - (NSString *)languageForWordRange:(NSRange)range inString:(NSString *)string orthography:(NSOrthography *)orthography;
86 @interface NSAttributedString (WebNSAttributedStringDetails)
87 - (id)_initWithDOMRange:(DOMRange*)range;
88 - (DOMDocumentFragment*)_documentFromRange:(NSRange)range document:(DOMDocument*)document documentAttributes:(NSDictionary *)dict subresources:(NSArray **)subresources;
91 static WebViewInsertAction kit(EditorInsertAction coreAction)
93 return static_cast<WebViewInsertAction>(coreAction);
96 static const int InvalidCorrectionPanelTag = 0;
99 @interface WebUndoStep : NSObject
101 RefPtr<UndoStep> m_step;
104 + (WebUndoStep *)stepWithUndoStep:(PassRefPtr<UndoStep>)step;
109 @implementation WebUndoStep
113 JSC::initializeThreading();
114 WTF::initializeMainThreadToProcessMainThread();
115 WebCore::RunLoop::initializeMainRunLoop();
116 WebCoreObjCFinalizeOnMainThread(self);
119 - (id)initWithUndoStep:(PassRefPtr<UndoStep>)step
131 if (WebCoreObjCScheduleDeallocateOnMainThread([WebUndoStep class], self))
139 ASSERT_MAIN_THREAD();
144 + (WebUndoStep *)stepWithUndoStep:(PassRefPtr<UndoStep>)step
146 return [[[WebUndoStep alloc] initWithUndoStep:step] autorelease];
156 @interface WebEditorUndoTarget : NSObject
160 - (void)undoEditing:(id)arg;
161 - (void)redoEditing:(id)arg;
165 @implementation WebEditorUndoTarget
167 - (void)undoEditing:(id)arg
169 ASSERT([arg isKindOfClass:[WebUndoStep class]]);
170 [arg step]->unapply();
173 - (void)redoEditing:(id)arg
175 ASSERT([arg isKindOfClass:[WebUndoStep class]]);
176 [arg step]->reapply();
181 void WebEditorClient::pageDestroyed()
186 WebEditorClient::WebEditorClient(WebView *webView)
188 , m_undoTarget([[[WebEditorUndoTarget alloc] init] autorelease])
189 , m_haveUndoRedoOperations(false)
193 WebEditorClient::~WebEditorClient()
197 bool WebEditorClient::isContinuousSpellCheckingEnabled()
199 return [m_webView isContinuousSpellCheckingEnabled];
202 void WebEditorClient::toggleContinuousSpellChecking()
204 [m_webView toggleContinuousSpellChecking:nil];
207 bool WebEditorClient::isGrammarCheckingEnabled()
209 return [m_webView isGrammarCheckingEnabled];
212 void WebEditorClient::toggleGrammarChecking()
214 [m_webView toggleGrammarChecking:nil];
217 int WebEditorClient::spellCheckerDocumentTag()
219 return [m_webView spellCheckerDocumentTag];
222 bool WebEditorClient::shouldDeleteRange(Range* range)
224 return [[m_webView _editingDelegateForwarder] webView:m_webView
225 shouldDeleteDOMRange:kit(range)];
228 #if ENABLE(DELETION_UI)
229 bool WebEditorClient::shouldShowDeleteInterface(HTMLElement* element)
231 return [[m_webView _editingDelegateForwarder] webView:m_webView
232 shouldShowDeleteInterfaceForElement:kit(element)];
236 bool WebEditorClient::smartInsertDeleteEnabled()
238 Page* page = [m_webView page];
241 return page->settings()->smartInsertDeleteEnabled();
244 bool WebEditorClient::isSelectTrailingWhitespaceEnabled()
246 Page* page = [m_webView page];
249 return page->settings()->selectTrailingWhitespaceEnabled();
252 bool WebEditorClient::shouldApplyStyle(StylePropertySet* style, Range* range)
254 RefPtr<MutableStylePropertySet> mutableStyle = style->isMutable() ? static_cast<MutableStylePropertySet*>(style) : style->mutableCopy();
255 return [[m_webView _editingDelegateForwarder] webView:m_webView
256 shouldApplyStyle:kit(mutableStyle->ensureCSSStyleDeclaration()) toElementsInDOMRange:kit(range)];
259 bool WebEditorClient::shouldMoveRangeAfterDelete(Range* range, Range* rangeToBeReplaced)
261 return [[m_webView _editingDelegateForwarder] webView:m_webView
262 shouldMoveRangeAfterDelete:kit(range) replacingRange:kit(rangeToBeReplaced)];
265 bool WebEditorClient::shouldBeginEditing(Range* range)
267 return [[m_webView _editingDelegateForwarder] webView:m_webView
268 shouldBeginEditingInDOMRange:kit(range)];
273 bool WebEditorClient::shouldEndEditing(Range* range)
275 return [[m_webView _editingDelegateForwarder] webView:m_webView
276 shouldEndEditingInDOMRange:kit(range)];
279 bool WebEditorClient::shouldInsertText(const String& text, Range* range, EditorInsertAction action)
281 WebView* webView = m_webView;
282 return [[webView _editingDelegateForwarder] webView:webView shouldInsertText:text replacingDOMRange:kit(range) givenAction:kit(action)];
285 bool WebEditorClient::shouldChangeSelectedRange(Range* fromRange, Range* toRange, EAffinity selectionAffinity, bool stillSelecting)
287 return [m_webView _shouldChangeSelectedDOMRange:kit(fromRange) toDOMRange:kit(toRange) affinity:kit(selectionAffinity) stillSelecting:stillSelecting];
290 void WebEditorClient::didBeginEditing()
292 [[NSNotificationCenter defaultCenter] postNotificationName:WebViewDidBeginEditingNotification object:m_webView];
295 void WebEditorClient::respondToChangedContents()
297 NSView <WebDocumentView> *view = [[[m_webView selectedFrame] frameView] documentView];
298 if ([view isKindOfClass:[WebHTMLView class]])
299 [(WebHTMLView *)view _updateFontPanel];
300 [[NSNotificationCenter defaultCenter] postNotificationName:WebViewDidChangeNotification object:m_webView];
303 void WebEditorClient::respondToChangedSelection(Frame* frame)
305 NSView<WebDocumentView> *documentView = [[kit(frame) frameView] documentView];
306 if ([documentView isKindOfClass:[WebHTMLView class]])
307 [(WebHTMLView *)documentView _selectionChanged];
309 // FIXME: This quirk is needed due to <rdar://problem/5009625> - We can phase it out once Aperture can adopt the new behavior on their end
310 if (!WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITHOUT_APERTURE_QUIRK) && [[[NSBundle mainBundle] bundleIdentifier] isEqualToString:@"com.apple.Aperture"])
313 [[NSNotificationCenter defaultCenter] postNotificationName:WebViewDidChangeSelectionNotification object:m_webView];
316 void WebEditorClient::didEndEditing()
318 [[NSNotificationCenter defaultCenter] postNotificationName:WebViewDidEndEditingNotification object:m_webView];
321 void WebEditorClient::didWriteSelectionToPasteboard()
323 [[m_webView _editingDelegateForwarder] webView:m_webView didWriteSelectionToPasteboard:[NSPasteboard generalPasteboard]];
326 void WebEditorClient::willWriteSelectionToPasteboard(WebCore::Range*)
328 // Not implemented WebKit, only WebKit2.
331 void WebEditorClient::getClientPasteboardDataForRange(WebCore::Range*, Vector<String>& pasteboardTypes, Vector<RefPtr<WebCore::SharedBuffer> >& pasteboardData)
333 // Not implemented WebKit, only WebKit2.
336 void WebEditorClient::didSetSelectionTypesForPasteboard()
338 [[m_webView _editingDelegateForwarder] webView:m_webView didSetSelectionTypesForPasteboard:[NSPasteboard generalPasteboard]];
341 NSString *WebEditorClient::userVisibleString(NSURL *URL)
343 return [URL _web_userVisibleString];
346 NSURL *WebEditorClient::canonicalizeURL(NSURL *URL)
348 return [URL _webkit_canonicalize];
351 NSURL *WebEditorClient::canonicalizeURLString(NSString *URLString)
354 if ([URLString _webkit_looksLikeAbsoluteURL])
355 URL = [[NSURL _web_URLWithUserTypedString:URLString] _webkit_canonicalize];
359 static NSArray *createExcludedElementsForAttributedStringConversion()
361 NSArray *elements = [[NSArray alloc] initWithObjects:
362 // Omit style since we want style to be inline so the fragment can be easily inserted.
364 // Omit xml so the result is not XHTML.
366 // Omit tags that will get stripped when converted to a fragment anyway.
367 @"doctype", @"html", @"head", @"body",
368 // Omit deprecated tags.
369 @"applet", @"basefont", @"center", @"dir", @"font", @"isindex", @"menu", @"s", @"strike", @"u",
370 // Omit object so no file attachments are part of the fragment.
376 DocumentFragment* WebEditorClient::documentFragmentFromAttributedString(NSAttributedString *string, Vector<RefPtr<ArchiveResource> >& resources)
378 static NSArray *excludedElements = createExcludedElementsForAttributedStringConversion();
380 NSDictionary *dictionary = [[NSDictionary alloc] initWithObjectsAndKeys: excludedElements, NSExcludedElementsDocumentAttribute,
381 nil, @"WebResourceHandler", nil];
383 NSArray *subResources;
384 DOMDocumentFragment* fragment = [string _documentFromRange:NSMakeRange(0, [string length])
385 document:[[m_webView mainFrame] DOMDocument]
386 documentAttributes:dictionary
387 subresources:&subResources];
388 for (WebResource* resource in subResources)
389 resources.append([resource _coreResource]);
391 [dictionary release];
392 return core(fragment);
395 void WebEditorClient::setInsertionPasteboard(const String& pasteboardName)
397 NSPasteboard *pasteboard = pasteboardName.isEmpty() ? nil : [NSPasteboard pasteboardWithName:pasteboardName];
398 [m_webView _setInsertionPasteboard:pasteboard];
402 void WebEditorClient::uppercaseWord()
404 [m_webView uppercaseWord:nil];
407 void WebEditorClient::lowercaseWord()
409 [m_webView lowercaseWord:nil];
412 void WebEditorClient::capitalizeWord()
414 [m_webView capitalizeWord:nil];
418 #if USE(AUTOMATIC_TEXT_REPLACEMENT)
419 void WebEditorClient::showSubstitutionsPanel(bool show)
421 NSPanel *spellingPanel = [[NSSpellChecker sharedSpellChecker] substitutionsPanel];
423 [spellingPanel orderFront:nil];
425 [spellingPanel orderOut:nil];
428 bool WebEditorClient::substitutionsPanelIsShowing()
430 return [[[NSSpellChecker sharedSpellChecker] substitutionsPanel] isVisible];
433 void WebEditorClient::toggleSmartInsertDelete()
435 [m_webView toggleSmartInsertDelete:nil];
438 bool WebEditorClient::isAutomaticQuoteSubstitutionEnabled()
440 return [m_webView isAutomaticQuoteSubstitutionEnabled];
443 void WebEditorClient::toggleAutomaticQuoteSubstitution()
445 [m_webView toggleAutomaticQuoteSubstitution:nil];
448 bool WebEditorClient::isAutomaticLinkDetectionEnabled()
450 return [m_webView isAutomaticLinkDetectionEnabled];
453 void WebEditorClient::toggleAutomaticLinkDetection()
455 [m_webView toggleAutomaticLinkDetection:nil];
458 bool WebEditorClient::isAutomaticDashSubstitutionEnabled()
460 return [m_webView isAutomaticDashSubstitutionEnabled];
463 void WebEditorClient::toggleAutomaticDashSubstitution()
465 [m_webView toggleAutomaticDashSubstitution:nil];
468 bool WebEditorClient::isAutomaticTextReplacementEnabled()
470 return [m_webView isAutomaticTextReplacementEnabled];
473 void WebEditorClient::toggleAutomaticTextReplacement()
475 [m_webView toggleAutomaticTextReplacement:nil];
478 bool WebEditorClient::isAutomaticSpellingCorrectionEnabled()
480 return [m_webView isAutomaticSpellingCorrectionEnabled];
483 void WebEditorClient::toggleAutomaticSpellingCorrection()
485 [m_webView toggleAutomaticSpellingCorrection:nil];
487 #endif // USE(AUTOMATIC_TEXT_REPLACEMENT)
489 bool WebEditorClient::shouldInsertNode(Node *node, Range* replacingRange, EditorInsertAction givenAction)
491 return [[m_webView _editingDelegateForwarder] webView:m_webView shouldInsertNode:kit(node) replacingDOMRange:kit(replacingRange) givenAction:(WebViewInsertAction)givenAction];
494 static NSString* undoNameForEditAction(EditAction editAction)
496 switch (editAction) {
497 case EditActionUnspecified: return nil;
498 case EditActionSetColor: return UI_STRING_KEY_INTERNAL("Set Color", "Set Color (Undo action name)", "Undo action name");
499 case EditActionSetBackgroundColor: return UI_STRING_KEY_INTERNAL("Set Background Color", "Set Background Color (Undo action name)", "Undo action name");
500 case EditActionTurnOffKerning: return UI_STRING_KEY_INTERNAL("Turn Off Kerning", "Turn Off Kerning (Undo action name)", "Undo action name");
501 case EditActionTightenKerning: return UI_STRING_KEY_INTERNAL("Tighten Kerning", "Tighten Kerning (Undo action name)", "Undo action name");
502 case EditActionLoosenKerning: return UI_STRING_KEY_INTERNAL("Loosen Kerning", "Loosen Kerning (Undo action name)", "Undo action name");
503 case EditActionUseStandardKerning: return UI_STRING_KEY_INTERNAL("Use Standard Kerning", "Use Standard Kerning (Undo action name)", "Undo action name");
504 case EditActionTurnOffLigatures: return UI_STRING_KEY_INTERNAL("Turn Off Ligatures", "Turn Off Ligatures (Undo action name)", "Undo action name");
505 case EditActionUseStandardLigatures: return UI_STRING_KEY_INTERNAL("Use Standard Ligatures", "Use Standard Ligatures (Undo action name)", "Undo action name");
506 case EditActionUseAllLigatures: return UI_STRING_KEY_INTERNAL("Use All Ligatures", "Use All Ligatures (Undo action name)", "Undo action name");
507 case EditActionRaiseBaseline: return UI_STRING_KEY_INTERNAL("Raise Baseline", "Raise Baseline (Undo action name)", "Undo action name");
508 case EditActionLowerBaseline: return UI_STRING_KEY_INTERNAL("Lower Baseline", "Lower Baseline (Undo action name)", "Undo action name");
509 case EditActionSetTraditionalCharacterShape: return UI_STRING_KEY_INTERNAL("Set Traditional Character Shape", "Set Traditional Character Shape (Undo action name)", "Undo action name");
510 case EditActionSetFont: return UI_STRING_KEY_INTERNAL("Set Font", "Set Font (Undo action name)", "Undo action name");
511 case EditActionChangeAttributes: return UI_STRING_KEY_INTERNAL("Change Attributes", "Change Attributes (Undo action name)", "Undo action name");
512 case EditActionAlignLeft: return UI_STRING_KEY_INTERNAL("Align Left", "Align Left (Undo action name)", "Undo action name");
513 case EditActionAlignRight: return UI_STRING_KEY_INTERNAL("Align Right", "Align Right (Undo action name)", "Undo action name");
514 case EditActionCenter: return UI_STRING_KEY_INTERNAL("Center", "Center (Undo action name)", "Undo action name");
515 case EditActionJustify: return UI_STRING_KEY_INTERNAL("Justify", "Justify (Undo action name)", "Undo action name");
516 case EditActionSetWritingDirection: return UI_STRING_KEY_INTERNAL("Set Writing Direction", "Set Writing Direction (Undo action name)", "Undo action name");
517 case EditActionSubscript: return UI_STRING_KEY_INTERNAL("Subscript", "Subscript (Undo action name)", "Undo action name");
518 case EditActionSuperscript: return UI_STRING_KEY_INTERNAL("Superscript", "Superscript (Undo action name)", "Undo action name");
519 case EditActionUnderline: return UI_STRING_KEY_INTERNAL("Underline", "Underline (Undo action name)", "Undo action name");
520 case EditActionOutline: return UI_STRING_KEY_INTERNAL("Outline", "Outline (Undo action name)", "Undo action name");
521 case EditActionUnscript: return UI_STRING_KEY_INTERNAL("Unscript", "Unscript (Undo action name)", "Undo action name");
522 case EditActionDrag: return UI_STRING_KEY_INTERNAL("Drag", "Drag (Undo action name)", "Undo action name");
523 case EditActionCut: return UI_STRING_KEY_INTERNAL("Cut", "Cut (Undo action name)", "Undo action name");
524 case EditActionPaste: return UI_STRING_KEY_INTERNAL("Paste", "Paste (Undo action name)", "Undo action name");
525 case EditActionPasteFont: return UI_STRING_KEY_INTERNAL("Paste Font", "Paste Font (Undo action name)", "Undo action name");
526 case EditActionPasteRuler: return UI_STRING_KEY_INTERNAL("Paste Ruler", "Paste Ruler (Undo action name)", "Undo action name");
527 case EditActionTyping: return UI_STRING_KEY_INTERNAL("Typing", "Typing (Undo action name)", "Undo action name");
528 case EditActionCreateLink: return UI_STRING_KEY_INTERNAL("Create Link", "Create Link (Undo action name)", "Undo action name");
529 case EditActionUnlink: return UI_STRING_KEY_INTERNAL("Unlink", "Unlink (Undo action name)", "Undo action name");
530 case EditActionInsertList: return UI_STRING_KEY_INTERNAL("Insert List", "Insert List (Undo action name)", "Undo action name");
531 case EditActionFormatBlock: return UI_STRING_KEY_INTERNAL("Formatting", "Format Block (Undo action name)", "Undo action name");
532 case EditActionIndent: return UI_STRING_KEY_INTERNAL("Indent", "Indent (Undo action name)", "Undo action name");
533 case EditActionOutdent: return UI_STRING_KEY_INTERNAL("Outdent", "Outdent (Undo action name)", "Undo action name");
534 case EditActionBold: return UI_STRING_KEY_INTERNAL("Bold", "Bold (Undo action name)", "Undo action name");
535 case EditActionItalics: return UI_STRING_KEY_INTERNAL("Italics", "Italics (Undo action name)", "Undo action name");
540 void WebEditorClient::registerUndoOrRedoStep(PassRefPtr<UndoStep> step, bool isRedo)
544 NSUndoManager *undoManager = [m_webView undoManager];
545 NSString *actionName = undoNameForEditAction(step->editingAction());
546 WebUndoStep *webEntry = [WebUndoStep stepWithUndoStep:step];
547 [undoManager beginUndoGrouping];
548 [undoManager registerUndoWithTarget:m_undoTarget.get() selector:(isRedo ? @selector(redoEditing:) : @selector(undoEditing:)) object:webEntry];
550 [undoManager setActionName:actionName];
551 [undoManager endUndoGrouping];
552 m_haveUndoRedoOperations = YES;
555 void WebEditorClient::registerUndoStep(PassRefPtr<UndoStep> cmd)
557 registerUndoOrRedoStep(cmd, false);
560 void WebEditorClient::registerRedoStep(PassRefPtr<UndoStep> cmd)
562 registerUndoOrRedoStep(cmd, true);
565 void WebEditorClient::clearUndoRedoOperations()
567 if (m_haveUndoRedoOperations) {
568 // workaround for <rdar://problem/4645507> NSUndoManager dies
569 // with uncaught exception when undo items cleared while
571 NSUndoManager *undoManager = [m_webView undoManager];
572 int groupingLevel = [undoManager groupingLevel];
573 for (int i = 0; i < groupingLevel; ++i)
574 [undoManager endUndoGrouping];
576 [undoManager removeAllActionsWithTarget:m_undoTarget.get()];
578 for (int i = 0; i < groupingLevel; ++i)
579 [undoManager beginUndoGrouping];
581 m_haveUndoRedoOperations = NO;
585 bool WebEditorClient::canCopyCut(Frame*, bool defaultValue) const
590 bool WebEditorClient::canPaste(Frame*, bool defaultValue) const
595 bool WebEditorClient::canUndo() const
597 return [[m_webView undoManager] canUndo];
600 bool WebEditorClient::canRedo() const
602 return [[m_webView undoManager] canRedo];
605 void WebEditorClient::undo()
608 [[m_webView undoManager] undo];
611 void WebEditorClient::redo()
614 [[m_webView undoManager] redo];
617 void WebEditorClient::handleKeyboardEvent(KeyboardEvent* event)
619 Frame* frame = event->target()->toNode()->document()->frame();
620 WebHTMLView *webHTMLView = [[kit(frame) frameView] documentView];
621 if ([webHTMLView _interpretKeyEvent:event savingCommands:NO])
622 event->setDefaultHandled();
625 void WebEditorClient::handleInputMethodKeydown(KeyboardEvent* event)
627 Frame* frame = event->target()->toNode()->document()->frame();
628 WebHTMLView *webHTMLView = [[kit(frame) frameView] documentView];
629 if ([webHTMLView _interpretKeyEvent:event savingCommands:YES])
630 event->setDefaultHandled();
633 #define FormDelegateLog(ctrl) LOG(FormDelegate, "control=%@", ctrl)
635 void WebEditorClient::textFieldDidBeginEditing(Element* element)
637 if (!element->hasTagName(inputTag))
640 DOMHTMLInputElement* inputElement = kit(static_cast<HTMLInputElement*>(element));
641 FormDelegateLog(inputElement);
642 CallFormDelegate(m_webView, @selector(textFieldDidBeginEditing:inFrame:), inputElement, kit(element->document()->frame()));
645 void WebEditorClient::textFieldDidEndEditing(Element* element)
647 if (!element->hasTagName(inputTag))
650 DOMHTMLInputElement* inputElement = kit(static_cast<HTMLInputElement*>(element));
651 FormDelegateLog(inputElement);
652 CallFormDelegate(m_webView, @selector(textFieldDidEndEditing:inFrame:), inputElement, kit(element->document()->frame()));
655 void WebEditorClient::textDidChangeInTextField(Element* element)
657 if (!element->hasTagName(inputTag))
660 if (!UserTypingGestureIndicator::processingUserTypingGesture() || UserTypingGestureIndicator::focusedElementAtGestureStart() != element)
663 DOMHTMLInputElement* inputElement = kit(static_cast<HTMLInputElement*>(element));
664 FormDelegateLog(inputElement);
665 CallFormDelegate(m_webView, @selector(textDidChangeInTextField:inFrame:), inputElement, kit(element->document()->frame()));
668 static SEL selectorForKeyEvent(KeyboardEvent* event)
670 // FIXME: This helper function is for the auto-fill code so we can pass a selector to the form delegate.
671 // Eventually, we should move all of the auto-fill code down to WebKit and remove the need for this function by
672 // not relying on the selector in the new implementation.
673 // The key identifiers are from <http://www.w3.org/TR/DOM-Level-3-Events/keyset.html#KeySet-Set>
674 const String& key = event->keyIdentifier();
676 return @selector(moveUp:);
678 return @selector(moveDown:);
680 return @selector(cancel:);
681 if (key == "U+0009") {
682 if (event->shiftKey())
683 return @selector(insertBacktab:);
684 return @selector(insertTab:);
687 return @selector(insertNewline:);
691 bool WebEditorClient::doTextFieldCommandFromEvent(Element* element, KeyboardEvent* event)
693 if (!element->hasTagName(inputTag))
696 DOMHTMLInputElement* inputElement = kit(static_cast<HTMLInputElement*>(element));
697 FormDelegateLog(inputElement);
698 if (SEL commandSelector = selectorForKeyEvent(event))
699 return CallFormDelegateReturningBoolean(NO, m_webView, @selector(textField:doCommandBySelector:inFrame:), inputElement, commandSelector, kit(element->document()->frame()));
703 void WebEditorClient::textWillBeDeletedInTextField(Element* element)
705 if (!element->hasTagName(inputTag))
708 DOMHTMLInputElement* inputElement = kit(static_cast<HTMLInputElement*>(element));
709 FormDelegateLog(inputElement);
710 // We're using the deleteBackward selector for all deletion operations since the autofill code treats all deletions the same way.
711 CallFormDelegateReturningBoolean(NO, m_webView, @selector(textField:doCommandBySelector:inFrame:), inputElement, @selector(deleteBackward:), kit(element->document()->frame()));
714 void WebEditorClient::textDidChangeInTextArea(Element* element)
716 if (!element->hasTagName(textareaTag))
719 DOMHTMLTextAreaElement* textAreaElement = kit(static_cast<HTMLTextAreaElement*>(element));
720 FormDelegateLog(textAreaElement);
721 CallFormDelegate(m_webView, @selector(textDidChangeInTextArea:inFrame:), textAreaElement, kit(element->document()->frame()));
724 bool WebEditorClient::shouldEraseMarkersAfterChangeSelection(TextCheckingType type) const
726 // This prevents erasing spelling markers on OS X Lion or later to match AppKit on these Mac OS X versions.
727 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
728 return type != TextCheckingTypeSpelling;
734 void WebEditorClient::ignoreWordInSpellDocument(const String& text)
736 [[NSSpellChecker sharedSpellChecker] ignoreWord:text
737 inSpellDocumentWithTag:spellCheckerDocumentTag()];
740 void WebEditorClient::learnWord(const String& text)
742 [[NSSpellChecker sharedSpellChecker] learnWord:text];
745 void WebEditorClient::checkSpellingOfString(const UChar* text, int length, int* misspellingLocation, int* misspellingLength)
747 NSString* textString = [[NSString alloc] initWithCharactersNoCopy:const_cast<UChar*>(text) length:length freeWhenDone:NO];
748 NSRange range = [[NSSpellChecker sharedSpellChecker] checkSpellingOfString:textString startingAt:0 language:nil wrap:NO inSpellDocumentWithTag:spellCheckerDocumentTag() wordCount:NULL];
749 [textString release];
750 if (misspellingLocation) {
751 // WebCore expects -1 to represent "not found"
752 if (range.location == NSNotFound)
753 *misspellingLocation = -1;
755 *misspellingLocation = range.location;
758 if (misspellingLength)
759 *misspellingLength = range.length;
762 String WebEditorClient::getAutoCorrectSuggestionForMisspelledWord(const String& inputWord)
764 // This method can be implemented using customized algorithms for the particular browser.
765 // Currently, it computes an empty string.
769 void WebEditorClient::checkGrammarOfString(const UChar* text, int length, Vector<GrammarDetail>& details, int* badGrammarLocation, int* badGrammarLength)
771 NSArray *grammarDetails;
772 NSString* textString = [[NSString alloc] initWithCharactersNoCopy:const_cast<UChar*>(text) length:length freeWhenDone:NO];
773 NSRange range = [[NSSpellChecker sharedSpellChecker] checkGrammarOfString:textString startingAt:0 language:nil wrap:NO inSpellDocumentWithTag:spellCheckerDocumentTag() details:&grammarDetails];
774 [textString release];
775 if (badGrammarLocation)
776 // WebCore expects -1 to represent "not found"
777 *badGrammarLocation = (range.location == NSNotFound) ? -1 : static_cast<int>(range.location);
778 if (badGrammarLength)
779 *badGrammarLength = range.length;
780 for (NSDictionary *detail in grammarDetails) {
782 GrammarDetail grammarDetail;
783 NSValue *detailRangeAsNSValue = [detail objectForKey:NSGrammarRange];
784 ASSERT(detailRangeAsNSValue);
785 NSRange detailNSRange = [detailRangeAsNSValue rangeValue];
786 ASSERT(detailNSRange.location != NSNotFound);
787 ASSERT(detailNSRange.length > 0);
788 grammarDetail.location = detailNSRange.location;
789 grammarDetail.length = detailNSRange.length;
790 grammarDetail.userDescription = [detail objectForKey:NSGrammarUserDescription];
791 NSArray *guesses = [detail objectForKey:NSGrammarCorrections];
792 for (NSString *guess in guesses)
793 grammarDetail.guesses.append(String(guess));
794 details.append(grammarDetail);
798 static Vector<TextCheckingResult> core(NSArray *incomingResults, TextCheckingTypeMask checkingTypes)
800 Vector<TextCheckingResult> results;
802 for (NSTextCheckingResult *incomingResult in incomingResults) {
803 NSRange resultRange = [incomingResult range];
804 NSTextCheckingType resultType = [incomingResult resultType];
805 ASSERT(resultRange.location != NSNotFound);
806 ASSERT(resultRange.length > 0);
807 if (NSTextCheckingTypeSpelling == resultType && 0 != (checkingTypes & NSTextCheckingTypeSpelling)) {
808 TextCheckingResult result;
809 result.type = TextCheckingTypeSpelling;
810 result.location = resultRange.location;
811 result.length = resultRange.length;
812 results.append(result);
813 } else if (NSTextCheckingTypeGrammar == resultType && 0 != (checkingTypes & NSTextCheckingTypeGrammar)) {
814 TextCheckingResult result;
815 NSArray *details = [incomingResult grammarDetails];
816 result.type = TextCheckingTypeGrammar;
817 result.location = resultRange.location;
818 result.length = resultRange.length;
819 for (NSDictionary *incomingDetail in details) {
820 ASSERT(incomingDetail);
821 GrammarDetail detail;
822 NSValue *detailRangeAsNSValue = [incomingDetail objectForKey:NSGrammarRange];
823 ASSERT(detailRangeAsNSValue);
824 NSRange detailNSRange = [detailRangeAsNSValue rangeValue];
825 ASSERT(detailNSRange.location != NSNotFound);
826 ASSERT(detailNSRange.length > 0);
827 detail.location = detailNSRange.location;
828 detail.length = detailNSRange.length;
829 detail.userDescription = [incomingDetail objectForKey:NSGrammarUserDescription];
830 NSArray *guesses = [incomingDetail objectForKey:NSGrammarCorrections];
831 for (NSString *guess in guesses)
832 detail.guesses.append(String(guess));
833 result.details.append(detail);
835 results.append(result);
836 } else if (NSTextCheckingTypeLink == resultType && 0 != (checkingTypes & NSTextCheckingTypeLink)) {
837 TextCheckingResult result;
838 result.type = TextCheckingTypeLink;
839 result.location = resultRange.location;
840 result.length = resultRange.length;
841 result.replacement = [[incomingResult URL] absoluteString];
842 results.append(result);
843 } else if (NSTextCheckingTypeQuote == resultType && 0 != (checkingTypes & NSTextCheckingTypeQuote)) {
844 TextCheckingResult result;
845 result.type = TextCheckingTypeQuote;
846 result.location = resultRange.location;
847 result.length = resultRange.length;
848 result.replacement = [incomingResult replacementString];
849 results.append(result);
850 } else if (NSTextCheckingTypeDash == resultType && 0 != (checkingTypes & NSTextCheckingTypeDash)) {
851 TextCheckingResult result;
852 result.type = TextCheckingTypeDash;
853 result.location = resultRange.location;
854 result.length = resultRange.length;
855 result.replacement = [incomingResult replacementString];
856 results.append(result);
857 } else if (NSTextCheckingTypeReplacement == resultType && 0 != (checkingTypes & NSTextCheckingTypeReplacement)) {
858 TextCheckingResult result;
859 result.type = TextCheckingTypeReplacement;
860 result.location = resultRange.location;
861 result.length = resultRange.length;
862 result.replacement = [incomingResult replacementString];
863 results.append(result);
864 } else if (NSTextCheckingTypeCorrection == resultType && 0 != (checkingTypes & NSTextCheckingTypeCorrection)) {
865 TextCheckingResult result;
866 result.type = TextCheckingTypeCorrection;
867 result.location = resultRange.location;
868 result.length = resultRange.length;
869 result.replacement = [incomingResult replacementString];
870 results.append(result);
877 void WebEditorClient::checkTextOfParagraph(const UChar* text, int length, TextCheckingTypeMask checkingTypes, Vector<TextCheckingResult>& results)
879 NSString *textString = [[NSString alloc] initWithCharactersNoCopy:const_cast<UChar*>(text) length:length freeWhenDone:NO];
880 NSArray *incomingResults = [[NSSpellChecker sharedSpellChecker] checkString:textString range:NSMakeRange(0, [textString length]) types:(checkingTypes|NSTextCheckingTypeOrthography) options:nil inSpellDocumentWithTag:spellCheckerDocumentTag() orthography:NULL wordCount:NULL];
881 [textString release];
882 results = core(incomingResults, checkingTypes);
885 void WebEditorClient::updateSpellingUIWithGrammarString(const String& badGrammarPhrase, const GrammarDetail& grammarDetail)
887 NSMutableArray* corrections = [NSMutableArray array];
888 for (unsigned i = 0; i < grammarDetail.guesses.size(); i++) {
889 NSString* guess = grammarDetail.guesses[i];
890 [corrections addObject:guess];
892 NSRange grammarRange = NSMakeRange(grammarDetail.location, grammarDetail.length);
893 NSString* grammarUserDescription = grammarDetail.userDescription;
894 NSDictionary* grammarDetailDict = [NSDictionary dictionaryWithObjectsAndKeys:[NSValue valueWithRange:grammarRange], NSGrammarRange, grammarUserDescription, NSGrammarUserDescription, corrections, NSGrammarCorrections, nil];
896 [[NSSpellChecker sharedSpellChecker] updateSpellingPanelWithGrammarString:badGrammarPhrase detail:grammarDetailDict];
899 void WebEditorClient::updateSpellingUIWithMisspelledWord(const String& misspelledWord)
901 [[NSSpellChecker sharedSpellChecker] updateSpellingPanelWithMisspelledWord:misspelledWord];
904 void WebEditorClient::showSpellingUI(bool show)
906 NSPanel *spellingPanel = [[NSSpellChecker sharedSpellChecker] spellingPanel];
908 [spellingPanel orderFront:nil];
910 [spellingPanel orderOut:nil];
913 bool WebEditorClient::spellingUIIsShowing()
915 return [[[NSSpellChecker sharedSpellChecker] spellingPanel] isVisible];
918 void WebEditorClient::getGuessesForWord(const String& word, const String& context, Vector<String>& guesses) {
920 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
921 NSString* language = nil;
922 NSOrthography* orthography = nil;
923 NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
924 if (context.length()) {
925 [checker checkString:context range:NSMakeRange(0, context.length()) types:NSTextCheckingTypeOrthography options:0 inSpellDocumentWithTag:spellCheckerDocumentTag() orthography:&orthography wordCount:0];
926 language = [checker languageForWordRange:NSMakeRange(0, context.length()) inString:context orthography:orthography];
928 NSArray* stringsArray = [checker guessesForWordRange:NSMakeRange(0, word.length()) inString:word language:language inSpellDocumentWithTag:spellCheckerDocumentTag()];
930 NSArray* stringsArray = [[NSSpellChecker sharedSpellChecker] guessesForWord:word];
932 unsigned count = [stringsArray count];
935 NSEnumerator* enumerator = [stringsArray objectEnumerator];
937 while ((string = [enumerator nextObject]) != nil)
938 guesses.append(string);
942 void WebEditorClient::willSetInputMethodState()
946 void WebEditorClient::setInputMethodState(bool)
950 @interface WebEditorSpellCheckResponder : NSObject
952 WebEditorClient* _client;
954 RetainPtr<NSArray> _results;
956 - (id)initWithClient:(WebEditorClient*)client sequence:(int)sequence results:(NSArray*)results;
960 @implementation WebEditorSpellCheckResponder
961 - (id)initWithClient:(WebEditorClient*)client sequence:(int)sequence results:(NSArray*)results
967 _sequence = sequence;
974 _client->didCheckSucceed(_sequence, _results.get());
979 void WebEditorClient::didCheckSucceed(int sequence, NSArray* results)
981 ASSERT_UNUSED(sequence, sequence == m_textCheckingRequest->data().sequence());
982 m_textCheckingRequest->didSucceed(core(results, m_textCheckingRequest->data().mask()));
983 m_textCheckingRequest.clear();
986 void WebEditorClient::requestCheckingOfString(PassRefPtr<WebCore::TextCheckingRequest> request)
988 ASSERT(!m_textCheckingRequest);
989 m_textCheckingRequest = request;
991 int sequence = m_textCheckingRequest->data().sequence();
992 NSRange range = NSMakeRange(0, m_textCheckingRequest->data().text().length());
993 NSRunLoop* currentLoop = [NSRunLoop currentRunLoop];
994 [[NSSpellChecker sharedSpellChecker] requestCheckingOfString:m_textCheckingRequest->data().text() range:range types:NSTextCheckingAllSystemTypes options:0 inSpellDocumentWithTag:0
995 completionHandler:^(NSInteger, NSArray* results, NSOrthography*, NSInteger) {
996 [currentLoop performSelector:@selector(perform)
997 target:[[[WebEditorSpellCheckResponder alloc] initWithClient:this sequence:sequence results:results] autorelease]
998 argument:nil order:0 modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]];