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