1 /* 2 * Copyright (C) 2009, 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 #import "config.h" 27 28 #if ENABLE(FULLSCREEN_API) 29 30 #import "WKFullScreenWindowController.h" 31 32 #import "LayerTreeContext.h" 33 #import "WKAPICast.h" 34 #import "WKViewInternal.h" 35 #import "WebFullScreenManagerProxy.h" 36 #import "WebPageProxy.h" 37 #import <Carbon/Carbon.h> // For SetSystemUIMode() 38 #import <IOKit/pwr_mgt/IOPMLib.h> // For IOPMAssertionCreate() 39 #import <QuartzCore/QuartzCore.h> 40 #import <WebCore/FloatRect.h> 41 #import <WebCore/IntRect.h> 42 #import <WebKitSystemInterface.h> 43 44 static const NSTimeInterval tickleTimerInterval = 1.0; 45 46 using namespace WebKit; 47 using namespace WebCore; 48 49 #if defined(BUILDING_ON_LEOPARD) 50 @interface CATransaction(SnowLeopardConvenienceFunctions) 51 + (void)setDisableActions:(BOOL)flag; 52 + (void)setAnimationDuration:(CFTimeInterval)dur; 53 @end 54 55 @implementation CATransaction(SnowLeopardConvenienceFunctions) 56 + (void)setDisableActions:(BOOL)flag 57 { 58 [self setValue:[NSNumber numberWithBool:flag] forKey:kCATransactionDisableActions]; 59 } 60 61 + (void)setAnimationDuration:(CFTimeInterval)dur 62 { 63 [self setValue:[NSNumber numberWithDouble:dur] forKey:kCATransactionAnimationDuration]; 64 } 65 @end 66 67 #endif 68 69 @interface WKFullScreenWindow : NSWindow 70 { 71 NSView* _animationView; 72 CALayer* _backgroundLayer; 73 } 74 - (CALayer*)backgroundLayer; 75 - (NSView*)animationView; 76 @end 77 78 @interface WKFullScreenWindowController(Private) 79 - (void)_requestExitFullScreenWithAnimation:(BOOL)animation; 80 - (void)_updateMenuAndDockForFullScreen; 81 - (void)_updatePowerAssertions; 82 - (WKFullScreenWindow *)_fullScreenWindow; 83 - (CFTimeInterval)_animationDuration; 84 - (void)_swapView:(NSView*)view with:(NSView*)otherView; 85 - (WebFullScreenManagerProxy*)_manager; 86 @end 87 88 @interface NSWindow(IsOnActiveSpaceAdditionForTigerAndLeopard) 89 - (BOOL)isOnActiveSpace; 90 @end 91 92 @implementation WKFullScreenWindowController 93 94 #pragma mark - 95 #pragma mark Initialization 96 - (id)init 97 { 98 NSWindow *window = [[WKFullScreenWindow alloc] initWithContentRect:NSZeroRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]; 99 self = [super initWithWindow:window]; 100 [window release]; 101 if (!self) 102 return nil; 103 [self windowDidLoad]; 104 105 return self; 106 } 107 108 - (void)dealloc 109 { 110 [self setWebView:nil]; 111 112 [NSObject cancelPreviousPerformRequestsWithTarget:self]; 113 114 [[NSNotificationCenter defaultCenter] removeObserver:self]; 115 [super dealloc]; 116 } 117 118 - (void)windowDidLoad 119 { 120 [super windowDidLoad]; 121 122 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidResignActive:) name:NSApplicationDidResignActiveNotification object:NSApp]; 123 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidChangeScreenParameters:) name:NSApplicationDidChangeScreenParametersNotification object:NSApp]; 124 } 125 126 #pragma mark - 127 #pragma mark Accessors 128 129 - (WKView*)webView 130 { 131 return _webView; 132 } 133 134 - (void)setWebView:(WKView *)webView 135 { 136 [webView retain]; 137 [_webView release]; 138 _webView = webView; 139 } 140 141 #pragma mark - 142 #pragma mark Notifications 143 144 - (void)applicationDidResignActive:(NSNotification*)notification 145 { 146 // Check to see if the fullScreenWindow is on the active space; this function is available 147 // on 10.6 and later, so default to YES if the function is not available: 148 NSWindow* fullScreenWindow = [self _fullScreenWindow]; 149 BOOL isOnActiveSpace = ([fullScreenWindow respondsToSelector:@selector(isOnActiveSpace)] ? [fullScreenWindow isOnActiveSpace] : YES); 150 151 // Replicate the QuickTime Player (X) behavior when losing active application status: 152 // Is the fullScreen screen the main screen? (Note: this covers the case where only a 153 // single screen is available.) Is the fullScreen screen on the current space? IFF so, 154 // then exit fullScreen mode. 155 if ([fullScreenWindow screen] == [[NSScreen screens] objectAtIndex:0] && isOnActiveSpace) 156 [self _requestExitFullScreenWithAnimation:NO]; 157 } 158 159 - (void)applicationDidChangeScreenParameters:(NSNotification*)notification 160 { 161 // The user may have changed the main screen by moving the menu bar, or they may have changed 162 // the Dock's size or location, or they may have changed the fullScreen screen's dimensions. 163 // Update our presentation parameters, and ensure that the full screen window occupies the 164 // entire screen: 165 [self _updateMenuAndDockForFullScreen]; 166 NSWindow* window = [self window]; 167 [window setFrame:[[window screen] frame] display:YES]; 168 } 169 170 #pragma mark - 171 #pragma mark Exposed Interface 172 173 - (void)enterFullScreen:(NSScreen *)screen 174 { 175 if (_isFullScreen) 176 return; 177 178 _isFullScreen = YES; 179 _isAnimating = YES; 180 181 NSDisableScreenUpdates(); 182 183 if (!screen) 184 screen = [NSScreen mainScreen]; 185 NSRect screenFrame = [screen frame]; 186 187 NSRect webViewFrame = [_webView convertRectToBase:[_webView frame]]; 188 webViewFrame.origin = [[_webView window] convertBaseToScreen:webViewFrame.origin]; 189 190 // In the case of a multi-monitor setup where the webView straddles two 191 // monitors, we must create a window large enough to contain the destination 192 // frame and the initial frame. 193 NSRect windowFrame = NSUnionRect(screenFrame, webViewFrame); 194 [[self window] setFrame:windowFrame display:YES]; 195 196 CALayer* backgroundLayer = [[self _fullScreenWindow] backgroundLayer]; 197 NSRect backgroundFrame = {[[self window] convertScreenToBase:screenFrame.origin], screenFrame.size}; 198 backgroundFrame = [[[self window] contentView] convertRectFromBase:backgroundFrame]; 199 200 [CATransaction begin]; 201 [CATransaction setDisableActions:YES]; 202 [backgroundLayer setFrame:NSRectToCGRect(backgroundFrame)]; 203 [CATransaction commit]; 204 205 CFTimeInterval duration = [self _animationDuration]; 206 [self _manager]->willEnterFullScreen(); 207 [self _manager]->beginEnterFullScreenAnimation(duration); 208 } 209 210 - (void)beganEnterFullScreenAnimation 211 { 212 [self _updateMenuAndDockForFullScreen]; 213 [self _updatePowerAssertions]; 214 215 // In a previous incarnation, the NSWindow attached to this controller may have 216 // been on a different screen. Temporarily change the collectionBehavior of the window: 217 NSWindow* fullScreenWindow = [self window]; 218 NSWindowCollectionBehavior behavior = [fullScreenWindow collectionBehavior]; 219 [fullScreenWindow setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces]; 220 [fullScreenWindow makeKeyAndOrderFront:self]; 221 [fullScreenWindow setCollectionBehavior:behavior]; 222 223 // Start the opacity animation. We can use implicit animations here because we don't care when 224 // the animation finishes. 225 [CATransaction begin]; 226 [CATransaction setAnimationDuration:[self _animationDuration]]; 227 [[[self _fullScreenWindow] backgroundLayer] setOpacity:1]; 228 [CATransaction commit]; 229 230 NSEnableScreenUpdates(); 231 _isAnimating = YES; 232 } 233 234 - (void)finishedEnterFullScreenAnimation:(bool)completed 235 { 236 NSDisableScreenUpdates(); 237 238 if (completed) { 239 // Swap the webView placeholder into place. 240 if (!_webViewPlaceholder) 241 _webViewPlaceholder.adoptNS([[NSView alloc] init]); 242 [self _swapView:_webView with:_webViewPlaceholder.get()]; 243 244 // Then insert the WebView into the full screen window 245 NSView* animationView = [[self _fullScreenWindow] animationView]; 246 [animationView addSubview:_webView positioned:NSWindowBelow relativeTo:_layerHostingView.get()]; 247 [_webView setFrame:[animationView bounds]]; 248 249 // FIXME: In Barolo, orderIn will animate, which is not what we want. Find a way 250 // to work around this behavior. 251 //[[_webViewPlaceholder.get() window] orderOut:self]; 252 [[self window] makeKeyAndOrderFront:self]; 253 } 254 255 [self _manager]->didEnterFullScreen(); 256 NSEnableScreenUpdates(); 257 258 _isAnimating = NO; 259 } 260 261 - (void)exitFullScreen 262 { 263 if (!_isFullScreen) 264 return; 265 266 _isFullScreen = NO; 267 _isAnimating = YES; 268 269 NSDisableScreenUpdates(); 270 271 [self _manager]->willExitFullScreen(); 272 [self _manager]->beginExitFullScreenAnimation([self _animationDuration]); 273 } 274 275 - (void)beganExitFullScreenAnimation 276 { 277 [self _updateMenuAndDockForFullScreen]; 278 [self _updatePowerAssertions]; 279 280 // The user may have moved the fullScreen window in Spaces, so temporarily change 281 // the collectionBehavior of the webView's window: 282 NSWindow* webWindow = [[self webView] window]; 283 NSWindowCollectionBehavior behavior = [webWindow collectionBehavior]; 284 [webWindow setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces]; 285 [webWindow orderWindow:NSWindowBelow relativeTo:[[self window] windowNumber]]; 286 [webWindow setCollectionBehavior:behavior]; 287 288 // Swap the webView back into its original position: 289 if ([_webView window] == [self window]) 290 [self _swapView:_webViewPlaceholder.get() with:_webView]; 291 292 [CATransaction begin]; 293 [CATransaction setAnimationDuration:[self _animationDuration]]; 294 [[[self _fullScreenWindow] backgroundLayer] setOpacity:0]; 295 [CATransaction commit]; 296 297 NSEnableScreenUpdates(); 298 _isAnimating = YES; 299 } 300 301 - (void)finishedExitFullScreenAnimation:(bool)completed 302 { 303 NSDisableScreenUpdates(); 304 305 if (completed) { 306 [self _updateMenuAndDockForFullScreen]; 307 [self _updatePowerAssertions]; 308 [NSCursor setHiddenUntilMouseMoves:YES]; 309 310 [[self window] orderOut:self]; 311 [[_webView window] makeKeyAndOrderFront:self]; 312 } 313 314 [self _manager]->didExitFullScreen(); 315 NSEnableScreenUpdates(); 316 317 _isAnimating = NO; 318 } 319 320 - (void)enterAcceleratedCompositingMode:(const WebKit::LayerTreeContext&)layerTreeContext 321 { 322 if (_layerHostingView) 323 return; 324 325 ASSERT(!layerTreeContext.isEmpty()); 326 327 // Create an NSView that will host our layer tree. 328 _layerHostingView.adoptNS([[NSView alloc] initWithFrame:[[self window] frame]]); 329 [_layerHostingView.get() setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; 330 331 [CATransaction begin]; 332 [CATransaction setDisableActions:YES]; 333 WKFullScreenWindow* window = [self _fullScreenWindow]; 334 [[window animationView] addSubview:_layerHostingView.get()]; 335 336 // Create a root layer that will back the NSView. 337 RetainPtr<CALayer> rootLayer(AdoptNS, [[CALayer alloc] init]); 338 #ifndef NDEBUG 339 [rootLayer.get() setName:@"Hosting root layer"]; 340 #endif 341 342 CALayer *renderLayer = WKMakeRenderLayer(layerTreeContext.contextID); 343 [rootLayer.get() addSublayer:renderLayer]; 344 345 [_layerHostingView.get() setLayer:rootLayer.get()]; 346 [_layerHostingView.get() setWantsLayer:YES]; 347 [[window backgroundLayer] setHidden:NO]; 348 [CATransaction commit]; 349 } 350 351 - (void)exitAcceleratedCompositingMode 352 { 353 if (!_layerHostingView) 354 return; 355 356 [CATransaction begin]; 357 [CATransaction setDisableActions:YES]; 358 [_layerHostingView.get() removeFromSuperview]; 359 [_layerHostingView.get() setLayer:nil]; 360 [_layerHostingView.get() setWantsLayer:NO]; 361 [[[self _fullScreenWindow] backgroundLayer] setHidden:YES]; 362 [CATransaction commit]; 363 364 _layerHostingView = 0; 365 } 366 367 - (WebCore::IntRect)getFullScreenRect 368 { 369 return enclosingIntRect([[self window] frame]); 370 } 371 372 #pragma mark - 373 #pragma mark Internal Interface 374 375 - (void)_updateMenuAndDockForFullScreen 376 { 377 // NSApplicationPresentationOptions is available on > 10.6 only: 378 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 379 NSApplicationPresentationOptions options = NSApplicationPresentationDefault; 380 NSScreen* fullScreenScreen = [[self window] screen]; 381 382 if (_isFullScreen) { 383 // Auto-hide the menu bar if the fullScreenScreen contains the menu bar: 384 // NOTE: if the fullScreenScreen contains the menu bar but not the dock, we must still 385 // auto-hide the dock, or an exception will be thrown. 386 if ([[NSScreen screens] objectAtIndex:0] == fullScreenScreen) 387 options |= (NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationAutoHideDock); 388 // Check if the current screen contains the dock by comparing the screen's frame to its 389 // visibleFrame; if a dock is present, the visibleFrame will differ. If the current screen 390 // contains the dock, hide it. 391 else if (!NSEqualRects([fullScreenScreen frame], [fullScreenScreen visibleFrame])) 392 options |= NSApplicationPresentationAutoHideDock; 393 } 394 395 if ([NSApp respondsToSelector:@selector(setPresentationOptions:)]) 396 [NSApp setPresentationOptions:options]; 397 else 398 #endif 399 SetSystemUIMode(_isFullScreen ? kUIModeNormal : kUIModeAllHidden, 0); 400 } 401 402 #if !defined(BUILDING_ON_TIGER) // IOPMAssertionCreateWithName not defined on < 10.5 403 - (void)_disableIdleDisplaySleep 404 { 405 if (_idleDisplaySleepAssertion == kIOPMNullAssertionID) 406 #if defined(BUILDING_ON_LEOPARD) // IOPMAssertionCreateWithName is not defined in the 10.5 SDK 407 IOPMAssertionCreate(kIOPMAssertionTypeNoDisplaySleep, kIOPMAssertionLevelOn, &_idleDisplaySleepAssertion); 408 #else // IOPMAssertionCreate is depreciated in > 10.5 409 IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep, kIOPMAssertionLevelOn, CFSTR("WebKit playing a video fullScreen."), &_idleDisplaySleepAssertion); 410 #endif 411 } 412 413 - (void)_enableIdleDisplaySleep 414 { 415 if (_idleDisplaySleepAssertion != kIOPMNullAssertionID) { 416 IOPMAssertionRelease(_idleDisplaySleepAssertion); 417 _idleDisplaySleepAssertion = kIOPMNullAssertionID; 418 } 419 } 420 421 - (void)_disableIdleSystemSleep 422 { 423 if (_idleSystemSleepAssertion == kIOPMNullAssertionID) 424 #if defined(BUILDING_ON_LEOPARD) // IOPMAssertionCreateWithName is not defined in the 10.5 SDK 425 IOPMAssertionCreate(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, &_idleSystemSleepAssertion); 426 #else // IOPMAssertionCreate is depreciated in > 10.5 427 IOPMAssertionCreateWithName(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, CFSTR("WebKit playing a video fullScreen."), &_idleSystemSleepAssertion); 428 #endif 429 } 430 431 - (void)_enableIdleSystemSleep 432 { 433 if (_idleSystemSleepAssertion != kIOPMNullAssertionID) { 434 IOPMAssertionRelease(_idleSystemSleepAssertion); 435 _idleSystemSleepAssertion = kIOPMNullAssertionID; 436 } 437 } 438 439 - (void)_enableTickleTimer 440 { 441 [_tickleTimer invalidate]; 442 [_tickleTimer release]; 443 _tickleTimer = [[NSTimer scheduledTimerWithTimeInterval:tickleTimerInterval target:self selector:@selector(_tickleTimerFired) userInfo:nil repeats:YES] retain]; 444 } 445 446 - (void)_disableTickleTimer 447 { 448 [_tickleTimer invalidate]; 449 [_tickleTimer release]; 450 _tickleTimer = nil; 451 } 452 453 - (void)_tickleTimerFired 454 { 455 UpdateSystemActivity(OverallAct); 456 } 457 #endif 458 459 - (void)_updatePowerAssertions 460 { 461 #if !defined(BUILDING_ON_TIGER) 462 if (_isPlaying && _isFullScreen) { 463 [self _disableIdleSystemSleep]; 464 [self _disableIdleDisplaySleep]; 465 [self _enableTickleTimer]; 466 } else { 467 [self _enableIdleSystemSleep]; 468 [self _enableIdleDisplaySleep]; 469 [self _disableTickleTimer]; 470 } 471 #endif 472 } 473 474 - (WebFullScreenManagerProxy*)_manager 475 { 476 WebPageProxy* webPage = toImpl([_webView pageRef]); 477 if (!webPage) 478 return 0; 479 return webPage->fullScreenManager(); 480 } 481 482 - (void)_requestExit 483 { 484 [self exitFullScreen]; 485 _forceDisableAnimation = NO; 486 } 487 488 - (void)_requestExitFullScreenWithAnimation:(BOOL)animation 489 { 490 _forceDisableAnimation = !animation; 491 [self performSelector:@selector(_requestExit) withObject:nil afterDelay:0]; 492 493 } 494 495 - (void)_swapView:(NSView*)view with:(NSView*)otherView 496 { 497 [otherView setFrame:[view frame]]; 498 [otherView setAutoresizingMask:[view autoresizingMask]]; 499 [otherView removeFromSuperview]; 500 [[view superview] replaceSubview:view with:otherView]; 501 } 502 503 #pragma mark - 504 #pragma mark Utility Functions 505 506 - (WKFullScreenWindow *)_fullScreenWindow 507 { 508 ASSERT([[self window] isKindOfClass:[WKFullScreenWindow class]]); 509 return (WKFullScreenWindow *)[self window]; 510 } 511 512 - (CFTimeInterval)_animationDuration 513 { 514 static const CFTimeInterval defaultDuration = 0.5; 515 CFTimeInterval duration = defaultDuration; 516 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 517 NSUInteger modifierFlags = [NSEvent modifierFlags]; 518 #else 519 NSUInteger modifierFlags = [[NSApp currentEvent] modifierFlags]; 520 #endif 521 if ((modifierFlags & NSControlKeyMask) == NSControlKeyMask) 522 duration *= 2; 523 if ((modifierFlags & NSShiftKeyMask) == NSShiftKeyMask) 524 duration *= 10; 525 if (_forceDisableAnimation) { 526 // This will disable scale animation 527 duration = 0; 528 } 529 return duration; 530 } 531 532 @end 533 534 #pragma mark - 535 @implementation WKFullScreenWindow 536 537 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag 538 { 539 UNUSED_PARAM(aStyle); 540 self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:bufferingType defer:flag]; 541 if (!self) 542 return nil; 543 [self setOpaque:NO]; 544 [self setBackgroundColor:[NSColor clearColor]]; 545 [self setIgnoresMouseEvents:NO]; 546 [self setAcceptsMouseMovedEvents:YES]; 547 [self setReleasedWhenClosed:NO]; 548 [self setHasShadow:YES]; 549 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 550 [self setMovable:NO]; 551 #else 552 [self setMovableByWindowBackground:NO]; 553 #endif 554 555 NSView* contentView = [self contentView]; 556 _animationView = [[NSView alloc] initWithFrame:[contentView bounds]]; 557 558 CALayer* contentLayer = [[CALayer alloc] init]; 559 [_animationView setLayer:contentLayer]; 560 [_animationView setWantsLayer:YES]; 561 [_animationView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; 562 [contentView addSubview:_animationView]; 563 564 _backgroundLayer = [[CALayer alloc] init]; 565 [contentLayer addSublayer:_backgroundLayer]; 566 567 [_backgroundLayer setBackgroundColor:CGColorGetConstantColor(kCGColorBlack)]; 568 [_backgroundLayer setOpacity:0]; 569 return self; 570 } 571 572 - (void)dealloc 573 { 574 [_animationView release]; 575 [_backgroundLayer release]; 576 [super dealloc]; 577 } 578 579 - (BOOL)canBecomeKeyWindow 580 { 581 return YES; 582 } 583 584 - (void)keyDown:(NSEvent *)theEvent 585 { 586 if ([[theEvent charactersIgnoringModifiers] isEqual:@"\e"]) // Esacpe key-code 587 [self cancelOperation:self]; 588 else [super keyDown:theEvent]; 589 } 590 591 - (void)cancelOperation:(id)sender 592 { 593 UNUSED_PARAM(sender); 594 [[self windowController] _requestExitFullScreenWithAnimation:YES]; 595 } 596 597 - (CALayer*)backgroundLayer 598 { 599 return _backgroundLayer; 600 } 601 602 - (NSView*)animationView 603 { 604 return _animationView; 605 } 606 @end 607 608 #endif 609