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