| /* |
| * 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) */ |