Occasional crashes in commitTransientZoom's transaction completion block
[WebKit-https.git] / Source / WebKit2 / WebProcess / WebPage / mac / TiledCoreAnimationDrawingArea.mm
1 /*
2  * Copyright (C) 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 #import "TiledCoreAnimationDrawingArea.h"
28
29 #if !PLATFORM(IOS)
30
31 #import "ColorSpaceData.h"
32 #import "DrawingAreaProxyMessages.h"
33 #import "LayerHostingContext.h"
34 #import "LayerTreeContext.h"
35 #import "ViewGestureControllerMessages.h"
36 #import "WebFrame.h"
37 #import "WebPage.h"
38 #import "WebPageCreationParameters.h"
39 #import "WebPageProxyMessages.h"
40 #import "WebProcess.h"
41 #import <QuartzCore/QuartzCore.h>
42 #import <WebCore/FrameView.h>
43 #import <WebCore/GraphicsContext.h>
44 #import <WebCore/GraphicsLayerCA.h>
45 #import <WebCore/MainFrame.h>
46 #import <WebCore/Page.h>
47 #import <WebCore/PlatformCAAnimationMac.h>
48 #import <WebCore/RenderLayerBacking.h>
49 #import <WebCore/RenderLayerCompositor.h>
50 #import <WebCore/RenderView.h>
51 #import <WebCore/ScrollbarTheme.h>
52 #import <WebCore/Settings.h>
53 #import <WebCore/TiledBacking.h>
54 #import <wtf/MainThread.h>
55
56 #if ENABLE(ASYNC_SCROLLING)
57 #import <WebCore/AsyncScrollingCoordinator.h>
58 #import <WebCore/ScrollingThread.h>
59 #import <WebCore/ScrollingTree.h>
60 #endif
61
62 @interface CATransaction (Details)
63 + (void)synchronize;
64 @end
65
66 using namespace WebCore;
67
68 namespace WebKit {
69
70 TiledCoreAnimationDrawingArea::TiledCoreAnimationDrawingArea(WebPage& webPage, const WebPageCreationParameters& parameters)
71     : DrawingArea(DrawingAreaTypeTiledCoreAnimation, webPage)
72     , m_layerTreeStateIsFrozen(false)
73     , m_layerFlushScheduler(this)
74     , m_isPaintingSuspended(!(parameters.viewState & ViewState::IsVisible))
75     , m_exposedRect(FloatRect::infiniteRect())
76     , m_scrolledExposedRect(FloatRect::infiniteRect())
77     , m_transientZoomScale(1)
78     , m_sendDidUpdateViewStateTimer(RunLoop::main(), this, &TiledCoreAnimationDrawingArea::didUpdateViewStateTimerFired)
79 {
80     m_webPage.corePage()->settings().setForceCompositingMode(true);
81
82     m_hostingLayer = [CALayer layer];
83     [m_hostingLayer setFrame:m_webPage.bounds()];
84     [m_hostingLayer setOpaque:YES];
85     [m_hostingLayer setGeometryFlipped:YES];
86
87     updateLayerHostingContext();
88     setColorSpace(parameters.colorSpace);
89
90     LayerTreeContext layerTreeContext;
91     layerTreeContext.contextID = m_layerHostingContext->contextID();
92     m_webPage.send(Messages::DrawingAreaProxy::EnterAcceleratedCompositingMode(0, layerTreeContext));
93 }
94
95 TiledCoreAnimationDrawingArea::~TiledCoreAnimationDrawingArea()
96 {
97     m_layerFlushScheduler.invalidate();
98 }
99
100 void TiledCoreAnimationDrawingArea::setNeedsDisplay()
101 {
102 }
103
104 void TiledCoreAnimationDrawingArea::setNeedsDisplayInRect(const IntRect& rect)
105 {
106 }
107
108 void TiledCoreAnimationDrawingArea::scroll(const IntRect& scrollRect, const IntSize& scrollDelta)
109 {
110     updateScrolledExposedRect();
111 }
112
113 void TiledCoreAnimationDrawingArea::setRootCompositingLayer(GraphicsLayer* graphicsLayer)
114 {
115     CALayer *rootLayer = graphicsLayer ? graphicsLayer->platformLayer() : nil;
116
117     if (m_layerTreeStateIsFrozen) {
118         m_pendingRootLayer = rootLayer;
119         return;
120     }
121
122     setRootCompositingLayer(rootLayer);
123 }
124
125 void TiledCoreAnimationDrawingArea::forceRepaint()
126 {
127     if (m_layerTreeStateIsFrozen)
128         return;
129
130     for (Frame* frame = &m_webPage.corePage()->mainFrame(); frame; frame = frame->tree().traverseNext()) {
131         FrameView* frameView = frame->view();
132         if (!frameView || !frameView->tiledBacking())
133             continue;
134
135         frameView->tiledBacking()->forceRepaint();
136     }
137
138     flushLayers();
139     [CATransaction flush];
140     [CATransaction synchronize];
141 }
142
143 bool TiledCoreAnimationDrawingArea::forceRepaintAsync(uint64_t callbackID)
144 {
145     if (m_layerTreeStateIsFrozen)
146         return false;
147
148     dispatchAfterEnsuringUpdatedScrollPosition([this, callbackID] {
149         m_webPage.drawingArea()->forceRepaint();
150         m_webPage.send(Messages::WebPageProxy::VoidCallback(callbackID));
151     });
152     return true;
153 }
154
155 void TiledCoreAnimationDrawingArea::setLayerTreeStateIsFrozen(bool layerTreeStateIsFrozen)
156 {
157     if (m_layerTreeStateIsFrozen == layerTreeStateIsFrozen)
158         return;
159
160     m_layerTreeStateIsFrozen = layerTreeStateIsFrozen;
161     if (m_layerTreeStateIsFrozen)
162         m_layerFlushScheduler.suspend();
163     else
164         m_layerFlushScheduler.resume();
165 }
166
167 bool TiledCoreAnimationDrawingArea::layerTreeStateIsFrozen() const
168 {
169     return m_layerTreeStateIsFrozen;
170 }
171
172 void TiledCoreAnimationDrawingArea::scheduleCompositingLayerFlush()
173 {
174     m_layerFlushScheduler.schedule();
175 }
176
177 void TiledCoreAnimationDrawingArea::scheduleCompositingLayerFlushImmediately()
178 {
179     scheduleCompositingLayerFlush();
180 }
181
182 void TiledCoreAnimationDrawingArea::updatePreferences(const WebPreferencesStore&)
183 {
184     Settings& settings = m_webPage.corePage()->settings();
185
186 #if ENABLE(ASYNC_SCROLLING)
187     if (AsyncScrollingCoordinator* scrollingCoordinator = toAsyncScrollingCoordinator(m_webPage.corePage()->scrollingCoordinator())) {
188         bool scrollingPerformanceLoggingEnabled = m_webPage.scrollingPerformanceLoggingEnabled();
189         ScrollingThread::dispatch(bind(&ScrollingTree::setScrollingPerformanceLoggingEnabled, scrollingCoordinator->scrollingTree(), scrollingPerformanceLoggingEnabled));
190     }
191 #endif
192
193     // Fixed position elements need to be composited and create stacking contexts
194     // in order to be scrolled by the ScrollingCoordinator. We also want to keep
195     // Settings:setFixedPositionCreatesStackingContext() enabled for iOS. See
196     // <rdar://problem/9813262> for more details.
197     settings.setAcceleratedCompositingForFixedPositionEnabled(true);
198     settings.setFixedPositionCreatesStackingContext(true);
199
200     bool showTiledScrollingIndicator = settings.showTiledScrollingIndicator();
201     if (showTiledScrollingIndicator == !!m_debugInfoLayer)
202         return;
203
204     updateDebugInfoLayer(showTiledScrollingIndicator);
205 }
206
207 void TiledCoreAnimationDrawingArea::mainFrameContentSizeChanged(const IntSize& size)
208 {
209     m_webPage.pageOverlayController().didChangeDocumentSize();
210 }
211
212 void TiledCoreAnimationDrawingArea::updateIntrinsicContentSizeIfNeeded()
213 {
214     if (!m_webPage.minimumLayoutSize().width())
215         return;
216
217     FrameView* frameView = m_webPage.mainFrameView();
218     if (!frameView)
219         return;
220
221     if (frameView->needsLayout())
222         return;
223
224     IntSize contentSize = frameView->autoSizingIntrinsicContentSize();
225     if (m_lastSentIntrinsicContentSize == contentSize)
226         return;
227
228     m_lastSentIntrinsicContentSize = contentSize;
229     m_webPage.send(Messages::DrawingAreaProxy::IntrinsicContentSizeDidChange(contentSize));
230 }
231
232 void TiledCoreAnimationDrawingArea::dispatchAfterEnsuringUpdatedScrollPosition(std::function<void ()> function)
233 {
234 #if ENABLE(ASYNC_SCROLLING)
235     if (!m_webPage.corePage()->scrollingCoordinator()) {
236         function();
237         return;
238     }
239
240     m_webPage.ref();
241     m_webPage.corePage()->scrollingCoordinator()->commitTreeStateIfNeeded();
242
243     if (!m_layerTreeStateIsFrozen)
244         m_layerFlushScheduler.suspend();
245
246     // It is possible for the drawing area to be destroyed before the bound block
247     // is invoked, so grab a reference to the web page here so we can access the drawing area through it.
248     // (The web page is already kept alive by dispatchAfterEnsuringUpdatedScrollPosition).
249     WebPage* webPage = &m_webPage;
250
251     ScrollingThread::dispatchBarrier([this, webPage, function] {
252         DrawingArea* drawingArea = webPage->drawingArea();
253         if (!drawingArea)
254             return;
255
256         function();
257
258         if (!m_layerTreeStateIsFrozen)
259             m_layerFlushScheduler.resume();
260
261         webPage->deref();
262     });
263 #else
264     function();
265 #endif
266 }
267
268 bool TiledCoreAnimationDrawingArea::flushLayers()
269 {
270     ASSERT(!m_layerTreeStateIsFrozen);
271
272     // This gets called outside of the normal event loop so wrap in an autorelease pool
273     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
274
275     m_webPage.layoutIfNeeded();
276
277     updateIntrinsicContentSizeIfNeeded();
278
279     if (m_pendingRootLayer) {
280         setRootCompositingLayer(m_pendingRootLayer.get());
281         m_pendingRootLayer = nullptr;
282     }
283
284     FloatRect visibleRect = [m_hostingLayer frame];
285     visibleRect.intersect(m_scrolledExposedRect);
286     m_webPage.pageOverlayController().flushPageOverlayLayers(visibleRect);
287
288     bool returnValue = m_webPage.mainFrameView()->flushCompositingStateIncludingSubframes();
289 #if ENABLE(ASYNC_SCROLLING)
290     if (ScrollingCoordinator* scrollingCoordinator = m_webPage.corePage()->scrollingCoordinator())
291         scrollingCoordinator->commitTreeStateIfNeeded();
292 #endif
293
294     // If we have an active transient zoom, we want the zoom to win over any changes
295     // that WebCore makes to the relevant layers, so re-apply our changes after flushing.
296     if (m_transientZoomScale != 1)
297         applyTransientZoomToLayers(m_transientZoomScale, m_transientZoomOrigin);
298
299     [pool drain];
300     return returnValue;
301 }
302
303 void TiledCoreAnimationDrawingArea::viewStateDidChange(ViewState::Flags changed, bool wantsDidUpdateViewState)
304 {
305     if (changed & ViewState::IsVisible) {
306         if (m_webPage.isVisible())
307             resumePainting();
308         else
309             suspendPainting();
310     }
311
312     if (wantsDidUpdateViewState)
313         m_sendDidUpdateViewStateTimer.startOneShot(0);
314 }
315
316 void TiledCoreAnimationDrawingArea::didUpdateViewStateTimerFired()
317 {
318     [CATransaction flush];
319     m_webPage.send(Messages::WebPageProxy::DidUpdateViewState());
320 }
321
322 void TiledCoreAnimationDrawingArea::suspendPainting()
323 {
324     ASSERT(!m_isPaintingSuspended);
325     m_isPaintingSuspended = true;
326
327     [m_hostingLayer setValue:@YES forKey:@"NSCAViewRenderPaused"];
328     [[NSNotificationCenter defaultCenter] postNotificationName:@"NSCAViewRenderDidPauseNotification" object:nil userInfo:[NSDictionary dictionaryWithObject:m_hostingLayer.get() forKey:@"layer"]];
329 }
330
331 void TiledCoreAnimationDrawingArea::resumePainting()
332 {
333     if (!m_isPaintingSuspended) {
334         // FIXME: We can get a call to resumePainting when painting is not suspended.
335         // This happens when sending a synchronous message to create a new page. See <rdar://problem/8976531>.
336         return;
337     }
338     m_isPaintingSuspended = false;
339
340     [m_hostingLayer setValue:@NO forKey:@"NSCAViewRenderPaused"];
341     [[NSNotificationCenter defaultCenter] postNotificationName:@"NSCAViewRenderDidResumeNotification" object:nil userInfo:[NSDictionary dictionaryWithObject:m_hostingLayer.get() forKey:@"layer"]];
342 }
343
344 void TiledCoreAnimationDrawingArea::setExposedRect(const FloatRect& exposedRect)
345 {
346     m_exposedRect = exposedRect;
347     updateScrolledExposedRect();
348 }
349
350 void TiledCoreAnimationDrawingArea::updateScrolledExposedRect()
351 {
352     FrameView* frameView = m_webPage.mainFrameView();
353     if (!frameView)
354         return;
355
356     m_scrolledExposedRect = m_exposedRect;
357
358 #if !PLATFORM(IOS)
359     if (!m_exposedRect.isInfinite()) {
360         IntPoint scrollPositionWithOrigin = frameView->scrollPosition() + toIntSize(frameView->scrollOrigin());
361         m_scrolledExposedRect.moveBy(scrollPositionWithOrigin);
362     }
363 #endif
364
365     frameView->setExposedRect(m_scrolledExposedRect);
366
367     m_webPage.pageOverlayController().didChangeExposedRect();
368 }
369
370 void TiledCoreAnimationDrawingArea::updateGeometry(const IntSize& viewSize, const IntSize& layerPosition)
371 {
372     m_inUpdateGeometry = true;
373
374     IntSize size = viewSize;
375     IntSize contentSize = IntSize(-1, -1);
376
377     if (!m_webPage.minimumLayoutSize().width() || m_webPage.autoSizingShouldExpandToViewHeight())
378         m_webPage.setSize(size);
379
380     FrameView* frameView = m_webPage.mainFrameView();
381
382     if (m_webPage.autoSizingShouldExpandToViewHeight() && frameView)
383         frameView->setAutoSizeFixedMinimumHeight(viewSize.height());
384
385     m_webPage.layoutIfNeeded();
386
387     if (m_webPage.minimumLayoutSize().width() && frameView) {
388         contentSize = frameView->autoSizingIntrinsicContentSize();
389         size = contentSize;
390     }
391
392     if (!m_layerTreeStateIsFrozen)
393         flushLayers();
394
395     [CATransaction begin];
396     [CATransaction setDisableActions:YES];
397
398     [m_hostingLayer setFrame:CGRectMake(layerPosition.width(), layerPosition.height(), viewSize.width(), viewSize.height())];
399
400     [CATransaction commit];
401     
402     [CATransaction flush];
403     [CATransaction synchronize];
404
405     m_webPage.send(Messages::DrawingAreaProxy::DidUpdateGeometry());
406
407     m_inUpdateGeometry = false;
408 }
409
410 void TiledCoreAnimationDrawingArea::setDeviceScaleFactor(float deviceScaleFactor)
411 {
412     m_webPage.setDeviceScaleFactor(deviceScaleFactor);
413 }
414
415 void TiledCoreAnimationDrawingArea::setLayerHostingMode(LayerHostingMode)
416 {
417     updateLayerHostingContext();
418
419     // Finally, inform the UIProcess that the context has changed.
420     LayerTreeContext layerTreeContext;
421     layerTreeContext.contextID = m_layerHostingContext->contextID();
422     m_webPage.send(Messages::DrawingAreaProxy::UpdateAcceleratedCompositingMode(0, layerTreeContext));
423 }
424
425 void TiledCoreAnimationDrawingArea::setColorSpace(const ColorSpaceData& colorSpace)
426 {
427     m_layerHostingContext->setColorSpace(colorSpace.cgColorSpace.get());
428 }
429
430 void TiledCoreAnimationDrawingArea::updateLayerHostingContext()
431 {
432     RetainPtr<CGColorSpaceRef> colorSpace;
433
434     // Invalidate the old context.
435     if (m_layerHostingContext) {
436         colorSpace = m_layerHostingContext->colorSpace();
437         m_layerHostingContext->invalidate();
438         m_layerHostingContext = nullptr;
439     }
440
441     // Create a new context and set it up.
442     switch (m_webPage.layerHostingMode()) {
443     case LayerHostingMode::InProcess:
444         m_layerHostingContext = LayerHostingContext::createForPort(WebProcess::shared().compositingRenderServerPort());
445         break;
446 #if HAVE(OUT_OF_PROCESS_LAYER_HOSTING)
447     case LayerHostingMode::OutOfProcess:
448         m_layerHostingContext = LayerHostingContext::createForExternalHostingProcess();
449         break;
450 #endif
451     }
452
453     if (m_rootLayer)
454         m_layerHostingContext->setRootLayer(m_hostingLayer.get());
455
456     if (colorSpace)
457         m_layerHostingContext->setColorSpace(colorSpace.get());
458 }
459
460 void TiledCoreAnimationDrawingArea::setRootCompositingLayer(CALayer *layer)
461 {
462     ASSERT(!m_layerTreeStateIsFrozen);
463
464     [CATransaction begin];
465     [CATransaction setDisableActions:YES];
466
467     [m_hostingLayer setSublayers:layer ? @[ layer, m_webPage.pageOverlayController().viewOverlayRootLayer()->platformLayer() ] : @[ ]];
468
469     bool hadRootLayer = !!m_rootLayer;
470     m_rootLayer = layer;
471     [m_rootLayer setSublayerTransform:m_transform];
472
473     if (hadRootLayer != !!layer)
474         m_layerHostingContext->setRootLayer(layer ? m_hostingLayer.get() : 0);
475
476     updateDebugInfoLayer(m_webPage.corePage()->settings().showTiledScrollingIndicator());
477
478     [CATransaction commit];
479 }
480
481 TiledBacking* TiledCoreAnimationDrawingArea::mainFrameTiledBacking() const
482 {
483     FrameView* frameView = m_webPage.mainFrameView();
484     return frameView ? frameView->tiledBacking() : nullptr;
485 }
486
487 void TiledCoreAnimationDrawingArea::updateDebugInfoLayer(bool showLayer)
488 {
489     if (showLayer) {
490         if (TiledBacking* tiledBacking = mainFrameTiledBacking()) {
491             if (PlatformCALayer* indicatorLayer = tiledBacking->tiledScrollingIndicatorLayer())
492                 m_debugInfoLayer = indicatorLayer->platformLayer();
493         }
494
495         if (m_debugInfoLayer) {
496 #ifndef NDEBUG
497             [m_debugInfoLayer setName:@"Debug Info"];
498 #endif
499             [m_hostingLayer addSublayer:m_debugInfoLayer.get()];
500         }
501     } else if (m_debugInfoLayer) {
502         [m_debugInfoLayer removeFromSuperlayer];
503         m_debugInfoLayer = nullptr;
504     }
505 }
506
507 bool TiledCoreAnimationDrawingArea::shouldUseTiledBackingForFrameView(const FrameView* frameView)
508 {
509     return frameView && frameView->frame().isMainFrame();
510 }
511
512 PlatformCALayer* TiledCoreAnimationDrawingArea::layerForTransientZoom() const
513 {
514     RenderLayerBacking* renderViewBacking = m_webPage.mainFrameView()->renderView()->layer()->backing();
515
516     if (GraphicsLayer* contentsContainmentLayer = renderViewBacking->contentsContainmentLayer())
517         return toGraphicsLayerCA(contentsContainmentLayer)->platformCALayer();
518
519     return toGraphicsLayerCA(renderViewBacking->graphicsLayer())->platformCALayer();
520 }
521
522 PlatformCALayer* TiledCoreAnimationDrawingArea::shadowLayerForTransientZoom() const
523 {
524     RenderLayerCompositor& renderLayerCompositor = m_webPage.mainFrameView()->renderView()->compositor();
525
526     if (GraphicsLayer* shadowGraphicsLayer = renderLayerCompositor.layerForContentShadow())
527         return toGraphicsLayerCA(shadowGraphicsLayer)->platformCALayer();
528
529     return nullptr;
530 }
531     
532 static FloatPoint shadowLayerPositionForFrame(FrameView& frameView, FloatPoint origin)
533 {
534     FloatPoint position = frameView.renderView()->documentRect().location() + FloatPoint(0, FrameView::yPositionForRootContentLayer(frameView.scrollPosition(), frameView.topContentInset(), frameView.headerHeight()));
535
536     return position + origin.expandedTo(FloatPoint());
537 }
538
539 static FloatRect shadowLayerBoundsForFrame(FrameView& frameView, float transientScale)
540 {
541     FloatRect clipLayerFrame(frameView.renderView()->documentRect());
542     FloatRect shadowLayerFrame = clipLayerFrame;
543     
544     shadowLayerFrame.scale(transientScale / frameView.frame().page()->pageScaleFactor());
545     shadowLayerFrame.intersect(clipLayerFrame);
546     
547     return shadowLayerFrame;
548 }
549
550 void TiledCoreAnimationDrawingArea::applyTransientZoomToLayers(double scale, FloatPoint origin)
551 {
552     // FIXME: Scrollbars should stay in-place and change height while zooming.
553
554     if (!m_hostingLayer)
555         return;
556
557     TransformationMatrix transform;
558     transform.translate(origin.x(), origin.y());
559     transform.scale(scale);
560
561     PlatformCALayer* zoomLayer = layerForTransientZoom();
562     zoomLayer->setTransform(transform);
563     zoomLayer->setAnchorPoint(FloatPoint3D());
564     zoomLayer->setPosition(FloatPoint3D());
565     
566     if (PlatformCALayer* shadowLayer = shadowLayerForTransientZoom()) {
567         FrameView& frameView = *m_webPage.mainFrameView();
568         shadowLayer->setBounds(shadowLayerBoundsForFrame(frameView, scale));
569         shadowLayer->setPosition(shadowLayerPositionForFrame(frameView, origin));
570     }
571
572     m_transientZoomScale = scale;
573     m_transientZoomOrigin = origin;
574 }
575
576 void TiledCoreAnimationDrawingArea::adjustTransientZoom(double scale, FloatPoint origin)
577 {
578     applyTransientZoomToLayers(scale, origin);
579
580     double currentPageScale = m_webPage.pageScaleFactor();
581     if (scale > currentPageScale)
582         return;
583
584     FrameView* frameView = m_webPage.mainFrameView();
585     FloatRect tileCoverageRect = frameView->visibleContentRectIncludingScrollbars();
586     tileCoverageRect.moveBy(-origin);
587     tileCoverageRect.scale(currentPageScale / scale);
588     frameView->renderView()->layer()->backing()->tiledBacking()->prepopulateRect(tileCoverageRect);
589 }
590
591 static RetainPtr<CABasicAnimation> transientZoomSnapAnimationForKeyPath(String keyPath)
592 {
593     const float transientZoomSnapBackDuration = 0.25;
594
595     RetainPtr<CABasicAnimation> animation = [CABasicAnimation animationWithKeyPath:keyPath];
596     [animation setDuration:transientZoomSnapBackDuration];
597     [animation setFillMode:kCAFillModeForwards];
598     [animation setRemovedOnCompletion:false];
599     [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
600
601     return animation;
602 }
603
604 void TiledCoreAnimationDrawingArea::commitTransientZoom(double scale, FloatPoint origin)
605 {
606     FrameView& frameView = *m_webPage.mainFrameView();
607     FloatRect visibleContentRect = frameView.visibleContentRectIncludingScrollbars();
608
609     FloatPoint constrainedOrigin = visibleContentRect.location();
610     constrainedOrigin.moveBy(-origin);
611
612     IntSize scaledTotalContentsSize = frameView.totalContentsSize();
613     scaledTotalContentsSize.scale(scale / m_webPage.pageScaleFactor());
614
615     // Scaling may have exposed the overhang area, so we need to constrain the final
616     // layer position exactly like scrolling will once it's committed, to ensure that
617     // scrolling doesn't make the view jump.
618     constrainedOrigin = ScrollableArea::constrainScrollPositionForOverhang(roundedIntRect(visibleContentRect), scaledTotalContentsSize, roundedIntPoint(constrainedOrigin), frameView.scrollOrigin(), frameView.headerHeight(), frameView.footerHeight());
619     constrainedOrigin.moveBy(-visibleContentRect.location());
620     constrainedOrigin = -constrainedOrigin;
621
622     if (m_transientZoomScale == scale && roundedIntPoint(m_transientZoomOrigin) == roundedIntPoint(constrainedOrigin)) {
623         // We're already at the right scale and position, so we don't need to animate.
624         applyTransientZoomToPage(scale, origin);
625         return;
626     }
627
628     TransformationMatrix transform;
629     transform.translate(constrainedOrigin.x(), constrainedOrigin.y());
630     transform.scale(scale);
631
632     RetainPtr<CABasicAnimation> renderViewAnimationCA = transientZoomSnapAnimationForKeyPath("transform");
633     RefPtr<PlatformCAAnimation> renderViewAnimation = PlatformCAAnimationMac::create(renderViewAnimationCA.get());
634     renderViewAnimation->setToValue(transform);
635
636     RetainPtr<CALayer> shadowCALayer;
637     if (PlatformCALayer* shadowLayer = shadowLayerForTransientZoom())
638         shadowCALayer = shadowLayer->platformLayer();
639
640     RefPtr<PlatformCALayer> zoomLayer = layerForTransientZoom();
641     RefPtr<WebPage> page = &m_webPage;
642
643     [CATransaction begin];
644     [CATransaction setCompletionBlock:[zoomLayer, shadowCALayer, page, scale, origin] () {
645         zoomLayer->removeAnimationForKey("transientZoomCommit");
646         if (shadowCALayer)
647             [shadowCALayer removeAllAnimations];
648
649         if (TiledCoreAnimationDrawingArea* drawingArea = static_cast<TiledCoreAnimationDrawingArea*>(page->drawingArea()))
650             drawingArea->applyTransientZoomToPage(scale, origin);
651     }];
652
653     zoomLayer->addAnimationForKey("transientZoomCommit", renderViewAnimation.get());
654
655     if (shadowCALayer) {
656         FloatRect shadowBounds = shadowLayerBoundsForFrame(frameView, scale);
657         RetainPtr<CGPathRef> shadowPath = adoptCF(CGPathCreateWithRect(shadowBounds, NULL)).get();
658
659         RetainPtr<CABasicAnimation> shadowBoundsAnimation = transientZoomSnapAnimationForKeyPath("bounds");
660         [shadowBoundsAnimation setToValue:[NSValue valueWithRect:shadowBounds]];
661         RetainPtr<CABasicAnimation> shadowPositionAnimation = transientZoomSnapAnimationForKeyPath("position");
662         [shadowPositionAnimation setToValue:[NSValue valueWithPoint:shadowLayerPositionForFrame(frameView, constrainedOrigin)]];
663         RetainPtr<CABasicAnimation> shadowPathAnimation = transientZoomSnapAnimationForKeyPath("shadowPath");
664         [shadowPathAnimation setToValue:(id)shadowPath.get()];
665
666         [shadowCALayer addAnimation:shadowBoundsAnimation.get() forKey:@"transientZoomCommitShadowBounds"];
667         [shadowCALayer addAnimation:shadowPositionAnimation.get() forKey:@"transientZoomCommitShadowPosition"];
668         [shadowCALayer addAnimation:shadowPathAnimation.get() forKey:@"transientZoomCommitShadowPath"];
669     }
670
671     [CATransaction commit];
672 }
673
674 void TiledCoreAnimationDrawingArea::applyTransientZoomToPage(double scale, FloatPoint origin)
675 {
676     // If the page scale is already the target scale, setPageScaleFactor() will short-circuit
677     // and not apply the transform, so we can't depend on it to do so.
678     TransformationMatrix finalTransform;
679     finalTransform.scale(scale);
680     layerForTransientZoom()->setTransform(finalTransform);
681     
682     FrameView& frameView = *m_webPage.mainFrameView();
683
684     if (PlatformCALayer* shadowLayer = shadowLayerForTransientZoom()) {
685         shadowLayer->setBounds(shadowLayerBoundsForFrame(frameView, 1));
686         shadowLayer->setPosition(shadowLayerPositionForFrame(frameView, FloatPoint()));
687     }
688
689     FloatPoint unscrolledOrigin(origin);
690     FloatRect unobscuredContentRect = frameView.unobscuredContentRectIncludingScrollbars();
691     unscrolledOrigin.moveBy(-unobscuredContentRect.location());
692     m_webPage.scalePage(scale, roundedIntPoint(-unscrolledOrigin));
693     m_transientZoomScale = 1;
694     flushLayers();
695 }
696
697 } // namespace WebKit
698
699 #endif // !PLATFORM(IOS)