1/* 2 * Copyright (C) 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 14 * its contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29#import "WebFrameView.h" 30 31#import "WebClipView.h" 32#import "WebDataSourcePrivate.h" 33#import "WebDocument.h" 34#import "WebDynamicScrollBarsViewInternal.h" 35#import "WebFrame.h" 36#import "WebFrameInternal.h" 37#import "WebFrameViewInternal.h" 38#import "WebFrameViewPrivate.h" 39#import "WebHistoryItemInternal.h" 40#import "WebHTMLViewPrivate.h" 41#import "WebKeyGenerator.h" 42#import "WebKitErrorsPrivate.h" 43#import "WebKitStatisticsPrivate.h" 44#import "WebKitVersionChecks.h" 45#import "WebNSDictionaryExtras.h" 46#import "WebNSObjectExtras.h" 47#import "WebNSPasteboardExtras.h" 48#import "WebNSViewExtras.h" 49#import "WebNSWindowExtras.h" 50#import "WebPDFView.h" 51#import "WebPreferenceKeysPrivate.h" 52#import "WebResourceInternal.h" 53#import "WebSystemInterface.h" 54#import "WebViewFactory.h" 55#import "WebViewInternal.h" 56#import "WebViewPrivate.h" 57#import <Foundation/NSURLRequest.h> 58#import <WebCore/BackForwardListImpl.h> 59#import <WebCore/DragController.h> 60#import <WebCore/EventHandler.h> 61#import <WebCore/Frame.h> 62#import <WebCore/FrameView.h> 63#import <WebCore/HistoryItem.h> 64#import <WebCore/Page.h> 65#import <WebCore/RenderPart.h> 66#import <WebCore/ThreadCheck.h> 67#import <WebCore/WebCoreFrameView.h> 68#import <WebCore/WebCoreView.h> 69#import <WebKitSystemInterface.h> 70#import <wtf/Assertions.h> 71 72using namespace WebCore; 73 74@interface NSWindow (WindowPrivate) 75- (BOOL)_needsToResetDragMargins; 76- (void)_setNeedsToResetDragMargins:(BOOL)s; 77@end 78 79@interface NSClipView (AppKitSecretsIKnow) 80- (BOOL)_scrollTo:(const NSPoint *)newOrigin animate:(BOOL)animate; // need the boolean result from this method 81@end 82 83enum { 84 SpaceKey = 0x0020 85}; 86 87@interface WebFrameView (WebFrameViewFileInternal) <WebCoreFrameView> 88- (float)_verticalKeyboardScrollDistance; 89@end 90 91@interface WebFrameViewPrivate : NSObject { 92@public 93 WebFrame *webFrame; 94 WebDynamicScrollBarsView *frameScrollView; 95 BOOL includedInWebKitStatistics; 96} 97@end 98 99@implementation WebFrameViewPrivate 100 101- (void)dealloc 102{ 103 [frameScrollView release]; 104 [super dealloc]; 105} 106 107@end 108 109@implementation WebFrameView (WebFrameViewFileInternal) 110 111- (float)_verticalKeyboardScrollDistance 112{ 113 // Arrow keys scroll the same distance that clicking the scroll arrow does. 114 return [[self _scrollView] verticalLineScroll]; 115} 116 117- (Frame*)_web_frame 118{ 119 return core(_private->webFrame); 120} 121 122@end 123 124@implementation WebFrameView (WebInternal) 125 126// Note that the WebVew is not retained. 127- (WebView *)_webView 128{ 129 return [_private->webFrame webView]; 130} 131 132- (void)_setDocumentView:(NSView <WebDocumentView> *)view 133{ 134 WebDynamicScrollBarsView *sv = [self _scrollView]; 135 core([self _webView])->dragController()->setDidInitiateDrag(false); 136 137 [sv setSuppressLayout:YES]; 138 139 // If the old view is the first responder, transfer first responder status to the new view as 140 // a convenience and so that we don't leave the window pointing to a view that's no longer in it. 141 NSWindow *window = [sv window]; 142 NSResponder *firstResponder = [window firstResponder]; 143 bool makeNewViewFirstResponder = [firstResponder isKindOfClass:[NSView class]] && [(NSView *)firstResponder isDescendantOf:[sv documentView]]; 144 145 // Suppress the resetting of drag margins since we know we can't affect them. 146 BOOL resetDragMargins = [window _needsToResetDragMargins]; 147 [window _setNeedsToResetDragMargins:NO]; 148 [sv setDocumentView:view]; 149 [window _setNeedsToResetDragMargins:resetDragMargins]; 150 151 if (makeNewViewFirstResponder) 152 [window makeFirstResponder:view]; 153 [sv setSuppressLayout:NO]; 154} 155 156-(NSView <WebDocumentView> *)_makeDocumentViewForDataSource:(WebDataSource *)dataSource 157{ 158 NSString* MIMEType = [dataSource _responseMIMEType]; 159 if (!MIMEType) 160 MIMEType = @"text/html"; 161 Class viewClass = [self _viewClassForMIMEType:MIMEType]; 162 NSView <WebDocumentView> *documentView; 163 if (viewClass) { 164 // If the dataSource's representation has already been created, and it is also the 165 // same class as the desired documentView, then use it as the documentView instead 166 // of creating another one (Radar 4340787). 167 id <WebDocumentRepresentation> dataSourceRepresentation = [dataSource representation]; 168 if (dataSourceRepresentation && [dataSourceRepresentation class] == viewClass) 169 documentView = (NSView <WebDocumentView> *)[dataSourceRepresentation retain]; 170 else 171 documentView = [[viewClass alloc] initWithFrame:[self bounds]]; 172 } else 173 documentView = nil; 174 175 [self _setDocumentView:documentView]; 176 [documentView release]; 177 178 return documentView; 179} 180 181- (void)_setWebFrame:(WebFrame *)webFrame 182{ 183 if (!webFrame) { 184 NSView *docV = [self documentView]; 185 if ([docV respondsToSelector:@selector(close)]) 186 [docV performSelector:@selector(close)]; 187 } 188 189 // Not retained because the WebView owns the WebFrame, which owns the WebFrameView. 190 _private->webFrame = webFrame; 191 192 if (!_private->includedInWebKitStatistics && [webFrame _isIncludedInWebKitStatistics]) { 193 _private->includedInWebKitStatistics = YES; 194 ++WebFrameViewCount; 195 } 196} 197 198- (WebDynamicScrollBarsView *)_scrollView 199{ 200 // This can be called by [super dealloc] when cleaning up the key view loop, 201 // after _private has been nilled out. 202 if (_private == nil) 203 return nil; 204 return _private->frameScrollView; 205} 206 207- (float)_verticalPageScrollDistance 208{ 209 float height = [[self _contentView] bounds].size.height; 210 return max<float>(height * Scrollbar::minFractionToStepWhenPaging(), height - Scrollbar::maxOverlapBetweenPages()); 211} 212 213static inline void addTypesFromClass(NSMutableDictionary *allTypes, Class objCClass, NSArray *supportTypes) 214{ 215 NSEnumerator *enumerator = [supportTypes objectEnumerator]; 216 ASSERT(enumerator != nil); 217 NSString *mime = nil; 218 while ((mime = [enumerator nextObject]) != nil) { 219 // Don't clobber previously-registered classes. 220 if ([allTypes objectForKey:mime] == nil) 221 [allTypes setObject:objCClass forKey:mime]; 222 } 223} 224 225+ (NSMutableDictionary *)_viewTypesAllowImageTypeOmission:(BOOL)allowImageTypeOmission 226{ 227 static NSMutableDictionary *viewTypes = nil; 228 static BOOL addedImageTypes = NO; 229 230 if (!viewTypes) { 231 viewTypes = [[NSMutableDictionary alloc] init]; 232 addTypesFromClass(viewTypes, [WebHTMLView class], [WebHTMLView supportedNonImageMIMETypes]); 233 234 // Since this is a "secret default" we don't bother registering it. 235 BOOL omitPDFSupport = [[NSUserDefaults standardUserDefaults] boolForKey:@"WebKitOmitPDFSupport"]; 236 if (!omitPDFSupport) 237 addTypesFromClass(viewTypes, [WebPDFView class], [WebPDFView supportedMIMETypes]); 238 } 239 240 if (!addedImageTypes && !allowImageTypeOmission) { 241 addTypesFromClass(viewTypes, [WebHTMLView class], [WebHTMLView supportedImageMIMETypes]); 242 addedImageTypes = YES; 243 } 244 245 return viewTypes; 246} 247 248+ (BOOL)_canShowMIMETypeAsHTML:(NSString *)MIMEType 249{ 250 return [[[self _viewTypesAllowImageTypeOmission:YES] _webkit_objectForMIMEType:MIMEType] isSubclassOfClass:[WebHTMLView class]]; 251} 252 253+ (Class)_viewClassForMIMEType:(NSString *)MIMEType allowingPlugins:(BOOL)allowPlugins 254{ 255 Class viewClass; 256 return [WebView _viewClass:&viewClass andRepresentationClass:nil forMIMEType:MIMEType allowingPlugins:allowPlugins] ? viewClass : nil; 257} 258 259- (Class)_viewClassForMIMEType:(NSString *)MIMEType 260{ 261 return [[self class] _viewClassForMIMEType:MIMEType allowingPlugins:[[[self _webView] preferences] arePlugInsEnabled]]; 262} 263 264- (void)_install 265{ 266 ASSERT(_private->webFrame); 267 ASSERT(_private->frameScrollView); 268 269 Frame* frame = core(_private->webFrame); 270 271 ASSERT(frame); 272 ASSERT(frame->page()); 273 274 // If this isn't the main frame, it must have an owner element set, or it 275 // won't ever get installed in the view hierarchy. 276 ASSERT(frame == frame->page()->mainFrame() || frame->ownerElement()); 277 278 FrameView* view = frame->view(); 279 280 view->setPlatformWidget(_private->frameScrollView); 281 282 // FIXME: Frame tries to do this too. Is this code needed? 283 if (RenderPart* owner = frame->ownerRenderer()) { 284 owner->setWidget(view); 285 // Now the render part owns the view, so we don't any more. 286 } 287 288 view->updateCanHaveScrollbars(); 289} 290 291@end 292 293@implementation WebFrameView 294 295- (id)initWithFrame:(NSRect)frame 296{ 297 self = [super initWithFrame:frame]; 298 if (!self) 299 return nil; 300 301 static bool didFirstTimeInitialization; 302 if (!didFirstTimeInitialization) { 303 didFirstTimeInitialization = true; 304 InitWebCoreSystemInterface(); 305 306 // Need to tell WebCore what function to call for the "History Item has Changed" notification. 307 // Note: We also do this in WebHistoryItem's init method. 308 WebCore::notifyHistoryItemChanged = WKNotifyHistoryItemChanged; 309 310 [WebViewFactory createSharedFactory]; 311 312// FIXME: Remove the NSAppKitVersionNumberWithDeferredWindowDisplaySupport check once 313// once AppKit's Deferred Window Display support is available. 314#if defined(BUILDING_ON_TIGER) || defined(BUILDING_ON_LEOPARD) || !defined(NSAppKitVersionNumberWithDeferredWindowDisplaySupport) 315 // CoreGraphics deferred updates are disabled if WebKitEnableCoalescedUpdatesPreferenceKey is NO 316 // or has no value. For compatibility with Mac OS X 10.5 and lower, deferred updates are off by default. 317 if (![[NSUserDefaults standardUserDefaults] boolForKey:WebKitEnableDeferredUpdatesPreferenceKey]) 318 WKDisableCGDeferredUpdates(); 319#endif 320 if (!WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITH_MAIN_THREAD_EXCEPTIONS)) 321 setDefaultThreadViolationBehavior(LogOnFirstThreadViolation, ThreadViolationRoundOne); 322 323 bool throwExceptionsForRoundTwo = WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITH_ROUND_TWO_MAIN_THREAD_EXCEPTIONS); 324#ifdef MAIL_THREAD_WORKAROUND 325 // Even if old Mail is linked with new WebKit, don't throw exceptions. 326 if ([WebResource _needMailThreadWorkaroundIfCalledOffMainThread]) 327 throwExceptionsForRoundTwo = false; 328#endif 329 if (!throwExceptionsForRoundTwo) 330 setDefaultThreadViolationBehavior(LogOnFirstThreadViolation, ThreadViolationRoundTwo); 331 } 332 333 _private = [[WebFrameViewPrivate alloc] init]; 334 335 WebDynamicScrollBarsView *scrollView = [[WebDynamicScrollBarsView alloc] initWithFrame:NSMakeRect(0.0f, 0.0f, frame.size.width, frame.size.height)]; 336 _private->frameScrollView = scrollView; 337 [scrollView setContentView:[[[WebClipView alloc] initWithFrame:[scrollView bounds]] autorelease]]; 338 [scrollView setDrawsBackground:NO]; 339 [scrollView setHasVerticalScroller:NO]; 340 [scrollView setHasHorizontalScroller:NO]; 341 [scrollView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; 342 [scrollView setLineScroll:Scrollbar::pixelsPerLineStep()]; 343 [self addSubview:scrollView]; 344 345 // Don't call our overridden version of setNextKeyView here; we need to make the standard NSView 346 // link between us and our subview so that previousKeyView and previousValidKeyView work as expected. 347 // This works together with our becomeFirstResponder and setNextKeyView overrides. 348 [super setNextKeyView:scrollView]; 349 350 return self; 351} 352 353- (void)dealloc 354{ 355 if (_private && _private->includedInWebKitStatistics) 356 --WebFrameViewCount; 357 358 [_private release]; 359 _private = nil; 360 361 [super dealloc]; 362} 363 364- (void)finalize 365{ 366 if (_private && _private->includedInWebKitStatistics) 367 --WebFrameViewCount; 368 369 [super finalize]; 370} 371 372- (WebFrame *)webFrame 373{ 374 // This method can be called beneath -[NSView dealloc] after _private has been cleared. 375 return _private ? _private->webFrame : nil; 376} 377 378- (void)setAllowsScrolling:(BOOL)flag 379{ 380 WebCore::Frame *frame = core([self webFrame]); 381 if (WebCore::FrameView *view = frame? frame->view() : 0) 382 view->setCanHaveScrollbars(flag); 383} 384 385- (BOOL)allowsScrolling 386{ 387 WebCore::Frame *frame = core([self webFrame]); 388 if (WebCore::FrameView *view = frame? frame->view() : 0) 389 return view->canHaveScrollbars(); 390 return YES; 391} 392 393- (NSView <WebDocumentView> *)documentView 394{ 395 return [[self _scrollView] documentView]; 396} 397 398- (BOOL)acceptsFirstResponder 399{ 400 // We always accept first responder; this matches OS X 10.2 WebKit 401 // behavior (see 3469791). 402 return YES; 403} 404 405- (BOOL)becomeFirstResponder 406{ 407 // This works together with setNextKeyView to splice the WebFrameView into 408 // the key loop similar to the way NSScrollView does this. Note that 409 // WebView has similar code. 410 411 NSWindow *window = [self window]; 412 if ([window keyViewSelectionDirection] == NSSelectingPrevious) { 413 NSView *previousValidKeyView = [self previousValidKeyView]; 414 // If we couldn't find a previous valid key view, ask the WebView. This handles frameset 415 // cases (one is mentioned in Radar bug 3748628). Note that previousValidKeyView should 416 // never be self but can be due to AppKit oddness (mentioned in Radar bug 3748628). 417 if (previousValidKeyView == nil || previousValidKeyView == self) 418 previousValidKeyView = [[[self webFrame] webView] previousValidKeyView]; 419 [window makeFirstResponder:previousValidKeyView]; 420 } else { 421 // If the scroll view won't accept first-responderness now, then just become 422 // the first responder ourself like a normal view. This lets us be the first 423 // responder in cases where no page has yet been loaded. 424 if ([[self _scrollView] acceptsFirstResponder]) 425 [window makeFirstResponder:[self _scrollView]]; 426 } 427 428 return YES; 429} 430 431- (void)setNextKeyView:(NSView *)aView 432{ 433 // This works together with becomeFirstResponder to splice the WebFrameView into 434 // the key loop similar to the way NSScrollView does this. Note that 435 // WebView has very similar code. 436 if ([self _scrollView] != nil) { 437 [[self _scrollView] setNextKeyView:aView]; 438 } else { 439 [super setNextKeyView:aView]; 440 } 441} 442 443- (BOOL)isOpaque 444{ 445 return [[self _webView] drawsBackground]; 446} 447 448- (void)drawRect:(NSRect)rect 449{ 450 if ([self documentView] == nil) { 451 // Need to paint ourselves if there's no documentView to do it instead. 452 if ([[self _webView] drawsBackground]) { 453 [[[self _webView] backgroundColor] set]; 454 NSRectFill(rect); 455 } 456 } else { 457#ifndef NDEBUG 458 if ([[self _scrollView] drawsBackground]) { 459 [[NSColor cyanColor] set]; 460 NSRectFill(rect); 461 } 462#endif 463 } 464} 465 466- (NSRect)visibleRect 467{ 468 // This method can be called beneath -[NSView dealloc] after we have cleared _private. 469 if (!_private) 470 return [super visibleRect]; 471 472 // FIXME: <rdar://problem/6213380> This method does not work correctly with transforms, for two reasons: 473 // 1) [super visibleRect] does not account for the transform, since it is not represented 474 // in the NSView hierarchy. 475 // 2) -_getVisibleRect: does not correct for transforms. 476 477 NSRect rendererVisibleRect; 478 if (![[self webFrame] _getVisibleRect:&rendererVisibleRect]) 479 return [super visibleRect]; 480 481 if (NSIsEmptyRect(rendererVisibleRect)) 482 return NSZeroRect; 483 484 NSRect viewVisibleRect = [super visibleRect]; 485 if (NSIsEmptyRect(viewVisibleRect)) 486 return NSZeroRect; 487 488 NSRect frame = [self frame]; 489 // rendererVisibleRect is in the parent's coordinate space, and frame is in the superview's coordinate space. 490 // The return value from this method needs to be in this view's coordinate space. We get that right by subtracting 491 // the origins (and correcting for flipping), but when we support transforms, we will need to do better than this. 492 rendererVisibleRect.origin.x -= frame.origin.x; 493 rendererVisibleRect.origin.y = NSMaxY(frame) - NSMaxY(rendererVisibleRect); 494 return NSIntersectionRect(rendererVisibleRect, viewVisibleRect); 495} 496 497- (void)setFrameSize:(NSSize)size 498{ 499 if (!NSEqualSizes(size, [self frame].size)) { 500 // See WebFrameLoaderClient::provisionalLoadStarted. 501 if ([[[self webFrame] webView] drawsBackground]) 502 [[self _scrollView] setDrawsBackground:YES]; 503 if (Frame* coreFrame = [self _web_frame]) { 504 if (FrameView* coreFrameView = coreFrame->view()) 505 coreFrameView->setNeedsLayout(); 506 } 507 } 508 [super setFrameSize:size]; 509} 510 511- (void)setBoundsSize:(NSSize)size 512{ 513 [super setBoundsSize:size]; 514 [[self _scrollView] setFrameSize:size]; 515} 516 517- (void)viewDidMoveToWindow 518{ 519 // See WebFrameLoaderClient::provisionalLoadStarted. 520 // Need to check _private for nil because this can be called inside -[WebView initWithCoder:]. 521 if (_private && [[[self webFrame] webView] drawsBackground]) 522 [[self _scrollView] setDrawsBackground:YES]; 523 [super viewDidMoveToWindow]; 524} 525 526- (BOOL)_scrollOverflowInDirection:(ScrollDirection)direction granularity:(ScrollGranularity)granularity 527{ 528 // scrolling overflows is only applicable if we're dealing with an WebHTMLView 529 if (![[self documentView] isKindOfClass:[WebHTMLView class]]) 530 return NO; 531 Frame* frame = core([self webFrame]); 532 if (!frame) 533 return NO; 534 return frame->eventHandler()->scrollOverflow(direction, granularity); 535} 536 537 538- (BOOL)_isVerticalDocument 539{ 540 Frame* coreFrame = [self _web_frame]; 541 if (!coreFrame) 542 return YES; 543 Document* document = coreFrame->document(); 544 if (!document) 545 return YES; 546 RenderObject* renderView = document->renderer(); 547 if (!renderView) 548 return YES; 549 return renderView->style()->isHorizontalWritingMode(); 550} 551 552- (BOOL)_isFlippedDocument 553{ 554 Frame* coreFrame = [self _web_frame]; 555 if (!coreFrame) 556 return NO; 557 Document* document = coreFrame->document(); 558 if (!document) 559 return NO; 560 RenderObject* renderView = document->renderer(); 561 if (!renderView) 562 return NO; 563 return renderView->style()->isFlippedBlocksWritingMode(); 564} 565 566- (BOOL)_scrollToBeginningOfDocument 567{ 568 if ([self _scrollOverflowInDirection:ScrollUp granularity:ScrollByDocument]) 569 return YES; 570 if (![self _isScrollable]) 571 return NO; 572 NSPoint point = [[[self _scrollView] documentView] frame].origin; 573 point.x += [[self _scrollView] scrollOrigin].x; 574 point.y += [[self _scrollView] scrollOrigin].y; 575 return [[self _contentView] _scrollTo:&point animate:YES]; 576} 577 578- (BOOL)_scrollToEndOfDocument 579{ 580 if ([self _scrollOverflowInDirection:ScrollDown granularity:ScrollByDocument]) 581 return YES; 582 if (![self _isScrollable]) 583 return NO; 584 NSRect frame = [[[self _scrollView] documentView] frame]; 585 586 bool isVertical = [self _isVerticalDocument]; 587 bool isFlipped = [self _isFlippedDocument]; 588 589 NSPoint point; 590 if (isVertical) { 591 if (!isFlipped) 592 point = NSMakePoint(frame.origin.x, NSMaxY(frame)); 593 else 594 point = NSMakePoint(frame.origin.x, NSMinY(frame)); 595 } else { 596 if (!isFlipped) 597 point = NSMakePoint(NSMaxX(frame), frame.origin.y); 598 else 599 point = NSMakePoint(NSMinX(frame), frame.origin.y); 600 } 601 602 // Reset the position opposite to the block progression direction. 603 if (isVertical) 604 point.x += [[self _scrollView] scrollOrigin].x; 605 else 606 point.y += [[self _scrollView] scrollOrigin].y; 607 return [[self _contentView] _scrollTo:&point animate:YES]; 608} 609 610- (void)scrollToBeginningOfDocument:(id)sender 611{ 612 if ([self _scrollToBeginningOfDocument]) 613 return; 614 615 if (WebFrameView *child = [self _largestScrollableChild]) { 616 if ([child _scrollToBeginningOfDocument]) 617 return; 618 } 619 [[self nextResponder] tryToPerform:@selector(scrollToBeginningOfDocument:) with:sender]; 620} 621 622- (void)scrollToEndOfDocument:(id)sender 623{ 624 if ([self _scrollToEndOfDocument]) 625 return; 626 627 if (WebFrameView *child = [self _largestScrollableChild]) { 628 if ([child _scrollToEndOfDocument]) 629 return; 630 } 631 [[self nextResponder] tryToPerform:@selector(scrollToEndOfDocument:) with:sender]; 632} 633 634- (void)_goBack 635{ 636 [[self _webView] goBack]; 637} 638 639- (void)_goForward 640{ 641 [[self _webView] goForward]; 642} 643 644- (BOOL)_scrollVerticallyBy:(float)delta 645{ 646 // This method uses the secret method _scrollTo on NSClipView. 647 // It does that because it needs to know definitively whether scrolling was 648 // done or not to help implement the "scroll parent if we are at the limit" feature. 649 // In the presence of smooth scrolling, there's no easy way to tell if the method 650 // did any scrolling or not with the public API. 651 NSPoint point = [[self _contentView] bounds].origin; 652 point.y += delta; 653 return [[self _contentView] _scrollTo:&point animate:YES]; 654} 655 656- (BOOL)_scrollHorizontallyBy:(float)delta 657{ 658 NSPoint point = [[self _contentView] bounds].origin; 659 point.x += delta; 660 return [[self _contentView] _scrollTo:&point animate:YES]; 661} 662 663- (float)_horizontalKeyboardScrollDistance 664{ 665 // Arrow keys scroll the same distance that clicking the scroll arrow does. 666 return [[self _scrollView] horizontalLineScroll]; 667} 668 669- (float)_horizontalPageScrollDistance 670{ 671 float width = [[self _contentView] bounds].size.width; 672 return max<float>(width * Scrollbar::minFractionToStepWhenPaging(), width - Scrollbar::maxOverlapBetweenPages()); 673} 674 675- (BOOL)_pageVertically:(BOOL)up 676{ 677 if ([self _scrollOverflowInDirection:up ? ScrollUp : ScrollDown granularity:ScrollByPage]) 678 return YES; 679 680 if (![self _isScrollable]) 681 return [[self _largestScrollableChild] _pageVertically:up]; 682 683 float delta = [self _verticalPageScrollDistance]; 684 return [self _scrollVerticallyBy:up ? -delta : delta]; 685} 686 687- (BOOL)_pageHorizontally:(BOOL)left 688{ 689 if ([self _scrollOverflowInDirection:left ? ScrollLeft : ScrollRight granularity:ScrollByPage]) 690 return YES; 691 692 if (![self _isScrollable]) 693 return [[self _largestScrollableChild] _pageHorizontally:left]; 694 695 float delta = [self _horizontalPageScrollDistance]; 696 return [self _scrollHorizontallyBy:left ? -delta : delta]; 697} 698 699- (BOOL)_pageInBlockProgressionDirection:(BOOL)forward 700{ 701 // Determine whether we're calling _pageVertically or _pageHorizontally. 702 BOOL isVerticalDocument = [self _isVerticalDocument]; 703 BOOL isFlippedBlock = [self _isFlippedDocument]; 704 if (isVerticalDocument) 705 return [self _pageVertically:isFlippedBlock ? !forward : forward]; 706 return [self _pageHorizontally:isFlippedBlock ? !forward : forward]; 707} 708 709- (BOOL)_scrollLineVertically:(BOOL)up 710{ 711 if ([self _scrollOverflowInDirection:up ? ScrollUp : ScrollDown granularity:ScrollByLine]) 712 return YES; 713 714 if (![self _isScrollable]) 715 return [[self _largestScrollableChild] _scrollLineVertically:up]; 716 717 float delta = [self _verticalKeyboardScrollDistance]; 718 return [self _scrollVerticallyBy:up ? -delta : delta]; 719} 720 721- (BOOL)_scrollLineHorizontally:(BOOL)left 722{ 723 if ([self _scrollOverflowInDirection:left ? ScrollLeft : ScrollRight granularity:ScrollByLine]) 724 return YES; 725 726 if (![self _isScrollable]) 727 return [[self _largestScrollableChild] _scrollLineHorizontally:left]; 728 729 float delta = [self _horizontalKeyboardScrollDistance]; 730 return [self _scrollHorizontallyBy:left ? -delta : delta]; 731} 732 733- (void)scrollPageUp:(id)sender 734{ 735 if (![self _pageInBlockProgressionDirection:YES]) { 736 // If we were already at the top, tell the next responder to scroll if it can. 737 [[self nextResponder] tryToPerform:@selector(scrollPageUp:) with:sender]; 738 } 739} 740 741- (void)scrollPageDown:(id)sender 742{ 743 if (![self _pageInBlockProgressionDirection:NO]) { 744 // If we were already at the bottom, tell the next responder to scroll if it can. 745 [[self nextResponder] tryToPerform:@selector(scrollPageDown:) with:sender]; 746 } 747} 748 749- (void)scrollLineUp:(id)sender 750{ 751 if (![self _scrollLineVertically:YES]) 752 [[self nextResponder] tryToPerform:@selector(scrollLineUp:) with:sender]; 753} 754 755- (void)scrollLineDown:(id)sender 756{ 757 if (![self _scrollLineVertically:NO]) 758 [[self nextResponder] tryToPerform:@selector(scrollLineDown:) with:sender]; 759} 760 761- (BOOL)_firstResponderIsFormControl 762{ 763 NSResponder *firstResponder = [[self window] firstResponder]; 764 765 // WebHTMLView is an NSControl subclass these days, but it's not a form control 766 if ([firstResponder isKindOfClass:[WebHTMLView class]]) { 767 return NO; 768 } 769 return [firstResponder isKindOfClass:[NSControl class]]; 770} 771 772- (void)keyDown:(NSEvent *)event 773{ 774 // Implement common browser behaviors for all kinds of content. 775 776 // FIXME: This is not a good time to execute commands for WebHTMLView. We should run these at the time commands sent by key bindings 777 // are executed for consistency. 778 // This doesn't work automatically because most of the keys handled here are translated into moveXXX commands, which are not handled 779 // by Editor when focus is not in editable content. 780 781 NSString *characters = [event characters]; 782 int index, count; 783 BOOL callSuper = YES; 784 Frame* coreFrame = [self _web_frame]; 785 BOOL maintainsBackForwardList = coreFrame && static_cast<BackForwardListImpl*>(coreFrame->page()->backForwardList())->enabled() ? YES : NO; 786 787 count = [characters length]; 788 for (index = 0; index < count; ++index) { 789 switch ([characters characterAtIndex:index]) { 790 case NSDeleteCharacter: 791 if (!maintainsBackForwardList) { 792 callSuper = YES; 793 break; 794 } 795 // This odd behavior matches some existing browsers, 796 // including Windows IE 797 if ([event modifierFlags] & NSShiftKeyMask) { 798 [self _goForward]; 799 } else { 800 [self _goBack]; 801 } 802 callSuper = NO; 803 break; 804 case SpaceKey: 805 // Checking for a control will allow events to percolate 806 // correctly when the focus is on a form control and we 807 // are in full keyboard access mode. 808 if ((![self allowsScrolling] && ![self _largestScrollableChild]) || [self _firstResponderIsFormControl]) { 809 callSuper = YES; 810 break; 811 } 812 if ([event modifierFlags] & NSShiftKeyMask) { 813 [self scrollPageUp:nil]; 814 } else { 815 [self scrollPageDown:nil]; 816 } 817 callSuper = NO; 818 break; 819 case NSPageUpFunctionKey: 820 if (![self allowsScrolling] && ![self _largestScrollableChild]) { 821 callSuper = YES; 822 break; 823 } 824 [self scrollPageUp:nil]; 825 callSuper = NO; 826 break; 827 case NSPageDownFunctionKey: 828 if (![self allowsScrolling] && ![self _largestScrollableChild]) { 829 callSuper = YES; 830 break; 831 } 832 [self scrollPageDown:nil]; 833 callSuper = NO; 834 break; 835 case NSHomeFunctionKey: 836 if (![self allowsScrolling] && ![self _largestScrollableChild]) { 837 callSuper = YES; 838 break; 839 } 840 [self scrollToBeginningOfDocument:nil]; 841 callSuper = NO; 842 break; 843 case NSEndFunctionKey: 844 if (![self allowsScrolling] && ![self _largestScrollableChild]) { 845 callSuper = YES; 846 break; 847 } 848 [self scrollToEndOfDocument:nil]; 849 callSuper = NO; 850 break; 851 case NSUpArrowFunctionKey: 852 // We don't handle shifted or control-arrow keys here, so let super have a chance. 853 if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) { 854 callSuper = YES; 855 break; 856 } 857 if ((![self allowsScrolling] && ![self _largestScrollableChild]) || 858 [[[self window] firstResponder] isKindOfClass:[NSPopUpButton class]]) { 859 // Let arrow keys go through to pop up buttons 860 // <rdar://problem/3455910>: hitting up or down arrows when focus is on a 861 // pop-up menu should pop the menu 862 callSuper = YES; 863 break; 864 } 865 if ([event modifierFlags] & NSCommandKeyMask) { 866 [self scrollToBeginningOfDocument:nil]; 867 } else if ([event modifierFlags] & NSAlternateKeyMask) { 868 [self scrollPageUp:nil]; 869 } else { 870 [self scrollLineUp:nil]; 871 } 872 callSuper = NO; 873 break; 874 case NSDownArrowFunctionKey: 875 // We don't handle shifted or control-arrow keys here, so let super have a chance. 876 if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) { 877 callSuper = YES; 878 break; 879 } 880 if ((![self allowsScrolling] && ![self _largestScrollableChild]) || 881 [[[self window] firstResponder] isKindOfClass:[NSPopUpButton class]]) { 882 // Let arrow keys go through to pop up buttons 883 // <rdar://problem/3455910>: hitting up or down arrows when focus is on a 884 // pop-up menu should pop the menu 885 callSuper = YES; 886 break; 887 } 888 if ([event modifierFlags] & NSCommandKeyMask) { 889 [self scrollToEndOfDocument:nil]; 890 } else if ([event modifierFlags] & NSAlternateKeyMask) { 891 [self scrollPageDown:nil]; 892 } else { 893 [self scrollLineDown:nil]; 894 } 895 callSuper = NO; 896 break; 897 case NSLeftArrowFunctionKey: 898 // We don't handle shifted or control-arrow keys here, so let super have a chance. 899 if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) { 900 callSuper = YES; 901 break; 902 } 903 // Check back/forward related keys. 904 if ([event modifierFlags] & NSCommandKeyMask) { 905 if (!maintainsBackForwardList) { 906 callSuper = YES; 907 break; 908 } 909 [self _goBack]; 910 } else { 911 // Now check scrolling related keys. 912 if ((![self allowsScrolling] && ![self _largestScrollableChild])) { 913 callSuper = YES; 914 break; 915 } 916 917 if ([event modifierFlags] & NSAlternateKeyMask) { 918 [self _pageHorizontally:YES]; 919 } else { 920 [self _scrollLineHorizontally:YES]; 921 } 922 } 923 callSuper = NO; 924 break; 925 case NSRightArrowFunctionKey: 926 // We don't handle shifted or control-arrow keys here, so let super have a chance. 927 if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) { 928 callSuper = YES; 929 break; 930 } 931 // Check back/forward related keys. 932 if ([event modifierFlags] & NSCommandKeyMask) { 933 if (!maintainsBackForwardList) { 934 callSuper = YES; 935 break; 936 } 937 [self _goForward]; 938 } else { 939 // Now check scrolling related keys. 940 if ((![self allowsScrolling] && ![self _largestScrollableChild])) { 941 callSuper = YES; 942 break; 943 } 944 945 if ([event modifierFlags] & NSAlternateKeyMask) { 946 [self _pageHorizontally:NO]; 947 } else { 948 [self _scrollLineHorizontally:NO]; 949 } 950 } 951 callSuper = NO; 952 break; 953 } 954 } 955 956 if (callSuper) { 957 [super keyDown:event]; 958 } else { 959 // if we did something useful, get the cursor out of the way 960 [NSCursor setHiddenUntilMouseMoves:YES]; 961 } 962} 963 964- (NSView *)_webcore_effectiveFirstResponder 965{ 966 NSView *view = [self documentView]; 967 return view ? [view _webcore_effectiveFirstResponder] : [super _webcore_effectiveFirstResponder]; 968} 969 970- (BOOL)canPrintHeadersAndFooters 971{ 972 NSView *documentView = [[self _scrollView] documentView]; 973 if ([documentView respondsToSelector:@selector(canPrintHeadersAndFooters)]) { 974 return [(id)documentView canPrintHeadersAndFooters]; 975 } 976 return NO; 977} 978 979- (NSPrintOperation *)printOperationWithPrintInfo:(NSPrintInfo *)printInfo 980{ 981 NSView *documentView = [[self _scrollView] documentView]; 982 if (!documentView) { 983 return nil; 984 } 985 if ([documentView respondsToSelector:@selector(printOperationWithPrintInfo:)]) { 986 return [(id)documentView printOperationWithPrintInfo:printInfo]; 987 } 988 return [NSPrintOperation printOperationWithView:documentView printInfo:printInfo]; 989} 990 991- (BOOL)documentViewShouldHandlePrint 992{ 993 NSView *documentView = [[self _scrollView] documentView]; 994 if (documentView && [documentView respondsToSelector:@selector(documentViewShouldHandlePrint)]) 995 return [(id)documentView documentViewShouldHandlePrint]; 996 997 return NO; 998} 999 1000- (void)printDocumentView 1001{ 1002 NSView *documentView = [[self _scrollView] documentView]; 1003 if (documentView && [documentView respondsToSelector:@selector(printDocumentView)]) 1004 [(id)documentView printDocumentView]; 1005} 1006 1007@end 1008 1009@implementation WebFrameView (WebPrivate) 1010 1011- (float)_area 1012{ 1013 NSRect frame = [self frame]; 1014 return frame.size.height * frame.size.width; 1015} 1016 1017- (BOOL)_isScrollable 1018{ 1019 WebDynamicScrollBarsView *scrollView = [self _scrollView]; 1020 return [scrollView horizontalScrollingAllowed] || [scrollView verticalScrollingAllowed]; 1021} 1022 1023- (WebFrameView *)_largestScrollableChild 1024{ 1025 WebFrameView *largest = nil; 1026 NSArray *frameChildren = [[self webFrame] childFrames]; 1027 1028 unsigned i; 1029 for (i=0; i < [frameChildren count]; i++) { 1030 WebFrameView *childFrameView = [[frameChildren objectAtIndex:i] frameView]; 1031 WebFrameView *scrollableFrameView = [childFrameView _isScrollable] ? childFrameView : [childFrameView _largestScrollableChild]; 1032 if (!scrollableFrameView) 1033 continue; 1034 1035 // Some ads lurk in child frames of zero width and height, see radar 4406994. These don't count as scrollable. 1036 // Maybe someday we'll discover that this minimum area check should be larger, but this covers the known cases. 1037 float area = [scrollableFrameView _area]; 1038 if (area < 1.0) 1039 continue; 1040 1041 if (!largest || (area > [largest _area])) { 1042 largest = scrollableFrameView; 1043 } 1044 } 1045 1046 return largest; 1047} 1048 1049- (BOOL)_hasScrollBars 1050{ 1051 // FIXME: This method was used by Safari 4.0.x and older versions, but has not been used by any other WebKit 1052 // clients to my knowledge, and will not be used by future versions of Safari. It can probably be removed 1053 // once we no longer need to keep nightly WebKit builds working with Safari 4.0.x and earlier. 1054 NSScrollView *scrollView = [self _scrollView]; 1055 return [scrollView hasHorizontalScroller] || [scrollView hasVerticalScroller]; 1056} 1057 1058- (WebFrameView *)_largestChildWithScrollBars 1059{ 1060 // FIXME: This method was used by Safari 4.0.x and older versions, but has not been used by any other WebKit 1061 // clients to my knowledge, and will not be used by future versions of Safari. It can probably be removed 1062 // once we no longer need to keep nightly WebKit builds working with Safari 4.0.x and earlier. 1063 WebFrameView *largest = nil; 1064 NSArray *frameChildren = [[self webFrame] childFrames]; 1065 1066 unsigned i; 1067 for (i=0; i < [frameChildren count]; i++) { 1068 WebFrameView *childFrameView = [[frameChildren objectAtIndex:i] frameView]; 1069 WebFrameView *scrollableFrameView = [childFrameView _hasScrollBars] ? childFrameView : [childFrameView _largestChildWithScrollBars]; 1070 if (!scrollableFrameView) 1071 continue; 1072 1073 // Some ads lurk in child frames of zero width and height, see radar 4406994. These don't count as scrollable. 1074 // Maybe someday we'll discover that this minimum area check should be larger, but this covers the known cases. 1075 float area = [scrollableFrameView _area]; 1076 if (area < 1.0) 1077 continue; 1078 1079 if (!largest || (area > [largest _area])) { 1080 largest = scrollableFrameView; 1081 } 1082 } 1083 1084 return largest; 1085} 1086 1087- (NSClipView *)_contentView 1088{ 1089 return [[self _scrollView] contentView]; 1090} 1091 1092- (Class)_customScrollViewClass 1093{ 1094 if ([_private->frameScrollView class] == [WebDynamicScrollBarsView class]) 1095 return nil; 1096 return [_private->frameScrollView class]; 1097} 1098 1099- (void)_setCustomScrollViewClass:(Class)customClass 1100{ 1101 if (!customClass) 1102 customClass = [WebDynamicScrollBarsView class]; 1103 ASSERT([customClass isSubclassOfClass:[WebDynamicScrollBarsView class]]); 1104 if (customClass == [_private->frameScrollView class]) 1105 return; 1106 if (![customClass isSubclassOfClass:[WebDynamicScrollBarsView class]]) 1107 return; 1108 1109 WebDynamicScrollBarsView *oldScrollView = _private->frameScrollView; // already retained 1110 NSView <WebDocumentView> *documentView = [[self documentView] retain]; 1111 1112 WebDynamicScrollBarsView *scrollView = [[customClass alloc] initWithFrame:[oldScrollView frame]]; 1113 [scrollView setContentView:[[[WebClipView alloc] initWithFrame:[scrollView bounds]] autorelease]]; 1114 [scrollView setDrawsBackground:[oldScrollView drawsBackground]]; 1115 [scrollView setHasVerticalScroller:[oldScrollView hasVerticalScroller]]; 1116 [scrollView setHasHorizontalScroller:[oldScrollView hasHorizontalScroller]]; 1117 [scrollView setAutoresizingMask:[oldScrollView autoresizingMask]]; 1118 [scrollView setLineScroll:[oldScrollView lineScroll]]; 1119 [self addSubview:scrollView]; 1120 1121 // don't call our overridden version here; we need to make the standard NSView link between us 1122 // and our subview so that previousKeyView and previousValidKeyView work as expected. This works 1123 // together with our becomeFirstResponder and setNextKeyView overrides. 1124 [super setNextKeyView:scrollView]; 1125 1126 _private->frameScrollView = scrollView; 1127 1128 [self _setDocumentView:documentView]; 1129 [self _install]; 1130 1131 [oldScrollView removeFromSuperview]; 1132 [oldScrollView release]; 1133 [documentView release]; 1134} 1135 1136@end 1137