blob: 55e650531cb5e0bcb9e7837e19937842d735140b [file] [log] [blame]
/*
* Copyright (C) 2011, 2012 Nokia Corporation and/or its subsidiary(-ies)
* Copyright (C) 2011 Benjamin Poulain <benjamin@webkit.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this program; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "config.h"
#include "PageViewportController.h"
#include "PageViewportControllerClient.h"
#include "WebPageProxy.h"
#include <WebCore/FloatRect.h>
#include <WebCore/FloatSize.h>
#include <wtf/MathExtras.h>
using namespace WebCore;
namespace WebKit {
bool fuzzyCompare(float a, float b, float epsilon)
{
return std::abs(a - b) < epsilon;
}
ViewportUpdateDeferrer::ViewportUpdateDeferrer(PageViewportController* PageViewportController, SuspendContentFlag suspendContentFlag)
: m_controller(PageViewportController)
{
m_controller->m_activeDeferrerCount++;
// There is no need to suspend content for immediate updates
// only during animations or longer gestures.
if (suspendContentFlag == DeferUpdateAndSuspendContent)
m_controller->suspendContent();
}
ViewportUpdateDeferrer::~ViewportUpdateDeferrer()
{
if (--(m_controller->m_activeDeferrerCount))
return;
m_controller->resumeContent();
// Make sure that tiles all around the viewport will be requested.
m_controller->syncVisibleContents();
}
PageViewportController::PageViewportController(WebKit::WebPageProxy* proxy, PageViewportControllerClient* client)
: m_webPageProxy(proxy)
, m_client(client)
, m_allowsUserScaling(false)
, m_minimumScaleToFit(1)
, m_activeDeferrerCount(0)
, m_hasSuspendedContent(false)
, m_hadUserInteraction(false)
, m_effectiveScale(1)
{
// Initializing Viewport Raw Attributes to avoid random negative scale factors
// if there is a race condition between the first layout and setting the viewport attributes for the first time.
m_rawAttributes.initialScale = 1;
m_rawAttributes.minimumScale = 1;
m_rawAttributes.maximumScale = 1;
m_rawAttributes.userScalable = m_allowsUserScaling;
ASSERT(m_client);
m_client->setController(this);
}
float PageViewportController::innerBoundedViewportScale(float viewportScale) const
{
return clampTo(viewportScale, toViewportScale(m_minimumScaleToFit), toViewportScale(m_rawAttributes.maximumScale));
}
float PageViewportController::outerBoundedViewportScale(float viewportScale) const
{
if (m_allowsUserScaling) {
// Bounded by [0.1, 10.0] like the viewport meta code in WebCore.
float hardMin = toViewportScale(std::max<float>(0.1, 0.5 * m_minimumScaleToFit));
float hardMax = toViewportScale(std::min<float>(10, 2 * m_rawAttributes.maximumScale));
return clampTo(viewportScale, hardMin, hardMax);
}
return innerBoundedViewportScale(viewportScale);
}
float PageViewportController::devicePixelRatio() const
{
return m_webPageProxy->deviceScaleFactor();
}
FloatPoint PageViewportController::clampViewportToContents(const WebCore::FloatPoint& viewportPos, float viewportScale)
{
const float horizontalRange = std::max(0.f, m_contentsSize.width() - m_viewportSize.width() / viewportScale);
const float verticalRange = std::max(0.f, m_contentsSize.height() - m_viewportSize.height() / viewportScale);
return FloatPoint(clampTo(viewportPos.x(), 0.f, horizontalRange), clampTo(viewportPos.y(), 0.f, verticalRange));
}
void PageViewportController::didCommitLoad()
{
// Do not count the previous committed page contents as covered.
m_lastFrameCoveredRect = FloatRect();
// Reset the position to the top, page/history scroll requests may override this before we re-enable rendering.
applyPositionAfterRenderingContents(FloatPoint());
}
void PageViewportController::didChangeContentsSize(const IntSize& newSize)
{
m_contentsSize = newSize;
updateMinimumScaleToFit();
}
void PageViewportController::didRenderFrame(const IntSize& contentsSize, const IntRect& coveredRect)
{
// Only update the viewport's contents dimensions along with its render.
m_client->didChangeContentsSize(contentsSize);
m_lastFrameCoveredRect = coveredRect;
// Apply any scale or scroll position we locked to be set on the viewport
// only when there is something to display there. The scale goes first to
// avoid offsetting our deferred position by scaling at the viewport center.
// All position and scale changes resulting from a web process event should
// go through here to be applied on the viewport to avoid showing incomplete
// tiles to the user during a few milliseconds.
ViewportUpdateDeferrer guard(this);
if (m_effectiveScaleIsLocked) {
m_client->setContentsScale(m_effectiveScale, false);
m_effectiveScaleIsLocked = false;
}
if (m_viewportPosIsLocked) {
FloatPoint clampedPos = clampViewportToContents(m_viewportPos, m_effectiveScale);
// There might be rendered frames not covering our requested position yet, wait for it.
if (FloatRect(clampedPos, viewportSizeInContentsCoordinates()).intersects(coveredRect)) {
m_client->setViewportPosition(clampedPos);
m_viewportPosIsLocked = false;
}
}
}
void PageViewportController::pageTransitionViewportReady()
{
if (!m_rawAttributes.layoutSize.isEmpty()) {
m_hadUserInteraction = false;
applyScaleAfterRenderingContents(innerBoundedViewportScale(toViewportScale(m_rawAttributes.initialScale)));
}
// At this point we should already have received the first viewport arguments and the requested scroll
// position for the newly loaded page and sent our reactions to the web process. It's now safe to tell
// the web process to start rendering the new page contents and possibly re-use the current tiles.
// This assumes that all messages have been handled in order and that nothing has been pushed back on the event loop.
m_webPageProxy->commitPageTransitionViewport();
}
void PageViewportController::pageDidRequestScroll(const IntPoint& cssPosition)
{
// Ignore the request if suspended. Can only happen due to delay in event delivery.
if (m_activeDeferrerCount)
return;
FloatRect endVisibleContentRect(clampViewportToContents(cssPosition, m_effectiveScale), viewportSizeInContentsCoordinates());
if (m_lastFrameCoveredRect.intersects(endVisibleContentRect))
m_client->setViewportPosition(endVisibleContentRect.location());
else
// Keep the unclamped position in case the contents size is changed later on.
applyPositionAfterRenderingContents(cssPosition);
}
void PageViewportController::didChangeViewportSize(const FloatSize& newSize)
{
if (newSize.isEmpty())
return;
m_viewportSize = newSize;
// Let the WebProcess know about the new viewport size, so that
// it can resize the content accordingly.
m_webPageProxy->setViewportSize(roundedIntSize(newSize));
syncVisibleContents();
}
void PageViewportController::didChangeContentsVisibility(const FloatPoint& viewportPos, float viewportScale, const FloatPoint& trajectoryVector)
{
if (!m_viewportPosIsLocked)
m_viewportPos = viewportPos;
if (!m_effectiveScaleIsLocked)
m_effectiveScale = viewportScale;
syncVisibleContents(trajectoryVector);
}
void PageViewportController::syncVisibleContents(const FloatPoint& trajectoryVector)
{
DrawingAreaProxy* const drawingArea = m_webPageProxy->drawingArea();
if (!drawingArea || m_viewportSize.isEmpty() || m_contentsSize.isEmpty())
return;
FloatRect visibleContentsRect(clampViewportToContents(m_viewportPos, m_effectiveScale), viewportSizeInContentsCoordinates());
visibleContentsRect.intersect(FloatRect(FloatPoint::zero(), m_contentsSize));
drawingArea->setVisibleContentsRect(visibleContentsRect, m_effectiveScale, trajectoryVector);
m_client->didChangeVisibleContents();
}
void PageViewportController::didChangeViewportAttributes(const WebCore::ViewportAttributes& newAttributes)
{
if (newAttributes.layoutSize.isEmpty())
return;
m_rawAttributes = newAttributes;
WebCore::restrictScaleFactorToInitialScaleIfNotUserScalable(m_rawAttributes);
m_allowsUserScaling = !!m_rawAttributes.userScalable;
updateMinimumScaleToFit();
m_client->didChangeViewportAttributes();
}
WebCore::FloatSize PageViewportController::viewportSizeInContentsCoordinates() const
{
return WebCore::FloatSize(m_viewportSize.width() / m_effectiveScale, m_viewportSize.height() / m_effectiveScale);
}
void PageViewportController::suspendContent()
{
if (m_hasSuspendedContent)
return;
m_hasSuspendedContent = true;
m_webPageProxy->suspendActiveDOMObjectsAndAnimations();
}
void PageViewportController::resumeContent()
{
m_client->didResumeContent();
if (!m_hasSuspendedContent)
return;
m_hasSuspendedContent = false;
m_webPageProxy->resumeActiveDOMObjectsAndAnimations();
}
void PageViewportController::applyScaleAfterRenderingContents(float scale)
{
m_effectiveScale = scale;
m_effectiveScaleIsLocked = true;
syncVisibleContents();
}
void PageViewportController::applyPositionAfterRenderingContents(const FloatPoint& pos)
{
m_viewportPos = pos;
m_viewportPosIsLocked = true;
syncVisibleContents();
}
void PageViewportController::updateMinimumScaleToFit()
{
if (m_viewportSize.isEmpty())
return;
float minimumScale = WebCore::computeMinimumScaleFactorForContentContained(m_rawAttributes, WebCore::roundedIntSize(m_viewportSize), WebCore::roundedIntSize(m_contentsSize));
if (!fuzzyCompare(minimumScale, m_minimumScaleToFit, 0.001)) {
m_minimumScaleToFit = minimumScale;
if (!m_hadUserInteraction && !hasSuspendedContent())
applyScaleAfterRenderingContents(toViewportScale(minimumScale));
m_client->didChangeViewportAttributes();
}
}
} // namespace WebKit