blob: 91eeaf670c1736c302e6138e01a21a514d17d8bc [file] [log] [blame]
/*
* Copyright (C) 2009, 2010, 2011 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#if ENABLE(FULLSCREEN_API)
#import "WKFullScreenWindowController.h"
#import "LayerTreeContext.h"
#import "WKAPICast.h"
#import "WKViewInternal.h"
#import "WebFullScreenManagerProxy.h"
#import "WebPageProxy.h"
#import <Carbon/Carbon.h> // For SetSystemUIMode()
#import <IOKit/pwr_mgt/IOPMLib.h> // For IOPMAssertionCreate()
#import <QuartzCore/QuartzCore.h>
#import <WebCore/FloatRect.h>
#import <WebCore/IntRect.h>
#import <WebKitSystemInterface.h>
static const NSTimeInterval tickleTimerInterval = 1.0;
using namespace WebKit;
using namespace WebCore;
#if defined(BUILDING_ON_LEOPARD)
@interface CATransaction(SnowLeopardConvenienceFunctions)
+ (void)setDisableActions:(BOOL)flag;
+ (void)setAnimationDuration:(CFTimeInterval)dur;
@end
@implementation CATransaction(SnowLeopardConvenienceFunctions)
+ (void)setDisableActions:(BOOL)flag
{
[self setValue:[NSNumber numberWithBool:flag] forKey:kCATransactionDisableActions];
}
+ (void)setAnimationDuration:(CFTimeInterval)dur
{
[self setValue:[NSNumber numberWithDouble:dur] forKey:kCATransactionAnimationDuration];
}
@end
#endif
@interface WKFullScreenWindow : NSWindow
{
NSView* _animationView;
CALayer* _backgroundLayer;
}
- (CALayer*)backgroundLayer;
- (NSView*)animationView;
@end
@interface WKFullScreenWindowController(Private)
- (void)_requestExitFullScreenWithAnimation:(BOOL)animation;
- (void)_updateMenuAndDockForFullScreen;
- (void)_updatePowerAssertions;
- (WKFullScreenWindow *)_fullScreenWindow;
- (CFTimeInterval)_animationDuration;
- (void)_swapView:(NSView*)view with:(NSView*)otherView;
- (WebFullScreenManagerProxy*)_manager;
@end
@interface NSWindow(IsOnActiveSpaceAdditionForTigerAndLeopard)
- (BOOL)isOnActiveSpace;
@end
@implementation WKFullScreenWindowController
#pragma mark -
#pragma mark Initialization
- (id)init
{
NSWindow *window = [[WKFullScreenWindow alloc] initWithContentRect:NSZeroRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
self = [super initWithWindow:window];
[window release];
if (!self)
return nil;
[self windowDidLoad];
return self;
}
- (void)dealloc
{
[self setWebView:nil];
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
- (void)windowDidLoad
{
[super windowDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidResignActive:) name:NSApplicationDidResignActiveNotification object:NSApp];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidChangeScreenParameters:) name:NSApplicationDidChangeScreenParametersNotification object:NSApp];
}
#pragma mark -
#pragma mark Accessors
- (WKView*)webView
{
return _webView;
}
- (void)setWebView:(WKView *)webView
{
[webView retain];
[_webView release];
_webView = webView;
}
#pragma mark -
#pragma mark Notifications
- (void)applicationDidResignActive:(NSNotification*)notification
{
// Check to see if the fullScreenWindow is on the active space; this function is available
// on 10.6 and later, so default to YES if the function is not available:
NSWindow* fullScreenWindow = [self _fullScreenWindow];
BOOL isOnActiveSpace = ([fullScreenWindow respondsToSelector:@selector(isOnActiveSpace)] ? [fullScreenWindow isOnActiveSpace] : YES);
// Replicate the QuickTime Player (X) behavior when losing active application status:
// Is the fullScreen screen the main screen? (Note: this covers the case where only a
// single screen is available.) Is the fullScreen screen on the current space? IFF so,
// then exit fullScreen mode.
if ([fullScreenWindow screen] == [[NSScreen screens] objectAtIndex:0] && isOnActiveSpace)
[self _requestExitFullScreenWithAnimation:NO];
}
- (void)applicationDidChangeScreenParameters:(NSNotification*)notification
{
// The user may have changed the main screen by moving the menu bar, or they may have changed
// the Dock's size or location, or they may have changed the fullScreen screen's dimensions.
// Update our presentation parameters, and ensure that the full screen window occupies the
// entire screen:
[self _updateMenuAndDockForFullScreen];
NSWindow* window = [self window];
[window setFrame:[[window screen] frame] display:YES];
}
#pragma mark -
#pragma mark Exposed Interface
- (void)enterFullScreen:(NSScreen *)screen
{
if (_isFullScreen)
return;
_isFullScreen = YES;
_isAnimating = YES;
NSDisableScreenUpdates();
if (!screen)
screen = [NSScreen mainScreen];
NSRect screenFrame = [screen frame];
NSRect webViewFrame = [_webView convertRectToBase:[_webView frame]];
webViewFrame.origin = [[_webView window] convertBaseToScreen:webViewFrame.origin];
// In the case of a multi-monitor setup where the webView straddles two
// monitors, we must create a window large enough to contain the destination
// frame and the initial frame.
NSRect windowFrame = NSUnionRect(screenFrame, webViewFrame);
[[self window] setFrame:windowFrame display:YES];
CALayer* backgroundLayer = [[self _fullScreenWindow] backgroundLayer];
NSRect backgroundFrame = {[[self window] convertScreenToBase:screenFrame.origin], screenFrame.size};
backgroundFrame = [[[self window] contentView] convertRectFromBase:backgroundFrame];
[CATransaction begin];
[CATransaction setDisableActions:YES];
[backgroundLayer setFrame:NSRectToCGRect(backgroundFrame)];
[CATransaction commit];
CFTimeInterval duration = [self _animationDuration];
[self _manager]->willEnterFullScreen();
[self _manager]->beginEnterFullScreenAnimation(duration);
}
- (void)beganEnterFullScreenAnimation
{
[self _updateMenuAndDockForFullScreen];
[self _updatePowerAssertions];
// In a previous incarnation, the NSWindow attached to this controller may have
// been on a different screen. Temporarily change the collectionBehavior of the window:
NSWindow* fullScreenWindow = [self window];
NSWindowCollectionBehavior behavior = [fullScreenWindow collectionBehavior];
[fullScreenWindow setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces];
[fullScreenWindow makeKeyAndOrderFront:self];
[fullScreenWindow setCollectionBehavior:behavior];
// Start the opacity animation. We can use implicit animations here because we don't care when
// the animation finishes.
[CATransaction begin];
[CATransaction setAnimationDuration:[self _animationDuration]];
[[[self _fullScreenWindow] backgroundLayer] setOpacity:1];
[CATransaction commit];
NSEnableScreenUpdates();
_isAnimating = YES;
}
- (void)finishedEnterFullScreenAnimation:(bool)completed
{
NSDisableScreenUpdates();
if (completed) {
// Swap the webView placeholder into place.
if (!_webViewPlaceholder)
_webViewPlaceholder.adoptNS([[NSView alloc] init]);
[self _swapView:_webView with:_webViewPlaceholder.get()];
// Then insert the WebView into the full screen window
NSView* animationView = [[self _fullScreenWindow] animationView];
[animationView addSubview:_webView positioned:NSWindowBelow relativeTo:_layerHostingView.get()];
[_webView setFrame:[animationView bounds]];
// FIXME: In Barolo, orderIn will animate, which is not what we want. Find a way
// to work around this behavior.
//[[_webViewPlaceholder.get() window] orderOut:self];
[[self window] makeKeyAndOrderFront:self];
}
[self _manager]->didEnterFullScreen();
NSEnableScreenUpdates();
_isAnimating = NO;
}
- (void)exitFullScreen
{
if (!_isFullScreen)
return;
_isFullScreen = NO;
_isAnimating = YES;
NSDisableScreenUpdates();
[self _manager]->willExitFullScreen();
[self _manager]->beginExitFullScreenAnimation([self _animationDuration]);
}
- (void)beganExitFullScreenAnimation
{
[self _updateMenuAndDockForFullScreen];
[self _updatePowerAssertions];
// The user may have moved the fullScreen window in Spaces, so temporarily change
// the collectionBehavior of the webView's window:
NSWindow* webWindow = [[self webView] window];
NSWindowCollectionBehavior behavior = [webWindow collectionBehavior];
[webWindow setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces];
[webWindow orderWindow:NSWindowBelow relativeTo:[[self window] windowNumber]];
[webWindow setCollectionBehavior:behavior];
// Swap the webView back into its original position:
if ([_webView window] == [self window])
[self _swapView:_webViewPlaceholder.get() with:_webView];
[CATransaction begin];
[CATransaction setAnimationDuration:[self _animationDuration]];
[[[self _fullScreenWindow] backgroundLayer] setOpacity:0];
[CATransaction commit];
NSEnableScreenUpdates();
_isAnimating = YES;
}
- (void)finishedExitFullScreenAnimation:(bool)completed
{
NSDisableScreenUpdates();
if (completed) {
[self _updateMenuAndDockForFullScreen];
[self _updatePowerAssertions];
[NSCursor setHiddenUntilMouseMoves:YES];
[[self window] orderOut:self];
[[_webView window] makeKeyAndOrderFront:self];
}
[self _manager]->didExitFullScreen();
NSEnableScreenUpdates();
_isAnimating = NO;
}
- (void)enterAcceleratedCompositingMode:(const WebKit::LayerTreeContext&)layerTreeContext
{
if (_layerHostingView)
return;
ASSERT(!layerTreeContext.isEmpty());
// Create an NSView that will host our layer tree.
_layerHostingView.adoptNS([[NSView alloc] initWithFrame:[[self window] frame]]);
[_layerHostingView.get() setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
[CATransaction begin];
[CATransaction setDisableActions:YES];
WKFullScreenWindow* window = [self _fullScreenWindow];
[[window animationView] addSubview:_layerHostingView.get()];
// Create a root layer that will back the NSView.
RetainPtr<CALayer> rootLayer(AdoptNS, [[CALayer alloc] init]);
#ifndef NDEBUG
[rootLayer.get() setName:@"Hosting root layer"];
#endif
CALayer *renderLayer = WKMakeRenderLayer(layerTreeContext.contextID);
[rootLayer.get() addSublayer:renderLayer];
[_layerHostingView.get() setLayer:rootLayer.get()];
[_layerHostingView.get() setWantsLayer:YES];
[[window backgroundLayer] setHidden:NO];
[CATransaction commit];
}
- (void)exitAcceleratedCompositingMode
{
if (!_layerHostingView)
return;
[CATransaction begin];
[CATransaction setDisableActions:YES];
[_layerHostingView.get() removeFromSuperview];
[_layerHostingView.get() setLayer:nil];
[_layerHostingView.get() setWantsLayer:NO];
[[[self _fullScreenWindow] backgroundLayer] setHidden:YES];
[CATransaction commit];
_layerHostingView = 0;
}
- (WebCore::IntRect)getFullScreenRect
{
return enclosingIntRect([[self window] frame]);
}
#pragma mark -
#pragma mark Internal Interface
- (void)_updateMenuAndDockForFullScreen
{
// NSApplicationPresentationOptions is available on > 10.6 only:
#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
NSApplicationPresentationOptions options = NSApplicationPresentationDefault;
NSScreen* fullScreenScreen = [[self window] screen];
if (_isFullScreen) {
// Auto-hide the menu bar if the fullScreenScreen contains the menu bar:
// NOTE: if the fullScreenScreen contains the menu bar but not the dock, we must still
// auto-hide the dock, or an exception will be thrown.
if ([[NSScreen screens] objectAtIndex:0] == fullScreenScreen)
options |= (NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationAutoHideDock);
// Check if the current screen contains the dock by comparing the screen's frame to its
// visibleFrame; if a dock is present, the visibleFrame will differ. If the current screen
// contains the dock, hide it.
else if (!NSEqualRects([fullScreenScreen frame], [fullScreenScreen visibleFrame]))
options |= NSApplicationPresentationAutoHideDock;
}
if ([NSApp respondsToSelector:@selector(setPresentationOptions:)])
[NSApp setPresentationOptions:options];
else
#endif
SetSystemUIMode(_isFullScreen ? kUIModeNormal : kUIModeAllHidden, 0);
}
#if !defined(BUILDING_ON_TIGER) // IOPMAssertionCreateWithName not defined on < 10.5
- (void)_disableIdleDisplaySleep
{
if (_idleDisplaySleepAssertion == kIOPMNullAssertionID)
#if defined(BUILDING_ON_LEOPARD) // IOPMAssertionCreateWithName is not defined in the 10.5 SDK
IOPMAssertionCreate(kIOPMAssertionTypeNoDisplaySleep, kIOPMAssertionLevelOn, &_idleDisplaySleepAssertion);
#else // IOPMAssertionCreate is depreciated in > 10.5
IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep, kIOPMAssertionLevelOn, CFSTR("WebKit playing a video fullScreen."), &_idleDisplaySleepAssertion);
#endif
}
- (void)_enableIdleDisplaySleep
{
if (_idleDisplaySleepAssertion != kIOPMNullAssertionID) {
IOPMAssertionRelease(_idleDisplaySleepAssertion);
_idleDisplaySleepAssertion = kIOPMNullAssertionID;
}
}
- (void)_disableIdleSystemSleep
{
if (_idleSystemSleepAssertion == kIOPMNullAssertionID)
#if defined(BUILDING_ON_LEOPARD) // IOPMAssertionCreateWithName is not defined in the 10.5 SDK
IOPMAssertionCreate(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, &_idleSystemSleepAssertion);
#else // IOPMAssertionCreate is depreciated in > 10.5
IOPMAssertionCreateWithName(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, CFSTR("WebKit playing a video fullScreen."), &_idleSystemSleepAssertion);
#endif
}
- (void)_enableIdleSystemSleep
{
if (_idleSystemSleepAssertion != kIOPMNullAssertionID) {
IOPMAssertionRelease(_idleSystemSleepAssertion);
_idleSystemSleepAssertion = kIOPMNullAssertionID;
}
}
- (void)_enableTickleTimer
{
[_tickleTimer invalidate];
[_tickleTimer release];
_tickleTimer = [[NSTimer scheduledTimerWithTimeInterval:tickleTimerInterval target:self selector:@selector(_tickleTimerFired) userInfo:nil repeats:YES] retain];
}
- (void)_disableTickleTimer
{
[_tickleTimer invalidate];
[_tickleTimer release];
_tickleTimer = nil;
}
- (void)_tickleTimerFired
{
UpdateSystemActivity(OverallAct);
}
#endif
- (void)_updatePowerAssertions
{
#if !defined(BUILDING_ON_TIGER)
if (_isPlaying && _isFullScreen) {
[self _disableIdleSystemSleep];
[self _disableIdleDisplaySleep];
[self _enableTickleTimer];
} else {
[self _enableIdleSystemSleep];
[self _enableIdleDisplaySleep];
[self _disableTickleTimer];
}
#endif
}
- (WebFullScreenManagerProxy*)_manager
{
WebPageProxy* webPage = toImpl([_webView pageRef]);
if (!webPage)
return 0;
return webPage->fullScreenManager();
}
- (void)_requestExit
{
[self exitFullScreen];
_forceDisableAnimation = NO;
}
- (void)_requestExitFullScreenWithAnimation:(BOOL)animation
{
_forceDisableAnimation = !animation;
[self performSelector:@selector(_requestExit) withObject:nil afterDelay:0];
}
- (void)_swapView:(NSView*)view with:(NSView*)otherView
{
[otherView setFrame:[view frame]];
[otherView setAutoresizingMask:[view autoresizingMask]];
[otherView removeFromSuperview];
[[view superview] replaceSubview:view with:otherView];
}
#pragma mark -
#pragma mark Utility Functions
- (WKFullScreenWindow *)_fullScreenWindow
{
ASSERT([[self window] isKindOfClass:[WKFullScreenWindow class]]);
return (WKFullScreenWindow *)[self window];
}
- (CFTimeInterval)_animationDuration
{
static const CFTimeInterval defaultDuration = 0.5;
CFTimeInterval duration = defaultDuration;
#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
NSUInteger modifierFlags = [NSEvent modifierFlags];
#else
NSUInteger modifierFlags = [[NSApp currentEvent] modifierFlags];
#endif
if ((modifierFlags & NSControlKeyMask) == NSControlKeyMask)
duration *= 2;
if ((modifierFlags & NSShiftKeyMask) == NSShiftKeyMask)
duration *= 10;
if (_forceDisableAnimation) {
// This will disable scale animation
duration = 0;
}
return duration;
}
@end
#pragma mark -
@implementation WKFullScreenWindow
- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag
{
UNUSED_PARAM(aStyle);
self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:bufferingType defer:flag];
if (!self)
return nil;
[self setOpaque:NO];
[self setBackgroundColor:[NSColor clearColor]];
[self setIgnoresMouseEvents:NO];
[self setAcceptsMouseMovedEvents:YES];
[self setReleasedWhenClosed:NO];
[self setHasShadow:YES];
#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
[self setMovable:NO];
#else
[self setMovableByWindowBackground:NO];
#endif
NSView* contentView = [self contentView];
_animationView = [[NSView alloc] initWithFrame:[contentView bounds]];
CALayer* contentLayer = [[CALayer alloc] init];
[_animationView setLayer:contentLayer];
[_animationView setWantsLayer:YES];
[_animationView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
[contentView addSubview:_animationView];
_backgroundLayer = [[CALayer alloc] init];
[contentLayer addSublayer:_backgroundLayer];
[_backgroundLayer setBackgroundColor:CGColorGetConstantColor(kCGColorBlack)];
[_backgroundLayer setOpacity:0];
return self;
}
- (void)dealloc
{
[_animationView release];
[_backgroundLayer release];
[super dealloc];
}
- (BOOL)canBecomeKeyWindow
{
return YES;
}
- (void)keyDown:(NSEvent *)theEvent
{
if ([[theEvent charactersIgnoringModifiers] isEqual:@"\e"]) // Esacpe key-code
[self cancelOperation:self];
else [super keyDown:theEvent];
}
- (void)cancelOperation:(id)sender
{
UNUSED_PARAM(sender);
[[self windowController] _requestExitFullScreenWithAnimation:YES];
}
- (CALayer*)backgroundLayer
{
return _backgroundLayer;
}
- (NSView*)animationView
{
return _animationView;
}
@end
#endif