1/*
2 * Copyright (C) 2011, 2012, 2013 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 "TileController.h"
28
29#import "IntRect.h"
30#import "PlatformCALayer.h"
31#import "Region.h"
32#import "LayerPool.h"
33#import "WebLayer.h"
34#import "WebTiledBackingLayer.h"
35#import "WebTileLayer.h"
36#import <QuartzCore/QuartzCore.h>
37#import <wtf/MainThread.h>
38#import <WebCore/BlockExceptions.h>
39#import <utility>
40
41using namespace std;
42
43#if PLATFORM(IOS) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
44@interface CALayer (WebCALayerDetails)
45- (void)setAcceleratesDrawing:(BOOL)flag;
46@end
47#endif
48
49@interface WebTiledScrollingIndicatorLayer : CALayer {
50    WebCore::TileController* _tileController;
51    CALayer *_visibleRectFrameLayer; // Owned by being a sublayer.
52}
53@property (assign) WebCore::TileController* tileController;
54@property (assign) CALayer* visibleRectFrameLayer;
55@end
56
57@implementation WebTiledScrollingIndicatorLayer
58@synthesize tileController = _tileController;
59@synthesize visibleRectFrameLayer = _visibleRectFrameLayer;
60- (id)init
61{
62    if ((self = [super init])) {
63        [self setStyle:[NSDictionary dictionaryWithObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNull null], @"bounds", [NSNull null], @"position", [NSNull null], @"contents", nil] forKey:@"actions"]];
64
65        _visibleRectFrameLayer = [CALayer layer];
66        [_visibleRectFrameLayer setStyle:[NSDictionary dictionaryWithObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNull null], @"bounds", [NSNull null], @"position", [NSNull null], @"borderColor", nil] forKey:@"actions"]];
67        [self addSublayer:_visibleRectFrameLayer];
68        [_visibleRectFrameLayer setBorderColor:WebCore::cachedCGColor(WebCore::Color(255, 0, 0), WebCore::ColorSpaceDeviceRGB)];
69        [_visibleRectFrameLayer setBorderWidth:2];
70        return self;
71    }
72    return nil;
73}
74
75- (void)drawInContext:(CGContextRef)context
76{
77    if (_tileController)
78        _tileController->drawTileMapContents(context, [self bounds]);
79}
80@end
81
82namespace WebCore {
83
84enum TileValidationPolicyFlag {
85    PruneSecondaryTiles = 1 << 0,
86    UnparentAllTiles = 1 << 1
87};
88
89static const int defaultTileWidth = 512;
90static const int defaultTileHeight = 512;
91
92PassOwnPtr<TileController> TileController::create(WebTiledBackingLayer* tileCacheLayer)
93{
94    return adoptPtr(new TileController(tileCacheLayer));
95}
96
97TileController::TileController(WebTiledBackingLayer* tileCacheLayer)
98    : m_tileCacheLayer(tileCacheLayer)
99    , m_tileContainerLayer(adoptNS([[CALayer alloc] init]))
100    , m_tileSize(defaultTileWidth, defaultTileHeight)
101    , m_tileRevalidationTimer(this, &TileController::tileRevalidationTimerFired)
102    , m_cohortRemovalTimer(this, &TileController::cohortRemovalTimerFired)
103    , m_scale(1)
104    , m_deviceScaleFactor(1)
105    , m_tileCoverage(CoverageForVisibleArea)
106    , m_isInWindow(false)
107    , m_scrollingPerformanceLoggingEnabled(false)
108    , m_aggressivelyRetainsTiles(false)
109    , m_unparentsOffscreenTiles(false)
110    , m_acceleratesDrawing(false)
111    , m_tilesAreOpaque(false)
112    , m_clipsToExposedRect(false)
113    , m_tileDebugBorderWidth(0)
114    , m_indicatorMode(ThreadedScrollingIndication)
115{
116    [CATransaction begin];
117    [CATransaction setDisableActions:YES];
118    [m_tileCacheLayer addSublayer:m_tileContainerLayer.get()];
119#ifndef NDEBUG
120    [m_tileContainerLayer.get() setName:@"TileController Container Layer"];
121#endif
122    [CATransaction commit];
123}
124
125TileController::~TileController()
126{
127    ASSERT(isMainThread());
128
129    for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
130        const TileInfo& tileInfo = it->value;
131        [tileInfo.layer.get() setTileController:0];
132    }
133
134    if (m_tiledScrollingIndicatorLayer)
135        [m_tiledScrollingIndicatorLayer.get() setTileController:nil];
136}
137
138void TileController::tileCacheLayerBoundsChanged()
139{
140    if (m_tiles.isEmpty()) {
141        // We must revalidate immediately instead of using a timer when there are
142        // no tiles to avoid a flash when transitioning from one page to another.
143        revalidateTiles();
144        return;
145    }
146
147    scheduleTileRevalidation(0);
148}
149
150void TileController::setNeedsDisplay()
151{
152    for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
153        const TileInfo& tileInfo = it->value;
154        [tileInfo.layer.get() setNeedsDisplay];
155    }
156}
157
158void TileController::setNeedsDisplayInRect(const IntRect& rect)
159{
160    if (m_tiles.isEmpty())
161        return;
162
163    FloatRect scaledRect(rect);
164    scaledRect.scale(m_scale);
165    IntRect repaintRectInTileCoords(enclosingIntRect(scaledRect));
166
167    // For small invalidations, lookup the covered tiles.
168    if (repaintRectInTileCoords.height() < 2 * m_tileSize.height() && repaintRectInTileCoords.width() < 2 * m_tileSize.width()) {
169        TileIndex topLeft;
170        TileIndex bottomRight;
171        getTileIndexRangeForRect(repaintRectInTileCoords, topLeft, bottomRight);
172
173        for (int y = topLeft.y(); y <= bottomRight.y(); ++y) {
174            for (int x = topLeft.x(); x <= bottomRight.x(); ++x) {
175                TileIndex tileIndex(x, y);
176
177                TileMap::iterator it = m_tiles.find(tileIndex);
178                if (it != m_tiles.end())
179                    setTileNeedsDisplayInRect(tileIndex, it->value, repaintRectInTileCoords, m_primaryTileCoverageRect);
180            }
181        }
182        return;
183    }
184
185    for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it)
186        setTileNeedsDisplayInRect(it->key, it->value, repaintRectInTileCoords, m_primaryTileCoverageRect);
187}
188
189void TileController::setTileNeedsDisplayInRect(const TileIndex& tileIndex, TileInfo& tileInfo, const IntRect& repaintRectInTileCoords, const IntRect& coverageRectInTileCoords)
190{
191    WebTileLayer* tileLayer = tileInfo.layer.get();
192
193    IntRect tileRect = rectForTileIndex(tileIndex);
194    IntRect tileRepaintRect = tileRect;
195    tileRepaintRect.intersect(repaintRectInTileCoords);
196    if (tileRepaintRect.isEmpty())
197        return;
198
199    tileRepaintRect.moveBy(-tileRect.location());
200
201    // We could test for intersection with the visible rect. This would reduce painting yet more,
202    // but may make scrolling stale tiles into view more frequent.
203    if (tileRect.intersects(coverageRectInTileCoords)) {
204        [tileLayer setNeedsDisplayInRect:tileRepaintRect];
205
206        if (shouldShowRepaintCounters()) {
207            CGRect bounds = [tileLayer bounds];
208            CGRect indicatorRect = CGRectMake(bounds.origin.x, bounds.origin.y, 52, 27);
209            [tileLayer setNeedsDisplayInRect:indicatorRect];
210        }
211    } else
212        tileInfo.hasStaleContent = true;
213}
214
215
216void TileController::drawLayer(WebTileLayer *layer, CGContextRef context)
217{
218    PlatformCALayer* platformLayer = PlatformCALayer::platformCALayer(m_tileCacheLayer);
219    if (!platformLayer)
220        return;
221
222    CGContextSaveGState(context);
223
224    CGPoint layerOrigin = [layer frame].origin;
225    CGContextTranslateCTM(context, -layerOrigin.x, -layerOrigin.y);
226    CGContextScaleCTM(context, m_scale, m_scale);
227    drawLayerContents(context, layer, platformLayer);
228
229    CGContextRestoreGState(context);
230
231    drawRepaintCounter(layer, context);
232}
233
234void TileController::setScale(CGFloat scale)
235{
236    PlatformCALayer* platformLayer = PlatformCALayer::platformCALayer(m_tileCacheLayer);
237    float deviceScaleFactor = platformLayer->owner()->platformCALayerDeviceScaleFactor();
238
239    // The scale we get is the produce of the page scale factor and device scale factor.
240    // Divide by the device scale factor so we'll get the page scale factor.
241    scale /= deviceScaleFactor;
242
243    if (m_scale == scale && m_deviceScaleFactor == deviceScaleFactor)
244        return;
245
246#if PLATFORM(IOS) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
247    Vector<FloatRect> dirtyRects;
248
249    m_deviceScaleFactor = deviceScaleFactor;
250    m_scale = scale;
251    [m_tileContainerLayer.get() setTransform:CATransform3DMakeScale(1 / m_scale, 1 / m_scale, 1)];
252
253    revalidateTiles(PruneSecondaryTiles, PruneSecondaryTiles);
254
255    for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
256        const TileInfo& tileInfo = it->value;
257        [tileInfo.layer.get() setContentsScale:deviceScaleFactor];
258
259        IntRect tileRect = rectForTileIndex(it->key);
260        FloatRect scaledTileRect = tileRect;
261
262        scaledTileRect.scale(1 / m_scale);
263        dirtyRects.append(scaledTileRect);
264    }
265
266    platformLayer->owner()->platformCALayerDidCreateTiles(dirtyRects);
267#endif
268}
269
270void TileController::setAcceleratesDrawing(bool acceleratesDrawing)
271{
272#if PLATFORM(IOS) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
273    if (m_acceleratesDrawing == acceleratesDrawing)
274        return;
275
276    m_acceleratesDrawing = acceleratesDrawing;
277
278    for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
279        const TileInfo& tileInfo = it->value;
280        [tileInfo.layer.get() setAcceleratesDrawing:m_acceleratesDrawing];
281    }
282#else
283    UNUSED_PARAM(acceleratesDrawing);
284#endif
285}
286
287void TileController::setTilesOpaque(bool opaque)
288{
289    if (opaque == m_tilesAreOpaque)
290        return;
291
292    m_tilesAreOpaque = opaque;
293
294    for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
295        const TileInfo& tileInfo = it->value;
296        [tileInfo.layer.get() setOpaque:opaque];
297    }
298}
299
300void TileController::setVisibleRect(const FloatRect& visibleRect)
301{
302    if (m_visibleRect == visibleRect)
303        return;
304
305    m_visibleRect = visibleRect;
306    revalidateTiles();
307}
308
309bool TileController::tilesWouldChangeForVisibleRect(const FloatRect& newVisibleRect) const
310{
311    FloatRect visibleRect = newVisibleRect;
312
313    if (m_clipsToExposedRect)
314        visibleRect.intersect(m_exposedRect);
315
316    if (visibleRect.isEmpty() || bounds().isEmpty())
317        return false;
318
319    FloatRect currentTileCoverageRect = computeTileCoverageRect(m_visibleRect, newVisibleRect);
320    FloatRect scaledRect(currentTileCoverageRect);
321    scaledRect.scale(m_scale);
322    IntRect currentCoverageRectInTileCoords(enclosingIntRect(scaledRect));
323
324    IntSize newTileSize = tileSizeForCoverageRect(currentTileCoverageRect);
325    bool tileSizeChanged = newTileSize != m_tileSize;
326    if (tileSizeChanged)
327        return true;
328
329    TileIndex topLeft;
330    TileIndex bottomRight;
331    getTileIndexRangeForRect(currentCoverageRectInTileCoords, topLeft, bottomRight);
332
333    IntRect coverageRect = rectForTileIndex(topLeft);
334    coverageRect.unite(rectForTileIndex(bottomRight));
335    return coverageRect != m_primaryTileCoverageRect;
336}
337
338void TileController::setExposedRect(const FloatRect& exposedRect)
339{
340    if (m_exposedRect == exposedRect)
341        return;
342
343    m_exposedRect = exposedRect;
344    revalidateTiles();
345}
346
347void TileController::setClipsToExposedRect(bool clipsToExposedRect)
348{
349    if (m_clipsToExposedRect == clipsToExposedRect)
350        return;
351
352    m_clipsToExposedRect = clipsToExposedRect;
353
354    // Going from not clipping to clipping, we don't need to revalidate right away.
355    if (clipsToExposedRect)
356        revalidateTiles();
357}
358
359void TileController::prepopulateRect(const FloatRect& rect)
360{
361    ensureTilesForRect(rect);
362}
363
364void TileController::setIsInWindow(bool isInWindow)
365{
366    if (m_isInWindow == isInWindow)
367        return;
368
369    m_isInWindow = isInWindow;
370
371    if (m_isInWindow)
372        revalidateTiles();
373    else {
374        const double tileRevalidationTimeout = 4;
375        scheduleTileRevalidation(tileRevalidationTimeout);
376    }
377}
378
379void TileController::setTileCoverage(TileCoverage coverage)
380{
381    if (coverage == m_tileCoverage)
382        return;
383
384    m_tileCoverage = coverage;
385    scheduleTileRevalidation(0);
386}
387
388void TileController::forceRepaint()
389{
390    setNeedsDisplay();
391}
392
393void TileController::setTileDebugBorderWidth(float borderWidth)
394{
395    if (m_tileDebugBorderWidth == borderWidth)
396        return;
397
398    m_tileDebugBorderWidth = borderWidth;
399    for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
400        const TileInfo& tileInfo = it->value;
401        [tileInfo.layer.get() setBorderWidth:m_tileDebugBorderWidth];
402    }
403}
404
405void TileController::setTileDebugBorderColor(CGColorRef borderColor)
406{
407    if (m_tileDebugBorderColor == borderColor)
408        return;
409
410    m_tileDebugBorderColor = borderColor;
411    for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
412        const TileInfo& tileInfo = it->value;
413        [tileInfo.layer.get() setBorderColor:m_tileDebugBorderColor.get()];
414    }
415}
416
417IntRect TileController::bounds() const
418{
419    return IntRect(IntPoint(), IntSize([m_tileCacheLayer bounds].size));
420}
421
422IntRect TileController::rectForTileIndex(const TileIndex& tileIndex) const
423{
424    IntRect rect(tileIndex.x() * m_tileSize.width(), tileIndex.y() * m_tileSize.height(), m_tileSize.width(), m_tileSize.height());
425    IntRect scaledBounds(bounds());
426    scaledBounds.scale(m_scale);
427
428    rect.intersect(scaledBounds);
429
430    return rect;
431}
432
433void TileController::getTileIndexRangeForRect(const IntRect& rect, TileIndex& topLeft, TileIndex& bottomRight) const
434{
435    IntRect clampedRect = bounds();
436    clampedRect.scale(m_scale);
437    clampedRect.intersect(rect);
438
439    topLeft.setX(max(clampedRect.x() / m_tileSize.width(), 0));
440    topLeft.setY(max(clampedRect.y() / m_tileSize.height(), 0));
441
442    int bottomXRatio = ceil((float)clampedRect.maxX() / m_tileSize.width());
443    bottomRight.setX(max(bottomXRatio - 1, 0));
444
445    int bottomYRatio = ceil((float)clampedRect.maxY() / m_tileSize.height());
446    bottomRight.setY(max(bottomYRatio - 1, 0));
447}
448
449FloatRect TileController::computeTileCoverageRect(const FloatRect& previousVisibleRect, const FloatRect& currentVisibleRect) const
450{
451    FloatRect visibleRect = currentVisibleRect;
452
453    if (m_clipsToExposedRect)
454        visibleRect.intersect(m_exposedRect);
455
456    // 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.
457    // Furthermore, if the page can't have scrollbars (for example if its body element has overflow:hidden) it's very unlikely that the
458    // page will ever be scrolled so we limit the tile coverage rect as well.
459    if (!m_isInWindow || m_tileCoverage & CoverageForSlowScrolling)
460        return visibleRect;
461
462    bool largeVisibleRectChange = !previousVisibleRect.isEmpty() && !visibleRect.intersects(previousVisibleRect);
463
464    // FIXME: look at how far the document can scroll in each dimension.
465    float coverageHorizontalSize = visibleRect.width();
466    float coverageVerticalSize = visibleRect.height();
467
468    // Inflate the coverage rect so that it covers 2x of the visible width and 3x of the visible height.
469    // These values were chosen because it's more common to have tall pages and to scroll vertically,
470    // so we keep more tiles above and below the current area.
471    if (m_tileCoverage & CoverageForHorizontalScrolling && !largeVisibleRectChange)
472        coverageHorizontalSize *= 2;
473
474    if (m_tileCoverage & CoverageForVerticalScrolling && !largeVisibleRectChange)
475        coverageVerticalSize *= 3;
476
477    // Don't extend coverage before 0 or after the end.
478    FloatRect coverageBounds = bounds();
479    float coverageLeft = visibleRect.x() - (coverageHorizontalSize - visibleRect.width()) / 2;
480    coverageLeft = min(coverageLeft, coverageBounds.maxX() - coverageHorizontalSize);
481    coverageLeft = max(coverageLeft, coverageBounds.x());
482
483    float coverageTop = visibleRect.y() - (coverageVerticalSize - visibleRect.height()) / 2;
484    coverageTop = min(coverageTop, coverageBounds.maxY() - coverageVerticalSize);
485    coverageTop = max(coverageTop, coverageBounds.y());
486
487    return FloatRect(coverageLeft, coverageTop, coverageHorizontalSize, coverageVerticalSize);
488}
489
490IntSize TileController::tileSizeForCoverageRect(const FloatRect& coverageRect) const
491{
492    if (m_tileCoverage & CoverageForSlowScrolling) {
493        FloatSize tileSize = coverageRect.size();
494        tileSize.scale(m_scale);
495        return expandedIntSize(tileSize);
496    }
497
498    return IntSize(defaultTileWidth, defaultTileHeight);
499}
500
501void TileController::scheduleTileRevalidation(double interval)
502{
503    if (m_tileRevalidationTimer.isActive() && m_tileRevalidationTimer.nextFireInterval() < interval)
504        return;
505
506    m_tileRevalidationTimer.startOneShot(interval);
507}
508
509void TileController::tileRevalidationTimerFired(Timer<TileController>*)
510{
511    TileValidationPolicyFlags foregroundValidationPolicy = m_aggressivelyRetainsTiles ? 0 : PruneSecondaryTiles;
512    TileValidationPolicyFlags backgroundValidationPolicy = foregroundValidationPolicy | UnparentAllTiles;
513
514    revalidateTiles(foregroundValidationPolicy, backgroundValidationPolicy);
515}
516
517unsigned TileController::blankPixelCount() const
518{
519    WebTileLayerList tiles(m_tiles.size());
520
521    for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it)
522        tiles.append(it->value.layer);
523
524    return blankPixelCountForTiles(tiles, m_visibleRect, IntPoint(0,0));
525}
526
527unsigned TileController::blankPixelCountForTiles(const WebTileLayerList& tiles, const FloatRect& visibleRect, const IntPoint& tileTranslation)
528{
529    Region paintedVisibleTiles;
530
531    for (WebTileLayerList::const_iterator it = tiles.begin(), end = tiles.end(); it != end; ++it) {
532        const WebTileLayer* tileLayer = it->get();
533
534        FloatRect visiblePart(CGRectOffset([tileLayer frame], tileTranslation.x(), tileTranslation.y()));
535        visiblePart.intersect(visibleRect);
536
537        if (!visiblePart.isEmpty())
538            paintedVisibleTiles.unite(enclosingIntRect(visiblePart));
539    }
540
541    Region uncoveredRegion(enclosingIntRect(visibleRect));
542    uncoveredRegion.subtract(paintedVisibleTiles);
543
544    return uncoveredRegion.totalArea();
545}
546
547static inline void queueTileForRemoval(const TileController::TileIndex& tileIndex, const TileController::TileInfo& tileInfo, Vector<TileController::TileIndex>& tilesToRemove)
548{
549    WebTileLayer* tileLayer = tileInfo.layer.get();
550    [tileLayer removeFromSuperlayer];
551    [tileLayer setTileController:0];
552    tilesToRemove.append(tileIndex);
553}
554
555void TileController::removeAllTiles()
556{
557    Vector<TileIndex> tilesToRemove;
558
559    for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it)
560        queueTileForRemoval(it->key, it->value, tilesToRemove);
561
562    for (size_t i = 0; i < tilesToRemove.size(); ++i) {
563        TileInfo tileInfo = m_tiles.take(tilesToRemove[i]);
564        LayerPool::sharedPool()->addLayer(tileInfo.layer);
565    }
566}
567
568void TileController::removeAllSecondaryTiles()
569{
570    Vector<TileIndex> tilesToRemove;
571
572    for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
573        const TileInfo& tileInfo = it->value;
574        if (tileInfo.cohort == VisibleTileCohort)
575            continue;
576
577        queueTileForRemoval(it->key, it->value, tilesToRemove);
578    }
579
580    for (size_t i = 0; i < tilesToRemove.size(); ++i) {
581        TileInfo tileInfo = m_tiles.take(tilesToRemove[i]);
582        LayerPool::sharedPool()->addLayer(tileInfo.layer);
583    }
584}
585
586void TileController::removeTilesInCohort(TileCohort cohort)
587{
588    ASSERT(cohort != VisibleTileCohort);
589    Vector<TileIndex> tilesToRemove;
590
591    for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
592        const TileInfo& tileInfo = it->value;
593        if (tileInfo.cohort != cohort)
594            continue;
595
596        queueTileForRemoval(it->key, it->value, tilesToRemove);
597    }
598
599    for (size_t i = 0; i < tilesToRemove.size(); ++i) {
600        TileInfo tileInfo = m_tiles.take(tilesToRemove[i]);
601        LayerPool::sharedPool()->addLayer(tileInfo.layer);
602    }
603}
604
605void TileController::revalidateTiles(TileValidationPolicyFlags foregroundValidationPolicy, TileValidationPolicyFlags backgroundValidationPolicy)
606{
607    // If the underlying PlatformLayer has been destroyed, but the WebTiledBackingLayer hasn't
608    // platformLayer will be null here.
609    PlatformCALayer* platformLayer = PlatformCALayer::platformCALayer(m_tileCacheLayer);
610    if (!platformLayer)
611        return;
612
613    FloatRect visibleRect = m_visibleRect;
614    IntRect bounds = this->bounds();
615
616    if (m_clipsToExposedRect)
617        visibleRect.intersect(m_exposedRect);
618
619    if (visibleRect.isEmpty() || bounds.isEmpty())
620        return;
621
622    TileValidationPolicyFlags validationPolicy = m_isInWindow ? foregroundValidationPolicy : backgroundValidationPolicy;
623
624    FloatRect tileCoverageRect = computeTileCoverageRect(m_visibleRectAtLastRevalidate, m_visibleRect);
625    FloatRect scaledRect(tileCoverageRect);
626    scaledRect.scale(m_scale);
627    IntRect coverageRectInTileCoords(enclosingIntRect(scaledRect));
628
629    IntSize oldTileSize = m_tileSize;
630    m_tileSize = tileSizeForCoverageRect(tileCoverageRect);
631    bool tileSizeChanged = m_tileSize != oldTileSize;
632
633    if (tileSizeChanged) {
634        removeAllTiles();
635        m_cohortList.clear();
636    } else {
637        TileCohort currCohort = nextTileCohort();
638        unsigned tilesInCohort = 0;
639
640        // Move tiles newly outside the coverage rect into the cohort map.
641        for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
642            TileInfo& tileInfo = it->value;
643            TileIndex tileIndex = it->key;
644
645            WebTileLayer* tileLayer = tileInfo.layer.get();
646            IntRect tileRect = rectForTileIndex(tileIndex);
647            if (tileRect.intersects(coverageRectInTileCoords)) {
648                tileInfo.cohort = VisibleTileCohort;
649                if (tileInfo.hasStaleContent) {
650                    // FIXME: store a dirty region per layer?
651                    [tileLayer setNeedsDisplay];
652                    tileInfo.hasStaleContent = false;
653                }
654            } else {
655                // Add to the currentCohort if not already in one.
656                if (tileInfo.cohort == VisibleTileCohort) {
657                    tileInfo.cohort = currCohort;
658                    ++tilesInCohort;
659
660                    if (m_unparentsOffscreenTiles)
661                        [tileInfo.layer.get() removeFromSuperlayer];
662                }
663            }
664        }
665
666        if (tilesInCohort)
667            startedNewCohort(currCohort);
668
669        if (!m_aggressivelyRetainsTiles)
670            scheduleCohortRemoval();
671    }
672
673    TileIndex topLeft;
674    TileIndex bottomRight;
675    getTileIndexRangeForRect(coverageRectInTileCoords, topLeft, bottomRight);
676
677    Vector<FloatRect> dirtyRects;
678
679    // Ensure primary tile coverage tiles.
680    m_primaryTileCoverageRect = IntRect();
681
682    for (int y = topLeft.y(); y <= bottomRight.y(); ++y) {
683        for (int x = topLeft.x(); x <= bottomRight.x(); ++x) {
684            TileIndex tileIndex(x, y);
685
686            IntRect tileRect = rectForTileIndex(tileIndex);
687            m_primaryTileCoverageRect.unite(tileRect);
688
689            bool shouldChangeTileLayerFrame = false;
690
691            TileInfo& tileInfo = m_tiles.add(tileIndex, TileInfo()).iterator->value;
692            if (!tileInfo.layer)
693                tileInfo.layer = createTileLayer(tileRect);
694            else {
695                // We already have a layer for this tile. Ensure that its size is correct.
696                FloatSize tileLayerSize([tileInfo.layer.get() frame].size);
697                shouldChangeTileLayerFrame = tileLayerSize != FloatSize(tileRect.size());
698
699                if (shouldChangeTileLayerFrame)
700                    [tileInfo.layer.get() setFrame:tileRect];
701            }
702
703            bool shouldParentTileLayer = (!m_unparentsOffscreenTiles || m_isInWindow) && ![tileInfo.layer.get() superlayer];
704
705            if (shouldParentTileLayer)
706                [m_tileContainerLayer.get() addSublayer:tileInfo.layer.get()];
707
708            if ((shouldParentTileLayer && [tileInfo.layer.get() needsDisplay]) || shouldChangeTileLayerFrame) {
709                FloatRect scaledTileRect = tileRect;
710                scaledTileRect.scale(1 / m_scale);
711                dirtyRects.append(scaledTileRect);
712            }
713        }
714    }
715
716    if (validationPolicy & PruneSecondaryTiles) {
717        removeAllSecondaryTiles();
718        m_cohortList.clear();
719    }
720
721    if (m_unparentsOffscreenTiles && (validationPolicy & UnparentAllTiles)) {
722        for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it)
723            [it->value.layer.get() removeFromSuperlayer];
724    }
725
726    if (m_boundsAtLastRevalidate != bounds) {
727        FloatRect scaledBounds(bounds);
728        scaledBounds.scale(m_scale);
729        IntRect boundsInTileCoords(enclosingIntRect(scaledBounds));
730
731        TileIndex topLeftForBounds;
732        TileIndex bottomRightForBounds;
733        getTileIndexRangeForRect(boundsInTileCoords, topLeftForBounds, bottomRightForBounds);
734
735        Vector<TileIndex> tilesToRemove;
736        for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
737            const TileIndex& index = it->key;
738            if (index.y() < topLeftForBounds.y()
739                || index.y() > bottomRightForBounds.y()
740                || index.x() < topLeftForBounds.x()
741                || index.x() > bottomRightForBounds.x())
742                queueTileForRemoval(index, it->value, tilesToRemove);
743        }
744
745        for (size_t i = 0, size = tilesToRemove.size(); i < size; ++i) {
746            TileInfo tileInfo = m_tiles.take(tilesToRemove[i]);
747            LayerPool::sharedPool()->addLayer(tileInfo.layer);
748        }
749    }
750
751    if (m_tiledScrollingIndicatorLayer)
752        updateTileCoverageMap();
753
754    m_visibleRectAtLastRevalidate = visibleRect;
755    m_boundsAtLastRevalidate = bounds;
756
757    if (dirtyRects.isEmpty())
758        return;
759
760    // This will ensure we flush compositing state and do layout in this run loop iteration.
761    platformLayer->owner()->platformCALayerDidCreateTiles(dirtyRects);
762}
763
764TileController::TileCohort TileController::nextTileCohort() const
765{
766    if (!m_cohortList.isEmpty())
767        return m_cohortList.last().cohort + 1;
768
769    return 1;
770}
771
772void TileController::startedNewCohort(TileCohort cohort)
773{
774    m_cohortList.append(TileCohortInfo(cohort, monotonicallyIncreasingTime()));
775}
776
777TileController::TileCohort TileController::newestTileCohort() const
778{
779    return m_cohortList.isEmpty() ? 0 : m_cohortList.last().cohort;
780}
781
782TileController::TileCohort TileController::oldestTileCohort() const
783{
784    return m_cohortList.isEmpty() ? 0 : m_cohortList.first().cohort;
785}
786
787void TileController::scheduleCohortRemoval()
788{
789    const double cohortRemovalTimerSeconds = 1;
790
791    // Start the timer, or reschedule the timer from now if it's already active.
792    if (!m_cohortRemovalTimer.isActive())
793        m_cohortRemovalTimer.startRepeating(cohortRemovalTimerSeconds);
794}
795
796void TileController::cohortRemovalTimerFired(Timer<TileController>*)
797{
798    if (m_cohortList.isEmpty()) {
799        m_cohortRemovalTimer.stop();
800        return;
801    }
802
803    double cohortLifeTimeSeconds = 2;
804    double timeThreshold = monotonicallyIncreasingTime() - cohortLifeTimeSeconds;
805
806    while (!m_cohortList.isEmpty() && m_cohortList.first().creationTime < timeThreshold) {
807        TileCohortInfo firstCohort = m_cohortList.takeFirst();
808        removeTilesInCohort(firstCohort.cohort);
809    }
810
811    if (m_tiledScrollingIndicatorLayer)
812        updateTileCoverageMap();
813}
814
815void TileController::ensureTilesForRect(const FloatRect& rect)
816{
817    if (m_unparentsOffscreenTiles && !m_isInWindow)
818        return;
819
820    PlatformCALayer* platformLayer = PlatformCALayer::platformCALayer(m_tileCacheLayer);
821    if (!platformLayer)
822        return;
823
824    FloatRect scaledRect(rect);
825    scaledRect.scale(m_scale);
826    IntRect rectInTileCoords(enclosingIntRect(scaledRect));
827
828    if (m_primaryTileCoverageRect.contains(rectInTileCoords))
829        return;
830
831    TileIndex topLeft;
832    TileIndex bottomRight;
833    getTileIndexRangeForRect(rectInTileCoords, topLeft, bottomRight);
834
835    Vector<FloatRect> dirtyRects;
836    TileCohort currCohort = nextTileCohort();
837    unsigned tilesInCohort = 0;
838
839    for (int y = topLeft.y(); y <= bottomRight.y(); ++y) {
840        for (int x = topLeft.x(); x <= bottomRight.x(); ++x) {
841            TileIndex tileIndex(x, y);
842
843            IntRect tileRect = rectForTileIndex(tileIndex);
844            TileInfo& tileInfo = m_tiles.add(tileIndex, TileInfo()).iterator->value;
845
846            bool shouldChangeTileLayerFrame = false;
847
848            if (!tileInfo.layer)
849                tileInfo.layer = createTileLayer(tileRect);
850            else {
851                // We already have a layer for this tile. Ensure that its size is correct.
852                CGSize tileLayerSize = [tileInfo.layer.get() frame].size;
853                shouldChangeTileLayerFrame = tileLayerSize.width < tileRect.width() || tileLayerSize.height < tileRect.height();
854
855                if (shouldChangeTileLayerFrame)
856                    [tileInfo.layer.get() setFrame:tileRect];
857            }
858
859            if (!tileRect.intersects(m_primaryTileCoverageRect)) {
860                tileInfo.cohort = currCohort;
861                ++tilesInCohort;
862            }
863
864            bool shouldParentTileLayer = ![tileInfo.layer.get() superlayer];
865
866            if (shouldParentTileLayer)
867                [m_tileContainerLayer.get() addSublayer:tileInfo.layer.get()];
868
869            if ((shouldParentTileLayer && [tileInfo.layer.get() needsDisplay]) || shouldChangeTileLayerFrame) {
870                FloatRect scaledTileRect = tileRect;
871                scaledTileRect.scale(1 / m_scale);
872                dirtyRects.append(scaledTileRect);
873            }
874        }
875    }
876
877    if (tilesInCohort)
878        startedNewCohort(currCohort);
879
880    if (m_tiledScrollingIndicatorLayer)
881        updateTileCoverageMap();
882
883    // This will ensure we flush compositing state and do layout in this run loop iteration.
884    if (!dirtyRects.isEmpty())
885        platformLayer->owner()->platformCALayerDidCreateTiles(dirtyRects);
886}
887
888void TileController::updateTileCoverageMap()
889{
890    FloatRect containerBounds = bounds();
891    FloatRect visibleRect = this->visibleRect();
892
893    if (m_clipsToExposedRect)
894        visibleRect.intersect(m_exposedRect);
895
896    visibleRect.contract(4, 4); // Layer is positioned 2px from top and left edges.
897
898    float widthScale = 1;
899    float scale = 1;
900    if (!containerBounds.isEmpty()) {
901        widthScale = std::min<float>(visibleRect.width() / containerBounds.width(), 0.1);
902        scale = std::min(widthScale, visibleRect.height() / containerBounds.height());
903    }
904
905    float indicatorScale = scale * m_scale;
906    FloatRect mapBounds = containerBounds;
907    mapBounds.scale(indicatorScale, indicatorScale);
908
909    BEGIN_BLOCK_OBJC_EXCEPTIONS
910
911    if (m_clipsToExposedRect)
912        [m_tiledScrollingIndicatorLayer.get() setPosition:m_exposedRect.location() + FloatPoint(2, 2)];
913    else
914        [m_tiledScrollingIndicatorLayer.get() setPosition:CGPointMake(2, 2)];
915
916    [m_tiledScrollingIndicatorLayer.get() setBounds:mapBounds];
917    [m_tiledScrollingIndicatorLayer.get() setNeedsDisplay];
918
919    visibleRect.scale(indicatorScale, indicatorScale);
920    visibleRect.expand(2, 2);
921    [[m_tiledScrollingIndicatorLayer.get() visibleRectFrameLayer] setFrame:visibleRect];
922
923    Color backgroundColor;
924    switch (m_indicatorMode) {
925    case MainThreadScrollingBecauseOfStyleIndication:
926        backgroundColor = Color(255, 0, 0);
927        break;
928    case MainThreadScrollingBecauseOfEventHandlersIndication:
929        backgroundColor = Color(255, 255, 0);
930        break;
931    case ThreadedScrollingIndication:
932        backgroundColor = Color(0, 200, 0);
933        break;
934    }
935
936    [[m_tiledScrollingIndicatorLayer.get() visibleRectFrameLayer] setBorderColor:cachedCGColor(backgroundColor, ColorSpaceDeviceRGB)];
937
938    END_BLOCK_OBJC_EXCEPTIONS
939}
940
941IntRect TileController::tileGridExtent() const
942{
943    TileIndex topLeft;
944    TileIndex bottomRight;
945    getTileIndexRangeForRect(m_primaryTileCoverageRect, topLeft, bottomRight);
946
947    // Return index of top, left tile and the number of tiles across and down.
948    return IntRect(topLeft.x(), topLeft.y(), bottomRight.x() - topLeft.x() + 1, bottomRight.y() - topLeft.y() + 1);
949}
950
951double TileController::retainedTileBackingStoreMemory() const
952{
953    double totalBytes = 0;
954
955    for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
956        const TileInfo& tileInfo = it->value;
957        if ([tileInfo.layer.get() superlayer]) {
958            CGRect bounds = [tileInfo.layer.get() bounds];
959            double contentsScale = [tileInfo.layer.get() contentsScale];
960            totalBytes += 4 * bounds.size.width * contentsScale * bounds.size.height * contentsScale;
961        }
962    }
963
964    return totalBytes;
965}
966
967// Return the rect in layer coords, not tile coords.
968IntRect TileController::tileCoverageRect() const
969{
970    IntRect coverageRectInLayerCoords(m_primaryTileCoverageRect);
971    coverageRectInLayerCoords.scale(1 / m_scale);
972    return coverageRectInLayerCoords;
973}
974
975CALayer *TileController::tiledScrollingIndicatorLayer()
976{
977    if (!m_tiledScrollingIndicatorLayer) {
978        m_tiledScrollingIndicatorLayer = [WebTiledScrollingIndicatorLayer layer];
979        [m_tiledScrollingIndicatorLayer.get() setTileController:this];
980        [m_tiledScrollingIndicatorLayer.get() setOpacity:0.75];
981        [m_tiledScrollingIndicatorLayer.get() setAnchorPoint:CGPointZero];
982        [m_tiledScrollingIndicatorLayer.get() setBorderColor:cachedCGColor(Color::black, ColorSpaceDeviceRGB)];
983        [m_tiledScrollingIndicatorLayer.get() setBorderWidth:1];
984        [m_tiledScrollingIndicatorLayer.get() setPosition:CGPointMake(2, 2)];
985        updateTileCoverageMap();
986    }
987
988    return m_tiledScrollingIndicatorLayer.get();
989}
990
991void TileController::setScrollingModeIndication(ScrollingModeIndication scrollingMode)
992{
993    if (scrollingMode == m_indicatorMode)
994        return;
995
996    m_indicatorMode = scrollingMode;
997
998    if (m_tiledScrollingIndicatorLayer)
999        updateTileCoverageMap();
1000}
1001
1002WebTileLayer* TileController::tileLayerAtIndex(const TileIndex& index) const
1003{
1004    return m_tiles.get(index).layer.get();
1005}
1006
1007RetainPtr<WebTileLayer> TileController::createTileLayer(const IntRect& tileRect)
1008{
1009    RetainPtr<WebTileLayer> layer = LayerPool::sharedPool()->takeLayerWithSize(tileRect.size());
1010    if (layer)
1011        [layer resetPaintCount];
1012    else
1013        layer = adoptNS([[WebTileLayer alloc] init]);
1014    [layer.get() setAnchorPoint:CGPointZero];
1015    [layer.get() setFrame:tileRect];
1016    [layer.get() setTileController:this];
1017    [layer.get() setBorderColor:m_tileDebugBorderColor.get()];
1018    [layer.get() setBorderWidth:m_tileDebugBorderWidth];
1019    [layer.get() setEdgeAntialiasingMask:0];
1020    [layer.get() setOpaque:m_tilesAreOpaque];
1021#ifndef NDEBUG
1022    [layer.get() setName:@"Tile"];
1023#endif
1024
1025#if PLATFORM(IOS) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
1026    [layer.get() setContentsScale:m_deviceScaleFactor];
1027    [layer.get() setAcceleratesDrawing:m_acceleratesDrawing];
1028#endif
1029
1030    [layer setNeedsDisplay];
1031
1032    return layer;
1033}
1034
1035bool TileController::shouldShowRepaintCounters() const
1036{
1037    PlatformCALayer* platformLayer = PlatformCALayer::platformCALayer(m_tileCacheLayer);
1038    if (!platformLayer)
1039        return false;
1040
1041    WebCore::PlatformCALayerClient* layerContents = platformLayer->owner();
1042    ASSERT(layerContents);
1043    if (!layerContents)
1044        return false;
1045
1046    return layerContents->platformCALayerShowRepaintCounter(0);
1047}
1048
1049void TileController::drawRepaintCounter(WebTileLayer *layer, CGContextRef context)
1050{
1051    unsigned paintCount = [layer incrementPaintCount];
1052    if (!shouldShowRepaintCounters())
1053        return;
1054
1055    // FIXME: Some of this code could be shared with WebLayer.
1056    char text[16]; // that's a lot of repaints
1057    snprintf(text, sizeof(text), "%d", paintCount);
1058
1059    CGRect indicatorBox = [layer bounds];
1060    indicatorBox.size.width = 12 + 10 * strlen(text);
1061    indicatorBox.size.height = 27;
1062    CGContextSaveGState(context);
1063
1064    CGContextSetAlpha(context, 0.5f);
1065    CGContextBeginTransparencyLayerWithRect(context, indicatorBox, 0);
1066
1067    CGContextSetFillColorWithColor(context, m_tileDebugBorderColor.get());
1068    CGContextFillRect(context, indicatorBox);
1069
1070    PlatformCALayer* platformLayer = PlatformCALayer::platformCALayer(m_tileCacheLayer);
1071
1072    if (platformLayer->acceleratesDrawing())
1073        CGContextSetRGBFillColor(context, 1, 0, 0, 1);
1074    else
1075        CGContextSetRGBFillColor(context, 1, 1, 1, 1);
1076
1077#pragma clang diagnostic push
1078#pragma clang diagnostic ignored "-Wdeprecated-declarations"
1079    CGContextSetTextMatrix(context, CGAffineTransformMakeScale(1, -1));
1080    CGContextSelectFont(context, "Helvetica", 22, kCGEncodingMacRoman);
1081    CGContextShowTextAtPoint(context, indicatorBox.origin.x + 5, indicatorBox.origin.y + 22, text, strlen(text));
1082#pragma clang diagnostic pop
1083
1084    CGContextEndTransparencyLayer(context);
1085    CGContextRestoreGState(context);
1086}
1087
1088void TileController::drawTileMapContents(CGContextRef context, CGRect layerBounds)
1089{
1090    CGContextSetRGBFillColor(context, 0.3, 0.3, 0.3, 1);
1091    CGContextFillRect(context, layerBounds);
1092
1093    CGFloat scaleFactor = layerBounds.size.width / bounds().width();
1094
1095    CGFloat contextScale = scaleFactor / scale();
1096    CGContextScaleCTM(context, contextScale, contextScale);
1097
1098    for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
1099        const TileInfo& tileInfo = it->value;
1100        WebTileLayer* tileLayer = tileInfo.layer.get();
1101
1102        CGFloat red = 1;
1103        CGFloat green = 1;
1104        CGFloat blue = 1;
1105        if (tileInfo.hasStaleContent) {
1106            red = 0.25;
1107            green = 0.125;
1108            blue = 0;
1109        }
1110
1111        TileCohort newestCohort = newestTileCohort();
1112        TileCohort oldestCohort = oldestTileCohort();
1113
1114        if (!m_aggressivelyRetainsTiles && tileInfo.cohort != VisibleTileCohort && newestCohort > oldestCohort) {
1115            float cohortProportion = static_cast<float>((newestCohort - tileInfo.cohort)) / (newestCohort - oldestCohort);
1116            CGContextSetRGBFillColor(context, red, green, blue, 1 - cohortProportion);
1117        } else
1118            CGContextSetRGBFillColor(context, red, green, blue, 1);
1119
1120        if ([tileLayer superlayer]) {
1121            CGContextSetLineWidth(context, 0.5 / contextScale);
1122            CGContextSetRGBStrokeColor(context, 0, 0, 0, 1);
1123        } else {
1124            CGContextSetLineWidth(context, 1 / contextScale);
1125            CGContextSetRGBStrokeColor(context, 0.2, 0.1, 0.9, 1);
1126        }
1127
1128        CGRect frame = [tileLayer frame];
1129        CGContextFillRect(context, frame);
1130        CGContextStrokeRect(context, frame);
1131    }
1132}
1133
1134
1135} // namespace WebCore
1136