1/*
2 * Copyright (C) 2011-2014 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#include "config.h"
27#include "TileController.h"
28
29#include "IntRect.h"
30#include "PlatformCALayer.h"
31#include "Region.h"
32#include "TileCoverageMap.h"
33#include "TileGrid.h"
34#include <utility>
35#include <wtf/MainThread.h>
36
37#if PLATFORM(IOS)
38#include "TileControllerMemoryHandlerIOS.h"
39#endif
40
41namespace WebCore {
42
43PassOwnPtr<TileController> TileController::create(PlatformCALayer* rootPlatformLayer)
44{
45    return adoptPtr(new TileController(rootPlatformLayer));
46}
47
48TileController::TileController(PlatformCALayer* rootPlatformLayer)
49    : m_tileCacheLayer(rootPlatformLayer)
50    , m_tileGrid(std::make_unique<TileGrid>(*this))
51    , m_tileSize(defaultTileWidth, defaultTileHeight)
52    , m_tileRevalidationTimer(this, &TileController::tileRevalidationTimerFired)
53    , m_zoomedOutContentsScale(0)
54    , m_deviceScaleFactor(owningGraphicsLayer()->platformCALayerDeviceScaleFactor())
55    , m_tileCoverage(CoverageForVisibleArea)
56    , m_marginTop(0)
57    , m_marginBottom(0)
58    , m_marginLeft(0)
59    , m_marginRight(0)
60    , m_isInWindow(false)
61    , m_scrollingPerformanceLoggingEnabled(false)
62    , m_unparentsOffscreenTiles(false)
63    , m_acceleratesDrawing(false)
64    , m_tilesAreOpaque(false)
65    , m_hasTilesWithTemporaryScaleFactor(false)
66    , m_tileDebugBorderWidth(0)
67    , m_indicatorMode(AsyncScrollingIndication)
68    , m_topContentInset(0)
69{
70}
71
72TileController::~TileController()
73{
74    ASSERT(isMainThread());
75
76#if PLATFORM(IOS)
77    tileControllerMemoryHandler().removeTileController(this);
78#endif
79}
80
81void TileController::tileCacheLayerBoundsChanged()
82{
83    ASSERT(owningGraphicsLayer()->isCommittingChanges());
84    setNeedsRevalidateTiles();
85}
86
87void TileController::setNeedsDisplay()
88{
89    tileGrid().setNeedsDisplay();
90    m_zoomedOutTileGrid = nullptr;
91}
92
93void TileController::setNeedsDisplayInRect(const IntRect& rect)
94{
95    tileGrid().setNeedsDisplayInRect(rect);
96    if (m_zoomedOutTileGrid)
97        m_zoomedOutTileGrid->dropTilesInRect(rect);
98}
99
100void TileController::setContentsScale(float scale)
101{
102    ASSERT(owningGraphicsLayer()->isCommittingChanges());
103
104    float deviceScaleFactor = owningGraphicsLayer()->platformCALayerDeviceScaleFactor();
105    // The scale we get is the product of the page scale factor and device scale factor.
106    // Divide by the device scale factor so we'll get the page scale factor.
107    scale /= deviceScaleFactor;
108
109    if (tileGrid().scale() == scale && m_deviceScaleFactor == deviceScaleFactor && !m_hasTilesWithTemporaryScaleFactor)
110        return;
111
112    m_hasTilesWithTemporaryScaleFactor = false;
113    m_deviceScaleFactor = deviceScaleFactor;
114
115    if (m_coverageMap)
116        m_coverageMap->setDeviceScaleFactor(deviceScaleFactor);
117
118    if (m_zoomedOutTileGrid && m_zoomedOutTileGrid->scale() == scale) {
119        m_tileGrid = WTF::move(m_zoomedOutTileGrid);
120        m_tileGrid->revalidateTiles(0);
121        return;
122    }
123
124    if (m_zoomedOutContentsScale && m_zoomedOutContentsScale == tileGrid().scale() && tileGrid().scale() != scale && !m_hasTilesWithTemporaryScaleFactor) {
125        m_zoomedOutTileGrid = WTF::move(m_tileGrid);
126        m_tileGrid = std::make_unique<TileGrid>(*this);
127    }
128
129    tileGrid().setScale(scale);
130    tileGrid().setNeedsDisplay();
131}
132
133float TileController::contentsScale() const
134{
135    return tileGrid().scale() * m_deviceScaleFactor;
136}
137
138float TileController::zoomedOutContentsScale() const
139{
140    return m_zoomedOutContentsScale * m_deviceScaleFactor;
141}
142
143void TileController::setZoomedOutContentsScale(float scale)
144{
145    ASSERT(owningGraphicsLayer()->isCommittingChanges());
146
147    float deviceScaleFactor = owningGraphicsLayer()->platformCALayerDeviceScaleFactor();
148    scale /= deviceScaleFactor;
149
150    if (m_zoomedOutContentsScale == scale)
151        return;
152    m_zoomedOutContentsScale = scale;
153
154    if (m_zoomedOutTileGrid && m_zoomedOutTileGrid->scale() != m_zoomedOutContentsScale)
155        m_zoomedOutTileGrid = nullptr;
156}
157
158void TileController::setAcceleratesDrawing(bool acceleratesDrawing)
159{
160    if (m_acceleratesDrawing == acceleratesDrawing)
161        return;
162    m_acceleratesDrawing = acceleratesDrawing;
163
164    tileGrid().updateTileLayerProperties();
165}
166
167void TileController::setTilesOpaque(bool opaque)
168{
169    if (opaque == m_tilesAreOpaque)
170        return;
171    m_tilesAreOpaque = opaque;
172
173    tileGrid().updateTileLayerProperties();
174}
175
176void TileController::setVisibleRect(const FloatRect& visibleRect)
177{
178    ASSERT(owningGraphicsLayer()->isCommittingChanges());
179    if (m_visibleRect == visibleRect)
180        return;
181
182    m_visibleRect = visibleRect;
183    setNeedsRevalidateTiles();
184}
185
186bool TileController::tilesWouldChangeForVisibleRect(const FloatRect& newVisibleRect) const
187{
188    if (bounds().isEmpty())
189        return false;
190    return tileGrid().tilesWouldChangeForVisibleRect(newVisibleRect, m_visibleRect);
191}
192
193void TileController::setTopContentInset(float topContentInset)
194{
195    m_topContentInset = topContentInset;
196    setTiledScrollingIndicatorPosition(FloatPoint(0, m_topContentInset));
197}
198
199void TileController::setTiledScrollingIndicatorPosition(const FloatPoint& position)
200{
201    if (!m_coverageMap)
202        return;
203    m_coverageMap->setPosition(position);
204    m_coverageMap->update();
205}
206
207void TileController::prepopulateRect(const FloatRect& rect)
208{
209    if (tileGrid().prepopulateRect(rect))
210        setNeedsRevalidateTiles();
211}
212
213void TileController::setIsInWindow(bool isInWindow)
214{
215    if (m_isInWindow == isInWindow)
216        return;
217
218    m_isInWindow = isInWindow;
219
220    if (m_isInWindow)
221        setNeedsRevalidateTiles();
222    else {
223        const double tileRevalidationTimeout = 4;
224        scheduleTileRevalidation(tileRevalidationTimeout);
225    }
226}
227
228void TileController::setTileCoverage(TileCoverage coverage)
229{
230    if (coverage == m_tileCoverage)
231        return;
232
233    m_tileCoverage = coverage;
234    setNeedsRevalidateTiles();
235}
236
237void TileController::revalidateTiles()
238{
239    ASSERT(owningGraphicsLayer()->isCommittingChanges());
240    tileGrid().revalidateTiles(0);
241    m_visibleRectAtLastRevalidate = m_visibleRect;
242}
243
244void TileController::forceRepaint()
245{
246    setNeedsDisplay();
247}
248
249void TileController::setTileDebugBorderWidth(float borderWidth)
250{
251    if (m_tileDebugBorderWidth == borderWidth)
252        return;
253    m_tileDebugBorderWidth = borderWidth;
254
255    tileGrid().updateTileLayerProperties();
256}
257
258void TileController::setTileDebugBorderColor(Color borderColor)
259{
260    if (m_tileDebugBorderColor == borderColor)
261        return;
262    m_tileDebugBorderColor = borderColor;
263
264    tileGrid().updateTileLayerProperties();
265}
266
267IntRect TileController::bounds() const
268{
269    IntPoint boundsOriginIncludingMargin(-leftMarginWidth(), -topMarginHeight());
270    IntSize boundsSizeIncludingMargin = expandedIntSize(m_tileCacheLayer->bounds().size());
271    boundsSizeIncludingMargin.expand(leftMarginWidth() + rightMarginWidth(), topMarginHeight() + bottomMarginHeight());
272
273    return IntRect(boundsOriginIncludingMargin, boundsSizeIncludingMargin);
274}
275
276IntRect TileController::boundsWithoutMargin() const
277{
278    return IntRect(IntPoint(), expandedIntSize(m_tileCacheLayer->bounds().size()));
279}
280
281IntRect TileController::boundsAtLastRevalidateWithoutMargin() const
282{
283    IntRect boundsWithoutMargin = IntRect(IntPoint(), m_boundsAtLastRevalidate.size());
284    boundsWithoutMargin.contract(IntSize(leftMarginWidth() + rightMarginWidth(), topMarginHeight() + bottomMarginHeight()));
285    return boundsWithoutMargin;
286}
287
288FloatRect TileController::computeTileCoverageRect(const FloatRect& previousVisibleRect, const FloatRect& visibleRect) const
289{
290    // If the page is not in a window (for example if it's in a background tab), we limit the tile coverage rect to the visible rect.
291    if (!m_isInWindow)
292        return visibleRect;
293
294    // FIXME: look at how far the document can scroll in each dimension.
295    float coverageHorizontalSize = visibleRect.width();
296    float coverageVerticalSize = visibleRect.height();
297
298#if PLATFORM(IOS)
299    UNUSED_PARAM(previousVisibleRect);
300    return visibleRect;
301#else
302    bool largeVisibleRectChange = !previousVisibleRect.isEmpty() && !visibleRect.intersects(previousVisibleRect);
303
304    // Inflate the coverage rect so that it covers 2x of the visible width and 3x of the visible height.
305    // These values were chosen because it's more common to have tall pages and to scroll vertically,
306    // so we keep more tiles above and below the current area.
307
308    if (m_tileCoverage & CoverageForHorizontalScrolling && !largeVisibleRectChange)
309        coverageHorizontalSize *= 2;
310
311    if (m_tileCoverage & CoverageForVerticalScrolling && !largeVisibleRectChange)
312        coverageVerticalSize *= 3;
313#endif
314    coverageVerticalSize += topMarginHeight() + bottomMarginHeight();
315    coverageHorizontalSize += leftMarginWidth() + rightMarginWidth();
316
317    FloatRect coverageBounds = bounds();
318    float coverageLeft = visibleRect.x() - (coverageHorizontalSize - visibleRect.width()) / 2;
319    coverageLeft = std::min(coverageLeft, coverageBounds.maxX() - coverageHorizontalSize);
320    coverageLeft = std::max(coverageLeft, coverageBounds.x());
321
322    float coverageTop = visibleRect.y() - (coverageVerticalSize - visibleRect.height()) / 2;
323    coverageTop = std::min(coverageTop, coverageBounds.maxY() - coverageVerticalSize);
324    coverageTop = std::max(coverageTop, coverageBounds.y());
325
326    return FloatRect(coverageLeft, coverageTop, coverageHorizontalSize, coverageVerticalSize);
327}
328
329void TileController::scheduleTileRevalidation(double interval)
330{
331    if (m_tileRevalidationTimer.isActive() && m_tileRevalidationTimer.nextFireInterval() < interval)
332        return;
333
334    m_tileRevalidationTimer.startOneShot(interval);
335}
336
337bool TileController::shouldAggressivelyRetainTiles() const
338{
339    return owningGraphicsLayer()->platformCALayerShouldAggressivelyRetainTiles(m_tileCacheLayer);
340}
341
342bool TileController::shouldTemporarilyRetainTileCohorts() const
343{
344    return owningGraphicsLayer()->platformCALayerShouldTemporarilyRetainTileCohorts(m_tileCacheLayer);
345}
346
347void TileController::tileRevalidationTimerFired(Timer<TileController>*)
348{
349    if (!owningGraphicsLayer())
350        return;
351
352    if (m_isInWindow) {
353        setNeedsRevalidateTiles();
354        return;
355    }
356    // If we are not visible get rid of the zoomed-out tiles.
357    m_zoomedOutTileGrid = nullptr;
358
359    unsigned validationPolicy = (shouldAggressivelyRetainTiles() ? 0 : TileGrid::PruneSecondaryTiles) | TileGrid::UnparentAllTiles;
360
361    tileGrid().revalidateTiles(validationPolicy);
362}
363
364void TileController::didRevalidateTiles()
365{
366    m_visibleRectAtLastRevalidate = visibleRect();
367    m_boundsAtLastRevalidate = bounds();
368
369    updateTileCoverageMap();
370}
371
372unsigned TileController::blankPixelCount() const
373{
374    return tileGrid().blankPixelCount();
375}
376
377unsigned TileController::blankPixelCountForTiles(const PlatformLayerList& tiles, const FloatRect& visibleRect, const IntPoint& tileTranslation)
378{
379    Region paintedVisibleTiles;
380
381    for (PlatformLayerList::const_iterator it = tiles.begin(), end = tiles.end(); it != end; ++it) {
382        const PlatformLayer* tileLayer = it->get();
383
384        FloatRect visiblePart(CGRectOffset(PlatformCALayer::frameForLayer(tileLayer), tileTranslation.x(), tileTranslation.y()));
385        visiblePart.intersect(visibleRect);
386
387        if (!visiblePart.isEmpty())
388            paintedVisibleTiles.unite(enclosingIntRect(visiblePart));
389    }
390
391    Region uncoveredRegion(enclosingIntRect(visibleRect));
392    uncoveredRegion.subtract(paintedVisibleTiles);
393
394    return uncoveredRegion.totalArea();
395}
396
397void TileController::setNeedsRevalidateTiles()
398{
399    owningGraphicsLayer()->platformCALayerSetNeedsToRevalidateTiles();
400}
401
402void TileController::updateTileCoverageMap()
403{
404    if (m_coverageMap)
405        m_coverageMap->update();
406}
407
408IntRect TileController::tileGridExtent() const
409{
410    return tileGrid().extent();
411}
412
413double TileController::retainedTileBackingStoreMemory() const
414{
415    double bytes = tileGrid().retainedTileBackingStoreMemory();
416    if (m_zoomedOutTileGrid)
417        bytes += m_zoomedOutTileGrid->retainedTileBackingStoreMemory();
418    return bytes;
419}
420
421// Return the rect in layer coords, not tile coords.
422IntRect TileController::tileCoverageRect() const
423{
424    return tileGrid().tileCoverageRect();
425}
426
427PlatformCALayer* TileController::tiledScrollingIndicatorLayer()
428{
429    if (!m_coverageMap)
430        m_coverageMap = std::make_unique<TileCoverageMap>(*this);
431
432    return &m_coverageMap->layer();
433}
434
435void TileController::setScrollingModeIndication(ScrollingModeIndication scrollingMode)
436{
437    if (scrollingMode == m_indicatorMode)
438        return;
439
440    m_indicatorMode = scrollingMode;
441
442    updateTileCoverageMap();
443}
444
445void TileController::setTileMargins(int marginTop, int marginBottom, int marginLeft, int marginRight)
446{
447    m_marginTop = marginTop;
448    m_marginBottom = marginBottom;
449    m_marginLeft = marginLeft;
450    m_marginRight = marginRight;
451
452    setNeedsRevalidateTiles();
453}
454
455bool TileController::hasMargins() const
456{
457    return m_marginTop || m_marginBottom || m_marginLeft || m_marginRight;
458}
459
460bool TileController::hasHorizontalMargins() const
461{
462    return m_marginLeft || m_marginRight;
463}
464
465bool TileController::hasVerticalMargins() const
466{
467    return m_marginTop || m_marginBottom;
468}
469
470int TileController::topMarginHeight() const
471{
472    return m_marginTop / tileGrid().scale();
473}
474
475int TileController::bottomMarginHeight() const
476{
477    return m_marginBottom / tileGrid().scale();
478}
479
480int TileController::leftMarginWidth() const
481{
482    return m_marginLeft / tileGrid().scale();
483}
484
485int TileController::rightMarginWidth() const
486{
487    return m_marginRight / tileGrid().scale();
488}
489
490RefPtr<PlatformCALayer> TileController::createTileLayer(const IntRect& tileRect, TileGrid& grid)
491{
492    RefPtr<PlatformCALayer> layer = m_tileCacheLayer->createCompatibleLayerOrTakeFromPool(PlatformCALayer::LayerTypeTiledBackingTileLayer, &grid, tileRect.size());
493
494    layer->setAnchorPoint(FloatPoint3D());
495    layer->setPosition(tileRect.location());
496    layer->setBorderColor(m_tileDebugBorderColor);
497    layer->setBorderWidth(m_tileDebugBorderWidth);
498    layer->setEdgeAntialiasingMask(0);
499    layer->setOpaque(m_tilesAreOpaque);
500#ifndef NDEBUG
501    layer->setName("Tile");
502#endif
503
504    float temporaryScaleFactor = owningGraphicsLayer()->platformCALayerContentsScaleMultiplierForNewTiles(m_tileCacheLayer);
505    m_hasTilesWithTemporaryScaleFactor |= temporaryScaleFactor != 1;
506
507    layer->setContentsScale(m_deviceScaleFactor * temporaryScaleFactor);
508    layer->setAcceleratesDrawing(m_acceleratesDrawing);
509
510    layer->setNeedsDisplay();
511
512    return layer;
513}
514
515Vector<RefPtr<PlatformCALayer>> TileController::containerLayers()
516{
517    Vector<RefPtr<PlatformCALayer>> layerList;
518    if (m_zoomedOutTileGrid)
519        layerList.append(&m_zoomedOutTileGrid->containerLayer());
520    layerList.append(&tileGrid().containerLayer());
521    return layerList;
522}
523
524#if PLATFORM(IOS)
525unsigned TileController::numberOfUnparentedTiles() const
526{
527    unsigned count = tileGrid().numberOfUnparentedTiles();
528    if (m_zoomedOutTileGrid)
529        count += m_zoomedOutTileGrid->numberOfUnparentedTiles();
530    return count;
531}
532
533void TileController::removeUnparentedTilesNow()
534{
535    tileGrid().removeUnparentedTilesNow();
536    if (m_zoomedOutTileGrid)
537        m_zoomedOutTileGrid->removeUnparentedTilesNow();
538
539    updateTileCoverageMap();
540}
541#endif
542
543} // namespace WebCore
544