Home | History | Annotate | Download | only in WebView
      1 /*
      2  * Copyright (C) 2010, 2011 Apple Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
     14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
     15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
     17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
     23  * THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #if ENABLE(FULLSCREEN_API)
     27 
     28 #import "WebFullScreenController.h"
     29 
     30 #import "WebPreferencesPrivate.h"
     31 #import "WebWindowAnimation.h"
     32 #import "WebViewInternal.h"
     33 #import <IOKit/pwr_mgt/IOPMLib.h>
     34 #import <OSServices/Power.h>
     35 #import <WebCore/AnimationList.h>
     36 #import <WebCore/CSSPropertyNames.h>
     37 #import <WebCore/Color.h>
     38 #import <WebCore/Document.h>
     39 #import <WebCore/DOMDocument.h>
     40 #import <WebCore/DOMDocumentInternal.h>
     41 #import <WebCore/DOMHTMLElement.h>
     42 #import <WebCore/DOMWindow.h>
     43 #import <WebCore/EventListener.h>
     44 #import <WebCore/EventNames.h>
     45 #import <WebCore/HTMLElement.h>
     46 #import <WebCore/HTMLNames.h>
     47 #import <WebCore/HTMLMediaElement.h>
     48 #import <WebCore/IntRect.h>
     49 #import <WebCore/NodeList.h>
     50 #import <WebCore/SoftLinking.h>
     51 #import <WebCore/RenderBlock.h>
     52 #import <WebCore/RenderLayer.h>
     53 #import <WebCore/RenderLayerBacking.h>
     54 #import <objc/objc-runtime.h>
     55 #import <wtf/UnusedParam.h>
     56 
     57 static const NSTimeInterval tickleTimerInterval = 1.0;
     58 static NSString* const isEnteringFullscreenKey = @"isEnteringFullscreen";
     59 
     60 using namespace WebCore;
     61 
     62 #if defined(BUILDING_ON_LEOPARD)
     63 @interface CATransaction(SnowLeopardConvenienceFunctions)
     64 + (void)setDisableActions:(BOOL)flag;
     65 + (void)setAnimationDuration:(CFTimeInterval)dur;
     66 @end
     67 
     68 @implementation CATransaction(SnowLeopardConvenienceFunctions)
     69 + (void)setDisableActions:(BOOL)flag
     70 {
     71     [self setValue:[NSNumber numberWithBool:flag] forKey:kCATransactionDisableActions];
     72 }
     73 
     74 + (void)setAnimationDuration:(CFTimeInterval)dur
     75 {
     76     [self setValue:[NSNumber numberWithDouble:dur] forKey:kCATransactionAnimationDuration];
     77 }
     78 @end
     79 
     80 #endif
     81 
     82 @interface WebFullscreenWindow : NSWindow
     83 #if !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_TIGER)
     84 <NSAnimationDelegate>
     85 #endif
     86 {
     87     NSView* _animationView;
     88 
     89     CALayer* _rendererLayer;
     90     CALayer* _backgroundLayer;
     91 }
     92 - (CALayer*)rendererLayer;
     93 - (void)setRendererLayer:(CALayer*)rendererLayer;
     94 - (CALayer*)backgroundLayer;
     95 - (NSView*)animationView;
     96 @end
     97 
     98 class MediaEventListener : public EventListener {
     99 public:
    100     static PassRefPtr<MediaEventListener> create(WebFullScreenController* delegate);
    101     virtual bool operator==(const EventListener&);
    102     virtual void handleEvent(ScriptExecutionContext*, Event*);
    103 
    104 private:
    105     MediaEventListener(WebFullScreenController* delegate);
    106     WebFullScreenController* delegate;
    107 };
    108 
    109 @interface WebFullScreenController(Private)
    110 - (void)_requestExitFullscreenWithAnimation:(BOOL)animation;
    111 - (void)_updateMenuAndDockForFullscreen;
    112 - (void)_updatePowerAssertions;
    113 - (WebFullscreenWindow *)_fullscreenWindow;
    114 - (Document*)_document;
    115 - (CFTimeInterval)_animationDuration;
    116 - (BOOL)_isAnyMoviePlaying;
    117 @end
    118 
    119 @interface NSWindow(IsOnActiveSpaceAdditionForTigerAndLeopard)
    120 - (BOOL)isOnActiveSpace;
    121 @end
    122 
    123 @implementation WebFullScreenController
    124 
    125 #pragma mark -
    126 #pragma mark Initialization
    127 - (id)init
    128 {
    129     // Do not defer window creation, to make sure -windowNumber is created (needed by WebWindowScaleAnimation).
    130     NSWindow *window = [[WebFullscreenWindow alloc] initWithContentRect:NSZeroRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
    131     self = [super initWithWindow:window];
    132     [window release];
    133     if (!self)
    134         return nil;
    135     [self windowDidLoad];
    136     _mediaEventListener = MediaEventListener::create(self);
    137     return self;
    138 
    139 }
    140 
    141 - (void)dealloc
    142 {
    143     ASSERT(!_tickleTimer);
    144 
    145     [self setWebView:nil];
    146     [_placeholderView release];
    147 
    148     [[NSNotificationCenter defaultCenter] removeObserver:self];
    149     [super dealloc];
    150 }
    151 
    152 - (void)windowDidLoad
    153 {
    154 #ifdef BUILDING_ON_TIGER
    155     // WebFullScreenController is not supported on Tiger:
    156     ASSERT_NOT_REACHED();
    157 #else
    158 
    159     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidResignActive:) name:NSApplicationDidResignActiveNotification object:NSApp];
    160     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidChangeScreenParameters:) name:NSApplicationDidChangeScreenParametersNotification object:NSApp];
    161 #endif
    162 }
    163 
    164 #pragma mark -
    165 #pragma mark Accessors
    166 
    167 - (WebView*)webView
    168 {
    169     return _webView;
    170 }
    171 
    172 - (void)setWebView:(WebView *)webView
    173 {
    174     [webView retain];
    175     [_webView release];
    176     _webView = webView;
    177 }
    178 
    179 - (Element*)element
    180 {
    181     return _element.get();
    182 }
    183 
    184 - (void)setElement:(PassRefPtr<Element>)element
    185 {
    186 #ifdef BUILDING_ON_TIGER
    187     // WebFullScreenController is not supported on Tiger:
    188     ASSERT_NOT_REACHED();
    189 #else
    190     // When a new Element is set as the current full screen element, register event
    191     // listeners on that Element's window, listening for changes in media play states.
    192     // We will use these events to determine whether to disable the screensaver and
    193     // display sleep timers when playing video in full screen. Make sure to unregister
    194     // the events on the old element's window, if necessary, as well.
    195 
    196     EventNames& eventNames = WebCore::eventNames();
    197 
    198     if (_element) {
    199         DOMWindow* window = _element->document()->domWindow();
    200         if (window) {
    201             window->removeEventListener(eventNames.playEvent,  _mediaEventListener.get(), true);
    202             window->removeEventListener(eventNames.pauseEvent, _mediaEventListener.get(), true);
    203             window->removeEventListener(eventNames.endedEvent, _mediaEventListener.get(), true);
    204         }
    205     }
    206 
    207     _element = element;
    208 
    209     if (_element) {
    210         DOMWindow* window = _element->document()->domWindow();
    211         if (window) {
    212             window->addEventListener(eventNames.playEvent,  _mediaEventListener, true);
    213             window->addEventListener(eventNames.pauseEvent, _mediaEventListener, true);
    214             window->addEventListener(eventNames.endedEvent, _mediaEventListener, true);
    215         }
    216     }
    217 #endif
    218 }
    219 
    220 - (RenderBox*)renderer
    221 {
    222     return _renderer;
    223 }
    224 
    225 - (void)setRenderer:(RenderBox*)renderer
    226 {
    227 #ifdef BUILDING_ON_TIGER
    228     // WebFullScreenController is not supported on Tiger:
    229     ASSERT_NOT_REACHED();
    230 #else
    231     _renderer = renderer;
    232 #endif
    233 }
    234 
    235 #pragma mark -
    236 #pragma mark Notifications
    237 
    238 - (void)windowDidExitFullscreen:(BOOL)finished
    239 {
    240     if (!_isAnimating)
    241         return;
    242 
    243     if (_isFullscreen)
    244         return;
    245 
    246     NSDisableScreenUpdates();
    247     ASSERT(_element);
    248     [self _document]->setFullScreenRendererBackgroundColor(Color::black);
    249     [self _document]->webkitDidExitFullScreenForElement(_element.get());
    250     [self setElement:nil];
    251 
    252     if (finished) {
    253         [self _updateMenuAndDockForFullscreen];
    254         [self _updatePowerAssertions];
    255 
    256         [[_webView window] display];
    257         [[self _fullscreenWindow] setRendererLayer:nil];
    258         [[self window] close];
    259     }
    260 
    261     NSEnableScreenUpdates();
    262 
    263     _isAnimating = NO;
    264     [self autorelease]; // Associated -retain is in -exitFullscreen.
    265 }
    266 
    267 - (void)windowDidEnterFullscreen:(BOOL)finished
    268 {
    269     if (!_isAnimating)
    270         return;
    271 
    272     if (!_isFullscreen)
    273         return;
    274 
    275     NSDisableScreenUpdates();
    276     [self _document]->webkitDidEnterFullScreenForElement(_element.get());
    277     [self _document]->setFullScreenRendererBackgroundColor(Color::black);
    278 
    279     if (finished) {
    280         [self _updateMenuAndDockForFullscreen];
    281         [self _updatePowerAssertions];
    282         [NSCursor setHiddenUntilMouseMoves:YES];
    283 
    284         // Move the webView into our fullscreen Window
    285         if (!_placeholderView)
    286             _placeholderView = [[NSView alloc] init];
    287 
    288         // Do not swap the placeholder into place if already is in a window,
    289         // assuming the placeholder's window will always be the webView's
    290         // original window.
    291         if (![_placeholderView window]) {
    292             WebView* webView = [self webView];
    293             [_placeholderView setFrame:[webView frame]];
    294             [_placeholderView setAutoresizingMask:[webView autoresizingMask]];
    295             [_placeholderView removeFromSuperview];
    296             [[webView superview] replaceSubview:webView with:_placeholderView];
    297 
    298             [[[self window] contentView] addSubview:webView];
    299             [webView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
    300             [webView setFrame:[[[self window] contentView] bounds]];
    301         }
    302 
    303         WebFullscreenWindow* window = [self _fullscreenWindow];
    304         [window setBackgroundColor:[NSColor blackColor]];
    305         [window setOpaque:YES];
    306 
    307         [CATransaction begin];
    308         [CATransaction setDisableActions:YES];
    309         [[[window animationView] layer] setOpacity:0];
    310         [CATransaction commit];
    311     }
    312     NSEnableScreenUpdates();
    313 
    314     _isAnimating = NO;
    315 }
    316 
    317 - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)finished
    318 {
    319     BOOL isEnteringFullscreenAnimation = [[anim valueForKey:isEnteringFullscreenKey] boolValue];
    320 
    321     if (!isEnteringFullscreenAnimation)
    322         [self windowDidExitFullscreen:finished];
    323     else
    324         [self windowDidEnterFullscreen:finished];
    325 }
    326 
    327 - (void)applicationDidResignActive:(NSNotification*)notification
    328 {
    329     // Check to see if the fullscreenWindow is on the active space; this function is available
    330     // on 10.6 and later, so default to YES if the function is not available:
    331     NSWindow* fullscreenWindow = [self _fullscreenWindow];
    332     BOOL isOnActiveSpace = ([fullscreenWindow respondsToSelector:@selector(isOnActiveSpace)] ? [fullscreenWindow isOnActiveSpace] : YES);
    333 
    334     // Replicate the QuickTime Player (X) behavior when losing active application status:
    335     // Is the fullscreen screen the main screen? (Note: this covers the case where only a
    336     // single screen is available.)  Is the fullscreen screen on the current space? IFF so,
    337     // then exit fullscreen mode.
    338     if ([fullscreenWindow screen] == [[NSScreen screens] objectAtIndex:0] && isOnActiveSpace)
    339          [self _requestExitFullscreenWithAnimation:NO];
    340 }
    341 
    342 - (void)applicationDidChangeScreenParameters:(NSNotification*)notification
    343 {
    344     // The user may have changed the main screen by moving the menu bar, or they may have changed
    345     // the Dock's size or location, or they may have changed the fullscreen screen's dimensions.
    346     // Update our presentation parameters, and ensure that the full screen window occupies the
    347     // entire screen:
    348     [self _updateMenuAndDockForFullscreen];
    349     NSWindow* window = [self window];
    350     [window setFrame:[[window screen] frame] display:YES];
    351 }
    352 
    353 #pragma mark -
    354 #pragma mark Exposed Interface
    355 
    356 - (void)enterFullscreen:(NSScreen *)screen
    357 {
    358     // Disable animation if we are already in full-screen mode.
    359     BOOL shouldAnimate = !_isFullscreen;
    360 
    361     if (_isAnimating) {
    362         // The CAAnimation delegate functions will only be called the
    363         // next trip through the run-loop, so manually call the delegate
    364         // function here, letting it know the animation did not complete:
    365         [self windowDidExitFullscreen:NO];
    366         ASSERT(!_isAnimating);
    367     }
    368     _isFullscreen = YES;
    369     _isAnimating = YES;
    370 
    371     // setElement: must be called with a non-nil value before calling enterFullscreen:.
    372     ASSERT(_element);
    373 
    374     NSDisableScreenUpdates();
    375 
    376     if (!screen)
    377         screen = [NSScreen mainScreen];
    378     NSRect screenFrame = [screen frame];
    379 
    380     WebView* webView = [self webView];
    381     NSRect webViewFrame = [webView convertRectToBase:[webView frame]];
    382     webViewFrame.origin = [[webView window] convertBaseToScreen:webViewFrame.origin];
    383 
    384     NSRect elementFrame = _element->screenRect();
    385 
    386     // In the case of a multi-monitor setup where the webView straddles two
    387     // monitors, we must create a window large enough to contain the destination
    388     // frame and the initial frame.
    389     NSRect windowFrame = NSUnionRect(screenFrame, elementFrame);
    390     [[self window] setFrame:windowFrame display:YES];
    391 
    392     // In a previous incarnation, the NSWindow attached to this controller may have
    393     // been on a different screen. Temporarily change the collectionBehavior of the window:
    394     NSWindowCollectionBehavior behavior = [[self window] collectionBehavior];
    395     [[self window] setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces];
    396     [[self window] makeKeyAndOrderFront:self];
    397     [[self window] setCollectionBehavior:behavior];
    398 
    399     NSView* animationView = [[self _fullscreenWindow] animationView];
    400 
    401     NSRect backgroundBounds = {[[self window] convertScreenToBase:screenFrame.origin], screenFrame.size};
    402     backgroundBounds = [animationView convertRectFromBase:backgroundBounds];
    403     // Flip the background layer's coordinate system.
    404     backgroundBounds.origin.y = windowFrame.size.height - NSMaxY(backgroundBounds);
    405 
    406     // Set our fullscreen element's initial frame, and flip the coordinate systems from
    407     // screen coordinates (bottom/left) to layer coordinates (top/left):
    408     _initialFrame = NSRectToCGRect(NSIntersectionRect(elementFrame, webViewFrame));
    409     _initialFrame.origin.y = screenFrame.size.height - CGRectGetMaxY(_initialFrame);
    410 
    411     // Inform the document that we will begin entering full screen. This will change
    412     // pseudo-classes on the fullscreen element and the document element.
    413     Document* document = [self _document];
    414     document->webkitWillEnterFullScreenForElement(_element.get());
    415 
    416     // Check to see if the fullscreen renderer is composited. If not, accelerated graphics
    417     // may be disabled. In this case, do not attempt to animate the contents into place;
    418     // merely snap to the final position:
    419     if (!shouldAnimate || !_renderer || !_renderer->layer()->isComposited()) {
    420         [self windowDidEnterFullscreen:YES];
    421         NSEnableScreenUpdates();
    422         return;
    423     }
    424 
    425     // Set up the final style of the FullScreen render block. Set an absolute
    426     // width and height equal to the size of the screen, and anchor the layer
    427     // at the top, left at (0,0). The RenderFullScreen style is already set
    428     // to position:fixed.
    429     [self _document]->setFullScreenRendererSize(IntSize(screenFrame.size));
    430     [self _document]->setFullScreenRendererBackgroundColor(Color::transparent);
    431 
    432     // Cause the document to layout, thus calculating a new fullscreen element size:
    433     [self _document]->updateLayout();
    434 
    435     // FIXME: try to use the fullscreen element's calculated x, y, width, and height instead of the
    436     // renderBox functions:
    437     RenderBox* childRenderer = _renderer->firstChildBox();
    438     CGRect destinationFrame = CGRectMake(childRenderer->x(), childRenderer->y(), childRenderer->width(), childRenderer->height());
    439 
    440     // Some properties haven't propogated from the GraphicsLayer to the CALayer yet. So
    441     // tell the renderer's layer to sync it's compositing state:
    442     GraphicsLayer* rendererGraphics = _renderer->layer()->backing()->graphicsLayer();
    443     rendererGraphics->syncCompositingState();
    444 
    445     CALayer* rendererLayer = rendererGraphics->platformLayer();
    446     [[self _fullscreenWindow] setRendererLayer:rendererLayer];
    447 
    448     CFTimeInterval duration = [self _animationDuration];
    449 
    450     // Create a transformation matrix that will transform the renderer layer such that
    451     // the fullscreen element appears to move from its starting position and size to its
    452     // final one. Perform the transformation in two steps, using the CALayer's matrix
    453     // math to calculate the effects of each step:
    454     // 1. Apply a scale tranform to shrink the apparent size of the layer to the original
    455     //    element screen size.
    456     // 2. Apply a translation transform to move the shrunk layer into the same screen position
    457     //    as the original element.
    458     CATransform3D shrinkTransform = CATransform3DMakeScale(_initialFrame.size.width / destinationFrame.size.width, _initialFrame.size.height / destinationFrame.size.height, 1);
    459     [rendererLayer setTransform:shrinkTransform];
    460     CGRect shrunkDestinationFrame = [rendererLayer convertRect:destinationFrame toLayer:[animationView layer]];
    461     CATransform3D moveTransform = CATransform3DMakeTranslation(_initialFrame.origin.x - shrunkDestinationFrame.origin.x, _initialFrame.origin.y - shrunkDestinationFrame.origin.y, 0);
    462     CATransform3D finalTransform = CATransform3DConcat(shrinkTransform, moveTransform);
    463     [rendererLayer setTransform:finalTransform];
    464 
    465     CALayer* backgroundLayer = [[self _fullscreenWindow] backgroundLayer];
    466 
    467     // Start the opacity animation. We can use implicit animations here because we don't care when
    468     // the animation finishes.
    469     [CATransaction begin];
    470     [CATransaction setAnimationDuration:duration];
    471     [backgroundLayer setOpacity:1];
    472     [CATransaction commit];
    473 
    474     // Use a CABasicAnimation here for the zoom effect. We want to be notified that the animation has
    475     // completed by way of the CAAnimation delegate.
    476     CABasicAnimation* zoomAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
    477     [zoomAnimation setFromValue:[NSValue valueWithCATransform3D:finalTransform]];
    478     [zoomAnimation setToValue:[NSValue valueWithCATransform3D:CATransform3DIdentity]];
    479     [zoomAnimation setDelegate:self];
    480     [zoomAnimation setDuration:duration];
    481     [zoomAnimation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
    482     [zoomAnimation setFillMode:kCAFillModeForwards];
    483     [zoomAnimation setValue:(id)kCFBooleanTrue forKey:isEnteringFullscreenKey];
    484 
    485     // Disable implicit animations and set the layer's transformation matrix to its final state.
    486     [CATransaction begin];
    487     [CATransaction setDisableActions:YES];
    488     [rendererLayer setTransform:CATransform3DIdentity];
    489     [rendererLayer addAnimation:zoomAnimation forKey:@"zoom"];
    490     [backgroundLayer setFrame:NSRectToCGRect(backgroundBounds)];
    491     [CATransaction commit];
    492 
    493     NSEnableScreenUpdates();
    494 }
    495 
    496 - (void)exitFullscreen
    497 {
    498     if (!_isFullscreen)
    499         return;
    500 
    501     CATransform3D startTransform = CATransform3DIdentity;
    502     if (_isAnimating) {
    503         if (_renderer && _renderer->layer()->isComposited()) {
    504             CALayer* rendererLayer = _renderer->layer()->backing()->graphicsLayer()->platformLayer();
    505             startTransform = [[rendererLayer presentationLayer] transform];
    506         }
    507 
    508         // The CAAnimation delegate functions will only be called the
    509         // next trip through the run-loop, so manually call the delegate
    510         // function here, letting it know the animation did not complete:
    511         [self windowDidEnterFullscreen:NO];
    512         ASSERT(!_isAnimating);
    513     }
    514     _isFullscreen = NO;
    515     _isAnimating = YES;
    516 
    517     NSDisableScreenUpdates();
    518 
    519     // The user may have moved the fullscreen window in Spaces, so temporarily change
    520     // the collectionBehavior of the webView's window:
    521     NSWindow* webWindow = [[self webView] window];
    522     NSWindowCollectionBehavior behavior = [webWindow collectionBehavior];
    523     [webWindow setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces];
    524     [webWindow orderWindow:NSWindowBelow relativeTo:[[self window] windowNumber]];
    525     [webWindow setCollectionBehavior:behavior];
    526 
    527     // The fullscreen animation may have been cancelled before the
    528     // webView was moved to the fullscreen window. Check to see
    529     // if the _placeholderView exists and is in a window before
    530     // attempting to swap the webView back to it's original tree:
    531     if (_placeholderView && [_placeholderView window]) {
    532         // Move the webView back to its own native window:
    533         WebView* webView = [self webView];
    534         [webView setFrame:[_placeholderView frame]];
    535         [webView setAutoresizingMask:[_placeholderView autoresizingMask]];
    536         [webView removeFromSuperview];
    537         [[_placeholderView superview] replaceSubview:_placeholderView with:webView];
    538 
    539         // Because the animation view is layer-hosted, make sure to
    540         // disable animations when changing the layer's opacity. Other-
    541         // wise, the content will appear to fade into view.
    542         [CATransaction begin];
    543         [CATransaction setDisableActions:YES];
    544         WebFullscreenWindow* window = [self _fullscreenWindow];
    545         [[[window animationView] layer] setOpacity:1];
    546         [window setBackgroundColor:[NSColor clearColor]];
    547         [window setOpaque:NO];
    548         [CATransaction commit];
    549     }
    550 
    551     NSView* animationView = [[self _fullscreenWindow] animationView];
    552     CGRect layerEndFrame = NSRectToCGRect([animationView convertRect:NSRectFromCGRect(_initialFrame) fromView:nil]);
    553 
    554     // The _renderer might be NULL due to its ancestor being removed:
    555     CGRect layerStartFrame = CGRectZero;
    556     if (_renderer) {
    557         RenderBox* childRenderer = _renderer->firstChildBox();
    558         layerStartFrame = CGRectMake(childRenderer->x(), childRenderer->y(), childRenderer->width(), childRenderer->height());
    559     }
    560 
    561     [self _document]->webkitWillExitFullScreenForElement(_element.get());
    562     [self _document]->updateLayout();
    563 
    564     // We have to retain ourselves because we want to be alive for the end of the animation.
    565     // If our owner releases us we could crash if this is not the case.
    566     // Balanced in windowDidExitFullscreen
    567     [self retain];
    568 
    569     // Check to see if the fullscreen renderer is composited. If not, accelerated graphics
    570     // may be disabled. In this case, do not attempt to animate the contents into place;
    571     // merely snap to the final position:
    572     if (!_renderer || !_renderer->layer()->isComposited()) {
    573         [self windowDidExitFullscreen:YES];
    574         NSEnableScreenUpdates();
    575         return;
    576     }
    577 
    578     GraphicsLayer* rendererGraphics = _renderer->layer()->backing()->graphicsLayer();
    579 
    580     [self _document]->setFullScreenRendererBackgroundColor(Color::transparent);
    581 
    582     rendererGraphics->syncCompositingState();
    583 
    584     CALayer* rendererLayer = rendererGraphics->platformLayer();
    585     [[self _fullscreenWindow] setRendererLayer:rendererLayer];
    586 
    587     // Create a transformation matrix that will transform the renderer layer such that
    588     // the fullscreen element appears to move from the full screen to its original position
    589     // and size. Perform the transformation in two steps, using the CALayer's matrix
    590     // math to calculate the effects of each step:
    591     // 1. Apply a scale tranform to shrink the apparent size of the layer to the original
    592     //    element screen size.
    593     // 2. Apply a translation transform to move the shrunk layer into the same screen position
    594     //    as the original element.
    595     CATransform3D shrinkTransform = CATransform3DMakeScale(layerEndFrame.size.width / layerStartFrame.size.width, layerEndFrame.size.height / layerStartFrame.size.height, 1);
    596     [rendererLayer setTransform:shrinkTransform];
    597     CGRect shrunkDestinationFrame = [rendererLayer convertRect:layerStartFrame toLayer:[animationView layer]];
    598     CATransform3D moveTransform = CATransform3DMakeTranslation(layerEndFrame.origin.x - shrunkDestinationFrame.origin.x, layerEndFrame.origin.y - shrunkDestinationFrame.origin.y, 0);
    599     CATransform3D finalTransform = CATransform3DConcat(shrinkTransform, moveTransform);
    600     [rendererLayer setTransform:finalTransform];
    601 
    602     CFTimeInterval duration = [self _animationDuration];
    603 
    604     CALayer* backgroundLayer = [[self _fullscreenWindow] backgroundLayer];
    605     [CATransaction begin];
    606     [CATransaction setAnimationDuration:duration];
    607     [backgroundLayer setOpacity:0];
    608     [CATransaction commit];
    609 
    610     CABasicAnimation* zoomAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
    611     [zoomAnimation setFromValue:[NSValue valueWithCATransform3D:startTransform]];
    612     [zoomAnimation setToValue:[NSValue valueWithCATransform3D:finalTransform]];
    613     [zoomAnimation setDelegate:self];
    614     [zoomAnimation setDuration:duration];
    615     [zoomAnimation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
    616     [zoomAnimation setFillMode:kCAFillModeBoth];
    617     [zoomAnimation setRemovedOnCompletion:NO];
    618     [zoomAnimation setValue:(id)kCFBooleanFalse forKey:isEnteringFullscreenKey];
    619 
    620     [rendererLayer addAnimation:zoomAnimation forKey:@"zoom"];
    621 
    622     NSEnableScreenUpdates();
    623 }
    624 
    625 #pragma mark -
    626 #pragma mark Internal Interface
    627 
    628 - (void)_updateMenuAndDockForFullscreen
    629 {
    630     // NSApplicationPresentationOptions is available on > 10.6 only:
    631 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
    632     NSApplicationPresentationOptions options = NSApplicationPresentationDefault;
    633     NSScreen* fullscreenScreen = [[self window] screen];
    634 
    635     if (_isFullscreen) {
    636         // Auto-hide the menu bar if the fullscreenScreen contains the menu bar:
    637         // NOTE: if the fullscreenScreen contains the menu bar but not the dock, we must still
    638         // auto-hide the dock, or an exception will be thrown.
    639         if ([[NSScreen screens] objectAtIndex:0] == fullscreenScreen)
    640             options |= (NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationAutoHideDock);
    641         // Check if the current screen contains the dock by comparing the screen's frame to its
    642         // visibleFrame; if a dock is present, the visibleFrame will differ. If the current screen
    643         // contains the dock, hide it.
    644         else if (!NSEqualRects([fullscreenScreen frame], [fullscreenScreen visibleFrame]))
    645             options |= NSApplicationPresentationAutoHideDock;
    646     }
    647 
    648     if ([NSApp respondsToSelector:@selector(setPresentationOptions:)])
    649         [NSApp setPresentationOptions:options];
    650     else
    651 #endif
    652         SetSystemUIMode(_isFullscreen ? kUIModeNormal : kUIModeAllHidden, 0);
    653 }
    654 
    655 #if !defined(BUILDING_ON_TIGER) // IOPMAssertionCreateWithName not defined on < 10.5
    656 - (void)_disableIdleDisplaySleep
    657 {
    658     if (_idleDisplaySleepAssertion == kIOPMNullAssertionID)
    659 #if defined(BUILDING_ON_LEOPARD) // IOPMAssertionCreateWithName is not defined in the 10.5 SDK
    660         IOPMAssertionCreate(kIOPMAssertionTypeNoDisplaySleep, kIOPMAssertionLevelOn, &_idleDisplaySleepAssertion);
    661 #else // IOPMAssertionCreate is depreciated in > 10.5
    662         IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep, kIOPMAssertionLevelOn, CFSTR("WebKit playing a video fullscreen."), &_idleDisplaySleepAssertion);
    663 #endif
    664 }
    665 
    666 - (void)_enableIdleDisplaySleep
    667 {
    668     if (_idleDisplaySleepAssertion != kIOPMNullAssertionID) {
    669         IOPMAssertionRelease(_idleDisplaySleepAssertion);
    670         _idleDisplaySleepAssertion = kIOPMNullAssertionID;
    671     }
    672 }
    673 
    674 - (void)_disableIdleSystemSleep
    675 {
    676     if (_idleSystemSleepAssertion == kIOPMNullAssertionID)
    677 #if defined(BUILDING_ON_LEOPARD) // IOPMAssertionCreateWithName is not defined in the 10.5 SDK
    678         IOPMAssertionCreate(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, &_idleSystemSleepAssertion);
    679 #else // IOPMAssertionCreate is depreciated in > 10.5
    680     IOPMAssertionCreateWithName(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, CFSTR("WebKit playing a video fullscreen."), &_idleSystemSleepAssertion);
    681 #endif
    682 }
    683 
    684 - (void)_enableIdleSystemSleep
    685 {
    686     if (_idleSystemSleepAssertion != kIOPMNullAssertionID) {
    687         IOPMAssertionRelease(_idleSystemSleepAssertion);
    688         _idleSystemSleepAssertion = kIOPMNullAssertionID;
    689     }
    690 }
    691 
    692 - (void)_enableTickleTimer
    693 {
    694     [_tickleTimer invalidate];
    695     [_tickleTimer release];
    696     _tickleTimer = [[NSTimer scheduledTimerWithTimeInterval:tickleTimerInterval target:self selector:@selector(_tickleTimerFired) userInfo:nil repeats:YES] retain];
    697 }
    698 
    699 - (void)_disableTickleTimer
    700 {
    701     [_tickleTimer invalidate];
    702     [_tickleTimer release];
    703     _tickleTimer = nil;
    704 }
    705 
    706 - (void)_tickleTimerFired
    707 {
    708     UpdateSystemActivity(OverallAct);
    709 }
    710 #endif
    711 
    712 - (void)_updatePowerAssertions
    713 {
    714 #if !defined(BUILDING_ON_TIGER)
    715     BOOL isPlaying = [self _isAnyMoviePlaying];
    716 
    717     if (isPlaying && _isFullscreen) {
    718         [self _disableIdleSystemSleep];
    719         [self _disableIdleDisplaySleep];
    720         [self _enableTickleTimer];
    721     } else {
    722         [self _enableIdleSystemSleep];
    723         [self _enableIdleDisplaySleep];
    724         [self _disableTickleTimer];
    725     }
    726 #endif
    727 }
    728 
    729 - (void)_requestExit
    730 {
    731     [self exitFullscreen];
    732     _forceDisableAnimation = NO;
    733 }
    734 
    735 - (void)_requestExitFullscreenWithAnimation:(BOOL)animation
    736 {
    737     _forceDisableAnimation = !animation;
    738     [self performSelector:@selector(_requestExit) withObject:nil afterDelay:0];
    739 
    740 }
    741 
    742 - (BOOL)_isAnyMoviePlaying
    743 {
    744     if (!_element)
    745         return NO;
    746 
    747     Node* nextNode = _element.get();
    748     while (nextNode)
    749     {
    750         if (nextNode->hasTagName(HTMLNames::videoTag)) {
    751             HTMLMediaElement* element = static_cast<HTMLMediaElement*>(nextNode);
    752             if (!element->paused() && !element->ended())
    753                 return YES;
    754         }
    755 
    756         nextNode = nextNode->traverseNextNode(_element.get());
    757     }
    758 
    759     return NO;
    760 }
    761 
    762 #pragma mark -
    763 #pragma mark Utility Functions
    764 
    765 - (WebFullscreenWindow *)_fullscreenWindow
    766 {
    767     return (WebFullscreenWindow *)[self window];
    768 }
    769 
    770 - (Document*)_document
    771 {
    772     return core([[[self webView] mainFrame] DOMDocument]);
    773 }
    774 
    775 - (CFTimeInterval)_animationDuration
    776 {
    777     static const CFTimeInterval defaultDuration = 0.5;
    778     CFTimeInterval duration = defaultDuration;
    779 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
    780     NSUInteger modifierFlags = [NSEvent modifierFlags];
    781 #else
    782     NSUInteger modifierFlags = [[NSApp currentEvent] modifierFlags];
    783 #endif
    784     if ((modifierFlags & NSControlKeyMask) == NSControlKeyMask)
    785         duration *= 2;
    786     if ((modifierFlags & NSShiftKeyMask) == NSShiftKeyMask)
    787         duration *= 10;
    788     if (_forceDisableAnimation) {
    789         // This will disable scale animation
    790         duration = 0;
    791     }
    792     return duration;
    793 }
    794 
    795 @end
    796 
    797 #pragma mark -
    798 @implementation WebFullscreenWindow
    799 
    800 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag
    801 {
    802     UNUSED_PARAM(aStyle);
    803     self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:bufferingType defer:flag];
    804     if (!self)
    805         return nil;
    806     [self setOpaque:NO];
    807     [self setBackgroundColor:[NSColor clearColor]];
    808     [self setIgnoresMouseEvents:NO];
    809     [self setAcceptsMouseMovedEvents:YES];
    810     [self setReleasedWhenClosed:NO];
    811     [self setHasShadow:YES];
    812 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
    813     [self setMovable:NO];
    814 #else
    815     [self setMovableByWindowBackground:NO];
    816 #endif
    817 
    818     NSView* contentView = [self contentView];
    819     _animationView = [[NSView alloc] initWithFrame:[contentView bounds]];
    820 
    821     CALayer* contentLayer = [[CALayer alloc] init];
    822     [_animationView setLayer:contentLayer];
    823     [_animationView setWantsLayer:YES];
    824     [_animationView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
    825     [contentView addSubview:_animationView];
    826 
    827     _backgroundLayer = [[CALayer alloc] init];
    828     [contentLayer addSublayer:_backgroundLayer];
    829 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
    830     [contentLayer setGeometryFlipped:YES];
    831 #else
    832     [contentLayer setSublayerTransform:CATransform3DMakeScale(1, -1, 1)];
    833 #endif
    834     [contentLayer setOpacity:0];
    835 
    836     [_backgroundLayer setBackgroundColor:CGColorGetConstantColor(kCGColorBlack)];
    837     [_backgroundLayer setOpacity:0];
    838     return self;
    839 }
    840 
    841 - (void)dealloc
    842 {
    843     [_animationView release];
    844     [_backgroundLayer release];
    845     [_rendererLayer release];
    846     [super dealloc];
    847 }
    848 
    849 - (BOOL)canBecomeKeyWindow
    850 {
    851     return YES;
    852 }
    853 
    854 - (void)keyDown:(NSEvent *)theEvent
    855 {
    856     if ([[theEvent charactersIgnoringModifiers] isEqual:@"\e"]) // Esacpe key-code
    857         [self cancelOperation:self];
    858     else [super keyDown:theEvent];
    859 }
    860 
    861 - (void)cancelOperation:(id)sender
    862 {
    863     UNUSED_PARAM(sender);
    864     [[self windowController] _requestExitFullscreenWithAnimation:YES];
    865 }
    866 
    867 - (CALayer*)rendererLayer
    868 {
    869     return _rendererLayer;
    870 }
    871 
    872 - (void)setRendererLayer:(CALayer *)rendererLayer
    873 {
    874     [CATransaction begin];
    875     [CATransaction setDisableActions:YES];
    876     [rendererLayer retain];
    877     [_rendererLayer removeFromSuperlayer];
    878     [_rendererLayer release];
    879     _rendererLayer = rendererLayer;
    880 
    881     if (_rendererLayer)
    882         [[[self animationView] layer] addSublayer:_rendererLayer];
    883     [CATransaction commit];
    884 }
    885 
    886 - (CALayer*)backgroundLayer
    887 {
    888     return _backgroundLayer;
    889 }
    890 
    891 - (NSView*)animationView
    892 {
    893     return _animationView;
    894 }
    895 @end
    896 
    897 #pragma mark -
    898 #pragma mark MediaEventListener
    899 
    900 MediaEventListener::MediaEventListener(WebFullScreenController* delegate)
    901     : EventListener(CPPEventListenerType)
    902     , delegate(delegate)
    903 {
    904 }
    905 
    906 PassRefPtr<MediaEventListener> MediaEventListener::create(WebFullScreenController* delegate)
    907 {
    908     return adoptRef(new MediaEventListener(delegate));
    909 }
    910 
    911 bool MediaEventListener::operator==(const EventListener& listener)
    912 {
    913     return this == &listener;
    914 }
    915 
    916 void MediaEventListener::handleEvent(ScriptExecutionContext* context, Event* event)
    917 {
    918     [delegate _updatePowerAssertions];
    919 }
    920 
    921 #endif /* ENABLE(FULLSCREEN_API) */
    922