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