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 44static const NSTimeInterval tickleTimerInterval = 1.0; 45 46using namespace WebKit; 47using 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