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/BackForwardList.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} 96@end 97 98@implementation WebFrameViewPrivate 99 100- (void)dealloc 101{ 102 [frameScrollView release]; 103 [super dealloc]; 104} 105 106@end 107 108@implementation WebFrameView (WebFrameViewFileInternal) 109 110- (float)_verticalKeyboardScrollDistance 111{ 112 // Arrow keys scroll the same distance that clicking the scroll arrow does. 113 return [[self _scrollView] verticalLineScroll]; 114} 115 116- (Frame*)_web_frame 117{ 118 return core(_private->webFrame); 119} 120 121@end 122 123@implementation WebFrameView (WebInternal) 124 125// Note that the WebVew is not retained. 126- (WebView *)_webView 127{ 128 return [_private->webFrame webView]; 129} 130 131- (void)_setDocumentView:(NSView <WebDocumentView> *)view 132{ 133 WebDynamicScrollBarsView *sv = [self _scrollView]; 134 core([self _webView])->dragController()->setDidInitiateDrag(false); 135 136 [sv setSuppressLayout:YES]; 137 138 // If the old view is the first responder, transfer first responder status to the new view as 139 // a convenience and so that we don't leave the window pointing to a view that's no longer in it. 140 NSWindow *window = [sv window]; 141 NSResponder *firstResponder = [window firstResponder]; 142 bool makeNewViewFirstResponder = [firstResponder isKindOfClass:[NSView class]] && [(NSView *)firstResponder isDescendantOf:[sv documentView]]; 143 144 // Suppress the resetting of drag margins since we know we can't affect them. 145 BOOL resetDragMargins = [window _needsToResetDragMargins]; 146 [window _setNeedsToResetDragMargins:NO]; 147 [sv setDocumentView:view]; 148 [window _setNeedsToResetDragMargins:resetDragMargins]; 149 150 if (makeNewViewFirstResponder) 151 [window makeFirstResponder:view]; 152 [sv setSuppressLayout:NO]; 153} 154 155-(NSView <WebDocumentView> *)_makeDocumentViewForDataSource:(WebDataSource *)dataSource 156{ 157 NSString* MIMEType = [dataSource _responseMIMEType]; 158 if (!MIMEType) 159 MIMEType = @"text/html"; 160 Class viewClass = [self _viewClassForMIMEType:MIMEType]; 161 NSView <WebDocumentView> *documentView; 162 if (viewClass) { 163 // If the dataSource's representation has already been created, and it is also the 164 // same class as the desired documentView, then use it as the documentView instead 165 // of creating another one (Radar 4340787). 166 id <WebDocumentRepresentation> dataSourceRepresentation = [dataSource representation]; 167 if (dataSourceRepresentation && [dataSourceRepresentation class] == viewClass) 168 documentView = (NSView <WebDocumentView> *)[dataSourceRepresentation retain]; 169 else 170 documentView = [[viewClass alloc] initWithFrame:[self bounds]]; 171 } else 172 documentView = nil; 173 174 [self _setDocumentView:documentView]; 175 [documentView release]; 176 177 return documentView; 178} 179 180- (void)_setWebFrame:(WebFrame *)webFrame 181{ 182 if (!webFrame) { 183 NSView *docV = [self documentView]; 184 if ([docV respondsToSelector:@selector(close)]) 185 [docV performSelector:@selector(close)]; 186 } 187 188 // Not retained because the WebView owns the WebFrame, which owns the WebFrameView. 189 _private->webFrame = webFrame; 190} 191 192- (WebDynamicScrollBarsView *)_scrollView 193{ 194 // This can be called by [super dealloc] when cleaning up the key view loop, 195 // after _private has been nilled out. 196 if (_private == nil) 197 return nil; 198 return _private->frameScrollView; 199} 200 201- (float)_verticalPageScrollDistance 202{ 203 float height = [[self _contentView] bounds].size.height; 204 return max<float>(height * Scrollbar::minFractionToStepWhenPaging(), height - Scrollbar::maxOverlapBetweenPages()); 205} 206 207static inline void addTypesFromClass(NSMutableDictionary *allTypes, Class objCClass, NSArray *supportTypes) 208{ 209 NSEnumerator *enumerator = [supportTypes objectEnumerator]; 210 ASSERT(enumerator != nil); 211 NSString *mime = nil; 212 while ((mime = [enumerator nextObject]) != nil) { 213 // Don't clobber previously-registered classes. 214 if ([allTypes objectForKey:mime] == nil) 215 [allTypes setObject:objCClass forKey:mime]; 216 } 217} 218 219+ (NSMutableDictionary *)_viewTypesAllowImageTypeOmission:(BOOL)allowImageTypeOmission 220{ 221 static NSMutableDictionary *viewTypes = nil; 222 static BOOL addedImageTypes = NO; 223 224 if (!viewTypes) { 225 viewTypes = [[NSMutableDictionary alloc] init]; 226 addTypesFromClass(viewTypes, [WebHTMLView class], [WebHTMLView supportedNonImageMIMETypes]); 227 228 // Since this is a "secret default" we don't bother registering it. 229 BOOL omitPDFSupport = [[NSUserDefaults standardUserDefaults] boolForKey:@"WebKitOmitPDFSupport"]; 230 if (!omitPDFSupport) 231 addTypesFromClass(viewTypes, [WebPDFView class], [WebPDFView supportedMIMETypes]); 232 } 233 234 if (!addedImageTypes && !allowImageTypeOmission) { 235 addTypesFromClass(viewTypes, [WebHTMLView class], [WebHTMLView supportedImageMIMETypes]); 236 addedImageTypes = YES; 237 } 238 239 return viewTypes; 240} 241 242+ (BOOL)_canShowMIMETypeAsHTML:(NSString *)MIMEType 243{ 244 return [[[self _viewTypesAllowImageTypeOmission:YES] _webkit_objectForMIMEType:MIMEType] isSubclassOfClass:[WebHTMLView class]]; 245} 246 247+ (Class)_viewClassForMIMEType:(NSString *)MIMEType allowingPlugins:(BOOL)allowPlugins 248{ 249 Class viewClass; 250 return [WebView _viewClass:&viewClass andRepresentationClass:nil forMIMEType:MIMEType allowingPlugins:allowPlugins] ? viewClass : nil; 251} 252 253- (Class)_viewClassForMIMEType:(NSString *)MIMEType 254{ 255 return [[self class] _viewClassForMIMEType:MIMEType allowingPlugins:[[[self _webView] preferences] arePlugInsEnabled]]; 256} 257 258- (void)_install 259{ 260 ASSERT(_private->webFrame); 261 ASSERT(_private->frameScrollView); 262 263 Frame* frame = core(_private->webFrame); 264 265 ASSERT(frame); 266 ASSERT(frame->page()); 267 268 // If this isn't the main frame, it must have an owner element set, or it 269 // won't ever get installed in the view hierarchy. 270 ASSERT(frame == frame->page()->mainFrame() || frame->ownerElement()); 271 272 FrameView* view = frame->view(); 273 274 view->setPlatformWidget(_private->frameScrollView); 275 276 // FIXME: Frame tries to do this too. Is this code needed? 277 if (RenderPart* owner = frame->ownerRenderer()) { 278 owner->setWidget(view); 279 // Now the render part owns the view, so we don't any more. 280 } 281 282 view->updateCanHaveScrollbars(); 283} 284 285@end 286 287@implementation WebFrameView 288 289- initWithCoder:(NSCoder *)decoder 290{ 291 // Older nibs containing WebViews will also contain WebFrameViews. We need to keep track of 292 // their count also to match the decrement in -dealloc. 293 ++WebFrameViewCount; 294 return [super initWithCoder:decoder]; 295} 296 297- initWithFrame:(NSRect)frame 298{ 299 self = [super initWithFrame:frame]; 300 if (!self) 301 return nil; 302 303 static bool didFirstTimeInitialization; 304 if (!didFirstTimeInitialization) { 305 didFirstTimeInitialization = true; 306 InitWebCoreSystemInterface(); 307 308 // Need to tell WebCore what function to call for the "History Item has Changed" notification. 309 // Note: We also do this in WebHistoryItem's init method. 310 WebCore::notifyHistoryItemChanged = WKNotifyHistoryItemChanged; 311 312 [WebViewFactory createSharedFactory]; 313 [WebKeyGenerator createSharedGenerator]; 314 315// FIXME: Remove the NSAppKitVersionNumberWithDeferredWindowDisplaySupport check once 316// once AppKit's Deferred Window Display support is available. 317#if defined(BUILDING_ON_TIGER) || defined(BUILDING_ON_LEOPARD) || !defined(NSAppKitVersionNumberWithDeferredWindowDisplaySupport) 318 // CoreGraphics deferred updates are disabled if WebKitEnableCoalescedUpdatesPreferenceKey is NO 319 // or has no value. For compatibility with Mac OS X 10.5 and lower, deferred updates are off by default. 320 if (![[NSUserDefaults standardUserDefaults] boolForKey:WebKitEnableDeferredUpdatesPreferenceKey]) 321 WKDisableCGDeferredUpdates(); 322#endif 323 if (!WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITH_MAIN_THREAD_EXCEPTIONS)) 324 setDefaultThreadViolationBehavior(LogOnFirstThreadViolation, ThreadViolationRoundOne); 325 326 bool throwExceptionsForRoundTwo = WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITH_ROUND_TWO_MAIN_THREAD_EXCEPTIONS); 327#ifdef MAIL_THREAD_WORKAROUND 328 // Even if old Mail is linked with new WebKit, don't throw exceptions. 329 if ([WebResource _needMailThreadWorkaroundIfCalledOffMainThread]) 330 throwExceptionsForRoundTwo = false; 331#endif 332 if (!throwExceptionsForRoundTwo) 333 setDefaultThreadViolationBehavior(LogOnFirstThreadViolation, ThreadViolationRoundTwo); 334 } 335 336 _private = [[WebFrameViewPrivate alloc] init]; 337 338 WebDynamicScrollBarsView *scrollView = [[WebDynamicScrollBarsView alloc] initWithFrame:NSMakeRect(0.0f, 0.0f, frame.size.width, frame.size.height)]; 339 _private->frameScrollView = scrollView; 340 [scrollView setContentView:[[[WebClipView alloc] initWithFrame:[scrollView bounds]] autorelease]]; 341 [scrollView setDrawsBackground:NO]; 342 [scrollView setHasVerticalScroller:NO]; 343 [scrollView setHasHorizontalScroller:NO]; 344 [scrollView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; 345 [scrollView setLineScroll:Scrollbar::pixelsPerLineStep()]; 346 [self addSubview:scrollView]; 347 348 // Don't call our overridden version of setNextKeyView here; we need to make the standard NSView 349 // link between us and our subview so that previousKeyView and previousValidKeyView work as expected. 350 // This works together with our becomeFirstResponder and setNextKeyView overrides. 351 [super setNextKeyView:scrollView]; 352 353 ++WebFrameViewCount; 354 355 return self; 356} 357 358- (void)dealloc 359{ 360 --WebFrameViewCount; 361 362 [_private release]; 363 _private = nil; 364 365 [super dealloc]; 366} 367 368- (void)finalize 369{ 370 --WebFrameViewCount; 371 372 [super finalize]; 373} 374 375- (WebFrame *)webFrame 376{ 377 return _private->webFrame; 378} 379 380- (void)setAllowsScrolling:(BOOL)flag 381{ 382 WebCore::Frame *frame = core([self webFrame]); 383 if (WebCore::FrameView *view = frame? frame->view() : 0) 384 view->setCanHaveScrollbars(flag); 385} 386 387- (BOOL)allowsScrolling 388{ 389 WebCore::Frame *frame = core([self webFrame]); 390 if (WebCore::FrameView *view = frame? frame->view() : 0) 391 return view->canHaveScrollbars(); 392 return YES; 393} 394 395- (NSView <WebDocumentView> *)documentView 396{ 397 return [[self _scrollView] documentView]; 398} 399 400- (BOOL)acceptsFirstResponder 401{ 402 // We always accept first responder; this matches OS X 10.2 WebKit 403 // behavior (see 3469791). 404 return YES; 405} 406 407- (BOOL)becomeFirstResponder 408{ 409 // This works together with setNextKeyView to splice the WebFrameView into 410 // the key loop similar to the way NSScrollView does this. Note that 411 // WebView has similar code. 412 413 NSWindow *window = [self window]; 414 if ([window keyViewSelectionDirection] == NSSelectingPrevious) { 415 NSView *previousValidKeyView = [self previousValidKeyView]; 416 // If we couldn't find a previous valid key view, ask the WebView. This handles frameset 417 // cases (one is mentioned in Radar bug 3748628). Note that previousValidKeyView should 418 // never be self but can be due to AppKit oddness (mentioned in Radar bug 3748628). 419 if (previousValidKeyView == nil || previousValidKeyView == self) 420 previousValidKeyView = [[[self webFrame] webView] previousValidKeyView]; 421 [window makeFirstResponder:previousValidKeyView]; 422 } else { 423 // If the scroll view won't accept first-responderness now, then just become 424 // the first responder ourself like a normal view. This lets us be the first 425 // responder in cases where no page has yet been loaded. 426 if ([[self _scrollView] acceptsFirstResponder]) 427 [window makeFirstResponder:[self _scrollView]]; 428 } 429 430 return YES; 431} 432 433- (void)setNextKeyView:(NSView *)aView 434{ 435 // This works together with becomeFirstResponder to splice the WebFrameView into 436 // the key loop similar to the way NSScrollView does this. Note that 437 // WebView has very similar code. 438 if ([self _scrollView] != nil) { 439 [[self _scrollView] setNextKeyView:aView]; 440 } else { 441 [super setNextKeyView:aView]; 442 } 443} 444 445- (BOOL)isOpaque 446{ 447 return [[self _webView] drawsBackground]; 448} 449 450- (void)drawRect:(NSRect)rect 451{ 452 if ([self documentView] == nil) { 453 // Need to paint ourselves if there's no documentView to do it instead. 454 if ([[self _webView] drawsBackground]) { 455 [[[self _webView] backgroundColor] set]; 456 NSRectFill(rect); 457 } 458 } else { 459#ifndef NDEBUG 460 if ([[self _scrollView] drawsBackground]) { 461 [[NSColor cyanColor] set]; 462 NSRectFill(rect); 463 } 464#endif 465 } 466} 467 468- (NSRect)visibleRect 469{ 470 // This method can be called beneath -[NSView dealloc] after we have cleared _private. 471 if (!_private) 472 return [super visibleRect]; 473 474 // FIXME: <rdar://problem/6213380> This method does not work correctly with transforms, for two reasons: 475 // 1) [super visibleRect] does not account for the transform, since it is not represented 476 // in the NSView hierarchy. 477 // 2) -_getVisibleRect: does not correct for transforms. 478 479 NSRect rendererVisibleRect; 480 if (![[self webFrame] _getVisibleRect:&rendererVisibleRect]) 481 return [super visibleRect]; 482 483 if (NSIsEmptyRect(rendererVisibleRect)) 484 return NSZeroRect; 485 486 NSRect viewVisibleRect = [super visibleRect]; 487 if (NSIsEmptyRect(viewVisibleRect)) 488 return NSZeroRect; 489 490 NSRect frame = [self frame]; 491 // rendererVisibleRect is in the parent's coordinate space, and frame is in the superview's coordinate space. 492 // The return value from this method needs to be in this view's coordinate space. We get that right by subtracting 493 // the origins (and correcting for flipping), but when we support transforms, we will need to do better than this. 494 rendererVisibleRect.origin.x -= frame.origin.x; 495 rendererVisibleRect.origin.y = NSMaxY(frame) - NSMaxY(rendererVisibleRect); 496 return NSIntersectionRect(rendererVisibleRect, viewVisibleRect); 497} 498 499- (void)setFrameSize:(NSSize)size 500{ 501 if (!NSEqualSizes(size, [self frame].size)) { 502 // See WebFrameLoaderClient::provisionalLoadStarted. 503 if ([[[self webFrame] webView] drawsBackground]) 504 [[self _scrollView] setDrawsBackground:YES]; 505 if (Frame* coreFrame = [self _web_frame]) { 506 if (FrameView* coreFrameView = coreFrame->view()) 507 coreFrameView->setNeedsLayout(); 508 } 509 } 510 [super setFrameSize:size]; 511} 512 513- (void)viewDidMoveToWindow 514{ 515 // See WebFrameLoaderClient::provisionalLoadStarted. 516 // Need to check _private for nil because this can be called inside -[WebView initWithCoder:]. 517 if (_private && [[[self webFrame] webView] drawsBackground]) 518 [[self _scrollView] setDrawsBackground:YES]; 519 [super viewDidMoveToWindow]; 520} 521 522- (BOOL)_scrollOverflowInDirection:(ScrollDirection)direction granularity:(ScrollGranularity)granularity 523{ 524 // scrolling overflows is only applicable if we're dealing with an WebHTMLView 525 if (![[self documentView] isKindOfClass:[WebHTMLView class]]) 526 return NO; 527 Frame* frame = core([self webFrame]); 528 if (!frame) 529 return NO; 530 return frame->eventHandler()->scrollOverflow(direction, granularity); 531} 532 533- (BOOL)_scrollToBeginningOfDocument 534{ 535 if ([self _scrollOverflowInDirection:ScrollUp granularity:ScrollByDocument]) 536 return YES; 537 if (![self _hasScrollBars]) 538 return NO; 539 NSPoint point = [[[self _scrollView] documentView] frame].origin; 540 return [[self _contentView] _scrollTo:&point animate:YES]; 541} 542 543- (BOOL)_scrollToEndOfDocument 544{ 545 if ([self _scrollOverflowInDirection:ScrollDown granularity:ScrollByDocument]) 546 return YES; 547 if (![self _hasScrollBars]) 548 return NO; 549 NSRect frame = [[[self _scrollView] documentView] frame]; 550 NSPoint point = NSMakePoint(frame.origin.x, NSMaxY(frame)); 551 return [[self _contentView] _scrollTo:&point animate:YES]; 552} 553 554- (void)scrollToBeginningOfDocument:(id)sender 555{ 556 if ([self _scrollToBeginningOfDocument]) 557 return; 558 559 if (WebFrameView *child = [self _largestChildWithScrollBars]) { 560 if ([child _scrollToBeginningOfDocument]) 561 return; 562 } 563 [[self nextResponder] tryToPerform:@selector(scrollToBeginningOfDocument:) with:sender]; 564} 565 566- (void)scrollToEndOfDocument:(id)sender 567{ 568 if ([self _scrollToEndOfDocument]) 569 return; 570 571 if (WebFrameView *child = [self _largestChildWithScrollBars]) { 572 if ([child _scrollToEndOfDocument]) 573 return; 574 } 575 [[self nextResponder] tryToPerform:@selector(scrollToEndOfDocument:) with:sender]; 576} 577 578- (void)_goBack 579{ 580 [[self _webView] goBack]; 581} 582 583- (void)_goForward 584{ 585 [[self _webView] goForward]; 586} 587 588- (BOOL)_scrollVerticallyBy:(float)delta 589{ 590 // This method uses the secret method _scrollTo on NSClipView. 591 // It does that because it needs to know definitively whether scrolling was 592 // done or not to help implement the "scroll parent if we are at the limit" feature. 593 // In the presence of smooth scrolling, there's no easy way to tell if the method 594 // did any scrolling or not with the public API. 595 NSPoint point = [[self _contentView] bounds].origin; 596 point.y += delta; 597 return [[self _contentView] _scrollTo:&point animate:YES]; 598} 599 600- (BOOL)_scrollHorizontallyBy:(float)delta 601{ 602 NSPoint point = [[self _contentView] bounds].origin; 603 point.x += delta; 604 return [[self _contentView] _scrollTo:&point animate:YES]; 605} 606 607- (float)_horizontalKeyboardScrollDistance 608{ 609 // Arrow keys scroll the same distance that clicking the scroll arrow does. 610 return [[self _scrollView] horizontalLineScroll]; 611} 612 613- (float)_horizontalPageScrollDistance 614{ 615 float width = [[self _contentView] bounds].size.width; 616 return max<float>(width * Scrollbar::minFractionToStepWhenPaging(), width - Scrollbar::maxOverlapBetweenPages()); 617} 618 619- (BOOL)_pageVertically:(BOOL)up 620{ 621 if ([self _scrollOverflowInDirection:up ? ScrollUp : ScrollDown granularity:ScrollByPage]) 622 return YES; 623 624 if (![self _hasScrollBars]) 625 return [[self _largestChildWithScrollBars] _pageVertically:up]; 626 627 float delta = [self _verticalPageScrollDistance]; 628 return [self _scrollVerticallyBy:up ? -delta : delta]; 629} 630 631- (BOOL)_pageHorizontally:(BOOL)left 632{ 633 if ([self _scrollOverflowInDirection:left ? ScrollLeft : ScrollRight granularity:ScrollByPage]) 634 return YES; 635 636 if (![self _hasScrollBars]) 637 return [[self _largestChildWithScrollBars] _pageHorizontally:left]; 638 639 float delta = [self _horizontalPageScrollDistance]; 640 return [self _scrollHorizontallyBy:left ? -delta : delta]; 641} 642 643- (BOOL)_scrollLineVertically:(BOOL)up 644{ 645 if ([self _scrollOverflowInDirection:up ? ScrollUp : ScrollDown granularity:ScrollByLine]) 646 return YES; 647 648 if (![self _hasScrollBars]) 649 return [[self _largestChildWithScrollBars] _scrollLineVertically:up]; 650 651 float delta = [self _verticalKeyboardScrollDistance]; 652 return [self _scrollVerticallyBy:up ? -delta : delta]; 653} 654 655- (BOOL)_scrollLineHorizontally:(BOOL)left 656{ 657 if ([self _scrollOverflowInDirection:left ? ScrollLeft : ScrollRight granularity:ScrollByLine]) 658 return YES; 659 660 if (![self _hasScrollBars]) 661 return [[self _largestChildWithScrollBars] _scrollLineHorizontally:left]; 662 663 float delta = [self _horizontalKeyboardScrollDistance]; 664 return [self _scrollHorizontallyBy:left ? -delta : delta]; 665} 666 667- (void)scrollPageUp:(id)sender 668{ 669 if (![self _pageVertically:YES]) { 670 // If we were already at the top, tell the next responder to scroll if it can. 671 [[self nextResponder] tryToPerform:@selector(scrollPageUp:) with:sender]; 672 } 673} 674 675- (void)scrollPageDown:(id)sender 676{ 677 if (![self _pageVertically:NO]) { 678 // If we were already at the bottom, tell the next responder to scroll if it can. 679 [[self nextResponder] tryToPerform:@selector(scrollPageDown:) with:sender]; 680 } 681} 682 683- (void)scrollLineUp:(id)sender 684{ 685 if (![self _scrollLineVertically:YES]) 686 [[self nextResponder] tryToPerform:@selector(scrollLineUp:) with:sender]; 687} 688 689- (void)scrollLineDown:(id)sender 690{ 691 if (![self _scrollLineVertically:NO]) 692 [[self nextResponder] tryToPerform:@selector(scrollLineDown:) with:sender]; 693} 694 695- (BOOL)_firstResponderIsFormControl 696{ 697 NSResponder *firstResponder = [[self window] firstResponder]; 698 699 // WebHTMLView is an NSControl subclass these days, but it's not a form control 700 if ([firstResponder isKindOfClass:[WebHTMLView class]]) { 701 return NO; 702 } 703 return [firstResponder isKindOfClass:[NSControl class]]; 704} 705 706- (void)keyDown:(NSEvent *)event 707{ 708 NSString *characters = [event characters]; 709 int index, count; 710 BOOL callSuper = YES; 711 Frame* coreFrame = [self _web_frame]; 712 BOOL maintainsBackForwardList = coreFrame && coreFrame->page()->backForwardList()->enabled() ? YES : NO; 713 714 count = [characters length]; 715 for (index = 0; index < count; ++index) { 716 switch ([characters characterAtIndex:index]) { 717 case NSDeleteCharacter: 718 if (!maintainsBackForwardList) { 719 callSuper = YES; 720 break; 721 } 722 // This odd behavior matches some existing browsers, 723 // including Windows IE 724 if ([event modifierFlags] & NSShiftKeyMask) { 725 [self _goForward]; 726 } else { 727 [self _goBack]; 728 } 729 callSuper = NO; 730 break; 731 case SpaceKey: 732 // Checking for a control will allow events to percolate 733 // correctly when the focus is on a form control and we 734 // are in full keyboard access mode. 735 if ((![self allowsScrolling] && ![self _largestChildWithScrollBars]) || [self _firstResponderIsFormControl]) { 736 callSuper = YES; 737 break; 738 } 739 if ([event modifierFlags] & NSShiftKeyMask) { 740 [self scrollPageUp:nil]; 741 } else { 742 [self scrollPageDown:nil]; 743 } 744 callSuper = NO; 745 break; 746 case NSPageUpFunctionKey: 747 if (![self allowsScrolling] && ![self _largestChildWithScrollBars]) { 748 callSuper = YES; 749 break; 750 } 751 [self scrollPageUp:nil]; 752 callSuper = NO; 753 break; 754 case NSPageDownFunctionKey: 755 if (![self allowsScrolling] && ![self _largestChildWithScrollBars]) { 756 callSuper = YES; 757 break; 758 } 759 [self scrollPageDown:nil]; 760 callSuper = NO; 761 break; 762 case NSHomeFunctionKey: 763 if (![self allowsScrolling] && ![self _largestChildWithScrollBars]) { 764 callSuper = YES; 765 break; 766 } 767 [self scrollToBeginningOfDocument:nil]; 768 callSuper = NO; 769 break; 770 case NSEndFunctionKey: 771 if (![self allowsScrolling] && ![self _largestChildWithScrollBars]) { 772 callSuper = YES; 773 break; 774 } 775 [self scrollToEndOfDocument:nil]; 776 callSuper = NO; 777 break; 778 case NSUpArrowFunctionKey: 779 // We don't handle shifted or control-arrow keys here, so let super have a chance. 780 if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) { 781 callSuper = YES; 782 break; 783 } 784 if ((![self allowsScrolling] && ![self _largestChildWithScrollBars]) || 785 [[[self window] firstResponder] isKindOfClass:[NSPopUpButton class]]) { 786 // Let arrow keys go through to pop up buttons 787 // <rdar://problem/3455910>: hitting up or down arrows when focus is on a 788 // pop-up menu should pop the menu 789 callSuper = YES; 790 break; 791 } 792 if ([event modifierFlags] & NSCommandKeyMask) { 793 [self scrollToBeginningOfDocument:nil]; 794 } else if ([event modifierFlags] & NSAlternateKeyMask) { 795 [self scrollPageUp:nil]; 796 } else { 797 [self scrollLineUp:nil]; 798 } 799 callSuper = NO; 800 break; 801 case NSDownArrowFunctionKey: 802 // We don't handle shifted or control-arrow keys here, so let super have a chance. 803 if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) { 804 callSuper = YES; 805 break; 806 } 807 if ((![self allowsScrolling] && ![self _largestChildWithScrollBars]) || 808 [[[self window] firstResponder] isKindOfClass:[NSPopUpButton class]]) { 809 // Let arrow keys go through to pop up buttons 810 // <rdar://problem/3455910>: hitting up or down arrows when focus is on a 811 // pop-up menu should pop the menu 812 callSuper = YES; 813 break; 814 } 815 if ([event modifierFlags] & NSCommandKeyMask) { 816 [self scrollToEndOfDocument:nil]; 817 } else if ([event modifierFlags] & NSAlternateKeyMask) { 818 [self scrollPageDown:nil]; 819 } else { 820 [self scrollLineDown:nil]; 821 } 822 callSuper = NO; 823 break; 824 case NSLeftArrowFunctionKey: 825 // We don't handle shifted or control-arrow keys here, so let super have a chance. 826 if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) { 827 callSuper = YES; 828 break; 829 } 830 // Check back/forward related keys. 831 if ([event modifierFlags] & NSCommandKeyMask) { 832 if (!maintainsBackForwardList) { 833 callSuper = YES; 834 break; 835 } 836 [self _goBack]; 837 } else { 838 // Now check scrolling related keys. 839 if ((![self allowsScrolling] && ![self _largestChildWithScrollBars])) { 840 callSuper = YES; 841 break; 842 } 843 844 if ([event modifierFlags] & NSAlternateKeyMask) { 845 [self _pageHorizontally:YES]; 846 } else { 847 [self _scrollLineHorizontally:YES]; 848 } 849 } 850 callSuper = NO; 851 break; 852 case NSRightArrowFunctionKey: 853 // We don't handle shifted or control-arrow keys here, so let super have a chance. 854 if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) { 855 callSuper = YES; 856 break; 857 } 858 // Check back/forward related keys. 859 if ([event modifierFlags] & NSCommandKeyMask) { 860 if (!maintainsBackForwardList) { 861 callSuper = YES; 862 break; 863 } 864 [self _goForward]; 865 } else { 866 // Now check scrolling related keys. 867 if ((![self allowsScrolling] && ![self _largestChildWithScrollBars])) { 868 callSuper = YES; 869 break; 870 } 871 872 if ([event modifierFlags] & NSAlternateKeyMask) { 873 [self _pageHorizontally:NO]; 874 } else { 875 [self _scrollLineHorizontally:NO]; 876 } 877 } 878 callSuper = NO; 879 break; 880 } 881 } 882 883 if (callSuper) { 884 [super keyDown:event]; 885 } else { 886 // if we did something useful, get the cursor out of the way 887 [NSCursor setHiddenUntilMouseMoves:YES]; 888 } 889} 890 891- (NSView *)_webcore_effectiveFirstResponder 892{ 893 NSView *view = [self documentView]; 894 return view ? [view _webcore_effectiveFirstResponder] : [super _webcore_effectiveFirstResponder]; 895} 896 897- (BOOL)canPrintHeadersAndFooters 898{ 899 NSView *documentView = [[self _scrollView] documentView]; 900 if ([documentView respondsToSelector:@selector(canPrintHeadersAndFooters)]) { 901 return [(id)documentView canPrintHeadersAndFooters]; 902 } 903 return NO; 904} 905 906- (NSPrintOperation *)printOperationWithPrintInfo:(NSPrintInfo *)printInfo 907{ 908 NSView *documentView = [[self _scrollView] documentView]; 909 if (!documentView) { 910 return nil; 911 } 912 if ([documentView respondsToSelector:@selector(printOperationWithPrintInfo:)]) { 913 return [(id)documentView printOperationWithPrintInfo:printInfo]; 914 } 915 return [NSPrintOperation printOperationWithView:documentView printInfo:printInfo]; 916} 917 918- (BOOL)documentViewShouldHandlePrint 919{ 920 NSView *documentView = [[self _scrollView] documentView]; 921 if (documentView && [documentView respondsToSelector:@selector(documentViewShouldHandlePrint)]) 922 return [(id)documentView documentViewShouldHandlePrint]; 923 924 return NO; 925} 926 927- (void)printDocumentView 928{ 929 NSView *documentView = [[self _scrollView] documentView]; 930 if (documentView && [documentView respondsToSelector:@selector(printDocumentView)]) 931 [(id)documentView printDocumentView]; 932} 933 934@end 935 936@implementation WebFrameView (WebPrivate) 937 938- (float)_area 939{ 940 NSRect frame = [self frame]; 941 return frame.size.height * frame.size.width; 942} 943 944- (BOOL)_hasScrollBars 945{ 946 NSScrollView *scrollView = [self _scrollView]; 947 return [scrollView hasHorizontalScroller] || [scrollView hasVerticalScroller]; 948} 949 950- (WebFrameView *)_largestChildWithScrollBars 951{ 952 WebFrameView *largest = nil; 953 NSArray *frameChildren = [[self webFrame] childFrames]; 954 955 unsigned i; 956 for (i=0; i < [frameChildren count]; i++) { 957 WebFrameView *childFrameView = [[frameChildren objectAtIndex:i] frameView]; 958 WebFrameView *scrollableFrameView = [childFrameView _hasScrollBars] ? childFrameView : [childFrameView _largestChildWithScrollBars]; 959 if (!scrollableFrameView) 960 continue; 961 962 // Some ads lurk in child frames of zero width and height, see radar 4406994. These don't count as scrollable. 963 // Maybe someday we'll discover that this minimum area check should be larger, but this covers the known cases. 964 float area = [scrollableFrameView _area]; 965 if (area < 1.0) 966 continue; 967 968 if (!largest || (area > [largest _area])) { 969 largest = scrollableFrameView; 970 } 971 } 972 973 return largest; 974} 975 976- (NSClipView *)_contentView 977{ 978 return [[self _scrollView] contentView]; 979} 980 981- (Class)_customScrollViewClass 982{ 983 if ([_private->frameScrollView class] == [WebDynamicScrollBarsView class]) 984 return nil; 985 return [_private->frameScrollView class]; 986} 987 988- (void)_setCustomScrollViewClass:(Class)customClass 989{ 990 if (!customClass) 991 customClass = [WebDynamicScrollBarsView class]; 992 ASSERT([customClass isSubclassOfClass:[WebDynamicScrollBarsView class]]); 993 if (customClass == [_private->frameScrollView class]) 994 return; 995 if (![customClass isSubclassOfClass:[WebDynamicScrollBarsView class]]) 996 return; 997 998 WebDynamicScrollBarsView *oldScrollView = _private->frameScrollView; // already retained 999 NSView <WebDocumentView> *documentView = [[self documentView] retain]; 1000 1001 WebDynamicScrollBarsView *scrollView = [[customClass alloc] initWithFrame:[oldScrollView frame]]; 1002 [scrollView setContentView:[[[WebClipView alloc] initWithFrame:[scrollView bounds]] autorelease]]; 1003 [scrollView setDrawsBackground:[oldScrollView drawsBackground]]; 1004 [scrollView setHasVerticalScroller:[oldScrollView hasVerticalScroller]]; 1005 [scrollView setHasHorizontalScroller:[oldScrollView hasHorizontalScroller]]; 1006 [scrollView setAutoresizingMask:[oldScrollView autoresizingMask]]; 1007 [scrollView setLineScroll:[oldScrollView lineScroll]]; 1008 [self addSubview:scrollView]; 1009 1010 // don't call our overridden version here; we need to make the standard NSView link between us 1011 // and our subview so that previousKeyView and previousValidKeyView work as expected. This works 1012 // together with our becomeFirstResponder and setNextKeyView overrides. 1013 [super setNextKeyView:scrollView]; 1014 1015 _private->frameScrollView = scrollView; 1016 1017 [self _setDocumentView:documentView]; 1018 [self _install]; 1019 1020 [oldScrollView removeFromSuperview]; 1021 [oldScrollView release]; 1022 [documentView release]; 1023} 1024 1025@end 1026