1/* 2 * Copyright (C) 2010, 2011 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#if ENABLE(FULLSCREEN_API) 27 28#import "WebFullScreenController.h" 29 30#import "WebPreferencesPrivate.h" 31#import "WebWindowAnimation.h" 32#import "WebViewInternal.h" 33#import <IOKit/pwr_mgt/IOPMLib.h> 34#import <OSServices/Power.h> 35#import <WebCore/AnimationList.h> 36#import <WebCore/CSSPropertyNames.h> 37#import <WebCore/Color.h> 38#import <WebCore/Document.h> 39#import <WebCore/DOMDocument.h> 40#import <WebCore/DOMDocumentInternal.h> 41#import <WebCore/DOMHTMLElement.h> 42#import <WebCore/DOMWindow.h> 43#import <WebCore/EventListener.h> 44#import <WebCore/EventNames.h> 45#import <WebCore/HTMLElement.h> 46#import <WebCore/HTMLNames.h> 47#import <WebCore/HTMLMediaElement.h> 48#import <WebCore/IntRect.h> 49#import <WebCore/NodeList.h> 50#import <WebCore/SoftLinking.h> 51#import <WebCore/RenderBlock.h> 52#import <WebCore/RenderLayer.h> 53#import <WebCore/RenderLayerBacking.h> 54#import <objc/objc-runtime.h> 55#import <wtf/UnusedParam.h> 56 57static const NSTimeInterval tickleTimerInterval = 1.0; 58static NSString* const isEnteringFullscreenKey = @"isEnteringFullscreen"; 59 60using namespace WebCore; 61 62#if defined(BUILDING_ON_LEOPARD) 63@interface CATransaction(SnowLeopardConvenienceFunctions) 64+ (void)setDisableActions:(BOOL)flag; 65+ (void)setAnimationDuration:(CFTimeInterval)dur; 66@end 67 68@implementation CATransaction(SnowLeopardConvenienceFunctions) 69+ (void)setDisableActions:(BOOL)flag 70{ 71 [self setValue:[NSNumber numberWithBool:flag] forKey:kCATransactionDisableActions]; 72} 73 74+ (void)setAnimationDuration:(CFTimeInterval)dur 75{ 76 [self setValue:[NSNumber numberWithDouble:dur] forKey:kCATransactionAnimationDuration]; 77} 78@end 79 80#endif 81 82@interface WebFullscreenWindow : NSWindow 83#if !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_TIGER) 84<NSAnimationDelegate> 85#endif 86{ 87 NSView* _animationView; 88 89 CALayer* _rendererLayer; 90 CALayer* _backgroundLayer; 91} 92- (CALayer*)rendererLayer; 93- (void)setRendererLayer:(CALayer*)rendererLayer; 94- (CALayer*)backgroundLayer; 95- (NSView*)animationView; 96@end 97 98class MediaEventListener : public EventListener { 99public: 100 static PassRefPtr<MediaEventListener> create(WebFullScreenController* delegate); 101 virtual bool operator==(const EventListener&); 102 virtual void handleEvent(ScriptExecutionContext*, Event*); 103 104private: 105 MediaEventListener(WebFullScreenController* delegate); 106 WebFullScreenController* delegate; 107}; 108 109@interface WebFullScreenController(Private) 110- (void)_requestExitFullscreenWithAnimation:(BOOL)animation; 111- (void)_updateMenuAndDockForFullscreen; 112- (void)_updatePowerAssertions; 113- (WebFullscreenWindow *)_fullscreenWindow; 114- (Document*)_document; 115- (CFTimeInterval)_animationDuration; 116- (BOOL)_isAnyMoviePlaying; 117@end 118 119@interface NSWindow(IsOnActiveSpaceAdditionForTigerAndLeopard) 120- (BOOL)isOnActiveSpace; 121@end 122 123@implementation WebFullScreenController 124 125#pragma mark - 126#pragma mark Initialization 127- (id)init 128{ 129 // Do not defer window creation, to make sure -windowNumber is created (needed by WebWindowScaleAnimation). 130 NSWindow *window = [[WebFullscreenWindow alloc] initWithContentRect:NSZeroRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]; 131 self = [super initWithWindow:window]; 132 [window release]; 133 if (!self) 134 return nil; 135 [self windowDidLoad]; 136 _mediaEventListener = MediaEventListener::create(self); 137 return self; 138 139} 140 141- (void)dealloc 142{ 143 ASSERT(!_tickleTimer); 144 145 [self setWebView:nil]; 146 [_placeholderView release]; 147 148 [[NSNotificationCenter defaultCenter] removeObserver:self]; 149 [super dealloc]; 150} 151 152- (void)windowDidLoad 153{ 154#ifdef BUILDING_ON_TIGER 155 // WebFullScreenController is not supported on Tiger: 156 ASSERT_NOT_REACHED(); 157#else 158 159 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidResignActive:) name:NSApplicationDidResignActiveNotification object:NSApp]; 160 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidChangeScreenParameters:) name:NSApplicationDidChangeScreenParametersNotification object:NSApp]; 161#endif 162} 163 164#pragma mark - 165#pragma mark Accessors 166 167- (WebView*)webView 168{ 169 return _webView; 170} 171 172- (void)setWebView:(WebView *)webView 173{ 174 [webView retain]; 175 [_webView release]; 176 _webView = webView; 177} 178 179- (Element*)element 180{ 181 return _element.get(); 182} 183 184- (void)setElement:(PassRefPtr<Element>)element 185{ 186#ifdef BUILDING_ON_TIGER 187 // WebFullScreenController is not supported on Tiger: 188 ASSERT_NOT_REACHED(); 189#else 190 // When a new Element is set as the current full screen element, register event 191 // listeners on that Element's window, listening for changes in media play states. 192 // We will use these events to determine whether to disable the screensaver and 193 // display sleep timers when playing video in full screen. Make sure to unregister 194 // the events on the old element's window, if necessary, as well. 195 196 EventNames& eventNames = WebCore::eventNames(); 197 198 if (_element) { 199 DOMWindow* window = _element->document()->domWindow(); 200 if (window) { 201 window->removeEventListener(eventNames.playEvent, _mediaEventListener.get(), true); 202 window->removeEventListener(eventNames.pauseEvent, _mediaEventListener.get(), true); 203 window->removeEventListener(eventNames.endedEvent, _mediaEventListener.get(), true); 204 } 205 } 206 207 _element = element; 208 209 if (_element) { 210 DOMWindow* window = _element->document()->domWindow(); 211 if (window) { 212 window->addEventListener(eventNames.playEvent, _mediaEventListener, true); 213 window->addEventListener(eventNames.pauseEvent, _mediaEventListener, true); 214 window->addEventListener(eventNames.endedEvent, _mediaEventListener, true); 215 } 216 } 217#endif 218} 219 220- (RenderBox*)renderer 221{ 222 return _renderer; 223} 224 225- (void)setRenderer:(RenderBox*)renderer 226{ 227#ifdef BUILDING_ON_TIGER 228 // WebFullScreenController is not supported on Tiger: 229 ASSERT_NOT_REACHED(); 230#else 231 _renderer = renderer; 232#endif 233} 234 235#pragma mark - 236#pragma mark Notifications 237 238- (void)windowDidExitFullscreen:(BOOL)finished 239{ 240 if (!_isAnimating) 241 return; 242 243 if (_isFullscreen) 244 return; 245 246 NSDisableScreenUpdates(); 247 ASSERT(_element); 248 [self _document]->setFullScreenRendererBackgroundColor(Color::black); 249 [self _document]->webkitDidExitFullScreenForElement(_element.get()); 250 [self setElement:nil]; 251 252 if (finished) { 253 [self _updateMenuAndDockForFullscreen]; 254 [self _updatePowerAssertions]; 255 256 [[_webView window] display]; 257 [[self _fullscreenWindow] setRendererLayer:nil]; 258 [[self window] close]; 259 } 260 261 NSEnableScreenUpdates(); 262 263 _isAnimating = NO; 264 [self autorelease]; // Associated -retain is in -exitFullscreen. 265} 266 267- (void)windowDidEnterFullscreen:(BOOL)finished 268{ 269 if (!_isAnimating) 270 return; 271 272 if (!_isFullscreen) 273 return; 274 275 NSDisableScreenUpdates(); 276 [self _document]->webkitDidEnterFullScreenForElement(_element.get()); 277 [self _document]->setFullScreenRendererBackgroundColor(Color::black); 278 279 if (finished) { 280 [self _updateMenuAndDockForFullscreen]; 281 [self _updatePowerAssertions]; 282 [NSCursor setHiddenUntilMouseMoves:YES]; 283 284 // Move the webView into our fullscreen Window 285 if (!_placeholderView) 286 _placeholderView = [[NSView alloc] init]; 287 288 // Do not swap the placeholder into place if already is in a window, 289 // assuming the placeholder's window will always be the webView's 290 // original window. 291 if (![_placeholderView window]) { 292 WebView* webView = [self webView]; 293 [_placeholderView setFrame:[webView frame]]; 294 [_placeholderView setAutoresizingMask:[webView autoresizingMask]]; 295 [_placeholderView removeFromSuperview]; 296 [[webView superview] replaceSubview:webView with:_placeholderView]; 297 298 [[[self window] contentView] addSubview:webView]; 299 [webView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; 300 [webView setFrame:[[[self window] contentView] bounds]]; 301 } 302 303 WebFullscreenWindow* window = [self _fullscreenWindow]; 304 [window setBackgroundColor:[NSColor blackColor]]; 305 [window setOpaque:YES]; 306 307 [CATransaction begin]; 308 [CATransaction setDisableActions:YES]; 309 [[[window animationView] layer] setOpacity:0]; 310 [CATransaction commit]; 311 } 312 NSEnableScreenUpdates(); 313 314 _isAnimating = NO; 315} 316 317- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)finished 318{ 319 BOOL isEnteringFullscreenAnimation = [[anim valueForKey:isEnteringFullscreenKey] boolValue]; 320 321 if (!isEnteringFullscreenAnimation) 322 [self windowDidExitFullscreen:finished]; 323 else 324 [self windowDidEnterFullscreen:finished]; 325} 326 327- (void)applicationDidResignActive:(NSNotification*)notification 328{ 329 // Check to see if the fullscreenWindow is on the active space; this function is available 330 // on 10.6 and later, so default to YES if the function is not available: 331 NSWindow* fullscreenWindow = [self _fullscreenWindow]; 332 BOOL isOnActiveSpace = ([fullscreenWindow respondsToSelector:@selector(isOnActiveSpace)] ? [fullscreenWindow isOnActiveSpace] : YES); 333 334 // Replicate the QuickTime Player (X) behavior when losing active application status: 335 // Is the fullscreen screen the main screen? (Note: this covers the case where only a 336 // single screen is available.) Is the fullscreen screen on the current space? IFF so, 337 // then exit fullscreen mode. 338 if ([fullscreenWindow screen] == [[NSScreen screens] objectAtIndex:0] && isOnActiveSpace) 339 [self _requestExitFullscreenWithAnimation:NO]; 340} 341 342- (void)applicationDidChangeScreenParameters:(NSNotification*)notification 343{ 344 // The user may have changed the main screen by moving the menu bar, or they may have changed 345 // the Dock's size or location, or they may have changed the fullscreen screen's dimensions. 346 // Update our presentation parameters, and ensure that the full screen window occupies the 347 // entire screen: 348 [self _updateMenuAndDockForFullscreen]; 349 NSWindow* window = [self window]; 350 [window setFrame:[[window screen] frame] display:YES]; 351} 352 353#pragma mark - 354#pragma mark Exposed Interface 355 356- (void)enterFullscreen:(NSScreen *)screen 357{ 358 // Disable animation if we are already in full-screen mode. 359 BOOL shouldAnimate = !_isFullscreen; 360 361 if (_isAnimating) { 362 // The CAAnimation delegate functions will only be called the 363 // next trip through the run-loop, so manually call the delegate 364 // function here, letting it know the animation did not complete: 365 [self windowDidExitFullscreen:NO]; 366 ASSERT(!_isAnimating); 367 } 368 _isFullscreen = YES; 369 _isAnimating = YES; 370 371 // setElement: must be called with a non-nil value before calling enterFullscreen:. 372 ASSERT(_element); 373 374 NSDisableScreenUpdates(); 375 376 if (!screen) 377 screen = [NSScreen mainScreen]; 378 NSRect screenFrame = [screen frame]; 379 380 WebView* webView = [self webView]; 381 NSRect webViewFrame = [webView convertRectToBase:[webView frame]]; 382 webViewFrame.origin = [[webView window] convertBaseToScreen:webViewFrame.origin]; 383 384 NSRect elementFrame = _element->screenRect(); 385 386 // In the case of a multi-monitor setup where the webView straddles two 387 // monitors, we must create a window large enough to contain the destination 388 // frame and the initial frame. 389 NSRect windowFrame = NSUnionRect(screenFrame, elementFrame); 390 [[self window] setFrame:windowFrame display:YES]; 391 392 // In a previous incarnation, the NSWindow attached to this controller may have 393 // been on a different screen. Temporarily change the collectionBehavior of the window: 394 NSWindowCollectionBehavior behavior = [[self window] collectionBehavior]; 395 [[self window] setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces]; 396 [[self window] makeKeyAndOrderFront:self]; 397 [[self window] setCollectionBehavior:behavior]; 398 399 NSView* animationView = [[self _fullscreenWindow] animationView]; 400 401 NSRect backgroundBounds = {[[self window] convertScreenToBase:screenFrame.origin], screenFrame.size}; 402 backgroundBounds = [animationView convertRectFromBase:backgroundBounds]; 403 // Flip the background layer's coordinate system. 404 backgroundBounds.origin.y = windowFrame.size.height - NSMaxY(backgroundBounds); 405 406 // Set our fullscreen element's initial frame, and flip the coordinate systems from 407 // screen coordinates (bottom/left) to layer coordinates (top/left): 408 _initialFrame = NSRectToCGRect(NSIntersectionRect(elementFrame, webViewFrame)); 409 _initialFrame.origin.y = screenFrame.size.height - CGRectGetMaxY(_initialFrame); 410 411 // Inform the document that we will begin entering full screen. This will change 412 // pseudo-classes on the fullscreen element and the document element. 413 Document* document = [self _document]; 414 document->webkitWillEnterFullScreenForElement(_element.get()); 415 416 // Check to see if the fullscreen renderer is composited. If not, accelerated graphics 417 // may be disabled. In this case, do not attempt to animate the contents into place; 418 // merely snap to the final position: 419 if (!shouldAnimate || !_renderer || !_renderer->layer()->isComposited()) { 420 [self windowDidEnterFullscreen:YES]; 421 NSEnableScreenUpdates(); 422 return; 423 } 424 425 // Set up the final style of the FullScreen render block. Set an absolute 426 // width and height equal to the size of the screen, and anchor the layer 427 // at the top, left at (0,0). The RenderFullScreen style is already set 428 // to position:fixed. 429 [self _document]->setFullScreenRendererSize(IntSize(screenFrame.size)); 430 [self _document]->setFullScreenRendererBackgroundColor(Color::transparent); 431 432 // Cause the document to layout, thus calculating a new fullscreen element size: 433 [self _document]->updateLayout(); 434 435 // FIXME: try to use the fullscreen element's calculated x, y, width, and height instead of the 436 // renderBox functions: 437 RenderBox* childRenderer = _renderer->firstChildBox(); 438 CGRect destinationFrame = CGRectMake(childRenderer->x(), childRenderer->y(), childRenderer->width(), childRenderer->height()); 439 440 // Some properties haven't propogated from the GraphicsLayer to the CALayer yet. So 441 // tell the renderer's layer to sync it's compositing state: 442 GraphicsLayer* rendererGraphics = _renderer->layer()->backing()->graphicsLayer(); 443 rendererGraphics->syncCompositingState(); 444 445 CALayer* rendererLayer = rendererGraphics->platformLayer(); 446 [[self _fullscreenWindow] setRendererLayer:rendererLayer]; 447 448 CFTimeInterval duration = [self _animationDuration]; 449 450 // Create a transformation matrix that will transform the renderer layer such that 451 // the fullscreen element appears to move from its starting position and size to its 452 // final one. Perform the transformation in two steps, using the CALayer's matrix 453 // math to calculate the effects of each step: 454 // 1. Apply a scale tranform to shrink the apparent size of the layer to the original 455 // element screen size. 456 // 2. Apply a translation transform to move the shrunk layer into the same screen position 457 // as the original element. 458 CATransform3D shrinkTransform = CATransform3DMakeScale(_initialFrame.size.width / destinationFrame.size.width, _initialFrame.size.height / destinationFrame.size.height, 1); 459 [rendererLayer setTransform:shrinkTransform]; 460 CGRect shrunkDestinationFrame = [rendererLayer convertRect:destinationFrame toLayer:[animationView layer]]; 461 CATransform3D moveTransform = CATransform3DMakeTranslation(_initialFrame.origin.x - shrunkDestinationFrame.origin.x, _initialFrame.origin.y - shrunkDestinationFrame.origin.y, 0); 462 CATransform3D finalTransform = CATransform3DConcat(shrinkTransform, moveTransform); 463 [rendererLayer setTransform:finalTransform]; 464 465 CALayer* backgroundLayer = [[self _fullscreenWindow] backgroundLayer]; 466 467 // Start the opacity animation. We can use implicit animations here because we don't care when 468 // the animation finishes. 469 [CATransaction begin]; 470 [CATransaction setAnimationDuration:duration]; 471 [backgroundLayer setOpacity:1]; 472 [CATransaction commit]; 473 474 // Use a CABasicAnimation here for the zoom effect. We want to be notified that the animation has 475 // completed by way of the CAAnimation delegate. 476 CABasicAnimation* zoomAnimation = [CABasicAnimation animationWithKeyPath:@"transform"]; 477 [zoomAnimation setFromValue:[NSValue valueWithCATransform3D:finalTransform]]; 478 [zoomAnimation setToValue:[NSValue valueWithCATransform3D:CATransform3DIdentity]]; 479 [zoomAnimation setDelegate:self]; 480 [zoomAnimation setDuration:duration]; 481 [zoomAnimation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]]; 482 [zoomAnimation setFillMode:kCAFillModeForwards]; 483 [zoomAnimation setValue:(id)kCFBooleanTrue forKey:isEnteringFullscreenKey]; 484 485 // Disable implicit animations and set the layer's transformation matrix to its final state. 486 [CATransaction begin]; 487 [CATransaction setDisableActions:YES]; 488 [rendererLayer setTransform:CATransform3DIdentity]; 489 [rendererLayer addAnimation:zoomAnimation forKey:@"zoom"]; 490 [backgroundLayer setFrame:NSRectToCGRect(backgroundBounds)]; 491 [CATransaction commit]; 492 493 NSEnableScreenUpdates(); 494} 495 496- (void)exitFullscreen 497{ 498 if (!_isFullscreen) 499 return; 500 501 CATransform3D startTransform = CATransform3DIdentity; 502 if (_isAnimating) { 503 if (_renderer && _renderer->layer()->isComposited()) { 504 CALayer* rendererLayer = _renderer->layer()->backing()->graphicsLayer()->platformLayer(); 505 startTransform = [[rendererLayer presentationLayer] transform]; 506 } 507 508 // The CAAnimation delegate functions will only be called the 509 // next trip through the run-loop, so manually call the delegate 510 // function here, letting it know the animation did not complete: 511 [self windowDidEnterFullscreen:NO]; 512 ASSERT(!_isAnimating); 513 } 514 _isFullscreen = NO; 515 _isAnimating = YES; 516 517 NSDisableScreenUpdates(); 518 519 // The user may have moved the fullscreen window in Spaces, so temporarily change 520 // the collectionBehavior of the webView's window: 521 NSWindow* webWindow = [[self webView] window]; 522 NSWindowCollectionBehavior behavior = [webWindow collectionBehavior]; 523 [webWindow setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces]; 524 [webWindow orderWindow:NSWindowBelow relativeTo:[[self window] windowNumber]]; 525 [webWindow setCollectionBehavior:behavior]; 526 527 // The fullscreen animation may have been cancelled before the 528 // webView was moved to the fullscreen window. Check to see 529 // if the _placeholderView exists and is in a window before 530 // attempting to swap the webView back to it's original tree: 531 if (_placeholderView && [_placeholderView window]) { 532 // Move the webView back to its own native window: 533 WebView* webView = [self webView]; 534 [webView setFrame:[_placeholderView frame]]; 535 [webView setAutoresizingMask:[_placeholderView autoresizingMask]]; 536 [webView removeFromSuperview]; 537 [[_placeholderView superview] replaceSubview:_placeholderView with:webView]; 538 539 // Because the animation view is layer-hosted, make sure to 540 // disable animations when changing the layer's opacity. Other- 541 // wise, the content will appear to fade into view. 542 [CATransaction begin]; 543 [CATransaction setDisableActions:YES]; 544 WebFullscreenWindow* window = [self _fullscreenWindow]; 545 [[[window animationView] layer] setOpacity:1]; 546 [window setBackgroundColor:[NSColor clearColor]]; 547 [window setOpaque:NO]; 548 [CATransaction commit]; 549 } 550 551 NSView* animationView = [[self _fullscreenWindow] animationView]; 552 CGRect layerEndFrame = NSRectToCGRect([animationView convertRect:NSRectFromCGRect(_initialFrame) fromView:nil]); 553 554 // The _renderer might be NULL due to its ancestor being removed: 555 CGRect layerStartFrame = CGRectZero; 556 if (_renderer) { 557 RenderBox* childRenderer = _renderer->firstChildBox(); 558 layerStartFrame = CGRectMake(childRenderer->x(), childRenderer->y(), childRenderer->width(), childRenderer->height()); 559 } 560 561 [self _document]->webkitWillExitFullScreenForElement(_element.get()); 562 [self _document]->updateLayout(); 563 564 // We have to retain ourselves because we want to be alive for the end of the animation. 565 // If our owner releases us we could crash if this is not the case. 566 // Balanced in windowDidExitFullscreen 567 [self retain]; 568 569 // Check to see if the fullscreen renderer is composited. If not, accelerated graphics 570 // may be disabled. In this case, do not attempt to animate the contents into place; 571 // merely snap to the final position: 572 if (!_renderer || !_renderer->layer()->isComposited()) { 573 [self windowDidExitFullscreen:YES]; 574 NSEnableScreenUpdates(); 575 return; 576 } 577 578 GraphicsLayer* rendererGraphics = _renderer->layer()->backing()->graphicsLayer(); 579 580 [self _document]->setFullScreenRendererBackgroundColor(Color::transparent); 581 582 rendererGraphics->syncCompositingState(); 583 584 CALayer* rendererLayer = rendererGraphics->platformLayer(); 585 [[self _fullscreenWindow] setRendererLayer:rendererLayer]; 586 587 // Create a transformation matrix that will transform the renderer layer such that 588 // the fullscreen element appears to move from the full screen to its original position 589 // and size. Perform the transformation in two steps, using the CALayer's matrix 590 // math to calculate the effects of each step: 591 // 1. Apply a scale tranform to shrink the apparent size of the layer to the original 592 // element screen size. 593 // 2. Apply a translation transform to move the shrunk layer into the same screen position 594 // as the original element. 595 CATransform3D shrinkTransform = CATransform3DMakeScale(layerEndFrame.size.width / layerStartFrame.size.width, layerEndFrame.size.height / layerStartFrame.size.height, 1); 596 [rendererLayer setTransform:shrinkTransform]; 597 CGRect shrunkDestinationFrame = [rendererLayer convertRect:layerStartFrame toLayer:[animationView layer]]; 598 CATransform3D moveTransform = CATransform3DMakeTranslation(layerEndFrame.origin.x - shrunkDestinationFrame.origin.x, layerEndFrame.origin.y - shrunkDestinationFrame.origin.y, 0); 599 CATransform3D finalTransform = CATransform3DConcat(shrinkTransform, moveTransform); 600 [rendererLayer setTransform:finalTransform]; 601 602 CFTimeInterval duration = [self _animationDuration]; 603 604 CALayer* backgroundLayer = [[self _fullscreenWindow] backgroundLayer]; 605 [CATransaction begin]; 606 [CATransaction setAnimationDuration:duration]; 607 [backgroundLayer setOpacity:0]; 608 [CATransaction commit]; 609 610 CABasicAnimation* zoomAnimation = [CABasicAnimation animationWithKeyPath:@"transform"]; 611 [zoomAnimation setFromValue:[NSValue valueWithCATransform3D:startTransform]]; 612 [zoomAnimation setToValue:[NSValue valueWithCATransform3D:finalTransform]]; 613 [zoomAnimation setDelegate:self]; 614 [zoomAnimation setDuration:duration]; 615 [zoomAnimation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]]; 616 [zoomAnimation setFillMode:kCAFillModeBoth]; 617 [zoomAnimation setRemovedOnCompletion:NO]; 618 [zoomAnimation setValue:(id)kCFBooleanFalse forKey:isEnteringFullscreenKey]; 619 620 [rendererLayer addAnimation:zoomAnimation forKey:@"zoom"]; 621 622 NSEnableScreenUpdates(); 623} 624 625#pragma mark - 626#pragma mark Internal Interface 627 628- (void)_updateMenuAndDockForFullscreen 629{ 630 // NSApplicationPresentationOptions is available on > 10.6 only: 631#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 632 NSApplicationPresentationOptions options = NSApplicationPresentationDefault; 633 NSScreen* fullscreenScreen = [[self window] screen]; 634 635 if (_isFullscreen) { 636 // Auto-hide the menu bar if the fullscreenScreen contains the menu bar: 637 // NOTE: if the fullscreenScreen contains the menu bar but not the dock, we must still 638 // auto-hide the dock, or an exception will be thrown. 639 if ([[NSScreen screens] objectAtIndex:0] == fullscreenScreen) 640 options |= (NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationAutoHideDock); 641 // Check if the current screen contains the dock by comparing the screen's frame to its 642 // visibleFrame; if a dock is present, the visibleFrame will differ. If the current screen 643 // contains the dock, hide it. 644 else if (!NSEqualRects([fullscreenScreen frame], [fullscreenScreen visibleFrame])) 645 options |= NSApplicationPresentationAutoHideDock; 646 } 647 648 if ([NSApp respondsToSelector:@selector(setPresentationOptions:)]) 649 [NSApp setPresentationOptions:options]; 650 else 651#endif 652 SetSystemUIMode(_isFullscreen ? kUIModeNormal : kUIModeAllHidden, 0); 653} 654 655#if !defined(BUILDING_ON_TIGER) // IOPMAssertionCreateWithName not defined on < 10.5 656- (void)_disableIdleDisplaySleep 657{ 658 if (_idleDisplaySleepAssertion == kIOPMNullAssertionID) 659#if defined(BUILDING_ON_LEOPARD) // IOPMAssertionCreateWithName is not defined in the 10.5 SDK 660 IOPMAssertionCreate(kIOPMAssertionTypeNoDisplaySleep, kIOPMAssertionLevelOn, &_idleDisplaySleepAssertion); 661#else // IOPMAssertionCreate is depreciated in > 10.5 662 IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep, kIOPMAssertionLevelOn, CFSTR("WebKit playing a video fullscreen."), &_idleDisplaySleepAssertion); 663#endif 664} 665 666- (void)_enableIdleDisplaySleep 667{ 668 if (_idleDisplaySleepAssertion != kIOPMNullAssertionID) { 669 IOPMAssertionRelease(_idleDisplaySleepAssertion); 670 _idleDisplaySleepAssertion = kIOPMNullAssertionID; 671 } 672} 673 674- (void)_disableIdleSystemSleep 675{ 676 if (_idleSystemSleepAssertion == kIOPMNullAssertionID) 677#if defined(BUILDING_ON_LEOPARD) // IOPMAssertionCreateWithName is not defined in the 10.5 SDK 678 IOPMAssertionCreate(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, &_idleSystemSleepAssertion); 679#else // IOPMAssertionCreate is depreciated in > 10.5 680 IOPMAssertionCreateWithName(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, CFSTR("WebKit playing a video fullscreen."), &_idleSystemSleepAssertion); 681#endif 682} 683 684- (void)_enableIdleSystemSleep 685{ 686 if (_idleSystemSleepAssertion != kIOPMNullAssertionID) { 687 IOPMAssertionRelease(_idleSystemSleepAssertion); 688 _idleSystemSleepAssertion = kIOPMNullAssertionID; 689 } 690} 691 692- (void)_enableTickleTimer 693{ 694 [_tickleTimer invalidate]; 695 [_tickleTimer release]; 696 _tickleTimer = [[NSTimer scheduledTimerWithTimeInterval:tickleTimerInterval target:self selector:@selector(_tickleTimerFired) userInfo:nil repeats:YES] retain]; 697} 698 699- (void)_disableTickleTimer 700{ 701 [_tickleTimer invalidate]; 702 [_tickleTimer release]; 703 _tickleTimer = nil; 704} 705 706- (void)_tickleTimerFired 707{ 708 UpdateSystemActivity(OverallAct); 709} 710#endif 711 712- (void)_updatePowerAssertions 713{ 714#if !defined(BUILDING_ON_TIGER) 715 BOOL isPlaying = [self _isAnyMoviePlaying]; 716 717 if (isPlaying && _isFullscreen) { 718 [self _disableIdleSystemSleep]; 719 [self _disableIdleDisplaySleep]; 720 [self _enableTickleTimer]; 721 } else { 722 [self _enableIdleSystemSleep]; 723 [self _enableIdleDisplaySleep]; 724 [self _disableTickleTimer]; 725 } 726#endif 727} 728 729- (void)_requestExit 730{ 731 [self exitFullscreen]; 732 _forceDisableAnimation = NO; 733} 734 735- (void)_requestExitFullscreenWithAnimation:(BOOL)animation 736{ 737 _forceDisableAnimation = !animation; 738 [self performSelector:@selector(_requestExit) withObject:nil afterDelay:0]; 739 740} 741 742- (BOOL)_isAnyMoviePlaying 743{ 744 if (!_element) 745 return NO; 746 747 Node* nextNode = _element.get(); 748 while (nextNode) 749 { 750 if (nextNode->hasTagName(HTMLNames::videoTag)) { 751 HTMLMediaElement* element = static_cast<HTMLMediaElement*>(nextNode); 752 if (!element->paused() && !element->ended()) 753 return YES; 754 } 755 756 nextNode = nextNode->traverseNextNode(_element.get()); 757 } 758 759 return NO; 760} 761 762#pragma mark - 763#pragma mark Utility Functions 764 765- (WebFullscreenWindow *)_fullscreenWindow 766{ 767 return (WebFullscreenWindow *)[self window]; 768} 769 770- (Document*)_document 771{ 772 return core([[[self webView] mainFrame] DOMDocument]); 773} 774 775- (CFTimeInterval)_animationDuration 776{ 777 static const CFTimeInterval defaultDuration = 0.5; 778 CFTimeInterval duration = defaultDuration; 779#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 780 NSUInteger modifierFlags = [NSEvent modifierFlags]; 781#else 782 NSUInteger modifierFlags = [[NSApp currentEvent] modifierFlags]; 783#endif 784 if ((modifierFlags & NSControlKeyMask) == NSControlKeyMask) 785 duration *= 2; 786 if ((modifierFlags & NSShiftKeyMask) == NSShiftKeyMask) 787 duration *= 10; 788 if (_forceDisableAnimation) { 789 // This will disable scale animation 790 duration = 0; 791 } 792 return duration; 793} 794 795@end 796 797#pragma mark - 798@implementation WebFullscreenWindow 799 800- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag 801{ 802 UNUSED_PARAM(aStyle); 803 self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:bufferingType defer:flag]; 804 if (!self) 805 return nil; 806 [self setOpaque:NO]; 807 [self setBackgroundColor:[NSColor clearColor]]; 808 [self setIgnoresMouseEvents:NO]; 809 [self setAcceptsMouseMovedEvents:YES]; 810 [self setReleasedWhenClosed:NO]; 811 [self setHasShadow:YES]; 812#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 813 [self setMovable:NO]; 814#else 815 [self setMovableByWindowBackground:NO]; 816#endif 817 818 NSView* contentView = [self contentView]; 819 _animationView = [[NSView alloc] initWithFrame:[contentView bounds]]; 820 821 CALayer* contentLayer = [[CALayer alloc] init]; 822 [_animationView setLayer:contentLayer]; 823 [_animationView setWantsLayer:YES]; 824 [_animationView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; 825 [contentView addSubview:_animationView]; 826 827 _backgroundLayer = [[CALayer alloc] init]; 828 [contentLayer addSublayer:_backgroundLayer]; 829#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 830 [contentLayer setGeometryFlipped:YES]; 831#else 832 [contentLayer setSublayerTransform:CATransform3DMakeScale(1, -1, 1)]; 833#endif 834 [contentLayer setOpacity:0]; 835 836 [_backgroundLayer setBackgroundColor:CGColorGetConstantColor(kCGColorBlack)]; 837 [_backgroundLayer setOpacity:0]; 838 return self; 839} 840 841- (void)dealloc 842{ 843 [_animationView release]; 844 [_backgroundLayer release]; 845 [_rendererLayer release]; 846 [super dealloc]; 847} 848 849- (BOOL)canBecomeKeyWindow 850{ 851 return YES; 852} 853 854- (void)keyDown:(NSEvent *)theEvent 855{ 856 if ([[theEvent charactersIgnoringModifiers] isEqual:@"\e"]) // Esacpe key-code 857 [self cancelOperation:self]; 858 else [super keyDown:theEvent]; 859} 860 861- (void)cancelOperation:(id)sender 862{ 863 UNUSED_PARAM(sender); 864 [[self windowController] _requestExitFullscreenWithAnimation:YES]; 865} 866 867- (CALayer*)rendererLayer 868{ 869 return _rendererLayer; 870} 871 872- (void)setRendererLayer:(CALayer *)rendererLayer 873{ 874 [CATransaction begin]; 875 [CATransaction setDisableActions:YES]; 876 [rendererLayer retain]; 877 [_rendererLayer removeFromSuperlayer]; 878 [_rendererLayer release]; 879 _rendererLayer = rendererLayer; 880 881 if (_rendererLayer) 882 [[[self animationView] layer] addSublayer:_rendererLayer]; 883 [CATransaction commit]; 884} 885 886- (CALayer*)backgroundLayer 887{ 888 return _backgroundLayer; 889} 890 891- (NSView*)animationView 892{ 893 return _animationView; 894} 895@end 896 897#pragma mark - 898#pragma mark MediaEventListener 899 900MediaEventListener::MediaEventListener(WebFullScreenController* delegate) 901 : EventListener(CPPEventListenerType) 902 , delegate(delegate) 903{ 904} 905 906PassRefPtr<MediaEventListener> MediaEventListener::create(WebFullScreenController* delegate) 907{ 908 return adoptRef(new MediaEventListener(delegate)); 909} 910 911bool MediaEventListener::operator==(const EventListener& listener) 912{ 913 return this == &listener; 914} 915 916void MediaEventListener::handleEvent(ScriptExecutionContext* context, Event* event) 917{ 918 [delegate _updatePowerAssertions]; 919} 920 921#endif /* ENABLE(FULLSCREEN_API) */ 922