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