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