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/BackForwardList.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}
96@end
97
98@implementation WebFrameViewPrivate
99
100- (void)dealloc
101{
102    [frameScrollView release];
103    [super dealloc];
104}
105
106@end
107
108@implementation WebFrameView (WebFrameViewFileInternal)
109
110- (float)_verticalKeyboardScrollDistance
111{
112    // Arrow keys scroll the same distance that clicking the scroll arrow does.
113    return [[self _scrollView] verticalLineScroll];
114}
115
116- (Frame*)_web_frame
117{
118    return core(_private->webFrame);
119}
120
121@end
122
123@implementation WebFrameView (WebInternal)
124
125// Note that the WebVew is not retained.
126- (WebView *)_webView
127{
128    return [_private->webFrame webView];
129}
130
131- (void)_setDocumentView:(NSView <WebDocumentView> *)view
132{
133    WebDynamicScrollBarsView *sv = [self _scrollView];
134    core([self _webView])->dragController()->setDidInitiateDrag(false);
135
136    [sv setSuppressLayout:YES];
137
138    // If the old view is the first responder, transfer first responder status to the new view as
139    // a convenience and so that we don't leave the window pointing to a view that's no longer in it.
140    NSWindow *window = [sv window];
141    NSResponder *firstResponder = [window firstResponder];
142    bool makeNewViewFirstResponder = [firstResponder isKindOfClass:[NSView class]] && [(NSView *)firstResponder isDescendantOf:[sv documentView]];
143
144    // Suppress the resetting of drag margins since we know we can't affect them.
145    BOOL resetDragMargins = [window _needsToResetDragMargins];
146    [window _setNeedsToResetDragMargins:NO];
147    [sv setDocumentView:view];
148    [window _setNeedsToResetDragMargins:resetDragMargins];
149
150    if (makeNewViewFirstResponder)
151        [window makeFirstResponder:view];
152    [sv setSuppressLayout:NO];
153}
154
155-(NSView <WebDocumentView> *)_makeDocumentViewForDataSource:(WebDataSource *)dataSource
156{
157    NSString* MIMEType = [dataSource _responseMIMEType];
158    if (!MIMEType)
159        MIMEType = @"text/html";
160    Class viewClass = [self _viewClassForMIMEType:MIMEType];
161    NSView <WebDocumentView> *documentView;
162    if (viewClass) {
163        // If the dataSource's representation has already been created, and it is also the
164        // same class as the desired documentView, then use it as the documentView instead
165        // of creating another one (Radar 4340787).
166        id <WebDocumentRepresentation> dataSourceRepresentation = [dataSource representation];
167        if (dataSourceRepresentation && [dataSourceRepresentation class] == viewClass)
168            documentView = (NSView <WebDocumentView> *)[dataSourceRepresentation retain];
169        else
170            documentView = [[viewClass alloc] initWithFrame:[self bounds]];
171    } else
172        documentView = nil;
173
174    [self _setDocumentView:documentView];
175    [documentView release];
176
177    return documentView;
178}
179
180- (void)_setWebFrame:(WebFrame *)webFrame
181{
182    if (!webFrame) {
183        NSView *docV = [self documentView];
184        if ([docV respondsToSelector:@selector(close)])
185            [docV performSelector:@selector(close)];
186    }
187
188    // Not retained because the WebView owns the WebFrame, which owns the WebFrameView.
189    _private->webFrame = webFrame;
190}
191
192- (WebDynamicScrollBarsView *)_scrollView
193{
194    // This can be called by [super dealloc] when cleaning up the key view loop,
195    // after _private has been nilled out.
196    if (_private == nil)
197        return nil;
198    return _private->frameScrollView;
199}
200
201- (float)_verticalPageScrollDistance
202{
203    float height = [[self _contentView] bounds].size.height;
204    return max<float>(height * Scrollbar::minFractionToStepWhenPaging(), height - Scrollbar::maxOverlapBetweenPages());
205}
206
207static inline void addTypesFromClass(NSMutableDictionary *allTypes, Class objCClass, NSArray *supportTypes)
208{
209    NSEnumerator *enumerator = [supportTypes objectEnumerator];
210    ASSERT(enumerator != nil);
211    NSString *mime = nil;
212    while ((mime = [enumerator nextObject]) != nil) {
213        // Don't clobber previously-registered classes.
214        if ([allTypes objectForKey:mime] == nil)
215            [allTypes setObject:objCClass forKey:mime];
216    }
217}
218
219+ (NSMutableDictionary *)_viewTypesAllowImageTypeOmission:(BOOL)allowImageTypeOmission
220{
221    static NSMutableDictionary *viewTypes = nil;
222    static BOOL addedImageTypes = NO;
223
224    if (!viewTypes) {
225        viewTypes = [[NSMutableDictionary alloc] init];
226        addTypesFromClass(viewTypes, [WebHTMLView class], [WebHTMLView supportedNonImageMIMETypes]);
227
228        // Since this is a "secret default" we don't bother registering it.
229        BOOL omitPDFSupport = [[NSUserDefaults standardUserDefaults] boolForKey:@"WebKitOmitPDFSupport"];
230        if (!omitPDFSupport)
231            addTypesFromClass(viewTypes, [WebPDFView class], [WebPDFView supportedMIMETypes]);
232    }
233
234    if (!addedImageTypes && !allowImageTypeOmission) {
235        addTypesFromClass(viewTypes, [WebHTMLView class], [WebHTMLView supportedImageMIMETypes]);
236        addedImageTypes = YES;
237    }
238
239    return viewTypes;
240}
241
242+ (BOOL)_canShowMIMETypeAsHTML:(NSString *)MIMEType
243{
244    return [[[self _viewTypesAllowImageTypeOmission:YES] _webkit_objectForMIMEType:MIMEType] isSubclassOfClass:[WebHTMLView class]];
245}
246
247+ (Class)_viewClassForMIMEType:(NSString *)MIMEType allowingPlugins:(BOOL)allowPlugins
248{
249    Class viewClass;
250    return [WebView _viewClass:&viewClass andRepresentationClass:nil forMIMEType:MIMEType allowingPlugins:allowPlugins] ? viewClass : nil;
251}
252
253- (Class)_viewClassForMIMEType:(NSString *)MIMEType
254{
255    return [[self class] _viewClassForMIMEType:MIMEType allowingPlugins:[[[self _webView] preferences] arePlugInsEnabled]];
256}
257
258- (void)_install
259{
260    ASSERT(_private->webFrame);
261    ASSERT(_private->frameScrollView);
262
263    Frame* frame = core(_private->webFrame);
264
265    ASSERT(frame);
266    ASSERT(frame->page());
267
268    // If this isn't the main frame, it must have an owner element set, or it
269    // won't ever get installed in the view hierarchy.
270    ASSERT(frame == frame->page()->mainFrame() || frame->ownerElement());
271
272    FrameView* view = frame->view();
273
274    view->setPlatformWidget(_private->frameScrollView);
275
276    // FIXME: Frame tries to do this too. Is this code needed?
277    if (RenderPart* owner = frame->ownerRenderer()) {
278        owner->setWidget(view);
279        // Now the render part owns the view, so we don't any more.
280    }
281
282    view->updateCanHaveScrollbars();
283}
284
285@end
286
287@implementation WebFrameView
288
289- initWithCoder:(NSCoder *)decoder
290{
291    // Older nibs containing WebViews will also contain WebFrameViews. We need to keep track of
292    // their count also to match the decrement in -dealloc.
293    ++WebFrameViewCount;
294    return [super initWithCoder:decoder];
295}
296
297- initWithFrame:(NSRect)frame
298{
299    self = [super initWithFrame:frame];
300    if (!self)
301        return nil;
302
303    static bool didFirstTimeInitialization;
304    if (!didFirstTimeInitialization) {
305        didFirstTimeInitialization = true;
306        InitWebCoreSystemInterface();
307
308        // Need to tell WebCore what function to call for the "History Item has Changed" notification.
309        // Note: We also do this in WebHistoryItem's init method.
310        WebCore::notifyHistoryItemChanged = WKNotifyHistoryItemChanged;
311
312        [WebViewFactory createSharedFactory];
313        [WebKeyGenerator createSharedGenerator];
314
315// FIXME: Remove the NSAppKitVersionNumberWithDeferredWindowDisplaySupport check once
316// once AppKit's Deferred Window Display support is available.
317#if defined(BUILDING_ON_TIGER) || defined(BUILDING_ON_LEOPARD) || !defined(NSAppKitVersionNumberWithDeferredWindowDisplaySupport)
318        // CoreGraphics deferred updates are disabled if WebKitEnableCoalescedUpdatesPreferenceKey is NO
319        // or has no value. For compatibility with Mac OS X 10.5 and lower, deferred updates are off by default.
320        if (![[NSUserDefaults standardUserDefaults] boolForKey:WebKitEnableDeferredUpdatesPreferenceKey])
321            WKDisableCGDeferredUpdates();
322#endif
323        if (!WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITH_MAIN_THREAD_EXCEPTIONS))
324            setDefaultThreadViolationBehavior(LogOnFirstThreadViolation, ThreadViolationRoundOne);
325
326        bool throwExceptionsForRoundTwo = WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITH_ROUND_TWO_MAIN_THREAD_EXCEPTIONS);
327#ifdef MAIL_THREAD_WORKAROUND
328        // Even if old Mail is linked with new WebKit, don't throw exceptions.
329        if ([WebResource _needMailThreadWorkaroundIfCalledOffMainThread])
330            throwExceptionsForRoundTwo = false;
331#endif
332        if (!throwExceptionsForRoundTwo)
333            setDefaultThreadViolationBehavior(LogOnFirstThreadViolation, ThreadViolationRoundTwo);
334    }
335
336    _private = [[WebFrameViewPrivate alloc] init];
337
338    WebDynamicScrollBarsView *scrollView = [[WebDynamicScrollBarsView alloc] initWithFrame:NSMakeRect(0.0f, 0.0f, frame.size.width, frame.size.height)];
339    _private->frameScrollView = scrollView;
340    [scrollView setContentView:[[[WebClipView alloc] initWithFrame:[scrollView bounds]] autorelease]];
341    [scrollView setDrawsBackground:NO];
342    [scrollView setHasVerticalScroller:NO];
343    [scrollView setHasHorizontalScroller:NO];
344    [scrollView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
345    [scrollView setLineScroll:Scrollbar::pixelsPerLineStep()];
346    [self addSubview:scrollView];
347
348    // Don't call our overridden version of setNextKeyView here; we need to make the standard NSView
349    // link between us and our subview so that previousKeyView and previousValidKeyView work as expected.
350    // This works together with our becomeFirstResponder and setNextKeyView overrides.
351    [super setNextKeyView:scrollView];
352
353    ++WebFrameViewCount;
354
355    return self;
356}
357
358- (void)dealloc
359{
360    --WebFrameViewCount;
361
362    [_private release];
363    _private = nil;
364
365    [super dealloc];
366}
367
368- (void)finalize
369{
370    --WebFrameViewCount;
371
372    [super finalize];
373}
374
375- (WebFrame *)webFrame
376{
377    return _private->webFrame;
378}
379
380- (void)setAllowsScrolling:(BOOL)flag
381{
382    WebCore::Frame *frame = core([self webFrame]);
383    if (WebCore::FrameView *view = frame? frame->view() : 0)
384        view->setCanHaveScrollbars(flag);
385}
386
387- (BOOL)allowsScrolling
388{
389    WebCore::Frame *frame = core([self webFrame]);
390    if (WebCore::FrameView *view = frame? frame->view() : 0)
391        return view->canHaveScrollbars();
392    return YES;
393}
394
395- (NSView <WebDocumentView> *)documentView
396{
397    return [[self _scrollView] documentView];
398}
399
400- (BOOL)acceptsFirstResponder
401{
402    // We always accept first responder; this matches OS X 10.2 WebKit
403    // behavior (see 3469791).
404    return YES;
405}
406
407- (BOOL)becomeFirstResponder
408{
409    // This works together with setNextKeyView to splice the WebFrameView into
410    // the key loop similar to the way NSScrollView does this. Note that
411    // WebView has similar code.
412
413    NSWindow *window = [self window];
414    if ([window keyViewSelectionDirection] == NSSelectingPrevious) {
415        NSView *previousValidKeyView = [self previousValidKeyView];
416        // If we couldn't find a previous valid key view, ask the WebView. This handles frameset
417        // cases (one is mentioned in Radar bug 3748628). Note that previousValidKeyView should
418        // never be self but can be due to AppKit oddness (mentioned in Radar bug 3748628).
419        if (previousValidKeyView == nil || previousValidKeyView == self)
420            previousValidKeyView = [[[self webFrame] webView] previousValidKeyView];
421        [window makeFirstResponder:previousValidKeyView];
422    } else {
423        // If the scroll view won't accept first-responderness now, then just become
424        // the first responder ourself like a normal view. This lets us be the first
425        // responder in cases where no page has yet been loaded.
426        if ([[self _scrollView] acceptsFirstResponder])
427            [window makeFirstResponder:[self _scrollView]];
428    }
429
430    return YES;
431}
432
433- (void)setNextKeyView:(NSView *)aView
434{
435    // This works together with becomeFirstResponder to splice the WebFrameView into
436    // the key loop similar to the way NSScrollView does this. Note that
437    // WebView has very similar code.
438    if ([self _scrollView] != nil) {
439        [[self _scrollView] setNextKeyView:aView];
440    } else {
441        [super setNextKeyView:aView];
442    }
443}
444
445- (BOOL)isOpaque
446{
447    return [[self _webView] drawsBackground];
448}
449
450- (void)drawRect:(NSRect)rect
451{
452    if ([self documentView] == nil) {
453        // Need to paint ourselves if there's no documentView to do it instead.
454        if ([[self _webView] drawsBackground]) {
455            [[[self _webView] backgroundColor] set];
456            NSRectFill(rect);
457        }
458    } else {
459#ifndef NDEBUG
460        if ([[self _scrollView] drawsBackground]) {
461            [[NSColor cyanColor] set];
462            NSRectFill(rect);
463        }
464#endif
465    }
466}
467
468- (NSRect)visibleRect
469{
470    // This method can be called beneath -[NSView dealloc] after we have cleared _private.
471    if (!_private)
472        return [super visibleRect];
473
474    // FIXME: <rdar://problem/6213380> This method does not work correctly with transforms, for two reasons:
475    // 1) [super visibleRect] does not account for the transform, since it is not represented
476    //    in the NSView hierarchy.
477    // 2) -_getVisibleRect: does not correct for transforms.
478
479    NSRect rendererVisibleRect;
480    if (![[self webFrame] _getVisibleRect:&rendererVisibleRect])
481        return [super visibleRect];
482
483    if (NSIsEmptyRect(rendererVisibleRect))
484        return NSZeroRect;
485
486    NSRect viewVisibleRect = [super visibleRect];
487    if (NSIsEmptyRect(viewVisibleRect))
488        return NSZeroRect;
489
490    NSRect frame = [self frame];
491    // rendererVisibleRect is in the parent's coordinate space, and frame is in the superview's coordinate space.
492    // The return value from this method needs to be in this view's coordinate space. We get that right by subtracting
493    // the origins (and correcting for flipping), but when we support transforms, we will need to do better than this.
494    rendererVisibleRect.origin.x -= frame.origin.x;
495    rendererVisibleRect.origin.y = NSMaxY(frame) - NSMaxY(rendererVisibleRect);
496    return NSIntersectionRect(rendererVisibleRect, viewVisibleRect);
497}
498
499- (void)setFrameSize:(NSSize)size
500{
501    if (!NSEqualSizes(size, [self frame].size)) {
502        // See WebFrameLoaderClient::provisionalLoadStarted.
503        if ([[[self webFrame] webView] drawsBackground])
504            [[self _scrollView] setDrawsBackground:YES];
505        if (Frame* coreFrame = [self _web_frame]) {
506            if (FrameView* coreFrameView = coreFrame->view())
507                coreFrameView->setNeedsLayout();
508        }
509    }
510    [super setFrameSize:size];
511}
512
513- (void)viewDidMoveToWindow
514{
515    // See WebFrameLoaderClient::provisionalLoadStarted.
516    // Need to check _private for nil because this can be called inside -[WebView initWithCoder:].
517    if (_private && [[[self webFrame] webView] drawsBackground])
518        [[self _scrollView] setDrawsBackground:YES];
519    [super viewDidMoveToWindow];
520}
521
522- (BOOL)_scrollOverflowInDirection:(ScrollDirection)direction granularity:(ScrollGranularity)granularity
523{
524    // scrolling overflows is only applicable if we're dealing with an WebHTMLView
525    if (![[self documentView] isKindOfClass:[WebHTMLView class]])
526        return NO;
527    Frame* frame = core([self webFrame]);
528    if (!frame)
529        return NO;
530    return frame->eventHandler()->scrollOverflow(direction, granularity);
531}
532
533- (BOOL)_scrollToBeginningOfDocument
534{
535    if ([self _scrollOverflowInDirection:ScrollUp granularity:ScrollByDocument])
536        return YES;
537    if (![self _hasScrollBars])
538        return NO;
539    NSPoint point = [[[self _scrollView] documentView] frame].origin;
540    return [[self _contentView] _scrollTo:&point animate:YES];
541}
542
543- (BOOL)_scrollToEndOfDocument
544{
545    if ([self _scrollOverflowInDirection:ScrollDown granularity:ScrollByDocument])
546        return YES;
547    if (![self _hasScrollBars])
548        return NO;
549    NSRect frame = [[[self _scrollView] documentView] frame];
550    NSPoint point = NSMakePoint(frame.origin.x, NSMaxY(frame));
551    return [[self _contentView] _scrollTo:&point animate:YES];
552}
553
554- (void)scrollToBeginningOfDocument:(id)sender
555{
556    if ([self _scrollToBeginningOfDocument])
557        return;
558
559    if (WebFrameView *child = [self _largestChildWithScrollBars]) {
560        if ([child _scrollToBeginningOfDocument])
561            return;
562    }
563    [[self nextResponder] tryToPerform:@selector(scrollToBeginningOfDocument:) with:sender];
564}
565
566- (void)scrollToEndOfDocument:(id)sender
567{
568    if ([self _scrollToEndOfDocument])
569        return;
570
571    if (WebFrameView *child = [self _largestChildWithScrollBars]) {
572        if ([child _scrollToEndOfDocument])
573            return;
574    }
575    [[self nextResponder] tryToPerform:@selector(scrollToEndOfDocument:) with:sender];
576}
577
578- (void)_goBack
579{
580    [[self _webView] goBack];
581}
582
583- (void)_goForward
584{
585    [[self _webView] goForward];
586}
587
588- (BOOL)_scrollVerticallyBy:(float)delta
589{
590    // This method uses the secret method _scrollTo on NSClipView.
591    // It does that because it needs to know definitively whether scrolling was
592    // done or not to help implement the "scroll parent if we are at the limit" feature.
593    // In the presence of smooth scrolling, there's no easy way to tell if the method
594    // did any scrolling or not with the public API.
595    NSPoint point = [[self _contentView] bounds].origin;
596    point.y += delta;
597    return [[self _contentView] _scrollTo:&point animate:YES];
598}
599
600- (BOOL)_scrollHorizontallyBy:(float)delta
601{
602    NSPoint point = [[self _contentView] bounds].origin;
603    point.x += delta;
604    return [[self _contentView] _scrollTo:&point animate:YES];
605}
606
607- (float)_horizontalKeyboardScrollDistance
608{
609    // Arrow keys scroll the same distance that clicking the scroll arrow does.
610    return [[self _scrollView] horizontalLineScroll];
611}
612
613- (float)_horizontalPageScrollDistance
614{
615    float width = [[self _contentView] bounds].size.width;
616    return max<float>(width * Scrollbar::minFractionToStepWhenPaging(), width - Scrollbar::maxOverlapBetweenPages());
617}
618
619- (BOOL)_pageVertically:(BOOL)up
620{
621    if ([self _scrollOverflowInDirection:up ? ScrollUp : ScrollDown granularity:ScrollByPage])
622        return YES;
623
624    if (![self _hasScrollBars])
625        return [[self _largestChildWithScrollBars] _pageVertically:up];
626
627    float delta = [self _verticalPageScrollDistance];
628    return [self _scrollVerticallyBy:up ? -delta : delta];
629}
630
631- (BOOL)_pageHorizontally:(BOOL)left
632{
633    if ([self _scrollOverflowInDirection:left ? ScrollLeft : ScrollRight granularity:ScrollByPage])
634        return YES;
635
636    if (![self _hasScrollBars])
637        return [[self _largestChildWithScrollBars] _pageHorizontally:left];
638
639    float delta = [self _horizontalPageScrollDistance];
640    return [self _scrollHorizontallyBy:left ? -delta : delta];
641}
642
643- (BOOL)_scrollLineVertically:(BOOL)up
644{
645    if ([self _scrollOverflowInDirection:up ? ScrollUp : ScrollDown granularity:ScrollByLine])
646        return YES;
647
648    if (![self _hasScrollBars])
649        return [[self _largestChildWithScrollBars] _scrollLineVertically:up];
650
651    float delta = [self _verticalKeyboardScrollDistance];
652    return [self _scrollVerticallyBy:up ? -delta : delta];
653}
654
655- (BOOL)_scrollLineHorizontally:(BOOL)left
656{
657    if ([self _scrollOverflowInDirection:left ? ScrollLeft : ScrollRight granularity:ScrollByLine])
658        return YES;
659
660    if (![self _hasScrollBars])
661        return [[self _largestChildWithScrollBars] _scrollLineHorizontally:left];
662
663    float delta = [self _horizontalKeyboardScrollDistance];
664    return [self _scrollHorizontallyBy:left ? -delta : delta];
665}
666
667- (void)scrollPageUp:(id)sender
668{
669    if (![self _pageVertically:YES]) {
670        // If we were already at the top, tell the next responder to scroll if it can.
671        [[self nextResponder] tryToPerform:@selector(scrollPageUp:) with:sender];
672    }
673}
674
675- (void)scrollPageDown:(id)sender
676{
677    if (![self _pageVertically:NO]) {
678        // If we were already at the bottom, tell the next responder to scroll if it can.
679        [[self nextResponder] tryToPerform:@selector(scrollPageDown:) with:sender];
680    }
681}
682
683- (void)scrollLineUp:(id)sender
684{
685    if (![self _scrollLineVertically:YES])
686        [[self nextResponder] tryToPerform:@selector(scrollLineUp:) with:sender];
687}
688
689- (void)scrollLineDown:(id)sender
690{
691    if (![self _scrollLineVertically:NO])
692        [[self nextResponder] tryToPerform:@selector(scrollLineDown:) with:sender];
693}
694
695- (BOOL)_firstResponderIsFormControl
696{
697    NSResponder *firstResponder = [[self window] firstResponder];
698
699    // WebHTMLView is an NSControl subclass these days, but it's not a form control
700    if ([firstResponder isKindOfClass:[WebHTMLView class]]) {
701        return NO;
702    }
703    return [firstResponder isKindOfClass:[NSControl class]];
704}
705
706- (void)keyDown:(NSEvent *)event
707{
708    NSString *characters = [event characters];
709    int index, count;
710    BOOL callSuper = YES;
711    Frame* coreFrame = [self _web_frame];
712    BOOL maintainsBackForwardList = coreFrame && coreFrame->page()->backForwardList()->enabled() ? YES : NO;
713
714    count = [characters length];
715    for (index = 0; index < count; ++index) {
716        switch ([characters characterAtIndex:index]) {
717            case NSDeleteCharacter:
718                if (!maintainsBackForwardList) {
719                    callSuper = YES;
720                    break;
721                }
722                // This odd behavior matches some existing browsers,
723                // including Windows IE
724                if ([event modifierFlags] & NSShiftKeyMask) {
725                    [self _goForward];
726                } else {
727                    [self _goBack];
728                }
729                callSuper = NO;
730                break;
731            case SpaceKey:
732                // Checking for a control will allow events to percolate
733                // correctly when the focus is on a form control and we
734                // are in full keyboard access mode.
735                if ((![self allowsScrolling] && ![self _largestChildWithScrollBars]) || [self _firstResponderIsFormControl]) {
736                    callSuper = YES;
737                    break;
738                }
739                if ([event modifierFlags] & NSShiftKeyMask) {
740                    [self scrollPageUp:nil];
741                } else {
742                    [self scrollPageDown:nil];
743                }
744                callSuper = NO;
745                break;
746            case NSPageUpFunctionKey:
747                if (![self allowsScrolling] && ![self _largestChildWithScrollBars]) {
748                    callSuper = YES;
749                    break;
750                }
751                [self scrollPageUp:nil];
752                callSuper = NO;
753                break;
754            case NSPageDownFunctionKey:
755                if (![self allowsScrolling] && ![self _largestChildWithScrollBars]) {
756                    callSuper = YES;
757                    break;
758                }
759                [self scrollPageDown:nil];
760                callSuper = NO;
761                break;
762            case NSHomeFunctionKey:
763                if (![self allowsScrolling] && ![self _largestChildWithScrollBars]) {
764                    callSuper = YES;
765                    break;
766                }
767                [self scrollToBeginningOfDocument:nil];
768                callSuper = NO;
769                break;
770            case NSEndFunctionKey:
771                if (![self allowsScrolling] && ![self _largestChildWithScrollBars]) {
772                    callSuper = YES;
773                    break;
774                }
775                [self scrollToEndOfDocument:nil];
776                callSuper = NO;
777                break;
778            case NSUpArrowFunctionKey:
779                // We don't handle shifted or control-arrow keys here, so let super have a chance.
780                if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) {
781                    callSuper = YES;
782                    break;
783                }
784                if ((![self allowsScrolling] && ![self _largestChildWithScrollBars]) ||
785                    [[[self window] firstResponder] isKindOfClass:[NSPopUpButton class]]) {
786                    // Let arrow keys go through to pop up buttons
787                    // <rdar://problem/3455910>: hitting up or down arrows when focus is on a
788                    // pop-up menu should pop the menu
789                    callSuper = YES;
790                    break;
791                }
792                if ([event modifierFlags] & NSCommandKeyMask) {
793                    [self scrollToBeginningOfDocument:nil];
794                } else if ([event modifierFlags] & NSAlternateKeyMask) {
795                    [self scrollPageUp:nil];
796                } else {
797                    [self scrollLineUp:nil];
798                }
799                callSuper = NO;
800                break;
801            case NSDownArrowFunctionKey:
802                // We don't handle shifted or control-arrow keys here, so let super have a chance.
803                if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) {
804                    callSuper = YES;
805                    break;
806                }
807                if ((![self allowsScrolling] && ![self _largestChildWithScrollBars]) ||
808                    [[[self window] firstResponder] isKindOfClass:[NSPopUpButton class]]) {
809                    // Let arrow keys go through to pop up buttons
810                    // <rdar://problem/3455910>: hitting up or down arrows when focus is on a
811                    // pop-up menu should pop the menu
812                    callSuper = YES;
813                    break;
814                }
815                if ([event modifierFlags] & NSCommandKeyMask) {
816                    [self scrollToEndOfDocument:nil];
817                } else if ([event modifierFlags] & NSAlternateKeyMask) {
818                    [self scrollPageDown:nil];
819                } else {
820                    [self scrollLineDown:nil];
821                }
822                callSuper = NO;
823                break;
824            case NSLeftArrowFunctionKey:
825                // We don't handle shifted or control-arrow keys here, so let super have a chance.
826                if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) {
827                    callSuper = YES;
828                    break;
829                }
830                // Check back/forward related keys.
831                if ([event modifierFlags] & NSCommandKeyMask) {
832                    if (!maintainsBackForwardList) {
833                        callSuper = YES;
834                        break;
835                    }
836                    [self _goBack];
837                } else {
838                    // Now check scrolling related keys.
839                    if ((![self allowsScrolling] && ![self _largestChildWithScrollBars])) {
840                        callSuper = YES;
841                        break;
842                    }
843
844                    if ([event modifierFlags] & NSAlternateKeyMask) {
845                        [self _pageHorizontally:YES];
846                    } else {
847                        [self _scrollLineHorizontally:YES];
848                    }
849                }
850                callSuper = NO;
851                break;
852            case NSRightArrowFunctionKey:
853                // We don't handle shifted or control-arrow keys here, so let super have a chance.
854                if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) {
855                    callSuper = YES;
856                    break;
857                }
858                // Check back/forward related keys.
859                if ([event modifierFlags] & NSCommandKeyMask) {
860                    if (!maintainsBackForwardList) {
861                        callSuper = YES;
862                        break;
863                    }
864                    [self _goForward];
865                } else {
866                    // Now check scrolling related keys.
867                    if ((![self allowsScrolling] && ![self _largestChildWithScrollBars])) {
868                        callSuper = YES;
869                        break;
870                    }
871
872                    if ([event modifierFlags] & NSAlternateKeyMask) {
873                        [self _pageHorizontally:NO];
874                    } else {
875                        [self _scrollLineHorizontally:NO];
876                    }
877                }
878                callSuper = NO;
879                break;
880        }
881    }
882
883    if (callSuper) {
884        [super keyDown:event];
885    } else {
886        // if we did something useful, get the cursor out of the way
887        [NSCursor setHiddenUntilMouseMoves:YES];
888    }
889}
890
891- (NSView *)_webcore_effectiveFirstResponder
892{
893    NSView *view = [self documentView];
894    return view ? [view _webcore_effectiveFirstResponder] : [super _webcore_effectiveFirstResponder];
895}
896
897- (BOOL)canPrintHeadersAndFooters
898{
899    NSView *documentView = [[self _scrollView] documentView];
900    if ([documentView respondsToSelector:@selector(canPrintHeadersAndFooters)]) {
901        return [(id)documentView canPrintHeadersAndFooters];
902    }
903    return NO;
904}
905
906- (NSPrintOperation *)printOperationWithPrintInfo:(NSPrintInfo *)printInfo
907{
908    NSView *documentView = [[self _scrollView] documentView];
909    if (!documentView) {
910        return nil;
911    }
912    if ([documentView respondsToSelector:@selector(printOperationWithPrintInfo:)]) {
913        return [(id)documentView printOperationWithPrintInfo:printInfo];
914    }
915    return [NSPrintOperation printOperationWithView:documentView printInfo:printInfo];
916}
917
918- (BOOL)documentViewShouldHandlePrint
919{
920    NSView *documentView = [[self _scrollView] documentView];
921    if (documentView && [documentView respondsToSelector:@selector(documentViewShouldHandlePrint)])
922        return [(id)documentView documentViewShouldHandlePrint];
923
924    return NO;
925}
926
927- (void)printDocumentView
928{
929    NSView *documentView = [[self _scrollView] documentView];
930    if (documentView && [documentView respondsToSelector:@selector(printDocumentView)])
931        [(id)documentView printDocumentView];
932}
933
934@end
935
936@implementation WebFrameView (WebPrivate)
937
938- (float)_area
939{
940    NSRect frame = [self frame];
941    return frame.size.height * frame.size.width;
942}
943
944- (BOOL)_hasScrollBars
945{
946    NSScrollView *scrollView = [self _scrollView];
947    return [scrollView hasHorizontalScroller] || [scrollView hasVerticalScroller];
948}
949
950- (WebFrameView *)_largestChildWithScrollBars
951{
952    WebFrameView *largest = nil;
953    NSArray *frameChildren = [[self webFrame] childFrames];
954
955    unsigned i;
956    for (i=0; i < [frameChildren count]; i++) {
957        WebFrameView *childFrameView = [[frameChildren objectAtIndex:i] frameView];
958        WebFrameView *scrollableFrameView = [childFrameView _hasScrollBars] ? childFrameView : [childFrameView _largestChildWithScrollBars];
959        if (!scrollableFrameView)
960            continue;
961
962        // Some ads lurk in child frames of zero width and height, see radar 4406994. These don't count as scrollable.
963        // Maybe someday we'll discover that this minimum area check should be larger, but this covers the known cases.
964        float area = [scrollableFrameView _area];
965        if (area < 1.0)
966            continue;
967
968        if (!largest || (area > [largest _area])) {
969            largest = scrollableFrameView;
970        }
971    }
972
973    return largest;
974}
975
976- (NSClipView *)_contentView
977{
978    return [[self _scrollView] contentView];
979}
980
981- (Class)_customScrollViewClass
982{
983    if ([_private->frameScrollView class] == [WebDynamicScrollBarsView class])
984        return nil;
985    return [_private->frameScrollView class];
986}
987
988- (void)_setCustomScrollViewClass:(Class)customClass
989{
990    if (!customClass)
991        customClass = [WebDynamicScrollBarsView class];
992    ASSERT([customClass isSubclassOfClass:[WebDynamicScrollBarsView class]]);
993    if (customClass == [_private->frameScrollView class])
994        return;
995    if (![customClass isSubclassOfClass:[WebDynamicScrollBarsView class]])
996        return;
997
998    WebDynamicScrollBarsView *oldScrollView = _private->frameScrollView; // already retained
999    NSView <WebDocumentView> *documentView = [[self documentView] retain];
1000
1001    WebDynamicScrollBarsView *scrollView  = [[customClass alloc] initWithFrame:[oldScrollView frame]];
1002    [scrollView setContentView:[[[WebClipView alloc] initWithFrame:[scrollView bounds]] autorelease]];
1003    [scrollView setDrawsBackground:[oldScrollView drawsBackground]];
1004    [scrollView setHasVerticalScroller:[oldScrollView hasVerticalScroller]];
1005    [scrollView setHasHorizontalScroller:[oldScrollView hasHorizontalScroller]];
1006    [scrollView setAutoresizingMask:[oldScrollView autoresizingMask]];
1007    [scrollView setLineScroll:[oldScrollView lineScroll]];
1008    [self addSubview:scrollView];
1009
1010    // don't call our overridden version here; we need to make the standard NSView link between us
1011    // and our subview so that previousKeyView and previousValidKeyView work as expected. This works
1012    // together with our becomeFirstResponder and setNextKeyView overrides.
1013    [super setNextKeyView:scrollView];
1014
1015    _private->frameScrollView = scrollView;
1016
1017    [self _setDocumentView:documentView];
1018    [self _install];
1019
1020    [oldScrollView removeFromSuperview];
1021    [oldScrollView release];
1022    [documentView release];
1023}
1024
1025@end
1026