blob: 3cc8355cb48e8393405a5d1a7de19a65b6e9f6c5 [file] [log] [blame]
/*
* Copyright (C) 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.
*/
#if ENABLE(FULLSCREEN_API)
#import "WebFullScreenController.h"
#import "WebPreferencesPrivate.h"
#import "WebWindowAnimation.h"
#import "WebViewInternal.h"
#import <IOKit/pwr_mgt/IOPMLib.h>
#import <OSServices/Power.h>
#import <WebCore/AnimationList.h>
#import <WebCore/CSSPropertyNames.h>
#import <WebCore/Color.h>
#import <WebCore/Document.h>
#import <WebCore/DOMDocument.h>
#import <WebCore/DOMDocumentInternal.h>
#import <WebCore/DOMHTMLElement.h>
#import <WebCore/DOMWindow.h>
#import <WebCore/EventListener.h>
#import <WebCore/EventNames.h>
#import <WebCore/HTMLElement.h>
#import <WebCore/HTMLNames.h>
#import <WebCore/HTMLMediaElement.h>
#import <WebCore/IntRect.h>
#import <WebCore/NodeList.h>
#import <WebCore/SoftLinking.h>
#import <WebCore/RenderBlock.h>
#import <WebCore/RenderLayer.h>
#import <WebCore/RenderLayerBacking.h>
#import <objc/objc-runtime.h>
#import <wtf/UnusedParam.h>
static const NSTimeInterval tickleTimerInterval = 1.0;
static NSString* const isEnteringFullscreenKey = @"isEnteringFullscreen";
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 WebFullscreenWindow : NSWindow
#if !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_TIGER)
<NSAnimationDelegate>
#endif
{
NSView* _animationView;
CALayer* _rendererLayer;
CALayer* _backgroundLayer;
}
- (CALayer*)rendererLayer;
- (void)setRendererLayer:(CALayer*)rendererLayer;
- (CALayer*)backgroundLayer;
- (NSView*)animationView;
@end
class MediaEventListener : public EventListener {
public:
static PassRefPtr<MediaEventListener> create(WebFullScreenController* delegate);
virtual bool operator==(const EventListener&);
virtual void handleEvent(ScriptExecutionContext*, Event*);
private:
MediaEventListener(WebFullScreenController* delegate);
WebFullScreenController* delegate;
};
@interface WebFullScreenController(Private)
- (void)_requestExitFullscreenWithAnimation:(BOOL)animation;
- (void)_updateMenuAndDockForFullscreen;
- (void)_updatePowerAssertions;
- (WebFullscreenWindow *)_fullscreenWindow;
- (Document*)_document;
- (CFTimeInterval)_animationDuration;
- (BOOL)_isAnyMoviePlaying;
@end
@interface NSWindow(IsOnActiveSpaceAdditionForTigerAndLeopard)
- (BOOL)isOnActiveSpace;
@end
@implementation WebFullScreenController
#pragma mark -
#pragma mark Initialization
- (id)init
{
// Do not defer window creation, to make sure -windowNumber is created (needed by WebWindowScaleAnimation).
NSWindow *window = [[WebFullscreenWindow alloc] initWithContentRect:NSZeroRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
self = [super initWithWindow:window];
[window release];
if (!self)
return nil;
[self windowDidLoad];
_mediaEventListener = MediaEventListener::create(self);
return self;
}
- (void)dealloc
{
ASSERT(!_tickleTimer);
[self setWebView:nil];
[_placeholderView release];
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
- (void)windowDidLoad
{
#ifdef BUILDING_ON_TIGER
// WebFullScreenController is not supported on Tiger:
ASSERT_NOT_REACHED();
#else
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidResignActive:) name:NSApplicationDidResignActiveNotification object:NSApp];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidChangeScreenParameters:) name:NSApplicationDidChangeScreenParametersNotification object:NSApp];
#endif
}
#pragma mark -
#pragma mark Accessors
- (WebView*)webView
{
return _webView;
}
- (void)setWebView:(WebView *)webView
{
[webView retain];
[_webView release];
_webView = webView;
}
- (Element*)element
{
return _element.get();
}
- (void)setElement:(PassRefPtr<Element>)element
{
#ifdef BUILDING_ON_TIGER
// WebFullScreenController is not supported on Tiger:
ASSERT_NOT_REACHED();
#else
// When a new Element is set as the current full screen element, register event
// listeners on that Element's window, listening for changes in media play states.
// We will use these events to determine whether to disable the screensaver and
// display sleep timers when playing video in full screen. Make sure to unregister
// the events on the old element's window, if necessary, as well.
EventNames& eventNames = WebCore::eventNames();
if (_element) {
DOMWindow* window = _element->document()->domWindow();
if (window) {
window->removeEventListener(eventNames.playEvent, _mediaEventListener.get(), true);
window->removeEventListener(eventNames.pauseEvent, _mediaEventListener.get(), true);
window->removeEventListener(eventNames.endedEvent, _mediaEventListener.get(), true);
}
}
_element = element;
if (_element) {
DOMWindow* window = _element->document()->domWindow();
if (window) {
window->addEventListener(eventNames.playEvent, _mediaEventListener, true);
window->addEventListener(eventNames.pauseEvent, _mediaEventListener, true);
window->addEventListener(eventNames.endedEvent, _mediaEventListener, true);
}
}
#endif
}
- (RenderBox*)renderer
{
return _renderer;
}
- (void)setRenderer:(RenderBox*)renderer
{
#ifdef BUILDING_ON_TIGER
// WebFullScreenController is not supported on Tiger:
ASSERT_NOT_REACHED();
#else
_renderer = renderer;
#endif
}
#pragma mark -
#pragma mark Notifications
- (void)windowDidExitFullscreen:(BOOL)finished
{
if (!_isAnimating)
return;
if (_isFullscreen)
return;
NSDisableScreenUpdates();
ASSERT(_element);
[self _document]->setFullScreenRendererBackgroundColor(Color::black);
[self _document]->webkitDidExitFullScreenForElement(_element.get());
[self setElement:nil];
if (finished) {
[self _updateMenuAndDockForFullscreen];
[self _updatePowerAssertions];
[[_webView window] display];
[[self _fullscreenWindow] setRendererLayer:nil];
[[self window] close];
}
NSEnableScreenUpdates();
_isAnimating = NO;
[self autorelease]; // Associated -retain is in -exitFullscreen.
}
- (void)windowDidEnterFullscreen:(BOOL)finished
{
if (!_isAnimating)
return;
if (!_isFullscreen)
return;
NSDisableScreenUpdates();
[self _document]->webkitDidEnterFullScreenForElement(_element.get());
[self _document]->setFullScreenRendererBackgroundColor(Color::black);
if (finished) {
[self _updateMenuAndDockForFullscreen];
[self _updatePowerAssertions];
[NSCursor setHiddenUntilMouseMoves:YES];
// Move the webView into our fullscreen Window
if (!_placeholderView)
_placeholderView = [[NSView alloc] init];
// Do not swap the placeholder into place if already is in a window,
// assuming the placeholder's window will always be the webView's
// original window.
if (![_placeholderView window]) {
WebView* webView = [self webView];
[_placeholderView setFrame:[webView frame]];
[_placeholderView setAutoresizingMask:[webView autoresizingMask]];
[_placeholderView removeFromSuperview];
[[webView superview] replaceSubview:webView with:_placeholderView];
[[[self window] contentView] addSubview:webView];
[webView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
[webView setFrame:[[[self window] contentView] bounds]];
}
WebFullscreenWindow* window = [self _fullscreenWindow];
[window setBackgroundColor:[NSColor blackColor]];
[window setOpaque:YES];
[CATransaction begin];
[CATransaction setDisableActions:YES];
[[[window animationView] layer] setOpacity:0];
[CATransaction commit];
}
NSEnableScreenUpdates();
_isAnimating = NO;
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)finished
{
BOOL isEnteringFullscreenAnimation = [[anim valueForKey:isEnteringFullscreenKey] boolValue];
if (!isEnteringFullscreenAnimation)
[self windowDidExitFullscreen:finished];
else
[self windowDidEnterFullscreen:finished];
}
- (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
{
// Disable animation if we are already in full-screen mode.
BOOL shouldAnimate = !_isFullscreen;
if (_isAnimating) {
// The CAAnimation delegate functions will only be called the
// next trip through the run-loop, so manually call the delegate
// function here, letting it know the animation did not complete:
[self windowDidExitFullscreen:NO];
ASSERT(!_isAnimating);
}
_isFullscreen = YES;
_isAnimating = YES;
// setElement: must be called with a non-nil value before calling enterFullscreen:.
ASSERT(_element);
NSDisableScreenUpdates();
if (!screen)
screen = [NSScreen mainScreen];
NSRect screenFrame = [screen frame];
WebView* webView = [self webView];
NSRect webViewFrame = [webView convertRectToBase:[webView frame]];
webViewFrame.origin = [[webView window] convertBaseToScreen:webViewFrame.origin];
NSRect elementFrame = _element->screenRect();
// 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, elementFrame);
[[self window] setFrame:windowFrame display:YES];
// In a previous incarnation, the NSWindow attached to this controller may have
// been on a different screen. Temporarily change the collectionBehavior of the window:
NSWindowCollectionBehavior behavior = [[self window] collectionBehavior];
[[self window] setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces];
[[self window] makeKeyAndOrderFront:self];
[[self window] setCollectionBehavior:behavior];
NSView* animationView = [[self _fullscreenWindow] animationView];
NSRect backgroundBounds = {[[self window] convertScreenToBase:screenFrame.origin], screenFrame.size};
backgroundBounds = [animationView convertRectFromBase:backgroundBounds];
// Flip the background layer's coordinate system.
backgroundBounds.origin.y = windowFrame.size.height - NSMaxY(backgroundBounds);
// Set our fullscreen element's initial frame, and flip the coordinate systems from
// screen coordinates (bottom/left) to layer coordinates (top/left):
_initialFrame = NSRectToCGRect(NSIntersectionRect(elementFrame, webViewFrame));
_initialFrame.origin.y = screenFrame.size.height - CGRectGetMaxY(_initialFrame);
// Inform the document that we will begin entering full screen. This will change
// pseudo-classes on the fullscreen element and the document element.
Document* document = [self _document];
document->webkitWillEnterFullScreenForElement(_element.get());
// Check to see if the fullscreen renderer is composited. If not, accelerated graphics
// may be disabled. In this case, do not attempt to animate the contents into place;
// merely snap to the final position:
if (!shouldAnimate || !_renderer || !_renderer->layer()->isComposited()) {
[self windowDidEnterFullscreen:YES];
NSEnableScreenUpdates();
return;
}
// Set up the final style of the FullScreen render block. Set an absolute
// width and height equal to the size of the screen, and anchor the layer
// at the top, left at (0,0). The RenderFullScreen style is already set
// to position:fixed.
[self _document]->setFullScreenRendererSize(IntSize(screenFrame.size));
[self _document]->setFullScreenRendererBackgroundColor(Color::transparent);
// Cause the document to layout, thus calculating a new fullscreen element size:
[self _document]->updateLayout();
// FIXME: try to use the fullscreen element's calculated x, y, width, and height instead of the
// renderBox functions:
RenderBox* childRenderer = _renderer->firstChildBox();
CGRect destinationFrame = CGRectMake(childRenderer->x(), childRenderer->y(), childRenderer->width(), childRenderer->height());
// Some properties haven't propogated from the GraphicsLayer to the CALayer yet. So
// tell the renderer's layer to sync it's compositing state:
GraphicsLayer* rendererGraphics = _renderer->layer()->backing()->graphicsLayer();
rendererGraphics->syncCompositingState();
CALayer* rendererLayer = rendererGraphics->platformLayer();
[[self _fullscreenWindow] setRendererLayer:rendererLayer];
CFTimeInterval duration = [self _animationDuration];
// Create a transformation matrix that will transform the renderer layer such that
// the fullscreen element appears to move from its starting position and size to its
// final one. Perform the transformation in two steps, using the CALayer's matrix
// math to calculate the effects of each step:
// 1. Apply a scale tranform to shrink the apparent size of the layer to the original
// element screen size.
// 2. Apply a translation transform to move the shrunk layer into the same screen position
// as the original element.
CATransform3D shrinkTransform = CATransform3DMakeScale(_initialFrame.size.width / destinationFrame.size.width, _initialFrame.size.height / destinationFrame.size.height, 1);
[rendererLayer setTransform:shrinkTransform];
CGRect shrunkDestinationFrame = [rendererLayer convertRect:destinationFrame toLayer:[animationView layer]];
CATransform3D moveTransform = CATransform3DMakeTranslation(_initialFrame.origin.x - shrunkDestinationFrame.origin.x, _initialFrame.origin.y - shrunkDestinationFrame.origin.y, 0);
CATransform3D finalTransform = CATransform3DConcat(shrinkTransform, moveTransform);
[rendererLayer setTransform:finalTransform];
CALayer* backgroundLayer = [[self _fullscreenWindow] backgroundLayer];
// Start the opacity animation. We can use implicit animations here because we don't care when
// the animation finishes.
[CATransaction begin];
[CATransaction setAnimationDuration:duration];
[backgroundLayer setOpacity:1];
[CATransaction commit];
// Use a CABasicAnimation here for the zoom effect. We want to be notified that the animation has
// completed by way of the CAAnimation delegate.
CABasicAnimation* zoomAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
[zoomAnimation setFromValue:[NSValue valueWithCATransform3D:finalTransform]];
[zoomAnimation setToValue:[NSValue valueWithCATransform3D:CATransform3DIdentity]];
[zoomAnimation setDelegate:self];
[zoomAnimation setDuration:duration];
[zoomAnimation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[zoomAnimation setFillMode:kCAFillModeForwards];
[zoomAnimation setValue:(id)kCFBooleanTrue forKey:isEnteringFullscreenKey];
// Disable implicit animations and set the layer's transformation matrix to its final state.
[CATransaction begin];
[CATransaction setDisableActions:YES];
[rendererLayer setTransform:CATransform3DIdentity];
[rendererLayer addAnimation:zoomAnimation forKey:@"zoom"];
[backgroundLayer setFrame:NSRectToCGRect(backgroundBounds)];
[CATransaction commit];
NSEnableScreenUpdates();
}
- (void)exitFullscreen
{
if (!_isFullscreen)
return;
CATransform3D startTransform = CATransform3DIdentity;
if (_isAnimating) {
if (_renderer && _renderer->layer()->isComposited()) {
CALayer* rendererLayer = _renderer->layer()->backing()->graphicsLayer()->platformLayer();
startTransform = [[rendererLayer presentationLayer] transform];
}
// The CAAnimation delegate functions will only be called the
// next trip through the run-loop, so manually call the delegate
// function here, letting it know the animation did not complete:
[self windowDidEnterFullscreen:NO];
ASSERT(!_isAnimating);
}
_isFullscreen = NO;
_isAnimating = YES;
NSDisableScreenUpdates();
// 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];
// The fullscreen animation may have been cancelled before the
// webView was moved to the fullscreen window. Check to see
// if the _placeholderView exists and is in a window before
// attempting to swap the webView back to it's original tree:
if (_placeholderView && [_placeholderView window]) {
// Move the webView back to its own native window:
WebView* webView = [self webView];
[webView setFrame:[_placeholderView frame]];
[webView setAutoresizingMask:[_placeholderView autoresizingMask]];
[webView removeFromSuperview];
[[_placeholderView superview] replaceSubview:_placeholderView with:webView];
// Because the animation view is layer-hosted, make sure to
// disable animations when changing the layer's opacity. Other-
// wise, the content will appear to fade into view.
[CATransaction begin];
[CATransaction setDisableActions:YES];
WebFullscreenWindow* window = [self _fullscreenWindow];
[[[window animationView] layer] setOpacity:1];
[window setBackgroundColor:[NSColor clearColor]];
[window setOpaque:NO];
[CATransaction commit];
}
NSView* animationView = [[self _fullscreenWindow] animationView];
CGRect layerEndFrame = NSRectToCGRect([animationView convertRect:NSRectFromCGRect(_initialFrame) fromView:nil]);
// The _renderer might be NULL due to its ancestor being removed:
CGRect layerStartFrame = CGRectZero;
if (_renderer) {
RenderBox* childRenderer = _renderer->firstChildBox();
layerStartFrame = CGRectMake(childRenderer->x(), childRenderer->y(), childRenderer->width(), childRenderer->height());
}
[self _document]->webkitWillExitFullScreenForElement(_element.get());
[self _document]->updateLayout();
// We have to retain ourselves because we want to be alive for the end of the animation.
// If our owner releases us we could crash if this is not the case.
// Balanced in windowDidExitFullscreen
[self retain];
// Check to see if the fullscreen renderer is composited. If not, accelerated graphics
// may be disabled. In this case, do not attempt to animate the contents into place;
// merely snap to the final position:
if (!_renderer || !_renderer->layer()->isComposited()) {
[self windowDidExitFullscreen:YES];
NSEnableScreenUpdates();
return;
}
GraphicsLayer* rendererGraphics = _renderer->layer()->backing()->graphicsLayer();
[self _document]->setFullScreenRendererBackgroundColor(Color::transparent);
rendererGraphics->syncCompositingState();
CALayer* rendererLayer = rendererGraphics->platformLayer();
[[self _fullscreenWindow] setRendererLayer:rendererLayer];
// Create a transformation matrix that will transform the renderer layer such that
// the fullscreen element appears to move from the full screen to its original position
// and size. Perform the transformation in two steps, using the CALayer's matrix
// math to calculate the effects of each step:
// 1. Apply a scale tranform to shrink the apparent size of the layer to the original
// element screen size.
// 2. Apply a translation transform to move the shrunk layer into the same screen position
// as the original element.
CATransform3D shrinkTransform = CATransform3DMakeScale(layerEndFrame.size.width / layerStartFrame.size.width, layerEndFrame.size.height / layerStartFrame.size.height, 1);
[rendererLayer setTransform:shrinkTransform];
CGRect shrunkDestinationFrame = [rendererLayer convertRect:layerStartFrame toLayer:[animationView layer]];
CATransform3D moveTransform = CATransform3DMakeTranslation(layerEndFrame.origin.x - shrunkDestinationFrame.origin.x, layerEndFrame.origin.y - shrunkDestinationFrame.origin.y, 0);
CATransform3D finalTransform = CATransform3DConcat(shrinkTransform, moveTransform);
[rendererLayer setTransform:finalTransform];
CFTimeInterval duration = [self _animationDuration];
CALayer* backgroundLayer = [[self _fullscreenWindow] backgroundLayer];
[CATransaction begin];
[CATransaction setAnimationDuration:duration];
[backgroundLayer setOpacity:0];
[CATransaction commit];
CABasicAnimation* zoomAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
[zoomAnimation setFromValue:[NSValue valueWithCATransform3D:startTransform]];
[zoomAnimation setToValue:[NSValue valueWithCATransform3D:finalTransform]];
[zoomAnimation setDelegate:self];
[zoomAnimation setDuration:duration];
[zoomAnimation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[zoomAnimation setFillMode:kCAFillModeBoth];
[zoomAnimation setRemovedOnCompletion:NO];
[zoomAnimation setValue:(id)kCFBooleanFalse forKey:isEnteringFullscreenKey];
[rendererLayer addAnimation:zoomAnimation forKey:@"zoom"];
NSEnableScreenUpdates();
}
#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)
BOOL isPlaying = [self _isAnyMoviePlaying];
if (isPlaying && _isFullscreen) {
[self _disableIdleSystemSleep];
[self _disableIdleDisplaySleep];
[self _enableTickleTimer];
} else {
[self _enableIdleSystemSleep];
[self _enableIdleDisplaySleep];
[self _disableTickleTimer];
}
#endif
}
- (void)_requestExit
{
[self exitFullscreen];
_forceDisableAnimation = NO;
}
- (void)_requestExitFullscreenWithAnimation:(BOOL)animation
{
_forceDisableAnimation = !animation;
[self performSelector:@selector(_requestExit) withObject:nil afterDelay:0];
}
- (BOOL)_isAnyMoviePlaying
{
if (!_element)
return NO;
Node* nextNode = _element.get();
while (nextNode)
{
if (nextNode->hasTagName(HTMLNames::videoTag)) {
HTMLMediaElement* element = static_cast<HTMLMediaElement*>(nextNode);
if (!element->paused() && !element->ended())
return YES;
}
nextNode = nextNode->traverseNextNode(_element.get());
}
return NO;
}
#pragma mark -
#pragma mark Utility Functions
- (WebFullscreenWindow *)_fullscreenWindow
{
return (WebFullscreenWindow *)[self window];
}
- (Document*)_document
{
return core([[[self webView] mainFrame] DOMDocument]);
}
- (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 WebFullscreenWindow
- (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];
#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
[contentLayer setGeometryFlipped:YES];
#else
[contentLayer setSublayerTransform:CATransform3DMakeScale(1, -1, 1)];
#endif
[contentLayer setOpacity:0];
[_backgroundLayer setBackgroundColor:CGColorGetConstantColor(kCGColorBlack)];
[_backgroundLayer setOpacity:0];
return self;
}
- (void)dealloc
{
[_animationView release];
[_backgroundLayer release];
[_rendererLayer 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*)rendererLayer
{
return _rendererLayer;
}
- (void)setRendererLayer:(CALayer *)rendererLayer
{
[CATransaction begin];
[CATransaction setDisableActions:YES];
[rendererLayer retain];
[_rendererLayer removeFromSuperlayer];
[_rendererLayer release];
_rendererLayer = rendererLayer;
if (_rendererLayer)
[[[self animationView] layer] addSublayer:_rendererLayer];
[CATransaction commit];
}
- (CALayer*)backgroundLayer
{
return _backgroundLayer;
}
- (NSView*)animationView
{
return _animationView;
}
@end
#pragma mark -
#pragma mark MediaEventListener
MediaEventListener::MediaEventListener(WebFullScreenController* delegate)
: EventListener(CPPEventListenerType)
, delegate(delegate)
{
}
PassRefPtr<MediaEventListener> MediaEventListener::create(WebFullScreenController* delegate)
{
return adoptRef(new MediaEventListener(delegate));
}
bool MediaEventListener::operator==(const EventListener& listener)
{
return this == &listener;
}
void MediaEventListener::handleEvent(ScriptExecutionContext* context, Event* event)
{
[delegate _updatePowerAssertions];
}
#endif /* ENABLE(FULLSCREEN_API) */