/* * Copyright (C) 2010, 2011 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #if ENABLE(FULLSCREEN_API) #import "WebFullScreenController.h" #import "WebPreferencesPrivate.h" #import "WebWindowAnimation.h" #import "WebViewInternal.h" #import <IOKit/pwr_mgt/IOPMLib.h> #import <OSServices/Power.h> #import <WebCore/AnimationList.h> #import <WebCore/CSSPropertyNames.h> #import <WebCore/Color.h> #import <WebCore/Document.h> #import <WebCore/DOMDocument.h> #import <WebCore/DOMDocumentInternal.h> #import <WebCore/DOMHTMLElement.h> #import <WebCore/DOMWindow.h> #import <WebCore/EventListener.h> #import <WebCore/EventNames.h> #import <WebCore/HTMLElement.h> #import <WebCore/HTMLNames.h> #import <WebCore/HTMLMediaElement.h> #import <WebCore/IntRect.h> #import <WebCore/NodeList.h> #import <WebCore/SoftLinking.h> #import <WebCore/RenderBlock.h> #import <WebCore/RenderLayer.h> #import <WebCore/RenderLayerBacking.h> #import <objc/objc-runtime.h> #import <wtf/UnusedParam.h> static const NSTimeInterval tickleTimerInterval = 1.0; static NSString* const isEnteringFullscreenKey = @"isEnteringFullscreen"; using namespace WebCore; #if defined(BUILDING_ON_LEOPARD) @interface CATransaction(SnowLeopardConvenienceFunctions) + (void)setDisableActions:(BOOL)flag; + (void)setAnimationDuration:(CFTimeInterval)dur; @end @implementation CATransaction(SnowLeopardConvenienceFunctions) + (void)setDisableActions:(BOOL)flag { [self setValue:[NSNumber numberWithBool:flag] forKey:kCATransactionDisableActions]; } + (void)setAnimationDuration:(CFTimeInterval)dur { [self setValue:[NSNumber numberWithDouble:dur] forKey:kCATransactionAnimationDuration]; } @end #endif @interface WebFullscreenWindow : NSWindow #if !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_TIGER) <NSAnimationDelegate> #endif { NSView* _animationView; CALayer* _rendererLayer; CALayer* _backgroundLayer; } - (CALayer*)rendererLayer; - (void)setRendererLayer:(CALayer*)rendererLayer; - (CALayer*)backgroundLayer; - (NSView*)animationView; @end class MediaEventListener : public EventListener { public: static PassRefPtr<MediaEventListener> create(WebFullScreenController* delegate); virtual bool operator==(const EventListener&); virtual void handleEvent(ScriptExecutionContext*, Event*); private: MediaEventListener(WebFullScreenController* delegate); WebFullScreenController* delegate; }; @interface WebFullScreenController(Private) - (void)_requestExitFullscreenWithAnimation:(BOOL)animation; - (void)_updateMenuAndDockForFullscreen; - (void)_updatePowerAssertions; - (WebFullscreenWindow *)_fullscreenWindow; - (Document*)_document; - (CFTimeInterval)_animationDuration; - (BOOL)_isAnyMoviePlaying; @end @interface NSWindow(IsOnActiveSpaceAdditionForTigerAndLeopard) - (BOOL)isOnActiveSpace; @end @implementation WebFullScreenController #pragma mark - #pragma mark Initialization - (id)init { // Do not defer window creation, to make sure -windowNumber is created (needed by WebWindowScaleAnimation). NSWindow *window = [[WebFullscreenWindow alloc] initWithContentRect:NSZeroRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]; self = [super initWithWindow:window]; [window release]; if (!self) return nil; [self windowDidLoad]; _mediaEventListener = MediaEventListener::create(self); return self; } - (void)dealloc { ASSERT(!_tickleTimer); [self setWebView:nil]; [_placeholderView release]; [[NSNotificationCenter defaultCenter] removeObserver:self]; [super dealloc]; } - (void)windowDidLoad { #ifdef BUILDING_ON_TIGER // WebFullScreenController is not supported on Tiger: ASSERT_NOT_REACHED(); #else [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidResignActive:) name:NSApplicationDidResignActiveNotification object:NSApp]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidChangeScreenParameters:) name:NSApplicationDidChangeScreenParametersNotification object:NSApp]; #endif } #pragma mark - #pragma mark Accessors - (WebView*)webView { return _webView; } - (void)setWebView:(WebView *)webView { [webView retain]; [_webView release]; _webView = webView; } - (Element*)element { return _element.get(); } - (void)setElement:(PassRefPtr<Element>)element { #ifdef BUILDING_ON_TIGER // WebFullScreenController is not supported on Tiger: ASSERT_NOT_REACHED(); #else // When a new Element is set as the current full screen element, register event // listeners on that Element's window, listening for changes in media play states. // We will use these events to determine whether to disable the screensaver and // display sleep timers when playing video in full screen. Make sure to unregister // the events on the old element's window, if necessary, as well. EventNames& eventNames = WebCore::eventNames(); if (_element) { DOMWindow* window = _element->document()->domWindow(); if (window) { window->removeEventListener(eventNames.playEvent, _mediaEventListener.get(), true); window->removeEventListener(eventNames.pauseEvent, _mediaEventListener.get(), true); window->removeEventListener(eventNames.endedEvent, _mediaEventListener.get(), true); } } _element = element; if (_element) { DOMWindow* window = _element->document()->domWindow(); if (window) { window->addEventListener(eventNames.playEvent, _mediaEventListener, true); window->addEventListener(eventNames.pauseEvent, _mediaEventListener, true); window->addEventListener(eventNames.endedEvent, _mediaEventListener, true); } } #endif } - (RenderBox*)renderer { return _renderer; } - (void)setRenderer:(RenderBox*)renderer { #ifdef BUILDING_ON_TIGER // WebFullScreenController is not supported on Tiger: ASSERT_NOT_REACHED(); #else _renderer = renderer; #endif } #pragma mark - #pragma mark Notifications - (void)windowDidExitFullscreen:(BOOL)finished { if (!_isAnimating) return; if (_isFullscreen) return; NSDisableScreenUpdates(); ASSERT(_element); [self _document]->setFullScreenRendererBackgroundColor(Color::black); [self _document]->webkitDidExitFullScreenForElement(_element.get()); [self setElement:nil]; if (finished) { [self _updateMenuAndDockForFullscreen]; [self _updatePowerAssertions]; [[_webView window] display]; [[self _fullscreenWindow] setRendererLayer:nil]; [[self window] close]; } NSEnableScreenUpdates(); _isAnimating = NO; [self autorelease]; // Associated -retain is in -exitFullscreen. } - (void)windowDidEnterFullscreen:(BOOL)finished { if (!_isAnimating) return; if (!_isFullscreen) return; NSDisableScreenUpdates(); [self _document]->webkitDidEnterFullScreenForElement(_element.get()); [self _document]->setFullScreenRendererBackgroundColor(Color::black); if (finished) { [self _updateMenuAndDockForFullscreen]; [self _updatePowerAssertions]; [NSCursor setHiddenUntilMouseMoves:YES]; // Move the webView into our fullscreen Window if (!_placeholderView) _placeholderView = [[NSView alloc] init]; // Do not swap the placeholder into place if already is in a window, // assuming the placeholder's window will always be the webView's // original window. if (![_placeholderView window]) { WebView* webView = [self webView]; [_placeholderView setFrame:[webView frame]]; [_placeholderView setAutoresizingMask:[webView autoresizingMask]]; [_placeholderView removeFromSuperview]; [[webView superview] replaceSubview:webView with:_placeholderView]; [[[self window] contentView] addSubview:webView]; [webView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; [webView setFrame:[[[self window] contentView] bounds]]; } WebFullscreenWindow* window = [self _fullscreenWindow]; [window setBackgroundColor:[NSColor blackColor]]; [window setOpaque:YES]; [CATransaction begin]; [CATransaction setDisableActions:YES]; [[[window animationView] layer] setOpacity:0]; [CATransaction commit]; } NSEnableScreenUpdates(); _isAnimating = NO; } - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)finished { BOOL isEnteringFullscreenAnimation = [[anim valueForKey:isEnteringFullscreenKey] boolValue]; if (!isEnteringFullscreenAnimation) [self windowDidExitFullscreen:finished]; else [self windowDidEnterFullscreen:finished]; } - (void)applicationDidResignActive:(NSNotification*)notification { // Check to see if the fullscreenWindow is on the active space; this function is available // on 10.6 and later, so default to YES if the function is not available: NSWindow* fullscreenWindow = [self _fullscreenWindow]; BOOL isOnActiveSpace = ([fullscreenWindow respondsToSelector:@selector(isOnActiveSpace)] ? [fullscreenWindow isOnActiveSpace] : YES); // Replicate the QuickTime Player (X) behavior when losing active application status: // Is the fullscreen screen the main screen? (Note: this covers the case where only a // single screen is available.) Is the fullscreen screen on the current space? IFF so, // then exit fullscreen mode. if ([fullscreenWindow screen] == [[NSScreen screens] objectAtIndex:0] && isOnActiveSpace) [self _requestExitFullscreenWithAnimation:NO]; } - (void)applicationDidChangeScreenParameters:(NSNotification*)notification { // The user may have changed the main screen by moving the menu bar, or they may have changed // the Dock's size or location, or they may have changed the fullscreen screen's dimensions. // Update our presentation parameters, and ensure that the full screen window occupies the // entire screen: [self _updateMenuAndDockForFullscreen]; NSWindow* window = [self window]; [window setFrame:[[window screen] frame] display:YES]; } #pragma mark - #pragma mark Exposed Interface - (void)enterFullscreen:(NSScreen *)screen { // Disable animation if we are already in full-screen mode. BOOL shouldAnimate = !_isFullscreen; if (_isAnimating) { // The CAAnimation delegate functions will only be called the // next trip through the run-loop, so manually call the delegate // function here, letting it know the animation did not complete: [self windowDidExitFullscreen:NO]; ASSERT(!_isAnimating); } _isFullscreen = YES; _isAnimating = YES; // setElement: must be called with a non-nil value before calling enterFullscreen:. ASSERT(_element); NSDisableScreenUpdates(); if (!screen) screen = [NSScreen mainScreen]; NSRect screenFrame = [screen frame]; WebView* webView = [self webView]; NSRect webViewFrame = [webView convertRectToBase:[webView frame]]; webViewFrame.origin = [[webView window] convertBaseToScreen:webViewFrame.origin]; NSRect elementFrame = _element->screenRect(); // In the case of a multi-monitor setup where the webView straddles two // monitors, we must create a window large enough to contain the destination // frame and the initial frame. NSRect windowFrame = NSUnionRect(screenFrame, elementFrame); [[self window] setFrame:windowFrame display:YES]; // In a previous incarnation, the NSWindow attached to this controller may have // been on a different screen. Temporarily change the collectionBehavior of the window: NSWindowCollectionBehavior behavior = [[self window] collectionBehavior]; [[self window] setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces]; [[self window] makeKeyAndOrderFront:self]; [[self window] setCollectionBehavior:behavior]; NSView* animationView = [[self _fullscreenWindow] animationView]; NSRect backgroundBounds = {[[self window] convertScreenToBase:screenFrame.origin], screenFrame.size}; backgroundBounds = [animationView convertRectFromBase:backgroundBounds]; // Flip the background layer's coordinate system. backgroundBounds.origin.y = windowFrame.size.height - NSMaxY(backgroundBounds); // Set our fullscreen element's initial frame, and flip the coordinate systems from // screen coordinates (bottom/left) to layer coordinates (top/left): _initialFrame = NSRectToCGRect(NSIntersectionRect(elementFrame, webViewFrame)); _initialFrame.origin.y = screenFrame.size.height - CGRectGetMaxY(_initialFrame); // Inform the document that we will begin entering full screen. This will change // pseudo-classes on the fullscreen element and the document element. Document* document = [self _document]; document->webkitWillEnterFullScreenForElement(_element.get()); // Check to see if the fullscreen renderer is composited. If not, accelerated graphics // may be disabled. In this case, do not attempt to animate the contents into place; // merely snap to the final position: if (!shouldAnimate || !_renderer || !_renderer->layer()->isComposited()) { [self windowDidEnterFullscreen:YES]; NSEnableScreenUpdates(); return; } // Set up the final style of the FullScreen render block. Set an absolute // width and height equal to the size of the screen, and anchor the layer // at the top, left at (0,0). The RenderFullScreen style is already set // to position:fixed. [self _document]->setFullScreenRendererSize(IntSize(screenFrame.size)); [self _document]->setFullScreenRendererBackgroundColor(Color::transparent); // Cause the document to layout, thus calculating a new fullscreen element size: [self _document]->updateLayout(); // FIXME: try to use the fullscreen element's calculated x, y, width, and height instead of the // renderBox functions: RenderBox* childRenderer = _renderer->firstChildBox(); CGRect destinationFrame = CGRectMake(childRenderer->x(), childRenderer->y(), childRenderer->width(), childRenderer->height()); // Some properties haven't propogated from the GraphicsLayer to the CALayer yet. So // tell the renderer's layer to sync it's compositing state: GraphicsLayer* rendererGraphics = _renderer->layer()->backing()->graphicsLayer(); rendererGraphics->syncCompositingState(); CALayer* rendererLayer = rendererGraphics->platformLayer(); [[self _fullscreenWindow] setRendererLayer:rendererLayer]; CFTimeInterval duration = [self _animationDuration]; // Create a transformation matrix that will transform the renderer layer such that // the fullscreen element appears to move from its starting position and size to its // final one. Perform the transformation in two steps, using the CALayer's matrix // math to calculate the effects of each step: // 1. Apply a scale tranform to shrink the apparent size of the layer to the original // element screen size. // 2. Apply a translation transform to move the shrunk layer into the same screen position // as the original element. CATransform3D shrinkTransform = CATransform3DMakeScale(_initialFrame.size.width / destinationFrame.size.width, _initialFrame.size.height / destinationFrame.size.height, 1); [rendererLayer setTransform:shrinkTransform]; CGRect shrunkDestinationFrame = [rendererLayer convertRect:destinationFrame toLayer:[animationView layer]]; CATransform3D moveTransform = CATransform3DMakeTranslation(_initialFrame.origin.x - shrunkDestinationFrame.origin.x, _initialFrame.origin.y - shrunkDestinationFrame.origin.y, 0); CATransform3D finalTransform = CATransform3DConcat(shrinkTransform, moveTransform); [rendererLayer setTransform:finalTransform]; CALayer* backgroundLayer = [[self _fullscreenWindow] backgroundLayer]; // Start the opacity animation. We can use implicit animations here because we don't care when // the animation finishes. [CATransaction begin]; [CATransaction setAnimationDuration:duration]; [backgroundLayer setOpacity:1]; [CATransaction commit]; // Use a CABasicAnimation here for the zoom effect. We want to be notified that the animation has // completed by way of the CAAnimation delegate. CABasicAnimation* zoomAnimation = [CABasicAnimation animationWithKeyPath:@"transform"]; [zoomAnimation setFromValue:[NSValue valueWithCATransform3D:finalTransform]]; [zoomAnimation setToValue:[NSValue valueWithCATransform3D:CATransform3DIdentity]]; [zoomAnimation setDelegate:self]; [zoomAnimation setDuration:duration]; [zoomAnimation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]]; [zoomAnimation setFillMode:kCAFillModeForwards]; [zoomAnimation setValue:(id)kCFBooleanTrue forKey:isEnteringFullscreenKey]; // Disable implicit animations and set the layer's transformation matrix to its final state. [CATransaction begin]; [CATransaction setDisableActions:YES]; [rendererLayer setTransform:CATransform3DIdentity]; [rendererLayer addAnimation:zoomAnimation forKey:@"zoom"]; [backgroundLayer setFrame:NSRectToCGRect(backgroundBounds)]; [CATransaction commit]; NSEnableScreenUpdates(); } - (void)exitFullscreen { if (!_isFullscreen) return; CATransform3D startTransform = CATransform3DIdentity; if (_isAnimating) { if (_renderer && _renderer->layer()->isComposited()) { CALayer* rendererLayer = _renderer->layer()->backing()->graphicsLayer()->platformLayer(); startTransform = [[rendererLayer presentationLayer] transform]; } // The CAAnimation delegate functions will only be called the // next trip through the run-loop, so manually call the delegate // function here, letting it know the animation did not complete: [self windowDidEnterFullscreen:NO]; ASSERT(!_isAnimating); } _isFullscreen = NO; _isAnimating = YES; NSDisableScreenUpdates(); // The user may have moved the fullscreen window in Spaces, so temporarily change // the collectionBehavior of the webView's window: NSWindow* webWindow = [[self webView] window]; NSWindowCollectionBehavior behavior = [webWindow collectionBehavior]; [webWindow setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces]; [webWindow orderWindow:NSWindowBelow relativeTo:[[self window] windowNumber]]; [webWindow setCollectionBehavior:behavior]; // The fullscreen animation may have been cancelled before the // webView was moved to the fullscreen window. Check to see // if the _placeholderView exists and is in a window before // attempting to swap the webView back to it's original tree: if (_placeholderView && [_placeholderView window]) { // Move the webView back to its own native window: WebView* webView = [self webView]; [webView setFrame:[_placeholderView frame]]; [webView setAutoresizingMask:[_placeholderView autoresizingMask]]; [webView removeFromSuperview]; [[_placeholderView superview] replaceSubview:_placeholderView with:webView]; // Because the animation view is layer-hosted, make sure to // disable animations when changing the layer's opacity. Other- // wise, the content will appear to fade into view. [CATransaction begin]; [CATransaction setDisableActions:YES]; WebFullscreenWindow* window = [self _fullscreenWindow]; [[[window animationView] layer] setOpacity:1]; [window setBackgroundColor:[NSColor clearColor]]; [window setOpaque:NO]; [CATransaction commit]; } NSView* animationView = [[self _fullscreenWindow] animationView]; CGRect layerEndFrame = NSRectToCGRect([animationView convertRect:NSRectFromCGRect(_initialFrame) fromView:nil]); // The _renderer might be NULL due to its ancestor being removed: CGRect layerStartFrame = CGRectZero; if (_renderer) { RenderBox* childRenderer = _renderer->firstChildBox(); layerStartFrame = CGRectMake(childRenderer->x(), childRenderer->y(), childRenderer->width(), childRenderer->height()); } [self _document]->webkitWillExitFullScreenForElement(_element.get()); [self _document]->updateLayout(); // We have to retain ourselves because we want to be alive for the end of the animation. // If our owner releases us we could crash if this is not the case. // Balanced in windowDidExitFullscreen [self retain]; // Check to see if the fullscreen renderer is composited. If not, accelerated graphics // may be disabled. In this case, do not attempt to animate the contents into place; // merely snap to the final position: if (!_renderer || !_renderer->layer()->isComposited()) { [self windowDidExitFullscreen:YES]; NSEnableScreenUpdates(); return; } GraphicsLayer* rendererGraphics = _renderer->layer()->backing()->graphicsLayer(); [self _document]->setFullScreenRendererBackgroundColor(Color::transparent); rendererGraphics->syncCompositingState(); CALayer* rendererLayer = rendererGraphics->platformLayer(); [[self _fullscreenWindow] setRendererLayer:rendererLayer]; // Create a transformation matrix that will transform the renderer layer such that // the fullscreen element appears to move from the full screen to its original position // and size. Perform the transformation in two steps, using the CALayer's matrix // math to calculate the effects of each step: // 1. Apply a scale tranform to shrink the apparent size of the layer to the original // element screen size. // 2. Apply a translation transform to move the shrunk layer into the same screen position // as the original element. CATransform3D shrinkTransform = CATransform3DMakeScale(layerEndFrame.size.width / layerStartFrame.size.width, layerEndFrame.size.height / layerStartFrame.size.height, 1); [rendererLayer setTransform:shrinkTransform]; CGRect shrunkDestinationFrame = [rendererLayer convertRect:layerStartFrame toLayer:[animationView layer]]; CATransform3D moveTransform = CATransform3DMakeTranslation(layerEndFrame.origin.x - shrunkDestinationFrame.origin.x, layerEndFrame.origin.y - shrunkDestinationFrame.origin.y, 0); CATransform3D finalTransform = CATransform3DConcat(shrinkTransform, moveTransform); [rendererLayer setTransform:finalTransform]; CFTimeInterval duration = [self _animationDuration]; CALayer* backgroundLayer = [[self _fullscreenWindow] backgroundLayer]; [CATransaction begin]; [CATransaction setAnimationDuration:duration]; [backgroundLayer setOpacity:0]; [CATransaction commit]; CABasicAnimation* zoomAnimation = [CABasicAnimation animationWithKeyPath:@"transform"]; [zoomAnimation setFromValue:[NSValue valueWithCATransform3D:startTransform]]; [zoomAnimation setToValue:[NSValue valueWithCATransform3D:finalTransform]]; [zoomAnimation setDelegate:self]; [zoomAnimation setDuration:duration]; [zoomAnimation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]]; [zoomAnimation setFillMode:kCAFillModeBoth]; [zoomAnimation setRemovedOnCompletion:NO]; [zoomAnimation setValue:(id)kCFBooleanFalse forKey:isEnteringFullscreenKey]; [rendererLayer addAnimation:zoomAnimation forKey:@"zoom"]; NSEnableScreenUpdates(); } #pragma mark - #pragma mark Internal Interface - (void)_updateMenuAndDockForFullscreen { // NSApplicationPresentationOptions is available on > 10.6 only: #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) NSApplicationPresentationOptions options = NSApplicationPresentationDefault; NSScreen* fullscreenScreen = [[self window] screen]; if (_isFullscreen) { // Auto-hide the menu bar if the fullscreenScreen contains the menu bar: // NOTE: if the fullscreenScreen contains the menu bar but not the dock, we must still // auto-hide the dock, or an exception will be thrown. if ([[NSScreen screens] objectAtIndex:0] == fullscreenScreen) options |= (NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationAutoHideDock); // Check if the current screen contains the dock by comparing the screen's frame to its // visibleFrame; if a dock is present, the visibleFrame will differ. If the current screen // contains the dock, hide it. else if (!NSEqualRects([fullscreenScreen frame], [fullscreenScreen visibleFrame])) options |= NSApplicationPresentationAutoHideDock; } if ([NSApp respondsToSelector:@selector(setPresentationOptions:)]) [NSApp setPresentationOptions:options]; else #endif SetSystemUIMode(_isFullscreen ? kUIModeNormal : kUIModeAllHidden, 0); } #if !defined(BUILDING_ON_TIGER) // IOPMAssertionCreateWithName not defined on < 10.5 - (void)_disableIdleDisplaySleep { if (_idleDisplaySleepAssertion == kIOPMNullAssertionID) #if defined(BUILDING_ON_LEOPARD) // IOPMAssertionCreateWithName is not defined in the 10.5 SDK IOPMAssertionCreate(kIOPMAssertionTypeNoDisplaySleep, kIOPMAssertionLevelOn, &_idleDisplaySleepAssertion); #else // IOPMAssertionCreate is depreciated in > 10.5 IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep, kIOPMAssertionLevelOn, CFSTR("WebKit playing a video fullscreen."), &_idleDisplaySleepAssertion); #endif } - (void)_enableIdleDisplaySleep { if (_idleDisplaySleepAssertion != kIOPMNullAssertionID) { IOPMAssertionRelease(_idleDisplaySleepAssertion); _idleDisplaySleepAssertion = kIOPMNullAssertionID; } } - (void)_disableIdleSystemSleep { if (_idleSystemSleepAssertion == kIOPMNullAssertionID) #if defined(BUILDING_ON_LEOPARD) // IOPMAssertionCreateWithName is not defined in the 10.5 SDK IOPMAssertionCreate(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, &_idleSystemSleepAssertion); #else // IOPMAssertionCreate is depreciated in > 10.5 IOPMAssertionCreateWithName(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, CFSTR("WebKit playing a video fullscreen."), &_idleSystemSleepAssertion); #endif } - (void)_enableIdleSystemSleep { if (_idleSystemSleepAssertion != kIOPMNullAssertionID) { IOPMAssertionRelease(_idleSystemSleepAssertion); _idleSystemSleepAssertion = kIOPMNullAssertionID; } } - (void)_enableTickleTimer { [_tickleTimer invalidate]; [_tickleTimer release]; _tickleTimer = [[NSTimer scheduledTimerWithTimeInterval:tickleTimerInterval target:self selector:@selector(_tickleTimerFired) userInfo:nil repeats:YES] retain]; } - (void)_disableTickleTimer { [_tickleTimer invalidate]; [_tickleTimer release]; _tickleTimer = nil; } - (void)_tickleTimerFired { UpdateSystemActivity(OverallAct); } #endif - (void)_updatePowerAssertions { #if !defined(BUILDING_ON_TIGER) BOOL isPlaying = [self _isAnyMoviePlaying]; if (isPlaying && _isFullscreen) { [self _disableIdleSystemSleep]; [self _disableIdleDisplaySleep]; [self _enableTickleTimer]; } else { [self _enableIdleSystemSleep]; [self _enableIdleDisplaySleep]; [self _disableTickleTimer]; } #endif } - (void)_requestExit { [self exitFullscreen]; _forceDisableAnimation = NO; } - (void)_requestExitFullscreenWithAnimation:(BOOL)animation { _forceDisableAnimation = !animation; [self performSelector:@selector(_requestExit) withObject:nil afterDelay:0]; } - (BOOL)_isAnyMoviePlaying { if (!_element) return NO; Node* nextNode = _element.get(); while (nextNode) { if (nextNode->hasTagName(HTMLNames::videoTag)) { HTMLMediaElement* element = static_cast<HTMLMediaElement*>(nextNode); if (!element->paused() && !element->ended()) return YES; } nextNode = nextNode->traverseNextNode(_element.get()); } return NO; } #pragma mark - #pragma mark Utility Functions - (WebFullscreenWindow *)_fullscreenWindow { return (WebFullscreenWindow *)[self window]; } - (Document*)_document { return core([[[self webView] mainFrame] DOMDocument]); } - (CFTimeInterval)_animationDuration { static const CFTimeInterval defaultDuration = 0.5; CFTimeInterval duration = defaultDuration; #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) NSUInteger modifierFlags = [NSEvent modifierFlags]; #else NSUInteger modifierFlags = [[NSApp currentEvent] modifierFlags]; #endif if ((modifierFlags & NSControlKeyMask) == NSControlKeyMask) duration *= 2; if ((modifierFlags & NSShiftKeyMask) == NSShiftKeyMask) duration *= 10; if (_forceDisableAnimation) { // This will disable scale animation duration = 0; } return duration; } @end #pragma mark - @implementation WebFullscreenWindow - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag { UNUSED_PARAM(aStyle); self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:bufferingType defer:flag]; if (!self) return nil; [self setOpaque:NO]; [self setBackgroundColor:[NSColor clearColor]]; [self setIgnoresMouseEvents:NO]; [self setAcceptsMouseMovedEvents:YES]; [self setReleasedWhenClosed:NO]; [self setHasShadow:YES]; #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) [self setMovable:NO]; #else [self setMovableByWindowBackground:NO]; #endif NSView* contentView = [self contentView]; _animationView = [[NSView alloc] initWithFrame:[contentView bounds]]; CALayer* contentLayer = [[CALayer alloc] init]; [_animationView setLayer:contentLayer]; [_animationView setWantsLayer:YES]; [_animationView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; [contentView addSubview:_animationView]; _backgroundLayer = [[CALayer alloc] init]; [contentLayer addSublayer:_backgroundLayer]; #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) [contentLayer setGeometryFlipped:YES]; #else [contentLayer setSublayerTransform:CATransform3DMakeScale(1, -1, 1)]; #endif [contentLayer setOpacity:0]; [_backgroundLayer setBackgroundColor:CGColorGetConstantColor(kCGColorBlack)]; [_backgroundLayer setOpacity:0]; return self; } - (void)dealloc { [_animationView release]; [_backgroundLayer release]; [_rendererLayer release]; [super dealloc]; } - (BOOL)canBecomeKeyWindow { return YES; } - (void)keyDown:(NSEvent *)theEvent { if ([[theEvent charactersIgnoringModifiers] isEqual:@"\e"]) // Esacpe key-code [self cancelOperation:self]; else [super keyDown:theEvent]; } - (void)cancelOperation:(id)sender { UNUSED_PARAM(sender); [[self windowController] _requestExitFullscreenWithAnimation:YES]; } - (CALayer*)rendererLayer { return _rendererLayer; } - (void)setRendererLayer:(CALayer *)rendererLayer { [CATransaction begin]; [CATransaction setDisableActions:YES]; [rendererLayer retain]; [_rendererLayer removeFromSuperlayer]; [_rendererLayer release]; _rendererLayer = rendererLayer; if (_rendererLayer) [[[self animationView] layer] addSublayer:_rendererLayer]; [CATransaction commit]; } - (CALayer*)backgroundLayer { return _backgroundLayer; } - (NSView*)animationView { return _animationView; } @end #pragma mark - #pragma mark MediaEventListener MediaEventListener::MediaEventListener(WebFullScreenController* delegate) : EventListener(CPPEventListenerType) , delegate(delegate) { } PassRefPtr<MediaEventListener> MediaEventListener::create(WebFullScreenController* delegate) { return adoptRef(new MediaEventListener(delegate)); } bool MediaEventListener::operator==(const EventListener& listener) { return this == &listener; } void MediaEventListener::handleEvent(ScriptExecutionContext* context, Event* event) { [delegate _updatePowerAssertions]; } #endif /* ENABLE(FULLSCREEN_API) */