--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/WebKit/mac/WebView/WebFrameView.mm Fri Sep 17 09:02:29 2010 +0300
@@ -0,0 +1,1062 @@
+/*
+ * Copyright (C) 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "WebFrameView.h"
+
+#import "WebClipView.h"
+#import "WebDataSourcePrivate.h"
+#import "WebDocument.h"
+#import "WebDynamicScrollBarsViewInternal.h"
+#import "WebFrame.h"
+#import "WebFrameInternal.h"
+#import "WebFrameViewInternal.h"
+#import "WebFrameViewPrivate.h"
+#import "WebHistoryItemInternal.h"
+#import "WebHTMLViewPrivate.h"
+#import "WebKeyGenerator.h"
+#import "WebKitErrorsPrivate.h"
+#import "WebKitStatisticsPrivate.h"
+#import "WebKitVersionChecks.h"
+#import "WebNSDictionaryExtras.h"
+#import "WebNSObjectExtras.h"
+#import "WebNSPasteboardExtras.h"
+#import "WebNSViewExtras.h"
+#import "WebNSWindowExtras.h"
+#import "WebPDFView.h"
+#import "WebPreferenceKeysPrivate.h"
+#import "WebResourceInternal.h"
+#import "WebSystemInterface.h"
+#import "WebViewFactory.h"
+#import "WebViewInternal.h"
+#import "WebViewPrivate.h"
+#import <Foundation/NSURLRequest.h>
+#import <WebCore/BackForwardList.h>
+#import <WebCore/DragController.h>
+#import <WebCore/EventHandler.h>
+#import <WebCore/Frame.h>
+#import <WebCore/FrameView.h>
+#import <WebCore/HistoryItem.h>
+#import <WebCore/Page.h>
+#import <WebCore/RenderPart.h>
+#import <WebCore/ThreadCheck.h>
+#import <WebCore/WebCoreFrameView.h>
+#import <WebCore/WebCoreView.h>
+#import <WebKitSystemInterface.h>
+#import <wtf/Assertions.h>
+
+using namespace WebCore;
+
+@interface NSWindow (WindowPrivate)
+- (BOOL)_needsToResetDragMargins;
+- (void)_setNeedsToResetDragMargins:(BOOL)s;
+@end
+
+@interface NSClipView (AppKitSecretsIKnow)
+- (BOOL)_scrollTo:(const NSPoint *)newOrigin animate:(BOOL)animate; // need the boolean result from this method
+@end
+
+enum {
+ SpaceKey = 0x0020
+};
+
+@interface WebFrameView (WebFrameViewFileInternal) <WebCoreFrameView>
+- (float)_verticalKeyboardScrollDistance;
+@end
+
+@interface WebFrameViewPrivate : NSObject {
+@public
+ WebFrame *webFrame;
+ WebDynamicScrollBarsView *frameScrollView;
+ BOOL includedInWebKitStatistics;
+}
+@end
+
+@implementation WebFrameViewPrivate
+
+- (void)dealloc
+{
+ [frameScrollView release];
+ [super dealloc];
+}
+
+@end
+
+@implementation WebFrameView (WebFrameViewFileInternal)
+
+- (float)_verticalKeyboardScrollDistance
+{
+ // Arrow keys scroll the same distance that clicking the scroll arrow does.
+ return [[self _scrollView] verticalLineScroll];
+}
+
+- (Frame*)_web_frame
+{
+ return core(_private->webFrame);
+}
+
+@end
+
+@implementation WebFrameView (WebInternal)
+
+// Note that the WebVew is not retained.
+- (WebView *)_webView
+{
+ return [_private->webFrame webView];
+}
+
+- (void)_setDocumentView:(NSView <WebDocumentView> *)view
+{
+ WebDynamicScrollBarsView *sv = [self _scrollView];
+ core([self _webView])->dragController()->setDidInitiateDrag(false);
+
+ [sv setSuppressLayout:YES];
+
+ // If the old view is the first responder, transfer first responder status to the new view as
+ // a convenience and so that we don't leave the window pointing to a view that's no longer in it.
+ NSWindow *window = [sv window];
+ NSResponder *firstResponder = [window firstResponder];
+ bool makeNewViewFirstResponder = [firstResponder isKindOfClass:[NSView class]] && [(NSView *)firstResponder isDescendantOf:[sv documentView]];
+
+ // Suppress the resetting of drag margins since we know we can't affect them.
+ BOOL resetDragMargins = [window _needsToResetDragMargins];
+ [window _setNeedsToResetDragMargins:NO];
+ [sv setDocumentView:view];
+ [window _setNeedsToResetDragMargins:resetDragMargins];
+
+ if (makeNewViewFirstResponder)
+ [window makeFirstResponder:view];
+ [sv setSuppressLayout:NO];
+}
+
+-(NSView <WebDocumentView> *)_makeDocumentViewForDataSource:(WebDataSource *)dataSource
+{
+ NSString* MIMEType = [dataSource _responseMIMEType];
+ if (!MIMEType)
+ MIMEType = @"text/html";
+ Class viewClass = [self _viewClassForMIMEType:MIMEType];
+ NSView <WebDocumentView> *documentView;
+ if (viewClass) {
+ // If the dataSource's representation has already been created, and it is also the
+ // same class as the desired documentView, then use it as the documentView instead
+ // of creating another one (Radar 4340787).
+ id <WebDocumentRepresentation> dataSourceRepresentation = [dataSource representation];
+ if (dataSourceRepresentation && [dataSourceRepresentation class] == viewClass)
+ documentView = (NSView <WebDocumentView> *)[dataSourceRepresentation retain];
+ else
+ documentView = [[viewClass alloc] initWithFrame:[self bounds]];
+ } else
+ documentView = nil;
+
+ [self _setDocumentView:documentView];
+ [documentView release];
+
+ return documentView;
+}
+
+- (void)_setWebFrame:(WebFrame *)webFrame
+{
+ if (!webFrame) {
+ NSView *docV = [self documentView];
+ if ([docV respondsToSelector:@selector(close)])
+ [docV performSelector:@selector(close)];
+ }
+
+ // Not retained because the WebView owns the WebFrame, which owns the WebFrameView.
+ _private->webFrame = webFrame;
+
+ if (!_private->includedInWebKitStatistics && [webFrame _isIncludedInWebKitStatistics]) {
+ _private->includedInWebKitStatistics = YES;
+ ++WebFrameViewCount;
+ }
+}
+
+- (WebDynamicScrollBarsView *)_scrollView
+{
+ // This can be called by [super dealloc] when cleaning up the key view loop,
+ // after _private has been nilled out.
+ if (_private == nil)
+ return nil;
+ return _private->frameScrollView;
+}
+
+- (float)_verticalPageScrollDistance
+{
+ float height = [[self _contentView] bounds].size.height;
+ return max<float>(height * Scrollbar::minFractionToStepWhenPaging(), height - Scrollbar::maxOverlapBetweenPages());
+}
+
+static inline void addTypesFromClass(NSMutableDictionary *allTypes, Class objCClass, NSArray *supportTypes)
+{
+ NSEnumerator *enumerator = [supportTypes objectEnumerator];
+ ASSERT(enumerator != nil);
+ NSString *mime = nil;
+ while ((mime = [enumerator nextObject]) != nil) {
+ // Don't clobber previously-registered classes.
+ if ([allTypes objectForKey:mime] == nil)
+ [allTypes setObject:objCClass forKey:mime];
+ }
+}
+
++ (NSMutableDictionary *)_viewTypesAllowImageTypeOmission:(BOOL)allowImageTypeOmission
+{
+ static NSMutableDictionary *viewTypes = nil;
+ static BOOL addedImageTypes = NO;
+
+ if (!viewTypes) {
+ viewTypes = [[NSMutableDictionary alloc] init];
+ addTypesFromClass(viewTypes, [WebHTMLView class], [WebHTMLView supportedNonImageMIMETypes]);
+
+ // Since this is a "secret default" we don't bother registering it.
+ BOOL omitPDFSupport = [[NSUserDefaults standardUserDefaults] boolForKey:@"WebKitOmitPDFSupport"];
+ if (!omitPDFSupport)
+ addTypesFromClass(viewTypes, [WebPDFView class], [WebPDFView supportedMIMETypes]);
+ }
+
+ if (!addedImageTypes && !allowImageTypeOmission) {
+ addTypesFromClass(viewTypes, [WebHTMLView class], [WebHTMLView supportedImageMIMETypes]);
+ addedImageTypes = YES;
+ }
+
+ return viewTypes;
+}
+
++ (BOOL)_canShowMIMETypeAsHTML:(NSString *)MIMEType
+{
+ return [[[self _viewTypesAllowImageTypeOmission:YES] _webkit_objectForMIMEType:MIMEType] isSubclassOfClass:[WebHTMLView class]];
+}
+
++ (Class)_viewClassForMIMEType:(NSString *)MIMEType allowingPlugins:(BOOL)allowPlugins
+{
+ Class viewClass;
+ return [WebView _viewClass:&viewClass andRepresentationClass:nil forMIMEType:MIMEType allowingPlugins:allowPlugins] ? viewClass : nil;
+}
+
+- (Class)_viewClassForMIMEType:(NSString *)MIMEType
+{
+ return [[self class] _viewClassForMIMEType:MIMEType allowingPlugins:[[[self _webView] preferences] arePlugInsEnabled]];
+}
+
+- (void)_install
+{
+ ASSERT(_private->webFrame);
+ ASSERT(_private->frameScrollView);
+
+ Frame* frame = core(_private->webFrame);
+
+ ASSERT(frame);
+ ASSERT(frame->page());
+
+ // If this isn't the main frame, it must have an owner element set, or it
+ // won't ever get installed in the view hierarchy.
+ ASSERT(frame == frame->page()->mainFrame() || frame->ownerElement());
+
+ FrameView* view = frame->view();
+
+ view->setPlatformWidget(_private->frameScrollView);
+
+ // FIXME: Frame tries to do this too. Is this code needed?
+ if (RenderPart* owner = frame->ownerRenderer()) {
+ owner->setWidget(view);
+ // Now the render part owns the view, so we don't any more.
+ }
+
+ view->updateCanHaveScrollbars();
+}
+
+@end
+
+@implementation WebFrameView
+
+- (id)initWithFrame:(NSRect)frame
+{
+ self = [super initWithFrame:frame];
+ if (!self)
+ return nil;
+
+ static bool didFirstTimeInitialization;
+ if (!didFirstTimeInitialization) {
+ didFirstTimeInitialization = true;
+ InitWebCoreSystemInterface();
+
+ // Need to tell WebCore what function to call for the "History Item has Changed" notification.
+ // Note: We also do this in WebHistoryItem's init method.
+ WebCore::notifyHistoryItemChanged = WKNotifyHistoryItemChanged;
+
+ [WebViewFactory createSharedFactory];
+ [WebKeyGenerator createSharedGenerator];
+
+// FIXME: Remove the NSAppKitVersionNumberWithDeferredWindowDisplaySupport check once
+// once AppKit's Deferred Window Display support is available.
+#if defined(BUILDING_ON_TIGER) || defined(BUILDING_ON_LEOPARD) || !defined(NSAppKitVersionNumberWithDeferredWindowDisplaySupport)
+ // CoreGraphics deferred updates are disabled if WebKitEnableCoalescedUpdatesPreferenceKey is NO
+ // or has no value. For compatibility with Mac OS X 10.5 and lower, deferred updates are off by default.
+ if (![[NSUserDefaults standardUserDefaults] boolForKey:WebKitEnableDeferredUpdatesPreferenceKey])
+ WKDisableCGDeferredUpdates();
+#endif
+ if (!WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITH_MAIN_THREAD_EXCEPTIONS))
+ setDefaultThreadViolationBehavior(LogOnFirstThreadViolation, ThreadViolationRoundOne);
+
+ bool throwExceptionsForRoundTwo = WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITH_ROUND_TWO_MAIN_THREAD_EXCEPTIONS);
+#ifdef MAIL_THREAD_WORKAROUND
+ // Even if old Mail is linked with new WebKit, don't throw exceptions.
+ if ([WebResource _needMailThreadWorkaroundIfCalledOffMainThread])
+ throwExceptionsForRoundTwo = false;
+#endif
+ if (!throwExceptionsForRoundTwo)
+ setDefaultThreadViolationBehavior(LogOnFirstThreadViolation, ThreadViolationRoundTwo);
+ }
+
+ _private = [[WebFrameViewPrivate alloc] init];
+
+ WebDynamicScrollBarsView *scrollView = [[WebDynamicScrollBarsView alloc] initWithFrame:NSMakeRect(0.0f, 0.0f, frame.size.width, frame.size.height)];
+ _private->frameScrollView = scrollView;
+ [scrollView setContentView:[[[WebClipView alloc] initWithFrame:[scrollView bounds]] autorelease]];
+ [scrollView setDrawsBackground:NO];
+ [scrollView setHasVerticalScroller:NO];
+ [scrollView setHasHorizontalScroller:NO];
+ [scrollView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+ [scrollView setLineScroll:Scrollbar::pixelsPerLineStep()];
+ [self addSubview:scrollView];
+
+ // Don't call our overridden version of setNextKeyView here; we need to make the standard NSView
+ // link between us and our subview so that previousKeyView and previousValidKeyView work as expected.
+ // This works together with our becomeFirstResponder and setNextKeyView overrides.
+ [super setNextKeyView:scrollView];
+
+ return self;
+}
+
+- (void)dealloc
+{
+ if (_private && _private->includedInWebKitStatistics)
+ --WebFrameViewCount;
+
+ [_private release];
+ _private = nil;
+
+ [super dealloc];
+}
+
+- (void)finalize
+{
+ if (_private && _private->includedInWebKitStatistics)
+ --WebFrameViewCount;
+
+ [super finalize];
+}
+
+- (WebFrame *)webFrame
+{
+ // This method can be called beneath -[NSView dealloc] after _private has been cleared.
+ return _private ? _private->webFrame : nil;
+}
+
+- (void)setAllowsScrolling:(BOOL)flag
+{
+ WebCore::Frame *frame = core([self webFrame]);
+ if (WebCore::FrameView *view = frame? frame->view() : 0)
+ view->setCanHaveScrollbars(flag);
+}
+
+- (BOOL)allowsScrolling
+{
+ WebCore::Frame *frame = core([self webFrame]);
+ if (WebCore::FrameView *view = frame? frame->view() : 0)
+ return view->canHaveScrollbars();
+ return YES;
+}
+
+- (NSView <WebDocumentView> *)documentView
+{
+ return [[self _scrollView] documentView];
+}
+
+- (BOOL)acceptsFirstResponder
+{
+ // We always accept first responder; this matches OS X 10.2 WebKit
+ // behavior (see 3469791).
+ return YES;
+}
+
+- (BOOL)becomeFirstResponder
+{
+ // This works together with setNextKeyView to splice the WebFrameView into
+ // the key loop similar to the way NSScrollView does this. Note that
+ // WebView has similar code.
+
+ NSWindow *window = [self window];
+ if ([window keyViewSelectionDirection] == NSSelectingPrevious) {
+ NSView *previousValidKeyView = [self previousValidKeyView];
+ // If we couldn't find a previous valid key view, ask the WebView. This handles frameset
+ // cases (one is mentioned in Radar bug 3748628). Note that previousValidKeyView should
+ // never be self but can be due to AppKit oddness (mentioned in Radar bug 3748628).
+ if (previousValidKeyView == nil || previousValidKeyView == self)
+ previousValidKeyView = [[[self webFrame] webView] previousValidKeyView];
+ [window makeFirstResponder:previousValidKeyView];
+ } else {
+ // If the scroll view won't accept first-responderness now, then just become
+ // the first responder ourself like a normal view. This lets us be the first
+ // responder in cases where no page has yet been loaded.
+ if ([[self _scrollView] acceptsFirstResponder])
+ [window makeFirstResponder:[self _scrollView]];
+ }
+
+ return YES;
+}
+
+- (void)setNextKeyView:(NSView *)aView
+{
+ // This works together with becomeFirstResponder to splice the WebFrameView into
+ // the key loop similar to the way NSScrollView does this. Note that
+ // WebView has very similar code.
+ if ([self _scrollView] != nil) {
+ [[self _scrollView] setNextKeyView:aView];
+ } else {
+ [super setNextKeyView:aView];
+ }
+}
+
+- (BOOL)isOpaque
+{
+ return [[self _webView] drawsBackground];
+}
+
+- (void)drawRect:(NSRect)rect
+{
+ if ([self documentView] == nil) {
+ // Need to paint ourselves if there's no documentView to do it instead.
+ if ([[self _webView] drawsBackground]) {
+ [[[self _webView] backgroundColor] set];
+ NSRectFill(rect);
+ }
+ } else {
+#ifndef NDEBUG
+ if ([[self _scrollView] drawsBackground]) {
+ [[NSColor cyanColor] set];
+ NSRectFill(rect);
+ }
+#endif
+ }
+}
+
+- (NSRect)visibleRect
+{
+ // This method can be called beneath -[NSView dealloc] after we have cleared _private.
+ if (!_private)
+ return [super visibleRect];
+
+ // FIXME: <rdar://problem/6213380> This method does not work correctly with transforms, for two reasons:
+ // 1) [super visibleRect] does not account for the transform, since it is not represented
+ // in the NSView hierarchy.
+ // 2) -_getVisibleRect: does not correct for transforms.
+
+ NSRect rendererVisibleRect;
+ if (![[self webFrame] _getVisibleRect:&rendererVisibleRect])
+ return [super visibleRect];
+
+ if (NSIsEmptyRect(rendererVisibleRect))
+ return NSZeroRect;
+
+ NSRect viewVisibleRect = [super visibleRect];
+ if (NSIsEmptyRect(viewVisibleRect))
+ return NSZeroRect;
+
+ NSRect frame = [self frame];
+ // rendererVisibleRect is in the parent's coordinate space, and frame is in the superview's coordinate space.
+ // The return value from this method needs to be in this view's coordinate space. We get that right by subtracting
+ // the origins (and correcting for flipping), but when we support transforms, we will need to do better than this.
+ rendererVisibleRect.origin.x -= frame.origin.x;
+ rendererVisibleRect.origin.y = NSMaxY(frame) - NSMaxY(rendererVisibleRect);
+ return NSIntersectionRect(rendererVisibleRect, viewVisibleRect);
+}
+
+- (void)setFrameSize:(NSSize)size
+{
+ if (!NSEqualSizes(size, [self frame].size)) {
+ // See WebFrameLoaderClient::provisionalLoadStarted.
+ if ([[[self webFrame] webView] drawsBackground])
+ [[self _scrollView] setDrawsBackground:YES];
+ if (Frame* coreFrame = [self _web_frame]) {
+ if (FrameView* coreFrameView = coreFrame->view())
+ coreFrameView->setNeedsLayout();
+ }
+ }
+ [super setFrameSize:size];
+}
+
+- (void)viewDidMoveToWindow
+{
+ // See WebFrameLoaderClient::provisionalLoadStarted.
+ // Need to check _private for nil because this can be called inside -[WebView initWithCoder:].
+ if (_private && [[[self webFrame] webView] drawsBackground])
+ [[self _scrollView] setDrawsBackground:YES];
+ [super viewDidMoveToWindow];
+}
+
+- (BOOL)_scrollOverflowInDirection:(ScrollDirection)direction granularity:(ScrollGranularity)granularity
+{
+ // scrolling overflows is only applicable if we're dealing with an WebHTMLView
+ if (![[self documentView] isKindOfClass:[WebHTMLView class]])
+ return NO;
+ Frame* frame = core([self webFrame]);
+ if (!frame)
+ return NO;
+ return frame->eventHandler()->scrollOverflow(direction, granularity);
+}
+
+- (BOOL)_scrollToBeginningOfDocument
+{
+ if ([self _scrollOverflowInDirection:ScrollUp granularity:ScrollByDocument])
+ return YES;
+ if (![self _isScrollable])
+ return NO;
+ NSPoint point = [[[self _scrollView] documentView] frame].origin;
+ return [[self _contentView] _scrollTo:&point animate:YES];
+}
+
+- (BOOL)_scrollToEndOfDocument
+{
+ if ([self _scrollOverflowInDirection:ScrollDown granularity:ScrollByDocument])
+ return YES;
+ if (![self _isScrollable])
+ return NO;
+ NSRect frame = [[[self _scrollView] documentView] frame];
+ NSPoint point = NSMakePoint(frame.origin.x, NSMaxY(frame));
+ return [[self _contentView] _scrollTo:&point animate:YES];
+}
+
+- (void)scrollToBeginningOfDocument:(id)sender
+{
+ if ([self _scrollToBeginningOfDocument])
+ return;
+
+ if (WebFrameView *child = [self _largestScrollableChild]) {
+ if ([child _scrollToBeginningOfDocument])
+ return;
+ }
+ [[self nextResponder] tryToPerform:@selector(scrollToBeginningOfDocument:) with:sender];
+}
+
+- (void)scrollToEndOfDocument:(id)sender
+{
+ if ([self _scrollToEndOfDocument])
+ return;
+
+ if (WebFrameView *child = [self _largestScrollableChild]) {
+ if ([child _scrollToEndOfDocument])
+ return;
+ }
+ [[self nextResponder] tryToPerform:@selector(scrollToEndOfDocument:) with:sender];
+}
+
+- (void)_goBack
+{
+ [[self _webView] goBack];
+}
+
+- (void)_goForward
+{
+ [[self _webView] goForward];
+}
+
+- (BOOL)_scrollVerticallyBy:(float)delta
+{
+ // This method uses the secret method _scrollTo on NSClipView.
+ // It does that because it needs to know definitively whether scrolling was
+ // done or not to help implement the "scroll parent if we are at the limit" feature.
+ // In the presence of smooth scrolling, there's no easy way to tell if the method
+ // did any scrolling or not with the public API.
+ NSPoint point = [[self _contentView] bounds].origin;
+ point.y += delta;
+ return [[self _contentView] _scrollTo:&point animate:YES];
+}
+
+- (BOOL)_scrollHorizontallyBy:(float)delta
+{
+ NSPoint point = [[self _contentView] bounds].origin;
+ point.x += delta;
+ return [[self _contentView] _scrollTo:&point animate:YES];
+}
+
+- (float)_horizontalKeyboardScrollDistance
+{
+ // Arrow keys scroll the same distance that clicking the scroll arrow does.
+ return [[self _scrollView] horizontalLineScroll];
+}
+
+- (float)_horizontalPageScrollDistance
+{
+ float width = [[self _contentView] bounds].size.width;
+ return max<float>(width * Scrollbar::minFractionToStepWhenPaging(), width - Scrollbar::maxOverlapBetweenPages());
+}
+
+- (BOOL)_pageVertically:(BOOL)up
+{
+ if ([self _scrollOverflowInDirection:up ? ScrollUp : ScrollDown granularity:ScrollByPage])
+ return YES;
+
+ if (![self _isScrollable])
+ return [[self _largestScrollableChild] _pageVertically:up];
+
+ float delta = [self _verticalPageScrollDistance];
+ return [self _scrollVerticallyBy:up ? -delta : delta];
+}
+
+- (BOOL)_pageHorizontally:(BOOL)left
+{
+ if ([self _scrollOverflowInDirection:left ? ScrollLeft : ScrollRight granularity:ScrollByPage])
+ return YES;
+
+ if (![self _isScrollable])
+ return [[self _largestScrollableChild] _pageHorizontally:left];
+
+ float delta = [self _horizontalPageScrollDistance];
+ return [self _scrollHorizontallyBy:left ? -delta : delta];
+}
+
+- (BOOL)_scrollLineVertically:(BOOL)up
+{
+ if ([self _scrollOverflowInDirection:up ? ScrollUp : ScrollDown granularity:ScrollByLine])
+ return YES;
+
+ if (![self _isScrollable])
+ return [[self _largestScrollableChild] _scrollLineVertically:up];
+
+ float delta = [self _verticalKeyboardScrollDistance];
+ return [self _scrollVerticallyBy:up ? -delta : delta];
+}
+
+- (BOOL)_scrollLineHorizontally:(BOOL)left
+{
+ if ([self _scrollOverflowInDirection:left ? ScrollLeft : ScrollRight granularity:ScrollByLine])
+ return YES;
+
+ if (![self _isScrollable])
+ return [[self _largestScrollableChild] _scrollLineHorizontally:left];
+
+ float delta = [self _horizontalKeyboardScrollDistance];
+ return [self _scrollHorizontallyBy:left ? -delta : delta];
+}
+
+- (void)scrollPageUp:(id)sender
+{
+ if (![self _pageVertically:YES]) {
+ // If we were already at the top, tell the next responder to scroll if it can.
+ [[self nextResponder] tryToPerform:@selector(scrollPageUp:) with:sender];
+ }
+}
+
+- (void)scrollPageDown:(id)sender
+{
+ if (![self _pageVertically:NO]) {
+ // If we were already at the bottom, tell the next responder to scroll if it can.
+ [[self nextResponder] tryToPerform:@selector(scrollPageDown:) with:sender];
+ }
+}
+
+- (void)scrollLineUp:(id)sender
+{
+ if (![self _scrollLineVertically:YES])
+ [[self nextResponder] tryToPerform:@selector(scrollLineUp:) with:sender];
+}
+
+- (void)scrollLineDown:(id)sender
+{
+ if (![self _scrollLineVertically:NO])
+ [[self nextResponder] tryToPerform:@selector(scrollLineDown:) with:sender];
+}
+
+- (BOOL)_firstResponderIsFormControl
+{
+ NSResponder *firstResponder = [[self window] firstResponder];
+
+ // WebHTMLView is an NSControl subclass these days, but it's not a form control
+ if ([firstResponder isKindOfClass:[WebHTMLView class]]) {
+ return NO;
+ }
+ return [firstResponder isKindOfClass:[NSControl class]];
+}
+
+- (void)keyDown:(NSEvent *)event
+{
+ NSString *characters = [event characters];
+ int index, count;
+ BOOL callSuper = YES;
+ Frame* coreFrame = [self _web_frame];
+ BOOL maintainsBackForwardList = coreFrame && coreFrame->page()->backForwardList()->enabled() ? YES : NO;
+
+ count = [characters length];
+ for (index = 0; index < count; ++index) {
+ switch ([characters characterAtIndex:index]) {
+ case NSDeleteCharacter:
+ if (!maintainsBackForwardList) {
+ callSuper = YES;
+ break;
+ }
+ // This odd behavior matches some existing browsers,
+ // including Windows IE
+ if ([event modifierFlags] & NSShiftKeyMask) {
+ [self _goForward];
+ } else {
+ [self _goBack];
+ }
+ callSuper = NO;
+ break;
+ case SpaceKey:
+ // Checking for a control will allow events to percolate
+ // correctly when the focus is on a form control and we
+ // are in full keyboard access mode.
+ if ((![self allowsScrolling] && ![self _largestScrollableChild]) || [self _firstResponderIsFormControl]) {
+ callSuper = YES;
+ break;
+ }
+ if ([event modifierFlags] & NSShiftKeyMask) {
+ [self scrollPageUp:nil];
+ } else {
+ [self scrollPageDown:nil];
+ }
+ callSuper = NO;
+ break;
+ case NSPageUpFunctionKey:
+ if (![self allowsScrolling] && ![self _largestScrollableChild]) {
+ callSuper = YES;
+ break;
+ }
+ [self scrollPageUp:nil];
+ callSuper = NO;
+ break;
+ case NSPageDownFunctionKey:
+ if (![self allowsScrolling] && ![self _largestScrollableChild]) {
+ callSuper = YES;
+ break;
+ }
+ [self scrollPageDown:nil];
+ callSuper = NO;
+ break;
+ case NSHomeFunctionKey:
+ if (![self allowsScrolling] && ![self _largestScrollableChild]) {
+ callSuper = YES;
+ break;
+ }
+ [self scrollToBeginningOfDocument:nil];
+ callSuper = NO;
+ break;
+ case NSEndFunctionKey:
+ if (![self allowsScrolling] && ![self _largestScrollableChild]) {
+ callSuper = YES;
+ break;
+ }
+ [self scrollToEndOfDocument:nil];
+ callSuper = NO;
+ break;
+ case NSUpArrowFunctionKey:
+ // We don't handle shifted or control-arrow keys here, so let super have a chance.
+ if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) {
+ callSuper = YES;
+ break;
+ }
+ if ((![self allowsScrolling] && ![self _largestScrollableChild]) ||
+ [[[self window] firstResponder] isKindOfClass:[NSPopUpButton class]]) {
+ // Let arrow keys go through to pop up buttons
+ // <rdar://problem/3455910>: hitting up or down arrows when focus is on a
+ // pop-up menu should pop the menu
+ callSuper = YES;
+ break;
+ }
+ if ([event modifierFlags] & NSCommandKeyMask) {
+ [self scrollToBeginningOfDocument:nil];
+ } else if ([event modifierFlags] & NSAlternateKeyMask) {
+ [self scrollPageUp:nil];
+ } else {
+ [self scrollLineUp:nil];
+ }
+ callSuper = NO;
+ break;
+ case NSDownArrowFunctionKey:
+ // We don't handle shifted or control-arrow keys here, so let super have a chance.
+ if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) {
+ callSuper = YES;
+ break;
+ }
+ if ((![self allowsScrolling] && ![self _largestScrollableChild]) ||
+ [[[self window] firstResponder] isKindOfClass:[NSPopUpButton class]]) {
+ // Let arrow keys go through to pop up buttons
+ // <rdar://problem/3455910>: hitting up or down arrows when focus is on a
+ // pop-up menu should pop the menu
+ callSuper = YES;
+ break;
+ }
+ if ([event modifierFlags] & NSCommandKeyMask) {
+ [self scrollToEndOfDocument:nil];
+ } else if ([event modifierFlags] & NSAlternateKeyMask) {
+ [self scrollPageDown:nil];
+ } else {
+ [self scrollLineDown:nil];
+ }
+ callSuper = NO;
+ break;
+ case NSLeftArrowFunctionKey:
+ // We don't handle shifted or control-arrow keys here, so let super have a chance.
+ if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) {
+ callSuper = YES;
+ break;
+ }
+ // Check back/forward related keys.
+ if ([event modifierFlags] & NSCommandKeyMask) {
+ if (!maintainsBackForwardList) {
+ callSuper = YES;
+ break;
+ }
+ [self _goBack];
+ } else {
+ // Now check scrolling related keys.
+ if ((![self allowsScrolling] && ![self _largestScrollableChild])) {
+ callSuper = YES;
+ break;
+ }
+
+ if ([event modifierFlags] & NSAlternateKeyMask) {
+ [self _pageHorizontally:YES];
+ } else {
+ [self _scrollLineHorizontally:YES];
+ }
+ }
+ callSuper = NO;
+ break;
+ case NSRightArrowFunctionKey:
+ // We don't handle shifted or control-arrow keys here, so let super have a chance.
+ if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) {
+ callSuper = YES;
+ break;
+ }
+ // Check back/forward related keys.
+ if ([event modifierFlags] & NSCommandKeyMask) {
+ if (!maintainsBackForwardList) {
+ callSuper = YES;
+ break;
+ }
+ [self _goForward];
+ } else {
+ // Now check scrolling related keys.
+ if ((![self allowsScrolling] && ![self _largestScrollableChild])) {
+ callSuper = YES;
+ break;
+ }
+
+ if ([event modifierFlags] & NSAlternateKeyMask) {
+ [self _pageHorizontally:NO];
+ } else {
+ [self _scrollLineHorizontally:NO];
+ }
+ }
+ callSuper = NO;
+ break;
+ }
+ }
+
+ if (callSuper) {
+ [super keyDown:event];
+ } else {
+ // if we did something useful, get the cursor out of the way
+ [NSCursor setHiddenUntilMouseMoves:YES];
+ }
+}
+
+- (NSView *)_webcore_effectiveFirstResponder
+{
+ NSView *view = [self documentView];
+ return view ? [view _webcore_effectiveFirstResponder] : [super _webcore_effectiveFirstResponder];
+}
+
+- (BOOL)canPrintHeadersAndFooters
+{
+ NSView *documentView = [[self _scrollView] documentView];
+ if ([documentView respondsToSelector:@selector(canPrintHeadersAndFooters)]) {
+ return [(id)documentView canPrintHeadersAndFooters];
+ }
+ return NO;
+}
+
+- (NSPrintOperation *)printOperationWithPrintInfo:(NSPrintInfo *)printInfo
+{
+ NSView *documentView = [[self _scrollView] documentView];
+ if (!documentView) {
+ return nil;
+ }
+ if ([documentView respondsToSelector:@selector(printOperationWithPrintInfo:)]) {
+ return [(id)documentView printOperationWithPrintInfo:printInfo];
+ }
+ return [NSPrintOperation printOperationWithView:documentView printInfo:printInfo];
+}
+
+- (BOOL)documentViewShouldHandlePrint
+{
+ NSView *documentView = [[self _scrollView] documentView];
+ if (documentView && [documentView respondsToSelector:@selector(documentViewShouldHandlePrint)])
+ return [(id)documentView documentViewShouldHandlePrint];
+
+ return NO;
+}
+
+- (void)printDocumentView
+{
+ NSView *documentView = [[self _scrollView] documentView];
+ if (documentView && [documentView respondsToSelector:@selector(printDocumentView)])
+ [(id)documentView printDocumentView];
+}
+
+@end
+
+@implementation WebFrameView (WebPrivate)
+
+- (float)_area
+{
+ NSRect frame = [self frame];
+ return frame.size.height * frame.size.width;
+}
+
+- (BOOL)_isScrollable
+{
+ WebDynamicScrollBarsView *scrollView = [self _scrollView];
+ return [scrollView horizontalScrollingAllowed] || [scrollView verticalScrollingAllowed];
+}
+
+- (WebFrameView *)_largestScrollableChild
+{
+ WebFrameView *largest = nil;
+ NSArray *frameChildren = [[self webFrame] childFrames];
+
+ unsigned i;
+ for (i=0; i < [frameChildren count]; i++) {
+ WebFrameView *childFrameView = [[frameChildren objectAtIndex:i] frameView];
+ WebFrameView *scrollableFrameView = [childFrameView _isScrollable] ? childFrameView : [childFrameView _largestScrollableChild];
+ if (!scrollableFrameView)
+ continue;
+
+ // Some ads lurk in child frames of zero width and height, see radar 4406994. These don't count as scrollable.
+ // Maybe someday we'll discover that this minimum area check should be larger, but this covers the known cases.
+ float area = [scrollableFrameView _area];
+ if (area < 1.0)
+ continue;
+
+ if (!largest || (area > [largest _area])) {
+ largest = scrollableFrameView;
+ }
+ }
+
+ return largest;
+}
+
+- (BOOL)_hasScrollBars
+{
+ // FIXME: This method was used by Safari 4.0.x and older versions, but has not been used by any other WebKit
+ // clients to my knowledge, and will not be used by future versions of Safari. It can probably be removed
+ // once we no longer need to keep nightly WebKit builds working with Safari 4.0.x and earlier.
+ NSScrollView *scrollView = [self _scrollView];
+ return [scrollView hasHorizontalScroller] || [scrollView hasVerticalScroller];
+}
+
+- (WebFrameView *)_largestChildWithScrollBars
+{
+ // FIXME: This method was used by Safari 4.0.x and older versions, but has not been used by any other WebKit
+ // clients to my knowledge, and will not be used by future versions of Safari. It can probably be removed
+ // once we no longer need to keep nightly WebKit builds working with Safari 4.0.x and earlier.
+ WebFrameView *largest = nil;
+ NSArray *frameChildren = [[self webFrame] childFrames];
+
+ unsigned i;
+ for (i=0; i < [frameChildren count]; i++) {
+ WebFrameView *childFrameView = [[frameChildren objectAtIndex:i] frameView];
+ WebFrameView *scrollableFrameView = [childFrameView _hasScrollBars] ? childFrameView : [childFrameView _largestChildWithScrollBars];
+ if (!scrollableFrameView)
+ continue;
+
+ // Some ads lurk in child frames of zero width and height, see radar 4406994. These don't count as scrollable.
+ // Maybe someday we'll discover that this minimum area check should be larger, but this covers the known cases.
+ float area = [scrollableFrameView _area];
+ if (area < 1.0)
+ continue;
+
+ if (!largest || (area > [largest _area])) {
+ largest = scrollableFrameView;
+ }
+ }
+
+ return largest;
+}
+
+- (NSClipView *)_contentView
+{
+ return [[self _scrollView] contentView];
+}
+
+- (Class)_customScrollViewClass
+{
+ if ([_private->frameScrollView class] == [WebDynamicScrollBarsView class])
+ return nil;
+ return [_private->frameScrollView class];
+}
+
+- (void)_setCustomScrollViewClass:(Class)customClass
+{
+ if (!customClass)
+ customClass = [WebDynamicScrollBarsView class];
+ ASSERT([customClass isSubclassOfClass:[WebDynamicScrollBarsView class]]);
+ if (customClass == [_private->frameScrollView class])
+ return;
+ if (![customClass isSubclassOfClass:[WebDynamicScrollBarsView class]])
+ return;
+
+ WebDynamicScrollBarsView *oldScrollView = _private->frameScrollView; // already retained
+ NSView <WebDocumentView> *documentView = [[self documentView] retain];
+
+ WebDynamicScrollBarsView *scrollView = [[customClass alloc] initWithFrame:[oldScrollView frame]];
+ [scrollView setContentView:[[[WebClipView alloc] initWithFrame:[scrollView bounds]] autorelease]];
+ [scrollView setDrawsBackground:[oldScrollView drawsBackground]];
+ [scrollView setHasVerticalScroller:[oldScrollView hasVerticalScroller]];
+ [scrollView setHasHorizontalScroller:[oldScrollView hasHorizontalScroller]];
+ [scrollView setAutoresizingMask:[oldScrollView autoresizingMask]];
+ [scrollView setLineScroll:[oldScrollView lineScroll]];
+ [self addSubview:scrollView];
+
+ // don't call our overridden version here; we need to make the standard NSView link between us
+ // and our subview so that previousKeyView and previousValidKeyView work as expected. This works
+ // together with our becomeFirstResponder and setNextKeyView overrides.
+ [super setNextKeyView:scrollView];
+
+ _private->frameScrollView = scrollView;
+
+ [self _setDocumentView:documentView];
+ [self _install];
+
+ [oldScrollView removeFromSuperview];
+ [oldScrollView release];
+ [documentView release];
+}
+
+@end