diff options
Diffstat (limited to 'Source/WebKit/mac/WebView/WebFullScreenController.mm')
-rw-r--r-- | Source/WebKit/mac/WebView/WebFullScreenController.mm | 937 |
1 files changed, 326 insertions, 611 deletions
diff --git a/Source/WebKit/mac/WebView/WebFullScreenController.mm b/Source/WebKit/mac/WebView/WebFullScreenController.mm index aae92e841..eda499cd9 100644 --- a/Source/WebKit/mac/WebView/WebFullScreenController.mm +++ b/Source/WebKit/mac/WebView/WebFullScreenController.mm @@ -30,72 +30,61 @@ #import "WebNSWindowExtras.h" #import "WebPreferencesPrivate.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/DOMDocument.h> -#import <WebCore/DOMDocumentInternal.h> -#import <WebCore/DOMHTMLElement.h> -#import <WebCore/DOMWindow.h> -#import <WebCore/DisplaySleepDisabler.h> #import <WebCore/Document.h> -#import <WebCore/EventListener.h> -#import <WebCore/EventNames.h> +#import <WebCore/Element.h> +#import <WebCore/FloatRect.h> #import <WebCore/HTMLElement.h> -#import <WebCore/HTMLMediaElement.h> -#import <WebCore/HTMLNames.h> #import <WebCore/IntRect.h> -#import <WebCore/NodeList.h> -#import <WebCore/RenderBlock.h> +#import <WebCore/Page.h> #import <WebCore/RenderLayer.h> #import <WebCore/RenderLayerBacking.h> +#import <WebCore/RenderObject.h> +#import <WebCore/RenderView.h> #import <WebCore/SoftLinking.h> -#import <objc/objc-runtime.h> +#import <WebCore/WebCoreFullScreenWindow.h> +#import <WebCore/WebWindowAnimation.h> +#import <WebKitSystemInterface.h> #import <wtf/RetainPtr.h> #import <wtf/UnusedParam.h> -static NSString* const isEnteringFullscreenKey = @"isEnteringFullscreen"; - using namespace WebCore; -@interface WebFullscreenWindow : NSWindow -#ifndef BUILDING_ON_LEOPARD -<NSAnimationDelegate> -#endif +static const CFTimeInterval defaultAnimationDuration = 0.5; + +static IntRect screenRectOfContents(Element* element) { - NSView* _animationView; - - CALayer* _rendererLayer; - CALayer* _backgroundLayer; + ASSERT(element); + if (element->renderer() && element->renderer()->hasLayer() && element->renderer()->enclosingLayer()->isComposited()) { + FloatQuad contentsBox = static_cast<FloatRect>(element->renderer()->enclosingLayer()->backing()->contentsBox()); + contentsBox = element->renderer()->localToAbsoluteQuad(contentsBox); + return element->renderer()->view()->frameView()->contentsToScreen(contentsBox.enclosingBoundingBox()); + } + return element->screenRect(); } -- (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; +@interface WebFullScreenController(Private)<NSAnimationDelegate> +- (void)_updateMenuAndDockForFullScreen; +- (void)_swapView:(NSView*)view with:(NSView*)otherView; - (Document*)_document; -- (CFTimeInterval)_animationDuration; -- (BOOL)_isAnyMoviePlaying; +- (void)_startEnterFullScreenAnimationWithDuration:(NSTimeInterval)duration; +- (void)_startExitFullScreenAnimationWithDuration:(NSTimeInterval)duration; +@end + +#if defined(BUILDING_ON_LEOPARD) || defined(BUILDING_ON_SNOW_LEOPARD) +@interface NSWindow(convertRectToScreenForLeopardAndSnowLeopard) +- (NSRect)convertRectToScreen:(NSRect)aRect; +@end + +@implementation NSWindow(convertRectToScreenForLeopardAndSnowLeopard) +- (NSRect)convertRectToScreen:(NSRect)rect +{ + NSRect frame = [self frame]; + rect.origin.x += frame.origin.x; + rect.origin.y += frame.origin.y; + return rect; +} @end +#endif @interface NSWindow(IsOnActiveSpaceAdditionForTigerAndLeopard) - (BOOL)isOnActiveSpace; @@ -108,21 +97,21 @@ private: - (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]; + NSWindow *window = [[WebCoreFullScreenWindow 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 { [self setWebView:nil]; - [_placeholderView release]; + + [NSObject cancelPreviousPerformRequestsWithTarget:self]; [[NSNotificationCenter defaultCenter] removeObserver:self]; [super dealloc]; @@ -130,7 +119,8 @@ private: - (void)windowDidLoad { - + [super windowDidLoad]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidResignActive:) name:NSApplicationDidResignActiveNotification object:NSApp]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidChangeScreenParameters:) name:NSApplicationDidChangeScreenParametersNotification object:NSApp]; } @@ -157,157 +147,25 @@ private: - (void)setElement:(PassRefPtr<Element>)element { - // 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); - } - } } -- (RenderBox*)renderer -{ - return _renderer; -} +#pragma mark - +#pragma mark NSWindowController overrides -- (void)setRenderer:(RenderBox*)renderer +- (void)cancelOperation:(id)sender { - _renderer = renderer; + [self performSelector:@selector(exitFullScreen) withObject:nil afterDelay:0]; } #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]; - - WebView *webView = [self webView]; - NSWindow *webWindow = [webView window]; - NSResponder *webWindowFirstResponder = [webWindow firstResponder]; - - // 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]) { - [_placeholderView setFrame:[webView frame]]; - [_placeholderView setAutoresizingMask:[webView autoresizingMask]]; - [_placeholderView removeFromSuperview]; - [[webView superview] replaceSubview:webView with:_placeholderView]; - - [[[self window] contentView] addSubview:webView]; - [[self window] makeResponder:webWindowFirstResponder firstResponderIfDescendantOfView:webView]; - [webView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; - [webView setFrame:[(NSView *)[[self window] contentView] bounds]]; - } - -#if !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD) - // In Lion, NSWindow will animate into and out of orderOut operations. Suppress that - // behavior here, making sure to reset the animation behavior afterward. - NSWindowAnimationBehavior animationBehavior = [webWindow animationBehavior]; - [webWindow setAnimationBehavior:NSWindowAnimationBehaviorNone]; -#endif - [webWindow orderOut:self]; -#if !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD) - [webWindow setAnimationBehavior:animationBehavior]; -#endif - - WebFullscreenWindow* window = [self _fullscreenWindow]; - [window setBackgroundColor:[NSColor blackColor]]; - [window setOpaque:YES]; - - [CATransaction begin]; - [CATransaction setValue:[NSNumber numberWithBool:YES] forKey:kCATransactionDisableActions]; - [[[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]; + NSWindow* fullscreenWindow = [self window]; BOOL isOnActiveSpace = ([fullscreenWindow respondsToSelector:@selector(isOnActiveSpace)] ? [fullscreenWindow isOnActiveSpace] : YES); // Replicate the QuickTime Player (X) behavior when losing active application status: @@ -315,7 +173,7 @@ private: // 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]; + [self cancelOperation:self]; } - (void)applicationDidChangeScreenParameters:(NSNotification*)notification @@ -324,306 +182,230 @@ private: // 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]; + [self _updateMenuAndDockForFullScreen]; NSWindow* window = [self window]; - [window setFrame:[[window screen] frame] display:YES]; + NSRect screenFrame = [[window screen] frame]; + [window setFrame:screenFrame display:YES]; + [_backgroundWindow.get() setFrame:screenFrame display:YES]; } #pragma mark - #pragma mark Exposed Interface -- (void)enterFullscreen:(NSScreen *)screen +- (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); + if (_isFullScreen) + return; + _isFullScreen = YES; - NSDisableScreenUpdates(); + [self _updateMenuAndDockForFullScreen]; 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 webViewFrame = [[_webView window] convertRectToScreen: + [_webView convertRect:[_webView frame] toView:nil]]; - NSRect elementFrame = _element->screenRect(); + // Flip coordinate system: + webViewFrame.origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(webViewFrame); - // 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]; + CGWindowID windowID = [[_webView window] windowNumber]; + RetainPtr<CGImageRef> webViewContents = adoptCF(CGWindowListCreateImage(NSRectToCGRect(webViewFrame), kCGWindowListOptionIncludingWindow, windowID, kCGWindowImageShouldBeOpaque)); - // 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; - } + // Screen updates to be re-enabled in beganEnterFullScreenWithInitialFrame:finalFrame: + NSDisableScreenUpdates(); + [[self window] setAutodisplay:NO]; - // 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); + NSResponder *webWindowFirstResponder = [[_webView window] firstResponder]; + [[self window] setFrame:screenFrame display:NO]; + + _initialFrame = screenRectOfContents(_element.get()); + + // Swap the webView placeholder into place. + if (!_webViewPlaceholder) { + _webViewPlaceholder.adoptNS([[NSView alloc] init]); + [_webViewPlaceholder.get() setLayer:[CALayer layer]]; + [_webViewPlaceholder.get() setWantsLayer:YES]; + } + [[_webViewPlaceholder.get() layer] setContents:(id)webViewContents.get()]; + [self _swapView:_webView with:_webViewPlaceholder.get()]; - // Cause the document to layout, thus calculating a new fullscreen element size: - [self _document]->updateLayout(); + // Then insert the WebView into the full screen window + NSView* contentView = [[self window] contentView]; + [contentView addSubview:_webView positioned:NSWindowBelow relativeTo:nil]; + [_webView setFrame:[contentView bounds]]; - // 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(destinationFrame); - - 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 setValue:[NSNumber numberWithDouble:duration] forKey:kCATransactionAnimationDuration]; - [backgroundLayer setOpacity:1]; - [CATransaction commit]; + [[self window] makeResponder:webWindowFirstResponder firstResponderIfDescendantOfView:_webView]; - // 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 setValue:[NSNumber numberWithBool:YES] forKey:kCATransactionDisableActions]; - [rendererLayer setTransform:CATransform3DIdentity]; - [rendererLayer addAnimation:zoomAnimation forKey:@"zoom"]; - [backgroundLayer setFrame:NSRectToCGRect(backgroundBounds)]; - [CATransaction commit]; + [self _document]->webkitWillEnterFullScreenForElement(_element.get()); + [self _document]->setAnimatingFullScreen(true); + [self _document]->updateLayout(); + + _finalFrame = screenRectOfContents(_element.get()); - NSEnableScreenUpdates(); + [self _updateMenuAndDockForFullScreen]; + + [self _startEnterFullScreenAnimationWithDuration:defaultAnimationDuration]; + + _isEnteringFullScreen = true; } -- (void)exitFullscreen +- (void)finishedEnterFullScreenAnimation:(bool)completed { - 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(); - + if (!_isEnteringFullScreen) + return; + _isEnteringFullScreen = NO; - // 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]; - NSResponder *fullScreenWindowFirstResponder = [[self window] firstResponder]; - [webView setFrame:[_placeholderView frame]]; - [webView setAutoresizingMask:[_placeholderView autoresizingMask]]; - [webView removeFromSuperview]; - [[_placeholderView superview] replaceSubview:_placeholderView with:webView]; - [[webView window] makeResponder:fullScreenWindowFirstResponder firstResponderIfDescendantOfView:webView]; + if (completed) { + // Screen updates to be re-enabled at the end of this block + NSDisableScreenUpdates(); + [self _document]->setAnimatingFullScreen(false); + [self _document]->webkitDidEnterFullScreenForElement(_element.get()); - NSWindow *webWindow = [[self webView] window]; + NSRect windowBounds = [[self window] frame]; + windowBounds.origin = NSZeroPoint; + WKWindowSetClipRect([self window], windowBounds); + + NSWindow *webWindow = [_webViewPlaceholder.get() window]; #if !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD) // In Lion, NSWindow will animate into and out of orderOut operations. Suppress that // behavior here, making sure to reset the animation behavior afterward. NSWindowAnimationBehavior animationBehavior = [webWindow animationBehavior]; [webWindow setAnimationBehavior:NSWindowAnimationBehaviorNone]; #endif - // The user may have moved the fullscreen window in Spaces, so temporarily change - // the collectionBehavior of the fullscreen window: + [webWindow orderOut:self]; +#if !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD) + [webWindow setAnimationBehavior:animationBehavior]; +#endif + + [_fadeAnimation.get() stopAnimation]; + [_fadeAnimation.get() setWindow:nil]; + _fadeAnimation = nullptr; + + [_backgroundWindow.get() orderOut:self]; + [_backgroundWindow.get() setFrame:NSZeroRect display:YES]; + NSEnableScreenUpdates(); + } else + [_scaleAnimation.get() stopAnimation]; +} + +- (void)exitFullScreen +{ + if (!_isFullScreen) + return; + _isFullScreen = NO; + + // Screen updates to be re-enabled in beganExitFullScreenWithInitialFrame:finalFrame: + NSDisableScreenUpdates(); + [[self window] setAutodisplay:NO]; + + _finalFrame = screenRectOfContents(_element.get()); + + [self _document]->webkitWillExitFullScreenForElement(_element.get()); + [self _document]->setAnimatingFullScreen(true); + + if (_isEnteringFullScreen) + [self finishedEnterFullScreenAnimation:NO]; + + [self _updateMenuAndDockForFullScreen]; + + NSWindow* webWindow = [_webViewPlaceholder.get() window]; +#if !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD) + // In Lion, NSWindow will animate into and out of orderOut operations. Suppress that + // behavior here, making sure to reset the animation behavior afterward. + NSWindowAnimationBehavior animationBehavior = [webWindow animationBehavior]; + [webWindow setAnimationBehavior:NSWindowAnimationBehaviorNone]; +#endif + // If the user has moved the fullScreen window into a new space, temporarily change + // the collectionBehavior of the webView's window so that it is pulled into the active space: + if (![webWindow isOnActiveSpace]) { NSWindowCollectionBehavior behavior = [webWindow collectionBehavior]; [webWindow setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces]; [webWindow orderWindow:NSWindowBelow relativeTo:[[self window] windowNumber]]; [webWindow setCollectionBehavior:behavior]; + } else + [webWindow orderWindow:NSWindowBelow relativeTo:[[self window] windowNumber]]; #if !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD) - [webWindow setAnimationBehavior:animationBehavior]; + [webWindow setAnimationBehavior:animationBehavior]; #endif - // 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 setValue:[NSNumber numberWithBool:YES] forKey:kCATransactionDisableActions]; - 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]); + [self _startExitFullScreenAnimationWithDuration:defaultAnimationDuration]; + _isExitingFullScreen = YES; +} - // 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()); - } +- (void)finishedExitFullScreenAnimation:(bool)completed +{ + if (!_isExitingFullScreen) + return; + _isExitingFullScreen = NO; - [self _document]->webkitWillExitFullScreenForElement(_element.get()); - [self _document]->updateLayout(); + [self _updateMenuAndDockForFullScreen]; - // 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]; + // Screen updates to be re-enabled at the end of this function + NSDisableScreenUpdates(); - // 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]->setAnimatingFullScreen(false); + [self _document]->webkitDidExitFullScreenForElement(_element.get()); - [self _document]->setFullScreenRendererBackgroundColor(Color::transparent); - - rendererGraphics->syncCompositingState(layerEndFrame); - - CALayer* rendererLayer = rendererGraphics->platformLayer(); - [[self _fullscreenWindow] setRendererLayer:rendererLayer]; + NSResponder *firstResponder = [[self window] firstResponder]; + [self _swapView:_webViewPlaceholder.get() with:_webView]; + [[_webView window] makeResponder:firstResponder firstResponderIfDescendantOfView:_webView]; - // 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 setValue:[NSNumber numberWithDouble:duration] forKey:kCATransactionAnimationDuration]; - [backgroundLayer setOpacity:0]; - [CATransaction commit]; + NSRect windowBounds = [[self window] frame]; + windowBounds.origin = NSZeroPoint; + WKWindowSetClipRect([self window], windowBounds); + + [[self window] orderOut:self]; + [[self window] setFrame:NSZeroRect display:YES]; - 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]; + [_fadeAnimation.get() stopAnimation]; + [_fadeAnimation.get() setWindow:nil]; + _fadeAnimation = nullptr; - [rendererLayer addAnimation:zoomAnimation forKey:@"zoom"]; + [_backgroundWindow.get() orderOut:self]; + [_backgroundWindow.get() setFrame:NSZeroRect display:YES]; NSEnableScreenUpdates(); } +- (void)close +{ + // We are being asked to close rapidly, most likely because the page + // has closed or the web process has crashed. Just walk through our + // normal exit full screen sequence, but don't wait to be called back + // in response. + if (_isFullScreen) + [self exitFullScreen]; + + if (_isExitingFullScreen) + [self finishedExitFullScreenAnimation:YES]; + + [super close]; +} + +#pragma mark - +#pragma mark NSAnimation delegate + +- (void)animationDidEnd:(NSAnimation*)animation +{ + if (_isFullScreen) + [self finishedEnterFullScreenAnimation:YES]; + else + [self finishedExitFullScreenAnimation:YES]; +} + #pragma mark - #pragma mark Internal Interface -- (void)_updateMenuAndDockForFullscreen +- (void)_updateMenuAndDockForFullScreen { // NSApplicationPresentationOptions is available on > 10.6 only: #ifndef BUILDING_ON_LEOPARD NSApplicationPresentationOptions options = NSApplicationPresentationDefault; NSScreen* fullscreenScreen = [[self window] screen]; - if (_isFullscreen) { + 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. @@ -640,212 +422,145 @@ private: [NSApp setPresentationOptions:options]; else #endif - SetSystemUIMode(_isFullscreen ? kUIModeNormal : kUIModeAllHidden, 0); + SetSystemUIMode(_isFullScreen ? kUIModeNormal : kUIModeAllHidden, 0); } -- (void)_updatePowerAssertions -{ - BOOL isPlaying = [self _isAnyMoviePlaying]; - - if (isPlaying && _isFullscreen) { - if (!_displaySleepDisabler) - _displaySleepDisabler = DisplaySleepDisabler::create("com.apple.WebKit - Fullscreen video"); - } else - _displaySleepDisabler = nullptr; -} - -- (void)_requestExit -{ - [self exitFullscreen]; - _forceDisableAnimation = NO; -} +#pragma mark - +#pragma mark Utility Functions -- (void)_requestExitFullscreenWithAnimation:(BOOL)animation +- (Document*)_document { - _forceDisableAnimation = !animation; - [self performSelector:@selector(_requestExit) withObject:nil afterDelay:0]; - + return _element->document(); } -- (BOOL)_isAnyMoviePlaying +- (void)_swapView:(NSView*)view with:(NSView*)otherView { -#if ENABLE(VIDEO) - if (!_element) - return NO; - - Node* nextNode = _element.get(); - while (nextNode) - { - if (nextNode->hasTagName(HTMLNames::videoTag) && static_cast<Element*>(nextNode)->isMediaElement()) { - HTMLMediaElement* element = static_cast<HTMLMediaElement*>(nextNode); - if (!element->paused() && !element->ended()) - return YES; - } - - nextNode = nextNode->traverseNextNode(_element.get()); - } -#endif - - return NO; + [CATransaction begin]; + [CATransaction setDisableActions:YES]; + [otherView setFrame:[view frame]]; + [otherView setAutoresizingMask:[view autoresizingMask]]; + [otherView removeFromSuperview]; + [[view superview] replaceSubview:view with:otherView]; + [CATransaction commit]; } -#pragma mark - -#pragma mark Utility Functions - -- (WebFullscreenWindow *)_fullscreenWindow +static RetainPtr<NSWindow> createBackgroundFullscreenWindow(NSRect frame) { - return (WebFullscreenWindow *)[self window]; + NSWindow *window = [[NSWindow alloc] initWithContentRect:frame styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]; + [window setOpaque:YES]; + [window setBackgroundColor:[NSColor blackColor]]; + [window setReleasedWhenClosed:NO]; + return adoptNS(window); } -- (Document*)_document +static NSRect windowFrameFromApparentFrames(NSRect screenFrame, NSRect initialFrame, NSRect finalFrame) { - return _element->document(); + NSRect initialWindowFrame; + CGFloat xScale = NSWidth(screenFrame) / NSWidth(finalFrame); + CGFloat yScale = NSHeight(screenFrame) / NSHeight(finalFrame); + CGFloat xTrans = NSMinX(screenFrame) - NSMinX(finalFrame); + CGFloat yTrans = NSMinY(screenFrame) - NSMinY(finalFrame); + initialWindowFrame.size = NSMakeSize(NSWidth(initialFrame) * xScale, NSHeight(initialFrame) * yScale); + initialWindowFrame.origin = NSMakePoint + ( NSMinX(initialFrame) + xTrans / (NSWidth(finalFrame) / NSWidth(initialFrame)) + , NSMinY(initialFrame) + yTrans / (NSHeight(finalFrame) / NSHeight(initialFrame))); + return initialWindowFrame; } -- (CFTimeInterval)_animationDuration +- (void)_startEnterFullScreenAnimationWithDuration:(NSTimeInterval)duration { - static const CFTimeInterval defaultDuration = 0.5; - CFTimeInterval duration = defaultDuration; -#ifndef 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; + NSRect screenFrame = [[[self window] screen] frame]; + NSRect initialWindowFrame = windowFrameFromApparentFrames(screenFrame, _initialFrame, _finalFrame); + + _scaleAnimation = adoptNS([[WebWindowScaleAnimation alloc] initWithHintedDuration:duration window:[self window] initalFrame:initialWindowFrame finalFrame:screenFrame]); + + [_scaleAnimation.get() setAnimationBlockingMode:NSAnimationNonblocking]; + [_scaleAnimation.get() setDelegate:self]; + [_scaleAnimation.get() setCurrentProgress:0]; + [_scaleAnimation.get() startAnimation]; + + // WKWindowSetClipRect takes window coordinates, so convert from screen coordinates here: + NSRect finalBounds = _finalFrame; + finalBounds.origin = [[self window] convertScreenToBase:finalBounds.origin]; + WKWindowSetClipRect([self window], finalBounds); + + [[self window] makeKeyAndOrderFront:self]; + + if (!_backgroundWindow) + _backgroundWindow = createBackgroundFullscreenWindow(screenFrame); + else + [_backgroundWindow.get() setFrame:screenFrame display:NO]; + + CGFloat currentAlpha = 0; + if (_fadeAnimation) { + currentAlpha = [_fadeAnimation.get() currentAlpha]; + [_fadeAnimation.get() stopAnimation]; + [_fadeAnimation.get() setWindow:nil]; } - return duration; + + _fadeAnimation = adoptNS([[WebWindowFadeAnimation alloc] initWithDuration:duration + window:_backgroundWindow.get() + initialAlpha:currentAlpha + finalAlpha:1]); + [_fadeAnimation.get() setAnimationBlockingMode:NSAnimationNonblocking]; + [_fadeAnimation.get() setCurrentProgress:0]; + [_fadeAnimation.get() startAnimation]; + + [_backgroundWindow.get() orderWindow:NSWindowBelow relativeTo:[[self window] windowNumber]]; + + [[self window] setAutodisplay:YES]; + [[self window] displayIfNeeded]; + // Screen updates disabled in enterFullScreen: + NSEnableScreenUpdates(); } -@end - -#pragma mark - -@implementation WebFullscreenWindow - -- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag +- (void)_startExitFullScreenAnimationWithDuration:(NSTimeInterval)duration { - 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]; -#ifndef BUILDING_ON_LEOPARD - [self setMovable:NO]; -#else - [self setMovableByWindowBackground:NO]; -#endif + NSRect screenFrame = [[[self window] screen] frame]; + NSRect initialWindowFrame = windowFrameFromApparentFrames(screenFrame, _initialFrame, _finalFrame); - NSView* contentView = [self contentView]; - _animationView = [[NSView alloc] initWithFrame:[contentView bounds]]; + NSRect currentFrame = _scaleAnimation ? [_scaleAnimation.get() currentFrame] : [[self window] frame]; + _scaleAnimation = adoptNS([[WebWindowScaleAnimation alloc] initWithHintedDuration:duration window:[self window] initalFrame:currentFrame finalFrame:initialWindowFrame]); - RetainPtr<CALayer> contentLayer(AdoptNS, [[CALayer alloc] init]); - [_animationView setLayer:contentLayer.get()]; - [_animationView setWantsLayer:YES]; - [_animationView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; - [contentView addSubview:_animationView]; + [_scaleAnimation.get() setAnimationBlockingMode:NSAnimationNonblocking]; + [_scaleAnimation.get() setDelegate:self]; + [_scaleAnimation.get() setCurrentProgress:0]; + [_scaleAnimation.get() startAnimation]; - _backgroundLayer = [[CALayer alloc] init]; - [contentLayer.get() addSublayer:_backgroundLayer]; -#ifndef BUILDING_ON_LEOPARD - [contentLayer.get() setGeometryFlipped:YES]; -#else - [contentLayer.get() setSublayerTransform:CATransform3DMakeScale(1, -1, 1)]; -#endif - [contentLayer.get() setOpacity:0]; + if (!_backgroundWindow) + _backgroundWindow = createBackgroundFullscreenWindow(screenFrame); + else + [_backgroundWindow.get() setFrame:screenFrame display:NO]; - [_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 setValue:[NSNumber numberWithBool:YES] forKey:kCATransactionDisableActions]; - [rendererLayer retain]; - [_rendererLayer removeFromSuperlayer]; - [_rendererLayer release]; - _rendererLayer = rendererLayer; - - if (_rendererLayer) - [[[self animationView] layer] addSublayer:_rendererLayer]; - [CATransaction commit]; -} + CGFloat currentAlpha = 1; + if (_fadeAnimation) { + currentAlpha = [_fadeAnimation.get() currentAlpha]; + [_fadeAnimation.get() stopAnimation]; + [_fadeAnimation.get() setWindow:nil]; + } + _fadeAnimation = adoptNS([[WebWindowFadeAnimation alloc] initWithDuration:duration + window:_backgroundWindow.get() + initialAlpha:currentAlpha + finalAlpha:0]); + [_fadeAnimation.get() setAnimationBlockingMode:NSAnimationNonblocking]; + [_fadeAnimation.get() setCurrentProgress:0]; + [_fadeAnimation.get() startAnimation]; + + [_backgroundWindow.get() orderWindow:NSWindowBelow relativeTo:[[self window] windowNumber]]; + + // WKWindowSetClipRect takes window coordinates, so convert from screen coordinates here: + NSRect finalBounds = _finalFrame; + finalBounds.origin = [[self window] convertScreenToBase:finalBounds.origin]; + WKWindowSetClipRect([self window], finalBounds); + + [[self window] setAutodisplay:YES]; + [[self window] displayIfNeeded]; -- (CALayer*)backgroundLayer -{ - return _backgroundLayer; + // Screen updates disabled in exitFullScreen: + NSEnableScreenUpdates(); } -- (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) */ |