summaryrefslogtreecommitdiffstats
path: root/Source/WebKit/mac/WebView/WebFullScreenController.mm
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebKit/mac/WebView/WebFullScreenController.mm')
-rw-r--r--Source/WebKit/mac/WebView/WebFullScreenController.mm924
1 files changed, 924 insertions, 0 deletions
diff --git a/Source/WebKit/mac/WebView/WebFullScreenController.mm b/Source/WebKit/mac/WebView/WebFullScreenController.mm
new file mode 100644
index 0000000..6529435
--- /dev/null
+++ b/Source/WebKit/mac/WebView/WebFullScreenController.mm
@@ -0,0 +1,924 @@
+/*
+ * 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.
+ */
+
+#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 viewBounds = [animationView bounds];
+
+ 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];
+ CGRect translatedDestinationFrame = [rendererLayer convertRect:destinationFrame toLayer:[animationView layer]];
+
+ 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];
+ CGRect translatedDestinationFrame = [rendererLayer convertRect:layerStartFrame toLayer:[animationView layer]];
+
+ 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) */