1/*
2 * Copyright (C) 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1.  Redistributions of source code must retain the above copyright
9 *     notice, this list of conditions and the following disclaimer.
10 * 2.  Redistributions in binary form must reproduce the above copyright
11 *     notice, this list of conditions and the following disclaimer in the
12 *     documentation and/or other materials provided with the distribution.
13 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 *     its contributors may be used to endorse or promote products derived
15 *     from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#import "WebFrameView.h"
30
31#import "WebClipView.h"
32#import "WebDataSourcePrivate.h"
33#import "WebDocument.h"
34#import "WebDynamicScrollBarsViewInternal.h"
35#import "WebFrame.h"
36#import "WebFrameInternal.h"
37#import "WebFrameViewInternal.h"
38#import "WebFrameViewPrivate.h"
39#import "WebHistoryItemInternal.h"
40#import "WebHTMLViewPrivate.h"
41#import "WebKeyGenerator.h"
42#import "WebKitErrorsPrivate.h"
43#import "WebKitStatisticsPrivate.h"
44#import "WebKitVersionChecks.h"
45#import "WebNSDictionaryExtras.h"
46#import "WebNSObjectExtras.h"
47#import "WebNSPasteboardExtras.h"
48#import "WebNSViewExtras.h"
49#import "WebNSWindowExtras.h"
50#import "WebPDFView.h"
51#import "WebPreferenceKeysPrivate.h"
52#import "WebResourceInternal.h"
53#import "WebSystemInterface.h"
54#import "WebViewFactory.h"
55#import "WebViewInternal.h"
56#import "WebViewPrivate.h"
57#import <Foundation/NSURLRequest.h>
58#import <WebCore/BackForwardListImpl.h>
59#import <WebCore/DragController.h>
60#import <WebCore/EventHandler.h>
61#import <WebCore/Frame.h>
62#import <WebCore/FrameView.h>
63#import <WebCore/HistoryItem.h>
64#import <WebCore/Page.h>
65#import <WebCore/RenderPart.h>
66#import <WebCore/ThreadCheck.h>
67#import <WebCore/WebCoreFrameView.h>
68#import <WebCore/WebCoreView.h>
69#import <WebKitSystemInterface.h>
70#import <wtf/Assertions.h>
71
72using namespace WebCore;
73
74@interface NSWindow (WindowPrivate)
75- (BOOL)_needsToResetDragMargins;
76- (void)_setNeedsToResetDragMargins:(BOOL)s;
77@end
78
79@interface NSClipView (AppKitSecretsIKnow)
80- (BOOL)_scrollTo:(const NSPoint *)newOrigin animate:(BOOL)animate; // need the boolean result from this method
81@end
82
83enum {
84    SpaceKey = 0x0020
85};
86
87@interface WebFrameView (WebFrameViewFileInternal) <WebCoreFrameView>
88- (float)_verticalKeyboardScrollDistance;
89@end
90
91@interface WebFrameViewPrivate : NSObject {
92@public
93    WebFrame *webFrame;
94    WebDynamicScrollBarsView *frameScrollView;
95    BOOL includedInWebKitStatistics;
96}
97@end
98
99@implementation WebFrameViewPrivate
100
101- (void)dealloc
102{
103    [frameScrollView release];
104    [super dealloc];
105}
106
107@end
108
109@implementation WebFrameView (WebFrameViewFileInternal)
110
111- (float)_verticalKeyboardScrollDistance
112{
113    // Arrow keys scroll the same distance that clicking the scroll arrow does.
114    return [[self _scrollView] verticalLineScroll];
115}
116
117- (Frame*)_web_frame
118{
119    return core(_private->webFrame);
120}
121
122@end
123
124@implementation WebFrameView (WebInternal)
125
126// Note that the WebVew is not retained.
127- (WebView *)_webView
128{
129    return [_private->webFrame webView];
130}
131
132- (void)_setDocumentView:(NSView <WebDocumentView> *)view
133{
134    WebDynamicScrollBarsView *sv = [self _scrollView];
135    core([self _webView])->dragController()->setDidInitiateDrag(false);
136
137    [sv setSuppressLayout:YES];
138
139    // If the old view is the first responder, transfer first responder status to the new view as
140    // a convenience and so that we don't leave the window pointing to a view that's no longer in it.
141    NSWindow *window = [sv window];
142    NSResponder *firstResponder = [window firstResponder];
143    bool makeNewViewFirstResponder = [firstResponder isKindOfClass:[NSView class]] && [(NSView *)firstResponder isDescendantOf:[sv documentView]];
144
145    // Suppress the resetting of drag margins since we know we can't affect them.
146    BOOL resetDragMargins = [window _needsToResetDragMargins];
147    [window _setNeedsToResetDragMargins:NO];
148    [sv setDocumentView:view];
149    [window _setNeedsToResetDragMargins:resetDragMargins];
150
151    if (makeNewViewFirstResponder)
152        [window makeFirstResponder:view];
153    [sv setSuppressLayout:NO];
154}
155
156-(NSView <WebDocumentView> *)_makeDocumentViewForDataSource:(WebDataSource *)dataSource
157{
158    NSString* MIMEType = [dataSource _responseMIMEType];
159    if (!MIMEType)
160        MIMEType = @"text/html";
161    Class viewClass = [self _viewClassForMIMEType:MIMEType];
162    NSView <WebDocumentView> *documentView;
163    if (viewClass) {
164        // If the dataSource's representation has already been created, and it is also the
165        // same class as the desired documentView, then use it as the documentView instead
166        // of creating another one (Radar 4340787).
167        id <WebDocumentRepresentation> dataSourceRepresentation = [dataSource representation];
168        if (dataSourceRepresentation && [dataSourceRepresentation class] == viewClass)
169            documentView = (NSView <WebDocumentView> *)[dataSourceRepresentation retain];
170        else
171            documentView = [[viewClass alloc] initWithFrame:[self bounds]];
172    } else
173        documentView = nil;
174
175    [self _setDocumentView:documentView];
176    [documentView release];
177
178    return documentView;
179}
180
181- (void)_setWebFrame:(WebFrame *)webFrame
182{
183    if (!webFrame) {
184        NSView *docV = [self documentView];
185        if ([docV respondsToSelector:@selector(close)])
186            [docV performSelector:@selector(close)];
187    }
188
189    // Not retained because the WebView owns the WebFrame, which owns the WebFrameView.
190    _private->webFrame = webFrame;
191
192    if (!_private->includedInWebKitStatistics && [webFrame _isIncludedInWebKitStatistics]) {
193        _private->includedInWebKitStatistics = YES;
194        ++WebFrameViewCount;
195    }
196}
197
198- (WebDynamicScrollBarsView *)_scrollView
199{
200    // This can be called by [super dealloc] when cleaning up the key view loop,
201    // after _private has been nilled out.
202    if (_private == nil)
203        return nil;
204    return _private->frameScrollView;
205}
206
207- (float)_verticalPageScrollDistance
208{
209    float height = [[self _contentView] bounds].size.height;
210    return max<float>(height * Scrollbar::minFractionToStepWhenPaging(), height - Scrollbar::maxOverlapBetweenPages());
211}
212
213static inline void addTypesFromClass(NSMutableDictionary *allTypes, Class objCClass, NSArray *supportTypes)
214{
215    NSEnumerator *enumerator = [supportTypes objectEnumerator];
216    ASSERT(enumerator != nil);
217    NSString *mime = nil;
218    while ((mime = [enumerator nextObject]) != nil) {
219        // Don't clobber previously-registered classes.
220        if ([allTypes objectForKey:mime] == nil)
221            [allTypes setObject:objCClass forKey:mime];
222    }
223}
224
225+ (NSMutableDictionary *)_viewTypesAllowImageTypeOmission:(BOOL)allowImageTypeOmission
226{
227    static NSMutableDictionary *viewTypes = nil;
228    static BOOL addedImageTypes = NO;
229
230    if (!viewTypes) {
231        viewTypes = [[NSMutableDictionary alloc] init];
232        addTypesFromClass(viewTypes, [WebHTMLView class], [WebHTMLView supportedNonImageMIMETypes]);
233
234        // Since this is a "secret default" we don't bother registering it.
235        BOOL omitPDFSupport = [[NSUserDefaults standardUserDefaults] boolForKey:@"WebKitOmitPDFSupport"];
236        if (!omitPDFSupport)
237            addTypesFromClass(viewTypes, [WebPDFView class], [WebPDFView supportedMIMETypes]);
238    }
239
240    if (!addedImageTypes && !allowImageTypeOmission) {
241        addTypesFromClass(viewTypes, [WebHTMLView class], [WebHTMLView supportedImageMIMETypes]);
242        addedImageTypes = YES;
243    }
244
245    return viewTypes;
246}
247
248+ (BOOL)_canShowMIMETypeAsHTML:(NSString *)MIMEType
249{
250    return [[[self _viewTypesAllowImageTypeOmission:YES] _webkit_objectForMIMEType:MIMEType] isSubclassOfClass:[WebHTMLView class]];
251}
252
253+ (Class)_viewClassForMIMEType:(NSString *)MIMEType allowingPlugins:(BOOL)allowPlugins
254{
255    Class viewClass;
256    return [WebView _viewClass:&viewClass andRepresentationClass:nil forMIMEType:MIMEType allowingPlugins:allowPlugins] ? viewClass : nil;
257}
258
259- (Class)_viewClassForMIMEType:(NSString *)MIMEType
260{
261    return [[self class] _viewClassForMIMEType:MIMEType allowingPlugins:[[[self _webView] preferences] arePlugInsEnabled]];
262}
263
264- (void)_install
265{
266    ASSERT(_private->webFrame);
267    ASSERT(_private->frameScrollView);
268
269    Frame* frame = core(_private->webFrame);
270
271    ASSERT(frame);
272    ASSERT(frame->page());
273
274    // If this isn't the main frame, it must have an owner element set, or it
275    // won't ever get installed in the view hierarchy.
276    ASSERT(frame == frame->page()->mainFrame() || frame->ownerElement());
277
278    FrameView* view = frame->view();
279
280    view->setPlatformWidget(_private->frameScrollView);
281
282    // FIXME: Frame tries to do this too. Is this code needed?
283    if (RenderPart* owner = frame->ownerRenderer()) {
284        owner->setWidget(view);
285        // Now the render part owns the view, so we don't any more.
286    }
287
288    view->updateCanHaveScrollbars();
289}
290
291@end
292
293@implementation WebFrameView
294
295- (id)initWithFrame:(NSRect)frame
296{
297    self = [super initWithFrame:frame];
298    if (!self)
299        return nil;
300
301    static bool didFirstTimeInitialization;
302    if (!didFirstTimeInitialization) {
303        didFirstTimeInitialization = true;
304        InitWebCoreSystemInterface();
305
306        // Need to tell WebCore what function to call for the "History Item has Changed" notification.
307        // Note: We also do this in WebHistoryItem's init method.
308        WebCore::notifyHistoryItemChanged = WKNotifyHistoryItemChanged;
309
310        [WebViewFactory createSharedFactory];
311
312// FIXME: Remove the NSAppKitVersionNumberWithDeferredWindowDisplaySupport check once
313// once AppKit's Deferred Window Display support is available.
314#if defined(BUILDING_ON_TIGER) || defined(BUILDING_ON_LEOPARD) || !defined(NSAppKitVersionNumberWithDeferredWindowDisplaySupport)
315        // CoreGraphics deferred updates are disabled if WebKitEnableCoalescedUpdatesPreferenceKey is NO
316        // or has no value. For compatibility with Mac OS X 10.5 and lower, deferred updates are off by default.
317        if (![[NSUserDefaults standardUserDefaults] boolForKey:WebKitEnableDeferredUpdatesPreferenceKey])
318            WKDisableCGDeferredUpdates();
319#endif
320        if (!WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITH_MAIN_THREAD_EXCEPTIONS))
321            setDefaultThreadViolationBehavior(LogOnFirstThreadViolation, ThreadViolationRoundOne);
322
323        bool throwExceptionsForRoundTwo = WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITH_ROUND_TWO_MAIN_THREAD_EXCEPTIONS);
324#ifdef MAIL_THREAD_WORKAROUND
325        // Even if old Mail is linked with new WebKit, don't throw exceptions.
326        if ([WebResource _needMailThreadWorkaroundIfCalledOffMainThread])
327            throwExceptionsForRoundTwo = false;
328#endif
329        if (!throwExceptionsForRoundTwo)
330            setDefaultThreadViolationBehavior(LogOnFirstThreadViolation, ThreadViolationRoundTwo);
331    }
332
333    _private = [[WebFrameViewPrivate alloc] init];
334
335    WebDynamicScrollBarsView *scrollView = [[WebDynamicScrollBarsView alloc] initWithFrame:NSMakeRect(0.0f, 0.0f, frame.size.width, frame.size.height)];
336    _private->frameScrollView = scrollView;
337    [scrollView setContentView:[[[WebClipView alloc] initWithFrame:[scrollView bounds]] autorelease]];
338    [scrollView setDrawsBackground:NO];
339    [scrollView setHasVerticalScroller:NO];
340    [scrollView setHasHorizontalScroller:NO];
341    [scrollView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
342    [scrollView setLineScroll:Scrollbar::pixelsPerLineStep()];
343    [self addSubview:scrollView];
344
345    // Don't call our overridden version of setNextKeyView here; we need to make the standard NSView
346    // link between us and our subview so that previousKeyView and previousValidKeyView work as expected.
347    // This works together with our becomeFirstResponder and setNextKeyView overrides.
348    [super setNextKeyView:scrollView];
349
350    return self;
351}
352
353- (void)dealloc
354{
355    if (_private && _private->includedInWebKitStatistics)
356        --WebFrameViewCount;
357
358    [_private release];
359    _private = nil;
360
361    [super dealloc];
362}
363
364- (void)finalize
365{
366    if (_private && _private->includedInWebKitStatistics)
367        --WebFrameViewCount;
368
369    [super finalize];
370}
371
372- (WebFrame *)webFrame
373{
374    // This method can be called beneath -[NSView dealloc] after _private has been cleared.
375    return _private ? _private->webFrame : nil;
376}
377
378- (void)setAllowsScrolling:(BOOL)flag
379{
380    WebCore::Frame *frame = core([self webFrame]);
381    if (WebCore::FrameView *view = frame? frame->view() : 0)
382        view->setCanHaveScrollbars(flag);
383}
384
385- (BOOL)allowsScrolling
386{
387    WebCore::Frame *frame = core([self webFrame]);
388    if (WebCore::FrameView *view = frame? frame->view() : 0)
389        return view->canHaveScrollbars();
390    return YES;
391}
392
393- (NSView <WebDocumentView> *)documentView
394{
395    return [[self _scrollView] documentView];
396}
397
398- (BOOL)acceptsFirstResponder
399{
400    // We always accept first responder; this matches OS X 10.2 WebKit
401    // behavior (see 3469791).
402    return YES;
403}
404
405- (BOOL)becomeFirstResponder
406{
407    // This works together with setNextKeyView to splice the WebFrameView into
408    // the key loop similar to the way NSScrollView does this. Note that
409    // WebView has similar code.
410
411    NSWindow *window = [self window];
412    if ([window keyViewSelectionDirection] == NSSelectingPrevious) {
413        NSView *previousValidKeyView = [self previousValidKeyView];
414        // If we couldn't find a previous valid key view, ask the WebView. This handles frameset
415        // cases (one is mentioned in Radar bug 3748628). Note that previousValidKeyView should
416        // never be self but can be due to AppKit oddness (mentioned in Radar bug 3748628).
417        if (previousValidKeyView == nil || previousValidKeyView == self)
418            previousValidKeyView = [[[self webFrame] webView] previousValidKeyView];
419        [window makeFirstResponder:previousValidKeyView];
420    } else {
421        // If the scroll view won't accept first-responderness now, then just become
422        // the first responder ourself like a normal view. This lets us be the first
423        // responder in cases where no page has yet been loaded.
424        if ([[self _scrollView] acceptsFirstResponder])
425            [window makeFirstResponder:[self _scrollView]];
426    }
427
428    return YES;
429}
430
431- (void)setNextKeyView:(NSView *)aView
432{
433    // This works together with becomeFirstResponder to splice the WebFrameView into
434    // the key loop similar to the way NSScrollView does this. Note that
435    // WebView has very similar code.
436    if ([self _scrollView] != nil) {
437        [[self _scrollView] setNextKeyView:aView];
438    } else {
439        [super setNextKeyView:aView];
440    }
441}
442
443- (BOOL)isOpaque
444{
445    return [[self _webView] drawsBackground];
446}
447
448- (void)drawRect:(NSRect)rect
449{
450    if ([self documentView] == nil) {
451        // Need to paint ourselves if there's no documentView to do it instead.
452        if ([[self _webView] drawsBackground]) {
453            [[[self _webView] backgroundColor] set];
454            NSRectFill(rect);
455        }
456    } else {
457#ifndef NDEBUG
458        if ([[self _scrollView] drawsBackground]) {
459            [[NSColor cyanColor] set];
460            NSRectFill(rect);
461        }
462#endif
463    }
464}
465
466- (NSRect)visibleRect
467{
468    // This method can be called beneath -[NSView dealloc] after we have cleared _private.
469    if (!_private)
470        return [super visibleRect];
471
472    // FIXME: <rdar://problem/6213380> This method does not work correctly with transforms, for two reasons:
473    // 1) [super visibleRect] does not account for the transform, since it is not represented
474    //    in the NSView hierarchy.
475    // 2) -_getVisibleRect: does not correct for transforms.
476
477    NSRect rendererVisibleRect;
478    if (![[self webFrame] _getVisibleRect:&rendererVisibleRect])
479        return [super visibleRect];
480
481    if (NSIsEmptyRect(rendererVisibleRect))
482        return NSZeroRect;
483
484    NSRect viewVisibleRect = [super visibleRect];
485    if (NSIsEmptyRect(viewVisibleRect))
486        return NSZeroRect;
487
488    NSRect frame = [self frame];
489    // rendererVisibleRect is in the parent's coordinate space, and frame is in the superview's coordinate space.
490    // The return value from this method needs to be in this view's coordinate space. We get that right by subtracting
491    // the origins (and correcting for flipping), but when we support transforms, we will need to do better than this.
492    rendererVisibleRect.origin.x -= frame.origin.x;
493    rendererVisibleRect.origin.y = NSMaxY(frame) - NSMaxY(rendererVisibleRect);
494    return NSIntersectionRect(rendererVisibleRect, viewVisibleRect);
495}
496
497- (void)setFrameSize:(NSSize)size
498{
499    if (!NSEqualSizes(size, [self frame].size)) {
500        // See WebFrameLoaderClient::provisionalLoadStarted.
501        if ([[[self webFrame] webView] drawsBackground])
502            [[self _scrollView] setDrawsBackground:YES];
503        if (Frame* coreFrame = [self _web_frame]) {
504            if (FrameView* coreFrameView = coreFrame->view())
505                coreFrameView->setNeedsLayout();
506        }
507    }
508    [super setFrameSize:size];
509}
510
511- (void)setBoundsSize:(NSSize)size
512{
513    [super setBoundsSize:size];
514    [[self _scrollView] setFrameSize:size];
515}
516
517- (void)viewDidMoveToWindow
518{
519    // See WebFrameLoaderClient::provisionalLoadStarted.
520    // Need to check _private for nil because this can be called inside -[WebView initWithCoder:].
521    if (_private && [[[self webFrame] webView] drawsBackground])
522        [[self _scrollView] setDrawsBackground:YES];
523    [super viewDidMoveToWindow];
524}
525
526- (BOOL)_scrollOverflowInDirection:(ScrollDirection)direction granularity:(ScrollGranularity)granularity
527{
528    // scrolling overflows is only applicable if we're dealing with an WebHTMLView
529    if (![[self documentView] isKindOfClass:[WebHTMLView class]])
530        return NO;
531    Frame* frame = core([self webFrame]);
532    if (!frame)
533        return NO;
534    return frame->eventHandler()->scrollOverflow(direction, granularity);
535}
536
537
538- (BOOL)_isVerticalDocument
539{
540    Frame* coreFrame = [self _web_frame];
541    if (!coreFrame)
542        return YES;
543    Document* document = coreFrame->document();
544    if (!document)
545        return YES;
546    RenderObject* renderView = document->renderer();
547    if (!renderView)
548        return YES;
549    return renderView->style()->isHorizontalWritingMode();
550}
551
552- (BOOL)_isFlippedDocument
553{
554    Frame* coreFrame = [self _web_frame];
555    if (!coreFrame)
556        return NO;
557    Document* document = coreFrame->document();
558    if (!document)
559        return NO;
560    RenderObject* renderView = document->renderer();
561    if (!renderView)
562        return NO;
563    return renderView->style()->isFlippedBlocksWritingMode();
564}
565
566- (BOOL)_scrollToBeginningOfDocument
567{
568    if ([self _scrollOverflowInDirection:ScrollUp granularity:ScrollByDocument])
569        return YES;
570    if (![self _isScrollable])
571        return NO;
572    NSPoint point = [[[self _scrollView] documentView] frame].origin;
573    point.x += [[self _scrollView] scrollOrigin].x;
574    point.y += [[self _scrollView] scrollOrigin].y;
575    return [[self _contentView] _scrollTo:&point animate:YES];
576}
577
578- (BOOL)_scrollToEndOfDocument
579{
580    if ([self _scrollOverflowInDirection:ScrollDown granularity:ScrollByDocument])
581        return YES;
582    if (![self _isScrollable])
583        return NO;
584    NSRect frame = [[[self _scrollView] documentView] frame];
585
586    bool isVertical = [self _isVerticalDocument];
587    bool isFlipped = [self _isFlippedDocument];
588
589    NSPoint point;
590    if (isVertical) {
591        if (!isFlipped)
592            point = NSMakePoint(frame.origin.x, NSMaxY(frame));
593        else
594            point = NSMakePoint(frame.origin.x, NSMinY(frame));
595    } else {
596        if (!isFlipped)
597            point = NSMakePoint(NSMaxX(frame), frame.origin.y);
598        else
599            point = NSMakePoint(NSMinX(frame), frame.origin.y);
600    }
601
602    // Reset the position opposite to the block progression direction.
603    if (isVertical)
604        point.x += [[self _scrollView] scrollOrigin].x;
605    else
606        point.y += [[self _scrollView] scrollOrigin].y;
607    return [[self _contentView] _scrollTo:&point animate:YES];
608}
609
610- (void)scrollToBeginningOfDocument:(id)sender
611{
612    if ([self _scrollToBeginningOfDocument])
613        return;
614
615    if (WebFrameView *child = [self _largestScrollableChild]) {
616        if ([child _scrollToBeginningOfDocument])
617            return;
618    }
619    [[self nextResponder] tryToPerform:@selector(scrollToBeginningOfDocument:) with:sender];
620}
621
622- (void)scrollToEndOfDocument:(id)sender
623{
624    if ([self _scrollToEndOfDocument])
625        return;
626
627    if (WebFrameView *child = [self _largestScrollableChild]) {
628        if ([child _scrollToEndOfDocument])
629            return;
630    }
631    [[self nextResponder] tryToPerform:@selector(scrollToEndOfDocument:) with:sender];
632}
633
634- (void)_goBack
635{
636    [[self _webView] goBack];
637}
638
639- (void)_goForward
640{
641    [[self _webView] goForward];
642}
643
644- (BOOL)_scrollVerticallyBy:(float)delta
645{
646    // This method uses the secret method _scrollTo on NSClipView.
647    // It does that because it needs to know definitively whether scrolling was
648    // done or not to help implement the "scroll parent if we are at the limit" feature.
649    // In the presence of smooth scrolling, there's no easy way to tell if the method
650    // did any scrolling or not with the public API.
651    NSPoint point = [[self _contentView] bounds].origin;
652    point.y += delta;
653    return [[self _contentView] _scrollTo:&point animate:YES];
654}
655
656- (BOOL)_scrollHorizontallyBy:(float)delta
657{
658    NSPoint point = [[self _contentView] bounds].origin;
659    point.x += delta;
660    return [[self _contentView] _scrollTo:&point animate:YES];
661}
662
663- (float)_horizontalKeyboardScrollDistance
664{
665    // Arrow keys scroll the same distance that clicking the scroll arrow does.
666    return [[self _scrollView] horizontalLineScroll];
667}
668
669- (float)_horizontalPageScrollDistance
670{
671    float width = [[self _contentView] bounds].size.width;
672    return max<float>(width * Scrollbar::minFractionToStepWhenPaging(), width - Scrollbar::maxOverlapBetweenPages());
673}
674
675- (BOOL)_pageVertically:(BOOL)up
676{
677    if ([self _scrollOverflowInDirection:up ? ScrollUp : ScrollDown granularity:ScrollByPage])
678        return YES;
679
680    if (![self _isScrollable])
681        return [[self _largestScrollableChild] _pageVertically:up];
682
683    float delta = [self _verticalPageScrollDistance];
684    return [self _scrollVerticallyBy:up ? -delta : delta];
685}
686
687- (BOOL)_pageHorizontally:(BOOL)left
688{
689    if ([self _scrollOverflowInDirection:left ? ScrollLeft : ScrollRight granularity:ScrollByPage])
690        return YES;
691
692    if (![self _isScrollable])
693        return [[self _largestScrollableChild] _pageHorizontally:left];
694
695    float delta = [self _horizontalPageScrollDistance];
696    return [self _scrollHorizontallyBy:left ? -delta : delta];
697}
698
699- (BOOL)_pageInBlockProgressionDirection:(BOOL)forward
700{
701    // Determine whether we're calling _pageVertically or _pageHorizontally.
702    BOOL isVerticalDocument = [self _isVerticalDocument];
703    BOOL isFlippedBlock = [self _isFlippedDocument];
704    if (isVerticalDocument)
705        return [self _pageVertically:isFlippedBlock ? !forward : forward];
706    return [self _pageHorizontally:isFlippedBlock ? !forward : forward];
707}
708
709- (BOOL)_scrollLineVertically:(BOOL)up
710{
711    if ([self _scrollOverflowInDirection:up ? ScrollUp : ScrollDown granularity:ScrollByLine])
712        return YES;
713
714    if (![self _isScrollable])
715        return [[self _largestScrollableChild] _scrollLineVertically:up];
716
717    float delta = [self _verticalKeyboardScrollDistance];
718    return [self _scrollVerticallyBy:up ? -delta : delta];
719}
720
721- (BOOL)_scrollLineHorizontally:(BOOL)left
722{
723    if ([self _scrollOverflowInDirection:left ? ScrollLeft : ScrollRight granularity:ScrollByLine])
724        return YES;
725
726    if (![self _isScrollable])
727        return [[self _largestScrollableChild] _scrollLineHorizontally:left];
728
729    float delta = [self _horizontalKeyboardScrollDistance];
730    return [self _scrollHorizontallyBy:left ? -delta : delta];
731}
732
733- (void)scrollPageUp:(id)sender
734{
735    if (![self _pageInBlockProgressionDirection:YES]) {
736        // If we were already at the top, tell the next responder to scroll if it can.
737        [[self nextResponder] tryToPerform:@selector(scrollPageUp:) with:sender];
738    }
739}
740
741- (void)scrollPageDown:(id)sender
742{
743    if (![self _pageInBlockProgressionDirection:NO]) {
744        // If we were already at the bottom, tell the next responder to scroll if it can.
745        [[self nextResponder] tryToPerform:@selector(scrollPageDown:) with:sender];
746    }
747}
748
749- (void)scrollLineUp:(id)sender
750{
751    if (![self _scrollLineVertically:YES])
752        [[self nextResponder] tryToPerform:@selector(scrollLineUp:) with:sender];
753}
754
755- (void)scrollLineDown:(id)sender
756{
757    if (![self _scrollLineVertically:NO])
758        [[self nextResponder] tryToPerform:@selector(scrollLineDown:) with:sender];
759}
760
761- (BOOL)_firstResponderIsFormControl
762{
763    NSResponder *firstResponder = [[self window] firstResponder];
764
765    // WebHTMLView is an NSControl subclass these days, but it's not a form control
766    if ([firstResponder isKindOfClass:[WebHTMLView class]]) {
767        return NO;
768    }
769    return [firstResponder isKindOfClass:[NSControl class]];
770}
771
772- (void)keyDown:(NSEvent *)event
773{
774    // Implement common browser behaviors for all kinds of content.
775
776    // FIXME: This is not a good time to execute commands for WebHTMLView. We should run these at the time commands sent by key bindings
777    // are executed for consistency.
778    // This doesn't work automatically because most of the keys handled here are translated into moveXXX commands, which are not handled
779    // by Editor when focus is not in editable content.
780
781    NSString *characters = [event characters];
782    int index, count;
783    BOOL callSuper = YES;
784    Frame* coreFrame = [self _web_frame];
785    BOOL maintainsBackForwardList = coreFrame && static_cast<BackForwardListImpl*>(coreFrame->page()->backForwardList())->enabled() ? YES : NO;
786
787    count = [characters length];
788    for (index = 0; index < count; ++index) {
789        switch ([characters characterAtIndex:index]) {
790            case NSDeleteCharacter:
791                if (!maintainsBackForwardList) {
792                    callSuper = YES;
793                    break;
794                }
795                // This odd behavior matches some existing browsers,
796                // including Windows IE
797                if ([event modifierFlags] & NSShiftKeyMask) {
798                    [self _goForward];
799                } else {
800                    [self _goBack];
801                }
802                callSuper = NO;
803                break;
804            case SpaceKey:
805                // Checking for a control will allow events to percolate
806                // correctly when the focus is on a form control and we
807                // are in full keyboard access mode.
808                if ((![self allowsScrolling] && ![self _largestScrollableChild]) || [self _firstResponderIsFormControl]) {
809                    callSuper = YES;
810                    break;
811                }
812                if ([event modifierFlags] & NSShiftKeyMask) {
813                    [self scrollPageUp:nil];
814                } else {
815                    [self scrollPageDown:nil];
816                }
817                callSuper = NO;
818                break;
819            case NSPageUpFunctionKey:
820                if (![self allowsScrolling] && ![self _largestScrollableChild]) {
821                    callSuper = YES;
822                    break;
823                }
824                [self scrollPageUp:nil];
825                callSuper = NO;
826                break;
827            case NSPageDownFunctionKey:
828                if (![self allowsScrolling] && ![self _largestScrollableChild]) {
829                    callSuper = YES;
830                    break;
831                }
832                [self scrollPageDown:nil];
833                callSuper = NO;
834                break;
835            case NSHomeFunctionKey:
836                if (![self allowsScrolling] && ![self _largestScrollableChild]) {
837                    callSuper = YES;
838                    break;
839                }
840                [self scrollToBeginningOfDocument:nil];
841                callSuper = NO;
842                break;
843            case NSEndFunctionKey:
844                if (![self allowsScrolling] && ![self _largestScrollableChild]) {
845                    callSuper = YES;
846                    break;
847                }
848                [self scrollToEndOfDocument:nil];
849                callSuper = NO;
850                break;
851            case NSUpArrowFunctionKey:
852                // We don't handle shifted or control-arrow keys here, so let super have a chance.
853                if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) {
854                    callSuper = YES;
855                    break;
856                }
857                if ((![self allowsScrolling] && ![self _largestScrollableChild]) ||
858                    [[[self window] firstResponder] isKindOfClass:[NSPopUpButton class]]) {
859                    // Let arrow keys go through to pop up buttons
860                    // <rdar://problem/3455910>: hitting up or down arrows when focus is on a
861                    // pop-up menu should pop the menu
862                    callSuper = YES;
863                    break;
864                }
865                if ([event modifierFlags] & NSCommandKeyMask) {
866                    [self scrollToBeginningOfDocument:nil];
867                } else if ([event modifierFlags] & NSAlternateKeyMask) {
868                    [self scrollPageUp:nil];
869                } else {
870                    [self scrollLineUp:nil];
871                }
872                callSuper = NO;
873                break;
874            case NSDownArrowFunctionKey:
875                // We don't handle shifted or control-arrow keys here, so let super have a chance.
876                if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) {
877                    callSuper = YES;
878                    break;
879                }
880                if ((![self allowsScrolling] && ![self _largestScrollableChild]) ||
881                    [[[self window] firstResponder] isKindOfClass:[NSPopUpButton class]]) {
882                    // Let arrow keys go through to pop up buttons
883                    // <rdar://problem/3455910>: hitting up or down arrows when focus is on a
884                    // pop-up menu should pop the menu
885                    callSuper = YES;
886                    break;
887                }
888                if ([event modifierFlags] & NSCommandKeyMask) {
889                    [self scrollToEndOfDocument:nil];
890                } else if ([event modifierFlags] & NSAlternateKeyMask) {
891                    [self scrollPageDown:nil];
892                } else {
893                    [self scrollLineDown:nil];
894                }
895                callSuper = NO;
896                break;
897            case NSLeftArrowFunctionKey:
898                // We don't handle shifted or control-arrow keys here, so let super have a chance.
899                if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) {
900                    callSuper = YES;
901                    break;
902                }
903                // Check back/forward related keys.
904                if ([event modifierFlags] & NSCommandKeyMask) {
905                    if (!maintainsBackForwardList) {
906                        callSuper = YES;
907                        break;
908                    }
909                    [self _goBack];
910                } else {
911                    // Now check scrolling related keys.
912                    if ((![self allowsScrolling] && ![self _largestScrollableChild])) {
913                        callSuper = YES;
914                        break;
915                    }
916
917                    if ([event modifierFlags] & NSAlternateKeyMask) {
918                        [self _pageHorizontally:YES];
919                    } else {
920                        [self _scrollLineHorizontally:YES];
921                    }
922                }
923                callSuper = NO;
924                break;
925            case NSRightArrowFunctionKey:
926                // We don't handle shifted or control-arrow keys here, so let super have a chance.
927                if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) {
928                    callSuper = YES;
929                    break;
930                }
931                // Check back/forward related keys.
932                if ([event modifierFlags] & NSCommandKeyMask) {
933                    if (!maintainsBackForwardList) {
934                        callSuper = YES;
935                        break;
936                    }
937                    [self _goForward];
938                } else {
939                    // Now check scrolling related keys.
940                    if ((![self allowsScrolling] && ![self _largestScrollableChild])) {
941                        callSuper = YES;
942                        break;
943                    }
944
945                    if ([event modifierFlags] & NSAlternateKeyMask) {
946                        [self _pageHorizontally:NO];
947                    } else {
948                        [self _scrollLineHorizontally:NO];
949                    }
950                }
951                callSuper = NO;
952                break;
953        }
954    }
955
956    if (callSuper) {
957        [super keyDown:event];
958    } else {
959        // if we did something useful, get the cursor out of the way
960        [NSCursor setHiddenUntilMouseMoves:YES];
961    }
962}
963
964- (NSView *)_webcore_effectiveFirstResponder
965{
966    NSView *view = [self documentView];
967    return view ? [view _webcore_effectiveFirstResponder] : [super _webcore_effectiveFirstResponder];
968}
969
970- (BOOL)canPrintHeadersAndFooters
971{
972    NSView *documentView = [[self _scrollView] documentView];
973    if ([documentView respondsToSelector:@selector(canPrintHeadersAndFooters)]) {
974        return [(id)documentView canPrintHeadersAndFooters];
975    }
976    return NO;
977}
978
979- (NSPrintOperation *)printOperationWithPrintInfo:(NSPrintInfo *)printInfo
980{
981    NSView *documentView = [[self _scrollView] documentView];
982    if (!documentView) {
983        return nil;
984    }
985    if ([documentView respondsToSelector:@selector(printOperationWithPrintInfo:)]) {
986        return [(id)documentView printOperationWithPrintInfo:printInfo];
987    }
988    return [NSPrintOperation printOperationWithView:documentView printInfo:printInfo];
989}
990
991- (BOOL)documentViewShouldHandlePrint
992{
993    NSView *documentView = [[self _scrollView] documentView];
994    if (documentView && [documentView respondsToSelector:@selector(documentViewShouldHandlePrint)])
995        return [(id)documentView documentViewShouldHandlePrint];
996
997    return NO;
998}
999
1000- (void)printDocumentView
1001{
1002    NSView *documentView = [[self _scrollView] documentView];
1003    if (documentView && [documentView respondsToSelector:@selector(printDocumentView)])
1004        [(id)documentView printDocumentView];
1005}
1006
1007@end
1008
1009@implementation WebFrameView (WebPrivate)
1010
1011- (float)_area
1012{
1013    NSRect frame = [self frame];
1014    return frame.size.height * frame.size.width;
1015}
1016
1017- (BOOL)_isScrollable
1018{
1019    WebDynamicScrollBarsView *scrollView = [self _scrollView];
1020    return [scrollView horizontalScrollingAllowed] || [scrollView verticalScrollingAllowed];
1021}
1022
1023- (WebFrameView *)_largestScrollableChild
1024{
1025    WebFrameView *largest = nil;
1026    NSArray *frameChildren = [[self webFrame] childFrames];
1027
1028    unsigned i;
1029    for (i=0; i < [frameChildren count]; i++) {
1030        WebFrameView *childFrameView = [[frameChildren objectAtIndex:i] frameView];
1031        WebFrameView *scrollableFrameView = [childFrameView _isScrollable] ? childFrameView : [childFrameView _largestScrollableChild];
1032        if (!scrollableFrameView)
1033            continue;
1034
1035        // Some ads lurk in child frames of zero width and height, see radar 4406994. These don't count as scrollable.
1036        // Maybe someday we'll discover that this minimum area check should be larger, but this covers the known cases.
1037        float area = [scrollableFrameView _area];
1038        if (area < 1.0)
1039            continue;
1040
1041        if (!largest || (area > [largest _area])) {
1042            largest = scrollableFrameView;
1043        }
1044    }
1045
1046    return largest;
1047}
1048
1049- (BOOL)_hasScrollBars
1050{
1051    // FIXME: This method was used by Safari 4.0.x and older versions, but has not been used by any other WebKit
1052    // clients to my knowledge, and will not be used by future versions of Safari. It can probably be removed
1053    // once we no longer need to keep nightly WebKit builds working with Safari 4.0.x and earlier.
1054    NSScrollView *scrollView = [self _scrollView];
1055    return [scrollView hasHorizontalScroller] || [scrollView hasVerticalScroller];
1056}
1057
1058- (WebFrameView *)_largestChildWithScrollBars
1059{
1060    // FIXME: This method was used by Safari 4.0.x and older versions, but has not been used by any other WebKit
1061    // clients to my knowledge, and will not be used by future versions of Safari. It can probably be removed
1062    // once we no longer need to keep nightly WebKit builds working with Safari 4.0.x and earlier.
1063    WebFrameView *largest = nil;
1064    NSArray *frameChildren = [[self webFrame] childFrames];
1065
1066    unsigned i;
1067    for (i=0; i < [frameChildren count]; i++) {
1068        WebFrameView *childFrameView = [[frameChildren objectAtIndex:i] frameView];
1069        WebFrameView *scrollableFrameView = [childFrameView _hasScrollBars] ? childFrameView : [childFrameView _largestChildWithScrollBars];
1070        if (!scrollableFrameView)
1071            continue;
1072
1073        // Some ads lurk in child frames of zero width and height, see radar 4406994. These don't count as scrollable.
1074        // Maybe someday we'll discover that this minimum area check should be larger, but this covers the known cases.
1075        float area = [scrollableFrameView _area];
1076        if (area < 1.0)
1077            continue;
1078
1079        if (!largest || (area > [largest _area])) {
1080            largest = scrollableFrameView;
1081        }
1082    }
1083
1084    return largest;
1085}
1086
1087- (NSClipView *)_contentView
1088{
1089    return [[self _scrollView] contentView];
1090}
1091
1092- (Class)_customScrollViewClass
1093{
1094    if ([_private->frameScrollView class] == [WebDynamicScrollBarsView class])
1095        return nil;
1096    return [_private->frameScrollView class];
1097}
1098
1099- (void)_setCustomScrollViewClass:(Class)customClass
1100{
1101    if (!customClass)
1102        customClass = [WebDynamicScrollBarsView class];
1103    ASSERT([customClass isSubclassOfClass:[WebDynamicScrollBarsView class]]);
1104    if (customClass == [_private->frameScrollView class])
1105        return;
1106    if (![customClass isSubclassOfClass:[WebDynamicScrollBarsView class]])
1107        return;
1108
1109    WebDynamicScrollBarsView *oldScrollView = _private->frameScrollView; // already retained
1110    NSView <WebDocumentView> *documentView = [[self documentView] retain];
1111
1112    WebDynamicScrollBarsView *scrollView  = [[customClass alloc] initWithFrame:[oldScrollView frame]];
1113    [scrollView setContentView:[[[WebClipView alloc] initWithFrame:[scrollView bounds]] autorelease]];
1114    [scrollView setDrawsBackground:[oldScrollView drawsBackground]];
1115    [scrollView setHasVerticalScroller:[oldScrollView hasVerticalScroller]];
1116    [scrollView setHasHorizontalScroller:[oldScrollView hasHorizontalScroller]];
1117    [scrollView setAutoresizingMask:[oldScrollView autoresizingMask]];
1118    [scrollView setLineScroll:[oldScrollView lineScroll]];
1119    [self addSubview:scrollView];
1120
1121    // don't call our overridden version here; we need to make the standard NSView link between us
1122    // and our subview so that previousKeyView and previousValidKeyView work as expected. This works
1123    // together with our becomeFirstResponder and setNextKeyView overrides.
1124    [super setNextKeyView:scrollView];
1125
1126    _private->frameScrollView = scrollView;
1127
1128    [self _setDocumentView:documentView];
1129    [self _install];
1130
1131    [oldScrollView removeFromSuperview];
1132    [oldScrollView release];
1133    [documentView release];
1134}
1135
1136@end
1137