diff -r 000000000000 -r 4f2f89ce4247 WebKit/mac/WebView/WebFrameView.mm --- /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 +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +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) +- (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 *)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 *)_makeDocumentViewForDataSource:(WebDataSource *)dataSource +{ + NSString* MIMEType = [dataSource _responseMIMEType]; + if (!MIMEType) + MIMEType = @"text/html"; + Class viewClass = [self _viewClassForMIMEType:MIMEType]; + NSView *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 dataSourceRepresentation = [dataSource representation]; + if (dataSourceRepresentation && [dataSourceRepresentation class] == viewClass) + documentView = (NSView *)[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(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 *)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: 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(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 + // : 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 + // : 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 *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