WKFullScreenWindowController.mm revision 2bde8e466a4451c7319e3a072d118917957d6554
1/*
2 * Copyright (C) 2009, 2010, 2011 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 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#import "config.h"
27
28#if ENABLE(FULLSCREEN_API)
29
30#import "WKFullScreenWindowController.h"
31
32#import "LayerTreeContext.h"
33#import "WKAPICast.h"
34#import "WKViewInternal.h"
35#import "WebFullScreenManagerProxy.h"
36#import "WebPageProxy.h"
37#import <Carbon/Carbon.h> // For SetSystemUIMode()
38#import <IOKit/pwr_mgt/IOPMLib.h> // For IOPMAssertionCreate()
39#import <QuartzCore/QuartzCore.h>
40#import <WebCore/FloatRect.h>
41#import <WebCore/IntRect.h>
42#import <WebKitSystemInterface.h>
43
44static const NSTimeInterval tickleTimerInterval = 1.0;
45
46using namespace WebKit;
47using namespace WebCore;
48
49#if defined(BUILDING_ON_LEOPARD)
50@interface CATransaction(SnowLeopardConvenienceFunctions)
51+ (void)setDisableActions:(BOOL)flag;
52+ (void)setAnimationDuration:(CFTimeInterval)dur;
53@end
54
55@implementation CATransaction(SnowLeopardConvenienceFunctions)
56+ (void)setDisableActions:(BOOL)flag
57{
58    [self setValue:[NSNumber numberWithBool:flag] forKey:kCATransactionDisableActions];
59}
60
61+ (void)setAnimationDuration:(CFTimeInterval)dur
62{
63    [self setValue:[NSNumber numberWithDouble:dur] forKey:kCATransactionAnimationDuration];
64}
65@end
66
67#endif
68
69@interface WKFullScreenWindow : NSWindow
70{
71    NSView* _animationView;
72    CALayer* _backgroundLayer;
73}
74- (CALayer*)backgroundLayer;
75- (NSView*)animationView;
76@end
77
78@interface WKFullScreenWindowController(Private)
79- (void)_requestExitFullScreenWithAnimation:(BOOL)animation;
80- (void)_updateMenuAndDockForFullScreen;
81- (void)_updatePowerAssertions;
82- (WKFullScreenWindow *)_fullScreenWindow;
83- (CFTimeInterval)_animationDuration;
84- (void)_swapView:(NSView*)view with:(NSView*)otherView;
85- (WebFullScreenManagerProxy*)_manager;
86@end
87
88@interface NSWindow(IsOnActiveSpaceAdditionForTigerAndLeopard)
89- (BOOL)isOnActiveSpace;
90@end
91
92@implementation WKFullScreenWindowController
93
94#pragma mark -
95#pragma mark Initialization
96- (id)init
97{
98    NSWindow *window = [[WKFullScreenWindow alloc] initWithContentRect:NSZeroRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
99    self = [super initWithWindow:window];
100    [window release];
101    if (!self)
102        return nil;
103    [self windowDidLoad];
104
105    return self;
106}
107
108- (void)dealloc
109{
110    [self setWebView:nil];
111
112    [NSObject cancelPreviousPerformRequestsWithTarget:self];
113
114    [[NSNotificationCenter defaultCenter] removeObserver:self];
115    [super dealloc];
116}
117
118- (void)windowDidLoad
119{
120    [super windowDidLoad];
121
122    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidResignActive:) name:NSApplicationDidResignActiveNotification object:NSApp];
123    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidChangeScreenParameters:) name:NSApplicationDidChangeScreenParametersNotification object:NSApp];
124}
125
126#pragma mark -
127#pragma mark Accessors
128
129- (WKView*)webView
130{
131    return _webView;
132}
133
134- (void)setWebView:(WKView *)webView
135{
136    [webView retain];
137    [_webView release];
138    _webView = webView;
139}
140
141#pragma mark -
142#pragma mark Notifications
143
144- (void)applicationDidResignActive:(NSNotification*)notification
145{
146    // Check to see if the fullScreenWindow is on the active space; this function is available
147    // on 10.6 and later, so default to YES if the function is not available:
148    NSWindow* fullScreenWindow = [self _fullScreenWindow];
149    BOOL isOnActiveSpace = ([fullScreenWindow respondsToSelector:@selector(isOnActiveSpace)] ? [fullScreenWindow isOnActiveSpace] : YES);
150
151    // Replicate the QuickTime Player (X) behavior when losing active application status:
152    // Is the fullScreen screen the main screen? (Note: this covers the case where only a
153    // single screen is available.)  Is the fullScreen screen on the current space? IFF so,
154    // then exit fullScreen mode.
155    if ([fullScreenWindow screen] == [[NSScreen screens] objectAtIndex:0] && isOnActiveSpace)
156        [self _requestExitFullScreenWithAnimation:NO];
157}
158
159- (void)applicationDidChangeScreenParameters:(NSNotification*)notification
160{
161    // The user may have changed the main screen by moving the menu bar, or they may have changed
162    // the Dock's size or location, or they may have changed the fullScreen screen's dimensions.
163    // Update our presentation parameters, and ensure that the full screen window occupies the
164    // entire screen:
165    [self _updateMenuAndDockForFullScreen];
166    NSWindow* window = [self window];
167    [window setFrame:[[window screen] frame] display:YES];
168}
169
170#pragma mark -
171#pragma mark Exposed Interface
172
173- (void)enterFullScreen:(NSScreen *)screen
174{
175    if (_isFullScreen)
176        return;
177
178    _isFullScreen = YES;
179    _isAnimating = YES;
180
181    NSDisableScreenUpdates();
182
183    if (!screen)
184        screen = [NSScreen mainScreen];
185    NSRect screenFrame = [screen frame];
186
187    NSRect webViewFrame = [_webView convertRectToBase:[_webView frame]];
188    webViewFrame.origin = [[_webView window] convertBaseToScreen:webViewFrame.origin];
189
190    // In the case of a multi-monitor setup where the webView straddles two
191    // monitors, we must create a window large enough to contain the destination
192    // frame and the initial frame.
193    NSRect windowFrame = NSUnionRect(screenFrame, webViewFrame);
194    [[self window] setFrame:windowFrame display:YES];
195
196    CALayer* backgroundLayer = [[self _fullScreenWindow] backgroundLayer];
197    NSRect backgroundFrame = {[[self window] convertScreenToBase:screenFrame.origin], screenFrame.size};
198    backgroundFrame = [[[self window] contentView] convertRectFromBase:backgroundFrame];
199
200    [CATransaction begin];
201    [CATransaction setDisableActions:YES];
202    [backgroundLayer setFrame:NSRectToCGRect(backgroundFrame)];
203    [CATransaction commit];
204
205    CFTimeInterval duration = [self _animationDuration];
206    [self _manager]->willEnterFullScreen();
207    [self _manager]->beginEnterFullScreenAnimation(duration);
208}
209
210- (void)beganEnterFullScreenAnimation
211{
212    [self _updateMenuAndDockForFullScreen];
213    [self _updatePowerAssertions];
214
215    // In a previous incarnation, the NSWindow attached to this controller may have
216    // been on a different screen. Temporarily change the collectionBehavior of the window:
217    NSWindow* fullScreenWindow = [self window];
218    NSWindowCollectionBehavior behavior = [fullScreenWindow collectionBehavior];
219    [fullScreenWindow setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces];
220    [fullScreenWindow makeKeyAndOrderFront:self];
221    [fullScreenWindow setCollectionBehavior:behavior];
222
223    // Start the opacity animation. We can use implicit animations here because we don't care when
224    // the animation finishes.
225    [CATransaction begin];
226    [CATransaction setAnimationDuration:[self _animationDuration]];
227    [[[self _fullScreenWindow] backgroundLayer] setOpacity:1];
228    [CATransaction commit];
229
230    NSEnableScreenUpdates();
231    _isAnimating = YES;
232}
233
234- (void)finishedEnterFullScreenAnimation:(bool)completed
235{
236    NSDisableScreenUpdates();
237
238    if (completed) {
239        // Swap the webView placeholder into place.
240        if (!_webViewPlaceholder)
241            _webViewPlaceholder.adoptNS([[NSView alloc] init]);
242        [self _swapView:_webView with:_webViewPlaceholder.get()];
243
244        // Then insert the WebView into the full screen window
245        NSView* animationView = [[self _fullScreenWindow] animationView];
246        [animationView addSubview:_webView positioned:NSWindowBelow relativeTo:_layerHostingView.get()];
247        [_webView setFrame:[animationView bounds]];
248
249        // FIXME: In Barolo, orderIn will animate, which is not what we want.  Find a way
250        // to work around this behavior.
251        //[[_webViewPlaceholder.get() window] orderOut:self];
252        [[self window] makeKeyAndOrderFront:self];
253    }
254
255    [self _manager]->didEnterFullScreen();
256    NSEnableScreenUpdates();
257
258    _isAnimating = NO;
259}
260
261- (void)exitFullScreen
262{
263    if (!_isFullScreen)
264        return;
265
266    _isFullScreen = NO;
267    _isAnimating = YES;
268
269    NSDisableScreenUpdates();
270
271    [self _manager]->willExitFullScreen();
272    [self _manager]->beginExitFullScreenAnimation([self _animationDuration]);
273}
274
275- (void)beganExitFullScreenAnimation
276{
277    [self _updateMenuAndDockForFullScreen];
278    [self _updatePowerAssertions];
279
280    // The user may have moved the fullScreen window in Spaces, so temporarily change
281    // the collectionBehavior of the webView's window:
282    NSWindow* webWindow = [[self webView] window];
283    NSWindowCollectionBehavior behavior = [webWindow collectionBehavior];
284    [webWindow setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces];
285    [webWindow orderWindow:NSWindowBelow relativeTo:[[self window] windowNumber]];
286    [webWindow setCollectionBehavior:behavior];
287
288    // Swap the webView back into its original position:
289    if ([_webView window] == [self window])
290        [self _swapView:_webViewPlaceholder.get() with:_webView];
291
292    [CATransaction begin];
293    [CATransaction setAnimationDuration:[self _animationDuration]];
294    [[[self _fullScreenWindow] backgroundLayer] setOpacity:0];
295    [CATransaction commit];
296
297    NSEnableScreenUpdates();
298    _isAnimating = YES;
299}
300
301- (void)finishedExitFullScreenAnimation:(bool)completed
302{
303    NSDisableScreenUpdates();
304
305    if (completed) {
306        [self _updateMenuAndDockForFullScreen];
307        [self _updatePowerAssertions];
308        [NSCursor setHiddenUntilMouseMoves:YES];
309
310        [[self window] orderOut:self];
311        [[_webView window] makeKeyAndOrderFront:self];
312    }
313
314    [self _manager]->didExitFullScreen();
315    NSEnableScreenUpdates();
316
317    _isAnimating = NO;
318}
319
320- (void)enterAcceleratedCompositingMode:(const WebKit::LayerTreeContext&)layerTreeContext
321{
322    if (_layerHostingView)
323        return;
324
325    ASSERT(!layerTreeContext.isEmpty());
326
327    // Create an NSView that will host our layer tree.
328    _layerHostingView.adoptNS([[NSView alloc] initWithFrame:[[self window] frame]]);
329    [_layerHostingView.get() setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
330
331    [CATransaction begin];
332    [CATransaction setDisableActions:YES];
333    WKFullScreenWindow* window = [self _fullScreenWindow];
334    [[window animationView] addSubview:_layerHostingView.get()];
335
336    // Create a root layer that will back the NSView.
337    RetainPtr<CALayer> rootLayer(AdoptNS, [[CALayer alloc] init]);
338#ifndef NDEBUG
339    [rootLayer.get() setName:@"Hosting root layer"];
340#endif
341
342    CALayer *renderLayer = WKMakeRenderLayer(layerTreeContext.contextID);
343    [rootLayer.get() addSublayer:renderLayer];
344
345    [_layerHostingView.get() setLayer:rootLayer.get()];
346    [_layerHostingView.get() setWantsLayer:YES];
347    [[window backgroundLayer] setHidden:NO];
348    [CATransaction commit];
349}
350
351- (void)exitAcceleratedCompositingMode
352{
353    if (!_layerHostingView)
354        return;
355
356    [CATransaction begin];
357    [CATransaction setDisableActions:YES];
358    [_layerHostingView.get() removeFromSuperview];
359    [_layerHostingView.get() setLayer:nil];
360    [_layerHostingView.get() setWantsLayer:NO];
361    [[[self _fullScreenWindow] backgroundLayer] setHidden:YES];
362    [CATransaction commit];
363
364    _layerHostingView = 0;
365}
366
367- (WebCore::IntRect)getFullScreenRect
368{
369    return enclosingIntRect([[self window] frame]);
370}
371
372#pragma mark -
373#pragma mark Internal Interface
374
375- (void)_updateMenuAndDockForFullScreen
376{
377    // NSApplicationPresentationOptions is available on > 10.6 only:
378#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
379    NSApplicationPresentationOptions options = NSApplicationPresentationDefault;
380    NSScreen* fullScreenScreen = [[self window] screen];
381
382    if (_isFullScreen) {
383        // Auto-hide the menu bar if the fullScreenScreen contains the menu bar:
384        // NOTE: if the fullScreenScreen contains the menu bar but not the dock, we must still
385        // auto-hide the dock, or an exception will be thrown.
386        if ([[NSScreen screens] objectAtIndex:0] == fullScreenScreen)
387            options |= (NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationAutoHideDock);
388        // Check if the current screen contains the dock by comparing the screen's frame to its
389        // visibleFrame; if a dock is present, the visibleFrame will differ. If the current screen
390        // contains the dock, hide it.
391        else if (!NSEqualRects([fullScreenScreen frame], [fullScreenScreen visibleFrame]))
392            options |= NSApplicationPresentationAutoHideDock;
393    }
394
395    if ([NSApp respondsToSelector:@selector(setPresentationOptions:)])
396        [NSApp setPresentationOptions:options];
397    else
398#endif
399        SetSystemUIMode(_isFullScreen ? kUIModeNormal : kUIModeAllHidden, 0);
400}
401
402#if !defined(BUILDING_ON_TIGER) // IOPMAssertionCreateWithName not defined on < 10.5
403- (void)_disableIdleDisplaySleep
404{
405    if (_idleDisplaySleepAssertion == kIOPMNullAssertionID)
406#if defined(BUILDING_ON_LEOPARD) // IOPMAssertionCreateWithName is not defined in the 10.5 SDK
407        IOPMAssertionCreate(kIOPMAssertionTypeNoDisplaySleep, kIOPMAssertionLevelOn, &_idleDisplaySleepAssertion);
408#else // IOPMAssertionCreate is depreciated in > 10.5
409    IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep, kIOPMAssertionLevelOn, CFSTR("WebKit playing a video fullScreen."), &_idleDisplaySleepAssertion);
410#endif
411}
412
413- (void)_enableIdleDisplaySleep
414{
415    if (_idleDisplaySleepAssertion != kIOPMNullAssertionID) {
416        IOPMAssertionRelease(_idleDisplaySleepAssertion);
417        _idleDisplaySleepAssertion = kIOPMNullAssertionID;
418    }
419}
420
421- (void)_disableIdleSystemSleep
422{
423    if (_idleSystemSleepAssertion == kIOPMNullAssertionID)
424#if defined(BUILDING_ON_LEOPARD) // IOPMAssertionCreateWithName is not defined in the 10.5 SDK
425        IOPMAssertionCreate(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, &_idleSystemSleepAssertion);
426#else // IOPMAssertionCreate is depreciated in > 10.5
427    IOPMAssertionCreateWithName(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, CFSTR("WebKit playing a video fullScreen."), &_idleSystemSleepAssertion);
428#endif
429}
430
431- (void)_enableIdleSystemSleep
432{
433    if (_idleSystemSleepAssertion != kIOPMNullAssertionID) {
434        IOPMAssertionRelease(_idleSystemSleepAssertion);
435        _idleSystemSleepAssertion = kIOPMNullAssertionID;
436    }
437}
438
439- (void)_enableTickleTimer
440{
441    [_tickleTimer invalidate];
442    [_tickleTimer release];
443    _tickleTimer = [[NSTimer scheduledTimerWithTimeInterval:tickleTimerInterval target:self selector:@selector(_tickleTimerFired) userInfo:nil repeats:YES] retain];
444}
445
446- (void)_disableTickleTimer
447{
448    [_tickleTimer invalidate];
449    [_tickleTimer release];
450    _tickleTimer = nil;
451}
452
453- (void)_tickleTimerFired
454{
455    UpdateSystemActivity(OverallAct);
456}
457#endif
458
459- (void)_updatePowerAssertions
460{
461#if !defined(BUILDING_ON_TIGER)
462    if (_isPlaying && _isFullScreen) {
463        [self _disableIdleSystemSleep];
464        [self _disableIdleDisplaySleep];
465        [self _enableTickleTimer];
466    } else {
467        [self _enableIdleSystemSleep];
468        [self _enableIdleDisplaySleep];
469        [self _disableTickleTimer];
470    }
471#endif
472}
473
474- (WebFullScreenManagerProxy*)_manager
475{
476    WebPageProxy* webPage = toImpl([_webView pageRef]);
477    if (!webPage)
478        return 0;
479    return webPage->fullScreenManager();
480}
481
482- (void)_requestExit
483{
484    [self exitFullScreen];
485    _forceDisableAnimation = NO;
486}
487
488- (void)_requestExitFullScreenWithAnimation:(BOOL)animation
489{
490    _forceDisableAnimation = !animation;
491    [self performSelector:@selector(_requestExit) withObject:nil afterDelay:0];
492
493}
494
495- (void)_swapView:(NSView*)view with:(NSView*)otherView
496{
497    [otherView setFrame:[view frame]];
498    [otherView setAutoresizingMask:[view autoresizingMask]];
499    [otherView removeFromSuperview];
500    [[view superview] replaceSubview:view with:otherView];
501}
502
503#pragma mark -
504#pragma mark Utility Functions
505
506- (WKFullScreenWindow *)_fullScreenWindow
507{
508    ASSERT([[self window] isKindOfClass:[WKFullScreenWindow class]]);
509    return (WKFullScreenWindow *)[self window];
510}
511
512- (CFTimeInterval)_animationDuration
513{
514    static const CFTimeInterval defaultDuration = 0.5;
515    CFTimeInterval duration = defaultDuration;
516#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
517    NSUInteger modifierFlags = [NSEvent modifierFlags];
518#else
519    NSUInteger modifierFlags = [[NSApp currentEvent] modifierFlags];
520#endif
521    if ((modifierFlags & NSControlKeyMask) == NSControlKeyMask)
522        duration *= 2;
523    if ((modifierFlags & NSShiftKeyMask) == NSShiftKeyMask)
524        duration *= 10;
525    if (_forceDisableAnimation) {
526        // This will disable scale animation
527        duration = 0;
528    }
529    return duration;
530}
531
532@end
533
534#pragma mark -
535@implementation WKFullScreenWindow
536
537- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag
538{
539    UNUSED_PARAM(aStyle);
540    self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:bufferingType defer:flag];
541    if (!self)
542        return nil;
543    [self setOpaque:NO];
544    [self setBackgroundColor:[NSColor clearColor]];
545    [self setIgnoresMouseEvents:NO];
546    [self setAcceptsMouseMovedEvents:YES];
547    [self setReleasedWhenClosed:NO];
548    [self setHasShadow:YES];
549#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
550    [self setMovable:NO];
551#else
552    [self setMovableByWindowBackground:NO];
553#endif
554
555    NSView* contentView = [self contentView];
556    _animationView = [[NSView alloc] initWithFrame:[contentView bounds]];
557
558    CALayer* contentLayer = [[CALayer alloc] init];
559    [_animationView setLayer:contentLayer];
560    [_animationView setWantsLayer:YES];
561    [_animationView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
562    [contentView addSubview:_animationView];
563
564    _backgroundLayer = [[CALayer alloc] init];
565    [contentLayer addSublayer:_backgroundLayer];
566
567    [_backgroundLayer setBackgroundColor:CGColorGetConstantColor(kCGColorBlack)];
568    [_backgroundLayer setOpacity:0];
569    return self;
570}
571
572- (void)dealloc
573{
574    [_animationView release];
575    [_backgroundLayer release];
576    [super dealloc];
577}
578
579- (BOOL)canBecomeKeyWindow
580{
581    return YES;
582}
583
584- (void)keyDown:(NSEvent *)theEvent
585{
586    if ([[theEvent charactersIgnoringModifiers] isEqual:@"\e"]) // Esacpe key-code
587        [self cancelOperation:self];
588    else [super keyDown:theEvent];
589}
590
591- (void)cancelOperation:(id)sender
592{
593    UNUSED_PARAM(sender);
594    [[self windowController] _requestExitFullScreenWithAnimation:YES];
595}
596
597- (CALayer*)backgroundLayer
598{
599    return _backgroundLayer;
600}
601
602- (NSView*)animationView
603{
604    return _animationView;
605}
606@end
607
608#endif
609