ea50e32077f2d7dd86da48aa70434b036e4eb11d
[WebKit-https.git] / Source / WebKit / UIProcess / mac / WKFullScreenWindowController.mm
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) && !PLATFORM(IOS)
29
30 #import "WKFullScreenWindowController.h"
31
32 #import "LayerTreeContext.h"
33 #import "WKAPICast.h"
34 #import "WKViewInternal.h"
35 #import "WKViewPrivate.h"
36 #import "WebFullScreenManagerProxy.h"
37 #import "WebPageProxy.h"
38 #import <QuartzCore/QuartzCore.h>
39 #import <WebCore/FloatRect.h>
40 #import <WebCore/GeometryUtilities.h>
41 #import <WebCore/IntRect.h>
42 #import <WebCore/LocalizedStrings.h>
43 #import <WebCore/WebCoreFullScreenPlaceholderView.h>
44 #import <WebCore/WebCoreFullScreenWindow.h>
45 #import <pal/system/SleepDisabler.h>
46 #import <wtf/BlockObjCExceptions.h>
47
48 using namespace WebKit;
49 using namespace WebCore;
50
51 static const NSTimeInterval DefaultWatchdogTimerInterval = 1;
52
53 enum FullScreenState : NSInteger {
54     NotInFullScreen,
55     WaitingToEnterFullScreen,
56     EnteringFullScreen,
57     InFullScreen,
58     WaitingToExitFullScreen,
59     ExitingFullScreen,
60 };
61
62 @interface NSWindow (WebNSWindowDetails)
63 - (void)exitFullScreenMode:(id)sender;
64 - (void)enterFullScreenMode:(id)sender;
65 @end
66
67 @interface WKFullScreenWindowController (Private) <NSAnimationDelegate>
68 - (void)_replaceView:(NSView*)view with:(NSView*)otherView;
69 - (WebFullScreenManagerProxy*)_manager;
70 - (void)_startEnterFullScreenAnimationWithDuration:(NSTimeInterval)duration;
71 - (void)_startExitFullScreenAnimationWithDuration:(NSTimeInterval)duration;
72 @end
73
74 static NSRect convertRectToScreen(NSWindow *window, NSRect rect)
75 {
76     return [window convertRectToScreen:rect];
77 }
78
79 static void makeResponderFirstResponderIfDescendantOfView(NSWindow *window, NSResponder *responder, NSView *view)
80 {
81     if ([responder isKindOfClass:[NSView class]] && [(NSView *)responder isDescendantOf:view])
82         [window makeFirstResponder:responder];
83 }
84
85 @implementation WKFullScreenWindowController
86
87 #pragma mark -
88 #pragma mark Initialization
89 - (id)initWithWindow:(NSWindow *)window webView:(NSView *)webView page:(WebPageProxy&)page
90 {
91     self = [super initWithWindow:window];
92     if (!self)
93         return nil;
94     [window setDelegate:self];
95     [window setCollectionBehavior:([window collectionBehavior] | NSWindowCollectionBehaviorFullScreenPrimary)];
96
97     NSView *contentView = [window contentView];
98     contentView.hidden = YES;
99     contentView.autoresizesSubviews = YES;
100
101     _backgroundView = adoptNS([[NSView alloc] initWithFrame:contentView.bounds]);
102     _backgroundView.get().layer = [CALayer layer];
103     _backgroundView.get().wantsLayer = YES;
104     _backgroundView.get().autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
105     [contentView addSubview:_backgroundView.get()];
106
107     _clipView = adoptNS([[NSView alloc] initWithFrame:contentView.bounds]);
108     [_clipView setWantsLayer:YES];
109     [_clipView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
110     [_backgroundView addSubview:_clipView.get()];
111
112     [self windowDidLoad];
113     [window displayIfNeeded];
114     _webView = webView;
115     _page = &page;
116     
117     return self;
118 }
119
120 - (void)dealloc
121 {
122     [[self window] setDelegate:nil];
123     
124     [NSObject cancelPreviousPerformRequestsWithTarget:self];
125     
126     [[NSNotificationCenter defaultCenter] removeObserver:self];
127
128     if (_repaintCallback) {
129         _repaintCallback->invalidate(WebKit::CallbackBase::Error::OwnerWasInvalidated);
130         // invalidate() calls completeFinishExitFullScreenAnimationAfterRepaint, which
131         // clears _repaintCallback.
132         ASSERT(!_repaintCallback);
133     }
134
135     [super dealloc];
136 }
137
138 #pragma mark -
139 #pragma mark Accessors
140
141 @synthesize initialFrame=_initialFrame;
142 @synthesize finalFrame=_finalFrame;
143
144 - (BOOL)isFullScreen
145 {
146     return _fullScreenState == WaitingToEnterFullScreen
147         || _fullScreenState == EnteringFullScreen
148         || _fullScreenState == InFullScreen;
149 }
150
151 - (WebCoreFullScreenPlaceholderView*)webViewPlaceholder
152 {
153     return _webViewPlaceholder.get();
154 }
155
156 - (void)setSavedConstraints:(NSArray *)savedConstraints
157 {
158     _savedConstraints = savedConstraints;
159 }
160
161 - (NSArray *)savedConstraints
162 {
163     return _savedConstraints.get();
164 }
165
166 #pragma mark -
167 #pragma mark NSWindowController overrides
168
169 - (void)cancelOperation:(id)sender
170 {
171     // If the page doesn't respond in DefaultWatchdogTimerInterval seconds, it could be because
172     // the WebProcess has hung, so exit anyway.
173     if (!_watchdogTimer) {
174         [self _manager]->requestExitFullScreen();
175         _watchdogTimer = adoptNS([[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:DefaultWatchdogTimerInterval] interval:0 target:self selector:@selector(_watchdogTimerFired:) userInfo:nil repeats:NO]);
176         [[NSRunLoop mainRunLoop] addTimer:_watchdogTimer.get() forMode:NSDefaultRunLoopMode];
177     }
178 }
179
180 #pragma mark -
181 #pragma mark Exposed Interface
182
183 static RetainPtr<CGDataProviderRef> createImageProviderWithCopiedData(CGDataProviderRef sourceProvider)
184 {
185     RetainPtr<CFDataRef> data = adoptCF(CGDataProviderCopyData(sourceProvider));
186     return adoptCF(CGDataProviderCreateWithCFData(data.get()));
187 }
188
189 static RetainPtr<CGImageRef> createImageWithCopiedData(CGImageRef sourceImage)
190 {
191     size_t width = CGImageGetWidth(sourceImage);
192     size_t height = CGImageGetHeight(sourceImage);
193     size_t bitsPerComponent = CGImageGetBitsPerComponent(sourceImage);
194     size_t bitsPerPixel = CGImageGetBitsPerPixel(sourceImage);
195     size_t bytesPerRow = CGImageGetBytesPerRow(sourceImage);
196     CGColorSpaceRef colorSpace = CGImageGetColorSpace(sourceImage);
197     CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(sourceImage);
198     RetainPtr<CGDataProviderRef> provider = createImageProviderWithCopiedData(CGImageGetDataProvider(sourceImage));
199     bool shouldInterpolate = CGImageGetShouldInterpolate(sourceImage);
200     CGColorRenderingIntent intent = CGImageGetRenderingIntent(sourceImage);
201
202     return adoptCF(CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpace, bitmapInfo, provider.get(), 0, shouldInterpolate, intent));
203 }
204
205 - (void)enterFullScreen:(NSScreen *)screen
206 {
207     if ([self isFullScreen])
208         return;
209     _fullScreenState = WaitingToEnterFullScreen;
210
211     if (!screen)
212         screen = [NSScreen mainScreen];
213     NSRect screenFrame = [screen frame];
214
215     NSRect webViewFrame = convertRectToScreen([_webView window], [_webView convertRect:[_webView frame] toView:nil]);
216
217     // Flip coordinate system:
218     webViewFrame.origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(webViewFrame);
219
220     CGWindowID windowID = [[_webView window] windowNumber];
221     RetainPtr<CGImageRef> webViewContents = adoptCF(CGWindowListCreateImage(NSRectToCGRect(webViewFrame), kCGWindowListOptionIncludingWindow, windowID, kCGWindowImageShouldBeOpaque));
222
223     // Using the returned CGImage directly would result in calls to the WindowServer every time
224     // the image was painted. Instead, copy the image data into our own process to eliminate that
225     // future overhead.
226     webViewContents = createImageWithCopiedData(webViewContents.get());
227
228 #pragma clang diagnostic push
229 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
230     // Screen updates to be re-enabled in _startEnterFullScreenAnimationWithDuration:
231     NSDisableScreenUpdates();
232     [[self window] setAutodisplay:NO];
233 #pragma clang diagnostic pop
234
235     [self _manager]->saveScrollPosition();
236     _savedTopContentInset = _page->topContentInset();
237     _page->setTopContentInset(0);
238     [[self window] setFrame:screenFrame display:NO];
239
240     // Painting is normally suspended when the WKView is removed from the window, but this is
241     // unnecessary in the full-screen animation case, and can cause bugs; see
242     // https://bugs.webkit.org/show_bug.cgi?id=88940 and https://bugs.webkit.org/show_bug.cgi?id=88374
243     // We will resume the normal behavior in _startEnterFullScreenAnimationWithDuration:
244     _page->setSuppressVisibilityUpdates(true);
245
246     // Swap the webView placeholder into place.
247     if (!_webViewPlaceholder) {
248         _webViewPlaceholder = adoptNS([[WebCoreFullScreenPlaceholderView alloc] initWithFrame:[_webView frame]]);
249         [_webViewPlaceholder setAction:@selector(cancelOperation:)];
250     }
251     [_webViewPlaceholder setTarget:nil];
252     [_webViewPlaceholder setContents:(__bridge id)webViewContents.get()];
253     self.savedConstraints = _webView.superview.constraints;
254     [self _replaceView:_webView with:_webViewPlaceholder.get()];
255     
256     // Then insert the WebView into the full screen window
257     NSView *contentView = [[self window] contentView];
258     [_clipView addSubview:_webView positioned:NSWindowBelow relativeTo:nil];
259     _webView.frame = NSInsetRect(contentView.bounds, 0, -_page->topContentInset());
260
261     _savedScale = _page->pageScaleFactor();
262     _page->scalePage(1, IntPoint());
263     [self _manager]->setAnimatingFullScreen(true);
264     [self _manager]->willEnterFullScreen();
265 }
266
267 - (void)beganEnterFullScreenWithInitialFrame:(NSRect)initialFrame finalFrame:(NSRect)finalFrame
268 {
269     if (_fullScreenState != WaitingToEnterFullScreen)
270         return;
271     _fullScreenState = EnteringFullScreen;
272
273     _initialFrame = initialFrame;
274     _finalFrame = finalFrame;
275
276     [self.window orderBack: self]; // Make sure the full screen window is part of the correct Space.
277     [[self window] enterFullScreenMode:self];
278 }
279
280 static const float minVideoWidth = 480 + 20 + 20; // Note: Keep in sync with mediaControlsApple.css (video:-webkit-full-screen::-webkit-media-controls-panel)
281
282 - (void)finishedEnterFullScreenAnimation:(bool)completed
283 {
284     if (_fullScreenState != EnteringFullScreen)
285         return;
286     
287     if (completed) {
288         _fullScreenState = InFullScreen;
289
290 #pragma clang diagnostic push
291 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
292         // Screen updates to be re-enabled ta the end of the current block.
293         NSDisableScreenUpdates();
294 #pragma clang diagnostic pop
295         [self _manager]->didEnterFullScreen();
296         [self _manager]->setAnimatingFullScreen(false);
297
298         [_backgroundView.get().layer removeAllAnimations];
299         [[_clipView layer] removeAllAnimations];
300         [[_clipView layer] setMask:nil];
301
302         [_webViewPlaceholder setExitWarningVisible:YES];
303         [_webViewPlaceholder setTarget:self];
304
305         NSSize minContentSize = self.window.contentMinSize;
306         minContentSize.width = minVideoWidth;
307         self.window.contentMinSize = minContentSize;
308     } else {
309         // Transition to fullscreen failed. Clean up.
310         _fullScreenState = NotInFullScreen;
311
312 #pragma clang diagnostic push
313 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
314         [[self window] setAutodisplay:YES];
315 #pragma clang diagnostic pop
316         _page->setSuppressVisibilityUpdates(false);
317
318         NSResponder *firstResponder = [[self window] firstResponder];
319         [self _replaceView:_webViewPlaceholder.get() with:_webView];
320         BEGIN_BLOCK_OBJC_EXCEPTIONS
321         [NSLayoutConstraint activateConstraints:self.savedConstraints];
322         END_BLOCK_OBJC_EXCEPTIONS
323         self.savedConstraints = nil;
324         makeResponderFirstResponderIfDescendantOfView(_webView.window, firstResponder, _webView);
325         [[_webView window] makeKeyAndOrderFront:self];
326
327         _page->scalePage(_savedScale, IntPoint());
328         [self _manager]->restoreScrollPosition();
329         _page->setTopContentInset(_savedTopContentInset);
330         [self _manager]->didExitFullScreen();
331         [self _manager]->setAnimatingFullScreen(false);
332     }
333
334 #pragma clang diagnostic push
335 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
336     NSEnableScreenUpdates();
337 #pragma clang diagnostic pop
338
339     if (_requestedExitFullScreen) {
340         _requestedExitFullScreen = NO;
341         [self exitFullScreen];
342     }
343 }
344
345 - (void)exitFullScreen
346 {
347     if (_fullScreenState == EnteringFullScreen
348         || _fullScreenState == WaitingToEnterFullScreen) {
349         // Do not try to exit fullscreen during the enter animation; remember
350         // that exit was requested and perform the exit upon enter fullscreen
351         // animation complete.
352         _requestedExitFullScreen = YES;
353         return;
354     }
355
356     if (_watchdogTimer) {
357         [_watchdogTimer invalidate];
358         _watchdogTimer.clear();
359     }
360
361     if (![self isFullScreen])
362         return;
363     _fullScreenState = WaitingToExitFullScreen;
364
365     [_webViewPlaceholder setExitWarningVisible:NO];
366
367 #pragma clang diagnostic push
368 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
369     // Screen updates to be re-enabled in _startExitFullScreenAnimationWithDuration: or beganExitFullScreenWithInitialFrame:finalFrame:
370     NSDisableScreenUpdates();
371     [[self window] setAutodisplay:NO];
372 #pragma clang diagnostic pop
373
374     // See the related comment in enterFullScreen:
375     // We will resume the normal behavior in _startExitFullScreenAnimationWithDuration:
376     _page->setSuppressVisibilityUpdates(true);
377     [_webViewPlaceholder setTarget:nil];
378
379     [self _manager]->setAnimatingFullScreen(true);
380     [self _manager]->willExitFullScreen();
381 }
382
383 - (void)requestExitFullScreen
384 {
385     [self _manager]->requestExitFullScreen();
386 }
387
388 - (void)beganExitFullScreenWithInitialFrame:(NSRect)initialFrame finalFrame:(NSRect)finalFrame
389 {
390     if (_fullScreenState != WaitingToExitFullScreen)
391         return;
392     _fullScreenState = ExitingFullScreen;
393
394     if (![[self window] isOnActiveSpace]) {
395         // If the full screen window is not in the active space, the NSWindow full screen animation delegate methods
396         // will never be called. So call finishedExitFullScreenAnimation explicitly.
397         [self finishedExitFullScreenAnimation:YES];
398
399         // Because we are breaking the normal animation pattern, re-enable screen updates
400         // as exitFullScreen has disabled them, but _startExitFullScreenAnimationWithDuration:
401         // will never be called.
402 #pragma clang diagnostic push
403 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
404         NSEnableScreenUpdates();
405 #pragma clang diagnostic pop
406     }
407
408     [[self window] exitFullScreenMode:self];
409 }
410
411 - (void)finishedExitFullScreenAnimation:(bool)completed
412 {
413     if (_fullScreenState == InFullScreen) {
414         // If we are currently in the InFullScreen state, this notification is unexpected, meaning
415         // fullscreen was exited without being initiated by WebKit. Do not return early, but continue to
416         // clean up our state by calling those methods which would have been called by -exitFullscreen,
417         // and proceed to close the fullscreen window.
418         [self _manager]->requestExitFullScreen();
419         [_webViewPlaceholder setTarget:nil];
420         [self _manager]->setAnimatingFullScreen(false);
421         [self _manager]->willExitFullScreen();
422     } else if (_fullScreenState != ExitingFullScreen)
423         return;
424     _fullScreenState = NotInFullScreen;
425
426     NSResponder *firstResponder = [[self window] firstResponder];
427
428 #pragma clang diagnostic push
429 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
430     // Screen updates to be re-enabled in completeFinishExitFullScreenAnimationAfterRepaint.
431     NSDisableScreenUpdates();
432 #pragma clang diagnostic pop
433     _page->setSuppressVisibilityUpdates(true);
434     [[self window] orderOut:self];
435     NSView *contentView = [[self window] contentView];
436     contentView.hidden = YES;
437     [_backgroundView.get().layer removeAllAnimations];
438 #pragma clang diagnostic push
439 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
440     [[_webViewPlaceholder window] setAutodisplay:NO];
441 #pragma clang diagnostic pop
442
443     [self _replaceView:_webViewPlaceholder.get() with:_webView];
444     BEGIN_BLOCK_OBJC_EXCEPTIONS
445     [NSLayoutConstraint activateConstraints:self.savedConstraints];
446     END_BLOCK_OBJC_EXCEPTIONS
447     self.savedConstraints = nil;
448     makeResponderFirstResponderIfDescendantOfView(_webView.window, firstResponder, _webView);
449
450     [[_webView window] makeKeyAndOrderFront:self];
451
452     // These messages must be sent after the swap or flashing will occur during forceRepaint:
453     [self _manager]->didExitFullScreen();
454     [self _manager]->setAnimatingFullScreen(false);
455     _page->scalePage(_savedScale, IntPoint());
456     [self _manager]->restoreScrollPosition();
457     _page->setTopContentInset(_savedTopContentInset);
458
459     if (_repaintCallback) {
460         _repaintCallback->invalidate(WebKit::CallbackBase::Error::OwnerWasInvalidated);
461         // invalidate() calls completeFinishExitFullScreenAnimationAfterRepaint, which
462         // clears _repaintCallback.
463         ASSERT(!_repaintCallback);
464     }
465     _repaintCallback = VoidCallback::create([self](WebKit::CallbackBase::Error) {
466         [self completeFinishExitFullScreenAnimationAfterRepaint];
467     });
468     _page->forceRepaint(_repaintCallback.copyRef());
469 }
470
471 - (void)completeFinishExitFullScreenAnimationAfterRepaint
472 {
473     _repaintCallback = nullptr;
474 #pragma clang diagnostic push
475 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
476     [[_webView window] setAutodisplay:YES];
477 #pragma clang diagnostic pop
478     [[_webView window] displayIfNeeded];
479     _page->setSuppressVisibilityUpdates(false);
480 #pragma clang diagnostic push
481 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
482     NSEnableScreenUpdates();
483 #pragma clang diagnostic pop
484 }
485
486 - (void)performClose:(id)sender
487 {
488     if ([self isFullScreen])
489         [self cancelOperation:sender];
490 }
491
492 - (void)close
493 {
494     // We are being asked to close rapidly, most likely because the page 
495     // has closed or the web process has crashed.  Just walk through our
496     // normal exit full screen sequence, but don't wait to be called back
497     // in response.
498     if ([self isFullScreen])
499         [self exitFullScreen];
500     
501     if (_fullScreenState == ExitingFullScreen)
502         [self finishedExitFullScreenAnimation:YES];
503
504     _webView = nil;
505
506     [super close];
507 }
508
509 #pragma mark -
510 #pragma mark Custom NSWindow Full Screen Animation
511
512 - (NSArray *)customWindowsToEnterFullScreenForWindow:(NSWindow *)window
513 {
514     return @[self.window];
515 }
516
517 - (NSArray *)customWindowsToExitFullScreenForWindow:(NSWindow *)window
518 {
519     return @[self.window];
520 }
521
522 - (void)window:(NSWindow *)window startCustomAnimationToEnterFullScreenWithDuration:(NSTimeInterval)duration
523 {
524     [self _startEnterFullScreenAnimationWithDuration:duration];
525 }
526
527 - (void)window:(NSWindow *)window startCustomAnimationToExitFullScreenWithDuration:(NSTimeInterval)duration
528 {
529     [self _startExitFullScreenAnimationWithDuration:duration];
530 }
531
532 - (void)windowDidFailToEnterFullScreen:(NSWindow *)window
533 {
534     [self finishedEnterFullScreenAnimation:NO];
535 }
536
537 - (void)windowDidEnterFullScreen:(NSNotification*)notification
538 {
539     [self finishedEnterFullScreenAnimation:YES];
540 }
541
542 - (void)windowDidFailToExitFullScreen:(NSWindow *)window
543 {
544     [self finishedExitFullScreenAnimation:NO];
545 }
546
547 - (void)windowDidExitFullScreen:(NSNotification*)notification
548 {
549     [self finishedExitFullScreenAnimation:YES];
550 }
551
552 - (NSWindow *)destinationWindowToExitFullScreenForWindow:(NSWindow *)window
553 {
554     return self.webViewPlaceholder.window;
555 }
556
557 #pragma mark -
558 #pragma mark Internal Interface
559
560 - (WebFullScreenManagerProxy*)_manager
561 {
562     if (!_page)
563         return nullptr;
564     return _page->fullScreenManager();
565 }
566
567 - (void)_replaceView:(NSView*)view with:(NSView*)otherView
568 {
569     [CATransaction begin];
570     [CATransaction setDisableActions:YES];
571     [otherView setFrame:[view frame]];        
572     [otherView setAutoresizingMask:[view autoresizingMask]];
573     [otherView removeFromSuperview];
574     [[view superview] addSubview:otherView positioned:NSWindowAbove relativeTo:view];
575     [view removeFromSuperview];
576     [CATransaction commit];
577 }
578
579 static CAMediaTimingFunction *timingFunctionForDuration(CFTimeInterval duration)
580 {
581     if (duration >= 0.8)
582         return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
583     return [CAMediaTimingFunction functionWithControlPoints:.25 :0 :0 :1];
584 }
585
586 enum AnimationDirection { AnimateIn, AnimateOut };
587 static CAAnimation *zoomAnimation(const FloatRect& initialFrame, const FloatRect& finalFrame, const FloatRect& screenFrame, CFTimeInterval duration, AnimationDirection direction)
588 {
589     CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
590     FloatRect scaleRect = smallestRectWithAspectRatioAroundRect(finalFrame.size().aspectRatio(), initialFrame);
591     CGAffineTransform resetOriginTransform = CGAffineTransformMakeTranslation(screenFrame.x() - finalFrame.x(), screenFrame.y() - finalFrame.y());
592     CGAffineTransform scaleTransform = CGAffineTransformMakeScale(scaleRect.width() / finalFrame.width(), scaleRect.height() / finalFrame.height());
593     CGAffineTransform translateTransform = CGAffineTransformMakeTranslation(scaleRect.x() - screenFrame.x(), scaleRect.y() - screenFrame.y());
594
595     CGAffineTransform finalTransform = CGAffineTransformConcat(CGAffineTransformConcat(resetOriginTransform, scaleTransform), translateTransform);
596     NSValue *scaleValue = [NSValue valueWithCATransform3D:CATransform3DMakeAffineTransform(finalTransform)];
597     if (direction == AnimateIn)
598         scaleAnimation.fromValue = scaleValue;
599     else
600         scaleAnimation.toValue = scaleValue;
601
602     scaleAnimation.duration = duration;
603     scaleAnimation.removedOnCompletion = NO;
604     scaleAnimation.fillMode = kCAFillModeBoth;
605     scaleAnimation.timingFunction = timingFunctionForDuration(duration);
606     return scaleAnimation;
607 }
608
609 static CALayer *createMask(const FloatRect& bounds)
610 {
611     CALayer *maskLayer = [CALayer layer];
612     maskLayer.anchorPoint = CGPointZero;
613     maskLayer.frame = bounds;
614     maskLayer.backgroundColor = CGColorGetConstantColor(kCGColorBlack);
615     maskLayer.autoresizingMask = (NSViewWidthSizable | NSViewHeightSizable);
616     return maskLayer;
617 }
618
619 static CAAnimation *maskAnimation(const FloatRect& initialFrame, const FloatRect& finalFrame, const FloatRect& screenFrame, CFTimeInterval duration, AnimationDirection direction)
620 {
621     CABasicAnimation *boundsAnimation = [CABasicAnimation animationWithKeyPath:@"bounds"];
622     FloatRect boundsRect = largestRectWithAspectRatioInsideRect(initialFrame.size().aspectRatio(), finalFrame);
623     NSValue *boundsValue = [NSValue valueWithRect:FloatRect(FloatPoint(), boundsRect.size())];
624     if (direction == AnimateIn)
625         boundsAnimation.fromValue = boundsValue;
626     else
627         boundsAnimation.toValue = boundsValue;
628
629     CABasicAnimation *positionAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
630     NSValue *positionValue = [NSValue valueWithPoint:FloatPoint(boundsRect.location() - screenFrame.location())];
631     if (direction == AnimateIn)
632         positionAnimation.fromValue = positionValue;
633     else
634         positionAnimation.toValue = positionValue;
635
636     CAAnimationGroup *animation = [CAAnimationGroup animation];
637     animation.animations = @[boundsAnimation, positionAnimation];
638     animation.duration = duration;
639     animation.removedOnCompletion = NO;
640     animation.fillMode = kCAFillModeBoth;
641     animation.timingFunction = timingFunctionForDuration(duration);
642     return animation;
643 }
644
645 static CAAnimation *fadeAnimation(CFTimeInterval duration, AnimationDirection direction)
646 {
647     CABasicAnimation *fadeAnimation = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
648     if (direction == AnimateIn)
649         fadeAnimation.toValue = (id)CGColorGetConstantColor(kCGColorBlack);
650     else
651         fadeAnimation.fromValue = (id)CGColorGetConstantColor(kCGColorBlack);
652     fadeAnimation.duration = duration;
653     fadeAnimation.removedOnCompletion = NO;
654     fadeAnimation.fillMode = kCAFillModeBoth;
655     fadeAnimation.timingFunction = timingFunctionForDuration(duration);
656     return fadeAnimation;
657 }
658
659 - (void)_startEnterFullScreenAnimationWithDuration:(NSTimeInterval)duration
660 {
661     NSView* contentView = [[self window] contentView];
662
663     [[_clipView layer] addAnimation:zoomAnimation(_initialFrame, _finalFrame, self.window.screen.frame, duration, AnimateIn) forKey:@"fullscreen"];
664     CALayer *maskLayer = createMask(contentView.bounds);
665     [maskLayer addAnimation:maskAnimation(_initialFrame, _finalFrame, self.window.screen.frame, duration, AnimateIn) forKey:@"fullscreen"];
666     [_clipView layer].mask = maskLayer;
667
668     contentView.hidden = NO;
669     [_backgroundView.get().layer addAnimation:fadeAnimation(duration, AnimateIn) forKey:@"fullscreen"];
670
671     NSWindow* window = [self window];
672     NSWindowCollectionBehavior behavior = [window collectionBehavior];
673     [window setCollectionBehavior:(behavior | NSWindowCollectionBehaviorCanJoinAllSpaces)];
674     [window makeKeyAndOrderFront:self];
675     [window setCollectionBehavior:behavior];
676     [window makeFirstResponder:_webView];
677
678     _page->setSuppressVisibilityUpdates(false);
679 #pragma clang diagnostic push
680 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
681     [[self window] setAutodisplay:YES];
682 #pragma clang diagnostic pop
683     [[self window] displayIfNeeded];
684 #pragma clang diagnostic push
685 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
686     NSEnableScreenUpdates();
687 #pragma clang diagnostic pop
688 }
689
690 - (void)_startExitFullScreenAnimationWithDuration:(NSTimeInterval)duration
691 {
692     if ([self isFullScreen]) {
693         // We still believe we're in full screen mode, so we must have been asked to exit full
694         // screen by the system full screen button.
695         [self _manager]->requestExitFullScreen();
696         [self exitFullScreen];
697         _fullScreenState = ExitingFullScreen;
698     }
699
700     [[_clipView layer] addAnimation:zoomAnimation(_initialFrame, _finalFrame, self.window.screen.frame, duration, AnimateOut) forKey:@"fullscreen"];
701     NSView* contentView = [[self window] contentView];
702     CALayer *maskLayer = createMask(contentView.bounds);
703     [maskLayer addAnimation:maskAnimation(_initialFrame, _finalFrame, self.window.screen.frame, duration, AnimateOut) forKey:@"fullscreen"];
704     [_clipView layer].mask = maskLayer;
705
706     contentView.hidden = NO;
707     [_backgroundView.get().layer addAnimation:fadeAnimation(duration, AnimateOut) forKey:@"fullscreen"];
708
709     _page->setSuppressVisibilityUpdates(false);
710 #pragma clang diagnostic push
711 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
712     [[self window] setAutodisplay:YES];
713 #pragma clang diagnostic pop
714     [[self window] displayIfNeeded];
715 #pragma clang diagnostic push
716 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
717     NSEnableScreenUpdates();
718 #pragma clang diagnostic pop
719 }
720
721 - (void)_watchdogTimerFired:(NSTimer *)timer
722 {
723     [self exitFullScreen];
724 }
725
726 @end
727
728 #endif // ENABLE(FULLSCREEN_API) && !PLATFORM(IOS)