1/* 2 * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. 3 * (C) 2006, 2007 Graham Dennis (graham.dennis@gmail.com) 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 "WebHTMLView.h" 31 32#import "DOMCSSStyleDeclarationInternal.h" 33#import "DOMDocumentFragmentInternal.h" 34#import "DOMDocumentInternal.h" 35#import "DOMNodeInternal.h" 36#import "DOMRangeInternal.h" 37#import "WebArchive.h" 38#import "WebClipView.h" 39#import "WebDOMOperationsInternal.h" 40#import "WebDataSourceInternal.h" 41#import "WebDefaultUIDelegate.h" 42#import "WebDelegateImplementationCaching.h" 43#import "WebDocumentInternal.h" 44#import "WebDynamicScrollBarsView.h" 45#import "WebEditingDelegate.h" 46#import "WebElementDictionary.h" 47#import "WebFrameInternal.h" 48#import "WebFramePrivate.h" 49#import "WebFrameViewInternal.h" 50#import "WebHTMLRepresentationPrivate.h" 51#import "WebHTMLViewInternal.h" 52#import "WebKitLogging.h" 53#import "WebKitNSStringExtras.h" 54#import "WebKitVersionChecks.h" 55#import "WebLocalizableStrings.h" 56#import "WebNSAttributedStringExtras.h" 57#import "WebNSEventExtras.h" 58#import "WebNSFileManagerExtras.h" 59#import "WebNSImageExtras.h" 60#import "WebNSObjectExtras.h" 61#import "WebNSPasteboardExtras.h" 62#import "WebNSPrintOperationExtras.h" 63#import "WebNSURLExtras.h" 64#import "WebNSViewExtras.h" 65#import "WebNetscapePluginView.h" 66#import "WebNodeHighlight.h" 67#import "WebPluginController.h" 68#import "WebPreferences.h" 69#import "WebPreferencesPrivate.h" 70#import "WebResourcePrivate.h" 71#import "WebStringTruncator.h" 72#import "WebTextCompletionController.h" 73#import "WebTypesInternal.h" 74#import "WebUIDelegatePrivate.h" 75#import "WebViewInternal.h" 76#import <AppKit/NSAccessibility.h> 77#import <ApplicationServices/ApplicationServices.h> 78#import <WebCore/CSSMutableStyleDeclaration.h> 79#import <WebCore/CachedImage.h> 80#import <WebCore/CachedResourceClient.h> 81#import <WebCore/ColorMac.h> 82#import <WebCore/ContextMenu.h> 83#import <WebCore/ContextMenuController.h> 84#import <WebCore/Document.h> 85#import <WebCore/DocumentFragment.h> 86#import <WebCore/DragController.h> 87#import <WebCore/Editor.h> 88#import <WebCore/EditorDeleteAction.h> 89#import <WebCore/Element.h> 90#import <WebCore/EventHandler.h> 91#import <WebCore/ExceptionHandlers.h> 92#import <WebCore/FloatRect.h> 93#import <WebCore/FocusController.h> 94#import <WebCore/Frame.h> 95#import <WebCore/FrameLoader.h> 96#import <WebCore/FrameView.h> 97#import <WebCore/HTMLNames.h> 98#import <WebCore/HitTestResult.h> 99#import <WebCore/Image.h> 100#import <WebCore/KeyboardEvent.h> 101#import <WebCore/LegacyWebArchive.h> 102#import <WebCore/MIMETypeRegistry.h> 103#import <WebCore/Page.h> 104#import <WebCore/PlatformKeyboardEvent.h> 105#import <WebCore/Range.h> 106#import <WebCore/SelectionController.h> 107#import <WebCore/SharedBuffer.h> 108#import <WebCore/SimpleFontData.h> 109#import <WebCore/Text.h> 110#import <WebCore/WebCoreObjCExtras.h> 111#import <WebCore/WebFontCache.h> 112#import <WebCore/markup.h> 113#import <WebKit/DOM.h> 114#import <WebKit/DOMExtensions.h> 115#import <WebKit/DOMPrivate.h> 116#import <WebKitSystemInterface.h> 117#import <dlfcn.h> 118#import <limits> 119#import <runtime/InitializeThreading.h> 120 121#if USE(ACCELERATED_COMPOSITING) 122#import <QuartzCore/QuartzCore.h> 123#endif 124 125using namespace WebCore; 126using namespace HTMLNames; 127using namespace WTF; 128 129@interface NSWindow (BorderViewAccess) 130- (NSView*)_web_borderView; 131@end 132 133@implementation NSWindow (BorderViewAccess) 134- (NSView*)_web_borderView 135{ 136 return _borderView; 137} 138@end 139 140@interface WebResponderChainSink : NSResponder { 141 NSResponder* _lastResponderInChain; 142 BOOL _receivedUnhandledCommand; 143} 144- (id)initWithResponderChain:(NSResponder *)chain; 145- (void)detach; 146- (BOOL)receivedUnhandledCommand; 147@end 148 149static IMP oldSetCursorIMP = NULL; 150 151#ifdef BUILDING_ON_TIGER 152 153static IMP oldResetCursorRectsIMP = NULL; 154static BOOL canSetCursor = YES; 155 156static void resetCursorRects(NSWindow* self, SEL cmd) 157{ 158 NSPoint point = [self mouseLocationOutsideOfEventStream]; 159 NSView* view = [[self _web_borderView] hitTest:point]; 160 if ([view isKindOfClass:[WebHTMLView class]]) { 161 WebHTMLView *htmlView = (WebHTMLView*)view; 162 NSPoint localPoint = [htmlView convertPoint:point fromView:nil]; 163 NSDictionary *dict = [htmlView elementAtPoint:localPoint allowShadowContent:NO]; 164 DOMElement *element = [dict objectForKey:WebElementDOMNodeKey]; 165 if (![element isKindOfClass:[DOMHTMLAppletElement class]] && ![element isKindOfClass:[DOMHTMLObjectElement class]] && 166 ![element isKindOfClass:[DOMHTMLEmbedElement class]]) 167 canSetCursor = NO; 168 } 169 oldResetCursorRectsIMP(self, cmd); 170 canSetCursor = YES; 171} 172 173static void setCursor(NSCursor* self, SEL cmd) 174{ 175 if (canSetCursor) 176 oldSetCursorIMP(self, cmd); 177} 178 179#else 180 181static void setCursor(NSWindow* self, SEL cmd, NSPoint point) 182{ 183 NSView* view = [[self _web_borderView] hitTest:point]; 184 if ([view isKindOfClass:[WebHTMLView class]]) { 185 WebHTMLView *htmlView = (WebHTMLView*)view; 186 NSPoint localPoint = [htmlView convertPoint:point fromView:nil]; 187 NSDictionary *dict = [htmlView elementAtPoint:localPoint allowShadowContent:NO]; 188 DOMElement *element = [dict objectForKey:WebElementDOMNodeKey]; 189 if (![element isKindOfClass:[DOMHTMLAppletElement class]] && ![element isKindOfClass:[DOMHTMLObjectElement class]] && 190 ![element isKindOfClass:[DOMHTMLEmbedElement class]]) 191 return; 192 } 193 oldSetCursorIMP(self, cmd, point); 194} 195 196#endif 197 198extern "C" { 199 200// Need to declare these attribute names because AppKit exports them but does not make them available in API or SPI headers. 201 202extern NSString *NSMarkedClauseSegmentAttributeName; 203extern NSString *NSTextInputReplacementRangeAttributeName; 204 205} 206 207@interface NSView (WebNSViewDetails) 208- (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView; 209- (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect; 210- (void)_recursive:(BOOL)recurse displayRectIgnoringOpacity:(NSRect)displayRect inContext:(NSGraphicsContext *)context topView:(BOOL)topView; 211- (NSRect)_dirtyRect; 212- (void)_setDrawsOwnDescendants:(BOOL)drawsOwnDescendants; 213- (void)_propagateDirtyRectsToOpaqueAncestors; 214- (void)_windowChangedKeyState; 215@end 216 217@interface NSApplication (WebNSApplicationDetails) 218- (void)speakString:(NSString *)string; 219@end 220 221@interface NSWindow (WebNSWindowDetails) 222- (id)_newFirstResponderAfterResigning; 223@end 224 225@interface NSAttributedString (WebNSAttributedStringDetails) 226- (id)_initWithDOMRange:(DOMRange *)range; 227- (DOMDocumentFragment *)_documentFromRange:(NSRange)range document:(DOMDocument *)document documentAttributes:(NSDictionary *)dict subresources:(NSArray **)subresources; 228@end 229 230@interface NSSpellChecker (WebNSSpellCheckerDetails) 231- (void)learnWord:(NSString *)word; 232@end 233 234// By imaging to a width a little wider than the available pixels, 235// thin pages will be scaled down a little, matching the way they 236// print in IE and Camino. This lets them use fewer sheets than they 237// would otherwise, which is presumably why other browsers do this. 238// Wide pages will be scaled down more than this. 239#define PrintingMinimumShrinkFactor 1.25f 240 241// This number determines how small we are willing to reduce the page content 242// in order to accommodate the widest line. If the page would have to be 243// reduced smaller to make the widest line fit, we just clip instead (this 244// behavior matches MacIE and Mozilla, at least) 245#define PrintingMaximumShrinkFactor 2.0f 246 247// This number determines how short the last printed page of a multi-page print session 248// can be before we try to shrink the scale in order to reduce the number of pages, and 249// thus eliminate the orphan. 250#define LastPrintedPageOrphanRatio 0.1f 251 252// This number determines the amount the scale factor is adjusted to try to eliminate orphans. 253// It has no direct mathematical relationship to LastPrintedPageOrphanRatio, due to variable 254// numbers of pages, logic to avoid breaking elements, and CSS-supplied hard page breaks. 255#define PrintingOrphanShrinkAdjustment 1.1f 256 257#define AUTOSCROLL_INTERVAL 0.1f 258 259#define DRAG_LABEL_BORDER_X 4.0f 260//Keep border_y in synch with DragController::LinkDragBorderInset 261#define DRAG_LABEL_BORDER_Y 2.0f 262#define DRAG_LABEL_RADIUS 5.0f 263#define DRAG_LABEL_BORDER_Y_OFFSET 2.0f 264 265#define MIN_DRAG_LABEL_WIDTH_BEFORE_CLIP 120.0f 266#define MAX_DRAG_LABEL_WIDTH 320.0f 267 268#define DRAG_LINK_LABEL_FONT_SIZE 11.0f 269#define DRAG_LINK_URL_FONT_SIZE 10.0f 270 271// Any non-zero value will do, but using something recognizable might help us debug some day. 272#define TRACKING_RECT_TAG 0xBADFACE 273 274// FIXME: This constant is copied from AppKit's _NXSmartPaste constant. 275#define WebSmartPastePboardType @"NeXT smart paste pasteboard type" 276 277#define STANDARD_WEIGHT 5 278#define MIN_BOLD_WEIGHT 7 279#define STANDARD_BOLD_WEIGHT 9 280 281// Fake URL scheme. 282#define WebDataProtocolScheme @"webkit-fake-url" 283 284// <rdar://problem/4985524> References to WebCoreScrollView as a subview of a WebHTMLView may be present 285// in some NIB files, so NSUnarchiver must be still able to look up this now-unused class. 286@interface WebCoreScrollView : NSScrollView 287@end 288 289@implementation WebCoreScrollView 290@end 291 292// if YES, do the standard NSView hit test (which can't give the right result when HTML overlaps a view) 293static BOOL forceNSViewHitTest; 294 295// if YES, do the "top WebHTMLView" hit test (which we'd like to do all the time but can't because of Java requirements [see bug 4349721]) 296static BOOL forceWebHTMLViewHitTest; 297 298static WebHTMLView *lastHitView; 299 300// We need this to be able to safely reference the CachedImage for the promised drag data 301static CachedResourceClient* promisedDataClient() 302{ 303 static CachedResourceClient* staticCachedResourceClient = new CachedResourceClient; 304 return staticCachedResourceClient; 305} 306 307@interface WebHTMLView (WebHTMLViewFileInternal) 308- (BOOL)_imageExistsAtPaths:(NSArray *)paths; 309- (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard inContext:(DOMRange *)context allowPlainText:(BOOL)allowPlainText; 310- (NSString *)_plainTextFromPasteboard:(NSPasteboard *)pasteboard; 311- (void)_pasteWithPasteboard:(NSPasteboard *)pasteboard allowPlainText:(BOOL)allowPlainText; 312- (void)_pasteAsPlainTextWithPasteboard:(NSPasteboard *)pasteboard; 313- (void)_removeMouseMovedObserverUnconditionally; 314- (void)_removeSuperviewObservers; 315- (void)_removeWindowObservers; 316- (BOOL)_shouldInsertFragment:(DOMDocumentFragment *)fragment replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action; 317- (BOOL)_shouldInsertText:(NSString *)text replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action; 318- (BOOL)_shouldReplaceSelectionWithText:(NSString *)text givenAction:(WebViewInsertAction)action; 319- (float)_calculatePrintHeight; 320- (DOMRange *)_selectedRange; 321- (BOOL)_shouldDeleteRange:(DOMRange *)range; 322- (NSView *)_hitViewForEvent:(NSEvent *)event; 323- (void)_writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard cachedAttributedString:(NSAttributedString *)attributedString; 324- (DOMRange *)_documentRange; 325- (void)_setMouseDownEvent:(NSEvent *)event; 326- (WebHTMLView *)_topHTMLView; 327- (BOOL)_isTopHTMLView; 328- (void)_web_setPrintingModeRecursive; 329- (void)_web_setPrintingModeRecursiveAndAdjustViewSize; 330- (void)_web_clearPrintingModeRecursive; 331@end 332 333#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 334 335@interface WebHTMLView (WebHTMLViewTextCheckingInternal) 336- (void)orderFrontSubstitutionsPanel:(id)sender; 337- (BOOL)smartInsertDeleteEnabled; 338- (void)setSmartInsertDeleteEnabled:(BOOL)flag; 339- (void)toggleSmartInsertDelete:(id)sender; 340- (BOOL)isAutomaticQuoteSubstitutionEnabled; 341- (void)setAutomaticQuoteSubstitutionEnabled:(BOOL)flag; 342- (void)toggleAutomaticQuoteSubstitution:(id)sender; 343- (BOOL)isAutomaticLinkDetectionEnabled; 344- (void)setAutomaticLinkDetectionEnabled:(BOOL)flag; 345- (void)toggleAutomaticLinkDetection:(id)sender; 346- (BOOL)isAutomaticDashSubstitutionEnabled; 347- (void)setAutomaticDashSubstitutionEnabled:(BOOL)flag; 348- (void)toggleAutomaticDashSubstitution:(id)sender; 349- (BOOL)isAutomaticTextReplacementEnabled; 350- (void)setAutomaticTextReplacementEnabled:(BOOL)flag; 351- (void)toggleAutomaticTextReplacement:(id)sender; 352- (BOOL)isAutomaticSpellingCorrectionEnabled; 353- (void)setAutomaticSpellingCorrectionEnabled:(BOOL)flag; 354- (void)toggleAutomaticSpellingCorrection:(id)sender; 355@end 356 357#endif 358 359@interface WebHTMLView (WebForwardDeclaration) // FIXME: Put this in a normal category and stop doing the forward declaration trick. 360- (void)_setPrinting:(BOOL)printing minimumPageWidth:(float)minPageWidth maximumPageWidth:(float)maxPageWidth adjustViewSize:(BOOL)adjustViewSize; 361@end 362 363@class NSTextInputContext; 364@interface NSResponder (AppKitDetails) 365- (NSTextInputContext *)inputContext; 366@end 367 368@interface NSObject (NSTextInputContextDetails) 369- (BOOL)wantsToHandleMouseEvents; 370- (BOOL)handleMouseEvent:(NSEvent *)event; 371@end 372 373@interface WebHTMLView (WebNSTextInputSupport) <NSTextInput> 374- (void)_updateSelectionForInputManager; 375@end 376 377@interface WebHTMLView (WebEditingStyleSupport) 378- (DOMCSSStyleDeclaration *)_emptyStyle; 379- (NSString *)_colorAsString:(NSColor *)color; 380@end 381 382@interface NSView (WebHTMLViewFileInternal) 383- (void)_web_addDescendantWebHTMLViewsToArray:(NSMutableArray *) array; 384@end 385 386@interface NSMutableDictionary (WebHTMLViewFileInternal) 387- (void)_web_setObjectIfNotNil:(id)object forKey:(id)key; 388@end 389 390struct WebHTMLViewInterpretKeyEventsParameters { 391 KeyboardEvent* event; 392 BOOL eventWasHandled; 393 BOOL shouldSaveCommand; 394 // The Input Method may consume an event and not tell us, in 395 // which case we should not bubble the event up the DOM 396 BOOL consumedByIM; 397}; 398 399@interface WebHTMLViewPrivate : NSObject { 400@public 401 BOOL closed; 402 BOOL needsToApplyStyles; 403 BOOL ignoringMouseDraggedEvents; 404 BOOL printing; 405 BOOL avoidingPrintOrphan; 406 BOOL observingMouseMovedNotifications; 407 BOOL observingSuperviewNotifications; 408 BOOL observingWindowNotifications; 409 410 id savedSubviews; 411 BOOL subviewsSetAside; 412 413#if USE(ACCELERATED_COMPOSITING) 414 NSView *layerHostingView; 415#endif 416 417 NSEvent *mouseDownEvent; // Kept after handling the event. 418 BOOL handlingMouseDownEvent; 419 NSEvent *keyDownEvent; // Kept after handling the event. 420 421 // A WebHTMLView has a single input context, but we return nil when in non-editable content to avoid making input methods do their work. 422 // This state is saved each time selection changes, because computing it causes style recalc, which is not always safe to do. 423 BOOL exposeInputContext; 424 425 NSPoint lastScrollPosition; 426 427 WebPluginController *pluginController; 428 429 NSString *toolTip; 430 NSToolTipTag lastToolTipTag; 431 id trackingRectOwner; 432 void *trackingRectUserData; 433 434 NSTimer *autoscrollTimer; 435 NSEvent *autoscrollTriggerEvent; 436 437 NSArray *pageRects; 438 439 NSMutableDictionary *highlighters; 440 441#ifdef BUILDING_ON_TIGER 442 BOOL nextResponderDisabledOnce; 443#endif 444 445 WebTextCompletionController *completionController; 446 447 BOOL transparentBackground; 448 449 WebHTMLViewInterpretKeyEventsParameters* interpretKeyEventsParameters; 450 BOOL receivedNOOP; 451 452 WebDataSource *dataSource; 453 WebCore::CachedImage* promisedDragTIFFDataSource; 454 455 CFRunLoopTimerRef updateMouseoverTimer; 456 457 SEL selectorForDoCommandBySelector; 458 459#ifndef NDEBUG 460 BOOL enumeratingSubviews; 461#endif 462} 463- (void)clear; 464@end 465 466static NSCellStateValue kit(TriState state) 467{ 468 switch (state) { 469 case FalseTriState: 470 return NSOffState; 471 case TrueTriState: 472 return NSOnState; 473 case MixedTriState: 474 return NSMixedState; 475 } 476 ASSERT_NOT_REACHED(); 477 return NSOffState; 478} 479 480@implementation WebHTMLViewPrivate 481 482+ (void)initialize 483{ 484 JSC::initializeThreading(); 485#ifndef BUILDING_ON_TIGER 486 WebCoreObjCFinalizeOnMainThread(self); 487#endif 488 489 if (!oldSetCursorIMP) { 490#ifdef BUILDING_ON_TIGER 491 Method setCursorMethod = class_getInstanceMethod([NSCursor class], @selector(set)); 492#else 493 Method setCursorMethod = class_getInstanceMethod([NSWindow class], @selector(_setCursorForMouseLocation:)); 494#endif 495 ASSERT(setCursorMethod); 496 497 oldSetCursorIMP = method_setImplementation(setCursorMethod, (IMP)setCursor); 498 ASSERT(oldSetCursorIMP); 499 } 500 501#ifdef BUILDING_ON_TIGER 502 if (!oldResetCursorRectsIMP) { 503 Method resetCursorRectsMethod = class_getInstanceMethod([NSWindow class], @selector(resetCursorRects)); 504 ASSERT(resetCursorRectsMethod); 505 oldResetCursorRectsIMP = method_setImplementation(resetCursorRectsMethod, (IMP)resetCursorRects); 506 ASSERT(oldResetCursorRectsIMP); 507 } 508#endif 509 510} 511 512- (void)dealloc 513{ 514 if (WebCoreObjCScheduleDeallocateOnMainThread([WebHTMLViewPrivate class], self)) 515 return; 516 517 ASSERT(!autoscrollTimer); 518 ASSERT(!autoscrollTriggerEvent); 519 ASSERT(!updateMouseoverTimer); 520 521 [mouseDownEvent release]; 522 [keyDownEvent release]; 523 [pluginController release]; 524 [toolTip release]; 525 [completionController release]; 526 [dataSource release]; 527 [highlighters release]; 528 if (promisedDragTIFFDataSource) 529 promisedDragTIFFDataSource->removeClient(promisedDataClient()); 530 531 [super dealloc]; 532} 533 534- (void)finalize 535{ 536 ASSERT_MAIN_THREAD(); 537 538 if (promisedDragTIFFDataSource) 539 promisedDragTIFFDataSource->removeClient(promisedDataClient()); 540 541 [super finalize]; 542} 543 544- (void)clear 545{ 546 [mouseDownEvent release]; 547 [keyDownEvent release]; 548 [pluginController release]; 549 [toolTip release]; 550 [completionController release]; 551 [dataSource release]; 552 [highlighters release]; 553 if (promisedDragTIFFDataSource) 554 promisedDragTIFFDataSource->removeClient(promisedDataClient()); 555 556 mouseDownEvent = nil; 557 keyDownEvent = nil; 558 pluginController = nil; 559 toolTip = nil; 560 completionController = nil; 561 dataSource = nil; 562 highlighters = nil; 563 promisedDragTIFFDataSource = 0; 564 565#if USE(ACCELERATED_COMPOSITING) 566 layerHostingView = nil; 567#endif 568} 569 570@end 571 572@implementation WebHTMLView (WebHTMLViewFileInternal) 573 574- (DOMRange *)_documentRange 575{ 576 return [[[self _frame] DOMDocument] _documentRange]; 577} 578 579- (BOOL)_imageExistsAtPaths:(NSArray *)paths 580{ 581 NSEnumerator *enumerator = [paths objectEnumerator]; 582 NSString *path; 583 584 while ((path = [enumerator nextObject]) != nil) { 585 NSString *MIMEType = WKGetMIMETypeForExtension([path pathExtension]); 586 if (MIMETypeRegistry::isSupportedImageResourceMIMEType(MIMEType)) 587 return YES; 588 } 589 590 return NO; 591} 592 593- (WebDataSource *)_dataSource 594{ 595 return _private->dataSource; 596} 597 598- (WebView *)_webView 599{ 600 return [_private->dataSource _webView]; 601} 602 603- (WebFrameView *)_frameView 604{ 605 return [[_private->dataSource webFrame] frameView]; 606} 607 608- (DOMDocumentFragment *)_documentFragmentWithPaths:(NSArray *)paths 609{ 610 DOMDocumentFragment *fragment; 611 NSEnumerator *enumerator = [paths objectEnumerator]; 612 NSMutableArray *domNodes = [[NSMutableArray alloc] init]; 613 NSString *path; 614 615 while ((path = [enumerator nextObject]) != nil) { 616 // Non-image file types; _web_userVisibleString is appropriate here because this will 617 // be pasted as visible text. 618 NSString *url = [[[NSURL fileURLWithPath:path] _webkit_canonicalize] _web_userVisibleString]; 619 [domNodes addObject:[[[self _frame] DOMDocument] createTextNode: url]]; 620 } 621 622 fragment = [[self _frame] _documentFragmentWithNodesAsParagraphs:domNodes]; 623 624 [domNodes release]; 625 626 return [fragment firstChild] != nil ? fragment : nil; 627} 628 629+ (NSArray *)_excludedElementsForAttributedStringConversion 630{ 631 static NSArray *elements = nil; 632 if (elements == nil) { 633 elements = [[NSArray alloc] initWithObjects: 634 // Omit style since we want style to be inline so the fragment can be easily inserted. 635 @"style", 636 // Omit xml so the result is not XHTML. 637 @"xml", 638 // Omit tags that will get stripped when converted to a fragment anyway. 639 @"doctype", @"html", @"head", @"body", 640 // Omit deprecated tags. 641 @"applet", @"basefont", @"center", @"dir", @"font", @"isindex", @"menu", @"s", @"strike", @"u", 642 // Omit object so no file attachments are part of the fragment. 643 @"object", nil]; 644 CFRetain(elements); 645 } 646 return elements; 647} 648 649static NSURL* uniqueURLWithRelativePart(NSString *relativePart) 650{ 651 CFUUIDRef UUIDRef = CFUUIDCreate(kCFAllocatorDefault); 652 NSString *UUIDString = (NSString *)CFUUIDCreateString(kCFAllocatorDefault, UUIDRef); 653 CFRelease(UUIDRef); 654 NSURL *URL = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@/%@", WebDataProtocolScheme, UUIDString, relativePart]]; 655 CFRelease(UUIDString); 656 657 return URL; 658} 659 660- (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard 661 inContext:(DOMRange *)context 662 allowPlainText:(BOOL)allowPlainText 663{ 664 NSArray *types = [pasteboard types]; 665 DOMDocumentFragment *fragment = nil; 666 667 if ([types containsObject:WebArchivePboardType] && 668 (fragment = [self _documentFragmentFromPasteboard:pasteboard 669 forType:WebArchivePboardType 670 inContext:context 671 subresources:0])) 672 return fragment; 673 674 if ([types containsObject:NSFilenamesPboardType] && 675 (fragment = [self _documentFragmentFromPasteboard:pasteboard 676 forType:NSFilenamesPboardType 677 inContext:context 678 subresources:0])) 679 return fragment; 680 681 if ([types containsObject:NSHTMLPboardType] && 682 (fragment = [self _documentFragmentFromPasteboard:pasteboard 683 forType:NSHTMLPboardType 684 inContext:context 685 subresources:0])) 686 return fragment; 687 688 if ([types containsObject:NSRTFDPboardType] && 689 (fragment = [self _documentFragmentFromPasteboard:pasteboard 690 forType:NSRTFDPboardType 691 inContext:context 692 subresources:0])) 693 return fragment; 694 695 if ([types containsObject:NSRTFPboardType] && 696 (fragment = [self _documentFragmentFromPasteboard:pasteboard 697 forType:NSRTFPboardType 698 inContext:context 699 subresources:0])) 700 return fragment; 701 702 if ([types containsObject:NSTIFFPboardType] && 703 (fragment = [self _documentFragmentFromPasteboard:pasteboard 704 forType:NSTIFFPboardType 705 inContext:context 706 subresources:0])) 707 return fragment; 708 709 if ([types containsObject:NSPDFPboardType] && 710 (fragment = [self _documentFragmentFromPasteboard:pasteboard 711 forType:NSPDFPboardType 712 inContext:context 713 subresources:0])) 714 return fragment; 715 716#if defined(BUILDING_ON_TIGER) || defined(BUILDING_ON_LEOPARD) 717 if ([types containsObject:NSPICTPboardType] && 718 (fragment = [self _documentFragmentFromPasteboard:pasteboard 719 forType:NSPICTPboardType 720 inContext:context 721 subresources:0])) 722 return fragment; 723#endif 724 725 // Only 10.5 and higher support setting and retrieving pasteboard types with UTIs, but we don't believe 726 // that any applications on Tiger put types for which we only have a UTI, like PNG, on the pasteboard. 727 if ([types containsObject:(NSString*)kUTTypePNG] && 728 (fragment = [self _documentFragmentFromPasteboard:pasteboard 729 forType:(NSString*)kUTTypePNG 730 inContext:context 731 subresources:0])) 732 return fragment; 733 734 if ([types containsObject:NSURLPboardType] && 735 (fragment = [self _documentFragmentFromPasteboard:pasteboard 736 forType:NSURLPboardType 737 inContext:context 738 subresources:0])) 739 return fragment; 740 741 if (allowPlainText && [types containsObject:NSStringPboardType] && 742 (fragment = [self _documentFragmentFromPasteboard:pasteboard 743 forType:NSStringPboardType 744 inContext:context 745 subresources:0])) { 746 return fragment; 747 } 748 749 return nil; 750} 751 752- (NSString *)_plainTextFromPasteboard:(NSPasteboard *)pasteboard 753{ 754 NSArray *types = [pasteboard types]; 755 756 if ([types containsObject:NSStringPboardType]) 757 return [[pasteboard stringForType:NSStringPboardType] precomposedStringWithCanonicalMapping]; 758 759 NSAttributedString *attributedString = nil; 760 NSString *string; 761 762 if ([types containsObject:NSRTFDPboardType]) 763 attributedString = [[NSAttributedString alloc] initWithRTFD:[pasteboard dataForType:NSRTFDPboardType] documentAttributes:NULL]; 764 if (attributedString == nil && [types containsObject:NSRTFPboardType]) 765 attributedString = [[NSAttributedString alloc] initWithRTF:[pasteboard dataForType:NSRTFPboardType] documentAttributes:NULL]; 766 if (attributedString != nil) { 767 string = [[attributedString string] copy]; 768 [attributedString release]; 769 return [string autorelease]; 770 } 771 772 if ([types containsObject:NSFilenamesPboardType]) { 773 string = [[pasteboard propertyListForType:NSFilenamesPboardType] componentsJoinedByString:@"\n"]; 774 if (string != nil) 775 return string; 776 } 777 778 NSURL *URL; 779 780 if ((URL = [NSURL URLFromPasteboard:pasteboard])) { 781 string = [URL _web_userVisibleString]; 782 if ([string length] > 0) 783 return string; 784 } 785 786 return nil; 787} 788 789- (void)_pasteWithPasteboard:(NSPasteboard *)pasteboard allowPlainText:(BOOL)allowPlainText 790{ 791 WebView *webView = [[self _webView] retain]; 792 [webView _setInsertionPasteboard:pasteboard]; 793 794 DOMRange *range = [self _selectedRange]; 795 DOMDocumentFragment *fragment = [self _documentFragmentFromPasteboard:pasteboard inContext:range allowPlainText:allowPlainText]; 796 if (fragment && [self _shouldInsertFragment:fragment replacingDOMRange:range givenAction:WebViewInsertActionPasted]) 797 [[self _frame] _replaceSelectionWithFragment:fragment selectReplacement:NO smartReplace:[self _canSmartReplaceWithPasteboard:pasteboard] matchStyle:NO]; 798 799 [webView _setInsertionPasteboard:nil]; 800 [webView release]; 801} 802 803- (void)_pasteAsPlainTextWithPasteboard:(NSPasteboard *)pasteboard 804{ 805 WebView *webView = [[self _webView] retain]; 806 [webView _setInsertionPasteboard:pasteboard]; 807 808 NSString *text = [self _plainTextFromPasteboard:pasteboard]; 809 if ([self _shouldReplaceSelectionWithText:text givenAction:WebViewInsertActionPasted]) 810 [[self _frame] _replaceSelectionWithText:text selectReplacement:NO smartReplace:[self _canSmartReplaceWithPasteboard:pasteboard]]; 811 812 [webView _setInsertionPasteboard:nil]; 813 [webView release]; 814} 815 816- (void)_removeMouseMovedObserverUnconditionally 817{ 818 if (!_private || !_private->observingMouseMovedNotifications) 819 return; 820 821 [[NSNotificationCenter defaultCenter] removeObserver:self name:WKMouseMovedNotification() object:nil]; 822 _private->observingMouseMovedNotifications = false; 823} 824 825- (void)_removeSuperviewObservers 826{ 827 if (!_private || !_private->observingSuperviewNotifications) 828 return; 829 830 NSView *superview = [self superview]; 831 if (!superview || ![self window]) 832 return; 833 834 NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; 835 [notificationCenter removeObserver:self name:NSViewFrameDidChangeNotification object:superview]; 836 [notificationCenter removeObserver:self name:NSViewBoundsDidChangeNotification object:superview]; 837 838 _private->observingSuperviewNotifications = false; 839} 840 841- (void)_removeWindowObservers 842{ 843 if (!_private->observingWindowNotifications) 844 return; 845 846 NSWindow *window = [self window]; 847 if (!window) 848 return; 849 850 NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; 851 [notificationCenter removeObserver:self name:NSWindowDidBecomeKeyNotification object:nil]; 852 [notificationCenter removeObserver:self name:NSWindowDidResignKeyNotification object:nil]; 853 [notificationCenter removeObserver:self name:NSWindowWillCloseNotification object:window]; 854 855 _private->observingWindowNotifications = false; 856} 857 858- (BOOL)_shouldInsertFragment:(DOMDocumentFragment *)fragment replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action 859{ 860 WebView *webView = [self _webView]; 861 DOMNode *child = [fragment firstChild]; 862 if ([fragment lastChild] == child && [child isKindOfClass:[DOMCharacterData class]]) 863 return [[webView _editingDelegateForwarder] webView:webView shouldInsertText:[(DOMCharacterData *)child data] replacingDOMRange:range givenAction:action]; 864 return [[webView _editingDelegateForwarder] webView:webView shouldInsertNode:fragment replacingDOMRange:range givenAction:action]; 865} 866 867- (BOOL)_shouldInsertText:(NSString *)text replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action 868{ 869 WebView *webView = [self _webView]; 870 return [[webView _editingDelegateForwarder] webView:webView shouldInsertText:text replacingDOMRange:range givenAction:action]; 871} 872 873- (BOOL)_shouldReplaceSelectionWithText:(NSString *)text givenAction:(WebViewInsertAction)action 874{ 875 return [self _shouldInsertText:text replacingDOMRange:[self _selectedRange] givenAction:action]; 876} 877 878// Calculate the vertical size of the view that fits on a single page 879- (float)_calculatePrintHeight 880{ 881 // Obtain the print info object for the current operation 882 NSPrintInfo *pi = [[NSPrintOperation currentOperation] printInfo]; 883 884 // Calculate the page height in points 885 NSSize paperSize = [pi paperSize]; 886 return paperSize.height - [pi topMargin] - [pi bottomMargin]; 887} 888 889- (DOMRange *)_selectedRange 890{ 891 Frame* coreFrame = core([self _frame]); 892 return coreFrame ? kit(coreFrame->selection()->toNormalizedRange().get()) : nil; 893} 894 895- (BOOL)_shouldDeleteRange:(DOMRange *)range 896{ 897 Frame* coreFrame = core([self _frame]); 898 return coreFrame && coreFrame->editor()->shouldDeleteRange(core(range)); 899} 900 901- (NSView *)_hitViewForEvent:(NSEvent *)event 902{ 903 // Usually, we hack AK's hitTest method to catch all events at the topmost WebHTMLView. 904 // Callers of this method, however, want to query the deepest view instead. 905 forceNSViewHitTest = YES; 906 NSView *hitView = [[[self window] contentView] hitTest:[event locationInWindow]]; 907 forceNSViewHitTest = NO; 908 return hitView; 909} 910 911- (void)_writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard cachedAttributedString:(NSAttributedString *)attributedString 912{ 913 // Put HTML on the pasteboard. 914 if ([types containsObject:WebArchivePboardType]) { 915 if (RefPtr<LegacyWebArchive> coreArchive = LegacyWebArchive::createFromSelection(core([self _frame]))) { 916 if (RetainPtr<CFDataRef> data = coreArchive ? coreArchive->rawDataRepresentation() : 0) 917 [pasteboard setData:(NSData *)data.get() forType:WebArchivePboardType]; 918 } 919 } 920 921 // Put the attributed string on the pasteboard (RTF/RTFD format). 922 if ([types containsObject:NSRTFDPboardType]) { 923 if (attributedString == nil) { 924 attributedString = [self selectedAttributedString]; 925 } 926 NSData *RTFDData = [attributedString RTFDFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil]; 927 [pasteboard setData:RTFDData forType:NSRTFDPboardType]; 928 } 929 if ([types containsObject:NSRTFPboardType]) { 930 if (attributedString == nil) { 931 attributedString = [self selectedAttributedString]; 932 } 933 if ([attributedString containsAttachments]) { 934 attributedString = [attributedString _web_attributedStringByStrippingAttachmentCharacters]; 935 } 936 NSData *RTFData = [attributedString RTFFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil]; 937 [pasteboard setData:RTFData forType:NSRTFPboardType]; 938 } 939 940 // Put plain string on the pasteboard. 941 if ([types containsObject:NSStringPboardType]) { 942 // Map to a plain old space because this is better for source code, other browsers do it, 943 // and because HTML forces you to do this any time you want two spaces in a row. 944 NSMutableString *s = [[self selectedString] mutableCopy]; 945 const unichar NonBreakingSpaceCharacter = 0xA0; 946 NSString *NonBreakingSpaceString = [NSString stringWithCharacters:&NonBreakingSpaceCharacter length:1]; 947 [s replaceOccurrencesOfString:NonBreakingSpaceString withString:@" " options:0 range:NSMakeRange(0, [s length])]; 948 [pasteboard setString:s forType:NSStringPboardType]; 949 [s release]; 950 } 951 952 if ([self _canSmartCopyOrDelete] && [types containsObject:WebSmartPastePboardType]) { 953 [pasteboard setData:nil forType:WebSmartPastePboardType]; 954 } 955} 956 957- (void)_setMouseDownEvent:(NSEvent *)event 958{ 959 ASSERT(!event || [event type] == NSLeftMouseDown || [event type] == NSRightMouseDown || [event type] == NSOtherMouseDown); 960 961 if (event == _private->mouseDownEvent) 962 return; 963 964 [event retain]; 965 [_private->mouseDownEvent release]; 966 _private->mouseDownEvent = event; 967} 968 969- (void)_cancelUpdateMouseoverTimer 970{ 971 if (_private->updateMouseoverTimer) { 972 CFRunLoopTimerInvalidate(_private->updateMouseoverTimer); 973 CFRelease(_private->updateMouseoverTimer); 974 _private->updateMouseoverTimer = NULL; 975 } 976} 977 978- (WebHTMLView *)_topHTMLView 979{ 980 // FIXME: this can fail if the dataSource is nil, which happens when the WebView is tearing down from the window closing. 981 WebHTMLView *view = (WebHTMLView *)[[[[_private->dataSource _webView] mainFrame] frameView] documentView]; 982 ASSERT(view); 983 ASSERT([view isKindOfClass:[WebHTMLView class]]); 984 return view; 985} 986 987- (BOOL)_isTopHTMLView 988{ 989 // FIXME: this should be a cached boolean that doesn't rely on _topHTMLView since that can fail (see _topHTMLView). 990 return self == [self _topHTMLView]; 991} 992 993- (void)_web_setPrintingModeRecursive 994{ 995 [self _setPrinting:YES minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO]; 996 997#ifndef NDEBUG 998 _private->enumeratingSubviews = YES; 999#endif 1000 1001 NSMutableArray *descendantWebHTMLViews = [[NSMutableArray alloc] init]; 1002 1003 [self _web_addDescendantWebHTMLViewsToArray:descendantWebHTMLViews]; 1004 1005 unsigned count = [descendantWebHTMLViews count]; 1006 for (unsigned i = 0; i < count; ++i) 1007 [[descendantWebHTMLViews objectAtIndex:i] _setPrinting:YES minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO]; 1008 1009 [descendantWebHTMLViews release]; 1010 1011#ifndef NDEBUG 1012 _private->enumeratingSubviews = NO; 1013#endif 1014} 1015 1016- (void)_web_clearPrintingModeRecursive 1017{ 1018 [self _setPrinting:NO minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO]; 1019 1020#ifndef NDEBUG 1021 _private->enumeratingSubviews = YES; 1022#endif 1023 1024 NSMutableArray *descendantWebHTMLViews = [[NSMutableArray alloc] init]; 1025 1026 [self _web_addDescendantWebHTMLViewsToArray:descendantWebHTMLViews]; 1027 1028 unsigned count = [descendantWebHTMLViews count]; 1029 for (unsigned i = 0; i < count; ++i) 1030 [[descendantWebHTMLViews objectAtIndex:i] _setPrinting:NO minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO]; 1031 1032 [descendantWebHTMLViews release]; 1033 1034#ifndef NDEBUG 1035 _private->enumeratingSubviews = NO; 1036#endif 1037} 1038 1039- (void)_web_setPrintingModeRecursiveAndAdjustViewSize 1040{ 1041 [self _setPrinting:YES minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:YES]; 1042 1043#ifndef NDEBUG 1044 _private->enumeratingSubviews = YES; 1045#endif 1046 1047 NSMutableArray *descendantWebHTMLViews = [[NSMutableArray alloc] init]; 1048 1049 [self _web_addDescendantWebHTMLViewsToArray:descendantWebHTMLViews]; 1050 1051 unsigned count = [descendantWebHTMLViews count]; 1052 for (unsigned i = 0; i < count; ++i) 1053 [[descendantWebHTMLViews objectAtIndex:i] _setPrinting:YES minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:YES]; 1054 1055 [descendantWebHTMLViews release]; 1056 1057#ifndef NDEBUG 1058 _private->enumeratingSubviews = NO; 1059#endif 1060} 1061 1062@end 1063 1064@implementation WebHTMLView (WebPrivate) 1065 1066+ (NSArray *)supportedMIMETypes 1067{ 1068 return [WebHTMLRepresentation supportedMIMETypes]; 1069} 1070 1071+ (NSArray *)supportedImageMIMETypes 1072{ 1073 return [WebHTMLRepresentation supportedImageMIMETypes]; 1074} 1075 1076+ (NSArray *)supportedNonImageMIMETypes 1077{ 1078 return [WebHTMLRepresentation supportedNonImageMIMETypes]; 1079} 1080 1081+ (NSArray *)unsupportedTextMIMETypes 1082{ 1083 return [NSArray arrayWithObjects: 1084 @"text/calendar", // iCal 1085 @"text/x-calendar", 1086 @"text/x-vcalendar", 1087 @"text/vcalendar", 1088 @"text/vcard", // vCard 1089 @"text/x-vcard", 1090 @"text/directory", 1091 @"text/ldif", // Netscape Address Book 1092 @"text/qif", // Quicken 1093 @"text/x-qif", 1094 @"text/x-csv", // CSV (for Address Book and Microsoft Outlook) 1095 @"text/x-vcf", // vCard type used in Sun affinity app 1096 @"text/rtf", // Rich Text Format 1097 nil]; 1098} 1099 1100+ (void)_postFlagsChangedEvent:(NSEvent *)flagsChangedEvent 1101{ 1102 // This is a workaround for: <rdar://problem/2981619> NSResponder_Private should include notification for FlagsChanged 1103 NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved 1104 location:[[flagsChangedEvent window] convertScreenToBase:[NSEvent mouseLocation]] 1105 modifierFlags:[flagsChangedEvent modifierFlags] 1106 timestamp:[flagsChangedEvent timestamp] 1107 windowNumber:[flagsChangedEvent windowNumber] 1108 context:[flagsChangedEvent context] 1109 eventNumber:0 clickCount:0 pressure:0]; 1110 1111 // Pretend it's a mouse move. 1112 [[NSNotificationCenter defaultCenter] 1113 postNotificationName:WKMouseMovedNotification() object:self 1114 userInfo:[NSDictionary dictionaryWithObject:fakeEvent forKey:@"NSEvent"]]; 1115} 1116 1117- (id)_bridge 1118{ 1119 // This method exists to maintain compatibility with Leopard's Dictionary.app, since it 1120 // calls _bridge to get access to convertNSRangeToDOMRange: and convertDOMRangeToNSRange:. 1121 // Return the WebFrame, which implements the compatibility methods. <rdar://problem/6002160> 1122 return [self _frame]; 1123} 1124 1125- (void)_updateMouseoverWithFakeEvent 1126{ 1127 [self _cancelUpdateMouseoverTimer]; 1128 1129 NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved 1130 location:[[self window] convertScreenToBase:[NSEvent mouseLocation]] 1131 modifierFlags:[[NSApp currentEvent] modifierFlags] 1132 timestamp:[NSDate timeIntervalSinceReferenceDate] 1133 windowNumber:[[self window] windowNumber] 1134 context:[[NSApp currentEvent] context] 1135 eventNumber:0 clickCount:0 pressure:0]; 1136 1137 [self _updateMouseoverWithEvent:fakeEvent]; 1138} 1139 1140static void _updateMouseoverTimerCallback(CFRunLoopTimerRef timer, void *info) 1141{ 1142 WebHTMLView *view = (WebHTMLView *)info; 1143 1144 [view _updateMouseoverWithFakeEvent]; 1145} 1146 1147- (void)_frameOrBoundsChanged 1148{ 1149 NSPoint origin = [[self superview] bounds].origin; 1150 if (!NSEqualPoints(_private->lastScrollPosition, origin)) { 1151 if (Frame* coreFrame = core([self _frame])) 1152 coreFrame->eventHandler()->sendScrollEvent(); 1153 [_private->completionController endRevertingChange:NO moveLeft:NO]; 1154 1155 WebView *webView = [self _webView]; 1156 [[webView _UIDelegateForwarder] webView:webView didScrollDocumentInFrameView:[self _frameView]]; 1157 } 1158 _private->lastScrollPosition = origin; 1159 1160 if ([self window] && !_private->closed && !_private->updateMouseoverTimer) { 1161 CFRunLoopTimerContext context = { 0, self, NULL, NULL, NULL }; 1162 1163 // Use a 100ms delay so that the synthetic mouse over update doesn't cause cursor thrashing when pages are loading 1164 // and scrolling rapidly back to back. 1165 _private->updateMouseoverTimer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent() + 0.1, 0, 0, 0, 1166 _updateMouseoverTimerCallback, &context); 1167 CFRunLoopAddTimer(CFRunLoopGetCurrent(), _private->updateMouseoverTimer, kCFRunLoopDefaultMode); 1168 } 1169 1170#if USE(ACCELERATED_COMPOSITING) && defined(BUILDING_ON_LEOPARD) 1171 [self _updateLayerHostingViewPosition]; 1172#endif 1173} 1174 1175- (void)_setAsideSubviews 1176{ 1177 ASSERT(!_private->subviewsSetAside); 1178 ASSERT(_private->savedSubviews == nil); 1179 _private->savedSubviews = _subviews; 1180#if USE(ACCELERATED_COMPOSITING) 1181 // We need to keep the layer-hosting view in the subviews, otherwise the layers flash. 1182 if (_private->layerHostingView) { 1183 NSArray* newSubviews = [[NSArray alloc] initWithObjects:_private->layerHostingView, nil]; 1184 _subviews = newSubviews; 1185 } else 1186 _subviews = nil; 1187#else 1188 _subviews = nil; 1189#endif 1190 _private->subviewsSetAside = YES; 1191 } 1192 1193 - (void)_restoreSubviews 1194 { 1195 ASSERT(_private->subviewsSetAside); 1196#if USE(ACCELERATED_COMPOSITING) 1197 if (_private->layerHostingView) { 1198 [_subviews release]; 1199 _subviews = _private->savedSubviews; 1200 } else { 1201 ASSERT(_subviews == nil); 1202 _subviews = _private->savedSubviews; 1203 } 1204#else 1205 ASSERT(_subviews == nil); 1206 _subviews = _private->savedSubviews; 1207#endif 1208 _private->savedSubviews = nil; 1209 _private->subviewsSetAside = NO; 1210} 1211 1212#ifndef NDEBUG 1213 1214- (void)didAddSubview:(NSView *)subview 1215{ 1216 if (_private->enumeratingSubviews) 1217 LOG(View, "A view of class %s was added during subview enumeration for layout or printing mode change. This view might paint without first receiving layout.", object_getClassName([subview class])); 1218} 1219 1220- (void)willRemoveSubview:(NSView *)subview 1221{ 1222 // Have to null-check _private, since this can be called via -dealloc when 1223 // cleaning up the the layerHostingView. 1224 if (_private && _private->enumeratingSubviews) 1225 LOG(View, "A view of class %s was removed during subview enumeration for layout or printing mode change. We will still do layout or the printing mode change even though this view is no longer in the view hierarchy.", object_getClassName([subview class])); 1226} 1227 1228#endif 1229 1230#ifdef BUILDING_ON_TIGER 1231 1232// This is called when we are about to draw, but before our dirty rect is propagated to our ancestors. 1233// That's the perfect time to do a layout, except that ideally we'd want to be sure that we're dirty 1234// before doing it. As a compromise, when we're opaque we do the layout only when actually asked to 1235// draw, but when we're transparent we do the layout at this stage so views behind us know that they 1236// need to be redrawn (in case the layout causes some things to get dirtied). 1237- (void)_propagateDirtyRectsToOpaqueAncestors 1238{ 1239 if (![[self _webView] drawsBackground]) 1240 [self _web_layoutIfNeededRecursive]; 1241 [super _propagateDirtyRectsToOpaqueAncestors]; 1242} 1243 1244#else 1245 1246- (void)viewWillDraw 1247{ 1248 // On window close we will be called when the datasource is nil, then hit an assert in _topHTMLView 1249 // So check if the dataSource is nil before calling [self _isTopHTMLView], this can be removed 1250 // once the FIXME in _isTopHTMLView is fixed. 1251 if (_private->dataSource && [self _isTopHTMLView]) 1252 [self _web_layoutIfNeededRecursive]; 1253 [super viewWillDraw]; 1254} 1255 1256#endif 1257 1258// Don't let AppKit even draw subviews. We take care of that. 1259- (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView 1260{ 1261 // This helps when we print as part of a larger print process. 1262 // If the WebHTMLView itself is what we're printing, then we will never have to do this. 1263 BOOL wasInPrintingMode = _private->printing; 1264 BOOL isPrinting = ![NSGraphicsContext currentContextDrawingToScreen]; 1265 if (isPrinting) { 1266 if (!wasInPrintingMode) 1267 [self _web_setPrintingModeRecursive]; 1268#ifndef BUILDING_ON_TIGER 1269 else 1270 [self _web_layoutIfNeededRecursive]; 1271#endif 1272 } else if (wasInPrintingMode) 1273 [self _web_clearPrintingModeRecursive]; 1274 1275#ifndef BUILDING_ON_TIGER 1276 // There are known cases where -viewWillDraw is not called on all views being drawn. 1277 // See <rdar://problem/6964278> for example. Performing layout at this point prevents us from 1278 // trying to paint without layout (which WebCore now refuses to do, instead bailing out without 1279 // drawing at all), but we may still fail to update and regions dirtied by the layout which are 1280 // not already dirty. 1281 if ([self _needsLayout]) { 1282 LOG_ERROR("View needs layout. Either -viewWillDraw wasn't called or layout was invalidated during the display operation. Performing layout now."); 1283 [self _web_layoutIfNeededRecursive]; 1284 } 1285#else 1286 // Because Tiger does not have viewWillDraw we need to do layout here. 1287 [self _web_layoutIfNeededRecursive]; 1288 [_subviews makeObjectsPerformSelector:@selector(_propagateDirtyRectsToOpaqueAncestors)]; 1289#endif 1290 1291 [self _setAsideSubviews]; 1292 [super _recursiveDisplayRectIfNeededIgnoringOpacity:rect isVisibleRect:isVisibleRect rectIsVisibleRectForView:visibleView topView:topView]; 1293 [self _restoreSubviews]; 1294 1295 if (wasInPrintingMode != isPrinting) { 1296 if (wasInPrintingMode) 1297 [self _web_setPrintingModeRecursive]; 1298 else 1299 [self _web_clearPrintingModeRecursive]; 1300 } 1301} 1302 1303// Don't let AppKit even draw subviews. We take care of that. 1304- (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect 1305{ 1306 BOOL needToSetAsideSubviews = !_private->subviewsSetAside; 1307 1308 BOOL wasInPrintingMode = _private->printing; 1309 BOOL isPrinting = ![NSGraphicsContext currentContextDrawingToScreen]; 1310 1311 if (needToSetAsideSubviews) { 1312 // This helps when we print as part of a larger print process. 1313 // If the WebHTMLView itself is what we're printing, then we will never have to do this. 1314 if (isPrinting) { 1315 if (!wasInPrintingMode) 1316 [self _web_setPrintingModeRecursive]; 1317#ifndef BUILDING_ON_TIGER 1318 else 1319 [self _web_layoutIfNeededRecursive]; 1320#endif 1321 } else if (wasInPrintingMode) 1322 [self _web_clearPrintingModeRecursive]; 1323 1324#ifdef BUILDING_ON_TIGER 1325 1326 // Because Tiger does not have viewWillDraw we need to do layout here. 1327 NSRect boundsBeforeLayout = [self bounds]; 1328 if (!NSIsEmptyRect(visRect)) 1329 [self _web_layoutIfNeededRecursive]; 1330 1331 // If layout changes the view's bounds, then we need to recompute the visRect. 1332 // That's because the visRect passed to us was based on the bounds at the time 1333 // we were called. This method is only displayed to draw "all", so it's safe 1334 // to just call visibleRect to compute the entire rectangle. 1335 if (!NSEqualRects(boundsBeforeLayout, [self bounds])) 1336 visRect = [self visibleRect]; 1337 1338#endif 1339 1340 [self _setAsideSubviews]; 1341 } 1342 1343 [super _recursiveDisplayAllDirtyWithLockFocus:needsLockFocus visRect:visRect]; 1344 1345 if (needToSetAsideSubviews) { 1346 if (wasInPrintingMode != isPrinting) { 1347 if (wasInPrintingMode) 1348 [self _web_setPrintingModeRecursive]; 1349 else 1350 [self _web_clearPrintingModeRecursive]; 1351 } 1352 1353 [self _restoreSubviews]; 1354 } 1355} 1356 1357// Don't let AppKit even draw subviews. We take care of that. 1358- (void)_recursive:(BOOL)recurse displayRectIgnoringOpacity:(NSRect)displayRect inContext:(NSGraphicsContext *)context topView:(BOOL)topView 1359{ 1360#ifdef BUILDING_ON_TIGER 1361 // Because Tiger does not have viewWillDraw we need to do layout here. 1362 [self _web_layoutIfNeededRecursive]; 1363#endif 1364 1365 [self _setAsideSubviews]; 1366 [super _recursive:recurse displayRectIgnoringOpacity:displayRect inContext:context topView:topView]; 1367 [self _restoreSubviews]; 1368} 1369 1370- (BOOL)_insideAnotherHTMLView 1371{ 1372 return self != [self _topHTMLView]; 1373} 1374 1375- (NSView *)hitTest:(NSPoint)point 1376{ 1377 // WebHTMLView objects handle all events for objects inside them. 1378 // To get those events, we prevent hit testing from AppKit. 1379 1380 // But there are three exceptions to this: 1381 // 1) For right mouse clicks and control clicks we don't yet have an implementation 1382 // that works for nested views, so we let the hit testing go through the 1383 // standard NSView code path (needs to be fixed, see bug 4361618). 1384 // 2) Java depends on doing a hit test inside it's mouse moved handling, 1385 // so we let the hit testing go through the standard NSView code path 1386 // when the current event is a mouse move (except when we are calling 1387 // from _updateMouseoverWithEvent, so we have to use a global, 1388 // forceWebHTMLViewHitTest, for that) 1389 // 3) The acceptsFirstMouse: and shouldDelayWindowOrderingForEvent: methods 1390 // both need to figure out which view to check with inside the WebHTMLView. 1391 // They use a global to change the behavior of hitTest: so they can get the 1392 // right view. The global is forceNSViewHitTest and the method they use to 1393 // do the hit testing is _hitViewForEvent:. (But this does not work correctly 1394 // when there is HTML overlapping the view, see bug 4361626) 1395 // 4) NSAccessibilityHitTest relies on this for checking the cursor position. 1396 // Our check for that is whether the event is NSFlagsChanged. This works 1397 // for VoiceOver's Control-Option-F5 command (move focus to item under cursor) 1398 // and Dictionary's Command-Control-D (open dictionary popup for item under cursor). 1399 // This is of course a hack. 1400 1401 if (_private->closed) 1402 return nil; 1403 1404 BOOL captureHitsOnSubviews; 1405 if (forceNSViewHitTest) 1406 captureHitsOnSubviews = NO; 1407 else if (forceWebHTMLViewHitTest) 1408 captureHitsOnSubviews = YES; 1409 else { 1410 NSEvent *event = [[self window] currentEvent]; 1411 captureHitsOnSubviews = !([event type] == NSMouseMoved 1412 || [event type] == NSRightMouseDown 1413 || ([event type] == NSLeftMouseDown && ([event modifierFlags] & NSControlKeyMask) != 0) 1414 || [event type] == NSFlagsChanged); 1415 } 1416 1417 if (!captureHitsOnSubviews) { 1418 NSView* hitView = [super hitTest:point]; 1419#if USE(ACCELERATED_COMPOSITING) 1420 if (_private && hitView == _private->layerHostingView) 1421 hitView = self; 1422#endif 1423 return hitView; 1424 } 1425 if ([[self superview] mouse:point inRect:[self frame]]) 1426 return self; 1427 return nil; 1428} 1429 1430- (void)_clearLastHitViewIfSelf 1431{ 1432 if (lastHitView == self) 1433 lastHitView = nil; 1434} 1435 1436- (NSTrackingRectTag)addTrackingRect:(NSRect)rect owner:(id)owner userData:(void *)data assumeInside:(BOOL)assumeInside 1437{ 1438 ASSERT(_private->trackingRectOwner == nil); 1439 _private->trackingRectOwner = owner; 1440 _private->trackingRectUserData = data; 1441 return TRACKING_RECT_TAG; 1442} 1443 1444- (NSTrackingRectTag)_addTrackingRect:(NSRect)rect owner:(id)owner userData:(void *)data assumeInside:(BOOL)assumeInside useTrackingNum:(int)tag 1445{ 1446 ASSERT(tag == 0 || tag == TRACKING_RECT_TAG); 1447 ASSERT(_private->trackingRectOwner == nil); 1448 _private->trackingRectOwner = owner; 1449 _private->trackingRectUserData = data; 1450 return TRACKING_RECT_TAG; 1451} 1452 1453- (void)_addTrackingRects:(NSRect *)rects owner:(id)owner userDataList:(void **)userDataList assumeInsideList:(BOOL *)assumeInsideList trackingNums:(NSTrackingRectTag *)trackingNums count:(int)count 1454{ 1455 ASSERT(count == 1); 1456 ASSERT(trackingNums[0] == 0 || trackingNums[0] == TRACKING_RECT_TAG); 1457 ASSERT(_private->trackingRectOwner == nil); 1458 _private->trackingRectOwner = owner; 1459 _private->trackingRectUserData = userDataList[0]; 1460 trackingNums[0] = TRACKING_RECT_TAG; 1461} 1462 1463- (void)removeTrackingRect:(NSTrackingRectTag)tag 1464{ 1465 if (tag == 0) 1466 return; 1467 1468 if (_private && (tag == TRACKING_RECT_TAG)) { 1469 _private->trackingRectOwner = nil; 1470 return; 1471 } 1472 1473 if (_private && (tag == _private->lastToolTipTag)) { 1474 [super removeTrackingRect:tag]; 1475 _private->lastToolTipTag = 0; 1476 return; 1477 } 1478 1479 // If any other tracking rect is being removed, we don't know how it was created 1480 // and it's possible there's a leak involved (see 3500217) 1481 ASSERT_NOT_REACHED(); 1482} 1483 1484- (void)_removeTrackingRects:(NSTrackingRectTag *)tags count:(int)count 1485{ 1486 int i; 1487 for (i = 0; i < count; ++i) { 1488 int tag = tags[i]; 1489 if (tag == 0) 1490 continue; 1491 ASSERT(tag == TRACKING_RECT_TAG); 1492 if (_private != nil) { 1493 _private->trackingRectOwner = nil; 1494 } 1495 } 1496} 1497 1498- (void)_sendToolTipMouseExited 1499{ 1500 // Nothing matters except window, trackingNumber, and userData. 1501 NSEvent *fakeEvent = [NSEvent enterExitEventWithType:NSMouseExited 1502 location:NSMakePoint(0, 0) 1503 modifierFlags:0 1504 timestamp:0 1505 windowNumber:[[self window] windowNumber] 1506 context:NULL 1507 eventNumber:0 1508 trackingNumber:TRACKING_RECT_TAG 1509 userData:_private->trackingRectUserData]; 1510 [_private->trackingRectOwner mouseExited:fakeEvent]; 1511} 1512 1513- (void)_sendToolTipMouseEntered 1514{ 1515 // Nothing matters except window, trackingNumber, and userData. 1516 NSEvent *fakeEvent = [NSEvent enterExitEventWithType:NSMouseEntered 1517 location:NSMakePoint(0, 0) 1518 modifierFlags:0 1519 timestamp:0 1520 windowNumber:[[self window] windowNumber] 1521 context:NULL 1522 eventNumber:0 1523 trackingNumber:TRACKING_RECT_TAG 1524 userData:_private->trackingRectUserData]; 1525 [_private->trackingRectOwner mouseEntered:fakeEvent]; 1526} 1527 1528- (void)_setToolTip:(NSString *)string 1529{ 1530 NSString *toolTip = [string length] == 0 ? nil : string; 1531 NSString *oldToolTip = _private->toolTip; 1532 if ((toolTip == nil || oldToolTip == nil) ? toolTip == oldToolTip : [toolTip isEqualToString:oldToolTip]) { 1533 return; 1534 } 1535 if (oldToolTip) { 1536 [self _sendToolTipMouseExited]; 1537 [oldToolTip release]; 1538 } 1539 _private->toolTip = [toolTip copy]; 1540 if (toolTip) { 1541 // See radar 3500217 for why we remove all tooltips rather than just the single one we created. 1542 [self removeAllToolTips]; 1543 NSRect wideOpenRect = NSMakeRect(-100000, -100000, 200000, 200000); 1544 _private->lastToolTipTag = [self addToolTipRect:wideOpenRect owner:self userData:NULL]; 1545 [self _sendToolTipMouseEntered]; 1546 } 1547} 1548 1549- (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point userData:(void *)data 1550{ 1551 return [[_private->toolTip copy] autorelease]; 1552} 1553 1554- (void)_updateMouseoverWithEvent:(NSEvent *)event 1555{ 1556 if (_private->closed) 1557 return; 1558 1559 NSView *contentView = [[event window] contentView]; 1560 NSPoint locationForHitTest = [[contentView superview] convertPoint:[event locationInWindow] fromView:nil]; 1561 1562 forceWebHTMLViewHitTest = YES; 1563 NSView *hitView = [contentView hitTest:locationForHitTest]; 1564 forceWebHTMLViewHitTest = NO; 1565 1566 WebHTMLView *view = nil; 1567 if ([hitView isKindOfClass:[WebHTMLView class]] && ![[(WebHTMLView *)hitView _webView] isHoverFeedbackSuspended]) 1568 view = (WebHTMLView *)hitView; 1569 1570 if (view) 1571 [view retain]; 1572 1573 if (lastHitView != view && lastHitView && [lastHitView _frame]) { 1574 // If we are moving out of a view (or frame), let's pretend the mouse moved 1575 // all the way out of that view. But we have to account for scrolling, because 1576 // WebCore doesn't understand our clipping. 1577 NSRect visibleRect = [[[[lastHitView _frame] frameView] _scrollView] documentVisibleRect]; 1578 float yScroll = visibleRect.origin.y; 1579 float xScroll = visibleRect.origin.x; 1580 1581 NSEvent *event = [NSEvent mouseEventWithType:NSMouseMoved 1582 location:NSMakePoint(-1 - xScroll, -1 - yScroll) 1583 modifierFlags:[[NSApp currentEvent] modifierFlags] 1584 timestamp:[NSDate timeIntervalSinceReferenceDate] 1585 windowNumber:[[view window] windowNumber] 1586 context:[[NSApp currentEvent] context] 1587 eventNumber:0 clickCount:0 pressure:0]; 1588 if (Frame* lastHitCoreFrame = core([lastHitView _frame])) 1589 lastHitCoreFrame->eventHandler()->mouseMoved(event); 1590 } 1591 1592 lastHitView = view; 1593 1594 if (view) { 1595 if (Frame* coreFrame = core([view _frame])) 1596 coreFrame->eventHandler()->mouseMoved(event); 1597 1598 [view release]; 1599 } 1600} 1601 1602// keep in sync with WebPasteboardHelper::insertablePasteboardTypes 1603+ (NSArray *)_insertablePasteboardTypes 1604{ 1605 static NSArray *types = nil; 1606 if (!types) { 1607 types = [[NSArray alloc] initWithObjects:WebArchivePboardType, NSHTMLPboardType, NSFilenamesPboardType, NSTIFFPboardType, NSPDFPboardType, 1608#if defined(BUILDING_ON_TIGER) || defined(BUILDING_ON_LEOPARD) 1609 NSPICTPboardType, 1610#endif 1611 NSURLPboardType, NSRTFDPboardType, NSRTFPboardType, NSStringPboardType, NSColorPboardType, kUTTypePNG, nil]; 1612 CFRetain(types); 1613 } 1614 return types; 1615} 1616 1617+ (NSArray *)_selectionPasteboardTypes 1618{ 1619 // FIXME: We should put data for NSHTMLPboardType on the pasteboard but Microsoft Excel doesn't like our format of HTML (3640423). 1620 return [NSArray arrayWithObjects:WebArchivePboardType, NSRTFDPboardType, NSRTFPboardType, NSStringPboardType, nil]; 1621} 1622 1623- (NSImage *)_dragImageForURL:(NSString*)urlString withLabel:(NSString*)label 1624{ 1625 BOOL drawURLString = YES; 1626 BOOL clipURLString = NO, clipLabelString = NO; 1627 1628 if (!label) { 1629 drawURLString = NO; 1630 label = urlString; 1631 } 1632 1633 NSFont *labelFont = [[NSFontManager sharedFontManager] convertFont:[NSFont systemFontOfSize:DRAG_LINK_LABEL_FONT_SIZE] 1634 toHaveTrait:NSBoldFontMask]; 1635 NSFont *urlFont = [NSFont systemFontOfSize: DRAG_LINK_URL_FONT_SIZE]; 1636 NSSize labelSize; 1637 labelSize.width = [label _web_widthWithFont: labelFont]; 1638 labelSize.height = [labelFont ascender] - [labelFont descender]; 1639 if (labelSize.width > MAX_DRAG_LABEL_WIDTH){ 1640 labelSize.width = MAX_DRAG_LABEL_WIDTH; 1641 clipLabelString = YES; 1642 } 1643 1644 NSSize imageSize, urlStringSize; 1645 imageSize.width = labelSize.width + DRAG_LABEL_BORDER_X * 2.0f; 1646 imageSize.height = labelSize.height + DRAG_LABEL_BORDER_Y * 2.0f; 1647 if (drawURLString) { 1648 urlStringSize.width = [urlString _web_widthWithFont: urlFont]; 1649 urlStringSize.height = [urlFont ascender] - [urlFont descender]; 1650 imageSize.height += urlStringSize.height; 1651 if (urlStringSize.width > MAX_DRAG_LABEL_WIDTH) { 1652 imageSize.width = MAX(MAX_DRAG_LABEL_WIDTH + DRAG_LABEL_BORDER_X * 2.0f, MIN_DRAG_LABEL_WIDTH_BEFORE_CLIP); 1653 clipURLString = YES; 1654 } else { 1655 imageSize.width = MAX(labelSize.width + DRAG_LABEL_BORDER_X * 2.0f, urlStringSize.width + DRAG_LABEL_BORDER_X * 2.0f); 1656 } 1657 } 1658 NSImage *dragImage = [[[NSImage alloc] initWithSize: imageSize] autorelease]; 1659 [dragImage lockFocus]; 1660 1661 [[NSColor colorWithDeviceRed: 0.7f green: 0.7f blue: 0.7f alpha: 0.8f] set]; 1662 1663 // Drag a rectangle with rounded corners/ 1664 NSBezierPath *path = [NSBezierPath bezierPath]; 1665 [path appendBezierPathWithOvalInRect: NSMakeRect(0.0f, 0.0f, DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f)]; 1666 [path appendBezierPathWithOvalInRect: NSMakeRect(0, imageSize.height - DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f)]; 1667 [path appendBezierPathWithOvalInRect: NSMakeRect(imageSize.width - DRAG_LABEL_RADIUS * 2.0f, imageSize.height - DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f)]; 1668 [path appendBezierPathWithOvalInRect: NSMakeRect(imageSize.width - DRAG_LABEL_RADIUS * 2.0f, 0.0f, DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f)]; 1669 1670 [path appendBezierPathWithRect: NSMakeRect(DRAG_LABEL_RADIUS, 0.0f, imageSize.width - DRAG_LABEL_RADIUS * 2.0f, imageSize.height)]; 1671 [path appendBezierPathWithRect: NSMakeRect(0.0f, DRAG_LABEL_RADIUS, DRAG_LABEL_RADIUS + 10.0f, imageSize.height - 2.0f * DRAG_LABEL_RADIUS)]; 1672 [path appendBezierPathWithRect: NSMakeRect(imageSize.width - DRAG_LABEL_RADIUS - 20.0f, DRAG_LABEL_RADIUS, DRAG_LABEL_RADIUS + 20.0f, imageSize.height - 2.0f * DRAG_LABEL_RADIUS)]; 1673 [path fill]; 1674 1675 NSColor *topColor = [NSColor colorWithDeviceWhite:0.0f alpha:0.75f]; 1676 NSColor *bottomColor = [NSColor colorWithDeviceWhite:1.0f alpha:0.5f]; 1677 if (drawURLString) { 1678 if (clipURLString) 1679 urlString = [WebStringTruncator centerTruncateString: urlString toWidth:imageSize.width - (DRAG_LABEL_BORDER_X * 2.0f) withFont:urlFont]; 1680 1681 [urlString _web_drawDoubledAtPoint:NSMakePoint(DRAG_LABEL_BORDER_X, DRAG_LABEL_BORDER_Y - [urlFont descender]) 1682 withTopColor:topColor bottomColor:bottomColor font:urlFont]; 1683 } 1684 1685 if (clipLabelString) 1686 label = [WebStringTruncator rightTruncateString: label toWidth:imageSize.width - (DRAG_LABEL_BORDER_X * 2.0f) withFont:labelFont]; 1687 [label _web_drawDoubledAtPoint:NSMakePoint (DRAG_LABEL_BORDER_X, imageSize.height - DRAG_LABEL_BORDER_Y_OFFSET - [labelFont pointSize]) 1688 withTopColor:topColor bottomColor:bottomColor font:labelFont]; 1689 1690 [dragImage unlockFocus]; 1691 1692 return dragImage; 1693} 1694 1695- (NSImage *)_dragImageForLinkElement:(NSDictionary *)element 1696{ 1697 NSURL *linkURL = [element objectForKey: WebElementLinkURLKey]; 1698 1699 NSString *label = [element objectForKey: WebElementLinkLabelKey]; 1700 NSString *urlString = [linkURL _web_userVisibleString]; 1701 return [self _dragImageForURL:urlString withLabel:label]; 1702} 1703 1704- (void)pasteboardChangedOwner:(NSPasteboard *)pasteboard 1705{ 1706 [self setPromisedDragTIFFDataSource:0]; 1707} 1708 1709- (void)pasteboard:(NSPasteboard *)pasteboard provideDataForType:(NSString *)type 1710{ 1711 if ([type isEqual:NSRTFDPboardType] && [[pasteboard types] containsObject:WebArchivePboardType]) { 1712 WebArchive *archive = [[WebArchive alloc] initWithData:[pasteboard dataForType:WebArchivePboardType]]; 1713 [pasteboard _web_writePromisedRTFDFromArchive:archive containsImage:[[pasteboard types] containsObject:NSTIFFPboardType]]; 1714 [archive release]; 1715 } else if ([type isEqual:NSTIFFPboardType] && [self promisedDragTIFFDataSource]) { 1716 if (Image* image = [self promisedDragTIFFDataSource]->image()) 1717 [pasteboard setData:(NSData *)image->getTIFFRepresentation() forType:NSTIFFPboardType]; 1718 [self setPromisedDragTIFFDataSource:0]; 1719 } 1720} 1721 1722- (void)_handleAutoscrollForMouseDragged:(NSEvent *)event 1723{ 1724 [self autoscroll:event]; 1725 [self _startAutoscrollTimer:event]; 1726} 1727 1728- (WebPluginController *)_pluginController 1729{ 1730 return _private->pluginController; 1731} 1732 1733- (void)_layoutForPrinting 1734{ 1735 // Set printing mode temporarily so we can adjust the size of the view. This will allow 1736 // AppKit's pagination code to use the correct height for the page content. Leaving printing 1737 // mode on indefinitely would interfere with Mail's printing mechanism (at least), so we just 1738 // turn it off again after adjusting the size. 1739 [self _web_setPrintingModeRecursiveAndAdjustViewSize]; 1740 [self _web_clearPrintingModeRecursive]; 1741} 1742 1743- (void)_smartInsertForString:(NSString *)pasteString replacingRange:(DOMRange *)rangeToReplace beforeString:(NSString **)beforeString afterString:(NSString **)afterString 1744{ 1745 if (!pasteString || !rangeToReplace || ![[self _webView] smartInsertDeleteEnabled]) { 1746 if (beforeString) 1747 *beforeString = nil; 1748 if (afterString) 1749 *afterString = nil; 1750 return; 1751 } 1752 1753 [[self _frame] _smartInsertForString:pasteString replacingRange:rangeToReplace beforeString:beforeString afterString:afterString]; 1754} 1755 1756- (BOOL)_canSmartReplaceWithPasteboard:(NSPasteboard *)pasteboard 1757{ 1758 return [[self _webView] smartInsertDeleteEnabled] && [[pasteboard types] containsObject:WebSmartPastePboardType]; 1759} 1760 1761- (void)_startAutoscrollTimer:(NSEvent *)triggerEvent 1762{ 1763 if (_private->autoscrollTimer == nil) { 1764 _private->autoscrollTimer = [[NSTimer scheduledTimerWithTimeInterval:AUTOSCROLL_INTERVAL 1765 target:self selector:@selector(_autoscroll) userInfo:nil repeats:YES] retain]; 1766 _private->autoscrollTriggerEvent = [triggerEvent retain]; 1767 } 1768} 1769 1770// FIXME: _selectionRect is deprecated in favor of selectionRect, which is in protocol WebDocumentSelection. 1771// We can't remove this yet because it's still in use by Mail. 1772- (NSRect)_selectionRect 1773{ 1774 return [self selectionRect]; 1775} 1776 1777- (void)_stopAutoscrollTimer 1778{ 1779 NSTimer *timer = _private->autoscrollTimer; 1780 _private->autoscrollTimer = nil; 1781 [_private->autoscrollTriggerEvent release]; 1782 _private->autoscrollTriggerEvent = nil; 1783 [timer invalidate]; 1784 [timer release]; 1785} 1786 1787- (void)_autoscroll 1788{ 1789 // Guarantee that the autoscroll timer is invalidated, even if we don't receive 1790 // a mouse up event. 1791 BOOL isStillDown = CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState, kCGMouseButtonLeft); 1792 if (!isStillDown){ 1793 [self _stopAutoscrollTimer]; 1794 return; 1795 } 1796 1797 NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseDragged 1798 location:[[self window] convertScreenToBase:[NSEvent mouseLocation]] 1799 modifierFlags:[[NSApp currentEvent] modifierFlags] 1800 timestamp:[NSDate timeIntervalSinceReferenceDate] 1801 windowNumber:[[self window] windowNumber] 1802 context:[[NSApp currentEvent] context] 1803 eventNumber:0 clickCount:0 pressure:0]; 1804 [self mouseDragged:fakeEvent]; 1805} 1806 1807- (BOOL)_canEdit 1808{ 1809 Frame* coreFrame = core([self _frame]); 1810 return coreFrame && coreFrame->editor()->canEdit(); 1811} 1812 1813- (BOOL)_canEditRichly 1814{ 1815 Frame* coreFrame = core([self _frame]); 1816 return coreFrame && coreFrame->editor()->canEditRichly(); 1817} 1818 1819- (BOOL)_canAlterCurrentSelection 1820{ 1821 return [self _hasSelectionOrInsertionPoint] && [self _isEditable]; 1822} 1823 1824- (BOOL)_hasSelection 1825{ 1826 Frame* coreFrame = core([self _frame]); 1827 return coreFrame && coreFrame->selection()->isRange(); 1828} 1829 1830- (BOOL)_hasSelectionOrInsertionPoint 1831{ 1832 Frame* coreFrame = core([self _frame]); 1833 return coreFrame && coreFrame->selection()->isCaretOrRange(); 1834} 1835 1836- (BOOL)_hasInsertionPoint 1837{ 1838 Frame* coreFrame = core([self _frame]); 1839 return coreFrame && coreFrame->selection()->isCaret(); 1840} 1841 1842- (BOOL)_isEditable 1843{ 1844 Frame* coreFrame = core([self _frame]); 1845 return coreFrame && coreFrame->selection()->isContentEditable(); 1846} 1847 1848- (BOOL)_transparentBackground 1849{ 1850 return _private->transparentBackground; 1851} 1852 1853- (void)_setTransparentBackground:(BOOL)f 1854{ 1855 _private->transparentBackground = f; 1856} 1857 1858- (NSImage *)_selectionDraggingImage 1859{ 1860 if ([self _hasSelection]) { 1861 NSImage *dragImage = core([self _frame])->selectionImage(); 1862 [dragImage _web_dissolveToFraction:WebDragImageAlpha]; 1863 return dragImage; 1864 } 1865 return nil; 1866} 1867 1868- (NSRect)_selectionDraggingRect 1869{ 1870 // Mail currently calls this method. We can eliminate it when Mail no longer calls it. 1871 return [self selectionRect]; 1872} 1873 1874- (DOMNode *)_insertOrderedList 1875{ 1876 Frame* coreFrame = core([self _frame]); 1877 return coreFrame ? kit(coreFrame->editor()->insertOrderedList().get()) : nil; 1878} 1879 1880- (DOMNode *)_insertUnorderedList 1881{ 1882 Frame* coreFrame = core([self _frame]); 1883 return coreFrame ? kit(coreFrame->editor()->insertUnorderedList().get()) : nil; 1884} 1885 1886- (BOOL)_canIncreaseSelectionListLevel 1887{ 1888 Frame* coreFrame = core([self _frame]); 1889 return coreFrame && coreFrame->editor()->canIncreaseSelectionListLevel(); 1890} 1891 1892- (BOOL)_canDecreaseSelectionListLevel 1893{ 1894 Frame* coreFrame = core([self _frame]); 1895 return coreFrame && coreFrame->editor()->canDecreaseSelectionListLevel(); 1896} 1897 1898- (DOMNode *)_increaseSelectionListLevel 1899{ 1900 Frame* coreFrame = core([self _frame]); 1901 return coreFrame ? kit(coreFrame->editor()->increaseSelectionListLevel().get()) : nil; 1902} 1903 1904- (DOMNode *)_increaseSelectionListLevelOrdered 1905{ 1906 Frame* coreFrame = core([self _frame]); 1907 return coreFrame ? kit(coreFrame->editor()->increaseSelectionListLevelOrdered().get()) : nil; 1908} 1909 1910- (DOMNode *)_increaseSelectionListLevelUnordered 1911{ 1912 Frame* coreFrame = core([self _frame]); 1913 return coreFrame ? kit(coreFrame->editor()->increaseSelectionListLevelUnordered().get()) : nil; 1914} 1915 1916- (void)_decreaseSelectionListLevel 1917{ 1918 Frame* coreFrame = core([self _frame]); 1919 if (coreFrame) 1920 coreFrame->editor()->decreaseSelectionListLevel(); 1921} 1922 1923- (void)_setHighlighter:(id<WebHTMLHighlighter>)highlighter ofType:(NSString*)type 1924{ 1925 if (!_private->highlighters) 1926 _private->highlighters = [[NSMutableDictionary alloc] init]; 1927 [_private->highlighters setObject:highlighter forKey:type]; 1928} 1929 1930- (void)_removeHighlighterOfType:(NSString*)type 1931{ 1932 [_private->highlighters removeObjectForKey:type]; 1933} 1934 1935- (void)_writeSelectionToPasteboard:(NSPasteboard *)pasteboard 1936{ 1937 ASSERT([self _hasSelection]); 1938 NSArray *types = [self pasteboardTypesForSelection]; 1939 1940 // Don't write RTFD to the pasteboard when the copied attributed string has no attachments. 1941 NSAttributedString *attributedString = [self selectedAttributedString]; 1942 NSMutableArray *mutableTypes = nil; 1943 if (![attributedString containsAttachments]) { 1944 mutableTypes = [types mutableCopy]; 1945 [mutableTypes removeObject:NSRTFDPboardType]; 1946 types = mutableTypes; 1947 } 1948 1949 [pasteboard declareTypes:types owner:[self _topHTMLView]]; 1950 [self _writeSelectionWithPasteboardTypes:types toPasteboard:pasteboard cachedAttributedString:attributedString]; 1951 [mutableTypes release]; 1952} 1953 1954- (void)close 1955{ 1956 // Check for a nil _private here in case we were created with initWithCoder. In that case, the WebView is just throwing 1957 // out the archived WebHTMLView and recreating a new one if needed. So close doesn't need to do anything in that case. 1958 if (!_private || _private->closed) 1959 return; 1960 1961 _private->closed = YES; 1962 1963 [self _cancelUpdateMouseoverTimer]; 1964 [self _clearLastHitViewIfSelf]; 1965 [self _removeMouseMovedObserverUnconditionally]; 1966 [self _removeWindowObservers]; 1967 [self _removeSuperviewObservers]; 1968 [_private->pluginController destroyAllPlugins]; 1969 [_private->pluginController setDataSource:nil]; 1970 // remove tooltips before clearing _private so removeTrackingRect: will work correctly 1971 [self removeAllToolTips]; 1972 1973#if USE(ACCELERATED_COMPOSITING) 1974 if (_private->layerHostingView) 1975 [[self _webView] _stoppedAcceleratedCompositingForFrame:[self _frame]]; 1976#endif 1977 1978 [_private clear]; 1979 1980 Page* page = core([self _webView]); 1981 if (page) 1982 page->dragController()->setDraggingImageURL(KURL()); 1983} 1984 1985- (BOOL)_hasHTMLDocument 1986{ 1987 Frame* coreFrame = core([self _frame]); 1988 if (!coreFrame) 1989 return NO; 1990 Document* document = coreFrame->document(); 1991 return document && document->isHTMLDocument(); 1992} 1993 1994- (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard 1995 forType:(NSString *)pboardType 1996 inContext:(DOMRange *)context 1997 subresources:(NSArray **)subresources 1998{ 1999 if (pboardType == WebArchivePboardType) { 2000 WebArchive *archive = [[WebArchive alloc] initWithData:[pasteboard dataForType:WebArchivePboardType]]; 2001 if (subresources) 2002 *subresources = [archive subresources]; 2003 DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithArchive:archive]; 2004 [archive release]; 2005 return fragment; 2006 } 2007 if (pboardType == NSFilenamesPboardType) 2008 return [self _documentFragmentWithPaths:[pasteboard propertyListForType:NSFilenamesPboardType]]; 2009 2010 if (pboardType == NSHTMLPboardType) { 2011 NSString *HTMLString = [pasteboard stringForType:NSHTMLPboardType]; 2012 // This is a hack to make Microsoft's HTML pasteboard data work. See 3778785. 2013 if ([HTMLString hasPrefix:@"Version:"]) { 2014 NSRange range = [HTMLString rangeOfString:@"<html" options:NSCaseInsensitiveSearch]; 2015 if (range.location != NSNotFound) 2016 HTMLString = [HTMLString substringFromIndex:range.location]; 2017 } 2018 if ([HTMLString length] == 0) 2019 return nil; 2020 2021 return [[self _frame] _documentFragmentWithMarkupString:HTMLString baseURLString:nil]; 2022 } 2023 2024 // The _hasHTMLDocument clause here is a workaround for a bug in NSAttributedString: Radar 5052369. 2025 // If we call _documentFromRange on an XML document we'll get "setInnerHTML: method not found". 2026 // FIXME: Remove this once bug 5052369 is fixed. 2027 if ([self _hasHTMLDocument] && pboardType == NSRTFPboardType || pboardType == NSRTFDPboardType) { 2028 NSAttributedString *string = nil; 2029 if (pboardType == NSRTFDPboardType) 2030 string = [[NSAttributedString alloc] initWithRTFD:[pasteboard dataForType:NSRTFDPboardType] documentAttributes:NULL]; 2031 if (string == nil) 2032 string = [[NSAttributedString alloc] initWithRTF:[pasteboard dataForType:NSRTFPboardType] documentAttributes:NULL]; 2033 if (string == nil) 2034 return nil; 2035 2036 NSDictionary *documentAttributes = [[NSDictionary alloc] initWithObjectsAndKeys: 2037 [[self class] _excludedElementsForAttributedStringConversion], NSExcludedElementsDocumentAttribute, 2038 self, @"WebResourceHandler", nil]; 2039 NSArray *s; 2040 2041 BOOL wasDeferringCallbacks = [[self _webView] defersCallbacks]; 2042 if (!wasDeferringCallbacks) 2043 [[self _webView] setDefersCallbacks:YES]; 2044 2045 DOMDocumentFragment *fragment = [string _documentFromRange:NSMakeRange(0, [string length]) 2046 document:[[self _frame] DOMDocument] 2047 documentAttributes:documentAttributes 2048 subresources:&s]; 2049 if (subresources) 2050 *subresources = s; 2051 2052 NSEnumerator *e = [s objectEnumerator]; 2053 WebResource *r; 2054 while ((r = [e nextObject])) 2055 [[self _dataSource] addSubresource:r]; 2056 2057 if (!wasDeferringCallbacks) 2058 [[self _webView] setDefersCallbacks:NO]; 2059 2060 [documentAttributes release]; 2061 [string release]; 2062 return fragment; 2063 } 2064 if (pboardType == NSTIFFPboardType) { 2065 WebResource *resource = [[WebResource alloc] initWithData:[pasteboard dataForType:NSTIFFPboardType] 2066 URL:uniqueURLWithRelativePart(@"image.tiff") 2067 MIMEType:@"image/tiff" 2068 textEncodingName:nil 2069 frameName:nil]; 2070 DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithImageResource:resource]; 2071 [resource release]; 2072 return fragment; 2073 } 2074 if (pboardType == NSPDFPboardType) { 2075 WebResource *resource = [[WebResource alloc] initWithData:[pasteboard dataForType:NSPDFPboardType] 2076 URL:uniqueURLWithRelativePart(@"application.pdf") 2077 MIMEType:@"application/pdf" 2078 textEncodingName:nil 2079 frameName:nil]; 2080 DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithImageResource:resource]; 2081 [resource release]; 2082 return fragment; 2083 } 2084#if defined(BUILDING_ON_TIGER) || defined(BUILDING_ON_LEOPARD) 2085 if (pboardType == NSPICTPboardType) { 2086 WebResource *resource = [[WebResource alloc] initWithData:[pasteboard dataForType:NSPICTPboardType] 2087 URL:uniqueURLWithRelativePart(@"image.pict") 2088 MIMEType:@"image/pict" 2089 textEncodingName:nil 2090 frameName:nil]; 2091 DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithImageResource:resource]; 2092 [resource release]; 2093 return fragment; 2094 } 2095#endif 2096 // Only 10.5 and higher support setting and retrieving pasteboard types with UTIs, but we don't believe 2097 // that any applications on Tiger put types for which we only have a UTI, like PNG, on the pasteboard. 2098 if ([pboardType isEqualToString:(NSString*)kUTTypePNG]) { 2099 WebResource *resource = [[WebResource alloc] initWithData:[pasteboard dataForType:(NSString*)kUTTypePNG] 2100 URL:uniqueURLWithRelativePart(@"image.png") 2101 MIMEType:@"image/png" 2102 textEncodingName:nil 2103 frameName:nil]; 2104 DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithImageResource:resource]; 2105 [resource release]; 2106 return fragment; 2107 } 2108 if (pboardType == NSURLPboardType) { 2109 NSURL *URL = [NSURL URLFromPasteboard:pasteboard]; 2110 DOMDocument* document = [[self _frame] DOMDocument]; 2111 ASSERT(document); 2112 if (!document) 2113 return nil; 2114 DOMHTMLAnchorElement *anchor = (DOMHTMLAnchorElement *)[document createElement:@"a"]; 2115 NSString *URLString = [URL _web_originalDataAsString]; // Original data is ASCII-only, so there is no need to precompose. 2116 if ([URLString length] == 0) 2117 return nil; 2118 NSString *URLTitleString = [[pasteboard stringForType:WebURLNamePboardType] precomposedStringWithCanonicalMapping]; 2119 DOMText *text = [document createTextNode:URLTitleString]; 2120 [anchor setHref:URLString]; 2121 [anchor appendChild:text]; 2122 DOMDocumentFragment *fragment = [document createDocumentFragment]; 2123 [fragment appendChild:anchor]; 2124 return fragment; 2125 } 2126 if (pboardType == NSStringPboardType) 2127 return kit(createFragmentFromText(core(context), [[pasteboard stringForType:NSStringPboardType] precomposedStringWithCanonicalMapping]).get()); 2128 return nil; 2129} 2130 2131#if ENABLE(NETSCAPE_PLUGIN_API) 2132- (void)_pauseNullEventsForAllNetscapePlugins 2133{ 2134 NSArray *subviews = [self subviews]; 2135 unsigned int subviewCount = [subviews count]; 2136 unsigned int subviewIndex; 2137 2138 for (subviewIndex = 0; subviewIndex < subviewCount; subviewIndex++) { 2139 NSView *subview = [subviews objectAtIndex:subviewIndex]; 2140 if ([subview isKindOfClass:[WebBaseNetscapePluginView class]]) 2141 [(WebBaseNetscapePluginView *)subview stopTimers]; 2142 } 2143} 2144#endif 2145 2146#if ENABLE(NETSCAPE_PLUGIN_API) 2147- (void)_resumeNullEventsForAllNetscapePlugins 2148{ 2149 NSArray *subviews = [self subviews]; 2150 unsigned int subviewCount = [subviews count]; 2151 unsigned int subviewIndex; 2152 2153 for (subviewIndex = 0; subviewIndex < subviewCount; subviewIndex++) { 2154 NSView *subview = [subviews objectAtIndex:subviewIndex]; 2155 if ([subview isKindOfClass:[WebBaseNetscapePluginView class]]) 2156 [(WebBaseNetscapePluginView *)subview restartTimers]; 2157 } 2158} 2159#endif 2160 2161- (BOOL)_isUsingAcceleratedCompositing 2162{ 2163#if USE(ACCELERATED_COMPOSITING) 2164 return _private->layerHostingView != nil; 2165#else 2166 return NO; 2167#endif 2168} 2169 2170@end 2171 2172@implementation NSView (WebHTMLViewFileInternal) 2173 2174- (void)_web_addDescendantWebHTMLViewsToArray:(NSMutableArray *)array 2175{ 2176 unsigned count = [_subviews count]; 2177 for (unsigned i = 0; i < count; ++i) { 2178 NSView *child = [_subviews objectAtIndex:i]; 2179 if ([child isKindOfClass:[WebHTMLView class]]) 2180 [array addObject:child]; 2181 [child _web_addDescendantWebHTMLViewsToArray:array]; 2182 } 2183} 2184 2185@end 2186 2187@implementation NSMutableDictionary (WebHTMLViewFileInternal) 2188 2189- (void)_web_setObjectIfNotNil:(id)object forKey:(id)key 2190{ 2191 if (object == nil) { 2192 [self removeObjectForKey:key]; 2193 } else { 2194 [self setObject:object forKey:key]; 2195 } 2196} 2197 2198@end 2199 2200static bool matchesExtensionOrEquivalent(NSString *filename, NSString *extension) 2201{ 2202 NSString *extensionAsSuffix = [@"." stringByAppendingString:extension]; 2203 return [filename _webkit_hasCaseInsensitiveSuffix:extensionAsSuffix] 2204 || ([extension _webkit_isCaseInsensitiveEqualToString:@"jpeg"] 2205 && [filename _webkit_hasCaseInsensitiveSuffix:@".jpg"]); 2206} 2207 2208#ifdef BUILDING_ON_TIGER 2209 2210// The following is a workaround for 2211// <rdar://problem/3429631> window stops getting mouse moved events after first tooltip appears 2212// The trick is to define a category on NSToolTipPanel that implements setAcceptsMouseMovedEvents:. 2213// Since the category will be searched before the real class, we'll prevent the flag from being 2214// set on the tool tip panel. 2215 2216@interface NSToolTipPanel : NSPanel 2217@end 2218 2219@interface NSToolTipPanel (WebHTMLViewFileInternal) 2220@end 2221 2222@implementation NSToolTipPanel (WebHTMLViewFileInternal) 2223 2224- (void)setAcceptsMouseMovedEvents:(BOOL)flag 2225{ 2226 // Do nothing, preventing the tool tip panel from trying to accept mouse-moved events. 2227} 2228 2229@end 2230 2231#endif 2232 2233@interface NSArray (WebHTMLView) 2234- (void)_web_makePluginViewsPerformSelector:(SEL)selector withObject:(id)object; 2235@end 2236 2237@implementation WebHTMLView 2238 2239+ (void)initialize 2240{ 2241 [NSApp registerServicesMenuSendTypes:[[self class] _selectionPasteboardTypes] 2242 returnTypes:[[self class] _insertablePasteboardTypes]]; 2243 JSC::initializeThreading(); 2244#ifndef BUILDING_ON_TIGER 2245 WebCoreObjCFinalizeOnMainThread(self); 2246#endif 2247} 2248 2249- (id)initWithFrame:(NSRect)frame 2250{ 2251 self = [super initWithFrame:frame]; 2252 if (!self) 2253 return nil; 2254 2255 [self setFocusRingType:NSFocusRingTypeNone]; 2256 2257 // Make all drawing go through us instead of subviews. 2258 [self _setDrawsOwnDescendants:YES]; 2259 2260 _private = [[WebHTMLViewPrivate alloc] init]; 2261 2262 _private->pluginController = [[WebPluginController alloc] initWithDocumentView:self]; 2263 2264 return self; 2265} 2266 2267- (void)dealloc 2268{ 2269 if (WebCoreObjCScheduleDeallocateOnMainThread([WebHTMLView class], self)) 2270 return; 2271 2272 // We can't assert that close has already been called because 2273 // this view can be removed from it's superview, even though 2274 // it could be needed later, so close if needed. 2275 [self close]; 2276 [_private release]; 2277 _private = nil; 2278 [super dealloc]; 2279} 2280 2281- (void)finalize 2282{ 2283 ASSERT_MAIN_THREAD(); 2284 // We can't assert that close has already been called because 2285 // this view can be removed from it's superview, even though 2286 // it could be needed later, so close if needed. 2287 [self close]; 2288 [super finalize]; 2289} 2290 2291// Returns YES if the delegate returns YES (so we should do no more work). 2292- (BOOL)callDelegateDoCommandBySelectorIfNeeded:(SEL)selector 2293{ 2294 BOOL callerAlreadyCalledDelegate = _private->selectorForDoCommandBySelector == selector; 2295 _private->selectorForDoCommandBySelector = 0; 2296 if (callerAlreadyCalledDelegate) 2297 return NO; 2298 WebView *webView = [self _webView]; 2299 return [[webView _editingDelegateForwarder] webView:webView doCommandBySelector:selector]; 2300} 2301 2302static String commandNameForSelector(SEL selector) 2303{ 2304 // Change a few command names into ones supported by WebCore::Editor. 2305 // If this list gets too long we might decide we need to use a hash table. 2306 if (selector == @selector(insertParagraphSeparator:) || selector == @selector(insertNewlineIgnoringFieldEditor:)) 2307 return "InsertNewline"; 2308 if (selector == @selector(insertTabIgnoringFieldEditor:)) 2309 return "InsertTab"; 2310 if (selector == @selector(pageDown:)) 2311 return "MovePageDown"; 2312 if (selector == @selector(pageDownAndModifySelection:)) 2313 return "MovePageDownAndModifySelection"; 2314 if (selector == @selector(pageUp:)) 2315 return "MovePageUp"; 2316 if (selector == @selector(pageUpAndModifySelection:)) 2317 return "MovePageUpAndModifySelection"; 2318 2319 // Remove the trailing colon. 2320 const char* selectorName = sel_getName(selector); 2321 size_t selectorNameLength = strlen(selectorName); 2322 if (selectorNameLength < 2 || selectorName[selectorNameLength - 1] != ':') 2323 return String(); 2324 return String(selectorName, selectorNameLength - 1); 2325} 2326 2327- (Editor::Command)coreCommandBySelector:(SEL)selector 2328{ 2329 Frame* coreFrame = core([self _frame]); 2330 if (!coreFrame) 2331 return Editor::Command(); 2332 return coreFrame->editor()->command(commandNameForSelector(selector)); 2333} 2334 2335- (Editor::Command)coreCommandByName:(const char*)name 2336{ 2337 Frame* coreFrame = core([self _frame]); 2338 if (!coreFrame) 2339 return Editor::Command(); 2340 return coreFrame->editor()->command(name); 2341} 2342 2343- (void)executeCoreCommandBySelector:(SEL)selector 2344{ 2345 if ([self callDelegateDoCommandBySelectorIfNeeded:selector]) 2346 return; 2347 [self coreCommandBySelector:selector].execute(); 2348} 2349 2350- (void)executeCoreCommandByName:(const char*)name 2351{ 2352 [self coreCommandByName:name].execute(); 2353} 2354 2355// These commands are forwarded to the Editor object in WebCore. 2356// Ideally we'd do this for all editing commands; more of the code 2357// should be moved from here to there, and more commands should be 2358// added to this list. 2359 2360// FIXME: Maybe we should set things up so that all these share a single method implementation function. 2361// The functions are identical. 2362 2363#define WEBCORE_COMMAND(command) - (void)command:(id)sender { [self executeCoreCommandBySelector:_cmd]; } 2364 2365WEBCORE_COMMAND(alignCenter) 2366WEBCORE_COMMAND(alignJustified) 2367WEBCORE_COMMAND(alignLeft) 2368WEBCORE_COMMAND(alignRight) 2369WEBCORE_COMMAND(copy) 2370WEBCORE_COMMAND(cut) 2371WEBCORE_COMMAND(delete) 2372WEBCORE_COMMAND(deleteBackward) 2373WEBCORE_COMMAND(deleteBackwardByDecomposingPreviousCharacter) 2374WEBCORE_COMMAND(deleteForward) 2375WEBCORE_COMMAND(deleteToBeginningOfLine) 2376WEBCORE_COMMAND(deleteToBeginningOfParagraph) 2377WEBCORE_COMMAND(deleteToEndOfLine) 2378WEBCORE_COMMAND(deleteToEndOfParagraph) 2379WEBCORE_COMMAND(deleteToMark) 2380WEBCORE_COMMAND(deleteWordBackward) 2381WEBCORE_COMMAND(deleteWordForward) 2382WEBCORE_COMMAND(ignoreSpelling) 2383WEBCORE_COMMAND(indent) 2384WEBCORE_COMMAND(insertBacktab) 2385WEBCORE_COMMAND(insertLineBreak) 2386WEBCORE_COMMAND(insertNewline) 2387WEBCORE_COMMAND(insertNewlineIgnoringFieldEditor) 2388WEBCORE_COMMAND(insertParagraphSeparator) 2389WEBCORE_COMMAND(insertTab) 2390WEBCORE_COMMAND(insertTabIgnoringFieldEditor) 2391WEBCORE_COMMAND(makeTextWritingDirectionLeftToRight) 2392WEBCORE_COMMAND(makeTextWritingDirectionNatural) 2393WEBCORE_COMMAND(makeTextWritingDirectionRightToLeft) 2394WEBCORE_COMMAND(moveBackward) 2395WEBCORE_COMMAND(moveBackwardAndModifySelection) 2396WEBCORE_COMMAND(moveDown) 2397WEBCORE_COMMAND(moveDownAndModifySelection) 2398WEBCORE_COMMAND(moveForward) 2399WEBCORE_COMMAND(moveForwardAndModifySelection) 2400WEBCORE_COMMAND(moveLeft) 2401WEBCORE_COMMAND(moveLeftAndModifySelection) 2402WEBCORE_COMMAND(moveParagraphBackwardAndModifySelection) 2403WEBCORE_COMMAND(moveParagraphForwardAndModifySelection) 2404WEBCORE_COMMAND(moveRight) 2405WEBCORE_COMMAND(moveRightAndModifySelection) 2406WEBCORE_COMMAND(moveToBeginningOfDocument) 2407WEBCORE_COMMAND(moveToBeginningOfDocumentAndModifySelection) 2408WEBCORE_COMMAND(moveToBeginningOfLine) 2409WEBCORE_COMMAND(moveToBeginningOfLineAndModifySelection) 2410WEBCORE_COMMAND(moveToBeginningOfParagraph) 2411WEBCORE_COMMAND(moveToBeginningOfParagraphAndModifySelection) 2412WEBCORE_COMMAND(moveToBeginningOfSentence) 2413WEBCORE_COMMAND(moveToBeginningOfSentenceAndModifySelection) 2414WEBCORE_COMMAND(moveToEndOfDocument) 2415WEBCORE_COMMAND(moveToEndOfDocumentAndModifySelection) 2416WEBCORE_COMMAND(moveToEndOfLine) 2417WEBCORE_COMMAND(moveToEndOfLineAndModifySelection) 2418WEBCORE_COMMAND(moveToEndOfParagraph) 2419WEBCORE_COMMAND(moveToEndOfParagraphAndModifySelection) 2420WEBCORE_COMMAND(moveToEndOfSentence) 2421WEBCORE_COMMAND(moveToEndOfSentenceAndModifySelection) 2422WEBCORE_COMMAND(moveToLeftEndOfLine) 2423WEBCORE_COMMAND(moveToLeftEndOfLineAndModifySelection) 2424WEBCORE_COMMAND(moveToRightEndOfLine) 2425WEBCORE_COMMAND(moveToRightEndOfLineAndModifySelection) 2426WEBCORE_COMMAND(moveUp) 2427WEBCORE_COMMAND(moveUpAndModifySelection) 2428WEBCORE_COMMAND(moveWordBackward) 2429WEBCORE_COMMAND(moveWordBackwardAndModifySelection) 2430WEBCORE_COMMAND(moveWordForward) 2431WEBCORE_COMMAND(moveWordForwardAndModifySelection) 2432WEBCORE_COMMAND(moveWordLeft) 2433WEBCORE_COMMAND(moveWordLeftAndModifySelection) 2434WEBCORE_COMMAND(moveWordRight) 2435WEBCORE_COMMAND(moveWordRightAndModifySelection) 2436WEBCORE_COMMAND(outdent) 2437WEBCORE_COMMAND(pageDown) 2438WEBCORE_COMMAND(pageDownAndModifySelection) 2439WEBCORE_COMMAND(pageUp) 2440WEBCORE_COMMAND(pageUpAndModifySelection) 2441WEBCORE_COMMAND(selectAll) 2442WEBCORE_COMMAND(selectLine) 2443WEBCORE_COMMAND(selectParagraph) 2444WEBCORE_COMMAND(selectSentence) 2445WEBCORE_COMMAND(selectToMark) 2446WEBCORE_COMMAND(selectWord) 2447WEBCORE_COMMAND(setMark) 2448WEBCORE_COMMAND(subscript) 2449WEBCORE_COMMAND(superscript) 2450WEBCORE_COMMAND(swapWithMark) 2451WEBCORE_COMMAND(transpose) 2452WEBCORE_COMMAND(underline) 2453WEBCORE_COMMAND(unscript) 2454WEBCORE_COMMAND(yank) 2455WEBCORE_COMMAND(yankAndSelect) 2456 2457#undef WEBCORE_COMMAND 2458 2459#define COMMAND_PROLOGUE if ([self callDelegateDoCommandBySelectorIfNeeded:_cmd]) return; 2460 2461- (IBAction)takeFindStringFromSelection:(id)sender 2462{ 2463 COMMAND_PROLOGUE 2464 2465 if (![self _hasSelection]) { 2466 NSBeep(); 2467 return; 2468 } 2469 2470 [NSPasteboard _web_setFindPasteboardString:[self selectedString] withOwner:self]; 2471} 2472 2473- (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pasteboard types:(NSArray *)types 2474{ 2475 [pasteboard declareTypes:types owner:[self _topHTMLView]]; 2476 [self writeSelectionWithPasteboardTypes:types toPasteboard:pasteboard]; 2477 return YES; 2478} 2479 2480- (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pasteboard 2481{ 2482 Frame* coreFrame = core([self _frame]); 2483 if (!coreFrame) 2484 return NO; 2485 if (coreFrame->selection()->isContentRichlyEditable()) 2486 [self _pasteWithPasteboard:pasteboard allowPlainText:YES]; 2487 else 2488 [self _pasteAsPlainTextWithPasteboard:pasteboard]; 2489 return YES; 2490} 2491 2492- (id)validRequestorForSendType:(NSString *)sendType returnType:(NSString *)returnType 2493{ 2494 BOOL isSendTypeOK = !sendType || ([[self pasteboardTypesForSelection] containsObject:sendType] && [self _hasSelection]); 2495 BOOL isReturnTypeOK = !returnType || ([[[self class] _insertablePasteboardTypes] containsObject:returnType] && [self _isEditable]); 2496 if (isSendTypeOK && isReturnTypeOK) 2497 return self; 2498 return [[self nextResponder] validRequestorForSendType:sendType returnType:returnType]; 2499} 2500 2501// jumpToSelection is the old name for what AppKit now calls centerSelectionInVisibleArea. Safari 2502// was using the old jumpToSelection selector in its menu. Newer versions of Safari will use the 2503// selector centerSelectionInVisibleArea. We'll leave the old selector in place for two reasons: 2504// (1) Compatibility between older Safari and newer WebKit; (2) other WebKit-based applications 2505// might be using the selector, and we don't want to break them. 2506- (void)jumpToSelection:(id)sender 2507{ 2508 COMMAND_PROLOGUE 2509 2510 if (Frame* coreFrame = core([self _frame])) 2511 coreFrame->revealSelection(ScrollAlignment::alignCenterAlways); 2512} 2513 2514- (NSCellStateValue)selectionHasStyle:(CSSStyleDeclaration*)style 2515{ 2516 Frame* coreFrame = core([self _frame]); 2517 if (!coreFrame) 2518 return NSOffState; 2519 return kit(coreFrame->editor()->selectionHasStyle(style)); 2520} 2521 2522- (BOOL)validateUserInterfaceItemWithoutDelegate:(id <NSValidatedUserInterfaceItem>)item 2523{ 2524 SEL action = [item action]; 2525 RefPtr<Frame> frame = core([self _frame]); 2526 2527 if (!frame) 2528 return NO; 2529 2530 if (Document* doc = frame->document()) { 2531 if (doc->isPluginDocument()) 2532 return NO; 2533 if (doc->isImageDocument()) { 2534 if (action == @selector(copy:)) 2535 return frame->loader()->isComplete(); 2536 return NO; 2537 } 2538 } 2539 2540 if (action == @selector(changeSpelling:) 2541 || action == @selector(_changeSpellingFromMenu:) 2542 || action == @selector(checkSpelling:) 2543 || action == @selector(complete:) 2544 || action == @selector(pasteFont:)) 2545 return [self _canEdit]; 2546 2547 if (action == @selector(showGuessPanel:)) { 2548#ifndef BUILDING_ON_TIGER 2549 // Match OS X AppKit behavior for post-Tiger. Don't change Tiger behavior. 2550 NSMenuItem *menuItem = (NSMenuItem *)item; 2551 if ([menuItem isKindOfClass:[NSMenuItem class]]) { 2552 BOOL panelShowing = [[[NSSpellChecker sharedSpellChecker] spellingPanel] isVisible]; 2553 [menuItem setTitle:panelShowing 2554 ? UI_STRING("Hide Spelling and Grammar", "menu item title") 2555 : UI_STRING("Show Spelling and Grammar", "menu item title")]; 2556 } 2557#endif 2558 return [self _canEdit]; 2559 } 2560 2561 if (action == @selector(changeBaseWritingDirection:) 2562 || action == @selector(makeBaseWritingDirectionLeftToRight:) 2563 || action == @selector(makeBaseWritingDirectionRightToLeft:)) { 2564 NSWritingDirection writingDirection; 2565 2566 if (action == @selector(changeBaseWritingDirection:)) { 2567 writingDirection = static_cast<NSWritingDirection>([item tag]); 2568 if (writingDirection == NSWritingDirectionNatural) 2569 return NO; 2570 } else if (action == @selector(makeBaseWritingDirectionLeftToRight:)) 2571 writingDirection = NSWritingDirectionLeftToRight; 2572 else 2573 writingDirection = NSWritingDirectionRightToLeft; 2574 2575 NSMenuItem *menuItem = (NSMenuItem *)item; 2576 if ([menuItem isKindOfClass:[NSMenuItem class]]) { 2577 RefPtr<CSSStyleDeclaration> style = CSSMutableStyleDeclaration::create(); 2578 ExceptionCode ec; 2579 style->setProperty("direction", writingDirection == NSWritingDirectionLeftToRight ? "LTR" : "RTL", ec); 2580 [menuItem setState:frame->editor()->selectionHasStyle(style.get())]; 2581 } 2582 return [self _canEdit]; 2583 } 2584 2585 if (action == @selector(makeBaseWritingDirectionNatural:)) { 2586 NSMenuItem *menuItem = (NSMenuItem *)item; 2587 if ([menuItem isKindOfClass:[NSMenuItem class]]) 2588 [menuItem setState:NSOffState]; 2589 return NO; 2590 } 2591 2592 if (action == @selector(toggleBaseWritingDirection:)) { 2593 NSMenuItem *menuItem = (NSMenuItem *)item; 2594 if ([menuItem isKindOfClass:[NSMenuItem class]]) { 2595 RefPtr<CSSStyleDeclaration> style = CSSMutableStyleDeclaration::create(); 2596 ExceptionCode ec; 2597 style->setProperty("direction", "RTL", ec); 2598 // Take control of the title of the menu item instead of just checking/unchecking it because 2599 // a check would be ambiguous. 2600 [menuItem setTitle:frame->editor()->selectionHasStyle(style.get()) 2601 ? UI_STRING("Left to Right", "Left to Right context menu item") 2602 : UI_STRING("Right to Left", "Right to Left context menu item")]; 2603 } 2604 return [self _canEdit]; 2605 } 2606 2607 if (action == @selector(changeAttributes:) 2608 || action == @selector(changeColor:) 2609 || action == @selector(changeFont:)) 2610 return [self _canEditRichly]; 2611 2612 if (action == @selector(capitalizeWord:) 2613 || action == @selector(lowercaseWord:) 2614 || action == @selector(uppercaseWord:)) 2615 return [self _hasSelection] && [self _isEditable]; 2616 2617 if (action == @selector(centerSelectionInVisibleArea:) 2618 || action == @selector(jumpToSelection:) 2619 || action == @selector(copyFont:)) 2620 return [self _hasSelection] || ([self _isEditable] && [self _hasInsertionPoint]); 2621 2622 if (action == @selector(changeDocumentBackgroundColor:)) 2623 return [[self _webView] isEditable] && [self _canEditRichly]; 2624 2625 if (action == @selector(_ignoreSpellingFromMenu:) 2626 || action == @selector(_learnSpellingFromMenu:) 2627 || action == @selector(takeFindStringFromSelection:)) 2628 return [self _hasSelection]; 2629 2630 if (action == @selector(paste:) || action == @selector(pasteAsPlainText:)) 2631 return frame && (frame->editor()->canDHTMLPaste() || frame->editor()->canPaste()); 2632 2633 if (action == @selector(pasteAsRichText:)) 2634 return frame && (frame->editor()->canDHTMLPaste() 2635 || (frame->editor()->canPaste() && frame->selection()->isContentRichlyEditable())); 2636 2637 if (action == @selector(performFindPanelAction:)) 2638 return NO; 2639 2640 if (action == @selector(_lookUpInDictionaryFromMenu:)) 2641 return [self _hasSelection]; 2642 2643#ifndef BUILDING_ON_TIGER 2644 if (action == @selector(toggleGrammarChecking:)) { 2645 // FIXME 4799134: WebView is the bottleneck for this grammar-checking logic, but we must validate 2646 // the selector here because we implement it here, and we must implement it here because the AppKit 2647 // code checks the first responder. 2648 NSMenuItem *menuItem = (NSMenuItem *)item; 2649 if ([menuItem isKindOfClass:[NSMenuItem class]]) 2650 [menuItem setState:[self isGrammarCheckingEnabled] ? NSOnState : NSOffState]; 2651 return YES; 2652 } 2653#endif 2654 2655#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 2656 if (action == @selector(orderFrontSubstitutionsPanel:)) { 2657 NSMenuItem *menuItem = (NSMenuItem *)item; 2658 if ([menuItem isKindOfClass:[NSMenuItem class]]) { 2659 BOOL panelShowing = [[[NSSpellChecker sharedSpellChecker] substitutionsPanel] isVisible]; 2660 [menuItem setTitle:panelShowing 2661 ? UI_STRING("Hide Substitutions", "menu item title") 2662 : UI_STRING("Show Substitutions", "menu item title")]; 2663 } 2664 return [self _canEdit]; 2665 } 2666 // FIXME 4799134: WebView is the bottleneck for this logic, but we must validate 2667 // the selector here because we implement it here, and we must implement it here because the AppKit 2668 // code checks the first responder. 2669 if (action == @selector(toggleSmartInsertDelete:)) { 2670 NSMenuItem *menuItem = (NSMenuItem *)item; 2671 if ([menuItem isKindOfClass:[NSMenuItem class]]) 2672 [menuItem setState:[self smartInsertDeleteEnabled] ? NSOnState : NSOffState]; 2673 return [self _canEdit]; 2674 } 2675 if (action == @selector(toggleAutomaticQuoteSubstitution:)) { 2676 NSMenuItem *menuItem = (NSMenuItem *)item; 2677 if ([menuItem isKindOfClass:[NSMenuItem class]]) 2678 [menuItem setState:[self isAutomaticQuoteSubstitutionEnabled] ? NSOnState : NSOffState]; 2679 return [self _canEdit]; 2680 } 2681 if (action == @selector(toggleAutomaticLinkDetection:)) { 2682 NSMenuItem *menuItem = (NSMenuItem *)item; 2683 if ([menuItem isKindOfClass:[NSMenuItem class]]) 2684 [menuItem setState:[self isAutomaticLinkDetectionEnabled] ? NSOnState : NSOffState]; 2685 return [self _canEdit]; 2686 } 2687 if (action == @selector(toggleAutomaticDashSubstitution:)) { 2688 NSMenuItem *menuItem = (NSMenuItem *)item; 2689 if ([menuItem isKindOfClass:[NSMenuItem class]]) 2690 [menuItem setState:[self isAutomaticDashSubstitutionEnabled] ? NSOnState : NSOffState]; 2691 return [self _canEdit]; 2692 } 2693 if (action == @selector(toggleAutomaticTextReplacement:)) { 2694 NSMenuItem *menuItem = (NSMenuItem *)item; 2695 if ([menuItem isKindOfClass:[NSMenuItem class]]) 2696 [menuItem setState:[self isAutomaticTextReplacementEnabled] ? NSOnState : NSOffState]; 2697 return [self _canEdit]; 2698 } 2699 if (action == @selector(toggleAutomaticSpellingCorrection:)) { 2700 NSMenuItem *menuItem = (NSMenuItem *)item; 2701 if ([menuItem isKindOfClass:[NSMenuItem class]]) 2702 [menuItem setState:[self isAutomaticSpellingCorrectionEnabled] ? NSOnState : NSOffState]; 2703 return [self _canEdit]; 2704 } 2705#endif 2706 2707 Editor::Command command = [self coreCommandBySelector:action]; 2708 if (command.isSupported()) { 2709 NSMenuItem *menuItem = (NSMenuItem *)item; 2710 if ([menuItem isKindOfClass:[NSMenuItem class]]) 2711 [menuItem setState:kit(command.state())]; 2712 return command.isEnabled(); 2713 } 2714 2715 return YES; 2716} 2717 2718- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item 2719{ 2720 // This can be called during teardown when _webView is nil. Return NO when this happens, because CallUIDelegateReturningBoolean 2721 // assumes the WebVIew is non-nil. 2722 if (![self _webView]) 2723 return NO; 2724 BOOL result = [self validateUserInterfaceItemWithoutDelegate:item]; 2725 return CallUIDelegateReturningBoolean(result, [self _webView], @selector(webView:validateUserInterfaceItem:defaultValidation:), item, result); 2726} 2727 2728- (BOOL)acceptsFirstResponder 2729{ 2730 // Don't accept first responder when we first click on this view. 2731 // We have to pass the event down through WebCore first to be sure we don't hit a subview. 2732 // Do accept first responder at any other time, for example from keyboard events, 2733 // or from calls back from WebCore once we begin mouse-down event handling. 2734 NSEvent *event = [NSApp currentEvent]; 2735 if ([event type] == NSLeftMouseDown 2736 && !_private->handlingMouseDownEvent 2737 && NSPointInRect([event locationInWindow], [self convertRect:[self visibleRect] toView:nil])) { 2738 return NO; 2739 } 2740 return YES; 2741} 2742 2743- (BOOL)maintainsInactiveSelection 2744{ 2745 // This method helps to determine whether the WebHTMLView should maintain 2746 // an inactive selection when it's not first responder. 2747 // Traditionally, these views have not maintained such selections, 2748 // clearing them when the view was not first responder. However, 2749 // to fix bugs like this one: 2750 // <rdar://problem/3672088>: "Editable WebViews should maintain a selection even 2751 // when they're not firstResponder" 2752 // it was decided to add a switch to act more like an NSTextView. 2753 2754 if ([[self _webView] maintainsInactiveSelection]) 2755 return YES; 2756 2757 // Predict the case where we are losing first responder status only to 2758 // gain it back again. Want to keep the selection in that case. 2759 id nextResponder = [[self window] _newFirstResponderAfterResigning]; 2760 if ([nextResponder isKindOfClass:[NSScrollView class]]) { 2761 id contentView = [nextResponder contentView]; 2762 if (contentView) 2763 nextResponder = contentView; 2764 } 2765 if ([nextResponder isKindOfClass:[NSClipView class]]) { 2766 id documentView = [nextResponder documentView]; 2767 if (documentView) 2768 nextResponder = documentView; 2769 } 2770 if (nextResponder == self) 2771 return YES; 2772 2773 Frame* coreFrame = core([self _frame]); 2774 bool selectionIsEditable = coreFrame && coreFrame->selection()->isContentEditable(); 2775 bool nextResponderIsInWebView = [nextResponder isKindOfClass:[NSView class]] 2776 && [nextResponder isDescendantOf:[[[self _webView] mainFrame] frameView]]; 2777 2778 return selectionIsEditable && nextResponderIsInWebView; 2779} 2780 2781- (void)addMouseMovedObserver 2782{ 2783 if (!_private->dataSource || ![self _isTopHTMLView] || _private->observingMouseMovedNotifications) 2784 return; 2785 2786 // Unless the Dashboard asks us to do this for all windows, keep an observer going only for the key window. 2787 if (!([[self window] isKeyWindow] 2788#if ENABLE(DASHBOARD_SUPPORT) 2789 || [[self _webView] _dashboardBehavior:WebDashboardBehaviorAlwaysSendMouseEventsToAllWindows] 2790#endif 2791 )) 2792 return; 2793 2794 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mouseMovedNotification:) 2795 name:WKMouseMovedNotification() object:nil]; 2796 [self _frameOrBoundsChanged]; 2797 _private->observingMouseMovedNotifications = true; 2798} 2799 2800- (void)removeMouseMovedObserver 2801{ 2802#if ENABLE(DASHBOARD_SUPPORT) 2803 // Don't remove the observer if we're running the Dashboard. 2804 if ([[self _webView] _dashboardBehavior:WebDashboardBehaviorAlwaysSendMouseEventsToAllWindows]) 2805 return; 2806#endif 2807 2808 [[self _webView] _mouseDidMoveOverElement:nil modifierFlags:0]; 2809 [self _removeMouseMovedObserverUnconditionally]; 2810} 2811 2812- (void)addSuperviewObservers 2813{ 2814 if (_private->observingSuperviewNotifications) 2815 return; 2816 2817 NSView *superview = [self superview]; 2818 if (!superview || ![self window]) 2819 return; 2820 2821 NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; 2822 [notificationCenter addObserver:self selector:@selector(_frameOrBoundsChanged) name:NSViewFrameDidChangeNotification object:superview]; 2823 [notificationCenter addObserver:self selector:@selector(_frameOrBoundsChanged) name:NSViewBoundsDidChangeNotification object:superview]; 2824 2825 // In addition to registering for frame/bounds change notifications, call -_frameOrBoundsChanged. 2826 // It will check the current scroll against the previous layout's scroll. We need to 2827 // do this here to catch the case where the WebView is laid out at one size, removed from its 2828 // window, resized, and inserted into another window. Our frame/bounds changed notifications 2829 // will not be sent in that situation, since we only watch for changes while in the view hierarchy. 2830 [self _frameOrBoundsChanged]; 2831 2832 _private->observingSuperviewNotifications = true; 2833} 2834 2835- (void)addWindowObservers 2836{ 2837 if (_private->observingWindowNotifications) 2838 return; 2839 2840 NSWindow *window = [self window]; 2841 if (!window) 2842 return; 2843 2844 NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; 2845 [notificationCenter addObserver:self selector:@selector(windowDidBecomeKey:) name:NSWindowDidBecomeKeyNotification object:nil]; 2846 [notificationCenter addObserver:self selector:@selector(windowDidResignKey:) name:NSWindowDidResignKeyNotification object:nil]; 2847 [notificationCenter addObserver:self selector:@selector(windowWillClose:) name:NSWindowWillCloseNotification object:window]; 2848 2849 _private->observingWindowNotifications = true; 2850} 2851 2852- (void)viewWillMoveToSuperview:(NSView *)newSuperview 2853{ 2854 [self _removeSuperviewObservers]; 2855} 2856 2857- (void)viewDidMoveToSuperview 2858{ 2859 if ([self superview] != nil) 2860 [self addSuperviewObservers]; 2861} 2862 2863- (void)viewWillMoveToWindow:(NSWindow *)window 2864{ 2865 // Don't do anything if we aren't initialized. This happens 2866 // when decoding a WebView. When WebViews are decoded their subviews 2867 // are created by initWithCoder: and so won't be normally 2868 // initialized. The stub views are discarded by WebView. 2869 if (!_private) 2870 return; 2871 2872 // FIXME: Some of these calls may not work because this view may be already removed from it's superview. 2873 [self _removeMouseMovedObserverUnconditionally]; 2874 [self _removeWindowObservers]; 2875 [self _removeSuperviewObservers]; 2876 [self _cancelUpdateMouseoverTimer]; 2877 2878 [[self _pluginController] stopAllPlugins]; 2879} 2880 2881- (void)viewDidMoveToWindow 2882{ 2883 // Don't do anything if we aren't initialized. This happens 2884 // when decoding a WebView. When WebViews are decoded their subviews 2885 // are created by initWithCoder: and so won't be normally 2886 // initialized. The stub views are discarded by WebView. 2887 if (!_private || _private->closed) 2888 return; 2889 2890 [self _stopAutoscrollTimer]; 2891 if ([self window]) { 2892 _private->lastScrollPosition = [[self superview] bounds].origin; 2893 [self addWindowObservers]; 2894 [self addSuperviewObservers]; 2895 [self addMouseMovedObserver]; 2896 2897 [[self _pluginController] startAllPlugins]; 2898 2899 _private->lastScrollPosition = NSZeroPoint; 2900 } 2901} 2902 2903- (void)viewWillMoveToHostWindow:(NSWindow *)hostWindow 2904{ 2905 [[self subviews] _web_makePluginViewsPerformSelector:@selector(viewWillMoveToHostWindow:) withObject:hostWindow]; 2906} 2907 2908- (void)viewDidMoveToHostWindow 2909{ 2910 [[self subviews] _web_makePluginViewsPerformSelector:@selector(viewDidMoveToHostWindow) withObject:nil]; 2911} 2912 2913 2914- (void)addSubview:(NSView *)view 2915{ 2916 [super addSubview:view]; 2917 2918 if ([WebPluginController isPlugInView:view]) 2919 [[self _pluginController] addPlugin:view]; 2920} 2921 2922- (void)willRemoveSubview:(NSView *)subview 2923{ 2924 if ([WebPluginController isPlugInView:subview]) 2925 [[self _pluginController] destroyPlugin:subview]; 2926 2927 [super willRemoveSubview:subview]; 2928} 2929 2930- (void)reapplyStyles 2931{ 2932 if (!_private->needsToApplyStyles) 2933 return; 2934 2935#ifdef LOG_TIMES 2936 double start = CFAbsoluteTimeGetCurrent(); 2937#endif 2938 2939 if (Frame* coreFrame = core([self _frame])) { 2940 if (FrameView* coreView = coreFrame->view()) 2941 coreView->setMediaType(_private->printing ? "print" : "screen"); 2942 if (Document* document = coreFrame->document()) 2943 document->setPrinting(_private->printing); 2944 coreFrame->reapplyStyles(); 2945 } 2946 2947#ifdef LOG_TIMES 2948 double thisTime = CFAbsoluteTimeGetCurrent() - start; 2949 LOG(Timing, "%s apply style seconds = %f", [self URL], thisTime); 2950#endif 2951 2952 _private->needsToApplyStyles = NO; 2953} 2954 2955// Do a layout, but set up a new fixed width for the purposes of doing printing layout. 2956// minPageWidth==0 implies a non-printing layout 2957- (void)layoutToMinimumPageWidth:(float)minPageWidth maximumPageWidth:(float)maxPageWidth adjustingViewSize:(BOOL)adjustViewSize 2958{ 2959 [self reapplyStyles]; 2960 2961 if (![self _needsLayout]) 2962 return; 2963 2964#ifdef LOG_TIMES 2965 double start = CFAbsoluteTimeGetCurrent(); 2966#endif 2967 2968 LOG(View, "%@ doing layout", self); 2969 2970 Frame* coreFrame = core([self _frame]); 2971 if (!coreFrame) 2972 return; 2973 2974 if (FrameView* coreView = coreFrame->view()) { 2975 if (minPageWidth > 0.0) 2976 coreView->forceLayoutWithPageWidthRange(minPageWidth, maxPageWidth, adjustViewSize); 2977 else { 2978 coreView->forceLayout(!adjustViewSize); 2979 if (adjustViewSize) 2980 coreView->adjustViewSize(); 2981 } 2982 } 2983 2984#ifdef LOG_TIMES 2985 double thisTime = CFAbsoluteTimeGetCurrent() - start; 2986 LOG(Timing, "%s layout seconds = %f", [self URL], thisTime); 2987#endif 2988} 2989 2990- (void)layout 2991{ 2992 [self layoutToMinimumPageWidth:0.0f maximumPageWidth:0.0f adjustingViewSize:NO]; 2993} 2994 2995// Deliver mouseup events to the DOM for button 2. 2996- (void)rightMouseUp:(NSEvent *)event 2997{ 2998 // There's a chance that if we run a nested event loop the event will be released. 2999 // Retaining and then autoreleasing prevents that from causing a problem later here or 3000 // inside AppKit code. 3001 [[event retain] autorelease]; 3002 3003 [super rightMouseUp:event]; 3004 3005 if (Frame* coreframe = core([self _frame])) 3006 coreframe->eventHandler()->mouseUp(event); 3007} 3008 3009- (NSMenu *)menuForEvent:(NSEvent *)event 3010{ 3011 // There's a chance that if we run a nested event loop the event will be released. 3012 // Retaining and then autoreleasing prevents that from causing a problem later here or 3013 // inside AppKit code. 3014 [[event retain] autorelease]; 3015 3016 [_private->completionController endRevertingChange:NO moveLeft:NO]; 3017 3018 RefPtr<Frame> coreFrame = core([self _frame]); 3019 if (!coreFrame) 3020 return nil; 3021 3022 Page* page = coreFrame->page(); 3023 if (!page) 3024 return nil; 3025 3026 // Match behavior of other browsers by sending a mousedown event for right clicks. 3027 _private->handlingMouseDownEvent = YES; 3028 page->contextMenuController()->clearContextMenu(); 3029 coreFrame->eventHandler()->mouseDown(event); 3030 BOOL handledEvent = coreFrame->eventHandler()->sendContextMenuEvent(event); 3031 _private->handlingMouseDownEvent = NO; 3032 3033 if (!handledEvent) 3034 return nil; 3035 3036 // Re-get page, since it might have gone away during event handling. 3037 page = coreFrame->page(); 3038 if (!page) 3039 return nil; 3040 3041 ContextMenu* coreMenu = page->contextMenuController()->contextMenu(); 3042 if (!coreMenu) 3043 return nil; 3044 3045 NSArray* menuItems = coreMenu->platformDescription(); 3046 if (!menuItems) 3047 return nil; 3048 3049 NSUInteger count = [menuItems count]; 3050 if (!count) 3051 return nil; 3052 3053 NSMenu* menu = [[[NSMenu alloc] init] autorelease]; 3054 for (NSUInteger i = 0; i < count; i++) 3055 [menu addItem:[menuItems objectAtIndex:i]]; 3056 return menu; 3057} 3058 3059- (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag 3060{ 3061 return [self searchFor:string direction:forward caseSensitive:caseFlag wrap:wrapFlag startInSelection:NO]; 3062} 3063 3064- (void)clearFocus 3065{ 3066 Frame* coreFrame = core([self _frame]); 3067 if (!coreFrame) 3068 return; 3069 Document* document = coreFrame->document(); 3070 if (!document) 3071 return; 3072 3073 document->setFocusedNode(0); 3074} 3075 3076- (BOOL)isOpaque 3077{ 3078 return [[self _webView] drawsBackground]; 3079} 3080 3081- (void)setNeedsDisplay:(BOOL)flag 3082{ 3083 LOG(View, "%@ setNeedsDisplay:%@", self, flag ? @"YES" : @"NO"); 3084 [super setNeedsDisplay:flag]; 3085} 3086 3087- (void)setNeedsLayout: (BOOL)flag 3088{ 3089 LOG(View, "%@ setNeedsLayout:%@", self, flag ? @"YES" : @"NO"); 3090 if (!flag) 3091 return; // There's no way to say you don't need a layout. 3092 if (Frame* frame = core([self _frame])) { 3093 if (frame->document() && frame->document()->inPageCache()) 3094 return; 3095 if (FrameView* view = frame->view()) 3096 view->setNeedsLayout(); 3097 } 3098} 3099 3100- (void)setNeedsToApplyStyles: (BOOL)flag 3101{ 3102 LOG(View, "%@ setNeedsToApplyStyles:%@", self, flag ? @"YES" : @"NO"); 3103 _private->needsToApplyStyles = flag; 3104} 3105 3106- (void)drawSingleRect:(NSRect)rect 3107{ 3108 [NSGraphicsContext saveGraphicsState]; 3109 NSRectClip(rect); 3110 3111 ASSERT([[self superview] isKindOfClass:[WebClipView class]]); 3112 3113 [(WebClipView *)[self superview] setAdditionalClip:rect]; 3114 3115 @try { 3116 if ([self _transparentBackground]) { 3117 [[NSColor clearColor] set]; 3118 NSRectFill (rect); 3119 } 3120 3121 [[self _frame] _drawRect:rect contentsOnly:YES]; 3122 3123 WebView *webView = [self _webView]; 3124 3125 // This hack is needed for <rdar://problem/5023545>. We can hit a race condition where drawRect will be 3126 // called after the WebView has closed. If the client did not properly close the WebView and set the 3127 // UIDelegate to nil, then the UIDelegate will be stale and this code will crash. 3128 static BOOL version3OrLaterClient = WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITHOUT_QUICKBOOKS_QUIRK); 3129 if (version3OrLaterClient) 3130 [[webView _UIDelegateForwarder] webView:webView didDrawRect:[webView convertRect:rect fromView:self]]; 3131 3132 if (WebNodeHighlight *currentHighlight = [webView currentNodeHighlight]) 3133 [currentHighlight setNeedsUpdateInTargetViewRect:[self convertRect:rect toView:[currentHighlight targetView]]]; 3134 3135 [(WebClipView *)[self superview] resetAdditionalClip]; 3136 3137 [NSGraphicsContext restoreGraphicsState]; 3138 } @catch (NSException *localException) { 3139 [(WebClipView *)[self superview] resetAdditionalClip]; 3140 [NSGraphicsContext restoreGraphicsState]; 3141 LOG_ERROR("Exception caught while drawing: %@", localException); 3142 [localException raise]; 3143 } 3144} 3145 3146- (void)drawRect:(NSRect)rect 3147{ 3148 ASSERT_MAIN_THREAD(); 3149 LOG(View, "%@ drawing", self); 3150 3151 const NSRect *rects; 3152 NSInteger count; 3153 [self getRectsBeingDrawn:&rects count:&count]; 3154 3155 BOOL subviewsWereSetAside = _private->subviewsSetAside; 3156 if (subviewsWereSetAside) 3157 [self _restoreSubviews]; 3158 3159#ifdef LOG_TIMES 3160 double start = CFAbsoluteTimeGetCurrent(); 3161#endif 3162 3163 if ([[self _webView] _mustDrawUnionedRect:rect singleRects:rects count:count]) 3164 [self drawSingleRect:rect]; 3165 else 3166 for (int i = 0; i < count; ++i) 3167 [self drawSingleRect:rects[i]]; 3168 3169#ifdef LOG_TIMES 3170 double thisTime = CFAbsoluteTimeGetCurrent() - start; 3171 LOG(Timing, "%s draw seconds = %f", widget->part()->baseURL().URL().latin1(), thisTime); 3172#endif 3173 3174 if (subviewsWereSetAside) 3175 [self _setAsideSubviews]; 3176 3177#if USE(ACCELERATED_COMPOSITING) 3178 if ([[self _webView] _needsOneShotDrawingSynchronization]) { 3179 // Disable screen updates so that any layer changes committed here 3180 // don't show up on the screen before the window flush at the end 3181 // of the current window display. 3182 [[self window] disableScreenUpdatesUntilFlush]; 3183 3184 // Make sure any layer changes that happened as a result of layout 3185 // via -viewWillDraw are committed. 3186 [CATransaction flush]; 3187 [[self _webView] _setNeedsOneShotDrawingSynchronization:NO]; 3188 } 3189#endif 3190} 3191 3192// Turn off the additional clip while computing our visibleRect. 3193- (NSRect)visibleRect 3194{ 3195 if (!([[self superview] isKindOfClass:[WebClipView class]])) 3196 return [super visibleRect]; 3197 3198 WebClipView *clipView = (WebClipView *)[self superview]; 3199 3200 BOOL hasAdditionalClip = [clipView hasAdditionalClip]; 3201 if (!hasAdditionalClip) { 3202 return [super visibleRect]; 3203 } 3204 3205 NSRect additionalClip = [clipView additionalClip]; 3206 [clipView resetAdditionalClip]; 3207 NSRect visibleRect = [super visibleRect]; 3208 [clipView setAdditionalClip:additionalClip]; 3209 return visibleRect; 3210} 3211 3212- (BOOL)isFlipped 3213{ 3214 return YES; 3215} 3216 3217- (void)windowDidBecomeKey:(NSNotification *)notification 3218{ 3219 if (!pthread_main_np()) { 3220 [self performSelectorOnMainThread:_cmd withObject:notification waitUntilDone:NO]; 3221 return; 3222 } 3223 3224 NSWindow *keyWindow = [notification object]; 3225 3226 if (keyWindow == [self window]) 3227 [self addMouseMovedObserver]; 3228} 3229 3230- (void)windowDidResignKey:(NSNotification *)notification 3231{ 3232 if (!pthread_main_np()) { 3233 [self performSelectorOnMainThread:_cmd withObject:notification waitUntilDone:NO]; 3234 return; 3235 } 3236 3237 NSWindow *formerKeyWindow = [notification object]; 3238 3239 if (formerKeyWindow == [self window]) 3240 [self removeMouseMovedObserver]; 3241 3242 if (formerKeyWindow == [self window] || formerKeyWindow == [[self window] attachedSheet]) 3243 [_private->completionController endRevertingChange:NO moveLeft:NO]; 3244} 3245 3246- (void)windowWillClose:(NSNotification *)notification 3247{ 3248 if (!pthread_main_np()) { 3249 [self performSelectorOnMainThread:_cmd withObject:notification waitUntilDone:NO]; 3250 return; 3251 } 3252 3253 [_private->completionController endRevertingChange:NO moveLeft:NO]; 3254 [[self _pluginController] destroyAllPlugins]; 3255} 3256 3257- (void)scrollWheel:(NSEvent *)event 3258{ 3259 // There's a chance that responding to this event will run a nested event loop, and 3260 // fetching a new event might release the old one. Retaining and then autoreleasing 3261 // the current event prevents that from causing a problem inside WebKit or AppKit code. 3262 [[event retain] autorelease]; 3263 3264 Frame* frame = core([self _frame]); 3265 if (!frame || !frame->eventHandler()->wheelEvent(event)) 3266 [super scrollWheel:event]; 3267} 3268 3269- (BOOL)_isSelectionEvent:(NSEvent *)event 3270{ 3271 NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil]; 3272 return [[[self elementAtPoint:point allowShadowContent:YES] objectForKey:WebElementIsSelectedKey] boolValue]; 3273} 3274 3275- (BOOL)acceptsFirstMouse:(NSEvent *)event 3276{ 3277 // There's a chance that responding to this event will run a nested event loop, and 3278 // fetching a new event might release the old one. Retaining and then autoreleasing 3279 // the current event prevents that from causing a problem inside WebKit or AppKit code. 3280 [[event retain] autorelease]; 3281 3282 NSView *hitView = [self _hitViewForEvent:event]; 3283 WebHTMLView *hitHTMLView = [hitView isKindOfClass:[self class]] ? (WebHTMLView *)hitView : nil; 3284 3285#if ENABLE(DASHBOARD_SUPPORT) 3286 if ([[self _webView] _dashboardBehavior:WebDashboardBehaviorAlwaysAcceptsFirstMouse]) 3287 return YES; 3288#endif 3289 3290 if (hitHTMLView) { 3291 bool result = false; 3292 if (Frame* coreFrame = core([hitHTMLView _frame])) { 3293 coreFrame->eventHandler()->setActivationEventNumber([event eventNumber]); 3294 [hitHTMLView _setMouseDownEvent:event]; 3295 if ([hitHTMLView _isSelectionEvent:event]) 3296 result = coreFrame->eventHandler()->eventMayStartDrag(event); 3297 [hitHTMLView _setMouseDownEvent:nil]; 3298 } 3299 return result; 3300 } 3301 return [hitView acceptsFirstMouse:event]; 3302} 3303 3304- (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent *)event 3305{ 3306 // There's a chance that responding to this event will run a nested event loop, and 3307 // fetching a new event might release the old one. Retaining and then autoreleasing 3308 // the current event prevents that from causing a problem inside WebKit or AppKit code. 3309 [[event retain] autorelease]; 3310 3311 NSView *hitView = [self _hitViewForEvent:event]; 3312 WebHTMLView *hitHTMLView = [hitView isKindOfClass:[self class]] ? (WebHTMLView *)hitView : nil; 3313 if (hitHTMLView) { 3314 bool result = false; 3315 if ([hitHTMLView _isSelectionEvent:event]) { 3316 if (Frame* coreFrame = core([hitHTMLView _frame])) { 3317 [hitHTMLView _setMouseDownEvent:event]; 3318 result = coreFrame->eventHandler()->eventMayStartDrag(event); 3319 [hitHTMLView _setMouseDownEvent:nil]; 3320 } 3321 } 3322 return result; 3323 } 3324 return [hitView shouldDelayWindowOrderingForEvent:event]; 3325} 3326 3327- (void)mouseDown:(NSEvent *)event 3328{ 3329 // There's a chance that responding to this event will run a nested event loop, and 3330 // fetching a new event might release the old one. Retaining and then autoreleasing 3331 // the current event prevents that from causing a problem inside WebKit or AppKit code. 3332 [[event retain] autorelease]; 3333 3334 RetainPtr<WebHTMLView> protector = self; 3335 if ([[self inputContext] wantsToHandleMouseEvents] && [[self inputContext] handleMouseEvent:event]) 3336 return; 3337 3338 _private->handlingMouseDownEvent = YES; 3339 3340 // Record the mouse down position so we can determine drag hysteresis. 3341 [self _setMouseDownEvent:event]; 3342 3343 NSInputManager *currentInputManager = [NSInputManager currentInputManager]; 3344 if ([currentInputManager wantsToHandleMouseEvents] && [currentInputManager handleMouseEvent:event]) 3345 goto done; 3346 3347 [_private->completionController endRevertingChange:NO moveLeft:NO]; 3348 3349 // If the web page handles the context menu event and menuForEvent: returns nil, we'll get control click events here. 3350 // We don't want to pass them along to KHTML a second time. 3351 if (!([event modifierFlags] & NSControlKeyMask)) { 3352 _private->ignoringMouseDraggedEvents = NO; 3353 3354 // Don't do any mouseover while the mouse is down. 3355 [self _cancelUpdateMouseoverTimer]; 3356 3357 // Let WebCore get a chance to deal with the event. This will call back to us 3358 // to start the autoscroll timer if appropriate. 3359 if (Frame* coreframe = core([self _frame])) 3360 coreframe->eventHandler()->mouseDown(event); 3361 } 3362 3363done: 3364 _private->handlingMouseDownEvent = NO; 3365} 3366 3367- (void)dragImage:(NSImage *)dragImage 3368 at:(NSPoint)at 3369 offset:(NSSize)offset 3370 event:(NSEvent *)event 3371 pasteboard:(NSPasteboard *)pasteboard 3372 source:(id)source 3373 slideBack:(BOOL)slideBack 3374{ 3375 ASSERT(self == [self _topHTMLView]); 3376 [super dragImage:dragImage at:at offset:offset event:event pasteboard:pasteboard source:source slideBack:slideBack]; 3377} 3378 3379- (void)mouseDragged:(NSEvent *)event 3380{ 3381 // There's a chance that responding to this event will run a nested event loop, and 3382 // fetching a new event might release the old one. Retaining and then autoreleasing 3383 // the current event prevents that from causing a problem inside WebKit or AppKit code. 3384 [[event retain] autorelease]; 3385 3386 NSInputManager *currentInputManager = [NSInputManager currentInputManager]; 3387 if ([currentInputManager wantsToHandleMouseEvents] && [currentInputManager handleMouseEvent:event]) 3388 return; 3389 3390 [self retain]; 3391 3392 if (!_private->ignoringMouseDraggedEvents) { 3393 if (Frame* frame = core([self _frame])) { 3394 if (Page* page = frame->page()) 3395 page->mainFrame()->eventHandler()->mouseDragged(event); 3396 } 3397 } 3398 3399 [self release]; 3400} 3401 3402- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal 3403{ 3404 ASSERT(![self _webView] || [self _isTopHTMLView]); 3405 3406 Page* page = core([self _webView]); 3407 if (!page) 3408 return NSDragOperationNone; 3409 3410 // FIXME: Why do we override the source provided operation here? Why not in DragController::startDrag 3411 if (page->dragController()->sourceDragOperation() == DragOperationNone) 3412 return NSDragOperationGeneric | NSDragOperationCopy; 3413 3414 return (NSDragOperation)page->dragController()->sourceDragOperation(); 3415} 3416 3417- (void)draggedImage:(NSImage *)image movedTo:(NSPoint)screenLoc 3418{ 3419 ASSERT(![self _webView] || [self _isTopHTMLView]); 3420 3421 NSPoint windowImageLoc = [[self window] convertScreenToBase:screenLoc]; 3422 NSPoint windowMouseLoc = windowImageLoc; 3423 3424 if (Page* page = core([self _webView])) { 3425 DragController* dragController = page->dragController(); 3426 NSPoint windowMouseLoc = NSMakePoint(windowImageLoc.x + dragController->dragOffset().x(), windowImageLoc.y + dragController->dragOffset().y()); 3427 } 3428 3429 [[self _frame] _dragSourceMovedTo:windowMouseLoc]; 3430} 3431 3432- (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation 3433{ 3434 ASSERT(![self _webView] || [self _isTopHTMLView]); 3435 3436 NSPoint windowImageLoc = [[self window] convertScreenToBase:aPoint]; 3437 NSPoint windowMouseLoc = windowImageLoc; 3438 3439 if (Page* page = core([self _webView])) { 3440 DragController* dragController = page->dragController(); 3441 windowMouseLoc = NSMakePoint(windowImageLoc.x + dragController->dragOffset().x(), windowImageLoc.y + dragController->dragOffset().y()); 3442 dragController->dragEnded(); 3443 } 3444 3445 [[self _frame] _dragSourceEndedAt:windowMouseLoc operation:operation]; 3446 3447 // Prevent queued mouseDragged events from coming after the drag and fake mouseUp event. 3448 _private->ignoringMouseDraggedEvents = YES; 3449 3450 // Once the dragging machinery kicks in, we no longer get mouse drags or the up event. 3451 // WebCore expects to get balanced down/up's, so we must fake up a mouseup. 3452 NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseUp 3453 location:windowMouseLoc 3454 modifierFlags:[[NSApp currentEvent] modifierFlags] 3455 timestamp:[NSDate timeIntervalSinceReferenceDate] 3456 windowNumber:[[self window] windowNumber] 3457 context:[[NSApp currentEvent] context] 3458 eventNumber:0 clickCount:0 pressure:0]; 3459 [self mouseUp:fakeEvent]; // This will also update the mouseover state. 3460} 3461 3462- (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination 3463{ 3464 NSFileWrapper *wrapper = nil; 3465 NSURL *draggingImageURL = nil; 3466 3467 if (WebCore::CachedImage* tiffResource = [self promisedDragTIFFDataSource]) { 3468 3469 SharedBuffer *buffer = static_cast<CachedResource*>(tiffResource)->data(); 3470 if (!buffer) 3471 goto noPromisedData; 3472 3473 NSData *data = buffer->createNSData(); 3474 NSURLResponse *response = tiffResource->response().nsURLResponse(); 3475 draggingImageURL = [response URL]; 3476 wrapper = [[[NSFileWrapper alloc] initRegularFileWithContents:data] autorelease]; 3477 NSString* filename = [response suggestedFilename]; 3478 NSString* trueExtension(tiffResource->image()->filenameExtension()); 3479 if (!matchesExtensionOrEquivalent(filename, trueExtension)) 3480 filename = [[filename stringByAppendingString:@"."] stringByAppendingString:trueExtension]; 3481 [wrapper setPreferredFilename:filename]; 3482 } 3483 3484noPromisedData: 3485 3486 if (!wrapper) { 3487 ASSERT(![self _webView] || [self _isTopHTMLView]); 3488 Page* page = core([self _webView]); 3489 3490 //If a load occurs midway through a drag, the view may be detached, which gives 3491 //us no ability to get to the original Page, so we cannot access any drag state 3492 //FIXME: is there a way to recover? 3493 if (!page) 3494 return nil; 3495 3496 const KURL& imageURL = page->dragController()->draggingImageURL(); 3497 ASSERT(!imageURL.isEmpty()); 3498 draggingImageURL = imageURL; 3499 3500 wrapper = [[self _dataSource] _fileWrapperForURL:draggingImageURL]; 3501 } 3502 3503 if (wrapper == nil) { 3504 LOG_ERROR("Failed to create image file."); 3505 return nil; 3506 } 3507 3508 // FIXME: Report an error if we fail to create a file. 3509 NSString *path = [[dropDestination path] stringByAppendingPathComponent:[wrapper preferredFilename]]; 3510 path = [[NSFileManager defaultManager] _webkit_pathWithUniqueFilenameForPath:path]; 3511 if (![wrapper writeToFile:path atomically:NO updateFilenames:YES]) 3512 LOG_ERROR("Failed to create image file via -[NSFileWrapper writeToFile:atomically:updateFilenames:]"); 3513 3514 if (draggingImageURL) 3515 [[NSFileManager defaultManager] _webkit_setMetadataURL:[draggingImageURL absoluteString] referrer:nil atPath:path]; 3516 3517 return [NSArray arrayWithObject:[path lastPathComponent]]; 3518} 3519 3520- (void)mouseUp:(NSEvent *)event 3521{ 3522 // There's a chance that responding to this event will run a nested event loop, and 3523 // fetching a new event might release the old one. Retaining and then autoreleasing 3524 // the current event prevents that from causing a problem inside WebKit or AppKit code. 3525 [[event retain] autorelease]; 3526 3527 [self _setMouseDownEvent:nil]; 3528 3529 NSInputManager *currentInputManager = [NSInputManager currentInputManager]; 3530 if ([currentInputManager wantsToHandleMouseEvents] && [currentInputManager handleMouseEvent:event]) 3531 return; 3532 3533 [self retain]; 3534 3535 [self _stopAutoscrollTimer]; 3536 if (Frame* frame = core([self _frame])) { 3537 if (Page* page = frame->page()) 3538 page->mainFrame()->eventHandler()->mouseUp(event); 3539 } 3540 [self _updateMouseoverWithFakeEvent]; 3541 3542 [self release]; 3543} 3544 3545- (void)mouseMovedNotification:(NSNotification *)notification 3546{ 3547 [self _updateMouseoverWithEvent:[[notification userInfo] objectForKey:@"NSEvent"]]; 3548} 3549 3550// returning YES from this method is the way we tell AppKit that it is ok for this view 3551// to be in the key loop even when "tab to all controls" is not on. 3552- (BOOL)needsPanelToBecomeKey 3553{ 3554 return YES; 3555} 3556 3557// Utility function to make sure we don't return anything through the NSTextInput 3558// API when an editable region is not currently focused. 3559static BOOL isTextInput(Frame* coreFrame) 3560{ 3561 return coreFrame && !coreFrame->selection()->isNone() && coreFrame->selection()->isContentEditable(); 3562} 3563 3564static BOOL isInPasswordField(Frame* coreFrame) 3565{ 3566 return coreFrame && coreFrame->selection()->isInPasswordField(); 3567} 3568 3569- (BOOL)becomeFirstResponder 3570{ 3571 NSSelectionDirection direction = NSDirectSelection; 3572 if (![[self _webView] _isPerformingProgrammaticFocus]) 3573 direction = [[self window] keyViewSelectionDirection]; 3574 3575 [self _updateFontPanel]; 3576 3577 Frame* frame = core([self _frame]); 3578 if (!frame) 3579 return YES; 3580 3581 BOOL exposeInputContext = isTextInput(frame) && !isInPasswordField(frame); 3582 if (exposeInputContext != _private->exposeInputContext) { 3583 _private->exposeInputContext = exposeInputContext; 3584 [NSApp updateWindows]; 3585 } 3586 3587 frame->editor()->setStartNewKillRingSequence(true); 3588 3589 Page* page = frame->page(); 3590 if (!page) 3591 return YES; 3592 3593 if (![[self _webView] _isPerformingProgrammaticFocus]) 3594 page->focusController()->setFocusedFrame(frame); 3595 3596 page->focusController()->setFocused(true); 3597 3598 if (direction == NSDirectSelection) 3599 return YES; 3600 3601 if (Document* document = frame->document()) 3602 document->setFocusedNode(0); 3603 page->focusController()->setInitialFocus(direction == NSSelectingNext ? FocusDirectionForward : FocusDirectionBackward, 3604 frame->eventHandler()->currentKeyboardEvent().get()); 3605 return YES; 3606} 3607 3608- (BOOL)resignFirstResponder 3609{ 3610 BOOL resign = [super resignFirstResponder]; 3611 if (resign) { 3612 [_private->completionController endRevertingChange:NO moveLeft:NO]; 3613 Frame* coreFrame = core([self _frame]); 3614 if (!coreFrame) 3615 return resign; 3616 Page* page = coreFrame->page(); 3617 if (!page) 3618 return resign; 3619 if (![self maintainsInactiveSelection]) { 3620 [self deselectAll]; 3621 if (![[self _webView] _isPerformingProgrammaticFocus]) 3622 [self clearFocus]; 3623 } 3624 3625 id nextResponder = [[self window] _newFirstResponderAfterResigning]; 3626 bool nextResponderIsInWebView = [nextResponder isKindOfClass:[NSView class]] 3627 && [nextResponder isDescendantOf:[[[self _webView] mainFrame] frameView]]; 3628 if (!nextResponderIsInWebView) 3629 page->focusController()->setFocused(false); 3630 } 3631 return resign; 3632} 3633 3634- (void)setDataSource:(WebDataSource *)dataSource 3635{ 3636 ASSERT(dataSource); 3637 if (_private->dataSource != dataSource) { 3638 ASSERT(!_private->closed); 3639 BOOL hadDataSource = _private->dataSource != nil; 3640 3641 [dataSource retain]; 3642 [_private->dataSource release]; 3643 _private->dataSource = dataSource; 3644 [_private->pluginController setDataSource:dataSource]; 3645 3646 if (!hadDataSource) 3647 [self addMouseMovedObserver]; 3648 } 3649} 3650 3651- (void)dataSourceUpdated:(WebDataSource *)dataSource 3652{ 3653} 3654 3655// This is an override of an NSControl method that wants to repaint the entire view when the window resigns/becomes 3656// key. WebHTMLView is an NSControl only because it hosts NSCells that are painted by WebCore's Aqua theme 3657// renderer (and those cells must be hosted by an enclosing NSControl in order to paint properly). 3658- (void)updateCell:(NSCell*)cell 3659{ 3660} 3661 3662// Does setNeedsDisplay:NO as a side effect when printing is ending. 3663// pageWidth != 0 implies we will relayout to a new width 3664- (void)_setPrinting:(BOOL)printing minimumPageWidth:(float)minPageWidth maximumPageWidth:(float)maxPageWidth adjustViewSize:(BOOL)adjustViewSize 3665{ 3666 WebFrame *frame = [self _frame]; 3667 NSArray *subframes = [frame childFrames]; 3668 unsigned n = [subframes count]; 3669 unsigned i; 3670 for (i = 0; i != n; ++i) { 3671 WebFrame *subframe = [subframes objectAtIndex:i]; 3672 WebFrameView *frameView = [subframe frameView]; 3673 if ([[subframe _dataSource] _isDocumentHTML]) { 3674 [(WebHTMLView *)[frameView documentView] _setPrinting:printing minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:adjustViewSize]; 3675 } 3676 } 3677 3678 if (printing != _private->printing) { 3679 [_private->pageRects release]; 3680 _private->pageRects = nil; 3681 _private->printing = printing; 3682 if (!printing) 3683 _private->avoidingPrintOrphan = NO; 3684 [self setNeedsToApplyStyles:YES]; 3685 [self setNeedsLayout:YES]; 3686 [self layoutToMinimumPageWidth:minPageWidth maximumPageWidth:maxPageWidth adjustingViewSize:adjustViewSize]; 3687 if (!printing) { 3688 // Can't do this when starting printing or nested printing won't work, see 3491427. 3689 [self setNeedsDisplay:NO]; 3690 } 3691 } 3692} 3693 3694- (BOOL)canPrintHeadersAndFooters 3695{ 3696 return YES; 3697} 3698 3699// This is needed for the case where the webview is embedded in the view that's being printed. 3700// It shouldn't be called when the webview is being printed directly. 3701- (void)adjustPageHeightNew:(CGFloat *)newBottom top:(CGFloat)oldTop bottom:(CGFloat)oldBottom limit:(CGFloat)bottomLimit 3702{ 3703 // This helps when we print as part of a larger print process. 3704 // If the WebHTMLView itself is what we're printing, then we will never have to do this. 3705 BOOL wasInPrintingMode = _private->printing; 3706 if (!wasInPrintingMode) 3707 [self _setPrinting:YES minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO]; 3708 3709 float newBottomFloat = *newBottom; 3710 if (Frame* frame = core([self _frame])) { 3711 if (FrameView* view = frame->view()) 3712 view->adjustPageHeight(&newBottomFloat, oldTop, oldBottom, bottomLimit); 3713 } 3714 3715#ifdef __LP64__ 3716 // If the new bottom is equal to the old bottom (when both are treated as floats), we just copy 3717 // oldBottom over to newBottom. This prevents rounding errors that can occur when converting newBottomFloat to a double. 3718 if (fabs((float)oldBottom - newBottomFloat) <= std::numeric_limits<float>::epsilon()) 3719 *newBottom = oldBottom; 3720 else 3721#endif 3722 *newBottom = newBottomFloat; 3723 3724 if (!wasInPrintingMode) { 3725 NSPrintOperation *currenPrintOperation = [NSPrintOperation currentOperation]; 3726 if (currenPrintOperation) 3727 // delay _setPrinting:NO until back to main loop as this method may get called repeatedly 3728 [self performSelector:@selector(_delayedEndPrintMode:) withObject:currenPrintOperation afterDelay:0]; 3729 else 3730 // not sure if this is actually ever invoked, it probably shouldn't be 3731 [self _setPrinting:NO minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO]; 3732 } 3733} 3734 3735- (float)_availablePaperWidthForPrintOperation:(NSPrintOperation *)printOperation 3736{ 3737 NSPrintInfo *printInfo = [printOperation printInfo]; 3738 return [printInfo paperSize].width - [printInfo leftMargin] - [printInfo rightMargin]; 3739} 3740 3741- (float)_scaleFactorForPrintOperation:(NSPrintOperation *)printOperation 3742{ 3743 float viewWidth = NSWidth([self bounds]); 3744 if (viewWidth < 1) { 3745 LOG_ERROR("%@ has no width when printing", self); 3746 return 1.0f; 3747 } 3748 3749 float userScaleFactor = [printOperation _web_pageSetupScaleFactor]; 3750 float maxShrinkToFitScaleFactor = 1.0f / PrintingMaximumShrinkFactor; 3751 float shrinkToFitScaleFactor = [self _availablePaperWidthForPrintOperation:printOperation]/viewWidth; 3752 float shrinkToAvoidOrphan = _private->avoidingPrintOrphan ? (1.0f / PrintingOrphanShrinkAdjustment) : 1.0f; 3753 return userScaleFactor * MAX(maxShrinkToFitScaleFactor, shrinkToFitScaleFactor) * shrinkToAvoidOrphan; 3754} 3755 3756// FIXME 3491344: This is a secret AppKit-internal method that we need to override in order 3757// to get our shrink-to-fit to work with a custom pagination scheme. We can do this better 3758// if AppKit makes it SPI/API. 3759- (CGFloat)_provideTotalScaleFactorForPrintOperation:(NSPrintOperation *)printOperation 3760{ 3761 return [self _scaleFactorForPrintOperation:printOperation]; 3762} 3763 3764// This is used for Carbon printing. At some point we might want to make this public API. 3765- (void)setPageWidthForPrinting:(float)pageWidth 3766{ 3767 [self _setPrinting:NO minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO]; 3768 [self _setPrinting:YES minimumPageWidth:pageWidth maximumPageWidth:pageWidth adjustViewSize:YES]; 3769} 3770 3771- (void)_endPrintMode 3772{ 3773 [self _setPrinting:NO minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:YES]; 3774 [[self window] setAutodisplay:YES]; 3775} 3776 3777- (void)_delayedEndPrintMode:(NSPrintOperation *)initiatingOperation 3778{ 3779 ASSERT_ARG(initiatingOperation, initiatingOperation != nil); 3780 NSPrintOperation *currentOperation = [NSPrintOperation currentOperation]; 3781 if (initiatingOperation == currentOperation) { 3782 // The print operation is still underway. We don't expect this to ever happen, hence the assert, but we're 3783 // being extra paranoid here since the printing code is so fragile. Delay the cleanup 3784 // further. 3785 ASSERT_NOT_REACHED(); 3786 [self performSelector:@selector(_delayedEndPrintMode:) withObject:initiatingOperation afterDelay:0]; 3787 } else if ([currentOperation view] == self) { 3788 // A new print job has started, but it is printing the same WebHTMLView again. We don't expect 3789 // this to ever happen, hence the assert, but we're being extra paranoid here since the printing code is so 3790 // fragile. Do nothing, because we don't want to break the print job currently in progress, and 3791 // the print job currently in progress is responsible for its own cleanup. 3792 ASSERT_NOT_REACHED(); 3793 } else { 3794 // The print job that kicked off this delayed call has finished, and this view is not being 3795 // printed again. We expect that no other print job has started. Since this delayed call wasn't 3796 // cancelled, beginDocument and endDocument must not have been called, and we need to clean up 3797 // the print mode here. 3798 ASSERT(currentOperation == nil); 3799 [self _endPrintMode]; 3800 } 3801} 3802 3803// Return the number of pages available for printing 3804- (BOOL)knowsPageRange:(NSRangePointer)range 3805{ 3806 // Must do this explicit display here, because otherwise the view might redisplay while the print 3807 // sheet was up, using printer fonts (and looking different). 3808 [self displayIfNeeded]; 3809 [[self window] setAutodisplay:NO]; 3810 3811 // If we are a frameset just print with the layout we have onscreen, otherwise relayout 3812 // according to the paper size 3813 float minLayoutWidth = 0.0f; 3814 float maxLayoutWidth = 0.0f; 3815 Frame* frame = core([self _frame]); 3816 if (!frame) 3817 return NO; 3818 if (!frame->document() || !frame->document()->isFrameSet()) { 3819 float paperWidth = [self _availablePaperWidthForPrintOperation:[NSPrintOperation currentOperation]]; 3820 minLayoutWidth = paperWidth * PrintingMinimumShrinkFactor; 3821 maxLayoutWidth = paperWidth * PrintingMaximumShrinkFactor; 3822 } 3823 [self _setPrinting:YES minimumPageWidth:minLayoutWidth maximumPageWidth:maxLayoutWidth adjustViewSize:YES]; // will relayout 3824 NSPrintOperation *printOperation = [NSPrintOperation currentOperation]; 3825 // Certain types of errors, including invalid page ranges, can cause beginDocument and 3826 // endDocument to be skipped after we've put ourselves in print mode (see 4145905). In those cases 3827 // we need to get out of print mode without relying on any more callbacks from the printing mechanism. 3828 // If we get as far as beginDocument without trouble, then this delayed request will be cancelled. 3829 // If not cancelled, this delayed call will be invoked in the next pass through the main event loop, 3830 // which is after beginDocument and endDocument would be called. 3831 [self performSelector:@selector(_delayedEndPrintMode:) withObject:printOperation afterDelay:0]; 3832 [[self _webView] _adjustPrintingMarginsForHeaderAndFooter]; 3833 3834 // There is a theoretical chance that someone could do some drawing between here and endDocument, 3835 // if something caused setNeedsDisplay after this point. If so, it's not a big tragedy, because 3836 // you'd simply see the printer fonts on screen. As of this writing, this does not happen with Safari. 3837 3838 range->location = 1; 3839 float totalScaleFactor = [self _scaleFactorForPrintOperation:printOperation]; 3840 float userScaleFactor = [printOperation _web_pageSetupScaleFactor]; 3841 [_private->pageRects release]; 3842 float fullPageHeight = floorf([self _calculatePrintHeight]/totalScaleFactor); 3843 NSArray *newPageRects = [[self _frame] _computePageRectsWithPrintWidthScaleFactor:userScaleFactor 3844 printHeight:fullPageHeight]; 3845 3846 // AppKit gets all messed up if you give it a zero-length page count (see 3576334), so if we 3847 // hit that case we'll pass along a degenerate 1 pixel square to print. This will print 3848 // a blank page (with correct-looking header and footer if that option is on), which matches 3849 // the behavior of IE and Camino at least. 3850 if ([newPageRects count] == 0) 3851 newPageRects = [NSArray arrayWithObject:[NSValue valueWithRect:NSMakeRect(0, 0, 1, 1)]]; 3852 else if ([newPageRects count] > 1) { 3853 // If the last page is a short orphan, try adjusting the print height slightly to see if this will squeeze the 3854 // content onto one fewer page. If it does, use the adjusted scale. If not, use the original scale. 3855 float lastPageHeight = NSHeight([[newPageRects lastObject] rectValue]); 3856 if (lastPageHeight/fullPageHeight < LastPrintedPageOrphanRatio) { 3857 NSArray *adjustedPageRects = [[self _frame] _computePageRectsWithPrintWidthScaleFactor:userScaleFactor 3858 printHeight:fullPageHeight*PrintingOrphanShrinkAdjustment]; 3859 // Use the adjusted rects only if the page count went down 3860 if ([adjustedPageRects count] < [newPageRects count]) { 3861 newPageRects = adjustedPageRects; 3862 _private->avoidingPrintOrphan = YES; 3863 } 3864 } 3865 } 3866 3867 _private->pageRects = [newPageRects retain]; 3868 3869 range->length = [_private->pageRects count]; 3870 3871 return YES; 3872} 3873 3874// Return the drawing rectangle for a particular page number 3875- (NSRect)rectForPage:(NSInteger)page 3876{ 3877 return [[_private->pageRects objectAtIndex:page - 1] rectValue]; 3878} 3879 3880- (void)drawPageBorderWithSize:(NSSize)borderSize 3881{ 3882 ASSERT(NSEqualSizes(borderSize, [[[NSPrintOperation currentOperation] printInfo] paperSize])); 3883 [[self _webView] _drawHeaderAndFooter]; 3884} 3885 3886- (void)beginDocument 3887{ 3888 @try { 3889 // From now on we'll get a chance to call _endPrintMode in either beginDocument or 3890 // endDocument, so we can cancel the "just in case" pending call. 3891 [NSObject cancelPreviousPerformRequestsWithTarget:self 3892 selector:@selector(_delayedEndPrintMode:) 3893 object:[NSPrintOperation currentOperation]]; 3894 [super beginDocument]; 3895 } @catch (NSException *localException) { 3896 // Exception during [super beginDocument] means that endDocument will not get called, 3897 // so we need to clean up our "print mode" here. 3898 [self _endPrintMode]; 3899 } 3900} 3901 3902- (void)endDocument 3903{ 3904 [super endDocument]; 3905 // Note sadly at this point [NSGraphicsContext currentContextDrawingToScreen] is still NO 3906 [self _endPrintMode]; 3907} 3908 3909- (void)keyDown:(NSEvent *)event 3910{ 3911 // There's a chance that responding to this event will run a nested event loop, and 3912 // fetching a new event might release the old one. Retaining and then autoreleasing 3913 // the current event prevents that from causing a problem inside WebKit or AppKit code. 3914 [[event retain] autorelease]; 3915 3916 RetainPtr<WebHTMLView> selfProtector = self; 3917 BOOL eventWasSentToWebCore = (_private->keyDownEvent == event); 3918 3919 BOOL callSuper = NO; 3920 3921 [_private->keyDownEvent release]; 3922 _private->keyDownEvent = [event retain]; 3923 3924 BOOL completionPopupWasOpen = _private->completionController && [_private->completionController popupWindowIsOpen]; 3925 Frame* coreFrame = core([self _frame]); 3926 if (!eventWasSentToWebCore && coreFrame && coreFrame->eventHandler()->keyEvent(event)) { 3927 // WebCore processed a key event, bail on any preexisting complete: UI 3928 if (completionPopupWasOpen) 3929 [_private->completionController endRevertingChange:YES moveLeft:NO]; 3930 } else if (!_private->completionController || ![_private->completionController filterKeyDown:event]) { 3931 // Not consumed by complete: popup window 3932 [_private->completionController endRevertingChange:YES moveLeft:NO]; 3933 callSuper = YES; 3934 } 3935 if (callSuper) 3936 [super keyDown:event]; 3937 else 3938 [NSCursor setHiddenUntilMouseMoves:YES]; 3939} 3940 3941- (void)keyUp:(NSEvent *)event 3942{ 3943 // There's a chance that responding to this event will run a nested event loop, and 3944 // fetching a new event might release the old one. Retaining and then autoreleasing 3945 // the current event prevents that from causing a problem inside WebKit or AppKit code. 3946 [[event retain] autorelease]; 3947 3948 BOOL eventWasSentToWebCore = (_private->keyDownEvent == event); 3949 3950 RetainPtr<WebHTMLView> selfProtector = self; 3951 Frame* coreFrame = core([self _frame]); 3952 if (coreFrame && !eventWasSentToWebCore) 3953 coreFrame->eventHandler()->keyEvent(event); 3954 else 3955 [super keyUp:event]; 3956} 3957 3958- (void)flagsChanged:(NSEvent *)event 3959{ 3960 // There's a chance that responding to this event will run a nested event loop, and 3961 // fetching a new event might release the old one. Retaining and then autoreleasing 3962 // the current event prevents that from causing a problem inside WebKit or AppKit code. 3963 [[event retain] autorelease]; 3964 3965 Frame* coreFrame = core([self _frame]); 3966 if (coreFrame) 3967 coreFrame->eventHandler()->capsLockStateMayHaveChanged(); 3968 3969 RetainPtr<WebHTMLView> selfProtector = self; 3970 3971 unsigned short keyCode = [event keyCode]; 3972 3973 //Don't make an event from the num lock and function keys 3974 if (coreFrame && keyCode != 0 && keyCode != 10 && keyCode != 63) { 3975 coreFrame->eventHandler()->keyEvent(PlatformKeyboardEvent(event)); 3976 return; 3977 } 3978 3979 [super flagsChanged:event]; 3980} 3981 3982- (id)accessibilityAttributeValue:(NSString*)attributeName 3983{ 3984 if ([attributeName isEqualToString: NSAccessibilityChildrenAttribute]) { 3985 id accTree = [[self _frame] _accessibilityTree]; 3986 if (accTree) 3987 return [NSArray arrayWithObject:accTree]; 3988 return nil; 3989 } 3990 return [super accessibilityAttributeValue:attributeName]; 3991} 3992 3993- (id)accessibilityFocusedUIElement 3994{ 3995 id accTree = [[self _frame] _accessibilityTree]; 3996 if (accTree) 3997 return [accTree accessibilityFocusedUIElement]; 3998 return self; 3999} 4000 4001- (id)accessibilityHitTest:(NSPoint)point 4002{ 4003 id accTree = [[self _frame] _accessibilityTree]; 4004 if (accTree) { 4005 NSPoint windowCoord = [[self window] convertScreenToBase:point]; 4006 return [accTree accessibilityHitTest:[self convertPoint:windowCoord fromView:nil]]; 4007 } 4008 return self; 4009} 4010 4011- (id)_accessibilityParentForSubview:(NSView *)subview 4012{ 4013 id accTree = [[self _frame] _accessibilityTree]; 4014 if (!accTree) 4015 return self; 4016 id parent = [accTree _accessibilityParentForSubview:subview]; 4017 if (!parent) 4018 return self; 4019 return parent; 4020} 4021 4022- (void)centerSelectionInVisibleArea:(id)sender 4023{ 4024 COMMAND_PROLOGUE 4025 4026 if (Frame* coreFrame = core([self _frame])) 4027 coreFrame->revealSelection(ScrollAlignment::alignCenterAlways); 4028} 4029 4030- (NSData *)_selectionStartFontAttributesAsRTF 4031{ 4032 Frame* coreFrame = core([self _frame]); 4033 NSAttributedString *string = [[NSAttributedString alloc] initWithString:@"x" 4034 attributes:coreFrame ? coreFrame->fontAttributesForSelectionStart() : nil]; 4035 NSData *data = [string RTFFromRange:NSMakeRange(0, [string length]) documentAttributes:nil]; 4036 [string release]; 4037 return data; 4038} 4039 4040- (NSDictionary *)_fontAttributesFromFontPasteboard 4041{ 4042 NSPasteboard *fontPasteboard = [NSPasteboard pasteboardWithName:NSFontPboard]; 4043 if (fontPasteboard == nil) 4044 return nil; 4045 NSData *data = [fontPasteboard dataForType:NSFontPboardType]; 4046 if (data == nil || [data length] == 0) 4047 return nil; 4048 // NSTextView does something more efficient by parsing the attributes only, but that's not available in API. 4049 NSAttributedString *string = [[[NSAttributedString alloc] initWithRTF:data documentAttributes:NULL] autorelease]; 4050 if (string == nil || [string length] == 0) 4051 return nil; 4052 return [string fontAttributesInRange:NSMakeRange(0, 1)]; 4053} 4054 4055- (DOMCSSStyleDeclaration *)_emptyStyle 4056{ 4057 return [[[self _frame] DOMDocument] createCSSStyleDeclaration]; 4058} 4059 4060- (NSString *)_colorAsString:(NSColor *)color 4061{ 4062 NSColor *rgbColor = [color colorUsingColorSpaceName:NSDeviceRGBColorSpace]; 4063 // FIXME: If color is non-nil and rgbColor is nil, that means we got some kind 4064 // of fancy color that can't be converted to RGB. Changing that to "transparent" 4065 // might not be great, but it's probably OK. 4066 if (rgbColor == nil) 4067 return @"transparent"; 4068 float r = [rgbColor redComponent]; 4069 float g = [rgbColor greenComponent]; 4070 float b = [rgbColor blueComponent]; 4071 float a = [rgbColor alphaComponent]; 4072 if (a == 0) 4073 return @"transparent"; 4074 if (r == 0 && g == 0 && b == 0 && a == 1) 4075 return @"black"; 4076 if (r == 1 && g == 1 && b == 1 && a == 1) 4077 return @"white"; 4078 // FIXME: Lots more named colors. Maybe we could use the table in WebCore? 4079 if (a == 1) 4080 return [NSString stringWithFormat:@"rgb(%.0f,%.0f,%.0f)", r * 255, g * 255, b * 255]; 4081 return [NSString stringWithFormat:@"rgba(%.0f,%.0f,%.0f,%f)", r * 255, g * 255, b * 255, a]; 4082} 4083 4084- (NSString *)_shadowAsString:(NSShadow *)shadow 4085{ 4086 if (shadow == nil) 4087 return @"none"; 4088 NSSize offset = [shadow shadowOffset]; 4089 float blurRadius = [shadow shadowBlurRadius]; 4090 if (offset.width == 0 && offset.height == 0 && blurRadius == 0) 4091 return @"none"; 4092 NSColor *color = [shadow shadowColor]; 4093 if (color == nil) 4094 return @"none"; 4095 // FIXME: Handle non-integral values here? 4096 if (blurRadius == 0) 4097 return [NSString stringWithFormat:@"%@ %.0fpx %.0fpx", [self _colorAsString:color], offset.width, offset.height]; 4098 return [NSString stringWithFormat:@"%@ %.0fpx %.0fpx %.0fpx", [self _colorAsString:color], offset.width, offset.height, blurRadius]; 4099} 4100 4101- (DOMCSSStyleDeclaration *)_styleFromFontAttributes:(NSDictionary *)dictionary 4102{ 4103 DOMCSSStyleDeclaration *style = [self _emptyStyle]; 4104 4105 NSColor *color = [dictionary objectForKey:NSBackgroundColorAttributeName]; 4106 [style setBackgroundColor:[self _colorAsString:color]]; 4107 4108 NSFont *font = [dictionary objectForKey:NSFontAttributeName]; 4109 if (!font) { 4110 [style setFontFamily:@"Helvetica"]; 4111 [style setFontSize:@"12px"]; 4112 [style setFontWeight:@"normal"]; 4113 [style setFontStyle:@"normal"]; 4114 } else { 4115 NSFontManager *fm = [NSFontManager sharedFontManager]; 4116 // FIXME: Need more sophisticated escaping code if we want to handle family names 4117 // with characters like single quote or backslash in their names. 4118 [style setFontFamily:[NSString stringWithFormat:@"'%@'", [font familyName]]]; 4119 [style setFontSize:[NSString stringWithFormat:@"%0.fpx", [font pointSize]]]; 4120 // FIXME: Map to the entire range of CSS weight values. 4121 if ([fm weightOfFont:font] >= MIN_BOLD_WEIGHT) 4122 [style setFontWeight:@"bold"]; 4123 else 4124 [style setFontWeight:@"normal"]; 4125 if ([fm traitsOfFont:font] & NSItalicFontMask) 4126 [style setFontStyle:@"italic"]; 4127 else 4128 [style setFontStyle:@"normal"]; 4129 } 4130 4131 color = [dictionary objectForKey:NSForegroundColorAttributeName]; 4132 [style setColor:color ? [self _colorAsString:color] : (NSString *)@"black"]; 4133 4134 NSShadow *shadow = [dictionary objectForKey:NSShadowAttributeName]; 4135 [style setTextShadow:[self _shadowAsString:shadow]]; 4136 4137 int strikethroughInt = [[dictionary objectForKey:NSStrikethroughStyleAttributeName] intValue]; 4138 4139 int superscriptInt = [[dictionary objectForKey:NSSuperscriptAttributeName] intValue]; 4140 if (superscriptInt > 0) 4141 [style setVerticalAlign:@"super"]; 4142 else if (superscriptInt < 0) 4143 [style setVerticalAlign:@"sub"]; 4144 else 4145 [style setVerticalAlign:@"baseline"]; 4146 int underlineInt = [[dictionary objectForKey:NSUnderlineStyleAttributeName] intValue]; 4147 // FIXME: Underline wins here if we have both (see bug 3790443). 4148 if (strikethroughInt == NSUnderlineStyleNone && underlineInt == NSUnderlineStyleNone) 4149 [style setProperty:@"-khtml-text-decorations-in-effect" value:@"none" priority:@""]; 4150 else if (underlineInt == NSUnderlineStyleNone) 4151 [style setProperty:@"-khtml-text-decorations-in-effect" value:@"line-through" priority:@""]; 4152 else 4153 [style setProperty:@"-khtml-text-decorations-in-effect" value:@"underline" priority:@""]; 4154 4155 return style; 4156} 4157 4158- (void)_applyStyleToSelection:(DOMCSSStyleDeclaration *)style withUndoAction:(EditAction)undoAction 4159{ 4160 if (Frame* coreFrame = core([self _frame])) 4161 coreFrame->editor()->applyStyleToSelection(core(style), undoAction); 4162} 4163 4164- (void)_applyParagraphStyleToSelection:(DOMCSSStyleDeclaration *)style withUndoAction:(EditAction)undoAction 4165{ 4166 if (Frame* coreFrame = core([self _frame])) 4167 coreFrame->editor()->applyParagraphStyleToSelection(core(style), undoAction); 4168} 4169 4170- (BOOL)_handleStyleKeyEquivalent:(NSEvent *)event 4171{ 4172 ASSERT([self _webView]); 4173 if (![[[self _webView] preferences] respectStandardStyleKeyEquivalents]) 4174 return NO; 4175 4176 if (![self _canEdit]) 4177 return NO; 4178 4179 if (([event modifierFlags] & NSDeviceIndependentModifierFlagsMask) != NSCommandKeyMask) 4180 return NO; 4181 4182 NSString *string = [event characters]; 4183 if ([string caseInsensitiveCompare:@"b"] == NSOrderedSame) { 4184 [self executeCoreCommandByName:"ToggleBold"]; 4185 return YES; 4186 } 4187 if ([string caseInsensitiveCompare:@"i"] == NSOrderedSame) { 4188 [self executeCoreCommandByName:"ToggleItalic"]; 4189 return YES; 4190 } 4191 4192 return NO; 4193} 4194 4195- (BOOL)performKeyEquivalent:(NSEvent *)event 4196{ 4197 // There's a chance that responding to this event will run a nested event loop, and 4198 // fetching a new event might release the old one. Retaining and then autoreleasing 4199 // the current event prevents that from causing a problem inside WebKit or AppKit code. 4200 [[event retain] autorelease]; 4201 4202 if ([self _handleStyleKeyEquivalent:event]) 4203 return YES; 4204 4205 BOOL eventWasSentToWebCore = (_private->keyDownEvent == event); 4206 BOOL ret = NO; 4207 4208 [_private->keyDownEvent release]; 4209 _private->keyDownEvent = [event retain]; 4210 4211 [self retain]; 4212 4213 // Pass command-key combos through WebCore if there is a key binding available for 4214 // this event. This lets web pages have a crack at intercepting command-modified keypresses. 4215 // But don't do it if we have already handled the event. 4216 // Pressing Esc results in a fake event being sent - don't pass it to WebCore. 4217 if (!eventWasSentToWebCore && event == [NSApp currentEvent] && self == [[self window] firstResponder]) 4218 if (Frame* frame = core([self _frame])) 4219 ret = frame->eventHandler()->keyEvent(event); 4220 4221 if (!ret) 4222 ret = [super performKeyEquivalent:event]; 4223 4224 [self release]; 4225 4226 return ret; 4227} 4228 4229- (void)copyFont:(id)sender 4230{ 4231 COMMAND_PROLOGUE 4232 4233 // Put RTF with font attributes on the pasteboard. 4234 // Maybe later we should add a pasteboard type that contains CSS text for "native" copy and paste font. 4235 NSPasteboard *fontPasteboard = [NSPasteboard pasteboardWithName:NSFontPboard]; 4236 [fontPasteboard declareTypes:[NSArray arrayWithObject:NSFontPboardType] owner:nil]; 4237 [fontPasteboard setData:[self _selectionStartFontAttributesAsRTF] forType:NSFontPboardType]; 4238} 4239 4240- (void)pasteFont:(id)sender 4241{ 4242 COMMAND_PROLOGUE 4243 4244 // Read RTF with font attributes from the pasteboard. 4245 // Maybe later we should add a pasteboard type that contains CSS text for "native" copy and paste font. 4246 [self _applyStyleToSelection:[self _styleFromFontAttributes:[self _fontAttributesFromFontPasteboard]] withUndoAction:EditActionPasteFont]; 4247} 4248 4249- (void)pasteAsRichText:(id)sender 4250{ 4251 COMMAND_PROLOGUE 4252 4253 // Since rich text always beats plain text when both are on the pasteboard, it's not 4254 // clear how this is different from plain old paste. 4255 [self _pasteWithPasteboard:[NSPasteboard generalPasteboard] allowPlainText:NO]; 4256} 4257 4258- (NSFont *)_originalFontA 4259{ 4260 return [[NSFontManager sharedFontManager] fontWithFamily:@"Helvetica" traits:0 weight:STANDARD_WEIGHT size:10.0f]; 4261} 4262 4263- (NSFont *)_originalFontB 4264{ 4265 return [[NSFontManager sharedFontManager] fontWithFamily:@"Times" traits:NSFontItalicTrait weight:STANDARD_BOLD_WEIGHT size:12.0f]; 4266} 4267 4268- (void)_addToStyle:(DOMCSSStyleDeclaration *)style fontA:(NSFont *)a fontB:(NSFont *)b 4269{ 4270 // Since there's no way to directly ask NSFontManager what style change it's going to do 4271 // we instead pass two "specimen" fonts to it and let it change them. We then deduce what 4272 // style change it was doing by looking at what happened to each of the two fonts. 4273 // So if it was making the text bold, both fonts will be bold after the fact. 4274 4275 if (a == nil || b == nil) 4276 return; 4277 4278 NSFontManager *fm = [NSFontManager sharedFontManager]; 4279 4280 NSFont *oa = [self _originalFontA]; 4281 4282 NSString *aFamilyName = [a familyName]; 4283 NSString *bFamilyName = [b familyName]; 4284 4285 int aPointSize = (int)[a pointSize]; 4286 int bPointSize = (int)[b pointSize]; 4287 4288 int aWeight = [fm weightOfFont:a]; 4289 int bWeight = [fm weightOfFont:b]; 4290 4291 BOOL aIsItalic = ([fm traitsOfFont:a] & NSItalicFontMask) != 0; 4292 BOOL bIsItalic = ([fm traitsOfFont:b] & NSItalicFontMask) != 0; 4293 4294 BOOL aIsBold = aWeight > MIN_BOLD_WEIGHT; 4295 4296 if ([aFamilyName isEqualToString:bFamilyName]) { 4297 NSString *familyNameForCSS = aFamilyName; 4298 4299 // The family name may not be specific enough to get us the font specified. 4300 // In some cases, the only way to get exactly what we are looking for is to use 4301 // the Postscript name. 4302 4303 // Find the font the same way the rendering code would later if it encountered this CSS. 4304 NSFontTraitMask traits = aIsItalic ? NSFontItalicTrait : 0; 4305 int weight = aIsBold ? STANDARD_BOLD_WEIGHT : STANDARD_WEIGHT; 4306 NSFont *foundFont = [WebFontCache fontWithFamily:aFamilyName traits:traits weight:weight size:aPointSize]; 4307 4308 // If we don't find a font with the same Postscript name, then we'll have to use the 4309 // Postscript name to make the CSS specific enough. 4310 if (![[foundFont fontName] isEqualToString:[a fontName]]) 4311 familyNameForCSS = [a fontName]; 4312 4313 // FIXME: Need more sophisticated escaping code if we want to handle family names 4314 // with characters like single quote or backslash in their names. 4315 [style setFontFamily:[NSString stringWithFormat:@"'%@'", familyNameForCSS]]; 4316 } 4317 4318 int soa = (int)[oa pointSize]; 4319 if (aPointSize == bPointSize) 4320 [style setFontSize:[NSString stringWithFormat:@"%dpx", aPointSize]]; 4321 else if (aPointSize < soa) 4322 [style _setFontSizeDelta:@"-1px"]; 4323 else if (aPointSize > soa) 4324 [style _setFontSizeDelta:@"1px"]; 4325 4326 // FIXME: Map to the entire range of CSS weight values. 4327 if (aWeight == bWeight) 4328 [style setFontWeight:aIsBold ? @"bold" : @"normal"]; 4329 4330 if (aIsItalic == bIsItalic) 4331 [style setFontStyle:aIsItalic ? @"italic" : @"normal"]; 4332} 4333 4334- (DOMCSSStyleDeclaration *)_styleFromFontManagerOperation 4335{ 4336 DOMCSSStyleDeclaration *style = [self _emptyStyle]; 4337 4338 NSFontManager *fm = [NSFontManager sharedFontManager]; 4339 4340 NSFont *oa = [self _originalFontA]; 4341 NSFont *ob = [self _originalFontB]; 4342 [self _addToStyle:style fontA:[fm convertFont:oa] fontB:[fm convertFont:ob]]; 4343 4344 return style; 4345} 4346 4347- (void)changeFont:(id)sender 4348{ 4349 COMMAND_PROLOGUE 4350 4351 [self _applyStyleToSelection:[self _styleFromFontManagerOperation] withUndoAction:EditActionSetFont]; 4352} 4353 4354- (DOMCSSStyleDeclaration *)_styleForAttributeChange:(id)sender 4355{ 4356 DOMCSSStyleDeclaration *style = [self _emptyStyle]; 4357 4358 NSShadow *shadow = [[NSShadow alloc] init]; 4359 [shadow setShadowOffset:NSMakeSize(1, 1)]; 4360 4361 NSDictionary *oa = [NSDictionary dictionaryWithObjectsAndKeys: 4362 [self _originalFontA], NSFontAttributeName, 4363 nil]; 4364 NSDictionary *ob = [NSDictionary dictionaryWithObjectsAndKeys: 4365 [NSColor blackColor], NSBackgroundColorAttributeName, 4366 [self _originalFontB], NSFontAttributeName, 4367 [NSColor whiteColor], NSForegroundColorAttributeName, 4368 shadow, NSShadowAttributeName, 4369 [NSNumber numberWithInt:NSUnderlineStyleSingle], NSStrikethroughStyleAttributeName, 4370 [NSNumber numberWithInt:1], NSSuperscriptAttributeName, 4371 [NSNumber numberWithInt:NSUnderlineStyleSingle], NSUnderlineStyleAttributeName, 4372 nil]; 4373 4374 [shadow release]; 4375 4376#if 0 4377 4378NSObliquenessAttributeName /* float; skew to be applied to glyphs, default 0: no skew */ 4379 // font-style, but that is just an on-off switch 4380 4381NSExpansionAttributeName /* float; log of expansion factor to be applied to glyphs, default 0: no expansion */ 4382 // font-stretch? 4383 4384NSKernAttributeName /* float, amount to modify default kerning, if 0, kerning off */ 4385 // letter-spacing? probably not good enough 4386 4387NSUnderlineColorAttributeName /* NSColor, default nil: same as foreground color */ 4388NSStrikethroughColorAttributeName /* NSColor, default nil: same as foreground color */ 4389 // text-decoration-color? 4390 4391NSLigatureAttributeName /* int, default 1: default ligatures, 0: no ligatures, 2: all ligatures */ 4392NSBaselineOffsetAttributeName /* float, in points; offset from baseline, default 0 */ 4393NSStrokeWidthAttributeName /* float, in percent of font point size, default 0: no stroke; positive for stroke alone, negative for stroke and fill (a typical value for outlined text would be 3.0) */ 4394NSStrokeColorAttributeName /* NSColor, default nil: same as foreground color */ 4395 // need extensions? 4396 4397#endif 4398 4399 NSDictionary *a = [sender convertAttributes:oa]; 4400 NSDictionary *b = [sender convertAttributes:ob]; 4401 4402 NSColor *ca = [a objectForKey:NSBackgroundColorAttributeName]; 4403 NSColor *cb = [b objectForKey:NSBackgroundColorAttributeName]; 4404 if (ca == cb) { 4405 [style setBackgroundColor:[self _colorAsString:ca]]; 4406 } 4407 4408 [self _addToStyle:style fontA:[a objectForKey:NSFontAttributeName] fontB:[b objectForKey:NSFontAttributeName]]; 4409 4410 ca = [a objectForKey:NSForegroundColorAttributeName]; 4411 cb = [b objectForKey:NSForegroundColorAttributeName]; 4412 if (ca == cb) { 4413 [style setColor:[self _colorAsString:ca]]; 4414 } 4415 4416 NSShadow *sha = [a objectForKey:NSShadowAttributeName]; 4417 if (sha) 4418 [style setTextShadow:[self _shadowAsString:sha]]; 4419 else if ([b objectForKey:NSShadowAttributeName] == nil) 4420 [style setTextShadow:@"none"]; 4421 4422 int sa = [[a objectForKey:NSStrikethroughStyleAttributeName] intValue]; 4423 int sb = [[b objectForKey:NSStrikethroughStyleAttributeName] intValue]; 4424 if (sa == sb) { 4425 if (sa == NSUnderlineStyleNone) 4426 [style setProperty:@"-khtml-text-decorations-in-effect" value:@"none" priority:@""]; 4427 // we really mean "no line-through" rather than "none" 4428 else 4429 [style setProperty:@"-khtml-text-decorations-in-effect" value:@"line-through" priority:@""]; 4430 // we really mean "add line-through" rather than "line-through" 4431 } 4432 4433 sa = [[a objectForKey:NSSuperscriptAttributeName] intValue]; 4434 sb = [[b objectForKey:NSSuperscriptAttributeName] intValue]; 4435 if (sa == sb) { 4436 if (sa > 0) 4437 [style setVerticalAlign:@"super"]; 4438 else if (sa < 0) 4439 [style setVerticalAlign:@"sub"]; 4440 else 4441 [style setVerticalAlign:@"baseline"]; 4442 } 4443 4444 int ua = [[a objectForKey:NSUnderlineStyleAttributeName] intValue]; 4445 int ub = [[b objectForKey:NSUnderlineStyleAttributeName] intValue]; 4446 if (ua == ub) { 4447 if (ua == NSUnderlineStyleNone) 4448 [style setProperty:@"-khtml-text-decorations-in-effect" value:@"none" priority:@""]; 4449 // we really mean "no underline" rather than "none" 4450 else 4451 [style setProperty:@"-khtml-text-decorations-in-effect" value:@"underline" priority:@""]; 4452 // we really mean "add underline" rather than "underline" 4453 } 4454 4455 return style; 4456} 4457 4458- (void)changeAttributes:(id)sender 4459{ 4460 COMMAND_PROLOGUE 4461 4462 [self _applyStyleToSelection:[self _styleForAttributeChange:sender] withUndoAction:EditActionChangeAttributes]; 4463} 4464 4465- (DOMCSSStyleDeclaration *)_styleFromColorPanelWithSelector:(SEL)selector 4466{ 4467 DOMCSSStyleDeclaration *style = [self _emptyStyle]; 4468 4469 ASSERT([style respondsToSelector:selector]); 4470 [style performSelector:selector withObject:[self _colorAsString:[[NSColorPanel sharedColorPanel] color]]]; 4471 4472 return style; 4473} 4474 4475- (EditAction)_undoActionFromColorPanelWithSelector:(SEL)selector 4476{ 4477 if (selector == @selector(setBackgroundColor:)) 4478 return EditActionSetBackgroundColor; 4479 return EditActionSetColor; 4480} 4481 4482- (void)_changeCSSColorUsingSelector:(SEL)selector inRange:(DOMRange *)range 4483{ 4484 DOMCSSStyleDeclaration *style = [self _styleFromColorPanelWithSelector:selector]; 4485 WebView *webView = [self _webView]; 4486 if ([[webView _editingDelegateForwarder] webView:webView shouldApplyStyle:style toElementsInDOMRange:range]) 4487 if (Frame* coreFrame = core([self _frame])) 4488 coreFrame->editor()->applyStyle(core(style), [self _undoActionFromColorPanelWithSelector:selector]); 4489} 4490 4491- (void)changeDocumentBackgroundColor:(id)sender 4492{ 4493 COMMAND_PROLOGUE 4494 4495 // Mimicking NSTextView, this method sets the background color for the 4496 // entire document. There is no NSTextView API for setting the background 4497 // color on the selected range only. Note that this method is currently 4498 // never called from the UI (see comment in changeColor:). 4499 // FIXME: this actually has no effect when called, probably due to 3654850. _documentRange seems 4500 // to do the right thing because it works in startSpeaking:, and I know setBackgroundColor: does the 4501 // right thing because I tested it with [self _selectedRange]. 4502 // FIXME: This won't actually apply the style to the entire range here, because it ends up calling 4503 // [frame _applyStyle:], which operates on the current selection. To make this work right, we'll 4504 // need to save off the selection, temporarily set it to the entire range, make the change, then 4505 // restore the old selection. 4506 [self _changeCSSColorUsingSelector:@selector(setBackgroundColor:) inRange:[self _documentRange]]; 4507} 4508 4509- (void)changeColor:(id)sender 4510{ 4511 COMMAND_PROLOGUE 4512 4513 // FIXME: in NSTextView, this method calls changeDocumentBackgroundColor: when a 4514 // private call has earlier been made by [NSFontFontEffectsBox changeColor:], see 3674493. 4515 // AppKit will have to be revised to allow this to work with anything that isn't an 4516 // NSTextView. However, this might not be required for Tiger, since the background-color 4517 // changing box in the font panel doesn't work in Mail (3674481), though it does in TextEdit. 4518 [self _applyStyleToSelection:[self _styleFromColorPanelWithSelector:@selector(setColor:)] 4519 withUndoAction:EditActionSetColor]; 4520} 4521 4522- (void)_changeWordCaseWithSelector:(SEL)selector 4523{ 4524 if (![self _canEdit]) 4525 return; 4526 4527 WebFrame *frame = [self _frame]; 4528 [self selectWord:nil]; 4529 NSString *word = [[frame _selectedString] performSelector:selector]; 4530 // FIXME: Does this need a different action context other than "typed"? 4531 if ([self _shouldReplaceSelectionWithText:word givenAction:WebViewInsertActionTyped]) 4532 [frame _replaceSelectionWithText:word selectReplacement:NO smartReplace:NO]; 4533} 4534 4535- (void)uppercaseWord:(id)sender 4536{ 4537 COMMAND_PROLOGUE 4538 4539 [self _changeWordCaseWithSelector:@selector(uppercaseString)]; 4540} 4541 4542- (void)lowercaseWord:(id)sender 4543{ 4544 COMMAND_PROLOGUE 4545 4546 [self _changeWordCaseWithSelector:@selector(lowercaseString)]; 4547} 4548 4549- (void)capitalizeWord:(id)sender 4550{ 4551 COMMAND_PROLOGUE 4552 4553 [self _changeWordCaseWithSelector:@selector(capitalizedString)]; 4554} 4555 4556- (void)complete:(id)sender 4557{ 4558 COMMAND_PROLOGUE 4559 4560 if (![self _canEdit]) 4561 return; 4562 if (!_private->completionController) 4563 _private->completionController = [[WebTextCompletionController alloc] initWithWebView:[self _webView] HTMLView:self]; 4564 [_private->completionController doCompletion]; 4565} 4566 4567- (void)checkSpelling:(id)sender 4568{ 4569 COMMAND_PROLOGUE 4570 4571 if (Frame* coreFrame = core([self _frame])) 4572 coreFrame->editor()->advanceToNextMisspelling(); 4573} 4574 4575- (void)showGuessPanel:(id)sender 4576{ 4577 COMMAND_PROLOGUE 4578 4579 NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker]; 4580 if (!checker) { 4581 LOG_ERROR("No NSSpellChecker"); 4582 return; 4583 } 4584 4585 NSPanel *spellingPanel = [checker spellingPanel]; 4586#ifndef BUILDING_ON_TIGER 4587 // Post-Tiger, this menu item is a show/hide toggle, to match AppKit. Leave Tiger behavior alone 4588 // to match rest of OS X. 4589 if ([spellingPanel isVisible]) { 4590 [spellingPanel orderOut:sender]; 4591 return; 4592 } 4593#endif 4594 4595 if (Frame* coreFrame = core([self _frame])) 4596 coreFrame->editor()->advanceToNextMisspelling(true); 4597 [spellingPanel orderFront:sender]; 4598} 4599 4600- (void)_changeSpellingToWord:(NSString *)newWord 4601{ 4602 if (![self _canEdit]) 4603 return; 4604 4605 // Don't correct to empty string. (AppKit checked this, we might as well too.) 4606 if (![NSSpellChecker sharedSpellChecker]) { 4607 LOG_ERROR("No NSSpellChecker"); 4608 return; 4609 } 4610 4611 if ([newWord isEqualToString:@""]) 4612 return; 4613 4614 if ([self _shouldReplaceSelectionWithText:newWord givenAction:WebViewInsertActionPasted]) 4615 [[self _frame] _replaceSelectionWithText:newWord selectReplacement:YES smartReplace:NO]; 4616} 4617 4618- (void)changeSpelling:(id)sender 4619{ 4620 COMMAND_PROLOGUE 4621 4622 [self _changeSpellingToWord:[[sender selectedCell] stringValue]]; 4623} 4624 4625- (void)performFindPanelAction:(id)sender 4626{ 4627 COMMAND_PROLOGUE 4628 4629 // Implementing this will probably require copying all of NSFindPanel.h and .m. 4630 // We need *almost* the same thing as AppKit, but not quite. 4631 LOG_ERROR("unimplemented"); 4632} 4633 4634- (void)startSpeaking:(id)sender 4635{ 4636 COMMAND_PROLOGUE 4637 4638 WebFrame *frame = [self _frame]; 4639 DOMRange *range = [self _selectedRange]; 4640 if (!range || [range collapsed]) 4641 range = [self _documentRange]; 4642 [NSApp speakString:[frame _stringForRange:range]]; 4643} 4644 4645- (void)stopSpeaking:(id)sender 4646{ 4647 COMMAND_PROLOGUE 4648 4649 [NSApp stopSpeaking:sender]; 4650} 4651 4652- (void)toggleBaseWritingDirection:(id)sender 4653{ 4654 COMMAND_PROLOGUE 4655 4656 if (![self _canEdit]) 4657 return; 4658 4659 Frame* coreFrame = core([self _frame]); 4660 if (!coreFrame) 4661 return; 4662 4663 WritingDirection direction = RightToLeftWritingDirection; 4664 switch (coreFrame->baseWritingDirectionForSelectionStart()) { 4665 case NSWritingDirectionLeftToRight: 4666 break; 4667 case NSWritingDirectionRightToLeft: 4668 direction = LeftToRightWritingDirection; 4669 break; 4670 // The writingDirectionForSelectionStart method will never return "natural". It 4671 // will always return a concrete direction. So, keep the compiler happy, and assert not reached. 4672 case NSWritingDirectionNatural: 4673 ASSERT_NOT_REACHED(); 4674 break; 4675 } 4676 4677 if (Frame* coreFrame = core([self _frame])) 4678 coreFrame->editor()->setBaseWritingDirection(direction); 4679} 4680 4681- (void)changeBaseWritingDirection:(id)sender 4682{ 4683 COMMAND_PROLOGUE 4684 4685 if (![self _canEdit]) 4686 return; 4687 4688 NSWritingDirection writingDirection = static_cast<NSWritingDirection>([sender tag]); 4689 4690 // We disable the menu item that performs this action because we can't implement 4691 // NSWritingDirectionNatural's behavior using CSS. 4692 ASSERT(writingDirection != NSWritingDirectionNatural); 4693 4694 if (Frame* coreFrame = core([self _frame])) 4695 coreFrame->editor()->setBaseWritingDirection(writingDirection == NSWritingDirectionLeftToRight ? LeftToRightWritingDirection : RightToLeftWritingDirection); 4696} 4697 4698static BOOL writingDirectionKeyBindingsEnabled() 4699{ 4700#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 4701 return YES; 4702#else 4703 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 4704 return [defaults boolForKey:@"NSAllowsBaseWritingDirectionKeyBindings"] || [defaults boolForKey:@"AppleTextDirection"]; 4705#endif 4706} 4707 4708- (void)_changeBaseWritingDirectionTo:(NSWritingDirection)direction 4709{ 4710 if (![self _canEdit]) 4711 return; 4712 4713 static BOOL bindingsEnabled = writingDirectionKeyBindingsEnabled(); 4714 4715 if (!bindingsEnabled) { 4716 NSBeep(); 4717 return; 4718 } 4719 4720 if (Frame* coreFrame = core([self _frame])) 4721 coreFrame->editor()->setBaseWritingDirection(direction == NSWritingDirectionLeftToRight ? LeftToRightWritingDirection : RightToLeftWritingDirection); 4722} 4723 4724- (void)makeBaseWritingDirectionLeftToRight:(id)sender 4725{ 4726 COMMAND_PROLOGUE 4727 4728 [self _changeBaseWritingDirectionTo:NSWritingDirectionLeftToRight]; 4729} 4730 4731- (void)makeBaseWritingDirectionRightToLeft:(id)sender 4732{ 4733 COMMAND_PROLOGUE 4734 4735 [self _changeBaseWritingDirectionTo:NSWritingDirectionRightToLeft]; 4736} 4737 4738#if defined(BUILDING_ON_TIGER) || defined(BUILDING_ON_LEOPARD) 4739- (void)changeBaseWritingDirectionToLTR:(id)sender 4740{ 4741 [self makeBaseWritingDirectionLeftToRight:sender]; 4742} 4743 4744- (void)changeBaseWritingDirectionToRTL:(id)sender 4745{ 4746 [self makeBaseWritingDirectionRightToLeft:sender]; 4747} 4748#endif 4749 4750- (void)makeBaseWritingDirectionNatural:(id)sender 4751{ 4752 LOG_ERROR("Sent from %@.", sender); 4753} 4754 4755#if 0 4756 4757// CSS does not have a way to specify an outline font, which may make this difficult to implement. 4758// Maybe a special case of text-shadow? 4759- (void)outline:(id)sender; 4760 4761// This is part of table support, which may be in NSTextView for Tiger. 4762// It's probably simple to do the equivalent thing for WebKit. 4763- (void)insertTable:(id)sender; 4764 4765// This could be important. 4766- (void)toggleTraditionalCharacterShape:(id)sender; 4767 4768// I'm not sure what the equivalents of these in the web world are. 4769- (void)insertLineSeparator:(id)sender; 4770- (void)insertPageBreak:(id)sender; 4771 4772// These methods are not implemented in NSTextView yet at the time of this writing. 4773- (void)changeCaseOfLetter:(id)sender; 4774- (void)transposeWords:(id)sender; 4775 4776#endif 4777 4778#ifndef BUILDING_ON_TIGER 4779 4780// Override this so that AppKit will send us arrow keys as key down events so we can 4781// support them via the key bindings mechanism. 4782- (BOOL)_wantsKeyDownForEvent:(NSEvent *)event 4783{ 4784 bool haveWebCoreFrame = core([self _frame]); 4785 4786 // If we have a frame, our keyDown method will handle key bindings after sending 4787 // the event through the DOM, so ask AppKit not to do its early special key binding 4788 // mapping. If we don't have a frame, just let things work the normal way without 4789 // a keyDown. 4790 return haveWebCoreFrame; 4791} 4792 4793#else 4794 4795// Super-hack alert. 4796// All this code accomplishes the same thing as the _wantsKeyDownForEvent method above. 4797 4798// Returns a selector only if called while: 4799// 1) first responder is self 4800// 2) handling a key down event 4801// 3) not yet inside keyDown: method 4802// 4) key is an arrow key 4803// The selector is the one that gets sent by -[NSWindow _processKeyboardUIKey] for this key. 4804- (SEL)_arrowKeyDownEventSelectorIfPreprocessing 4805{ 4806 NSWindow *w = [self window]; 4807 if ([w firstResponder] != self) 4808 return NULL; 4809 NSEvent *e = [w currentEvent]; 4810 if ([e type] != NSKeyDown) 4811 return NULL; 4812 if (e == _private->keyDownEvent) 4813 return NULL; 4814 NSString *s = [e charactersIgnoringModifiers]; 4815 if ([s length] == 0) 4816 return NULL; 4817 switch ([s characterAtIndex:0]) { 4818 case NSDownArrowFunctionKey: 4819 return @selector(moveDown:); 4820 case NSLeftArrowFunctionKey: 4821 return @selector(moveLeft:); 4822 case NSRightArrowFunctionKey: 4823 return @selector(moveRight:); 4824 case NSUpArrowFunctionKey: 4825 return @selector(moveUp:); 4826 default: 4827 return NULL; 4828 } 4829} 4830 4831// Returns NO instead of YES if called on the selector that the 4832// _arrowKeyDownEventSelectorIfPreprocessing method returns. 4833// This should only happen inside -[NSWindow _processKeyboardUIKey], 4834// and together with the change below should cause that method 4835// to return NO rather than handling the key. 4836// Also set a 1-shot flag for the nextResponder check below. 4837- (BOOL)respondsToSelector:(SEL)selector 4838{ 4839 if (![super respondsToSelector:selector]) 4840 return NO; 4841 SEL arrowKeySelector = [self _arrowKeyDownEventSelectorIfPreprocessing]; 4842 if (selector != arrowKeySelector) 4843 return YES; 4844 _private->nextResponderDisabledOnce = YES; 4845 return NO; 4846} 4847 4848// Returns nil instead of the next responder if called when the 4849// one-shot flag is set, and _arrowKeyDownEventSelectorIfPreprocessing 4850// returns something other than NULL. This should only happen inside 4851// -[NSWindow _processKeyboardUIKey] and together with the change above 4852// should cause that method to return NO rather than handling the key. 4853- (NSResponder *)nextResponder 4854{ 4855 BOOL disabled = _private->nextResponderDisabledOnce; 4856 _private->nextResponderDisabledOnce = NO; 4857 if (disabled && [self _arrowKeyDownEventSelectorIfPreprocessing] != NULL) 4858 return nil; 4859 return [super nextResponder]; 4860} 4861 4862#endif 4863 4864- (void)_updateControlTints 4865{ 4866 Frame* frame = core([self _frame]); 4867 if (!frame) 4868 return; 4869 FrameView* view = frame->view(); 4870 if (!view) 4871 return; 4872 view->updateControlTints(); 4873} 4874 4875// Despite its name, this is called at different times than windowDidBecomeKey is. 4876// It takes into account all the other factors that determine when NSCell draws 4877// with different tints, so it's the right call to use for control tints. We'd prefer 4878// to do this with API. <rdar://problem/5136760> 4879- (void)_windowChangedKeyState 4880{ 4881 if (pthread_main_np()) 4882 [self _updateControlTints]; 4883 else 4884 [self performSelectorOnMainThread:@selector(_updateControlTints) withObject:nil waitUntilDone:NO]; 4885 4886 [super _windowChangedKeyState]; 4887} 4888 4889- (void)otherMouseDown:(NSEvent *)event 4890{ 4891 if ([event buttonNumber] == 2) 4892 [self mouseDown:event]; 4893 else 4894 [super otherMouseDown:event]; 4895} 4896 4897- (void)otherMouseDragged:(NSEvent *)event 4898{ 4899 if ([event buttonNumber] == 2) 4900 [self mouseDragged:event]; 4901 else 4902 [super otherMouseDragged:event]; 4903} 4904 4905- (void)otherMouseUp:(NSEvent *)event 4906{ 4907 if ([event buttonNumber] == 2) 4908 [self mouseUp:event]; 4909 else 4910 [super otherMouseUp:event]; 4911} 4912 4913@end 4914 4915@implementation NSArray (WebHTMLView) 4916 4917- (void)_web_makePluginViewsPerformSelector:(SEL)selector withObject:(id)object 4918{ 4919#if ENABLE(NETSCAPE_PLUGIN_API) 4920 NSEnumerator *enumerator = [self objectEnumerator]; 4921 WebNetscapePluginView *view; 4922 while ((view = [enumerator nextObject]) != nil) 4923 if ([view isKindOfClass:[WebBaseNetscapePluginView class]]) 4924 [view performSelector:selector withObject:object]; 4925#endif 4926} 4927 4928@end 4929 4930@implementation WebHTMLView (WebInternal) 4931 4932- (void)_selectionChanged 4933{ 4934 [self _updateSelectionForInputManager]; 4935 [self _updateFontPanel]; 4936 if (Frame* coreFrame = core([self _frame])) 4937 coreFrame->editor()->setStartNewKillRingSequence(true); 4938} 4939 4940- (void)_updateFontPanel 4941{ 4942 // FIXME: NSTextView bails out if becoming or resigning first responder, for which it has ivar flags. Not 4943 // sure if we need to do something similar. 4944 4945 if (![self _canEdit]) 4946 return; 4947 4948 NSWindow *window = [self window]; 4949 // FIXME: is this first-responder check correct? What happens if a subframe is editable and is first responder? 4950 if ([NSApp keyWindow] != window || [window firstResponder] != self) 4951 return; 4952 4953 bool multipleFonts = false; 4954 NSFont *font = nil; 4955 if (Frame* coreFrame = core([self _frame])) { 4956 if (const SimpleFontData* fd = coreFrame->editor()->fontForSelection(multipleFonts)) 4957 font = fd->getNSFont(); 4958 } 4959 4960 4961 // FIXME: for now, return a bogus font that distinguishes the empty selection from the non-empty 4962 // selection. We should be able to remove this once the rest of this code works properly. 4963 if (font == nil) 4964 font = [self _hasSelection] ? [NSFont menuFontOfSize:23] : [NSFont toolTipsFontOfSize:17]; 4965 ASSERT(font != nil); 4966 4967 [[NSFontManager sharedFontManager] setSelectedFont:font isMultiple:multipleFonts]; 4968 4969 // FIXME: we don't keep track of selected attributes, or set them on the font panel. This 4970 // appears to have no effect on the UI. E.g., underlined text in Mail or TextEdit is 4971 // not reflected in the font panel. Maybe someday this will change. 4972} 4973 4974- (BOOL)_canSmartCopyOrDelete 4975{ 4976 if (![[self _webView] smartInsertDeleteEnabled]) 4977 return NO; 4978 Frame* coreFrame = core([self _frame]); 4979 return coreFrame && coreFrame->selectionGranularity() == WordGranularity; 4980} 4981 4982- (NSEvent *)_mouseDownEvent 4983{ 4984 return _private->mouseDownEvent; 4985} 4986 4987- (id<WebHTMLHighlighter>)_highlighterForType:(NSString*)type 4988{ 4989 return [_private->highlighters objectForKey:type]; 4990} 4991 4992- (WebFrame *)_frame 4993{ 4994 return [_private->dataSource webFrame]; 4995} 4996 4997- (void)paste:(id)sender 4998{ 4999 COMMAND_PROLOGUE 5000 5001 RetainPtr<WebHTMLView> selfProtector = self; 5002 RefPtr<Frame> coreFrame = core([self _frame]); 5003 if (!coreFrame) 5004 return; 5005 if (coreFrame->editor()->tryDHTMLPaste()) 5006 return; // DHTML did the whole operation 5007 if (!coreFrame->editor()->canPaste()) 5008 return; 5009 if (coreFrame->selection()->isContentRichlyEditable()) 5010 [self _pasteWithPasteboard:[NSPasteboard generalPasteboard] allowPlainText:YES]; 5011 else 5012 coreFrame->editor()->pasteAsPlainText(); 5013} 5014 5015- (void)pasteAsPlainText:(id)sender 5016{ 5017 COMMAND_PROLOGUE 5018 5019 if (![self _canEdit]) 5020 return; 5021 [self _pasteAsPlainTextWithPasteboard:[NSPasteboard generalPasteboard]]; 5022} 5023 5024- (void)closeIfNotCurrentView 5025{ 5026 if ([[[self _frame] frameView] documentView] != self) 5027 [self close]; 5028} 5029 5030- (DOMDocumentFragment*)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard 5031{ 5032 return [self _documentFragmentFromPasteboard:pasteboard inContext:nil allowPlainText:NO]; 5033} 5034 5035#ifndef BUILDING_ON_TIGER 5036 5037- (BOOL)isGrammarCheckingEnabled 5038{ 5039 // FIXME 4799134: WebView is the bottleneck for this grammar-checking logic, but we must implement the method here because 5040 // the AppKit code checks the first responder. 5041 return [[self _webView] isGrammarCheckingEnabled]; 5042} 5043 5044- (void)setGrammarCheckingEnabled:(BOOL)flag 5045{ 5046 // FIXME 4799134: WebView is the bottleneck for this grammar-checking logic, but we must implement the method here because 5047 // the AppKit code checks the first responder. 5048 [[self _webView] setGrammarCheckingEnabled:flag]; 5049} 5050 5051- (void)toggleGrammarChecking:(id)sender 5052{ 5053 // FIXME 4799134: WebView is the bottleneck for this grammar-checking logic, but we must implement the method here because 5054 // the AppKit code checks the first responder. 5055 [[self _webView] toggleGrammarChecking:sender]; 5056} 5057 5058 5059static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point) 5060{ 5061 NSArray *screens = [NSScreen screens]; 5062 5063 if ([screens count] == 0) { 5064 // You could theoretically get here if running with no monitor, in which case it doesn't matter 5065 // much where the "on-screen" point is. 5066 return CGPointMake(point.x, point.y); 5067 } 5068 5069 // Flip the y coordinate from the top of the menu bar screen -- see 4636390 5070 return CGPointMake(point.x, NSMaxY([[screens objectAtIndex:0] frame]) - point.y); 5071} 5072 5073#endif 5074 5075#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 5076 5077- (void)orderFrontSubstitutionsPanel:(id)sender 5078{ 5079 COMMAND_PROLOGUE 5080 5081 NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker]; 5082 if (!checker) { 5083 LOG_ERROR("No NSSpellChecker"); 5084 return; 5085 } 5086 5087 NSPanel *substitutionsPanel = [checker substitutionsPanel]; 5088 if ([substitutionsPanel isVisible]) { 5089 [substitutionsPanel orderOut:sender]; 5090 return; 5091 } 5092 [substitutionsPanel orderFront:sender]; 5093} 5094 5095// FIXME 4799134: WebView is the bottleneck for this logic, but we must implement these methods here because 5096// the AppKit code checks the first responder. 5097 5098- (BOOL)smartInsertDeleteEnabled 5099{ 5100 return [[self _webView] smartInsertDeleteEnabled]; 5101} 5102 5103- (void)setSmartInsertDeleteEnabled:(BOOL)flag 5104{ 5105 [[self _webView] setSmartInsertDeleteEnabled:flag]; 5106} 5107 5108- (void)toggleSmartInsertDelete:(id)sender 5109{ 5110 [[self _webView] toggleSmartInsertDelete:sender]; 5111} 5112 5113- (BOOL)isAutomaticQuoteSubstitutionEnabled 5114{ 5115 return [[self _webView] isAutomaticQuoteSubstitutionEnabled]; 5116} 5117 5118- (void)setAutomaticQuoteSubstitutionEnabled:(BOOL)flag 5119{ 5120 [[self _webView] setAutomaticQuoteSubstitutionEnabled:flag]; 5121} 5122 5123- (void)toggleAutomaticQuoteSubstitution:(id)sender 5124{ 5125 [[self _webView] toggleAutomaticQuoteSubstitution:sender]; 5126} 5127 5128- (BOOL)isAutomaticLinkDetectionEnabled 5129{ 5130 return [[self _webView] isAutomaticLinkDetectionEnabled]; 5131} 5132 5133- (void)setAutomaticLinkDetectionEnabled:(BOOL)flag 5134{ 5135 [[self _webView] setAutomaticLinkDetectionEnabled:flag]; 5136} 5137 5138- (void)toggleAutomaticLinkDetection:(id)sender 5139{ 5140 [[self _webView] toggleAutomaticLinkDetection:sender]; 5141} 5142 5143- (BOOL)isAutomaticDashSubstitutionEnabled 5144{ 5145 return [[self _webView] isAutomaticDashSubstitutionEnabled]; 5146} 5147 5148- (void)setAutomaticDashSubstitutionEnabled:(BOOL)flag 5149{ 5150 [[self _webView] setAutomaticDashSubstitutionEnabled:flag]; 5151} 5152 5153- (void)toggleAutomaticDashSubstitution:(id)sender 5154{ 5155 [[self _webView] toggleAutomaticDashSubstitution:sender]; 5156} 5157 5158- (BOOL)isAutomaticTextReplacementEnabled 5159{ 5160 return [[self _webView] isAutomaticTextReplacementEnabled]; 5161} 5162 5163- (void)setAutomaticTextReplacementEnabled:(BOOL)flag 5164{ 5165 [[self _webView] setAutomaticTextReplacementEnabled:flag]; 5166} 5167 5168- (void)toggleAutomaticTextReplacement:(id)sender 5169{ 5170 [[self _webView] toggleAutomaticTextReplacement:sender]; 5171} 5172 5173- (BOOL)isAutomaticSpellingCorrectionEnabled 5174{ 5175 return [[self _webView] isAutomaticSpellingCorrectionEnabled]; 5176} 5177 5178- (void)setAutomaticSpellingCorrectionEnabled:(BOOL)flag 5179{ 5180 [[self _webView] setAutomaticSpellingCorrectionEnabled:flag]; 5181} 5182 5183- (void)toggleAutomaticSpellingCorrection:(id)sender 5184{ 5185 [[self _webView] toggleAutomaticSpellingCorrection:sender]; 5186} 5187 5188#endif 5189 5190- (void)_lookUpInDictionaryFromMenu:(id)sender 5191{ 5192 // Dictionary API will accept a whitespace-only string and display UI as if it were real text, 5193 // so bail out early to avoid that. 5194 if ([[[self selectedString] _webkit_stringByTrimmingWhitespace] length] == 0) 5195 return; 5196 5197 NSAttributedString *attrString = [self selectedAttributedString]; 5198 5199 Frame* coreFrame = core([self _frame]); 5200 if (!coreFrame) 5201 return; 5202 5203 NSRect rect = coreFrame->selectionBounds(); 5204 5205#ifndef BUILDING_ON_TIGER 5206 NSDictionary *attributes = [attrString fontAttributesInRange:NSMakeRange(0,1)]; 5207 NSFont *font = [attributes objectForKey:NSFontAttributeName]; 5208 if (font) 5209 rect.origin.y += [font ascender]; 5210#endif 5211 5212#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 5213 [self showDefinitionForAttributedString:attrString atPoint:rect.origin]; 5214 return; 5215#endif 5216 5217 // We soft link to get the function that displays the dictionary (either pop-up window or app) to avoid the performance 5218 // penalty of linking to another framework. This function changed signature as well as framework between Tiger and Leopard, 5219 // so the two cases are handled separately. 5220 5221#ifdef BUILDING_ON_TIGER 5222 typedef OSStatus (*ServiceWindowShowFunction)(id inWordString, NSRect inWordBoundary, UInt16 inLineDirection); 5223 const char *frameworkPath = "/System/Library/Frameworks/ApplicationServices.framework/Frameworks/LangAnalysis.framework/LangAnalysis"; 5224 const char *functionName = "DCMDictionaryServiceWindowShow"; 5225#else 5226 typedef void (*ServiceWindowShowFunction)(id unusedDictionaryRef, id inWordString, CFRange selectionRange, id unusedFont, CGPoint textOrigin, Boolean verticalText, id unusedTransform); 5227 const char *frameworkPath = "/System/Library/Frameworks/Carbon.framework/Frameworks/HIToolbox.framework/HIToolbox"; 5228 const char *functionName = "HIDictionaryWindowShow"; 5229#endif 5230 5231 static bool lookedForFunction = false; 5232 static ServiceWindowShowFunction dictionaryServiceWindowShow = NULL; 5233 5234 if (!lookedForFunction) { 5235 void* langAnalysisFramework = dlopen(frameworkPath, RTLD_LAZY); 5236 ASSERT(langAnalysisFramework); 5237 if (langAnalysisFramework) 5238 dictionaryServiceWindowShow = (ServiceWindowShowFunction)dlsym(langAnalysisFramework, functionName); 5239 lookedForFunction = true; 5240 } 5241 5242 ASSERT(dictionaryServiceWindowShow); 5243 if (!dictionaryServiceWindowShow) { 5244 NSLog(@"Couldn't find the %s function in %s", functionName, frameworkPath); 5245 return; 5246 } 5247 5248#ifdef BUILDING_ON_TIGER 5249 // FIXME: must check for right-to-left here 5250 NSWritingDirection writingDirection = NSWritingDirectionLeftToRight; 5251 5252 // FIXME: the dictionary API expects the rect for the first line of selection. Passing 5253 // the rect for the entire selection, as we do here, positions the pop-up window near 5254 // the bottom of the selection rather than at the selected word. 5255 rect = [self convertRect:rect toView:nil]; 5256 rect.origin = [[self window] convertBaseToScreen:rect.origin]; 5257 NSData *data = [attrString RTFFromRange:NSMakeRange(0, [attrString length]) documentAttributes:nil]; 5258 dictionaryServiceWindowShow(data, rect, (writingDirection == NSWritingDirectionRightToLeft) ? 1 : 0); 5259#else 5260 // The HIDictionaryWindowShow function requires the origin, in CG screen coordinates, of the first character of text in the selection. 5261 // FIXME 4945808: We approximate this in a way that works well when a single word is selected, and less well in some other cases 5262 // (but no worse than we did in Tiger) 5263 NSPoint windowPoint = [self convertPoint:rect.origin toView:nil]; 5264 NSPoint screenPoint = [[self window] convertBaseToScreen:windowPoint]; 5265 5266 dictionaryServiceWindowShow(nil, attrString, CFRangeMake(0, [attrString length]), nil, 5267 coreGraphicsScreenPointForAppKitScreenPoint(screenPoint), false, nil); 5268#endif 5269} 5270 5271- (void)_hoverFeedbackSuspendedChanged 5272{ 5273 [self _updateMouseoverWithFakeEvent]; 5274} 5275 5276- (BOOL)_interceptEditingKeyEvent:(KeyboardEvent*)event shouldSaveCommand:(BOOL)shouldSave 5277{ 5278 // Ask AppKit to process the key event -- it will call back with either insertText or doCommandBySelector. 5279 WebHTMLViewInterpretKeyEventsParameters parameters; 5280 parameters.eventWasHandled = false; 5281 parameters.shouldSaveCommand = shouldSave; 5282 // If we're intercepting the initial IM call we assume that the IM has consumed the event, 5283 // and only change this assumption if one of the NSTextInput/Responder callbacks is used. 5284 // We assume the IM will *not* consume hotkey sequences 5285 parameters.consumedByIM = !event->metaKey() && shouldSave; 5286 5287 if (const PlatformKeyboardEvent* platformEvent = event->keyEvent()) { 5288 NSEvent *macEvent = platformEvent->macEvent(); 5289 if ([macEvent type] == NSKeyDown && [_private->completionController filterKeyDown:macEvent]) 5290 return true; 5291 5292 if ([macEvent type] == NSFlagsChanged) 5293 return false; 5294 5295 parameters.event = event; 5296 _private->interpretKeyEventsParameters = ¶meters; 5297 _private->receivedNOOP = NO; 5298 const Vector<KeypressCommand>& commands = event->keypressCommands(); 5299 bool hasKeypressCommand = !commands.isEmpty(); 5300 5301 // FIXME: interpretKeyEvents doesn't match application key equivalents (such as Cmd+A), 5302 // and sends noop: for those. As a result, we don't handle those from within WebCore, 5303 // but send a full sequence of DOM events, including an unneeded keypress. 5304 if (parameters.shouldSaveCommand || !hasKeypressCommand) 5305 [self interpretKeyEvents:[NSArray arrayWithObject:macEvent]]; 5306 else { 5307 size_t size = commands.size(); 5308 // Are there commands that would just cause text insertion if executed via Editor? 5309 // WebKit doesn't have enough information about mode to decide how they should be treated, so we leave it upon WebCore 5310 // to either handle them immediately (e.g. Tab that changes focus) or let a keypress event be generated 5311 // (e.g. Tab that inserts a Tab character, or Enter). 5312 bool haveTextInsertionCommands = false; 5313 for (size_t i = 0; i < size; ++i) { 5314 if ([self coreCommandBySelector:NSSelectorFromString(commands[i].commandName)].isTextInsertion()) 5315 haveTextInsertionCommands = true; 5316 } 5317 if (!haveTextInsertionCommands || platformEvent->type() == PlatformKeyboardEvent::Char) { 5318 for (size_t i = 0; i < size; ++i) { 5319 if (commands[i].commandName == "insertText:") 5320 [self insertText:commands[i].text]; 5321 else 5322 [self doCommandBySelector:NSSelectorFromString(commands[i].commandName)]; 5323 } 5324 } 5325 } 5326 _private->interpretKeyEventsParameters = 0; 5327 } 5328 return (!_private->receivedNOOP && parameters.eventWasHandled) || parameters.consumedByIM; 5329} 5330 5331- (WebCore::CachedImage*)promisedDragTIFFDataSource 5332{ 5333 return _private->promisedDragTIFFDataSource; 5334} 5335 5336- (void)setPromisedDragTIFFDataSource:(WebCore::CachedImage*)source 5337{ 5338 if (source) 5339 source->addClient(promisedDataClient()); 5340 5341 if (_private->promisedDragTIFFDataSource) 5342 _private->promisedDragTIFFDataSource->removeClient(promisedDataClient()); 5343 _private->promisedDragTIFFDataSource = source; 5344} 5345 5346#undef COMMAND_PROLOGUE 5347 5348- (void)_layoutIfNeeded 5349{ 5350 ASSERT(!_private->subviewsSetAside); 5351 5352 if (_private->needsToApplyStyles || [self _needsLayout]) 5353 [self layout]; 5354} 5355 5356- (void)_web_layoutIfNeededRecursive 5357{ 5358 [self _layoutIfNeeded]; 5359 5360#ifndef NDEBUG 5361 _private->enumeratingSubviews = YES; 5362#endif 5363 5364 NSMutableArray *descendantWebHTMLViews = [[NSMutableArray alloc] init]; 5365 5366 [self _web_addDescendantWebHTMLViewsToArray:descendantWebHTMLViews]; 5367 5368 unsigned count = [descendantWebHTMLViews count]; 5369 for (unsigned i = 0; i < count; ++i) 5370 [[descendantWebHTMLViews objectAtIndex:i] _layoutIfNeeded]; 5371 5372 [descendantWebHTMLViews release]; 5373 5374#ifndef NDEBUG 5375 _private->enumeratingSubviews = NO; 5376#endif 5377} 5378 5379- (void) _destroyAllWebPlugins 5380{ 5381 [[self _pluginController] destroyAllPlugins]; 5382} 5383 5384- (BOOL)_needsLayout 5385{ 5386 return [[self _frame] _needsLayout]; 5387} 5388 5389#if USE(ACCELERATED_COMPOSITING) 5390- (void)attachRootLayer:(CALayer*)layer 5391{ 5392 if (!_private->layerHostingView) { 5393 NSView* hostingView = [[NSView alloc] initWithFrame:[self bounds]]; 5394#if !defined(BUILDING_ON_LEOPARD) 5395 [hostingView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)]; 5396#endif 5397 [self addSubview:hostingView]; 5398 [hostingView release]; 5399 // hostingView is owned by being a subview of self 5400 _private->layerHostingView = hostingView; 5401 [[self _webView] _startedAcceleratedCompositingForFrame:[self _frame]]; 5402 } 5403 5404 // Make a container layer, which will get sized/positioned by AppKit and CA. 5405 CALayer* viewLayer = [CALayer layer]; 5406 5407#if defined(BUILDING_ON_LEOPARD) 5408 // Turn off default animations. 5409 NSNull *nullValue = [NSNull null]; 5410 NSDictionary *actions = [NSDictionary dictionaryWithObjectsAndKeys: 5411 nullValue, @"anchorPoint", 5412 nullValue, @"bounds", 5413 nullValue, @"contents", 5414 nullValue, @"contentsRect", 5415 nullValue, @"opacity", 5416 nullValue, @"position", 5417 nullValue, @"sublayerTransform", 5418 nullValue, @"sublayers", 5419 nullValue, @"transform", 5420 nil]; 5421 [viewLayer setStyle:[NSDictionary dictionaryWithObject:actions forKey:@"actions"]]; 5422#endif 5423 5424 [_private->layerHostingView setLayer:viewLayer]; 5425 [_private->layerHostingView setWantsLayer:YES]; 5426 5427 // Parent our root layer in the container layer 5428 [viewLayer addSublayer:layer]; 5429 5430#if defined(BUILDING_ON_LEOPARD) 5431 [self _updateLayerHostingViewPosition]; 5432#endif 5433} 5434 5435- (void)detachRootLayer 5436{ 5437 if (_private->layerHostingView) { 5438 [_private->layerHostingView setLayer:nil]; 5439 [_private->layerHostingView setWantsLayer:NO]; 5440 [_private->layerHostingView removeFromSuperview]; 5441 _private->layerHostingView = nil; 5442 [[self _webView] _stoppedAcceleratedCompositingForFrame:[self _frame]]; 5443 } 5444} 5445 5446#if defined(BUILDING_ON_LEOPARD) 5447// This method is necessary on Leopard to work around <rdar://problem/7067892>. 5448- (void)_updateLayerHostingViewPosition 5449{ 5450 if (!_private->layerHostingView) 5451 return; 5452 5453 const CGFloat maxHeight = 4096; 5454 NSRect layerViewFrame = [self bounds]; 5455 5456 if (layerViewFrame.size.height > maxHeight) { 5457 CGFloat documentHeight = layerViewFrame.size.height; 5458 5459 // Clamp the size of the view to <= 4096px to avoid the bug. 5460 layerViewFrame.size.height = maxHeight; 5461 NSRect visibleRect = [[self enclosingScrollView] documentVisibleRect]; 5462 5463 // Place the top of the layer-hosting view at the top of the visibleRect. 5464 CGFloat topOffset = NSMinY(visibleRect); 5465 layerViewFrame.origin.y = topOffset; 5466 5467 // Compensate for the moved view by adjusting the sublayer transform on the view's layer (using flipped coords). 5468 CGFloat bottomOffset = documentHeight - layerViewFrame.size.height - topOffset; 5469 [[_private->layerHostingView layer] setSublayerTransform:CATransform3DMakeTranslation(0, -bottomOffset, 0)]; 5470 } 5471 5472 [_private->layerHostingView setFrame:layerViewFrame]; 5473} 5474#endif // defined(BUILDING_ON_LEOPARD) 5475#endif // USE(ACCELERATED_COMPOSITING) 5476 5477@end 5478 5479@implementation WebHTMLView (WebNSTextInputSupport) 5480 5481- (NSArray *)validAttributesForMarkedText 5482{ 5483 static NSArray *validAttributes; 5484 if (!validAttributes) { 5485 validAttributes = [[NSArray alloc] initWithObjects: 5486 NSUnderlineStyleAttributeName, NSUnderlineColorAttributeName, 5487 NSMarkedClauseSegmentAttributeName, NSTextInputReplacementRangeAttributeName, nil]; 5488 // NSText also supports the following attributes, but it's 5489 // hard to tell which are really required for text input to 5490 // work well; I have not seen any input method make use of them yet. 5491 // NSFontAttributeName, NSForegroundColorAttributeName, 5492 // NSBackgroundColorAttributeName, NSLanguageAttributeName. 5493 CFRetain(validAttributes); 5494 } 5495 LOG(TextInput, "validAttributesForMarkedText -> (...)"); 5496 return validAttributes; 5497} 5498 5499- (NSTextInputContext *)inputContext 5500{ 5501 return _private->exposeInputContext ? [super inputContext] : nil; 5502} 5503 5504- (NSAttributedString *)textStorage 5505{ 5506 if (!_private->exposeInputContext) { 5507 LOG(TextInput, "textStorage -> nil"); 5508 return nil; 5509 } 5510 NSAttributedString *result = [self attributedSubstringFromRange:NSMakeRange(0, UINT_MAX)]; 5511 5512 LOG(TextInput, "textStorage -> \"%@\"", result ? [result string] : @""); 5513 5514 // We have to return an empty string rather than null to prevent TSM from calling -string 5515 return result ? result : [[[NSAttributedString alloc] initWithString:@""] autorelease]; 5516} 5517 5518- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint 5519{ 5520 NSWindow *window = [self window]; 5521 WebFrame *frame = [self _frame]; 5522 5523 if (window) 5524 thePoint = [window convertScreenToBase:thePoint]; 5525 thePoint = [self convertPoint:thePoint fromView:nil]; 5526 5527 DOMRange *range = [frame _characterRangeAtPoint:thePoint]; 5528 if (!range) { 5529 LOG(TextInput, "characterIndexForPoint:(%f, %f) -> NSNotFound", thePoint.x, thePoint.y); 5530 return NSNotFound; 5531 } 5532 5533 unsigned result = [frame _convertDOMRangeToNSRange:range].location; 5534 LOG(TextInput, "characterIndexForPoint:(%f, %f) -> %u", thePoint.x, thePoint.y, result); 5535 return result; 5536} 5537 5538- (NSRect)firstRectForCharacterRange:(NSRange)theRange 5539{ 5540 WebFrame *frame = [self _frame]; 5541 5542 // Just to match NSTextView's behavior. Regression tests cannot detect this; 5543 // to reproduce, use a test application from http://bugs.webkit.org/show_bug.cgi?id=4682 5544 // (type something; try ranges (1, -1) and (2, -1). 5545 if ((theRange.location + theRange.length < theRange.location) && (theRange.location + theRange.length != 0)) 5546 theRange.length = 0; 5547 5548 DOMRange *range = [frame _convertNSRangeToDOMRange:theRange]; 5549 if (!range) { 5550 LOG(TextInput, "firstRectForCharacterRange:(%u, %u) -> (0, 0, 0, 0)", theRange.location, theRange.length); 5551 return NSMakeRect(0, 0, 0, 0); 5552 } 5553 5554 ASSERT([range startContainer]); 5555 ASSERT([range endContainer]); 5556 5557 NSRect resultRect = [frame _firstRectForDOMRange:range]; 5558 resultRect = [self convertRect:resultRect toView:nil]; 5559 5560 NSWindow *window = [self window]; 5561 if (window) 5562 resultRect.origin = [window convertBaseToScreen:resultRect.origin]; 5563 5564 LOG(TextInput, "firstRectForCharacterRange:(%u, %u) -> (%f, %f, %f, %f)", theRange.location, theRange.length, resultRect.origin.x, resultRect.origin.y, resultRect.size.width, resultRect.size.height); 5565 return resultRect; 5566} 5567 5568- (NSRange)selectedRange 5569{ 5570 if (!isTextInput(core([self _frame]))) { 5571 LOG(TextInput, "selectedRange -> (NSNotFound, 0)"); 5572 return NSMakeRange(NSNotFound, 0); 5573 } 5574 NSRange result = [[self _frame] _selectedNSRange]; 5575 5576 LOG(TextInput, "selectedRange -> (%u, %u)", result.location, result.length); 5577 return result; 5578} 5579 5580- (NSRange)markedRange 5581{ 5582 WebFrame *webFrame = [self _frame]; 5583 Frame* coreFrame = core(webFrame); 5584 if (!coreFrame) 5585 return NSMakeRange(0, 0); 5586 NSRange result = [webFrame _convertToNSRange:coreFrame->editor()->compositionRange().get()]; 5587 5588 LOG(TextInput, "markedRange -> (%u, %u)", result.location, result.length); 5589 return result; 5590} 5591 5592- (NSAttributedString *)attributedSubstringFromRange:(NSRange)nsRange 5593{ 5594 WebFrame *frame = [self _frame]; 5595 Frame* coreFrame = core(frame); 5596 if (!isTextInput(coreFrame) || isInPasswordField(coreFrame)) { 5597 LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> nil", nsRange.location, nsRange.length); 5598 return nil; 5599 } 5600 DOMRange *domRange = [frame _convertNSRangeToDOMRange:nsRange]; 5601 if (!domRange) { 5602 LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> nil", nsRange.location, nsRange.length); 5603 return nil; 5604 } 5605 5606 NSAttributedString *result = [NSAttributedString _web_attributedStringFromRange:core(domRange)]; 5607 5608 // [NSAttributedString(WebKitExtras) _web_attributedStringFromRange:] insists on inserting a trailing 5609 // whitespace at the end of the string which breaks the ATOK input method. <rdar://problem/5400551> 5610 // To work around this we truncate the resultant string to the correct length. 5611 if ([result length] > nsRange.length) { 5612 ASSERT([result length] == nsRange.length + 1); 5613 ASSERT([[result string] characterAtIndex:nsRange.length] == '\n' || [[result string] characterAtIndex:nsRange.length] == ' '); 5614 result = [result attributedSubstringFromRange:NSMakeRange(0, nsRange.length)]; 5615 } 5616 LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> \"%@\"", nsRange.location, nsRange.length, [result string]); 5617 return result; 5618} 5619 5620// test for 10.4 because of <rdar://problem/4243463> 5621#ifdef BUILDING_ON_TIGER 5622- (long)conversationIdentifier 5623{ 5624 return (long)self; 5625} 5626#else 5627- (NSInteger)conversationIdentifier 5628{ 5629 return (NSInteger)self; 5630} 5631#endif 5632 5633- (BOOL)hasMarkedText 5634{ 5635 Frame* coreFrame = core([self _frame]); 5636 BOOL result = coreFrame && coreFrame->editor()->hasComposition(); 5637 LOG(TextInput, "hasMarkedText -> %u", result); 5638 return result; 5639} 5640 5641- (void)unmarkText 5642{ 5643 LOG(TextInput, "unmarkText"); 5644 5645 // Use pointer to get parameters passed to us by the caller of interpretKeyEvents. 5646 WebHTMLViewInterpretKeyEventsParameters* parameters = _private->interpretKeyEventsParameters; 5647 _private->interpretKeyEventsParameters = 0; 5648 5649 if (parameters) { 5650 parameters->eventWasHandled = YES; 5651 parameters->consumedByIM = NO; 5652 } 5653 5654 if (Frame* coreFrame = core([self _frame])) 5655 coreFrame->editor()->confirmComposition(); 5656} 5657 5658static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnderline>& result) 5659{ 5660 int length = [[string string] length]; 5661 5662 int i = 0; 5663 while (i < length) { 5664 NSRange range; 5665 NSDictionary *attrs = [string attributesAtIndex:i longestEffectiveRange:&range inRange:NSMakeRange(i, length - i)]; 5666 5667 if (NSNumber *style = [attrs objectForKey:NSUnderlineStyleAttributeName]) { 5668 Color color = Color::black; 5669 if (NSColor *colorAttr = [attrs objectForKey:NSUnderlineColorAttributeName]) 5670 color = colorFromNSColor([colorAttr colorUsingColorSpaceName:NSDeviceRGBColorSpace]); 5671 result.append(CompositionUnderline(range.location, NSMaxRange(range), color, [style intValue] > 1)); 5672 } 5673 5674 i = range.location + range.length; 5675 } 5676} 5677 5678- (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange 5679{ 5680 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; // Otherwise, NSString 5681 5682 LOG(TextInput, "setMarkedText:\"%@\" selectedRange:(%u, %u)", isAttributedString ? [string string] : string, newSelRange.location, newSelRange.length); 5683 5684 // Use pointer to get parameters passed to us by the caller of interpretKeyEvents. 5685 WebHTMLViewInterpretKeyEventsParameters* parameters = _private->interpretKeyEventsParameters; 5686 _private->interpretKeyEventsParameters = 0; 5687 5688 if (parameters) { 5689 parameters->eventWasHandled = YES; 5690 parameters->consumedByIM = NO; 5691 } 5692 5693 Frame* coreFrame = core([self _frame]); 5694 if (!coreFrame) 5695 return; 5696 5697 if (![self _isEditable]) 5698 return; 5699 5700 Vector<CompositionUnderline> underlines; 5701 NSString *text = string; 5702 5703 if (isAttributedString) { 5704 unsigned markedTextLength = [(NSString *)string length]; 5705 NSString *rangeString = [string attribute:NSTextInputReplacementRangeAttributeName atIndex:0 longestEffectiveRange:NULL inRange:NSMakeRange(0, markedTextLength)]; 5706 LOG(TextInput, " ReplacementRange: %@", rangeString); 5707 // The AppKit adds a 'secret' property to the string that contains the replacement range. 5708 // The replacement range is the range of the the text that should be replaced with the new string. 5709 if (rangeString) 5710 [[self _frame] _selectNSRange:NSRangeFromString(rangeString)]; 5711 5712 text = [string string]; 5713 extractUnderlines(string, underlines); 5714 } 5715 5716 coreFrame->editor()->setComposition(text, underlines, newSelRange.location, NSMaxRange(newSelRange)); 5717} 5718 5719- (void)doCommandBySelector:(SEL)selector 5720{ 5721 LOG(TextInput, "doCommandBySelector:\"%s\"", sel_getName(selector)); 5722 5723 // Use pointer to get parameters passed to us by the caller of interpretKeyEvents. 5724 // The same call to interpretKeyEvents can do more than one command. 5725 WebHTMLViewInterpretKeyEventsParameters* parameters = _private->interpretKeyEventsParameters; 5726 if (parameters) 5727 parameters->consumedByIM = NO; 5728 5729 if (selector == @selector(noop:)) { 5730 _private->receivedNOOP = YES; 5731 return; 5732 } 5733 5734 KeyboardEvent* event = parameters ? parameters->event : 0; 5735 bool shouldSaveCommand = parameters && parameters->shouldSaveCommand; 5736 5737 if (event && shouldSaveCommand) 5738 event->keypressCommands().append(KeypressCommand(NSStringFromSelector(selector))); 5739 else { 5740 // Make sure that only direct calls to doCommandBySelector: see the parameters by setting to 0. 5741 _private->interpretKeyEventsParameters = 0; 5742 5743 bool eventWasHandled; 5744 5745 WebView *webView = [self _webView]; 5746 if ([[webView _editingDelegateForwarder] webView:webView doCommandBySelector:selector]) 5747 eventWasHandled = true; 5748 else { 5749 Editor::Command command = [self coreCommandBySelector:selector]; 5750 if (command.isSupported()) 5751 eventWasHandled = command.execute(event); 5752 else { 5753 // If WebKit does not support this command, we need to pass the selector to super. 5754 _private->selectorForDoCommandBySelector = selector; 5755 5756 // The sink does two things: 1) Tells us if the responder went unhandled, and 5757 // 2) prevents any NSBeep; we don't ever want to beep here. 5758 WebResponderChainSink *sink = [[WebResponderChainSink alloc] initWithResponderChain:self]; 5759 [super doCommandBySelector:selector]; 5760 eventWasHandled = ![sink receivedUnhandledCommand]; 5761 [sink detach]; 5762 [sink release]; 5763 5764 _private->selectorForDoCommandBySelector = 0; 5765 } 5766 } 5767 5768 if (parameters) 5769 parameters->eventWasHandled = eventWasHandled; 5770 5771 // Restore the parameters so that other calls to doCommandBySelector: see them, 5772 // and other commands can participate in setting the "eventWasHandled" flag. 5773 _private->interpretKeyEventsParameters = parameters; 5774 } 5775} 5776 5777- (void)insertText:(id)string 5778{ 5779 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; // Otherwise, NSString 5780 5781 LOG(TextInput, "insertText:\"%@\"", isAttributedString ? [string string] : string); 5782 5783 WebHTMLViewInterpretKeyEventsParameters* parameters = _private->interpretKeyEventsParameters; 5784 _private->interpretKeyEventsParameters = 0; 5785 if (parameters) 5786 parameters->consumedByIM = NO; 5787 5788 // We don't support inserting an attributed string but input methods don't appear to require this. 5789 RefPtr<Frame> coreFrame = core([self _frame]); 5790 NSString *text; 5791 bool isFromInputMethod = coreFrame && coreFrame->editor()->hasComposition(); 5792 if (isAttributedString) { 5793 text = [string string]; 5794 // We deal with the NSTextInputReplacementRangeAttributeName attribute from NSAttributedString here 5795 // simply because it is used by at least one Input Method -- it corresonds to the kEventParamTextInputSendReplaceRange 5796 // event in TSM. This behaviour matches that of -[WebHTMLView setMarkedText:selectedRange:] when it receives an 5797 // NSAttributedString 5798 NSString *rangeString = [string attribute:NSTextInputReplacementRangeAttributeName atIndex:0 longestEffectiveRange:NULL inRange:NSMakeRange(0, [text length])]; 5799 LOG(TextInput, " ReplacementRange: %@", rangeString); 5800 if (rangeString) { 5801 [[self _frame] _selectNSRange:NSRangeFromString(rangeString)]; 5802 isFromInputMethod = YES; 5803 } 5804 } else 5805 text = string; 5806 5807 bool eventHandled = false; 5808 if ([text length]) { 5809 KeyboardEvent* event = parameters ? parameters->event : 0; 5810 5811 // insertText can be called from an input method or from normal key event processing 5812 // If its from normal key event processing, we may need to save the action to perform it later. 5813 // If its from an input method, then we should go ahead and insert the text now. 5814 // We assume it's from the input method if we have marked text. 5815 // FIXME: In theory, this could be wrong for some input methods, so we should try to find 5816 // another way to determine if the call is from the input method 5817 bool shouldSaveCommand = parameters && parameters->shouldSaveCommand; 5818 if (event && shouldSaveCommand && !isFromInputMethod) { 5819 event->keypressCommands().append(KeypressCommand("insertText:", text)); 5820 _private->interpretKeyEventsParameters = parameters; 5821 return; 5822 } 5823 5824 String eventText = text; 5825 eventText.replace(NSBackTabCharacter, NSTabCharacter); // same thing is done in KeyEventMac.mm in WebCore 5826 if (coreFrame && coreFrame->editor()->canEdit()) { 5827 if (!coreFrame->editor()->hasComposition()) 5828 eventHandled = coreFrame->editor()->insertText(eventText, event); 5829 else { 5830 eventHandled = true; 5831 coreFrame->editor()->confirmComposition(eventText); 5832 } 5833 } 5834 } 5835 5836 if (!parameters) 5837 return; 5838 5839 if (isFromInputMethod) { 5840 // Allow doCommandBySelector: to be called after insertText: by resetting interpretKeyEventsParameters 5841 _private->interpretKeyEventsParameters = parameters; 5842 parameters->consumedByIM = YES; 5843 return; 5844 } 5845 5846 parameters->eventWasHandled = eventHandled; 5847} 5848 5849- (void)_updateSelectionForInputManager 5850{ 5851 Frame* coreFrame = core([self _frame]); 5852 if (!coreFrame) 5853 return; 5854 5855 BOOL exposeInputContext = isTextInput(coreFrame) && !isInPasswordField(coreFrame); 5856 if (exposeInputContext != _private->exposeInputContext) { 5857 _private->exposeInputContext = exposeInputContext; 5858 // Let AppKit cache a potentially changed input context. 5859 // WebCore routinely sets the selection to None when editing, and IMs become unhappy when an input context suddenly turns nil, see bug 26009. 5860 if (!coreFrame->selection()->isNone()) 5861 [NSApp updateWindows]; 5862 } 5863 5864 if (!coreFrame->editor()->hasComposition()) 5865 return; 5866 5867 if (coreFrame->editor()->ignoreCompositionSelectionChange()) 5868 return; 5869 5870 unsigned start; 5871 unsigned end; 5872 if (coreFrame->editor()->getCompositionSelection(start, end)) 5873 [[NSInputManager currentInputManager] markedTextSelectionChanged:NSMakeRange(start, end - start) client:self]; 5874 else { 5875 coreFrame->editor()->confirmCompositionWithoutDisturbingSelection(); 5876 [[NSInputManager currentInputManager] markedTextAbandoned:self]; 5877 } 5878} 5879 5880@end 5881 5882@implementation WebHTMLView (WebDocumentPrivateProtocols) 5883 5884- (NSRect)selectionRect 5885{ 5886 if ([self _hasSelection]) 5887 return core([self _frame])->selectionBounds(); 5888 return NSZeroRect; 5889} 5890 5891- (NSArray *)selectionTextRects 5892{ 5893 if (![self _hasSelection]) 5894 return nil; 5895 5896 Vector<FloatRect> list; 5897 if (Frame* coreFrame = core([self _frame])) 5898 coreFrame->selectionTextRects(list); 5899 5900 unsigned size = list.size(); 5901 NSMutableArray *result = [[[NSMutableArray alloc] initWithCapacity:size] autorelease]; 5902 for (unsigned i = 0; i < size; ++i) 5903 [result addObject:[NSValue valueWithRect:list[i]]]; 5904 5905 return result; 5906} 5907 5908- (NSView *)selectionView 5909{ 5910 return self; 5911} 5912 5913- (NSImage *)selectionImageForcingBlackText:(BOOL)forceBlackText 5914{ 5915 if ([self _hasSelection]) 5916 return core([self _frame])->selectionImage(forceBlackText); 5917 return nil; 5918} 5919 5920- (NSRect)selectionImageRect 5921{ 5922 if ([self _hasSelection]) 5923 return core([self _frame])->selectionBounds(); 5924 return NSZeroRect; 5925} 5926 5927- (NSArray *)pasteboardTypesForSelection 5928{ 5929 if ([self _canSmartCopyOrDelete]) { 5930 NSMutableArray *types = [[[[self class] _selectionPasteboardTypes] mutableCopy] autorelease]; 5931 [types addObject:WebSmartPastePboardType]; 5932 return types; 5933 } else { 5934 return [[self class] _selectionPasteboardTypes]; 5935 } 5936} 5937 5938- (void)writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard 5939{ 5940 [self _writeSelectionWithPasteboardTypes:types toPasteboard:pasteboard cachedAttributedString:nil]; 5941} 5942 5943- (void)selectAll 5944{ 5945 Frame* coreFrame = core([self _frame]); 5946 if (coreFrame) 5947 coreFrame->selection()->selectAll(); 5948} 5949 5950- (void)deselectAll 5951{ 5952 Frame* coreFrame = core([self _frame]); 5953 if (!coreFrame) 5954 return; 5955 coreFrame->selection()->clear(); 5956} 5957 5958- (NSString *)string 5959{ 5960 return [[self _frame] _stringForRange:[self _documentRange]]; 5961} 5962 5963- (NSAttributedString *)_attributeStringFromDOMRange:(DOMRange *)range 5964{ 5965 NSAttributedString *attributedString; 5966#if !LOG_DISABLED 5967 double start = CFAbsoluteTimeGetCurrent(); 5968#endif 5969 attributedString = [[[NSAttributedString alloc] _initWithDOMRange:range] autorelease]; 5970#if !LOG_DISABLED 5971 double duration = CFAbsoluteTimeGetCurrent() - start; 5972 LOG(Timing, "creating attributed string from selection took %f seconds.", duration); 5973#endif 5974 return attributedString; 5975} 5976 5977- (NSAttributedString *)attributedString 5978{ 5979 DOMDocument *document = [[self _frame] DOMDocument]; 5980 NSAttributedString *attributedString = [self _attributeStringFromDOMRange:[document _documentRange]]; 5981 if (!attributedString) { 5982 Document* coreDocument = core(document); 5983 attributedString = [NSAttributedString _web_attributedStringFromRange:Range::create(coreDocument, coreDocument, 0, coreDocument, coreDocument->childNodeCount()).get()]; 5984 } 5985 return attributedString; 5986} 5987 5988- (NSString *)selectedString 5989{ 5990 return [[self _frame] _selectedString]; 5991} 5992 5993- (NSAttributedString *)selectedAttributedString 5994{ 5995 NSAttributedString *attributedString = [self _attributeStringFromDOMRange:[self _selectedRange]]; 5996 if (!attributedString) { 5997 Frame* coreFrame = core([self _frame]); 5998 if (coreFrame) { 5999 RefPtr<Range> range = coreFrame->selection()->selection().toNormalizedRange(); 6000 attributedString = [NSAttributedString _web_attributedStringFromRange:range.get()]; 6001 } 6002 } 6003 return attributedString; 6004} 6005 6006- (BOOL)supportsTextEncoding 6007{ 6008 return YES; 6009} 6010 6011- (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag startInSelection:(BOOL)startInSelection 6012{ 6013 if (![string length]) 6014 return NO; 6015 Frame* coreFrame = core([self _frame]); 6016 return coreFrame && coreFrame->findString(string, forward, caseFlag, wrapFlag, startInSelection); 6017} 6018 6019@end 6020 6021@implementation WebHTMLView (WebDocumentInternalProtocols) 6022 6023- (NSDictionary *)elementAtPoint:(NSPoint)point 6024{ 6025 return [self elementAtPoint:point allowShadowContent:NO]; 6026} 6027 6028- (NSDictionary *)elementAtPoint:(NSPoint)point allowShadowContent:(BOOL)allow 6029{ 6030 Frame* coreFrame = core([self _frame]); 6031 if (!coreFrame) 6032 return nil; 6033 return [[[WebElementDictionary alloc] initWithHitTestResult:coreFrame->eventHandler()->hitTestResultAtPoint(IntPoint(point), allow)] autorelease]; 6034} 6035 6036- (NSUInteger)markAllMatchesForText:(NSString *)string caseSensitive:(BOOL)caseFlag limit:(NSUInteger)limit 6037{ 6038 Frame* coreFrame = core([self _frame]); 6039 if (!coreFrame) 6040 return 0; 6041 return coreFrame->markAllMatchesForText(string, caseFlag, limit); 6042} 6043 6044- (void)setMarkedTextMatchesAreHighlighted:(BOOL)newValue 6045{ 6046 Frame* coreFrame = core([self _frame]); 6047 if (!coreFrame) 6048 return; 6049 coreFrame->setMarkedTextMatchesAreHighlighted(newValue); 6050} 6051 6052- (BOOL)markedTextMatchesAreHighlighted 6053{ 6054 Frame* coreFrame = core([self _frame]); 6055 return coreFrame && coreFrame->markedTextMatchesAreHighlighted(); 6056} 6057 6058- (void)unmarkAllTextMatches 6059{ 6060 Frame* coreFrame = core([self _frame]); 6061 if (!coreFrame) 6062 return; 6063 Document* document = coreFrame->document(); 6064 if (!document) 6065 return; 6066 document->removeMarkers(DocumentMarker::TextMatch); 6067} 6068 6069- (NSArray *)rectsForTextMatches 6070{ 6071 Frame* coreFrame = core([self _frame]); 6072 if (!coreFrame) 6073 return [NSArray array]; 6074 Document* document = coreFrame->document(); 6075 if (!document) 6076 return [NSArray array]; 6077 6078 Vector<IntRect> rects = document->renderedRectsForMarkers(DocumentMarker::TextMatch); 6079 unsigned count = rects.size(); 6080 NSMutableArray *result = [NSMutableArray arrayWithCapacity:count]; 6081 for (unsigned index = 0; index < count; ++index) 6082 [result addObject:[NSValue valueWithRect:rects[index]]]; 6083 return result; 6084} 6085 6086@end 6087 6088// This is used by AppKit and is included here so that WebDataProtocolScheme is only defined once. 6089@implementation NSURL (WebDataURL) 6090 6091+ (NSURL *)_web_uniqueWebDataURL 6092{ 6093 CFUUIDRef UUIDRef = CFUUIDCreate(kCFAllocatorDefault); 6094 NSString *UUIDString = (NSString *)CFUUIDCreateString(kCFAllocatorDefault, UUIDRef); 6095 CFRelease(UUIDRef); 6096 NSURL *URL = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@", WebDataProtocolScheme, UUIDString]]; 6097 CFRelease(UUIDString); 6098 return URL; 6099} 6100 6101@end 6102 6103@implementation WebResponderChainSink 6104 6105- (id)initWithResponderChain:(NSResponder *)chain 6106{ 6107 self = [super init]; 6108 _lastResponderInChain = chain; 6109 while (NSResponder *next = [_lastResponderInChain nextResponder]) 6110 _lastResponderInChain = next; 6111 [_lastResponderInChain setNextResponder:self]; 6112 return self; 6113} 6114 6115- (void)detach 6116{ 6117 [_lastResponderInChain setNextResponder:nil]; 6118 _lastResponderInChain = nil; 6119} 6120 6121- (BOOL)receivedUnhandledCommand 6122{ 6123 return _receivedUnhandledCommand; 6124} 6125 6126- (void)noResponderFor:(SEL)selector 6127{ 6128 _receivedUnhandledCommand = YES; 6129} 6130 6131- (void)doCommandBySelector:(SEL)selector 6132{ 6133 _receivedUnhandledCommand = YES; 6134} 6135 6136- (BOOL)tryToPerform:(SEL)action with:(id)object 6137{ 6138 _receivedUnhandledCommand = YES; 6139 return YES; 6140} 6141 6142@end 6143