Reviewed by John.
[WebKit-https.git] / WebKit / WebView / WebFrameView.mm
1 /*
2  * Copyright (C) 2005, 2006 Apple Computer, 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 "WebDataSource.h"
33 #import "WebDocument.h"
34 #import "WebDynamicScrollBarsView.h"
35 #import "WebFrame.h"
36 #import "WebFrameInternal.h"
37 #import "WebFrameBridge.h"
38 #import "WebFrameViewInternal.h"
39 #import "WebFrameViewPrivate.h"
40 #import "WebHistoryItemInternal.h"
41 #import "WebHTMLViewPrivate.h"
42 #import "WebKeyGenerator.h"
43 #import "WebKitErrorsPrivate.h"
44 #import "WebKitStatisticsPrivate.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 "WebSystemInterface.h"
53 #import "WebViewFactory.h"
54 #import "WebViewInternal.h"
55 #import "WebViewPrivate.h"
56 #import <Foundation/NSURLRequest.h>
57 #import <JavaScriptCore/Assertions.h>
58 #import <WebCore/DragController.h>
59 #import <WebCore/Frame.h>
60 #import <WebCore/FrameView.h>
61 #import <WebCore/HistoryItem.h>
62 #import <WebCore/Page.h>
63 #import <WebCore/WebCoreFrameView.h>
64 #import <WebCore/WebCoreView.h>
65 #import <WebKitSystemInterface.h>
66
67 @interface NSClipView (AppKitSecretsIKnow)
68 - (BOOL)_scrollTo:(const NSPoint *)newOrigin; // need the boolean result from this method
69 @end
70
71 enum {
72     SpaceKey = 0x0020
73 };
74
75 @interface WebFrameView (WebFrameViewFileInternal) <WebCoreBridgeHolder>
76 - (float)_verticalKeyboardScrollDistance;
77 - (WebCoreFrameBridge *) webCoreBridge;
78 @end
79
80 @interface WebFrameViewPrivate : NSObject
81 {
82 @public
83     WebFrame *webFrame;
84     WebDynamicScrollBarsView *frameScrollView;
85     
86     // These margin values are used to temporarily hold the margins of a frame until
87     // we have the appropriate document view type.
88     int marginWidth;
89     int marginHeight;
90 }
91 @end
92
93 @implementation WebFrameViewPrivate
94
95 - init
96 {
97     [super init];
98     
99     marginWidth = -1;
100     marginHeight = -1;
101     
102     return self;
103 }
104
105 - (void)dealloc
106 {
107     [frameScrollView release];
108     [super dealloc];
109 }
110
111 @end
112
113 @implementation WebFrameView (WebFrameViewFileInternal)
114
115 - (float)_verticalKeyboardScrollDistance
116 {
117     // Arrow keys scroll the same distance that clicking the scroll arrow does.
118     return [[self _scrollView] verticalLineScroll];
119 }
120
121 - (WebCoreFrameBridge *) webCoreBridge
122 {
123     return [_private->webFrame _bridge];
124 }
125
126 @end
127
128 @implementation WebFrameView (WebInternal)
129
130 // Note that the WebVew is not retained.
131 - (WebView *)_webView
132 {
133     return [_private->webFrame webView];
134 }
135
136 - (void)_setMarginWidth:(int)w
137 {
138     _private->marginWidth = w;
139 }
140
141 - (int)_marginWidth
142 {
143     return _private->marginWidth;
144 }
145
146 - (void)_setMarginHeight:(int)h
147 {
148     _private->marginHeight = h;
149 }
150
151 - (int)_marginHeight
152 {
153     return _private->marginHeight;
154 }
155
156 - (void)_setDocumentView:(NSView <WebDocumentView> *)view
157 {
158     WebDynamicScrollBarsView *sv = (WebDynamicScrollBarsView *)[self _scrollView];
159     core([self _webView])->dragController()->setDidInitiateDrag(false);
160     
161     [sv setSuppressLayout:YES];
162     
163     // Always start out with arrow.  New docView can then change as needed, but doesn't have to
164     // clean up after the previous docView.  Also TextView will set cursor when added to view
165     // tree, so must do this before setDocumentView:.
166     [sv setDocumentCursor:[NSCursor arrowCursor]];
167
168     [sv setDocumentView:view];
169     [sv setSuppressLayout:NO];
170 }
171
172 -(NSView <WebDocumentView> *)_makeDocumentViewForDataSource:(WebDataSource *)dataSource
173 {
174     NSString* MIMEType = [[dataSource response] MIMEType];
175     if (!MIMEType)
176         MIMEType = @"text/html";
177     Class viewClass = [[self class] _viewClassForMIMEType:MIMEType];
178     NSView <WebDocumentView> *documentView;
179     if (viewClass) {
180         // If the dataSource's representation has already been created, and it is also the
181         // same class as the desired documentView, then use it as the documentView instead
182         // of creating another one (Radar 4340787).
183         id <WebDocumentRepresentation> dataSourceRepresentation = [dataSource representation];
184         if (dataSourceRepresentation && [dataSourceRepresentation class] == viewClass)
185             documentView = (NSView <WebDocumentView> *)[dataSourceRepresentation retain];
186         else
187             documentView = [[viewClass alloc] initWithFrame:[self bounds]];
188     } else
189         documentView = nil;
190     
191     [self _setDocumentView:documentView];
192     [documentView release];
193     
194     return documentView;
195 }
196
197 - (void)_setWebFrame:(WebFrame *)webFrame
198 {
199     if (!webFrame) {
200         NSView *docV = [self documentView];
201         if ([docV respondsToSelector:@selector(close)])
202             [docV performSelector:@selector(close)];
203     }
204
205     // Not retained because the WebView owns the WebFrame, which owns the WebFrameView.
206     _private->webFrame = webFrame;    
207 }
208
209 - (NSScrollView *)_scrollView
210 {
211     // this can be called by [super dealloc] when cleaning up the keyview loop,
212     // after _private has been nilled out.
213     if (_private == nil) {
214         return nil;
215     }
216     return _private->frameScrollView;
217 }
218
219 - (float)_verticalPageScrollDistance
220 {
221     float overlap = [self _verticalKeyboardScrollDistance];
222     float height = [[self _contentView] bounds].size.height;
223     return (height < overlap) ? height / 2 : height - overlap;
224 }
225
226 static inline void addTypesFromClass(NSMutableDictionary *allTypes, Class objCClass, NSArray *supportTypes)
227 {
228     NSEnumerator *enumerator = [supportTypes objectEnumerator];
229     ASSERT(enumerator != nil);
230     NSString *mime = nil;
231     while ((mime = [enumerator nextObject]) != nil) {
232         // Don't clobber previously-registered classes.
233         if ([allTypes objectForKey:mime] == nil)
234             [allTypes setObject:objCClass forKey:mime];
235     }
236 }
237
238 + (NSMutableDictionary *)_viewTypesAllowImageTypeOmission:(BOOL)allowImageTypeOmission
239 {
240     static NSMutableDictionary *viewTypes = nil;
241     static BOOL addedImageTypes = NO;
242     
243     if (!viewTypes) {
244         viewTypes = [[NSMutableDictionary alloc] init];
245         addTypesFromClass(viewTypes, [WebHTMLView class], [WebHTMLView supportedNonImageMIMETypes]);
246
247         // Since this is a "secret default" we don't both registering it.
248         BOOL omitPDFSupport = [[NSUserDefaults standardUserDefaults] boolForKey:@"WebKitOmitPDFSupport"];
249         if (!omitPDFSupport)
250             addTypesFromClass(viewTypes, [WebPDFView class], [WebPDFView supportedMIMETypes]);
251     }
252     
253     if (!addedImageTypes && !allowImageTypeOmission) {
254         addTypesFromClass(viewTypes, [WebHTMLView class], [WebHTMLView supportedImageMIMETypes]);
255         addedImageTypes = YES;
256     }
257     
258     return viewTypes;
259 }
260
261 + (BOOL)_canShowMIMETypeAsHTML:(NSString *)MIMEType
262 {
263     return [[[self _viewTypesAllowImageTypeOmission:YES] _webkit_objectForMIMEType:MIMEType] isSubclassOfClass:[WebHTMLView class]];
264 }
265
266 + (Class)_viewClassForMIMEType:(NSString *)MIMEType
267 {
268     Class viewClass;
269     return [WebView _viewClass:&viewClass andRepresentationClass:nil forMIMEType:MIMEType] ? viewClass : nil;
270 }
271
272 @end
273
274 @implementation WebFrameView
275
276 - initWithFrame:(NSRect)frame
277 {
278     self = [super initWithFrame:frame];
279     if (!self)
280         return nil;
281  
282     static bool didFirstTimeInitialization;
283     if (!didFirstTimeInitialization) {
284         didFirstTimeInitialization = true;
285         InitWebCoreSystemInterface();
286         
287         // Need to tell WebCore what function to call for the 
288         // "History Item has Changed" notification
289         // Note: We also do this in WebHistoryItem's init method
290         WebCore::notifyHistoryItemChanged = WKNotifyHistoryItemChanged;
291
292         [WebViewFactory createSharedFactory];
293         [WebKeyGenerator createSharedGenerator];
294
295         NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
296         
297         // CoreGraphics deferred updates are disabled if WebKitEnableCoalescedUpdatesPreferenceKey is set
298         // to NO, or has no value.  For compatibility with Mac OS X 10.4.6, deferred updates are OFF by
299         // default.
300         if (![defaults boolForKey:WebKitEnableDeferredUpdatesPreferenceKey])
301             WKDisableCGDeferredUpdates();
302     }
303     
304     _private = [[WebFrameViewPrivate alloc] init];
305
306     WebDynamicScrollBarsView *scrollView  = [[WebDynamicScrollBarsView alloc] initWithFrame:NSMakeRect(0.0f, 0.0f, frame.size.width, frame.size.height)];
307     _private->frameScrollView = scrollView;
308     [scrollView setContentView:[[[WebClipView alloc] initWithFrame:[scrollView bounds]] autorelease]];
309     [scrollView setDrawsBackground:NO];
310     [scrollView setHasVerticalScroller:NO];
311     [scrollView setHasHorizontalScroller:NO];
312     [scrollView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
313     [scrollView setLineScroll:40.0f];
314     [self addSubview:scrollView];
315     // don't call our overridden version here; we need to make the standard NSView link between us
316     // and our subview so that previousKeyView and previousValidKeyView work as expected. This works
317     // together with our becomeFirstResponder and setNextKeyView overrides.
318     [super setNextKeyView:scrollView];
319     
320     ++WebFrameViewCount;
321     
322     return self;
323 }
324
325 - (void)dealloc 
326 {
327     --WebFrameViewCount;
328     
329     [_private release];
330     _private = nil;
331     
332     [super dealloc];
333 }
334
335 - (void)finalize 
336 {
337     --WebFrameViewCount;
338
339     [super finalize];
340 }
341
342 - (WebFrame *)webFrame
343 {
344     return _private->webFrame;
345 }
346
347 - (void)setAllowsScrolling:(BOOL)flag
348 {
349     WebDynamicScrollBarsView *scrollView = (WebDynamicScrollBarsView *)[self _scrollView];
350     [scrollView setAllowsScrolling:flag];
351     WebCore::Frame *frame = core([self webFrame]);
352     if (WebCore::FrameView *view = frame? frame->view() : 0) {
353         view->setHScrollbarMode((WebCore::ScrollbarMode)[scrollView horizontalScrollingMode]);
354         view->setVScrollbarMode((WebCore::ScrollbarMode)[scrollView verticalScrollingMode]);
355     }
356 }
357
358 - (BOOL)allowsScrolling
359 {
360     return [(WebDynamicScrollBarsView *)[self _scrollView] allowsScrolling];
361 }
362
363 - (NSView <WebDocumentView> *)documentView
364 {
365     return [[self _scrollView] documentView];
366 }
367
368 - (BOOL)acceptsFirstResponder
369 {
370     // We always accept first responder; this matches OS X 10.2 WebKit
371     // behavior (see 3469791).
372     return YES;
373 }
374
375 - (BOOL)becomeFirstResponder
376 {
377     // This works together with setNextKeyView to splice the WebFrameView into
378     // the key loop similar to the way NSScrollView does this. Note that
379     // WebView has similar code.
380     
381     // If the scrollView won't accept first-responderness now, then we just become
382     // the first responder ourself like a normal view. This lets us be the first 
383     // responder in cases where no page has yet been loaded (see 3469791).
384     if ([[self _scrollView] acceptsFirstResponder]) {
385         NSWindow *window = [self window];
386         if ([window keyViewSelectionDirection] == NSSelectingPrevious) {
387             NSView *previousValidKeyView = [self previousValidKeyView];
388             // If we couldn't find a previous valid key view, ask the webview. This handles frameset
389             // cases like 3748628. Note that previousValidKeyView should never be self but can be
390             // due to AppKit oddness (mentioned in 3748628).
391             if (previousValidKeyView == nil || previousValidKeyView == self) {
392                 previousValidKeyView = [[[self webFrame] webView] previousValidKeyView];
393             }
394             // I don't know if the following cases ever occur anymore, but I'm leaving in the old test for
395             // now to avoid causing trouble just before shipping Tiger.
396             ASSERT((previousValidKeyView != self) && (previousValidKeyView != [self _scrollView]));
397             if ((previousValidKeyView != self) && (previousValidKeyView != [self _scrollView])) {
398                 [window makeFirstResponder:previousValidKeyView];
399             }
400         } else {
401             [window makeFirstResponder:[self _scrollView]];
402         }
403     }    
404     
405     return YES;
406 }
407
408 - (void)setNextKeyView:(NSView *)aView
409 {
410     // This works together with becomeFirstResponder to splice the WebFrameView into
411     // the key loop similar to the way NSScrollView does this. Note that
412     // WebView has very similar code.
413     if ([self _scrollView] != nil) {
414         [[self _scrollView] setNextKeyView:aView];
415     } else {
416         [super setNextKeyView:aView];
417     }
418 }
419
420 - (BOOL)isOpaque
421 {
422     return [[self _webView] drawsBackground];
423 }
424
425 - (void)drawRect:(NSRect)rect
426 {
427     if ([self documentView] == nil) {
428         // Need to paint ourselves if there's no documentView to do it instead.
429         if ([[self _webView] drawsBackground]) {
430             [[[self _webView] backgroundColor] set];
431             NSRectFill(rect);
432         }
433     } else {
434 #ifndef NDEBUG
435         if ([[self _scrollView] drawsBackground]) {
436             [[NSColor cyanColor] set];
437             NSRectFill(rect);
438         }
439 #endif
440     }
441 }
442
443 - (void)setFrameSize:(NSSize)size
444 {
445     if (!NSEqualSizes(size, [self frame].size) && [[[self webFrame] webView] drawsBackground]) {
446         [[self _scrollView] setDrawsBackground:YES];
447     }
448     [super setFrameSize:size];
449 }
450
451 - (WebFrameBridge *)_bridge
452 {
453     return [[self webFrame] _bridge];
454 }
455
456 - (BOOL)_scrollOverflowInDirection:(WebScrollDirection)direction granularity:(WebScrollGranularity)granularity
457 {
458     // scrolling overflows is only applicable if we're dealing with an WebHTMLView
459     return ([[self documentView] isKindOfClass:[WebHTMLView class]] && [[self _bridge] scrollOverflowInDirection:direction granularity:granularity]);
460 }
461
462 - (void)scrollToBeginningOfDocument:(id)sender
463 {
464     if (![self _scrollOverflowInDirection:WebScrollUp granularity:WebScrollDocument]) {
465
466         if (![self _hasScrollBars]) {
467             [[self _largestChildWithScrollBars] scrollToBeginningOfDocument:sender];
468             return;
469         }
470
471         [[self _contentView] scrollPoint:[[[self _scrollView] documentView] frame].origin];
472     }
473 }
474
475 - (void)scrollToEndOfDocument:(id)sender
476 {
477     if (![self _scrollOverflowInDirection:WebScrollDown granularity:WebScrollDocument]) {
478
479         if (![self _hasScrollBars]) {
480             [[self _largestChildWithScrollBars] scrollToEndOfDocument:sender];
481             return;
482         }
483         
484         NSRect frame = [[[self _scrollView] documentView] frame];
485         [[self _contentView] scrollPoint:NSMakePoint(frame.origin.x, NSMaxY(frame))];
486     }
487 }
488
489 - (void)_goBack
490 {
491     [[self _webView] goBack];
492 }
493
494 - (void)_goForward
495 {
496     [[self _webView] goForward];
497 }
498
499 - (BOOL)_scrollVerticallyBy:(float)delta
500 {
501     // This method uses the secret method _scrollTo on NSClipView.
502     // It does that because it needs to know definitively whether scrolling was
503     // done or not to help implement the "scroll parent if we are at the limit" feature.
504     // In the presence of smooth scrolling, there's no easy way to tell if the method
505     // did any scrolling or not with the public API.
506     NSPoint point = [[self _contentView] bounds].origin;
507     point.y += delta;
508     return [[self _contentView] _scrollTo:&point];
509 }
510
511 - (BOOL)_scrollHorizontallyBy:(float)delta
512 {
513     NSPoint point = [[self _contentView] bounds].origin;
514     point.x += delta;
515     return [[self _contentView] _scrollTo:&point];
516 }
517
518 - (float)_horizontalKeyboardScrollDistance
519 {
520     // Arrow keys scroll the same distance that clicking the scroll arrow does.
521     return [[self _scrollView] horizontalLineScroll];
522 }
523
524 - (float)_horizontalPageScrollDistance
525 {
526     float overlap = [self _horizontalKeyboardScrollDistance];
527     float width = [[self _contentView] bounds].size.width;
528     return (width < overlap) ? width / 2 : width - overlap;
529 }
530
531 - (BOOL)_pageVertically:(BOOL)up
532 {
533     if ([self _scrollOverflowInDirection:up ? WebScrollUp : WebScrollDown granularity:WebScrollPage])
534         return YES;
535     
536     if (![self _hasScrollBars])
537         return [[self _largestChildWithScrollBars] _pageVertically:up];
538
539     float delta = [self _verticalPageScrollDistance];
540     return [self _scrollVerticallyBy:up ? -delta : delta];
541 }
542
543 - (BOOL)_pageHorizontally:(BOOL)left
544 {
545     if ([self _scrollOverflowInDirection:left ? WebScrollLeft : WebScrollRight granularity:WebScrollPage])
546         return YES;
547
548     if (![self _hasScrollBars])
549         return [[self _largestChildWithScrollBars] _pageHorizontally:left];
550     
551     float delta = [self _horizontalPageScrollDistance];
552     return [self _scrollHorizontallyBy:left ? -delta : delta];
553 }
554
555 - (BOOL)_scrollLineVertically:(BOOL)up
556 {
557     if ([self _scrollOverflowInDirection:up ? WebScrollUp : WebScrollDown granularity:WebScrollLine])
558         return YES;
559
560     if (![self _hasScrollBars])
561         return [[self _largestChildWithScrollBars] _scrollLineVertically:up];
562     
563     float delta = [self _verticalKeyboardScrollDistance];
564     return [self _scrollVerticallyBy:up ? -delta : delta];
565 }
566
567 - (BOOL)_scrollLineHorizontally:(BOOL)left
568 {
569     if ([self _scrollOverflowInDirection:left ? WebScrollLeft : WebScrollRight granularity:WebScrollLine])
570         return YES;
571
572     if (![self _hasScrollBars])
573         return [[self _largestChildWithScrollBars] _scrollLineHorizontally:left];
574
575     float delta = [self _horizontalKeyboardScrollDistance];
576     return [self _scrollHorizontallyBy:left ? -delta : delta];
577 }
578
579 - (void)scrollPageUp:(id)sender
580 {
581     if (![self _pageVertically:YES]) {
582         // If we were already at the top, tell the next responder to scroll if it can.
583         [[self nextResponder] tryToPerform:@selector(scrollPageUp:) with:sender];
584     }
585 }
586
587 - (void)scrollPageDown:(id)sender
588 {
589     if (![self _pageVertically:NO]) {
590         // If we were already at the bottom, tell the next responder to scroll if it can.
591         [[self nextResponder] tryToPerform:@selector(scrollPageDown:) with:sender];
592     }
593 }
594
595 - (void)scrollLineUp:(id)sender
596 {
597     [self _scrollLineVertically:YES];
598 }
599
600 - (void)scrollLineDown:(id)sender
601 {
602     [self _scrollLineVertically:NO];
603 }
604
605 - (BOOL)_firstResponderIsFormControl
606 {
607     NSResponder *firstResponder = [[self window] firstResponder];
608     
609     // WebHTMLView is an NSControl subclass these days, but it's not a form control
610     if ([firstResponder isKindOfClass:[WebHTMLView class]]) {
611         return NO;
612     }
613     return [firstResponder isKindOfClass:[NSControl class]];
614 }
615
616 - (void)keyDown:(NSEvent *)event
617 {
618     NSString *characters = [event characters];
619     int index, count;
620     BOOL callSuper = YES;
621     BOOL maintainsBackForwardList = core([self webFrame])->page()->backForwardList() ? YES : NO;
622     
623     count = [characters length];
624     for (index = 0; index < count; ++index) {
625         switch ([characters characterAtIndex:index]) {
626             case NSDeleteCharacter:
627                 if (!maintainsBackForwardList) {
628                     callSuper = YES;
629                     break;
630                 }
631                 // This odd behavior matches some existing browsers,
632                 // including Windows IE
633                 if ([event modifierFlags] & NSShiftKeyMask) {
634                     [self _goForward];
635                 } else {
636                     [self _goBack];
637                 }
638                 callSuper = NO;
639                 break;
640             case SpaceKey:
641                 // Checking for a control will allow events to percolate 
642                 // correctly when the focus is on a form control and we
643                 // are in full keyboard access mode.
644                 if ((![self allowsScrolling] && ![self _largestChildWithScrollBars]) || [self _firstResponderIsFormControl]) {
645                     callSuper = YES;
646                     break;
647                 }
648                 if ([event modifierFlags] & NSShiftKeyMask) {
649                     [self scrollPageUp:nil];
650                 } else {
651                     [self scrollPageDown:nil];
652                 }
653                 callSuper = NO;
654                 break;
655             case NSPageUpFunctionKey:
656                 if (![self allowsScrolling] && ![self _largestChildWithScrollBars]) {
657                     callSuper = YES;
658                     break;
659                 }
660                 [self scrollPageUp:nil];
661                 callSuper = NO;
662                 break;
663             case NSPageDownFunctionKey:
664                 if (![self allowsScrolling] && ![self _largestChildWithScrollBars]) {
665                     callSuper = YES;
666                     break;
667                 }
668                 [self scrollPageDown:nil];
669                 callSuper = NO;
670                 break;
671             case NSHomeFunctionKey:
672                 if (![self allowsScrolling] && ![self _largestChildWithScrollBars]) {
673                     callSuper = YES;
674                     break;
675                 }
676                 [self scrollToBeginningOfDocument:nil];
677                 callSuper = NO;
678                 break;
679             case NSEndFunctionKey:
680                 if (![self allowsScrolling] && ![self _largestChildWithScrollBars]) {
681                     callSuper = YES;
682                     break;
683                 }
684                 [self scrollToEndOfDocument:nil];
685                 callSuper = NO;
686                 break;
687             case NSUpArrowFunctionKey:
688                 // We don't handle shifted or control-arrow keys here, so let super have a chance.
689                 if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) {
690                     callSuper = YES;
691                     break;
692                 }
693                 if ((![self allowsScrolling] && ![self _largestChildWithScrollBars]) ||
694                     [[[self window] firstResponder] isKindOfClass:[NSPopUpButton class]]) {
695                     // Let arrow keys go through to pop up buttons
696                     // <rdar://problem/3455910>: hitting up or down arrows when focus is on a 
697                     // pop-up menu should pop the menu
698                     callSuper = YES;
699                     break;
700                 }
701                 if ([event modifierFlags] & NSCommandKeyMask) {
702                     [self scrollToBeginningOfDocument:nil];
703                 } else if ([event modifierFlags] & NSAlternateKeyMask) {
704                     [self scrollPageUp:nil];
705                 } else {
706                     [self scrollLineUp:nil];
707                 }
708                 callSuper = NO;
709                 break;
710             case NSDownArrowFunctionKey:
711                 // We don't handle shifted or control-arrow keys here, so let super have a chance.
712                 if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) {
713                     callSuper = YES;
714                     break;
715                 }
716                 if ((![self allowsScrolling] && ![self _largestChildWithScrollBars]) ||
717                     [[[self window] firstResponder] isKindOfClass:[NSPopUpButton class]]) {
718                     // Let arrow keys go through to pop up buttons
719                     // <rdar://problem/3455910>: hitting up or down arrows when focus is on a 
720                     // pop-up menu should pop the menu
721                     callSuper = YES;
722                     break;
723                 }
724                 if ([event modifierFlags] & NSCommandKeyMask) {
725                     [self scrollToEndOfDocument:nil];
726                 } else if ([event modifierFlags] & NSAlternateKeyMask) {
727                     [self scrollPageDown:nil];
728                 } else {
729                     [self scrollLineDown:nil];
730                 }
731                 callSuper = NO;
732                 break;
733             case NSLeftArrowFunctionKey:
734                 // We don't handle shifted or control-arrow keys here, so let super have a chance.
735                 if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) {
736                     callSuper = YES;
737                     break;
738                 }
739                 // Check back/forward related keys.
740                 if ([event modifierFlags] & NSCommandKeyMask) {
741                     if (!maintainsBackForwardList) {
742                         callSuper = YES;
743                         break;
744                     }
745                     [self _goBack];
746                 } else {
747                     // Now check scrolling related keys.
748                     if ((![self allowsScrolling] && ![self _largestChildWithScrollBars])) {
749                         callSuper = YES;
750                         break;
751                     }
752
753                     if ([event modifierFlags] & NSAlternateKeyMask) {
754                         [self _pageHorizontally:YES];
755                     } else {
756                         [self _scrollLineHorizontally:YES];
757                     }
758                 }
759                 callSuper = NO;
760                 break;
761             case NSRightArrowFunctionKey:
762                 // We don't handle shifted or control-arrow keys here, so let super have a chance.
763                 if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) {
764                     callSuper = YES;
765                     break;
766                 }
767                 // Check back/forward related keys.
768                 if ([event modifierFlags] & NSCommandKeyMask) {
769                     if (!maintainsBackForwardList) {
770                         callSuper = YES;
771                         break;
772                     }
773                     [self _goForward];
774                 } else {
775                     // Now check scrolling related keys.
776                     if ((![self allowsScrolling] && ![self _largestChildWithScrollBars])) {
777                         callSuper = YES;
778                         break;
779                     }
780
781                     if ([event modifierFlags] & NSAlternateKeyMask) {
782                         [self _pageHorizontally:NO];
783                     } else {
784                         [self _scrollLineHorizontally:NO];
785                     }
786                 }
787                 callSuper = NO;
788                 break;
789         }
790     }
791     
792     if (callSuper) {
793         [super keyDown:event];
794     } else {
795         // if we did something useful, get the cursor out of the way
796         [NSCursor setHiddenUntilMouseMoves:YES];
797     }
798 }
799
800 - (NSView *)_webcore_effectiveFirstResponder
801 {
802     NSView *view = [self documentView];
803     return view ? [view _webcore_effectiveFirstResponder] : [super _webcore_effectiveFirstResponder];
804 }
805
806 - (BOOL)canPrintHeadersAndFooters
807 {
808     NSView *documentView = [[self _scrollView] documentView];
809     if ([documentView respondsToSelector:@selector(canPrintHeadersAndFooters)]) {
810         return [(id)documentView canPrintHeadersAndFooters];
811     }
812     return NO;
813 }
814
815 - (NSPrintOperation *)printOperationWithPrintInfo:(NSPrintInfo *)printInfo
816 {
817     NSView *documentView = [[self _scrollView] documentView];
818     if (!documentView) {
819         return nil;
820     }
821     if ([documentView respondsToSelector:@selector(printOperationWithPrintInfo:)]) {
822         return [(id)documentView printOperationWithPrintInfo:printInfo];
823     }
824     return [NSPrintOperation printOperationWithView:documentView printInfo:printInfo];
825 }
826
827 - (BOOL)documentViewShouldHandlePrint
828 {
829     NSView *documentView = [[self _scrollView] documentView];
830     if (documentView && [documentView respondsToSelector:@selector(documentViewShouldHandlePrint)])
831         return [(id)documentView documentViewShouldHandlePrint];
832     
833     return NO;
834 }
835
836 - (void)printDocumentView
837 {
838     NSView *documentView = [[self _scrollView] documentView];
839     if (documentView && [documentView respondsToSelector:@selector(printDocumentView)])
840         [(id)documentView printDocumentView];
841 }
842
843 @end
844
845 @implementation WebFrameView (WebPrivate)
846
847 - (float)_area
848 {
849     NSRect frame = [self frame];
850     return frame.size.height * frame.size.width;
851 }
852
853 - (BOOL)_hasScrollBars
854 {
855     NSScrollView *scrollView = [self _scrollView];
856     return [scrollView hasHorizontalScroller] || [scrollView hasVerticalScroller];
857 }
858
859 - (WebFrameView *)_largestChildWithScrollBars
860 {
861     WebFrameView *largest = nil;
862     NSArray *frameChildren = [[self webFrame] childFrames];
863     
864     unsigned i;
865     for (i=0; i < [frameChildren count]; i++) {
866         WebFrameView *childFrameView = [[frameChildren objectAtIndex:i] frameView];
867         WebFrameView *scrollableFrameView = [childFrameView _hasScrollBars] ? childFrameView : [childFrameView _largestChildWithScrollBars];
868         if (!scrollableFrameView)
869             continue;
870         
871         // Some ads lurk in child frames of zero width and height, see radar 4406994. These don't count as scrollable.
872         // Maybe someday we'll discover that this minimum area check should be larger, but this covers the known cases.
873         float area = [scrollableFrameView _area];
874         if (area < 1.0)
875             continue;
876         
877         if (!largest || (area > [largest _area])) {
878             largest = scrollableFrameView;
879         }
880     }
881     
882     return largest;
883 }
884
885 - (NSClipView *)_contentView
886 {
887     return [[self _scrollView] contentView];
888 }
889
890 - (Class)_customScrollViewClass
891 {
892     if ([_private->frameScrollView class] == [WebDynamicScrollBarsView class])
893         return nil;
894     return [_private->frameScrollView class];
895 }
896
897 - (void)_setCustomScrollViewClass:(Class)customClass
898 {
899     if (!customClass)
900         customClass = [WebDynamicScrollBarsView class];
901     ASSERT([customClass isSubclassOfClass:[WebDynamicScrollBarsView class]]);
902     if (customClass == [_private->frameScrollView class])
903         return;
904     if ([customClass isSubclassOfClass:[WebDynamicScrollBarsView class]]) {
905         WebDynamicScrollBarsView *oldScrollView = _private->frameScrollView; // already retained
906         NSView <WebDocumentView> *documentView = [[self documentView] retain];
907
908         WebDynamicScrollBarsView *scrollView  = [[customClass alloc] initWithFrame:[oldScrollView frame]];
909         [scrollView setContentView:[[[WebClipView alloc] initWithFrame:[scrollView bounds]] autorelease]];
910         [scrollView setDrawsBackground:[oldScrollView drawsBackground]];
911         [scrollView setHasVerticalScroller:[oldScrollView hasVerticalScroller]];
912         [scrollView setHasHorizontalScroller:[oldScrollView hasHorizontalScroller]];
913         [scrollView setAutoresizingMask:[oldScrollView autoresizingMask]];
914         [scrollView setLineScroll:[oldScrollView lineScroll]];
915         [self addSubview:scrollView];
916
917         // don't call our overridden version here; we need to make the standard NSView link between us
918         // and our subview so that previousKeyView and previousValidKeyView work as expected. This works
919         // together with our becomeFirstResponder and setNextKeyView overrides.
920         [super setNextKeyView:scrollView];
921
922         _private->frameScrollView = scrollView;
923
924         [self _setDocumentView:documentView];
925         [[self _bridge] installInFrame:scrollView];
926
927         [oldScrollView removeFromSuperview];
928         [oldScrollView release];
929         [documentView release];
930     }
931 }
932
933 @end