diff options
Diffstat (limited to 'Source/WebKit2/UIProcess/mac/WKFullScreenWindowController.mm')
-rw-r--r-- | Source/WebKit2/UIProcess/mac/WKFullScreenWindowController.mm | 608 |
1 files changed, 608 insertions, 0 deletions
diff --git a/Source/WebKit2/UIProcess/mac/WKFullScreenWindowController.mm b/Source/WebKit2/UIProcess/mac/WKFullScreenWindowController.mm new file mode 100644 index 0000000..91eeaf6 --- /dev/null +++ b/Source/WebKit2/UIProcess/mac/WKFullScreenWindowController.mm @@ -0,0 +1,608 @@ +/* + * 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 |