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