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 72 using 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 83 enum { 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 207 static 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