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