diff options
author | Ben Murdoch <benm@google.com> | 2011-05-13 16:23:25 +0100 |
---|---|---|
committer | Ben Murdoch <benm@google.com> | 2011-05-16 11:35:02 +0100 |
commit | 65f03d4f644ce73618e5f4f50dd694b26f55ae12 (patch) | |
tree | f478babb801e720de7bfaee23443ffe029f58731 /Source/WebKit/mac/WebView/WebFullScreenController.mm | |
parent | 47de4a2fb7262c7ebdb9cd133ad2c54c187454d0 (diff) | |
download | external_webkit-65f03d4f644ce73618e5f4f50dd694b26f55ae12.zip external_webkit-65f03d4f644ce73618e5f4f50dd694b26f55ae12.tar.gz external_webkit-65f03d4f644ce73618e5f4f50dd694b26f55ae12.tar.bz2 |
Merge WebKit at r75993: Initial merge by git.
Change-Id: I602bbdc3974787a3b0450456a30a7868286921c3
Diffstat (limited to 'Source/WebKit/mac/WebView/WebFullScreenController.mm')
-rw-r--r-- | Source/WebKit/mac/WebView/WebFullScreenController.mm | 924 |
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) */ |