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