2 * Copyright (C) 2009, 2010, 2011 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
28 #if ENABLE(FULLSCREEN_API)
30 #import "WKFullScreenWindowController.h"
32 #import "LayerTreeContext.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 <WebKit/WebNSWindowExtras.h>
43 #import <WebKitSystemInterface.h>
44 #import <wtf/UnusedParam.h>
46 static const NSTimeInterval tickleTimerInterval = 1.0;
48 using namespace WebKit;
49 using namespace WebCore;
51 #if defined(BUILDING_ON_LEOPARD)
52 @interface CATransaction(SnowLeopardConvenienceFunctions)
53 + (void)setDisableActions:(BOOL)flag;
54 + (void)setAnimationDuration:(CFTimeInterval)dur;
57 @implementation CATransaction(SnowLeopardConvenienceFunctions)
58 + (void)setDisableActions:(BOOL)flag
60 [self setValue:[NSNumber numberWithBool:flag] forKey:kCATransactionDisableActions];
63 + (void)setAnimationDuration:(CFTimeInterval)dur
65 [self setValue:[NSNumber numberWithDouble:dur] forKey:kCATransactionAnimationDuration];
71 @interface WKFullScreenWindow : NSWindow
73 NSView* _animationView;
74 CALayer* _backgroundLayer;
76 - (CALayer*)backgroundLayer;
77 - (NSView*)animationView;
80 @interface WKFullScreenWindowController(Private)
81 - (void)_requestExitFullScreenWithAnimation:(BOOL)animation;
82 - (void)_updateMenuAndDockForFullScreen;
83 - (void)_updatePowerAssertions;
84 - (WKFullScreenWindow *)_fullScreenWindow;
85 - (CFTimeInterval)_animationDuration;
86 - (void)_swapView:(NSView*)view with:(NSView*)otherView;
87 - (WebPageProxy*)_page;
88 - (WebFullScreenManagerProxy*)_manager;
91 @interface NSWindow(IsOnActiveSpaceAdditionForTigerAndLeopard)
92 - (BOOL)isOnActiveSpace;
95 @implementation WKFullScreenWindowController
98 #pragma mark Initialization
101 NSWindow *window = [[WKFullScreenWindow alloc] initWithContentRect:NSZeroRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
102 self = [super initWithWindow:window];
106 [self windowDidLoad];
113 [self setWebView:nil];
115 [NSObject cancelPreviousPerformRequestsWithTarget:self];
117 [[NSNotificationCenter defaultCenter] removeObserver:self];
121 - (void)windowDidLoad
123 [super windowDidLoad];
125 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidResignActive:) name:NSApplicationDidResignActiveNotification object:NSApp];
126 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidChangeScreenParameters:) name:NSApplicationDidChangeScreenParametersNotification object:NSApp];
130 #pragma mark Accessors
137 - (void)setWebView:(WKView *)webView
145 #pragma mark Notifications
147 - (void)applicationDidResignActive:(NSNotification*)notification
149 // Check to see if the fullScreenWindow is on the active space; this function is available
150 // on 10.6 and later, so default to YES if the function is not available:
151 NSWindow* fullScreenWindow = [self _fullScreenWindow];
152 BOOL isOnActiveSpace = ([fullScreenWindow respondsToSelector:@selector(isOnActiveSpace)] ? [fullScreenWindow isOnActiveSpace] : YES);
154 // Replicate the QuickTime Player (X) behavior when losing active application status:
155 // Is the fullScreen screen the main screen? (Note: this covers the case where only a
156 // single screen is available.) Is the fullScreen screen on the current space? IFF so,
157 // then exit fullScreen mode.
158 if ([fullScreenWindow screen] == [[NSScreen screens] objectAtIndex:0] && isOnActiveSpace)
159 [self _requestExitFullScreenWithAnimation:NO];
162 - (void)applicationDidChangeScreenParameters:(NSNotification*)notification
164 // The user may have changed the main screen by moving the menu bar, or they may have changed
165 // the Dock's size or location, or they may have changed the fullScreen screen's dimensions.
166 // Update our presentation parameters, and ensure that the full screen window occupies the
168 [self _updateMenuAndDockForFullScreen];
169 NSWindow* window = [self window];
170 [window setFrame:[[window screen] frame] display:YES];
174 #pragma mark Exposed Interface
176 - (void)enterFullScreen:(NSScreen *)screen
183 NSDisableScreenUpdates();
186 screen = [NSScreen mainScreen];
187 NSRect screenFrame = [screen frame];
189 NSRect webViewFrame = [_webView convertRectToBase:[_webView frame]];
190 webViewFrame.origin = [[_webView window] convertBaseToScreen:webViewFrame.origin];
192 // In the case of a multi-monitor setup where the webView straddles two
193 // monitors, we must create a window large enough to contain the destination
194 // frame and the initial frame.
195 NSRect windowFrame = NSUnionRect(screenFrame, webViewFrame);
197 [CATransaction begin];
198 [CATransaction setDisableActions:YES];
199 [[self window] setFrame:windowFrame display:YES];
201 CALayer* backgroundLayer = [[self _fullScreenWindow] backgroundLayer];
202 NSRect backgroundFrame = {[[self window] convertScreenToBase:screenFrame.origin], screenFrame.size};
203 backgroundFrame = [[[self window] contentView] convertRectFromBase:backgroundFrame];
205 [backgroundLayer setFrame:NSRectToCGRect(backgroundFrame)];
206 [CATransaction commit];
208 CFTimeInterval duration = [self _animationDuration];
209 [self _manager]->willEnterFullScreen();
210 [self _manager]->beginEnterFullScreenAnimation(duration);
213 - (void)beganEnterFullScreenAnimation
215 if (_isEnteringFullScreen)
217 _isEnteringFullScreen = YES;
219 if (_isExitingFullScreen)
220 [self finishedExitFullScreenAnimation:NO];
222 [self _updateMenuAndDockForFullScreen];
223 [self _updatePowerAssertions];
225 // In a previous incarnation, the NSWindow attached to this controller may have
226 // been on a different screen. Temporarily change the collectionBehavior of the window:
227 NSWindow* fullScreenWindow = [self window];
228 NSWindowCollectionBehavior behavior = [fullScreenWindow collectionBehavior];
229 [fullScreenWindow setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces];
230 [fullScreenWindow makeKeyAndOrderFront:self];
231 [fullScreenWindow setCollectionBehavior:behavior];
233 // Start the opacity animation. We can use implicit animations here because we don't care when
234 // the animation finishes.
235 [CATransaction begin];
236 [CATransaction setAnimationDuration:[self _animationDuration]];
237 [[[self _fullScreenWindow] backgroundLayer] setOpacity:1];
238 [CATransaction commit];
240 NSEnableScreenUpdates();
243 - (void)finishedEnterFullScreenAnimation:(bool)completed
245 if (!_isEnteringFullScreen)
247 _isEnteringFullScreen = NO;
250 NSDisableScreenUpdates();
252 // Swap the webView placeholder into place.
253 if (!_webViewPlaceholder)
254 _webViewPlaceholder.adoptNS([[NSView alloc] init]);
255 NSResponder *webWindowFirstResponder = [[_webView window] firstResponder];
256 [self _swapView:_webView with:_webViewPlaceholder.get()];
258 // Then insert the WebView into the full screen window
259 NSView* contentView = [[self _fullScreenWindow] contentView];
260 [contentView addSubview:_webView positioned:NSWindowBelow relativeTo:nil];
261 [_webView setFrame:[contentView bounds]];
262 [[self window] makeResponder:webWindowFirstResponder firstResponderIfDescendantOfView:_webView];
264 NSWindow *webWindow = [_webViewPlaceholder.get() window];
265 #if !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
266 // In Lion, NSWindow will animate into and out of orderOut operations. Suppress that
267 // behavior here, making sure to reset the animation behavior afterward.
268 NSWindowAnimationBehavior animationBehavior = [webWindow animationBehavior];
269 [webWindow setAnimationBehavior:NSWindowAnimationBehaviorNone];
271 [webWindow orderOut:self];
272 #if !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
273 [webWindow setAnimationBehavior:animationBehavior];
275 [self _manager]->didEnterFullScreen();
278 // Complete the animation once -(void)exitCompositingMode is called.
281 - (void)exitFullScreen
288 NSDisableScreenUpdates();
290 [self _manager]->willExitFullScreen();
291 [self _manager]->beginExitFullScreenAnimation([self _animationDuration]);
294 - (void)beganExitFullScreenAnimation
296 if (_isExitingFullScreen)
298 _isExitingFullScreen = YES;
300 if (_isEnteringFullScreen)
301 [self finishedEnterFullScreenAnimation:NO];
303 [self _updateMenuAndDockForFullScreen];
304 [self _updatePowerAssertions];
306 // Swap the webView back into its original position:
307 if ([_webView window] == [self window]) {
308 NSResponder *fullScreenWindowFirstResponder = [[self _fullScreenWindow] firstResponder];
309 #if defined(BUILDING_ON_LEOPARD) || defined(BUILDING_ON_SNOW_LEOPARD)
310 // Work around a bug in AppKit <rdar://problem/9443385> where moving a
311 // layer-hosted view from a layer-backed view to a non-layer-backed view
312 // generates an exception.
313 if (![_webView wantsLayer] && [_webView layer]) {
314 [_webView removeFromSuperview];
315 for (NSView* child in [_webView subviews])
316 [[child layer] removeFromSuperlayer];
319 [self _swapView:_webViewPlaceholder.get() with:_webView];
320 [[_webView window] makeResponder:fullScreenWindowFirstResponder firstResponderIfDescendantOfView:_webView];
321 NSWindow* webWindow = [_webView window];
322 #if !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
323 // In Lion, NSWindow will animate into and out of orderOut operations. Suppress that
324 // behavior here, making sure to reset the animation behavior afterward.
325 NSWindowAnimationBehavior animationBehavior = [webWindow animationBehavior];
326 [webWindow setAnimationBehavior:NSWindowAnimationBehaviorNone];
328 // If the user has moved the fullScreen window into a new space, temporarily change
329 // the collectionBehavior of the webView's window so that it is pulled into the active space:
330 if (![webWindow isOnActiveSpace]) {
331 NSWindowCollectionBehavior behavior = [webWindow collectionBehavior];
332 [webWindow setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces];
333 [webWindow orderWindow:NSWindowBelow relativeTo:[[self window] windowNumber]];
334 [webWindow setCollectionBehavior:behavior];
336 [webWindow orderWindow:NSWindowBelow relativeTo:[[self window] windowNumber]];
338 #if !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
339 [webWindow setAnimationBehavior:animationBehavior];
343 [CATransaction begin];
344 [CATransaction setAnimationDuration:[self _animationDuration]];
345 [[[self _fullScreenWindow] backgroundLayer] setOpacity:0];
346 [CATransaction commit];
348 NSEnableScreenUpdates();
351 - (void)finishedExitFullScreenAnimation:(bool)completed
353 if (!_isExitingFullScreen)
355 _isExitingFullScreen = NO;
357 NSDisableScreenUpdates();
359 [self _updateMenuAndDockForFullScreen];
360 [self _updatePowerAssertions];
361 [NSCursor setHiddenUntilMouseMoves:YES];
363 [self _manager]->didExitFullScreen();
366 - (void)enterAcceleratedCompositingMode:(const WebKit::LayerTreeContext&)layerTreeContext
368 if (_layerHostingView)
371 // Create an NSView that will host our layer tree.
372 _layerHostingView.adoptNS([[NSView alloc] initWithFrame:[[self window] frame]]);
373 [_layerHostingView.get() setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
375 [CATransaction begin];
376 [CATransaction setDisableActions:YES];
377 WKFullScreenWindow* window = [self _fullScreenWindow];
378 [[window contentView] addSubview:_layerHostingView.get() positioned:NSWindowAbove relativeTo:nil];
380 // Create a root layer that will back the NSView.
381 RetainPtr<CALayer> rootLayer(AdoptNS, [[CALayer alloc] init]);
383 [rootLayer.get() setName:@"Hosting root layer"];
386 CALayer *renderLayer = WKMakeRenderLayer(layerTreeContext.contextID);
387 [rootLayer.get() addSublayer:renderLayer];
389 [_layerHostingView.get() setLayer:rootLayer.get()];
390 [_layerHostingView.get() setWantsLayer:YES];
391 [[window backgroundLayer] setHidden:NO];
392 [CATransaction commit];
395 - (void)exitAcceleratedCompositingMode
397 if (!_layerHostingView)
400 [CATransaction begin];
401 [CATransaction setDisableActions:YES];
402 [_layerHostingView.get() removeFromSuperview];
403 [_layerHostingView.get() setLayer:nil];
404 [_layerHostingView.get() setWantsLayer:NO];
405 [[[self _fullScreenWindow] backgroundLayer] setHidden:YES];
406 [CATransaction commit];
408 // Complete the animation out of full-screen mode
409 // by hiding the full-screen window:
410 if (!_isFullScreen) {
411 [[_webView window] display];
412 [[self window] orderOut:self];
413 [[_webView window] makeKeyAndOrderFront:self];
416 _layerHostingView = 0;
417 NSEnableScreenUpdates();
419 [self _manager]->disposeOfLayerClient();
422 - (WebCore::IntRect)getFullScreenRect
424 return enclosingIntRect([[self window] frame]);
429 // We are being asked to close rapidly, most likely because the page
430 // has closed or the web process has crashed. Just walk through our
431 // normal exit full screen sequence, but don't wait to be called back
434 [self exitFullScreen];
435 [self beganExitFullScreenAnimation];
438 if (_isExitingFullScreen)
439 [self finishedExitFullScreenAnimation:YES];
445 #pragma mark Internal Interface
447 - (void)_updateMenuAndDockForFullScreen
449 // NSApplicationPresentationOptions is available on > 10.6 only:
450 #ifndef BUILDING_ON_LEOPARD
451 NSApplicationPresentationOptions options = NSApplicationPresentationDefault;
452 NSScreen* fullScreenScreen = [[self window] screen];
455 // Auto-hide the menu bar if the fullScreenScreen contains the menu bar:
456 // NOTE: if the fullScreenScreen contains the menu bar but not the dock, we must still
457 // auto-hide the dock, or an exception will be thrown.
458 if ([[NSScreen screens] objectAtIndex:0] == fullScreenScreen)
459 options |= (NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationAutoHideDock);
460 // Check if the current screen contains the dock by comparing the screen's frame to its
461 // visibleFrame; if a dock is present, the visibleFrame will differ. If the current screen
462 // contains the dock, hide it.
463 else if (!NSEqualRects([fullScreenScreen frame], [fullScreenScreen visibleFrame]))
464 options |= NSApplicationPresentationAutoHideDock;
467 if ([NSApp respondsToSelector:@selector(setPresentationOptions:)])
468 [NSApp setPresentationOptions:options];
471 SetSystemUIMode(_isFullScreen ? kUIModeNormal : kUIModeAllHidden, 0);
474 - (void)_disableIdleDisplaySleep
476 if (_idleDisplaySleepAssertion == kIOPMNullAssertionID)
477 #if defined(BUILDING_ON_LEOPARD) // IOPMAssertionCreateWithName is not defined in the 10.5 SDK
478 IOPMAssertionCreate(kIOPMAssertionTypeNoDisplaySleep, kIOPMAssertionLevelOn, &_idleDisplaySleepAssertion);
479 #else // IOPMAssertionCreate is depreciated in > 10.5
480 IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep, kIOPMAssertionLevelOn, CFSTR("WebKit playing a video fullScreen."), &_idleDisplaySleepAssertion);
484 - (void)_enableIdleDisplaySleep
486 if (_idleDisplaySleepAssertion != kIOPMNullAssertionID) {
487 IOPMAssertionRelease(_idleDisplaySleepAssertion);
488 _idleDisplaySleepAssertion = kIOPMNullAssertionID;
492 - (void)_disableIdleSystemSleep
494 if (_idleSystemSleepAssertion == kIOPMNullAssertionID)
495 #if defined(BUILDING_ON_LEOPARD) // IOPMAssertionCreateWithName is not defined in the 10.5 SDK
496 IOPMAssertionCreate(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, &_idleSystemSleepAssertion);
497 #else // IOPMAssertionCreate is depreciated in > 10.5
498 IOPMAssertionCreateWithName(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, CFSTR("WebKit playing a video fullScreen."), &_idleSystemSleepAssertion);
502 - (void)_enableIdleSystemSleep
504 if (_idleSystemSleepAssertion != kIOPMNullAssertionID) {
505 IOPMAssertionRelease(_idleSystemSleepAssertion);
506 _idleSystemSleepAssertion = kIOPMNullAssertionID;
510 - (void)_enableTickleTimer
512 [_tickleTimer invalidate];
513 [_tickleTimer release];
514 _tickleTimer = [[NSTimer scheduledTimerWithTimeInterval:tickleTimerInterval target:self selector:@selector(_tickleTimerFired) userInfo:nil repeats:YES] retain];
517 - (void)_disableTickleTimer
519 [_tickleTimer invalidate];
520 [_tickleTimer release];
524 - (void)_tickleTimerFired
526 UpdateSystemActivity(OverallAct);
529 - (void)_updatePowerAssertions
531 if (_isPlaying && _isFullScreen) {
532 [self _disableIdleSystemSleep];
533 [self _disableIdleDisplaySleep];
534 [self _enableTickleTimer];
536 [self _enableIdleSystemSleep];
537 [self _enableIdleDisplaySleep];
538 [self _disableTickleTimer];
542 - (WebPageProxy*)_page
544 return toImpl([_webView pageRef]);
547 - (WebFullScreenManagerProxy*)_manager
549 WebPageProxy* webPage = [self _page];
552 return webPage->fullScreenManager();
557 [self exitFullScreen];
558 _forceDisableAnimation = NO;
561 - (void)_requestExitFullScreenWithAnimation:(BOOL)animation
563 _forceDisableAnimation = !animation;
564 [self performSelector:@selector(_requestExit) withObject:nil afterDelay:0];
568 - (void)_swapView:(NSView*)view with:(NSView*)otherView
570 [CATransaction begin];
571 [CATransaction setDisableActions:YES];
572 [otherView setFrame:[view frame]];
573 [otherView setAutoresizingMask:[view autoresizingMask]];
574 [otherView removeFromSuperview];
575 [[view superview] replaceSubview:view with:otherView];
576 [CATransaction commit];
580 #pragma mark Utility Functions
582 - (WKFullScreenWindow *)_fullScreenWindow
584 ASSERT([[self window] isKindOfClass:[WKFullScreenWindow class]]);
585 return (WKFullScreenWindow *)[self window];
588 - (CFTimeInterval)_animationDuration
590 static const CFTimeInterval defaultDuration = 0.5;
591 CFTimeInterval duration = defaultDuration;
592 #ifndef BUILDING_ON_LEOPARD
593 NSUInteger modifierFlags = [NSEvent modifierFlags];
595 NSUInteger modifierFlags = [[NSApp currentEvent] modifierFlags];
597 if ((modifierFlags & NSControlKeyMask) == NSControlKeyMask)
599 if ((modifierFlags & NSShiftKeyMask) == NSShiftKeyMask)
601 if (_forceDisableAnimation) {
602 // This will disable scale animation
611 @implementation WKFullScreenWindow
613 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag
615 UNUSED_PARAM(aStyle);
616 self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:bufferingType defer:flag];
620 [self setBackgroundColor:[NSColor clearColor]];
621 [self setIgnoresMouseEvents:NO];
622 [self setAcceptsMouseMovedEvents:YES];
623 [self setReleasedWhenClosed:NO];
624 [self setHasShadow:YES];
625 #ifndef BUILDING_ON_LEOPARD
626 [self setMovable:NO];
628 [self setMovableByWindowBackground:NO];
631 NSView* contentView = [self contentView];
632 [contentView setWantsLayer:YES];
633 _animationView = [[NSView alloc] initWithFrame:[contentView bounds]];
635 CALayer* contentLayer = [[CALayer alloc] init];
636 [_animationView setLayer:contentLayer];
637 [_animationView setWantsLayer:YES];
638 [_animationView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
639 [contentView addSubview:_animationView];
641 _backgroundLayer = [[CALayer alloc] init];
642 [contentLayer addSublayer:_backgroundLayer];
644 [_backgroundLayer setBackgroundColor:CGColorGetConstantColor(kCGColorBlack)];
645 [_backgroundLayer setOpacity:0];
651 [_animationView release];
652 [_backgroundLayer release];
656 - (BOOL)canBecomeKeyWindow
661 - (void)keyDown:(NSEvent *)theEvent
663 if ([[theEvent charactersIgnoringModifiers] isEqual:@"\e"]) // Esacpe key-code
664 [self cancelOperation:self];
665 else [super keyDown:theEvent];
668 - (void)cancelOperation:(id)sender
670 UNUSED_PARAM(sender);
671 [[self windowController] _requestExitFullScreenWithAnimation:YES];
674 - (CALayer*)backgroundLayer
676 return _backgroundLayer;
679 - (NSView*)animationView
681 return _animationView;