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