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