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