blob: 4cf51fc3089e1a396fb71782d4c309d7dd3923c6 [file] [log] [blame]
/*
* Copyright (C) 2011-2018 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#import "TiledCoreAnimationDrawingArea.h"
#if PLATFORM(MAC)
#import "ColorSpaceData.h"
#import "DrawingAreaProxyMessages.h"
#import "LayerHostingContext.h"
#import "LayerTreeContext.h"
#import "Logging.h"
#import "ViewGestureControllerMessages.h"
#import "WebFrame.h"
#import "WebPage.h"
#import "WebPageCreationParameters.h"
#import "WebPageProxyMessages.h"
#import "WebProcess.h"
#import <QuartzCore/QuartzCore.h>
#import <WebCore/DebugPageOverlays.h>
#import <WebCore/Frame.h>
#import <WebCore/FrameView.h>
#import <WebCore/GraphicsContext.h>
#import <WebCore/GraphicsLayerCA.h>
#import <WebCore/InspectorController.h>
#import <WebCore/Page.h>
#import <WebCore/PlatformCAAnimationCocoa.h>
#import <WebCore/RenderLayerBacking.h>
#import <WebCore/RenderLayerCompositor.h>
#import <WebCore/RenderView.h>
#import <WebCore/RunLoopObserver.h>
#import <WebCore/ScrollbarTheme.h>
#import <WebCore/Settings.h>
#import <WebCore/TiledBacking.h>
#import <WebCore/WebActionDisablingCALayerDelegate.h>
#import <pal/spi/cocoa/QuartzCoreSPI.h>
#import <wtf/MachSendRight.h>
#import <wtf/MainThread.h>
#if ENABLE(ASYNC_SCROLLING)
#import <WebCore/AsyncScrollingCoordinator.h>
#import <WebCore/ScrollingThread.h>
#import <WebCore/ScrollingTree.h>
#endif
namespace WebKit {
using namespace WebCore;
TiledCoreAnimationDrawingArea::TiledCoreAnimationDrawingArea(WebPage& webPage, const WebPageCreationParameters& parameters)
: DrawingArea(DrawingAreaTypeTiledCoreAnimation, parameters.drawingAreaIdentifier, webPage)
, m_layerFlushThrottlingTimer(*this, &TiledCoreAnimationDrawingArea::layerFlushThrottlingTimerFired)
, m_sendDidUpdateActivityStateTimer(RunLoop::main(), this, &TiledCoreAnimationDrawingArea::didUpdateActivityStateTimerFired)
, m_isPaintingSuspended(!(parameters.activityState & ActivityState::IsVisible))
{
m_webPage.corePage()->settings().setForceCompositingMode(true);
m_hostingLayer = [CALayer layer];
[m_hostingLayer setDelegate:[WebActionDisablingCALayerDelegate shared]];
[m_hostingLayer setFrame:m_webPage.bounds()];
[m_hostingLayer setOpaque:YES];
[m_hostingLayer setGeometryFlipped:YES];
m_layerFlushRunLoopObserver = makeUnique<RunLoopObserver>(static_cast<CFIndex>(RunLoopObserver::WellKnownRunLoopOrders::LayerFlush), [this]() {
this->layerFlushRunLoopCallback();
});
updateLayerHostingContext();
setColorSpace(parameters.colorSpace);
}
TiledCoreAnimationDrawingArea::~TiledCoreAnimationDrawingArea()
{
invalidateLayerFlushRunLoopObserver();
}
void TiledCoreAnimationDrawingArea::sendEnterAcceleratedCompositingModeIfNeeded()
{
if (!m_rootLayer)
return;
if (!m_needsSendEnterAcceleratedCompositingMode)
return;
m_needsSendEnterAcceleratedCompositingMode = false;
// Let the first commit complete before sending.
RunLoop::main().dispatch([this, weakThis = makeWeakPtr(*this)] {
if (!weakThis)
return;
LayerTreeContext layerTreeContext;
layerTreeContext.contextID = m_layerHostingContext->contextID();
send(Messages::DrawingAreaProxy::EnterAcceleratedCompositingMode(0, layerTreeContext));
});
}
void TiledCoreAnimationDrawingArea::setNeedsDisplay()
{
}
void TiledCoreAnimationDrawingArea::setNeedsDisplayInRect(const IntRect& rect)
{
}
void TiledCoreAnimationDrawingArea::scroll(const IntRect& scrollRect, const IntSize& scrollDelta)
{
updateScrolledExposedRect();
}
void TiledCoreAnimationDrawingArea::setRootCompositingLayer(GraphicsLayer* graphicsLayer)
{
CALayer *rootLayer = graphicsLayer ? graphicsLayer->platformLayer() : nil;
if (m_layerTreeStateIsFrozen) {
m_pendingRootLayer = rootLayer;
return;
}
m_pendingRootLayer = nullptr;
setRootCompositingLayer(rootLayer);
}
void TiledCoreAnimationDrawingArea::forceRepaint()
{
if (m_layerTreeStateIsFrozen)
return;
for (Frame* frame = &m_webPage.corePage()->mainFrame(); frame; frame = frame->tree().traverseNext()) {
FrameView* frameView = frame->view();
if (!frameView || !frameView->tiledBacking())
continue;
frameView->tiledBacking()->forceRepaint();
}
flushLayers();
[CATransaction flush];
[CATransaction synchronize];
}
bool TiledCoreAnimationDrawingArea::forceRepaintAsync(CallbackID callbackID)
{
if (m_layerTreeStateIsFrozen)
return false;
dispatchAfterEnsuringUpdatedScrollPosition([this, callbackID] {
m_webPage.drawingArea()->forceRepaint();
m_webPage.send(Messages::WebPageProxy::VoidCallback(callbackID));
});
return true;
}
void TiledCoreAnimationDrawingArea::setLayerTreeStateIsFrozen(bool layerTreeStateIsFrozen)
{
if (m_layerTreeStateIsFrozen == layerTreeStateIsFrozen)
return;
m_layerTreeStateIsFrozen = layerTreeStateIsFrozen;
if (m_layerTreeStateIsFrozen) {
invalidateLayerFlushRunLoopObserver();
m_layerFlushThrottlingTimer.stop();
} else {
// Immediate flush as any delay in unfreezing can result in flashes.
scheduleLayerFlushRunLoopObserver();
}
}
bool TiledCoreAnimationDrawingArea::layerTreeStateIsFrozen() const
{
return m_layerTreeStateIsFrozen;
}
void TiledCoreAnimationDrawingArea::scheduleInitialDeferredPaint()
{
}
void TiledCoreAnimationDrawingArea::scheduleCompositingLayerFlush()
{
if (m_layerTreeStateIsFrozen) {
m_isLayerFlushThrottlingTemporarilyDisabledForInteraction = false;
return;
}
if (m_isLayerFlushThrottlingTemporarilyDisabledForInteraction) {
m_isLayerFlushThrottlingTemporarilyDisabledForInteraction = false;
scheduleLayerFlushRunLoopObserver();
m_layerFlushThrottlingTimer.stop();
return;
}
if (m_layerFlushThrottlingTimer.isActive()) {
ASSERT(m_isThrottlingLayerFlushes);
return;
}
scheduleLayerFlushRunLoopObserver();
}
void TiledCoreAnimationDrawingArea::scheduleCompositingLayerFlushImmediately()
{
scheduleLayerFlushRunLoopObserver();
}
void TiledCoreAnimationDrawingArea::updatePreferences(const WebPreferencesStore&)
{
Settings& settings = m_webPage.corePage()->settings();
#if ENABLE(ASYNC_SCROLLING)
if (AsyncScrollingCoordinator* scrollingCoordinator = downcast<AsyncScrollingCoordinator>(m_webPage.corePage()->scrollingCoordinator())) {
bool scrollingPerformanceLoggingEnabled = m_webPage.scrollingPerformanceLoggingEnabled();
RefPtr<ScrollingTree> scrollingTree = scrollingCoordinator->scrollingTree();
ScrollingThread::dispatch([scrollingTree, scrollingPerformanceLoggingEnabled] {
scrollingTree->setScrollingPerformanceLoggingEnabled(scrollingPerformanceLoggingEnabled);
});
}
#endif
// Fixed position elements need to be composited and create stacking contexts
// in order to be scrolled by the ScrollingCoordinator.
settings.setAcceleratedCompositingForFixedPositionEnabled(true);
DebugPageOverlays::settingsChanged(*m_webPage.corePage());
bool showTiledScrollingIndicator = settings.showTiledScrollingIndicator();
if (showTiledScrollingIndicator == !!m_debugInfoLayer)
return;
updateDebugInfoLayer(showTiledScrollingIndicator);
}
void TiledCoreAnimationDrawingArea::updateRootLayers()
{
if (!m_rootLayer) {
[m_hostingLayer setSublayers:@[ ]];
return;
}
[m_hostingLayer setSublayers:m_viewOverlayRootLayer ? @[ m_rootLayer.get(), m_viewOverlayRootLayer->platformLayer() ] : @[ m_rootLayer.get() ]];
if (m_debugInfoLayer)
[m_hostingLayer addSublayer:m_debugInfoLayer.get()];
}
void TiledCoreAnimationDrawingArea::attachViewOverlayGraphicsLayer(GraphicsLayer* viewOverlayRootLayer)
{
m_viewOverlayRootLayer = viewOverlayRootLayer;
updateRootLayers();
scheduleCompositingLayerFlush();
}
void TiledCoreAnimationDrawingArea::mainFrameContentSizeChanged(const IntSize& size)
{
}
void TiledCoreAnimationDrawingArea::setShouldScaleViewToFitDocument(bool shouldScaleView)
{
if (m_shouldScaleViewToFitDocument == shouldScaleView)
return;
m_shouldScaleViewToFitDocument = shouldScaleView;
scheduleCompositingLayerFlush();
}
void TiledCoreAnimationDrawingArea::scaleViewToFitDocumentIfNeeded()
{
const int maximumDocumentWidthForScaling = 1440;
const float minimumViewScale = 0.1;
if (!m_shouldScaleViewToFitDocument)
return;
LOG(Resize, "TiledCoreAnimationDrawingArea %p scaleViewToFitDocumentIfNeeded", this);
m_webPage.layoutIfNeeded();
if (!m_webPage.mainFrameView() || !m_webPage.mainFrameView()->renderView())
return;
int viewWidth = m_webPage.size().width();
int documentWidth = m_webPage.mainFrameView()->renderView()->unscaledDocumentRect().width();
bool documentWidthChanged = m_lastDocumentSizeForScaleToFit.width() != documentWidth;
bool viewWidthChanged = m_lastViewSizeForScaleToFit.width() != viewWidth;
LOG(Resize, " documentWidthChanged=%d, viewWidthChanged=%d", documentWidthChanged, viewWidthChanged);
if (!documentWidthChanged && !viewWidthChanged)
return;
// The view is now bigger than the document, so we'll re-evaluate whether we have to scale.
if (m_isScalingViewToFitDocument && viewWidth >= m_lastDocumentSizeForScaleToFit.width())
m_isScalingViewToFitDocument = false;
// Our current understanding of the document width is still up to date, and we're in scaling mode.
// Update the viewScale without doing an extra layout to re-determine the document width.
if (m_isScalingViewToFitDocument) {
if (!documentWidthChanged) {
m_lastViewSizeForScaleToFit = m_webPage.size();
float viewScale = (float)viewWidth / (float)m_lastDocumentSizeForScaleToFit.width();
if (viewScale < minimumViewScale) {
viewScale = minimumViewScale;
documentWidth = std::ceil(viewWidth / viewScale);
}
IntSize fixedLayoutSize(documentWidth, std::ceil((m_webPage.size().height() - m_webPage.corePage()->topContentInset()) / viewScale));
m_webPage.setFixedLayoutSize(fixedLayoutSize);
m_webPage.scaleView(viewScale);
LOG(Resize, " using fixed layout at %dx%d. document width %d unchanged, scaled to %.4f to fit view width %d", fixedLayoutSize.width(), fixedLayoutSize.height(), documentWidth, viewScale, viewWidth);
return;
}
IntSize fixedLayoutSize = m_webPage.fixedLayoutSize();
if (documentWidth > fixedLayoutSize.width()) {
LOG(Resize, " page laid out wider than fixed layout width. Not attempting to re-scale");
return;
}
}
LOG(Resize, " doing unconstrained layout");
// Lay out at the view size.
m_webPage.setUseFixedLayout(false);
m_webPage.layoutIfNeeded();
if (!m_webPage.mainFrameView() || !m_webPage.mainFrameView()->renderView())
return;
IntSize documentSize = m_webPage.mainFrameView()->renderView()->unscaledDocumentRect().size();
m_lastViewSizeForScaleToFit = m_webPage.size();
m_lastDocumentSizeForScaleToFit = documentSize;
documentWidth = documentSize.width();
float viewScale = 1;
LOG(Resize, " unscaled document size %dx%d. need to scale down: %d", documentSize.width(), documentSize.height(), documentWidth && documentWidth < maximumDocumentWidthForScaling && viewWidth < documentWidth);
// Avoid scaling down documents that don't fit in a certain width, to allow
// sites that want horizontal scrollbars to continue to have them.
if (documentWidth && documentWidth < maximumDocumentWidthForScaling && viewWidth < documentWidth) {
// If the document doesn't fit in the view, scale it down but lay out at the view size.
m_isScalingViewToFitDocument = true;
m_webPage.setUseFixedLayout(true);
viewScale = (float)viewWidth / (float)documentWidth;
if (viewScale < minimumViewScale) {
viewScale = minimumViewScale;
documentWidth = std::ceil(viewWidth / viewScale);
}
IntSize fixedLayoutSize(documentWidth, std::ceil((m_webPage.size().height() - m_webPage.corePage()->topContentInset()) / viewScale));
m_webPage.setFixedLayoutSize(fixedLayoutSize);
LOG(Resize, " using fixed layout at %dx%d. document width %d, scaled to %.4f to fit view width %d", fixedLayoutSize.width(), fixedLayoutSize.height(), documentWidth, viewScale, viewWidth);
}
m_webPage.scaleView(viewScale);
}
void TiledCoreAnimationDrawingArea::dispatchAfterEnsuringUpdatedScrollPosition(WTF::Function<void ()>&& function)
{
#if ENABLE(ASYNC_SCROLLING)
if (!m_webPage.corePage()->scrollingCoordinator()) {
function();
return;
}
m_webPage.ref();
m_webPage.corePage()->scrollingCoordinator()->commitTreeStateIfNeeded();
if (!m_layerTreeStateIsFrozen)
invalidateLayerFlushRunLoopObserver();
// It is possible for the drawing area to be destroyed before the bound block
// is invoked, so grab a reference to the web page here so we can access the drawing area through it.
// (The web page is already kept alive by dispatchAfterEnsuringUpdatedScrollPosition).
WebPage* webPage = &m_webPage;
ScrollingThread::dispatchBarrier([this, webPage, function = WTFMove(function)] {
DrawingArea* drawingArea = webPage->drawingArea();
if (!drawingArea)
return;
function();
if (!m_layerTreeStateIsFrozen)
scheduleLayerFlushRunLoopObserver();
webPage->deref();
});
#else
function();
#endif
}
void TiledCoreAnimationDrawingArea::sendPendingNewlyReachedPaintingMilestones()
{
if (!m_pendingNewlyReachedPaintingMilestones)
return;
m_webPage.send(Messages::WebPageProxy::DidReachLayoutMilestone(m_pendingNewlyReachedPaintingMilestones));
m_pendingNewlyReachedPaintingMilestones = { };
}
void TiledCoreAnimationDrawingArea::addTransactionCallbackID(CallbackID callbackID)
{
m_pendingCallbackIDs.append(callbackID);
scheduleCompositingLayerFlush();
}
void TiledCoreAnimationDrawingArea::addCommitHandlers()
{
if (m_webPage.firstFlushAfterCommit())
return;
[CATransaction addCommitHandler:[retainedPage = makeRefPtr(&m_webPage)] {
if (Page* corePage = retainedPage->corePage()) {
if (Frame* coreFrame = retainedPage->mainFrame())
corePage->inspectorController().willComposite(*coreFrame);
}
} forPhase:kCATransactionPhasePreCommit];
[CATransaction addCommitHandler:[retainedPage = makeRefPtr(&m_webPage)] {
if (Page* corePage = retainedPage->corePage()) {
if (Frame* coreFrame = retainedPage->mainFrame())
corePage->inspectorController().didComposite(*coreFrame);
}
if (auto drawingArea = static_cast<TiledCoreAnimationDrawingArea*>(retainedPage->drawingArea()))
drawingArea->sendPendingNewlyReachedPaintingMilestones();
retainedPage->setFirstFlushAfterCommit(false);
} forPhase:kCATransactionPhasePostCommit];
m_webPage.setFirstFlushAfterCommit(true);
}
void TiledCoreAnimationDrawingArea::flushLayers(FlushType flushType)
{
if (layerTreeStateIsFrozen())
return;
@autoreleasepool {
scaleViewToFitDocumentIfNeeded();
m_webPage.updateRendering();
m_webPage.flushPendingEditorStateUpdate();
if (m_pendingRootLayer) {
setRootCompositingLayer(m_pendingRootLayer.get());
m_pendingRootLayer = nullptr;
}
FloatRect visibleRect = [m_hostingLayer frame];
if (m_scrolledViewExposedRect)
visibleRect.intersect(m_scrolledViewExposedRect.value());
// Because our view-relative overlay root layer is not attached to the main GraphicsLayer tree, we need to flush it manually.
if (m_viewOverlayRootLayer)
m_viewOverlayRootLayer->flushCompositingState(visibleRect);
addCommitHandlers();
bool didFlushAllFrames = m_webPage.mainFrameView()->flushCompositingStateIncludingSubframes();
#if ENABLE(ASYNC_SCROLLING)
if (auto* scrollingCoordinator = m_webPage.corePage()->scrollingCoordinator()) {
scrollingCoordinator->commitTreeStateIfNeeded();
if (flushType == FlushType::Normal)
scrollingCoordinator->applyScrollingTreeLayerPositions();
}
#endif
// If we have an active transient zoom, we want the zoom to win over any changes
// that WebCore makes to the relevant layers, so re-apply our changes after flushing.
if (m_transientZoomScale != 1)
applyTransientZoomToLayers(m_transientZoomScale, m_transientZoomOrigin);
if (!m_pendingCallbackIDs.isEmpty()) {
send(Messages::DrawingAreaProxy::DispatchPresentationCallbacksAfterFlushingLayers(m_pendingCallbackIDs));
m_pendingCallbackIDs.clear();
}
if (didFlushAllFrames) {
sendEnterAcceleratedCompositingModeIfNeeded();
invalidateLayerFlushRunLoopObserver();
}
if (m_isThrottlingLayerFlushes)
startLayerFlushThrottlingTimer();
else
m_layerFlushThrottlingTimer.stop();
}
}
void TiledCoreAnimationDrawingArea::activityStateDidChange(OptionSet<ActivityState::Flag> changed, ActivityStateChangeID activityStateChangeID, const Vector<CallbackID>& nextActivityStateChangeCallbackIDs)
{
m_nextActivityStateChangeCallbackIDs.appendVector(nextActivityStateChangeCallbackIDs);
m_activityStateChangeID = std::max(m_activityStateChangeID, activityStateChangeID);
if (changed & ActivityState::IsVisible) {
if (m_webPage.isVisible())
resumePainting();
else
suspendPainting();
}
if (m_activityStateChangeID != ActivityStateChangeAsynchronous || !m_nextActivityStateChangeCallbackIDs.isEmpty())
m_sendDidUpdateActivityStateTimer.startOneShot(0_s);
}
void TiledCoreAnimationDrawingArea::didUpdateActivityStateTimerFired()
{
[CATransaction flush];
if (m_activityStateChangeID != ActivityStateChangeAsynchronous)
m_webPage.send(Messages::WebPageProxy::DidUpdateActivityState());
for (const auto& callbackID : m_nextActivityStateChangeCallbackIDs)
m_webPage.send(Messages::WebPageProxy::VoidCallback(callbackID));
m_nextActivityStateChangeCallbackIDs.clear();
m_activityStateChangeID = ActivityStateChangeAsynchronous;
}
void TiledCoreAnimationDrawingArea::suspendPainting()
{
ASSERT(!m_isPaintingSuspended);
m_isPaintingSuspended = true;
[m_hostingLayer setValue:@YES forKey:@"NSCAViewRenderPaused"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"NSCAViewRenderDidPauseNotification" object:nil userInfo:[NSDictionary dictionaryWithObject:m_hostingLayer.get() forKey:@"layer"]];
}
void TiledCoreAnimationDrawingArea::resumePainting()
{
if (!m_isPaintingSuspended) {
// FIXME: We can get a call to resumePainting when painting is not suspended.
// This happens when sending a synchronous message to create a new page. See <rdar://problem/8976531>.
return;
}
m_isPaintingSuspended = false;
[m_hostingLayer setValue:@NO forKey:@"NSCAViewRenderPaused"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"NSCAViewRenderDidResumeNotification" object:nil userInfo:[NSDictionary dictionaryWithObject:m_hostingLayer.get() forKey:@"layer"]];
}
void TiledCoreAnimationDrawingArea::setViewExposedRect(Optional<FloatRect> viewExposedRect)
{
m_viewExposedRect = viewExposedRect;
updateScrolledExposedRect();
}
FloatRect TiledCoreAnimationDrawingArea::exposedContentRect() const
{
ASSERT_NOT_REACHED();
return { };
}
void TiledCoreAnimationDrawingArea::setExposedContentRect(const FloatRect&)
{
ASSERT_NOT_REACHED();
}
void TiledCoreAnimationDrawingArea::updateScrolledExposedRect()
{
FrameView* frameView = m_webPage.mainFrameView();
if (!frameView)
return;
m_scrolledViewExposedRect = m_viewExposedRect;
#if !PLATFORM(IOS_FAMILY)
if (m_viewExposedRect) {
ScrollOffset scrollOffset = frameView->scrollOffsetFromPosition(frameView->scrollPosition());
m_scrolledViewExposedRect.value().moveBy(scrollOffset);
}
#endif
frameView->setViewExposedRect(m_scrolledViewExposedRect);
}
void TiledCoreAnimationDrawingArea::updateGeometry(const IntSize& viewSize, bool flushSynchronously, const WTF::MachSendRight& fencePort)
{
m_inUpdateGeometry = true;
IntSize size = viewSize;
IntSize contentSize = IntSize(-1, -1);
if (!m_webPage.minimumSizeForAutoLayout().width() || m_webPage.autoSizingShouldExpandToViewHeight())
m_webPage.setSize(size);
FrameView* frameView = m_webPage.mainFrameView();
if (m_webPage.autoSizingShouldExpandToViewHeight() && frameView)
frameView->setAutoSizeFixedMinimumHeight(viewSize.height());
m_webPage.layoutIfNeeded();
if (m_webPage.minimumSizeForAutoLayout().width() && frameView) {
contentSize = frameView->autoSizingIntrinsicContentSize();
size = contentSize;
}
flushLayers();
[CATransaction begin];
[CATransaction setDisableActions:YES];
[m_hostingLayer setFrame:CGRectMake(0, 0, viewSize.width(), viewSize.height())];
[CATransaction commit];
if (flushSynchronously)
[CATransaction flush];
send(Messages::DrawingAreaProxy::DidUpdateGeometry());
m_inUpdateGeometry = false;
m_layerHostingContext->setFencePort(fencePort.sendRight());
}
void TiledCoreAnimationDrawingArea::setDeviceScaleFactor(float deviceScaleFactor)
{
m_webPage.setDeviceScaleFactor(deviceScaleFactor);
}
void TiledCoreAnimationDrawingArea::setLayerHostingMode(LayerHostingMode)
{
updateLayerHostingContext();
// Finally, inform the UIProcess that the context has changed.
LayerTreeContext layerTreeContext;
layerTreeContext.contextID = m_layerHostingContext->contextID();
send(Messages::DrawingAreaProxy::UpdateAcceleratedCompositingMode(0, layerTreeContext));
}
void TiledCoreAnimationDrawingArea::setColorSpace(const ColorSpaceData& colorSpace)
{
m_layerHostingContext->setColorSpace(colorSpace.cgColorSpace.get());
}
void TiledCoreAnimationDrawingArea::updateLayerHostingContext()
{
RetainPtr<CGColorSpaceRef> colorSpace;
// Invalidate the old context.
if (m_layerHostingContext) {
colorSpace = m_layerHostingContext->colorSpace();
m_layerHostingContext->invalidate();
m_layerHostingContext = nullptr;
}
// Create a new context and set it up.
switch (m_webPage.layerHostingMode()) {
case LayerHostingMode::InProcess:
m_layerHostingContext = LayerHostingContext::createForPort(WebProcess::singleton().compositingRenderServerPort());
break;
#if HAVE(OUT_OF_PROCESS_LAYER_HOSTING)
case LayerHostingMode::OutOfProcess:
m_layerHostingContext = LayerHostingContext::createForExternalHostingProcess();
break;
#endif
}
if (m_rootLayer)
m_layerHostingContext->setRootLayer(m_hostingLayer.get());
if (colorSpace)
m_layerHostingContext->setColorSpace(colorSpace.get());
}
void TiledCoreAnimationDrawingArea::setRootCompositingLayer(CALayer *layer)
{
ASSERT(!m_layerTreeStateIsFrozen);
[CATransaction begin];
[CATransaction setDisableActions:YES];
bool hadRootLayer = !!m_rootLayer;
m_rootLayer = layer;
updateRootLayers();
if (hadRootLayer != !!layer)
m_layerHostingContext->setRootLayer(layer ? m_hostingLayer.get() : nil);
updateDebugInfoLayer(layer && m_webPage.corePage()->settings().showTiledScrollingIndicator());
[CATransaction commit];
}
TiledBacking* TiledCoreAnimationDrawingArea::mainFrameTiledBacking() const
{
FrameView* frameView = m_webPage.mainFrameView();
return frameView ? frameView->tiledBacking() : nullptr;
}
void TiledCoreAnimationDrawingArea::updateDebugInfoLayer(bool showLayer)
{
if (m_debugInfoLayer) {
[m_debugInfoLayer removeFromSuperlayer];
m_debugInfoLayer = nil;
}
if (showLayer) {
if (TiledBacking* tiledBacking = mainFrameTiledBacking()) {
if (PlatformCALayer* indicatorLayer = tiledBacking->tiledScrollingIndicatorLayer())
m_debugInfoLayer = indicatorLayer->platformLayer();
}
if (m_debugInfoLayer) {
[m_debugInfoLayer setName:@"Debug Info"];
[m_hostingLayer addSublayer:m_debugInfoLayer.get()];
}
}
}
bool TiledCoreAnimationDrawingArea::shouldUseTiledBackingForFrameView(const FrameView& frameView) const
{
return frameView.frame().isMainFrame() || m_webPage.corePage()->settings().asyncFrameScrollingEnabled();
}
PlatformCALayer* TiledCoreAnimationDrawingArea::layerForTransientZoom() const
{
RenderLayerBacking* renderViewBacking = m_webPage.mainFrameView()->renderView()->layer()->backing();
if (GraphicsLayer* contentsContainmentLayer = renderViewBacking->contentsContainmentLayer())
return downcast<GraphicsLayerCA>(*contentsContainmentLayer).platformCALayer();
return downcast<GraphicsLayerCA>(*renderViewBacking->graphicsLayer()).platformCALayer();
}
PlatformCALayer* TiledCoreAnimationDrawingArea::shadowLayerForTransientZoom() const
{
RenderLayerCompositor& renderLayerCompositor = m_webPage.mainFrameView()->renderView()->compositor();
if (GraphicsLayer* shadowGraphicsLayer = renderLayerCompositor.layerForContentShadow())
return downcast<GraphicsLayerCA>(*shadowGraphicsLayer).platformCALayer();
return nullptr;
}
static FloatPoint shadowLayerPositionForFrame(FrameView& frameView, FloatPoint origin)
{
// FIXME: correct for b-t documents?
FloatPoint position = frameView.positionForRootContentLayer();
return position + origin.expandedTo(FloatPoint());
}
static FloatRect shadowLayerBoundsForFrame(FrameView& frameView, float transientScale)
{
FloatRect clipLayerFrame(frameView.renderView()->documentRect());
FloatRect shadowLayerFrame = clipLayerFrame;
shadowLayerFrame.scale(transientScale / frameView.frame().page()->pageScaleFactor());
shadowLayerFrame.intersect(clipLayerFrame);
return shadowLayerFrame;
}
void TiledCoreAnimationDrawingArea::applyTransientZoomToLayers(double scale, FloatPoint origin)
{
// FIXME: Scrollbars should stay in-place and change height while zooming.
if (!m_hostingLayer)
return;
TransformationMatrix transform;
transform.translate(origin.x(), origin.y());
transform.scale(scale);
PlatformCALayer* zoomLayer = layerForTransientZoom();
zoomLayer->setTransform(transform);
zoomLayer->setAnchorPoint(FloatPoint3D());
zoomLayer->setPosition(FloatPoint3D());
if (PlatformCALayer* shadowLayer = shadowLayerForTransientZoom()) {
FrameView& frameView = *m_webPage.mainFrameView();
shadowLayer->setBounds(shadowLayerBoundsForFrame(frameView, scale));
shadowLayer->setPosition(shadowLayerPositionForFrame(frameView, origin));
}
m_transientZoomScale = scale;
m_transientZoomOrigin = origin;
}
void TiledCoreAnimationDrawingArea::adjustTransientZoom(double scale, FloatPoint origin)
{
scale *= m_webPage.viewScaleFactor();
applyTransientZoomToLayers(scale, origin);
double currentPageScale = m_webPage.totalScaleFactor();
if (scale > currentPageScale)
return;
FrameView* frameView = m_webPage.mainFrameView();
FloatRect tileCoverageRect = frameView->visibleContentRectIncludingScrollbars();
tileCoverageRect.moveBy(-origin);
tileCoverageRect.scale(currentPageScale / scale);
frameView->renderView()->layer()->backing()->tiledBacking()->prepopulateRect(tileCoverageRect);
}
static RetainPtr<CABasicAnimation> transientZoomSnapAnimationForKeyPath(String keyPath)
{
const float transientZoomSnapBackDuration = 0.25;
RetainPtr<CABasicAnimation> animation = [CABasicAnimation animationWithKeyPath:keyPath];
[animation setDuration:transientZoomSnapBackDuration];
[animation setFillMode:kCAFillModeForwards];
[animation setRemovedOnCompletion:false];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
return animation;
}
void TiledCoreAnimationDrawingArea::commitTransientZoom(double scale, FloatPoint origin)
{
scale *= m_webPage.viewScaleFactor();
FrameView& frameView = *m_webPage.mainFrameView();
FloatRect visibleContentRect = frameView.visibleContentRectIncludingScrollbars();
FloatPoint constrainedOrigin = visibleContentRect.location();
constrainedOrigin.moveBy(-origin);
IntSize scaledTotalContentsSize = frameView.totalContentsSize();
scaledTotalContentsSize.scale(scale / m_webPage.totalScaleFactor());
// Scaling may have exposed the overhang area, so we need to constrain the final
// layer position exactly like scrolling will once it's committed, to ensure that
// scrolling doesn't make the view jump.
constrainedOrigin = ScrollableArea::constrainScrollPositionForOverhang(roundedIntRect(visibleContentRect), scaledTotalContentsSize, roundedIntPoint(constrainedOrigin), frameView.scrollOrigin(), frameView.headerHeight(), frameView.footerHeight());
constrainedOrigin.moveBy(-visibleContentRect.location());
constrainedOrigin = -constrainedOrigin;
if (m_transientZoomScale == scale && roundedIntPoint(m_transientZoomOrigin) == roundedIntPoint(constrainedOrigin)) {
// We're already at the right scale and position, so we don't need to animate.
applyTransientZoomToPage(scale, origin);
return;
}
TransformationMatrix transform;
transform.translate(constrainedOrigin.x(), constrainedOrigin.y());
transform.scale(scale);
RetainPtr<CABasicAnimation> renderViewAnimationCA = transientZoomSnapAnimationForKeyPath("transform");
auto renderViewAnimation = PlatformCAAnimationCocoa::create(renderViewAnimationCA.get());
renderViewAnimation->setToValue(transform);
RetainPtr<CALayer> shadowCALayer;
if (PlatformCALayer* shadowLayer = shadowLayerForTransientZoom())
shadowCALayer = shadowLayer->platformLayer();
RefPtr<PlatformCALayer> zoomLayer = layerForTransientZoom();
RefPtr<WebPage> page = &m_webPage;
[CATransaction begin];
[CATransaction setCompletionBlock:[zoomLayer, shadowCALayer, page, scale, origin] () {
zoomLayer->removeAnimationForKey("transientZoomCommit");
if (shadowCALayer)
[shadowCALayer removeAllAnimations];
if (TiledCoreAnimationDrawingArea* drawingArea = static_cast<TiledCoreAnimationDrawingArea*>(page->drawingArea()))
drawingArea->applyTransientZoomToPage(scale, origin);
}];
zoomLayer->addAnimationForKey("transientZoomCommit", renderViewAnimation.get());
if (shadowCALayer) {
FloatRect shadowBounds = shadowLayerBoundsForFrame(frameView, scale);
RetainPtr<CGPathRef> shadowPath = adoptCF(CGPathCreateWithRect(shadowBounds, NULL));
RetainPtr<CABasicAnimation> shadowBoundsAnimation = transientZoomSnapAnimationForKeyPath("bounds");
[shadowBoundsAnimation setToValue:[NSValue valueWithRect:shadowBounds]];
RetainPtr<CABasicAnimation> shadowPositionAnimation = transientZoomSnapAnimationForKeyPath("position");
[shadowPositionAnimation setToValue:[NSValue valueWithPoint:shadowLayerPositionForFrame(frameView, constrainedOrigin)]];
RetainPtr<CABasicAnimation> shadowPathAnimation = transientZoomSnapAnimationForKeyPath("shadowPath");
[shadowPathAnimation setToValue:(__bridge id)shadowPath.get()];
[shadowCALayer addAnimation:shadowBoundsAnimation.get() forKey:@"transientZoomCommitShadowBounds"];
[shadowCALayer addAnimation:shadowPositionAnimation.get() forKey:@"transientZoomCommitShadowPosition"];
[shadowCALayer addAnimation:shadowPathAnimation.get() forKey:@"transientZoomCommitShadowPath"];
}
[CATransaction commit];
}
void TiledCoreAnimationDrawingArea::applyTransientZoomToPage(double scale, FloatPoint origin)
{
// If the page scale is already the target scale, setPageScaleFactor() will short-circuit
// and not apply the transform, so we can't depend on it to do so.
TransformationMatrix finalTransform;
finalTransform.scale(scale);
layerForTransientZoom()->setTransform(finalTransform);
FrameView& frameView = *m_webPage.mainFrameView();
if (PlatformCALayer* shadowLayer = shadowLayerForTransientZoom()) {
shadowLayer->setBounds(shadowLayerBoundsForFrame(frameView, 1));
shadowLayer->setPosition(shadowLayerPositionForFrame(frameView, FloatPoint()));
}
FloatPoint unscrolledOrigin(origin);
FloatRect unobscuredContentRect = frameView.unobscuredContentRectIncludingScrollbars();
unscrolledOrigin.moveBy(-unobscuredContentRect.location());
m_webPage.scalePage(scale / m_webPage.viewScaleFactor(), roundedIntPoint(-unscrolledOrigin));
m_transientZoomScale = 1;
flushLayers(FlushType::TransientZoom);
}
void TiledCoreAnimationDrawingArea::addFence(const MachSendRight& fencePort)
{
m_layerHostingContext->setFencePort(fencePort.sendRight());
}
void TiledCoreAnimationDrawingArea::layerFlushRunLoopCallback()
{
flushLayers();
}
void TiledCoreAnimationDrawingArea::invalidateLayerFlushRunLoopObserver()
{
m_layerFlushRunLoopObserver->invalidate();
}
void TiledCoreAnimationDrawingArea::scheduleLayerFlushRunLoopObserver()
{
m_layerFlushRunLoopObserver->schedule(CFRunLoopGetCurrent());
}
bool TiledCoreAnimationDrawingArea::adjustLayerFlushThrottling(LayerFlushThrottleState::Flags flags)
{
bool wasThrottlingLayerFlushes = m_isThrottlingLayerFlushes;
m_isThrottlingLayerFlushes = flags & LayerFlushThrottleState::Enabled;
m_isLayerFlushThrottlingTemporarilyDisabledForInteraction = flags & LayerFlushThrottleState::UserIsInteracting;
if (wasThrottlingLayerFlushes == m_isThrottlingLayerFlushes)
return true;
m_layerFlushThrottlingTimer.stop();
if (m_layerTreeStateIsFrozen)
return true;
if (m_isThrottlingLayerFlushes) {
invalidateLayerFlushRunLoopObserver();
startLayerFlushThrottlingTimer();
} else
scheduleLayerFlushRunLoopObserver();
return true;
}
bool TiledCoreAnimationDrawingArea::layerFlushThrottlingIsActive() const
{
return m_isThrottlingLayerFlushes && !m_layerTreeStateIsFrozen;
}
void TiledCoreAnimationDrawingArea::startLayerFlushThrottlingTimer()
{
ASSERT(m_isThrottlingLayerFlushes);
const auto throttledFlushDelay = 500_ms;
m_layerFlushThrottlingTimer.startOneShot(throttledFlushDelay);
}
void TiledCoreAnimationDrawingArea::layerFlushThrottlingTimerFired()
{
if (m_layerTreeStateIsFrozen)
return;
scheduleLayerFlushRunLoopObserver();
}
} // namespace WebKit
#endif // PLATFORM(MAC)