--- /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 <IOKit/pwr_mgt/IOPMLib.h>
+#import <QTKit/QTKit.h>
+#import <WebCore/HTMLMediaElement.h>
+#import <WebCore/SoftLinking.h>
+#import <objc/objc-runtime.h>
+#import <wtf/UnusedParam.h>
+
+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)
+<NSAnimationDelegate>
+#endif
+{
+ SEL _controllerActionOnAnimationEnd;
+ WebWindowScaleAnimation *_fullscreenAnimation; // (retain)
+}
+- (void)animateFromRect:(NSRect)startRect toRect:(NSRect)endRect withSubAnimation:(NSAnimation *)subAnimation controllerAction:(SEL)controllerAction;
+@end
+
+@interface WebVideoFullscreenController(HUDWindowControllerDelegate) <WebVideoFullscreenHUDWindowControllerDelegate>
+- (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 <WebVideoFullscreenControllerDelegate>)delegate
+{
+ return _delegate;
+}
+
+- (void)setDelegate:(id <WebVideoFullscreenControllerDelegate>)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) */