diff -r 000000000000 -r 4f2f89ce4247 WebKit/mac/WebView/WebVideoFullscreenController.mm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebKit/mac/WebView/WebVideoFullscreenController.mm Fri Sep 17 09:02:29 2010 +0300 @@ -0,0 +1,579 @@ +/* + * Copyright (C) 2009 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(VIDEO) + +#import "WebVideoFullscreenController.h" + +#import "WebTypesInternal.h" +#import "WebVideoFullscreenHUDWindowController.h" +#import "WebWindowAnimation.h" +#import +#import +#import +#import +#import +#import + +SOFT_LINK_FRAMEWORK(QTKit) +SOFT_LINK_CLASS(QTKit, QTMovieLayer) + +SOFT_LINK_POINTER(QTKit, QTMovieRateDidChangeNotification, NSString *) + +#define QTMovieRateDidChangeNotification getQTMovieRateDidChangeNotification() + +@interface WebVideoFullscreenWindow : NSWindow +#if !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_TIGER) + +#endif +{ + SEL _controllerActionOnAnimationEnd; + WebWindowScaleAnimation *_fullscreenAnimation; // (retain) +} +- (void)animateFromRect:(NSRect)startRect toRect:(NSRect)endRect withSubAnimation:(NSAnimation *)subAnimation controllerAction:(SEL)controllerAction; +@end + +@interface WebVideoFullscreenController(HUDWindowControllerDelegate) +- (void)requestExitFullscreenWithAnimation:(BOOL)animation; +- (void)updateMenuAndDockForFullscreen; +- (void)updatePowerAssertions; +@end + +@interface NSWindow(IsOnActiveSpaceAdditionForTigerAndLeopard) +- (BOOL)isOnActiveSpace; +@end + +@implementation WebVideoFullscreenController +- (id)init +{ + // Do not defer window creation, to make sure -windowNumber is created (needed by WebWindowScaleAnimation). + NSWindow *window = [[WebVideoFullscreenWindow alloc] initWithContentRect:NSZeroRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]; + self = [super initWithWindow:window]; + [window release]; + if (!self) + return nil; + [self windowDidLoad]; + return self; + +} +- (void)dealloc +{ + ASSERT(!_backgroundFullscreenWindow); + ASSERT(!_fadeAnimation); + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [super dealloc]; +} + +- (WebVideoFullscreenWindow *)fullscreenWindow +{ + return (WebVideoFullscreenWindow *)[super window]; +} + +- (void)windowDidLoad +{ +#ifdef BUILDING_ON_TIGER + // WebVideoFullscreenController is not supported on Tiger: + ASSERT_NOT_REACHED(); +#else + WebVideoFullscreenWindow *window = [self fullscreenWindow]; + QTMovieLayer *layer = [[getQTMovieLayerClass() alloc] init]; + [[window contentView] setLayer:layer]; + [[window contentView] setWantsLayer:YES]; + if (_mediaElement && _mediaElement->platformMedia().type == WebCore::PlatformMedia::QTMovieType) + [layer setMovie:_mediaElement->platformMedia().media.qtMovie]; + [window setHasShadow:YES]; // This is nicer with a shadow. + [window setLevel:NSPopUpMenuWindowLevel-1]; + [layer release]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidResignActive:) name:NSApplicationDidResignActiveNotification object:NSApp]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidChangeScreenParameters:) name:NSApplicationDidChangeScreenParametersNotification object:NSApp]; +#endif +} + +- (WebCore::HTMLMediaElement*)mediaElement +{ + return _mediaElement.get(); +} + +- (void)setMediaElement:(WebCore::HTMLMediaElement*)mediaElement +{ +#ifdef BUILDING_ON_TIGER + // WebVideoFullscreenController is not supported on Tiger: + ASSERT_NOT_REACHED(); +#else + _mediaElement = mediaElement; + if ([self isWindowLoaded]) { + QTMovie *movie = _mediaElement->platformMedia().type == WebCore::PlatformMedia::QTMovieType ? _mediaElement->platformMedia().media.qtMovie : 0; + QTMovieLayer *movieLayer = (QTMovieLayer *)[[[self fullscreenWindow] contentView] layer]; + + ASSERT(movieLayer && [movieLayer isKindOfClass:[getQTMovieLayerClass() class]]); + ASSERT(movie); + [movieLayer setMovie:movie]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(rateChanged:) + name:QTMovieRateDidChangeNotification + object:movie]; + } +#endif +} + +- (id )delegate +{ + return _delegate; +} + +- (void)setDelegate:(id )delegate +{ + _delegate = delegate; +} + +- (CGFloat)clearFadeAnimation +{ + [_fadeAnimation stopAnimation]; + CGFloat previousAlpha = [_fadeAnimation currentAlpha]; + [_fadeAnimation setWindow:nil]; + [_fadeAnimation release]; + _fadeAnimation = nil; + return previousAlpha; +} + +- (void)windowDidExitFullscreen +{ + [self clearFadeAnimation]; + [[self window] close]; + [self setWindow:nil]; + [self updateMenuAndDockForFullscreen]; + [self updatePowerAssertions]; + [_hudController setDelegate:nil]; + [_hudController release]; + _hudController = nil; + [_backgroundFullscreenWindow close]; + [_backgroundFullscreenWindow release]; + _backgroundFullscreenWindow = nil; + + [self autorelease]; // Associated -retain is in -exitFullscreen. + _isEndingFullscreen = NO; +} + +- (void)windowDidEnterFullscreen +{ + [self clearFadeAnimation]; + + ASSERT(!_hudController); + _hudController = [[WebVideoFullscreenHUDWindowController alloc] init]; + [_hudController setDelegate:self]; + + [self updateMenuAndDockForFullscreen]; + [self updatePowerAssertions]; + [NSCursor setHiddenUntilMouseMoves:YES]; + + // Give the HUD keyboard focus initially + [_hudController fadeWindowIn]; +} + +- (NSRect)mediaElementRect +{ + return _mediaElement->screenRect(); +} + +- (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]; +} + + +#pragma mark - +#pragma mark Exposed Interface + +static void constrainFrameToRatioOfFrame(NSRect *frameToConstrain, const NSRect *frame) +{ + // Keep a constrained aspect ratio for the destination window + double originalRatio = frame->size.width / frame->size.height; + double newRatio = frameToConstrain->size.width / frameToConstrain->size.height; + if (newRatio > originalRatio) { + double newWidth = originalRatio * frameToConstrain->size.height; + double diff = frameToConstrain->size.width - newWidth; + frameToConstrain->size.width = newWidth; + frameToConstrain->origin.x += diff / 2; + } else { + double newHeight = frameToConstrain->size.width / originalRatio; + double diff = frameToConstrain->size.height - newHeight; + frameToConstrain->size.height = newHeight; + frameToConstrain->origin.y += diff / 2; + } +} + +static NSWindow *createBackgroundFullscreenWindow(NSRect frame, int level) +{ + NSWindow *window = [[NSWindow alloc] initWithContentRect:frame styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]; + [window setOpaque:YES]; + [window setBackgroundColor:[NSColor blackColor]]; + [window setLevel:level]; + [window setHidesOnDeactivate:YES]; + [window setReleasedWhenClosed:NO]; + return window; +} + +- (void)setupFadeAnimationIfNeededAndFadeIn:(BOOL)fadeIn +{ + CGFloat initialAlpha = fadeIn ? 0 : 1; + if (_fadeAnimation) { + // Make sure we support queuing animation if the previous one isn't over yet + initialAlpha = [self clearFadeAnimation]; + } + if (!_forceDisableAnimation) + _fadeAnimation = [[WebWindowFadeAnimation alloc] initWithDuration:0.2 window:_backgroundFullscreenWindow initialAlpha:initialAlpha finalAlpha:fadeIn ? 1 : 0]; +} + +- (void)enterFullscreen:(NSScreen *)screen +{ + if (!screen) + screen = [NSScreen mainScreen]; + + NSRect frame = [self mediaElementRect]; + NSRect endFrame = [screen frame]; + constrainFrameToRatioOfFrame(&endFrame, &frame); + + // Create a black window if needed + if (!_backgroundFullscreenWindow) + _backgroundFullscreenWindow = createBackgroundFullscreenWindow([screen frame], [[self window] level]-1); + else + [_backgroundFullscreenWindow setFrame:[screen frame] display:NO]; + + [self setupFadeAnimationIfNeededAndFadeIn:YES]; + if (_forceDisableAnimation) { + // This will disable scale animation + frame = NSZeroRect; + } + [[self fullscreenWindow] animateFromRect:frame toRect:endFrame withSubAnimation:_fadeAnimation controllerAction:@selector(windowDidEnterFullscreen)]; + + [_backgroundFullscreenWindow orderWindow:NSWindowBelow relativeTo:[[self fullscreenWindow] windowNumber]]; +} + +- (void)exitFullscreen +{ + if (_isEndingFullscreen) + return; + _isEndingFullscreen = YES; + [_hudController closeWindow]; + + NSRect endFrame = [self mediaElementRect]; + + [self setupFadeAnimationIfNeededAndFadeIn:NO]; + if (_forceDisableAnimation) { + // This will disable scale animation + endFrame = NSZeroRect; + } + + // 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]; + + [[self fullscreenWindow] animateFromRect:[[self window] frame] toRect:endFrame withSubAnimation:_fadeAnimation controllerAction:@selector(windowDidExitFullscreen)]; +} + +- (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]; + [[self window] setFrame:[[[self window] screen] frame] display:YES]; +} + +- (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 (!_isEndingFullscreen) { + // 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(_isEndingFullscreen ? 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; + } +} +#endif + +- (void)updatePowerAssertions +{ +#if !defined(BUILDING_ON_TIGER) + float rate = 0; + if (_mediaElement && _mediaElement->platformMedia().type == WebCore::PlatformMedia::QTMovieType) + rate = [_mediaElement->platformMedia().media.qtMovie rate]; + + if (rate && !_isEndingFullscreen) { + [self _disableIdleSystemSleep]; + [self _disableIdleDisplaySleep]; + } else { + [self _enableIdleSystemSleep]; + [self _enableIdleDisplaySleep]; + } +#endif +} + +#pragma mark - +#pragma mark Window callback + +- (void)_requestExit +{ + if (_mediaElement) + _mediaElement->exitFullscreen(); + _forceDisableAnimation = NO; +} + +- (void)requestExitFullscreenWithAnimation:(BOOL)animation +{ + if (_isEndingFullscreen) + return; + + _forceDisableAnimation = !animation; + [self performSelector:@selector(_requestExit) withObject:nil afterDelay:0]; + +} + +- (void)requestExitFullscreen +{ + [self requestExitFullscreenWithAnimation:YES]; +} + +- (void)fadeHUDIn +{ + [_hudController fadeWindowIn]; +} + +#pragma mark - +#pragma mark QTMovie callbacks + +- (void)rateChanged:(NSNotification *)unusedNotification +{ + UNUSED_PARAM(unusedNotification); + [_hudController updateRate]; + [self updatePowerAssertions]; +} + +@end + +@implementation WebVideoFullscreenWindow + +- (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 setHidesOnDeactivate:YES]; + [self setIgnoresMouseEvents:NO]; + [self setAcceptsMouseMovedEvents:YES]; + return self; +} + +- (void)dealloc +{ + ASSERT(!_fullscreenAnimation); + [super dealloc]; +} + +- (BOOL)resignFirstResponder +{ + return NO; +} + +- (BOOL)canBecomeKeyWindow +{ + return NO; +} + +- (void)mouseDown:(NSEvent *)theEvent +{ + UNUSED_PARAM(theEvent); +} + +- (void)cancelOperation:(id)sender +{ + UNUSED_PARAM(sender); + [[self windowController] requestExitFullscreen]; +} + +- (void)animatedResizeDidEnd +{ + // Call our windowController. + if (_controllerActionOnAnimationEnd) + [[self windowController] performSelector:_controllerActionOnAnimationEnd]; + _controllerActionOnAnimationEnd = NULL; +} + +// +// This function will animate a change of frame rectangle +// We support queuing animation, that means that we'll correctly +// interrupt the running animation, and queue the next one. +// +- (void)animateFromRect:(NSRect)startRect toRect:(NSRect)endRect withSubAnimation:(NSAnimation *)subAnimation controllerAction:(SEL)controllerAction +{ + _controllerActionOnAnimationEnd = controllerAction; + + BOOL wasAnimating = NO; + if (_fullscreenAnimation) { + wasAnimating = YES; + + // Interrupt any running animation. + [_fullscreenAnimation stopAnimation]; + + // Save the current rect to ensure a smooth transition. + startRect = [_fullscreenAnimation currentFrame]; + [_fullscreenAnimation release]; + _fullscreenAnimation = nil; + } + + if (NSIsEmptyRect(startRect) || NSIsEmptyRect(endRect)) { + // Fakely end the subanimation. + [subAnimation setCurrentProgress:1.0]; + // And remove the weak link to the window. + [subAnimation stopAnimation]; + + [self setFrame:endRect display:NO]; + [self makeKeyAndOrderFront:self]; + [self animatedResizeDidEnd]; + return; + } + + if (!wasAnimating) { + // We'll downscale the window during the animation based on the higher resolution rect + BOOL higherResolutionIsEndRect = startRect.size.width < endRect.size.width && startRect.size.height < endRect.size.height; + [self setFrame:higherResolutionIsEndRect ? endRect : startRect display:NO]; + } + + ASSERT(!_fullscreenAnimation); + _fullscreenAnimation = [[WebWindowScaleAnimation alloc] initWithHintedDuration:0.2 window:self initalFrame:startRect finalFrame:endRect]; + [_fullscreenAnimation setSubAnimation:subAnimation]; + [_fullscreenAnimation setDelegate:self]; + + // Make sure the animation has scaled the window before showing it. + [_fullscreenAnimation setCurrentProgress:0]; + [self makeKeyAndOrderFront:self]; + + [_fullscreenAnimation startAnimation]; +} + +- (void)animationDidEnd:(NSAnimation *)animation +{ +#if !defined(BUILDING_ON_TIGER) // Animations are never threaded on Tiger. + if (![NSThread isMainThread]) { + [self performSelectorOnMainThread:@selector(animationDidEnd:) withObject:animation waitUntilDone:NO]; + return; + } +#endif + if (animation != _fullscreenAnimation) + return; + + // The animation is not really over and was interrupted + // Don't send completion events. + if ([animation currentProgress] < 1.0) + return; + + // Ensure that animation (and subanimation) don't keep + // the weak reference to the window ivar that may be destroyed from + // now on. + [_fullscreenAnimation setWindow:nil]; + + [_fullscreenAnimation autorelease]; + _fullscreenAnimation = nil; + + [self animatedResizeDidEnd]; +} + +- (void)mouseMoved:(NSEvent *)theEvent +{ + [[self windowController] fadeHUDIn]; +} + +@end + +#endif /* ENABLE(VIDEO) */