Home | History | Annotate | Download | only in WebView
      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