| /* |
| * 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 |