9af8a8703cf8bd6b83e633ae73f92f0fe175e303
[WebKit-https.git] / Source / WebCore / platform / graphics / ca / mac / TileController.mm
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
41 using 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", 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
82 namespace WebCore {
83     
84 enum TileValidationPolicyFlag {
85     PruneSecondaryTiles = 1 << 0,
86     UnparentAllTiles = 1 << 1
87 };
88
89 static const int defaultTileWidth = 512;
90 static const int defaultTileHeight = 512;
91
92 PassOwnPtr<TileController> TileController::create(WebTiledBackingLayer* tileCacheLayer)
93 {
94     return adoptPtr(new TileController(tileCacheLayer));
95 }
96
97 TileController::TileController(WebTiledBackingLayer* tileCacheLayer)
98     : m_tileCacheLayer(tileCacheLayer)
99     , m_tileContainerLayer(adoptCF([[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
125 TileController::~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
138 void 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
150 void 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
158 void 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
189 void 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
216 void 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
234 void 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
270 void 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
287 void 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
300 void TileController::setVisibleRect(const FloatRect& visibleRect)
301 {
302     if (m_visibleRect == visibleRect)
303         return;
304
305     m_visibleRect = visibleRect;
306     revalidateTiles();
307 }
308
309 void TileController::setExposedRect(const FloatRect& exposedRect)
310 {
311     if (m_exposedRect == exposedRect)
312         return;
313
314     m_exposedRect = exposedRect;
315     revalidateTiles();
316 }
317
318 void TileController::setClipsToExposedRect(bool clipsToExposedRect)
319 {
320     if (m_clipsToExposedRect == clipsToExposedRect)
321         return;
322
323     m_clipsToExposedRect = clipsToExposedRect;
324
325     // Going from not clipping to clipping, we don't need to revalidate right away.
326     if (clipsToExposedRect)
327         revalidateTiles();
328 }
329
330 void TileController::prepopulateRect(const FloatRect& rect)
331 {
332     ensureTilesForRect(rect);
333 }
334
335 void TileController::setIsInWindow(bool isInWindow)
336 {
337     if (m_isInWindow == isInWindow)
338         return;
339
340     m_isInWindow = isInWindow;
341
342     if (m_isInWindow)
343         revalidateTiles();
344     else {
345         const double tileRevalidationTimeout = 4;
346         scheduleTileRevalidation(tileRevalidationTimeout);
347     }
348 }
349
350 void TileController::setTileCoverage(TileCoverage coverage)
351 {
352     if (coverage == m_tileCoverage)
353         return;
354
355     m_tileCoverage = coverage;
356     scheduleTileRevalidation(0);
357 }
358
359 void TileController::forceRepaint()
360 {
361     setNeedsDisplay();
362 }
363
364 void TileController::setTileDebugBorderWidth(float borderWidth)
365 {
366     if (m_tileDebugBorderWidth == borderWidth)
367         return;
368
369     m_tileDebugBorderWidth = borderWidth;
370     for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
371         const TileInfo& tileInfo = it->value;
372         [tileInfo.layer.get() setBorderWidth:m_tileDebugBorderWidth];
373     }
374 }
375
376 void TileController::setTileDebugBorderColor(CGColorRef borderColor)
377 {
378     if (m_tileDebugBorderColor == borderColor)
379         return;
380
381     m_tileDebugBorderColor = borderColor;
382     for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
383         const TileInfo& tileInfo = it->value;
384         [tileInfo.layer.get() setBorderColor:m_tileDebugBorderColor.get()];
385     }
386 }
387
388 IntRect TileController::bounds() const
389 {
390     return IntRect(IntPoint(), IntSize([m_tileCacheLayer bounds].size));
391 }
392
393 IntRect TileController::rectForTileIndex(const TileIndex& tileIndex) const
394 {
395     IntRect rect(tileIndex.x() * m_tileSize.width(), tileIndex.y() * m_tileSize.height(), m_tileSize.width(), m_tileSize.height());
396     IntRect scaledBounds(bounds());
397     scaledBounds.scale(m_scale);
398
399     rect.intersect(scaledBounds);
400
401     return rect;
402 }
403
404 void TileController::getTileIndexRangeForRect(const IntRect& rect, TileIndex& topLeft, TileIndex& bottomRight) const
405 {
406     IntRect clampedRect = bounds();
407     clampedRect.scale(m_scale);
408     clampedRect.intersect(rect);
409
410     topLeft.setX(max(clampedRect.x() / m_tileSize.width(), 0));
411     topLeft.setY(max(clampedRect.y() / m_tileSize.height(), 0));
412
413     int bottomXRatio = ceil((float)clampedRect.maxX() / m_tileSize.width());
414     bottomRight.setX(max(bottomXRatio - 1, 0));
415
416     int bottomYRatio = ceil((float)clampedRect.maxY() / m_tileSize.height());
417     bottomRight.setY(max(bottomYRatio - 1, 0));
418 }
419
420 FloatRect TileController::computeTileCoverageRect(const FloatRect& previousVisibleRect) const
421 {
422     FloatRect visibleRect = m_visibleRect;
423
424     if (m_clipsToExposedRect)
425         visibleRect.intersect(m_exposedRect);
426
427     // 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.
428     // Furthermore, if the page can't have scrollbars (for example if its body element has overflow:hidden) it's very unlikely that the
429     // page will ever be scrolled so we limit the tile coverage rect as well.
430     if (!m_isInWindow || m_tileCoverage & CoverageForSlowScrolling)
431         return visibleRect;
432
433     bool largeVisibleRectChange = !previousVisibleRect.isEmpty() && !visibleRect.intersects(previousVisibleRect);
434     
435     // FIXME: look at how far the document can scroll in each dimension.
436     float coverageHorizontalSize = visibleRect.width();
437     float coverageVerticalSize = visibleRect.height();
438     
439     // Inflate the coverage rect so that it covers 2x of the visible width and 3x of the visible height.
440     // These values were chosen because it's more common to have tall pages and to scroll vertically,
441     // so we keep more tiles above and below the current area.
442     if (m_tileCoverage & CoverageForHorizontalScrolling && !largeVisibleRectChange)
443         coverageHorizontalSize *= 2;
444
445     if (m_tileCoverage & CoverageForVerticalScrolling && !largeVisibleRectChange)
446         coverageVerticalSize *= 3;
447
448     // Don't extend coverage before 0 or after the end.
449     FloatRect coverageBounds = bounds();
450     float coverageLeft = visibleRect.x() - (coverageHorizontalSize - visibleRect.width()) / 2;
451     coverageLeft = min(coverageLeft, coverageBounds.maxX() - coverageHorizontalSize);
452     coverageLeft = max(coverageLeft, coverageBounds.x());
453
454     float coverageTop = visibleRect.y() - (coverageVerticalSize - visibleRect.height()) / 2;
455     coverageTop = min(coverageTop, coverageBounds.maxY() - coverageVerticalSize);
456     coverageTop = max(coverageTop, coverageBounds.y());
457
458     return FloatRect(coverageLeft, coverageTop, coverageHorizontalSize, coverageVerticalSize);
459 }
460
461 IntSize TileController::tileSizeForCoverageRect(const FloatRect& coverageRect) const
462 {
463     if (m_tileCoverage & CoverageForSlowScrolling) {
464         FloatSize tileSize = coverageRect.size();
465         tileSize.scale(m_scale);
466         return expandedIntSize(tileSize);
467     }
468
469     return IntSize(defaultTileWidth, defaultTileHeight);
470 }
471
472 void TileController::scheduleTileRevalidation(double interval)
473 {
474     if (m_tileRevalidationTimer.isActive() && m_tileRevalidationTimer.nextFireInterval() < interval)
475         return;
476
477     m_tileRevalidationTimer.startOneShot(interval);
478 }
479
480 void TileController::tileRevalidationTimerFired(Timer<TileController>*)
481 {
482     TileValidationPolicyFlags foregroundValidationPolicy = m_aggressivelyRetainsTiles ? 0 : PruneSecondaryTiles;
483     TileValidationPolicyFlags backgroundValidationPolicy = foregroundValidationPolicy | UnparentAllTiles;
484
485     revalidateTiles(foregroundValidationPolicy, backgroundValidationPolicy);
486 }
487
488 unsigned TileController::blankPixelCount() const
489 {
490     WebTileLayerList tiles(m_tiles.size());
491
492     for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it)
493         tiles.append(it->value.layer);
494
495     return blankPixelCountForTiles(tiles, m_visibleRect, IntPoint(0,0));
496 }
497
498 unsigned TileController::blankPixelCountForTiles(const WebTileLayerList& tiles, const FloatRect& visibleRect, const IntPoint& tileTranslation)
499 {
500     Region paintedVisibleTiles;
501
502     for (WebTileLayerList::const_iterator it = tiles.begin(), end = tiles.end(); it != end; ++it) {
503         const WebTileLayer* tileLayer = it->get();
504
505         FloatRect visiblePart(CGRectOffset([tileLayer frame], tileTranslation.x(), tileTranslation.y()));
506         visiblePart.intersect(visibleRect);
507
508         if (!visiblePart.isEmpty())
509             paintedVisibleTiles.unite(enclosingIntRect(visiblePart));
510     }
511
512     Region uncoveredRegion(enclosingIntRect(visibleRect));
513     uncoveredRegion.subtract(paintedVisibleTiles);
514
515     return uncoveredRegion.totalArea();
516 }
517
518 static inline void queueTileForRemoval(const TileController::TileIndex& tileIndex, const TileController::TileInfo& tileInfo, Vector<TileController::TileIndex>& tilesToRemove)
519 {
520     WebTileLayer* tileLayer = tileInfo.layer.get();
521     [tileLayer removeFromSuperlayer];
522     [tileLayer setTileController:0];
523     tilesToRemove.append(tileIndex);
524 }
525
526 void TileController::removeAllTiles()
527 {
528     Vector<TileIndex> tilesToRemove;
529
530     for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it)
531         queueTileForRemoval(it->key, it->value, tilesToRemove);
532
533     for (size_t i = 0; i < tilesToRemove.size(); ++i) {
534         TileInfo tileInfo = m_tiles.take(tilesToRemove[i]);
535         LayerPool::sharedPool()->addLayer(tileInfo.layer);
536     }
537 }
538
539 void TileController::removeAllSecondaryTiles()
540 {
541     Vector<TileIndex> tilesToRemove;
542
543     for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
544         const TileInfo& tileInfo = it->value;
545         if (tileInfo.cohort == VisibleTileCohort)
546             continue;
547
548         queueTileForRemoval(it->key, it->value, tilesToRemove);
549     }
550
551     for (size_t i = 0; i < tilesToRemove.size(); ++i) {
552         TileInfo tileInfo = m_tiles.take(tilesToRemove[i]);
553         LayerPool::sharedPool()->addLayer(tileInfo.layer);
554     }
555 }
556
557 void TileController::removeTilesInCohort(TileCohort cohort)
558 {
559     ASSERT(cohort != VisibleTileCohort);
560     Vector<TileIndex> tilesToRemove;
561
562     for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
563         const TileInfo& tileInfo = it->value;
564         if (tileInfo.cohort != cohort)
565             continue;
566
567         queueTileForRemoval(it->key, it->value, tilesToRemove);
568     }
569
570     for (size_t i = 0; i < tilesToRemove.size(); ++i) {
571         TileInfo tileInfo = m_tiles.take(tilesToRemove[i]);
572         LayerPool::sharedPool()->addLayer(tileInfo.layer);
573     }
574 }
575
576 void TileController::revalidateTiles(TileValidationPolicyFlags foregroundValidationPolicy, TileValidationPolicyFlags backgroundValidationPolicy)
577 {
578     // If the underlying PlatformLayer has been destroyed, but the WebTiledBackingLayer hasn't
579     // platformLayer will be null here.
580     PlatformCALayer* platformLayer = PlatformCALayer::platformCALayer(m_tileCacheLayer);
581     if (!platformLayer)
582         return;
583
584     FloatRect visibleRect = m_visibleRect;
585
586     if (m_clipsToExposedRect)
587         visibleRect.intersect(m_exposedRect);
588
589     if (visibleRect.isEmpty() || bounds().isEmpty())
590         return;
591     
592     TileValidationPolicyFlags validationPolicy = m_isInWindow ? foregroundValidationPolicy : backgroundValidationPolicy;
593     
594     FloatRect tileCoverageRect = computeTileCoverageRect(m_visibleRectAtLastRevalidate);
595     FloatRect scaledRect(tileCoverageRect);
596     scaledRect.scale(m_scale);
597     IntRect coverageRectInTileCoords(enclosingIntRect(scaledRect));
598
599     IntSize oldTileSize = m_tileSize;
600     m_tileSize = tileSizeForCoverageRect(tileCoverageRect);
601     bool tileSizeChanged = m_tileSize != oldTileSize;
602
603     if (tileSizeChanged) {
604         removeAllTiles();
605         m_cohortList.clear();
606     } else {
607         TileCohort currCohort = nextTileCohort();
608         unsigned tilesInCohort = 0;
609         
610         // Move tiles newly outside the coverage rect into the cohort map.
611         for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
612             TileInfo& tileInfo = it->value;
613             TileIndex tileIndex = it->key;
614
615             WebTileLayer* tileLayer = tileInfo.layer.get();
616             IntRect tileRect = rectForTileIndex(tileIndex);
617             if (tileRect.intersects(coverageRectInTileCoords)) {
618                 tileInfo.cohort = VisibleTileCohort;
619                 if (tileInfo.hasStaleContent) {
620                     // FIXME: store a dirty region per layer?
621                     [tileLayer setNeedsDisplay];
622                     tileInfo.hasStaleContent = false;
623                 }
624             } else {
625                 // Add to the currentCohort if not already in one.
626                 if (tileInfo.cohort == VisibleTileCohort) {
627                     tileInfo.cohort = currCohort;
628                     ++tilesInCohort;
629                     
630                     if (m_unparentsOffscreenTiles)
631                         [tileInfo.layer.get() removeFromSuperlayer];
632                 }
633             }
634         }
635         
636         if (tilesInCohort)
637             startedNewCohort(currCohort);
638
639         if (!m_aggressivelyRetainsTiles)
640             scheduleCohortRemoval();
641     }
642
643     TileIndex topLeft;
644     TileIndex bottomRight;
645     getTileIndexRangeForRect(coverageRectInTileCoords, topLeft, bottomRight);
646
647     Vector<FloatRect> dirtyRects;
648     
649     // Ensure primary tile coverage tiles.
650     m_primaryTileCoverageRect = IntRect();
651
652     for (int y = topLeft.y(); y <= bottomRight.y(); ++y) {
653         for (int x = topLeft.x(); x <= bottomRight.x(); ++x) {
654             TileIndex tileIndex(x, y);
655
656             IntRect tileRect = rectForTileIndex(tileIndex);
657             m_primaryTileCoverageRect.unite(tileRect);
658
659             TileInfo& tileInfo = m_tiles.add(tileIndex, TileInfo()).iterator->value;
660             if (!tileInfo.layer) {
661                 tileInfo.layer = createTileLayer(tileRect);
662                 if (!m_unparentsOffscreenTiles || m_isInWindow)
663                     [m_tileContainerLayer.get() addSublayer:tileInfo.layer.get()];
664             } else {
665                 if ((!m_unparentsOffscreenTiles || m_isInWindow) && ![tileInfo.layer.get() superlayer])
666                     [m_tileContainerLayer.get() addSublayer:tileInfo.layer.get()];
667
668                 // We already have a layer for this tile. Ensure that its size is correct.
669                 FloatSize tileLayerSize([tileInfo.layer.get() frame].size);
670                 if (tileLayerSize == FloatSize(tileRect.size()))
671                     continue;
672
673                 [tileInfo.layer.get() setFrame:tileRect];
674             }
675             
676             FloatRect scaledTileRect = tileRect;
677             scaledTileRect.scale(1 / m_scale);
678             dirtyRects.append(scaledTileRect);
679         }
680     }
681
682     if (validationPolicy & PruneSecondaryTiles) {
683         removeAllSecondaryTiles();
684         m_cohortList.clear();
685     }
686
687     if (m_unparentsOffscreenTiles && (validationPolicy & UnparentAllTiles)) {
688         for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it)
689             [it->value.layer.get() removeFromSuperlayer];
690     }
691     
692     if (m_tiledScrollingIndicatorLayer)
693         updateTileCoverageMap();
694
695     m_visibleRectAtLastRevalidate = visibleRect;
696
697     if (dirtyRects.isEmpty())
698         return;
699     platformLayer->owner()->platformCALayerDidCreateTiles(dirtyRects);
700 }
701
702 TileController::TileCohort TileController::nextTileCohort() const
703 {
704     if (!m_cohortList.isEmpty())
705         return m_cohortList.last().cohort + 1;
706
707     return 1;
708 }
709
710 void TileController::startedNewCohort(TileCohort cohort)
711 {
712     m_cohortList.append(TileCohortInfo(cohort, monotonicallyIncreasingTime()));
713 }
714
715 TileController::TileCohort TileController::newestTileCohort() const
716 {
717     return m_cohortList.isEmpty() ? 0 : m_cohortList.last().cohort;
718 }
719
720 TileController::TileCohort TileController::oldestTileCohort() const
721 {
722     return m_cohortList.isEmpty() ? 0 : m_cohortList.first().cohort;
723 }
724
725 void TileController::scheduleCohortRemoval()
726 {
727     const double cohortRemovalTimerSeconds = 1;
728
729     // Start the timer, or reschedule the timer from now if it's already active.
730     if (!m_cohortRemovalTimer.isActive())
731         m_cohortRemovalTimer.startRepeating(cohortRemovalTimerSeconds);
732 }
733
734 void TileController::cohortRemovalTimerFired(Timer<TileController>*)
735 {
736     if (m_cohortList.isEmpty()) {
737         m_cohortRemovalTimer.stop();
738         return;
739     }
740
741     double cohortLifeTimeSeconds = 2;
742     double timeThreshold = monotonicallyIncreasingTime() - cohortLifeTimeSeconds;
743
744     while (!m_cohortList.isEmpty() && m_cohortList.first().creationTime < timeThreshold) {
745         TileCohortInfo firstCohort = m_cohortList.takeFirst();
746         removeTilesInCohort(firstCohort.cohort);
747     }
748
749     if (m_tiledScrollingIndicatorLayer)
750         updateTileCoverageMap();
751 }
752
753 void TileController::ensureTilesForRect(const FloatRect& rect)
754 {
755     if (m_unparentsOffscreenTiles && !m_isInWindow)
756         return;
757
758     PlatformCALayer* platformLayer = PlatformCALayer::platformCALayer(m_tileCacheLayer);
759     if (!platformLayer)
760         return;
761
762     FloatRect scaledRect(rect);
763     scaledRect.scale(m_scale);
764     IntRect rectInTileCoords(enclosingIntRect(scaledRect));
765
766     if (m_primaryTileCoverageRect.contains(rectInTileCoords))
767         return;
768
769     TileIndex topLeft;
770     TileIndex bottomRight;
771     getTileIndexRangeForRect(rectInTileCoords, topLeft, bottomRight);
772
773     Vector<FloatRect> dirtyRects;
774     TileCohort currCohort = nextTileCohort();
775     unsigned tilesInCohort = 0;
776
777     for (int y = topLeft.y(); y <= bottomRight.y(); ++y) {
778         for (int x = topLeft.x(); x <= bottomRight.x(); ++x) {
779             TileIndex tileIndex(x, y);
780
781             IntRect tileRect = rectForTileIndex(tileIndex);
782             TileInfo& tileInfo = m_tiles.add(tileIndex, TileInfo()).iterator->value;
783             if (!tileInfo.layer) {
784                 tileInfo.layer = createTileLayer(tileRect);
785                 [m_tileContainerLayer.get() addSublayer:tileInfo.layer.get()];
786             } else {
787                 if (![tileInfo.layer.get() superlayer])
788                     [m_tileContainerLayer.get() addSublayer:tileInfo.layer.get()];
789
790                 // We already have a layer for this tile. Ensure that its size is correct.
791                 CGSize tileLayerSize = [tileInfo.layer.get() frame].size;
792                 if (tileLayerSize.width >= tileRect.width() && tileLayerSize.height >= tileRect.height())
793                     continue;
794                 [tileInfo.layer.get() setFrame:tileRect];
795             }
796
797             if (!tileRect.intersects(m_primaryTileCoverageRect)) {
798                 tileInfo.cohort = currCohort;
799                 ++tilesInCohort;
800             }
801
802             FloatRect scaledTileRect = tileRect;
803             scaledTileRect.scale(1 / m_scale);
804             dirtyRects.append(scaledTileRect);
805         }
806     }
807     
808     if (tilesInCohort)
809         startedNewCohort(currCohort);
810
811     if (m_tiledScrollingIndicatorLayer)
812         updateTileCoverageMap();
813
814     if (!dirtyRects.isEmpty())
815         platformLayer->owner()->platformCALayerDidCreateTiles(dirtyRects);
816 }
817
818 void TileController::updateTileCoverageMap()
819 {
820     FloatRect containerBounds = bounds();
821     FloatRect visibleRect = this->visibleRect();
822
823     if (m_clipsToExposedRect)
824         visibleRect.intersect(m_exposedRect);
825
826     visibleRect.contract(4, 4); // Layer is positioned 2px from top and left edges.
827
828     float widthScale = 1;
829     float scale = 1;
830     if (!containerBounds.isEmpty()) {
831         widthScale = std::min<float>(visibleRect.width() / containerBounds.width(), 0.1);
832         scale = std::min(widthScale, visibleRect.height() / containerBounds.height());
833     }
834     
835     float indicatorScale = scale * m_scale;
836     FloatRect mapBounds = containerBounds;
837     mapBounds.scale(indicatorScale, indicatorScale);
838     
839     BEGIN_BLOCK_OBJC_EXCEPTIONS
840     
841     if (m_clipsToExposedRect)
842         [m_tiledScrollingIndicatorLayer.get() setPosition:m_exposedRect.location() + FloatPoint(2, 2)];
843     else
844         [m_tiledScrollingIndicatorLayer.get() setPosition:CGPointMake(2, 2)];
845
846     [m_tiledScrollingIndicatorLayer.get() setBounds:mapBounds];
847     [m_tiledScrollingIndicatorLayer.get() setNeedsDisplay];
848
849     visibleRect.scale(indicatorScale, indicatorScale);
850     visibleRect.expand(2, 2);
851     [[m_tiledScrollingIndicatorLayer.get() visibleRectFrameLayer] setFrame:visibleRect];
852
853     Color backgroundColor;
854     switch (m_indicatorMode) {
855     case MainThreadScrollingBecauseOfStyleIndication:
856         backgroundColor = Color(255, 0, 0);
857         break;
858     case MainThreadScrollingBecauseOfEventHandlersIndication:
859         backgroundColor = Color(255, 255, 0);
860         break;
861     case ThreadedScrollingIndication:
862         backgroundColor = Color(0, 200, 0);
863         break;
864     }
865
866     [[m_tiledScrollingIndicatorLayer.get() visibleRectFrameLayer] setBorderColor:cachedCGColor(backgroundColor, ColorSpaceDeviceRGB)];
867
868     END_BLOCK_OBJC_EXCEPTIONS
869 }
870
871 IntRect TileController::tileGridExtent() const
872 {
873     TileIndex topLeft;
874     TileIndex bottomRight;
875     getTileIndexRangeForRect(m_primaryTileCoverageRect, topLeft, bottomRight);
876
877     // Return index of top, left tile and the number of tiles across and down.
878     return IntRect(topLeft.x(), topLeft.y(), bottomRight.x() - topLeft.x() + 1, bottomRight.y() - topLeft.y() + 1);
879 }
880
881 // Return the rect in layer coords, not tile coords.
882 IntRect TileController::tileCoverageRect() const
883 {
884     IntRect coverageRectInLayerCoords(m_primaryTileCoverageRect);
885     coverageRectInLayerCoords.scale(1 / m_scale);
886     return coverageRectInLayerCoords;
887 }
888
889 CALayer *TileController::tiledScrollingIndicatorLayer()
890 {
891     if (!m_tiledScrollingIndicatorLayer) {
892         m_tiledScrollingIndicatorLayer = [WebTiledScrollingIndicatorLayer layer];
893         [m_tiledScrollingIndicatorLayer.get() setTileController:this];
894         [m_tiledScrollingIndicatorLayer.get() setOpacity:0.75];
895         [m_tiledScrollingIndicatorLayer.get() setAnchorPoint:CGPointZero];
896         [m_tiledScrollingIndicatorLayer.get() setBorderColor:cachedCGColor(Color::black, ColorSpaceDeviceRGB)];
897         [m_tiledScrollingIndicatorLayer.get() setBorderWidth:1];
898         [m_tiledScrollingIndicatorLayer.get() setPosition:CGPointMake(2, 2)];
899         updateTileCoverageMap();
900     }
901
902     return m_tiledScrollingIndicatorLayer.get();
903 }
904
905 void TileController::setScrollingModeIndication(ScrollingModeIndication scrollingMode)
906 {
907     if (scrollingMode == m_indicatorMode)
908         return;
909
910     m_indicatorMode = scrollingMode;
911
912     if (m_tiledScrollingIndicatorLayer)
913         updateTileCoverageMap();
914 }
915
916 WebTileLayer* TileController::tileLayerAtIndex(const TileIndex& index) const
917 {
918     return m_tiles.get(index).layer.get();
919 }
920
921 RetainPtr<WebTileLayer> TileController::createTileLayer(const IntRect& tileRect)
922 {
923     RetainPtr<WebTileLayer> layer = LayerPool::sharedPool()->takeLayerWithSize(tileRect.size());
924     if (layer) {
925         // If we were able to restore a layer from the LayerPool, we should call setNeedsDisplay to
926         // ensure we avoid stale content.
927         [layer resetPaintCount];
928         [layer setNeedsDisplay];
929     } else
930         layer = adoptNS([[WebTileLayer alloc] init]);
931     [layer.get() setAnchorPoint:CGPointZero];
932     [layer.get() setFrame:tileRect];
933     [layer.get() setTileController:this];
934     [layer.get() setBorderColor:m_tileDebugBorderColor.get()];
935     [layer.get() setBorderWidth:m_tileDebugBorderWidth];
936     [layer.get() setEdgeAntialiasingMask:0];
937     [layer.get() setOpaque:m_tilesAreOpaque];
938 #ifndef NDEBUG
939     [layer.get() setName:@"Tile"];
940 #endif
941
942 #if PLATFORM(IOS) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
943     [layer.get() setContentsScale:m_deviceScaleFactor];
944     [layer.get() setAcceleratesDrawing:m_acceleratesDrawing];
945 #endif
946
947     return layer;
948 }
949
950 bool TileController::shouldShowRepaintCounters() const
951 {
952     PlatformCALayer* platformLayer = PlatformCALayer::platformCALayer(m_tileCacheLayer);
953     if (!platformLayer)
954         return false;
955
956     WebCore::PlatformCALayerClient* layerContents = platformLayer->owner();
957     ASSERT(layerContents);
958     if (!layerContents)
959         return false;
960
961     return layerContents->platformCALayerShowRepaintCounter(0);
962 }
963
964 void TileController::drawRepaintCounter(WebTileLayer *layer, CGContextRef context)
965 {
966     unsigned paintCount = [layer incrementPaintCount];
967     if (!shouldShowRepaintCounters())
968         return;
969
970     // FIXME: Some of this code could be shared with WebLayer.
971     char text[16]; // that's a lot of repaints
972     snprintf(text, sizeof(text), "%d", paintCount);
973
974     CGRect indicatorBox = [layer bounds];
975     indicatorBox.size.width = 12 + 10 * strlen(text);
976     indicatorBox.size.height = 27;
977     CGContextSaveGState(context);
978
979     CGContextSetAlpha(context, 0.5f);
980     CGContextBeginTransparencyLayerWithRect(context, indicatorBox, 0);
981
982     CGContextSetFillColorWithColor(context, m_tileDebugBorderColor.get());
983     CGContextFillRect(context, indicatorBox);
984
985     PlatformCALayer* platformLayer = PlatformCALayer::platformCALayer(m_tileCacheLayer);
986
987     if (platformLayer->acceleratesDrawing())
988         CGContextSetRGBFillColor(context, 1, 0, 0, 1);
989     else
990         CGContextSetRGBFillColor(context, 1, 1, 1, 1);
991
992 #pragma clang diagnostic push
993 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
994     CGContextSetTextMatrix(context, CGAffineTransformMakeScale(1, -1));
995     CGContextSelectFont(context, "Helvetica", 22, kCGEncodingMacRoman);
996     CGContextShowTextAtPoint(context, indicatorBox.origin.x + 5, indicatorBox.origin.y + 22, text, strlen(text));
997 #pragma clang diagnostic pop
998
999     CGContextEndTransparencyLayer(context);
1000     CGContextRestoreGState(context);
1001 }
1002
1003 void TileController::drawTileMapContents(CGContextRef context, CGRect layerBounds)
1004 {
1005     CGContextSetRGBFillColor(context, 0.3, 0.3, 0.3, 1);
1006     CGContextFillRect(context, layerBounds);
1007
1008     CGFloat scaleFactor = layerBounds.size.width / bounds().width();
1009
1010     CGFloat contextScale = scaleFactor / scale();
1011     CGContextScaleCTM(context, contextScale, contextScale);
1012     
1013     for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
1014         const TileInfo& tileInfo = it->value;
1015         WebTileLayer* tileLayer = tileInfo.layer.get();
1016
1017         CGFloat red = 1;
1018         CGFloat green = 1;
1019         CGFloat blue = 1;
1020         if (tileInfo.hasStaleContent) {
1021             red = 0.25;
1022             green = 0.125;
1023             blue = 0;
1024         }
1025
1026         TileCohort newestCohort = newestTileCohort();
1027         TileCohort oldestCohort = oldestTileCohort();
1028
1029         if (!m_aggressivelyRetainsTiles && tileInfo.cohort != VisibleTileCohort && newestCohort > oldestCohort) {
1030             float cohortProportion = static_cast<float>((newestCohort - tileInfo.cohort)) / (newestCohort - oldestCohort);
1031             CGContextSetRGBFillColor(context, red, green, blue, 1 - cohortProportion);
1032         } else
1033             CGContextSetRGBFillColor(context, red, green, blue, 1);
1034
1035         if ([tileLayer superlayer]) {
1036             CGContextSetLineWidth(context, 0.5 / contextScale);
1037             CGContextSetRGBStrokeColor(context, 0, 0, 0, 1);
1038         } else {
1039             CGContextSetLineWidth(context, 1 / contextScale);
1040             CGContextSetRGBStrokeColor(context, 0.2, 0.1, 0.9, 1);
1041         }
1042
1043         CGRect frame = [tileLayer frame];
1044         CGContextFillRect(context, frame);
1045         CGContextStrokeRect(context, frame);
1046     }
1047 }
1048     
1049
1050 } // namespace WebCore