/*
* Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
*
* 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.
*/
#include "config.h"
#include "TiledDrawingAreaProxy.h"
#if ENABLE(TILED_BACKING_STORE)
#include "DrawingAreaMessageKinds.h"
#include "DrawingAreaProxyMessageKinds.h"
#include "MessageID.h"
#include "UpdateChunk.h"
#include "WebCoreArgumentCoders.h"
#include "WebPageProxy.h"
#include "WebProcessProxy.h"
using namespace WebCore;
namespace WebKit {
static const int defaultTileWidth = 1024;
static const int defaultTileHeight = 1024;
PassOwnPtr<TiledDrawingAreaProxy> TiledDrawingAreaProxy::create(PlatformWebView* webView, WebPageProxy* webPageProxy)
{
return adoptPtr(new TiledDrawingAreaProxy(webView, webPageProxy));
}
TiledDrawingAreaProxy::TiledDrawingAreaProxy(PlatformWebView* webView, WebPageProxy* webPageProxy)
: DrawingAreaProxy(DrawingAreaTypeTiled, webPageProxy)
, m_isWaitingForDidSetFrameNotification(false)
, m_isVisible(true)
, m_webView(webView)
, m_tileBufferUpdateTimer(RunLoop::main(), this, &TiledDrawingAreaProxy::tileBufferUpdateTimerFired)
, m_tileCreationTimer(RunLoop::main(), this, &TiledDrawingAreaProxy::tileCreationTimerFired)
, m_tileSize(defaultTileWidth, defaultTileHeight)
, m_tileCreationDelay(0.01)
, m_keepAreaMultiplier(2.5, 4.5)
, m_coverAreaMultiplier(2, 3)
, m_contentsScale(1)
{
}
TiledDrawingAreaProxy::~TiledDrawingAreaProxy()
{
}
void TiledDrawingAreaProxy::sizeDidChange()
{
WebPageProxy* page = this->page();
if (!page || !page->isValid())
return;
if (m_size.isEmpty())
return;
m_viewSize = m_size;
m_lastSetViewSize = m_size;
if (m_isWaitingForDidSetFrameNotification)
return;
m_isWaitingForDidSetFrameNotification = true;
page->process()->responsivenessTimer()->start();
page->process()->deprecatedSend(DrawingAreaLegacyMessage::SetSize, page->pageID(), CoreIPC::In(m_size));
}
void TiledDrawingAreaProxy::setPageIsVisible(bool isVisible)
{
WebPageProxy* page = this->page();
if (isVisible == m_isVisible)
return;
m_isVisible = isVisible;
if (!page || !page->isValid())
return;
if (!m_isVisible) {
// Tell the web process that it doesn't need to paint anything for now.
page->process()->deprecatedSend(DrawingAreaLegacyMessage::SuspendPainting, page->pageID(), CoreIPC::In());
return;
}
// The page is now visible.
page->process()->deprecatedSend(DrawingAreaLegacyMessage::ResumePainting, page->pageID(), CoreIPC::In());
// FIXME: We should request a full repaint here if needed.
}
void TiledDrawingAreaProxy::didSetSize(const IntSize& viewSize)
{
ASSERT(m_isWaitingForDidSetFrameNotification);
m_isWaitingForDidSetFrameNotification = false;
if (viewSize != m_lastSetViewSize)
setSize(m_lastSetViewSize, IntSize());
WebPageProxy* page = this->page();
page->process()->responsivenessTimer()->stop();
}
void TiledDrawingAreaProxy::didReceiveMessage(CoreIPC::Connection*, CoreIPC::MessageID messageID, CoreIPC::ArgumentDecoder* arguments)
{
switch (messageID.get<DrawingAreaProxyLegacyMessage::Kind>()) {
case DrawingAreaProxyLegacyMessage::TileUpdated: {
int tileID;
UpdateChunk updateChunk;
float scale;
unsigned pendingUpdateCount;
if (!arguments->decode(CoreIPC::Out(tileID, updateChunk, scale, pendingUpdateCount)))
return;
TiledDrawingAreaTile* tile = m_tilesByID.get(tileID);
ASSERT(!tile || tile->ID() == tileID);
if (tile)
tile->updateFromChunk(&updateChunk, scale);
tileBufferUpdateComplete();
break;
}
case DrawingAreaProxyLegacyMessage::DidSetSize: {
IntSize size;
if (!arguments->decode(CoreIPC::Out(size)))
return;
didSetSize(size);
break;
}
case DrawingAreaProxyLegacyMessage::Invalidate: {
IntRect rect;
if (!arguments->decode(CoreIPC::Out(rect)))
return;
invalidate(rect);
break;
}
case DrawingAreaProxyLegacyMessage::AllTileUpdatesProcessed: {
tileBufferUpdateComplete();
break;
}
case DrawingAreaProxyLegacyMessage::SnapshotTaken: {
UpdateChunk chunk;
if (!arguments->decode(CoreIPC::Out(chunk)))
return;
snapshotTaken(chunk);
break;
}
default:
ASSERT_NOT_REACHED();
}
}
void TiledDrawingAreaProxy::requestTileUpdate(int tileID, const IntRect& dirtyRect)
{
page()->process()->connection()->deprecatedSend(DrawingAreaLegacyMessage::RequestTileUpdate, page()->pageID(), CoreIPC::In(tileID, dirtyRect, contentsScale()));
}
void TiledDrawingAreaProxy::waitUntilUpdatesComplete()
{
while (hasPendingUpdates()) {
int tileID;
UpdateChunk updateChunk;
float scale;
unsigned pendingUpdateCount;
static const double tileUpdateTimeout = 10.0;
OwnPtr<CoreIPC::ArgumentDecoder> arguments = page()->process()->connection()->deprecatedWaitFor(DrawingAreaProxyLegacyMessage::TileUpdated, page()->pageID(), tileUpdateTimeout);
if (!arguments)
break;
if (!arguments->decode(CoreIPC::Out(tileID, updateChunk, scale, pendingUpdateCount)))
break;
TiledDrawingAreaTile* tile = m_tilesByID.get(tileID);
ASSERT(!tile || tile->ID() == tileID);
if (tile)
tile->updateFromChunk(&updateChunk, scale);
}
tileBufferUpdateComplete();
}
PassRefPtr<TiledDrawingAreaTile> TiledDrawingAreaProxy::createTile(const TiledDrawingAreaTile::Coordinate& coordinate)
{
RefPtr<TiledDrawingAreaTile> tile = TiledDrawingAreaTile::create(this, coordinate);
setTile(coordinate, tile);
return tile;
}
void TiledDrawingAreaProxy::setTileSize(const IntSize& size)
{
if (m_tileSize == size)
return;
m_tileSize = size;
removeAllTiles();
startTileCreationTimer();
}
void TiledDrawingAreaProxy::setTileCreationDelay(double delay)
{
m_tileCreationDelay = delay;
}
void TiledDrawingAreaProxy::setKeepAndCoverAreaMultipliers(const FloatSize& keepMultiplier, const FloatSize& coverMultiplier)
{
m_keepAreaMultiplier = keepMultiplier;
m_coverAreaMultiplier = coverMultiplier;
startTileCreationTimer();
}
void TiledDrawingAreaProxy::takeSnapshot(const IntSize& size, const IntRect& contentsRect)
{
WebPageProxy* page = this->page();
page->process()->deprecatedSend(DrawingAreaLegacyMessage::TakeSnapshot, page->pageID(), CoreIPC::Out(size, contentsRect));
}
void TiledDrawingAreaProxy::invalidate(const IntRect& contentsDirtyRect)
{
IntRect dirtyRect(mapFromContents(contentsDirtyRect));
TiledDrawingAreaTile::Coordinate topLeft = tileCoordinateForPoint(dirtyRect.location());
TiledDrawingAreaTile::Coordinate bottomRight = tileCoordinateForPoint(IntPoint(dirtyRect.maxX(), dirtyRect.maxY()));
IntRect coverRect = calculateCoverRect(m_previousVisibleRect);
Vector<TiledDrawingAreaTile::Coordinate> tilesToRemove;
for (unsigned yCoordinate = topLeft.y(); yCoordinate <= bottomRight.y(); ++yCoordinate) {
for (unsigned xCoordinate = topLeft.x(); xCoordinate <= bottomRight.x(); ++xCoordinate) {
RefPtr<TiledDrawingAreaTile> currentTile = tileAt(TiledDrawingAreaTile::Coordinate(xCoordinate, yCoordinate));
if (!currentTile)
continue;
if (!currentTile->rect().intersects(dirtyRect))
continue;
// If a tile outside out current cover rect gets invalidated, just drop it instead of updating.
if (!currentTile->rect().intersects(coverRect)) {
tilesToRemove.append(currentTile->coordinate());
continue;
}
currentTile->invalidate(dirtyRect);
}
}
unsigned removeCount = tilesToRemove.size();
for (unsigned n = 0; n < removeCount; ++n)
removeTile(tilesToRemove[n]);
startTileBufferUpdateTimer();
}
void TiledDrawingAreaProxy::updateTileBuffers()
{
Vector<RefPtr<TiledDrawingAreaTile> > newDirtyTiles;
TileMap::iterator end = m_tiles.end();
for (TileMap::iterator it = m_tiles.begin(); it != end; ++it) {
RefPtr<TiledDrawingAreaTile>& current = it->second;
if (!current->isDirty())
continue;
newDirtyTiles.append(it->second);
}
if (newDirtyTiles.isEmpty())
return;
unsigned size = newDirtyTiles.size();
for (unsigned n = 0; n < size; ++n)
newDirtyTiles[n]->updateBackBuffer();
}
void TiledDrawingAreaProxy::tileBufferUpdateComplete()
{
// Bail out if all tile back buffers have not been updated.
Vector<TiledDrawingAreaTile*> tilesToFlip;
TileMap::iterator end = m_tiles.end();
for (TileMap::iterator it = m_tiles.begin(); it != end; ++it) {
RefPtr<TiledDrawingAreaTile>& current = it->second;
if (current->isReadyToPaint() && (current->isDirty() || current->hasBackBufferUpdatePending()))
return;
if (current->hasReadyBackBuffer())
tilesToFlip.append(current.get());
}
// Everything done, move back buffers to front.
Vector<IntRect> paintedArea;
unsigned size = tilesToFlip.size();
for (unsigned n = 0; n < size; ++n) {
TiledDrawingAreaTile* tile = tilesToFlip[n];
tile->swapBackBufferToFront();
// FIXME: should not request system repaint for the full tile.
paintedArea.append(mapToContents(tile->rect()));
}
if (size)
updateWebView(paintedArea);
m_tileCreationTimer.startOneShot(0);
}
bool TiledDrawingAreaProxy::paint(const IntRect& rect, PlatformDrawingContext context)
{
if (m_isWaitingForDidSetFrameNotification) {
WebPageProxy* page = this->page();
if (!page->isValid())
return false;
if (page->process()->isLaunching())
return false;
}
adjustVisibleRect();
GraphicsContext gc(context);
gc.save();
// Assumes the backing store is painted with the scale transform applied.
// Since tile content is already scaled, first revert the scaling from the painter.
gc.scale(FloatSize(1 / m_contentsScale, 1 / m_contentsScale));
IntRect dirtyRect = mapFromContents(rect);
TiledDrawingAreaTile::Coordinate topLeft = tileCoordinateForPoint(dirtyRect.location());
TiledDrawingAreaTile::Coordinate bottomRight = tileCoordinateForPoint(IntPoint(dirtyRect.maxX(), dirtyRect.maxY()));
for (unsigned yCoordinate = topLeft.y(); yCoordinate <= bottomRight.y(); ++yCoordinate) {
for (unsigned xCoordinate = topLeft.x(); xCoordinate <= bottomRight.x(); ++xCoordinate) {
TiledDrawingAreaTile::Coordinate currentCoordinate(xCoordinate, yCoordinate);
RefPtr<TiledDrawingAreaTile> currentTile = tileAt(currentCoordinate);
if (currentTile && currentTile->isReadyToPaint())
currentTile->paint(&gc, dirtyRect);
}
}
gc.restore();
return true;
}
void TiledDrawingAreaProxy::adjustVisibleRect()
{
IntRect visibleRect = mapFromContents(webViewVisibleRect());
if (m_previousVisibleRect == visibleRect)
return;
m_previousVisibleRect = visibleRect;
startTileCreationTimer();
}
void TiledDrawingAreaProxy::setContentsScale(float scale)
{
if (m_contentsScale == scale)
return;
m_contentsScale = scale;
removeAllTiles();
createTiles();
}
void TiledDrawingAreaProxy::removeAllTiles()
{
Vector<RefPtr<TiledDrawingAreaTile> > tilesToRemove;
copyValuesToVector(m_tiles, tilesToRemove);
unsigned removeCount = tilesToRemove.size();
for (unsigned n = 0; n < removeCount; ++n)
removeTile(tilesToRemove[n]->coordinate());
}
double TiledDrawingAreaProxy::tileDistance(const IntRect& viewport, const TiledDrawingAreaTile::Coordinate& tileCoordinate)
{
if (viewport.intersects(tileRectForCoordinate(tileCoordinate)))
return 0;
IntPoint viewCenter = viewport.location() + IntSize(viewport.width() / 2, viewport.height() / 2);
TiledDrawingAreaTile::Coordinate centerCoordinate = tileCoordinateForPoint(viewCenter);
// Manhattan distance, biased so that vertical distances are shorter.
const double horizontalBias = 1.3;
return abs(centerCoordinate.y() - tileCoordinate.y()) + horizontalBias * abs(centerCoordinate.x() - tileCoordinate.x());
}
IntRect TiledDrawingAreaProxy::calculateKeepRect(const IntRect& visibleRect) const
{
IntRect result = visibleRect;
// Inflates to both sides, so divide inflate delta by 2
result.inflateX(visibleRect.width() * (m_keepAreaMultiplier.width() - 1) / 2);
result.inflateY(visibleRect.height() * (m_keepAreaMultiplier.height() - 1) / 2);
result.intersect(contentsRect());
return result;
}
IntRect TiledDrawingAreaProxy::calculateCoverRect(const IntRect& visibleRect) const
{
IntRect result = visibleRect;
// Inflates to both sides, so divide inflate delta by 2
result.inflateX(visibleRect.width() * (m_coverAreaMultiplier.width() - 1) / 2);
result.inflateY(visibleRect.height() * (m_coverAreaMultiplier.height() - 1) / 2);
result.intersect(contentsRect());
return result;
}
void TiledDrawingAreaProxy::createTiles()
{
IntRect visibleRect = mapFromContents(webViewVisibleRect());
m_previousVisibleRect = visibleRect;
if (visibleRect.isEmpty())
return;
// Resize tiles on edges in case the contents size has changed.
bool didResizeTiles = resizeEdgeTiles();
// Remove tiles outside out current maximum keep rect.
dropTilesOutsideRect(calculateKeepRect(visibleRect));
// Cover the cover rect with tiles.
IntRect coverRect = calculateCoverRect(visibleRect);
// Search for the tile position closest to the viewport center that does not yet contain a tile.
// Which position is considered the closest depends on the tileDistance function.
double shortestDistance = std::numeric_limits<double>::infinity();
Vector<TiledDrawingAreaTile::Coordinate> tilesToCreate;
unsigned requiredTileCount = 0;
bool hasVisibleCheckers = false;
TiledDrawingAreaTile::Coordinate topLeft = tileCoordinateForPoint(visibleRect.location());
TiledDrawingAreaTile::Coordinate bottomRight = tileCoordinateForPoint(IntPoint(visibleRect.maxX(), visibleRect.maxY()));
for (unsigned yCoordinate = topLeft.y(); yCoordinate <= bottomRight.y(); ++yCoordinate) {
for (unsigned xCoordinate = topLeft.x(); xCoordinate <= bottomRight.x(); ++xCoordinate) {
TiledDrawingAreaTile::Coordinate currentCoordinate(xCoordinate, yCoordinate);
// Distance is 0 for all currently visible tiles.
double distance = tileDistance(visibleRect, currentCoordinate);
RefPtr<TiledDrawingAreaTile> tile = tileAt(currentCoordinate);
if (!distance && (!tile || !tile->isReadyToPaint()))
hasVisibleCheckers = true;
if (tile)
continue;
++requiredTileCount;
if (distance > shortestDistance)
continue;
if (distance < shortestDistance) {
tilesToCreate.clear();
shortestDistance = distance;
}
tilesToCreate.append(currentCoordinate);
}
}
if (hasVisibleCheckers && shortestDistance > 0)
return;
// Now construct the tile(s).
unsigned tilesToCreateCount = tilesToCreate.size();
for (unsigned n = 0; n < tilesToCreateCount; ++n)
createTile(tilesToCreate[n]);
requiredTileCount -= tilesToCreateCount;
// Paint the content of the newly created tiles.
if (tilesToCreateCount || didResizeTiles)
updateTileBuffers();
// Keep creating tiles until the whole coverRect is covered.
if (requiredTileCount)
m_tileCreationTimer.startOneShot(m_tileCreationDelay);
}
bool TiledDrawingAreaProxy::resizeEdgeTiles()
{
IntRect contentsRect = this->contentsRect();
bool wasResized = false;
Vector<TiledDrawingAreaTile::Coordinate> tilesToRemove;
TileMap::iterator end = m_tiles.end();
for (TileMap::iterator it = m_tiles.begin(); it != end; ++it) {
TiledDrawingAreaTile::Coordinate tileCoordinate = it->second->coordinate();
IntRect tileRect = it->second->rect();
IntRect expectedTileRect = tileRectForCoordinate(tileCoordinate);
if (!contentsRect.contains(tileRect))
tilesToRemove.append(tileCoordinate);
else if (expectedTileRect != tileRect) {
it->second->resize(expectedTileRect.size());
wasResized = true;
}
}
unsigned removeCount = tilesToRemove.size();
for (unsigned n = 0; n < removeCount; ++n)
removeTile(tilesToRemove[n]);
return wasResized;
}
void TiledDrawingAreaProxy::dropTilesOutsideRect(const IntRect& keepRect)
{
FloatRect keepRectF = keepRect;
Vector<TiledDrawingAreaTile::Coordinate> toRemove;
TileMap::iterator end = m_tiles.end();
for (TileMap::iterator it = m_tiles.begin(); it != end; ++it) {
TiledDrawingAreaTile::Coordinate coordinate = it->second->coordinate();
FloatRect tileRect = it->second->rect();
if (!tileRect.intersects(keepRectF))
toRemove.append(coordinate);
}
unsigned removeCount = toRemove.size();
for (unsigned n = 0; n < removeCount; ++n)
removeTile(toRemove[n]);
}
PassRefPtr<TiledDrawingAreaTile> TiledDrawingAreaProxy::tileAt(const TiledDrawingAreaTile::Coordinate& coordinate) const
{
return m_tiles.get(coordinate);
}
void TiledDrawingAreaProxy::setTile(const TiledDrawingAreaTile::Coordinate& coordinate, RefPtr<TiledDrawingAreaTile> tile)
{
m_tiles.set(coordinate, tile);
m_tilesByID.set(tile->ID(), tile.get());
}
void TiledDrawingAreaProxy::removeTile(const TiledDrawingAreaTile::Coordinate& coordinate)
{
RefPtr<TiledDrawingAreaTile> tile = m_tiles.take(coordinate);
m_tilesByID.remove(tile->ID());
if (!tile->hasBackBufferUpdatePending())
return;
WebPageProxy* page = this->page();
page->process()->deprecatedSend(DrawingAreaLegacyMessage::CancelTileUpdate, page->pageID(), CoreIPC::In(tile->ID()));
}
IntRect TiledDrawingAreaProxy::mapToContents(const IntRect& rect) const
{
return enclosingIntRect(FloatRect(rect.x() / m_contentsScale,
rect.y() / m_contentsScale,
rect.width() / m_contentsScale,
rect.height() / m_contentsScale));
}
IntRect TiledDrawingAreaProxy::mapFromContents(const IntRect& rect) const
{
return enclosingIntRect(FloatRect(rect.x() * m_contentsScale,
rect.y() * m_contentsScale,
rect.width() * m_contentsScale,
rect.height() * m_contentsScale));
}
IntRect TiledDrawingAreaProxy::contentsRect() const
{
return mapFromContents(IntRect(IntPoint(0, 0), m_viewSize));
}
IntRect TiledDrawingAreaProxy::tileRectForCoordinate(const TiledDrawingAreaTile::Coordinate& coordinate) const
{
IntRect rect(coordinate.x() * m_tileSize.width(),
coordinate.y() * m_tileSize.height(),
m_tileSize.width(),
m_tileSize.height());
rect.intersect(contentsRect());
return rect;
}
TiledDrawingAreaTile::Coordinate TiledDrawingAreaProxy::tileCoordinateForPoint(const IntPoint& point) const
{
int x = point.x() / m_tileSize.width();
int y = point.y() / m_tileSize.height();
return TiledDrawingAreaTile::Coordinate(std::max(x, 0), std::max(y, 0));
}
void TiledDrawingAreaProxy::startTileBufferUpdateTimer()
{
if (m_tileBufferUpdateTimer.isActive())
return;
m_tileBufferUpdateTimer.startOneShot(0);
}
void TiledDrawingAreaProxy::tileBufferUpdateTimerFired()
{
updateTileBuffers();
}
void TiledDrawingAreaProxy::startTileCreationTimer()
{
if (m_tileCreationTimer.isActive())
return;
m_tileCreationTimer.startOneShot(0);
}
void TiledDrawingAreaProxy::tileCreationTimerFired()
{
createTiles();
}
bool TiledDrawingAreaProxy::hasPendingUpdates() const
{
TileMap::const_iterator end = m_tiles.end();
for (TileMap::const_iterator it = m_tiles.begin(); it != end; ++it) {
const RefPtr<TiledDrawingAreaTile>& current = it->second;
if (current->hasBackBufferUpdatePending())
return true;
}
return false;
}
} // namespace WebKit
#endif