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