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