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