/* * 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/BackForwardListImpl.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]; // 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)setBoundsSize:(NSSize)size { [super setBoundsSize:size]; [[self _scrollView] 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)_isVerticalDocument { Frame* coreFrame = [self _web_frame]; if (!coreFrame) return YES; Document* document = coreFrame->document(); if (!document) return YES; RenderObject* renderView = document->renderer(); if (!renderView) return YES; return renderView->style()->isHorizontalWritingMode(); } - (BOOL)_isFlippedDocument { Frame* coreFrame = [self _web_frame]; if (!coreFrame) return NO; Document* document = coreFrame->document(); if (!document) return NO; RenderObject* renderView = document->renderer(); if (!renderView) return NO; return renderView->style()->isFlippedBlocksWritingMode(); } - (BOOL)_scrollToBeginningOfDocument { if ([self _scrollOverflowInDirection:ScrollUp granularity:ScrollByDocument]) return YES; if (![self _isScrollable]) return NO; NSPoint point = [[[self _scrollView] documentView] frame].origin; point.x += [[self _scrollView] scrollOrigin].x; point.y += [[self _scrollView] scrollOrigin].y; 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]; bool isVertical = [self _isVerticalDocument]; bool isFlipped = [self _isFlippedDocument]; NSPoint point; if (isVertical) { if (!isFlipped) point = NSMakePoint(frame.origin.x, NSMaxY(frame)); else point = NSMakePoint(frame.origin.x, NSMinY(frame)); } else { if (!isFlipped) point = NSMakePoint(NSMaxX(frame), frame.origin.y); else point = NSMakePoint(NSMinX(frame), frame.origin.y); } // Reset the position opposite to the block progression direction. if (isVertical) point.x += [[self _scrollView] scrollOrigin].x; else point.y += [[self _scrollView] scrollOrigin].y; 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)_pageInBlockProgressionDirection:(BOOL)forward { // Determine whether we're calling _pageVertically or _pageHorizontally. BOOL isVerticalDocument = [self _isVerticalDocument]; BOOL isFlippedBlock = [self _isFlippedDocument]; if (isVerticalDocument) return [self _pageVertically:isFlippedBlock ? !forward : forward]; return [self _pageHorizontally:isFlippedBlock ? !forward : forward]; } - (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 _pageInBlockProgressionDirection: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 _pageInBlockProgressionDirection: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 { // Implement common browser behaviors for all kinds of content. // FIXME: This is not a good time to execute commands for WebHTMLView. We should run these at the time commands sent by key bindings // are executed for consistency. // This doesn't work automatically because most of the keys handled here are translated into moveXXX commands, which are not handled // by Editor when focus is not in editable content. NSString *characters = [event characters]; int index, count; BOOL callSuper = YES; Frame* coreFrame = [self _web_frame]; BOOL maintainsBackForwardList = coreFrame && static_cast<BackForwardListImpl*>(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