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 57 static const NSTimeInterval tickleTimerInterval = 1.0; 58 static NSString* const isEnteringFullscreenKey = @"isEnteringFullscreen"; 59 60 using 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 98 class MediaEventListener : public EventListener { 99 public: 100 static PassRefPtr<MediaEventListener> create(WebFullScreenController* delegate); 101 virtual bool operator==(const EventListener&); 102 virtual void handleEvent(ScriptExecutionContext*, Event*); 103 104 private: 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 900 MediaEventListener::MediaEventListener(WebFullScreenController* delegate) 901 : EventListener(CPPEventListenerType) 902 , delegate(delegate) 903 { 904 } 905 906 PassRefPtr<MediaEventListener> MediaEventListener::create(WebFullScreenController* delegate) 907 { 908 return adoptRef(new MediaEventListener(delegate)); 909 } 910 911 bool MediaEventListener::operator==(const EventListener& listener) 912 { 913 return this == &listener; 914 } 915 916 void MediaEventListener::handleEvent(ScriptExecutionContext* context, Event* event) 917 { 918 [delegate _updatePowerAssertions]; 919 } 920 921 #endif /* ENABLE(FULLSCREEN_API) */ 922