[WTF] Import std::optional reference implementation as WTF::Optional
[WebKit-https.git] / Source / WebCore / platform / ios / LegacyTileCache.mm
1 /*
2  * Copyright (C) 2009, 2014 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "LegacyTileCache.h"
28
29 #if PLATFORM(IOS)
30
31 #include <CoreText/CoreText.h>
32 #include "FontAntialiasingStateSaver.h"
33 #include "LegacyTileGrid.h"
34 #include "LegacyTileGridTile.h"
35 #include "LegacyTileLayer.h"
36 #include "LegacyTileLayerPool.h"
37 #include "Logging.h"
38 #include "MemoryPressureHandler.h"
39 #include "QuartzCoreSPI.h"
40 #include "SystemMemory.h"
41 #include "WAKWindow.h"
42 #include "WKGraphics.h"
43 #include "WebCoreSystemInterface.h"
44 #include "WebCoreThreadRun.h"
45 #include <wtf/CurrentTime.h>
46 #include <wtf/RAMSize.h>
47
48 @interface WAKView (WebViewExtras)
49 - (void)_dispatchTileDidDraw:(CALayer*)tile;
50 - (void)_willStartScrollingOrZooming;
51 - (void)_didFinishScrollingOrZooming;
52 - (void)_dispatchTileDidDraw;
53 - (void)_scheduleLayerFlushForPendingTileCacheRepaint;
54 @end
55
56 @interface LegacyTileCacheTombstone : NSObject {
57     BOOL dead;
58 }
59 @property(getter=isDead) BOOL dead;
60
61 @end
62
63 @implementation LegacyTileCacheTombstone
64
65 @synthesize dead;
66
67 @end
68
69 namespace WebCore {
70
71 LegacyTileCache::LegacyTileCache(WAKWindow* window)
72     : m_window(window)
73     , m_keepsZoomedOutTiles(false)
74     , m_hasPendingLayoutTiles(false)
75     , m_hasPendingUpdateTilingMode(false)
76     , m_tombstone(adoptNS([[LegacyTileCacheTombstone alloc] init]))
77     , m_tilingMode(Normal)
78     , m_tilingDirection(TilingDirectionDown)
79     , m_tileSize(512, 512)
80     , m_tilesOpaque(true)
81     , m_tileBordersVisible(false)
82     , m_tilePaintCountersVisible(false)
83     , m_acceleratedDrawingEnabled(false)
84     , m_isSpeculativeTileCreationEnabled(true)
85     , m_didCallWillStartScrollingOrZooming(false)
86     , m_zoomedOutTileGrid(std::make_unique<LegacyTileGrid>(*this, m_tileSize))
87     , m_tileCreationTimer(*this, &LegacyTileCache::tileCreationTimerFired)
88     , m_currentScale(1.f)
89     , m_pendingScale(0)
90     , m_pendingZoomedOutScale(0)
91     , m_tileControllerShouldUseLowScaleTiles(false)
92 {
93     [hostLayer() insertSublayer:m_zoomedOutTileGrid->tileHostLayer() atIndex:0];
94     hostLayerSizeChanged();
95 }
96
97 LegacyTileCache::~LegacyTileCache()
98 {
99     [m_tombstone.get() setDead:true];
100 }
101
102 CGFloat LegacyTileCache::screenScale() const
103 {
104     return [m_window screenScale];
105 }
106
107 CALayer* LegacyTileCache::hostLayer() const
108 {
109     return [m_window hostLayer];
110 }
111
112 FloatRect LegacyTileCache::visibleRectInLayer(CALayer *layer) const
113 {
114     if (m_overrideVisibleRect)
115         return [layer convertRect:m_overrideVisibleRect.value() fromLayer:hostLayer()];
116
117     return [layer convertRect:[m_window extendedVisibleRect] fromLayer:hostLayer()];
118 }
119
120 void LegacyTileCache::setOverrideVisibleRect(std::optional<FloatRect> rect)
121 {
122     m_overrideVisibleRect = rect;
123 }
124
125 bool LegacyTileCache::tilesOpaque() const
126 {
127     return m_tilesOpaque;
128 }
129     
130 LegacyTileGrid* LegacyTileCache::activeTileGrid() const
131 {
132     if (!m_keepsZoomedOutTiles)
133         return m_zoomedOutTileGrid.get();
134     if (m_tilingMode == Zooming)
135         return m_zoomedOutTileGrid.get();
136     if (m_zoomedInTileGrid && m_currentScale == m_zoomedInTileGrid->scale())
137         return m_zoomedInTileGrid.get();
138     return m_zoomedOutTileGrid.get();
139 }
140
141 LegacyTileGrid* LegacyTileCache::inactiveTileGrid() const
142 {
143     return activeTileGrid() == m_zoomedOutTileGrid.get() ? m_zoomedInTileGrid.get() : m_zoomedOutTileGrid.get();
144 }
145
146 void LegacyTileCache::setTilesOpaque(bool opaque)
147 {
148     if (m_tilesOpaque == opaque)
149         return;
150
151     LockHolder locker(m_tileMutex);
152
153     m_tilesOpaque = opaque;
154     m_zoomedOutTileGrid->updateTileOpacity();
155     if (m_zoomedInTileGrid)
156         m_zoomedInTileGrid->updateTileOpacity();
157 }
158
159 void LegacyTileCache::doLayoutTiles()
160 {
161     if (isTileCreationSuspended())
162         return;
163
164     LockHolder locker(m_tileMutex);
165     LegacyTileGrid* activeGrid = activeTileGrid();
166     // Even though we aren't actually creating tiles in the inactive grid, we
167     // still need to drop invalid tiles in response to a layout.
168     // See <rdar://problem/9839867>.
169     if (LegacyTileGrid* inactiveGrid = inactiveTileGrid())
170         inactiveGrid->dropInvalidTiles();
171     if (activeGrid->checkDoSingleTileLayout())
172         return;
173     createTilesInActiveGrid(CoverVisibleOnly);
174 }
175
176 void LegacyTileCache::hostLayerSizeChanged()
177 {
178     m_zoomedOutTileGrid->updateHostLayerSize();
179     if (m_zoomedInTileGrid)
180         m_zoomedInTileGrid->updateHostLayerSize();
181 }
182
183 void LegacyTileCache::setKeepsZoomedOutTiles(bool keep)
184 {
185     m_keepsZoomedOutTiles = keep;
186 }
187
188 void LegacyTileCache::setCurrentScale(float scale)
189 {
190     ASSERT(scale > 0);
191
192     if (currentScale() == scale) {
193         m_pendingScale = 0;
194         return;
195     }
196     m_pendingScale = scale;
197     if (m_tilingMode == Disabled)
198         return;
199     commitScaleChange();
200
201     if (!keepsZoomedOutTiles() && !isTileInvalidationSuspended()) {
202         // Tile invalidation is normally suspended during zooming by UIKit but some applications
203         // using custom scrollviews may zoom without triggering the callbacks. Invalidate the tiles explicitly.
204         LockHolder locker(m_tileMutex);
205         activeTileGrid()->dropAllTiles();
206         activeTileGrid()->createTiles(CoverVisibleOnly);
207     }
208 }
209
210 void LegacyTileCache::setZoomedOutScale(float scale)
211 {
212     ASSERT(scale > 0);
213
214     if (zoomedOutScale() == scale) {
215         m_pendingZoomedOutScale = 0;
216         return;
217     }
218     m_pendingZoomedOutScale = scale;
219     if (m_tilingMode == Disabled)
220         return;
221     commitScaleChange();
222 }
223     
224 void LegacyTileCache::commitScaleChange()
225 {
226     ASSERT(m_pendingZoomedOutScale || m_pendingScale);
227     ASSERT(m_tilingMode != Disabled);
228     
229     LockHolder locker(m_tileMutex);
230
231     if (m_pendingZoomedOutScale) {
232         m_zoomedOutTileGrid->setScale(m_pendingZoomedOutScale);
233         m_pendingZoomedOutScale = 0;
234     }
235     
236     if (!m_keepsZoomedOutTiles) {
237         ASSERT(activeTileGrid() == m_zoomedOutTileGrid.get());
238         if (m_pendingScale) {
239             m_currentScale = m_pendingScale;
240             m_zoomedOutTileGrid->setScale(m_currentScale);
241         }
242         m_pendingScale = 0;
243         return;
244     }
245
246     if (m_pendingScale) {
247         m_currentScale = m_pendingScale;
248         m_pendingScale = 0;
249     }
250
251     if (m_currentScale != m_zoomedOutTileGrid->scale()) {
252         if (!m_zoomedInTileGrid) {
253             m_zoomedInTileGrid = std::make_unique<LegacyTileGrid>(*this, m_tileSize);
254             [hostLayer() addSublayer:m_zoomedInTileGrid->tileHostLayer()];
255             hostLayerSizeChanged();
256         }
257         m_zoomedInTileGrid->setScale(m_currentScale);
258     }
259
260     // Keep the current ordering during zooming.
261     if (m_tilingMode != Zooming)
262         bringActiveTileGridToFront();
263
264     adjustTileGridTransforms();
265     layoutTiles();
266 }
267
268 void LegacyTileCache::bringActiveTileGridToFront()
269 {
270     LegacyTileGrid* activeGrid = activeTileGrid();
271     LegacyTileGrid* otherGrid = inactiveTileGrid();
272     if (!otherGrid)
273         return;
274     CALayer* frontLayer = activeGrid->tileHostLayer();
275     CALayer* otherLayer = otherGrid->tileHostLayer();
276     [hostLayer() insertSublayer:frontLayer above:otherLayer];
277 }
278     
279 void LegacyTileCache::adjustTileGridTransforms()
280 {
281     CALayer* zoomedOutHostLayer = m_zoomedOutTileGrid->tileHostLayer();
282     float transformScale = currentScale() / zoomedOutScale();
283     [zoomedOutHostLayer setTransform:CATransform3DMakeScale(transformScale, transformScale, 1.0f)];
284     m_zoomedOutTileGrid->updateHostLayerSize();
285 }
286
287 void LegacyTileCache::layoutTiles()
288 {
289     if (m_hasPendingLayoutTiles)
290         return;
291     m_hasPendingLayoutTiles = true;
292
293     LegacyTileCacheTombstone *tombstone = m_tombstone.get();
294     WebThreadRun(^{
295         if ([tombstone isDead])
296             return;
297         m_hasPendingLayoutTiles = false;
298         doLayoutTiles();
299     });
300 }
301
302 void LegacyTileCache::layoutTilesNow()
303 {
304     ASSERT(WebThreadIsLockedOrDisabled());
305
306     // layoutTilesNow() is called after a zoom, while the tile mode is still set to Zooming.
307     // If we checked for isTileCreationSuspended here, that would cause <rdar://problem/8434112> (Page flashes after zooming in/out).
308     if (m_tilingMode == Disabled)
309         return;
310     
311     // FIXME: layoutTilesNow should be called after state has been set to non-zooming and the active grid is the final one. 
312     // Fix this in UIKit side (perhaps also getting rid of this call) and remove this code afterwards.
313     // <rdar://problem/9672993>
314     TilingMode savedTilingMode = m_tilingMode;
315     if (m_tilingMode == Zooming)
316         m_tilingMode = Minimal;
317
318     LockHolder locker(m_tileMutex);
319     LegacyTileGrid* activeGrid = activeTileGrid();
320     if (activeGrid->checkDoSingleTileLayout()) {
321         m_tilingMode = savedTilingMode;
322         return;
323     }
324     createTilesInActiveGrid(CoverVisibleOnly);
325     m_tilingMode = savedTilingMode;
326 }
327
328 void LegacyTileCache::layoutTilesNowForRect(const IntRect& rect)
329 {
330     ASSERT(WebThreadIsLockedOrDisabled());
331     LockHolder locker(m_tileMutex);
332
333     activeTileGrid()->addTilesCoveringRect(rect);
334 }
335
336 void LegacyTileCache::removeAllNonVisibleTiles()
337 {
338     LockHolder locker(m_tileMutex);
339     removeAllNonVisibleTilesInternal();
340 }
341
342 void LegacyTileCache::removeAllNonVisibleTilesInternal()
343 {
344     LegacyTileGrid* activeGrid = activeTileGrid();
345     if (keepsZoomedOutTiles() && activeGrid == m_zoomedInTileGrid.get() && activeGrid->hasTiles())
346         m_zoomedOutTileGrid->dropAllTiles();
347
348     IntRect activeTileBounds = activeGrid->bounds();
349     if (activeTileBounds.width() <= m_tileSize.width() && activeTileBounds.height() <= m_tileSize.height()) {
350         // If the view is smaller than a tile, keep the tile even if it is not visible.
351         activeGrid->dropTilesOutsideRect(activeTileBounds);
352         return;
353     }
354
355     activeGrid->dropTilesOutsideRect(activeGrid->visibleRect());
356 }
357
358 void LegacyTileCache::removeAllTiles()
359 {
360     LockHolder locker(m_tileMutex);
361     m_zoomedOutTileGrid->dropAllTiles();
362     if (m_zoomedInTileGrid)
363         m_zoomedInTileGrid->dropAllTiles();
364 }
365
366 void LegacyTileCache::removeForegroundTiles()
367 {
368     LockHolder locker(m_tileMutex);
369     if (!keepsZoomedOutTiles())
370         m_zoomedOutTileGrid->dropAllTiles();
371     if (m_zoomedInTileGrid)
372         m_zoomedInTileGrid->dropAllTiles();
373 }
374
375 void LegacyTileCache::setContentReplacementImage(RetainPtr<CGImageRef> contentReplacementImage)
376 {
377     LockHolder locker(m_contentReplacementImageMutex);
378     m_contentReplacementImage = contentReplacementImage;
379 }
380
381 RetainPtr<CGImageRef> LegacyTileCache::contentReplacementImage() const
382 {
383     LockHolder locker(m_contentReplacementImageMutex);
384     return m_contentReplacementImage;
385 }
386
387 void LegacyTileCache::setTileBordersVisible(bool flag)
388 {
389     if (flag == m_tileBordersVisible)
390         return;
391
392     m_tileBordersVisible = flag;
393     m_zoomedOutTileGrid->updateTileBorderVisibility();
394     if (m_zoomedInTileGrid)
395         m_zoomedInTileGrid->updateTileBorderVisibility();
396 }
397
398 void LegacyTileCache::setTilePaintCountersVisible(bool flag)
399 {
400     m_tilePaintCountersVisible = flag;
401     // The numbers will show up the next time the tiles paint.
402 }
403
404 void LegacyTileCache::finishedCreatingTiles(bool didCreateTiles, bool createMore)
405 {
406     // We need to ensure that all tiles are showing the same version of the content.
407     if (didCreateTiles && !m_savedDisplayRects.isEmpty())
408         flushSavedDisplayRects();
409
410     if (keepsZoomedOutTiles()) {
411         if (m_zoomedInTileGrid && activeTileGrid() == m_zoomedOutTileGrid.get() && m_tilingMode != Zooming && m_zoomedInTileGrid->hasTiles()) {
412             // This CA transaction will cover the screen with top level tiles.
413             // We can remove zoomed-in tiles without flashing.
414             m_zoomedInTileGrid->dropAllTiles();
415         } else if (activeTileGrid() == m_zoomedInTileGrid.get()) {
416             // Pass the minimum possible distance to consider all tiles, even visible ones.
417             m_zoomedOutTileGrid->dropDistantTiles(0, std::numeric_limits<double>::min());
418         }
419     }
420
421     // Keep creating tiles until the whole coverRect is covered.
422     if (createMore)
423         m_tileCreationTimer.startOneShot(0);
424 }
425
426 void LegacyTileCache::tileCreationTimerFired()
427 {
428     if (isTileCreationSuspended())
429         return;
430     LockHolder locker(m_tileMutex);
431     createTilesInActiveGrid(CoverSpeculative);
432 }
433
434 void LegacyTileCache::createTilesInActiveGrid(SynchronousTileCreationMode mode)
435 {
436     if (MemoryPressureHandler::singleton().isUnderMemoryPressure()) {
437         LOG(MemoryPressure, "Under memory pressure at: %s", __PRETTY_FUNCTION__);
438         removeAllNonVisibleTilesInternal();
439     }
440     activeTileGrid()->createTiles(mode);
441 }
442
443 unsigned LegacyTileCache::tileCapacityForGrid(LegacyTileGrid* grid)
444 {
445     static unsigned capacity;
446     if (!capacity) {
447         size_t totalMemory = ramSize() / 1024 / 1024;
448         if (totalMemory >= 1024)
449             capacity = 128 * 1024 * 1024;
450         else if (totalMemory >= 512)
451             capacity = 64 * 1024 * 1024;
452         else
453             capacity = 32 * 1024 * 1024;
454     }
455
456     int gridCapacity;
457
458     int memoryLevel = systemMemoryLevel();
459     if (memoryLevel < 15)
460         gridCapacity = capacity / 4;
461     else if (memoryLevel < 20)
462         gridCapacity = capacity / 2;
463     else if (memoryLevel < 30) 
464         gridCapacity = capacity * 3 / 4;
465     else
466         gridCapacity = capacity;
467
468     if (keepsZoomedOutTiles() && grid == m_zoomedOutTileGrid.get()) {
469         if (activeTileGrid() == m_zoomedOutTileGrid.get())
470             return gridCapacity;
471         return gridCapacity / 4;
472     }
473     return gridCapacity * 3 / 4;
474 }
475
476 Color LegacyTileCache::colorForGridTileBorder(LegacyTileGrid* grid) const
477 {
478     if (grid == m_zoomedOutTileGrid.get())
479         return Color(.3f, .0f, 0.4f, 0.5f);
480
481     return Color(.0f, .0f, 0.4f, 0.5f);
482 }
483
484 static bool shouldRepaintInPieces(const CGRect& dirtyRect, CGSRegionObj dirtyRegion, CGFloat contentsScale)
485 {
486     // Estimate whether or not we should use the unioned rect or the individual rects.
487     // We do this by computing the percentage of "wasted space" in the union. If that wasted space
488     // is too large, then we will do individual rect painting instead.
489     float singlePixels = 0;
490     unsigned rectCount = 0;
491
492     CGSRegionEnumeratorObj enumerator = CGSRegionEnumerator(dirtyRegion);
493     CGRect *subRect;
494     while ((subRect = CGSNextRect(enumerator))) {
495         ++rectCount;
496         singlePixels += subRect->size.width * subRect->size.height;
497     }
498     singlePixels /= (contentsScale * contentsScale);
499     CGSReleaseRegionEnumerator(enumerator);
500
501     const unsigned cRectThreshold = 10;
502     if (rectCount < 2 || rectCount > cRectThreshold)
503         return false;
504
505     const float cWastedSpaceThreshold = 0.50f;
506     float unionPixels = dirtyRect.size.width * dirtyRect.size.height;
507     float wastedSpace = 1.f - (singlePixels / unionPixels);
508     return wastedSpace > cWastedSpaceThreshold;
509 }
510
511 void LegacyTileCache::drawReplacementImage(LegacyTileLayer* layer, CGContextRef context, CGImageRef image)
512 {
513     CGContextSetRGBFillColor(context, 1, 1, 1, 1);
514     CGContextFillRect(context, CGContextGetClipBoundingBox(context));
515
516     CGFloat contentsScale = [layer contentsScale];
517     CGContextScaleCTM(context, 1 / contentsScale, -1 / contentsScale);
518     CGRect imageRect = CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image));
519     CGContextTranslateCTM(context, 0, -imageRect.size.height);
520     CGContextDrawImage(context, imageRect, image);
521 }
522
523 void LegacyTileCache::drawWindowContent(LegacyTileLayer* layer, CGContextRef context, CGRect dirtyRect)
524 {
525     CGRect frame = [layer frame];
526     FontAntialiasingStateSaver fontAntialiasingState(context, [m_window useOrientationDependentFontAntialiasing] && [layer isOpaque]);
527     fontAntialiasingState.setup([WAKWindow hasLandscapeOrientation]);
528
529     CGSRegionObj drawRegion = (CGSRegionObj)[layer regionBeingDrawn];
530     CGFloat contentsScale = [layer contentsScale];
531     if (drawRegion && shouldRepaintInPieces(dirtyRect, drawRegion, contentsScale)) {
532         // Use fine grained repaint rectangles to minimize the amount of painted pixels.
533         CGSRegionEnumeratorObj enumerator = CGSRegionEnumerator(drawRegion);
534         CGRect *subRect;
535         while ((subRect = CGSNextRect(enumerator))) {
536             CGRect adjustedSubRect = *subRect;
537             adjustedSubRect.origin.x /= contentsScale;
538             adjustedSubRect.origin.y = frame.size.height - (adjustedSubRect.origin.y + adjustedSubRect.size.height) / contentsScale;
539             adjustedSubRect.size.width /= contentsScale;
540             adjustedSubRect.size.height /= contentsScale;
541
542             CGRect subRectInSuper = [hostLayer() convertRect:adjustedSubRect fromLayer:layer];
543             [m_window displayRect:subRectInSuper];
544         }
545         CGSReleaseRegionEnumerator(enumerator);
546     } else {
547         // Simple repaint
548         CGRect dirtyRectInSuper = [hostLayer() convertRect:dirtyRect fromLayer:layer];
549         [m_window displayRect:dirtyRectInSuper];
550     }
551
552     fontAntialiasingState.restore();
553 }
554
555 void LegacyTileCache::drawLayer(LegacyTileLayer* layer, CGContextRef context)
556 {
557     // The web lock unlock observer runs after CA commit observer.
558     if (!WebThreadIsLockedOrDisabled()) {
559         LOG_ERROR("Drawing without holding the web thread lock");
560         ASSERT_NOT_REACHED();
561     }
562
563     WKSetCurrentGraphicsContext(context);
564
565     CGRect dirtyRect = CGContextGetClipBoundingBox(context);
566     CGRect frame = [layer frame];
567     CGContextTranslateCTM(context, -frame.origin.x, -frame.origin.y);
568     CGRect scaledFrame = [hostLayer() convertRect:[layer bounds] fromLayer:layer];
569     CGContextScaleCTM(context, frame.size.width / scaledFrame.size.width, frame.size.height / scaledFrame.size.height);
570
571     if (RetainPtr<CGImage> contentReplacementImage = this->contentReplacementImage())
572         drawReplacementImage(layer, context, contentReplacementImage.get());
573     else
574         drawWindowContent(layer, context, dirtyRect);
575
576     ++layer.paintCount;
577     if (m_tilePaintCountersVisible) {
578         char text[16];
579         snprintf(text, sizeof(text), "%d", layer.paintCount);
580
581         CGContextSaveGState(context);
582
583         CGContextTranslateCTM(context, frame.origin.x, frame.origin.y);
584         CGContextSetFillColorWithColor(context, cachedCGColor(colorForGridTileBorder([layer tileGrid])));
585         
586         CGRect labelBounds = [layer bounds];
587         labelBounds.size.width = 10 + 12 * strlen(text);
588         labelBounds.size.height = 25;
589         CGContextFillRect(context, labelBounds);
590
591         if (acceleratedDrawingEnabled())
592             CGContextSetRGBFillColor(context, 1, 0, 0, 0.4f);
593         else
594             CGContextSetRGBFillColor(context, 1, 1, 1, 0.6f);
595
596         auto matrix = CGAffineTransformMakeScale(1, -1);
597         auto font = adoptCF(CTFontCreateWithName(CFSTR("Helvetica"), 25, &matrix));
598         CFTypeRef keys[] = { kCTFontAttributeName, kCTForegroundColorFromContextAttributeName };
599         CFTypeRef values[] = { font.get(), kCFBooleanTrue };
600         auto attributes = adoptCF(CFDictionaryCreate(kCFAllocatorDefault, keys, values, WTF_ARRAY_LENGTH(keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
601         auto string = adoptCF(CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, reinterpret_cast<const UInt8*>(text), strlen(text), kCFStringEncodingUTF8, false, kCFAllocatorNull));
602         auto attributedString = adoptCF(CFAttributedStringCreate(kCFAllocatorDefault, string.get(), attributes.get()));
603         auto line = adoptCF(CTLineCreateWithAttributedString(attributedString.get()));
604         CGContextSetTextPosition(context, labelBounds.origin.x + 3, labelBounds.origin.y + 20);
605         CTLineDraw(line.get(), context);
606     
607         CGContextRestoreGState(context);        
608     }
609
610     WAKView* view = [m_window contentView];
611     [view performSelector:@selector(_dispatchTileDidDraw:) withObject:layer afterDelay:0.0];
612 }
613
614 void LegacyTileCache::setNeedsDisplay()
615 {
616     setNeedsDisplayInRect(IntRect(0, 0, std::numeric_limits<int>::max(), std::numeric_limits<int>::max()));
617 }
618
619 void LegacyTileCache::scheduleLayerFlushForPendingRepaint()
620 {
621     WAKView* view = [m_window contentView];
622     [view _scheduleLayerFlushForPendingTileCacheRepaint];
623 }
624
625 void LegacyTileCache::setNeedsDisplayInRect(const IntRect& dirtyRect)
626 {
627     LockHolder locker(m_savedDisplayRectMutex);
628     bool addedFirstRect = m_savedDisplayRects.isEmpty();
629     m_savedDisplayRects.append(dirtyRect);
630     if (!addedFirstRect)
631         return;
632     // Compositing layer flush will call back to doPendingRepaints(). The flush may be throttled and not happen immediately.
633     scheduleLayerFlushForPendingRepaint();
634 }
635
636 void LegacyTileCache::invalidateTiles(const IntRect& dirtyRect)
637 {
638     ASSERT(!m_tileMutex.tryLock());
639
640     LegacyTileGrid* activeGrid = activeTileGrid();
641     if (!keepsZoomedOutTiles()) {
642         activeGrid->invalidateTiles(dirtyRect);
643         return;
644     }
645     FloatRect scaledRect = dirtyRect;
646     scaledRect.scale(zoomedOutScale() / currentScale());
647     IntRect zoomedOutDirtyRect = enclosingIntRect(scaledRect);
648     if (activeGrid == m_zoomedOutTileGrid.get()) {
649         bool dummy;
650         IntRect coverRect = m_zoomedOutTileGrid->calculateCoverRect(m_zoomedOutTileGrid->visibleRect(), dummy);
651         // Instead of repainting a tile outside the cover rect, just remove it.
652         m_zoomedOutTileGrid->dropTilesBetweenRects(zoomedOutDirtyRect, coverRect);
653         m_zoomedOutTileGrid->invalidateTiles(zoomedOutDirtyRect);
654         // We need to invalidate zoomed in tiles as well while zooming, since
655         // we could switch back to the zoomed in grid without dropping its
656         // tiles.  See <rdar://problem/9946759>.
657         if (m_tilingMode == Zooming && m_zoomedInTileGrid)
658             m_zoomedInTileGrid->invalidateTiles(dirtyRect);
659         return;
660     }
661     if (!m_zoomedInTileGrid->hasTiles()) {
662         // If no tiles have been created yet for the zoomed in grid, we can't drop the zoomed out tiles.
663         m_zoomedOutTileGrid->invalidateTiles(zoomedOutDirtyRect);
664         return;
665     }
666     m_zoomedOutTileGrid->dropTilesIntersectingRect(zoomedOutDirtyRect);
667     m_zoomedInTileGrid->invalidateTiles(dirtyRect);
668 }
669     
670 bool LegacyTileCache::isTileCreationSuspended() const 
671 {
672     return (!keepsZoomedOutTiles() && m_tilingMode == Zooming) || m_tilingMode == Disabled;
673 }
674
675 bool LegacyTileCache::isTileInvalidationSuspended() const 
676
677     return m_tilingMode == Zooming || m_tilingMode == Panning || m_tilingMode == ScrollToTop || m_tilingMode == Disabled; 
678 }
679
680 void LegacyTileCache::updateTilingMode()
681 {
682     ASSERT(WebThreadIsCurrent() || !WebThreadIsEnabled());
683
684     WAKView* view = [m_window contentView];
685
686     if (m_tilingMode == Zooming || m_tilingMode == Panning || m_tilingMode == ScrollToTop) {
687         if (!m_didCallWillStartScrollingOrZooming) {
688             [view _willStartScrollingOrZooming];
689             m_didCallWillStartScrollingOrZooming = true;
690         }
691     } else {
692         if (m_didCallWillStartScrollingOrZooming) {
693             [view _didFinishScrollingOrZooming];
694             m_didCallWillStartScrollingOrZooming = false;
695         }
696         if (m_tilingMode == Disabled)
697             return;
698
699         LockHolder locker(m_tileMutex);
700         createTilesInActiveGrid(CoverVisibleOnly);
701
702         if (!m_savedDisplayRects.isEmpty())
703             scheduleLayerFlushForPendingRepaint();
704     }
705 }
706
707 void LegacyTileCache::setTilingMode(TilingMode tilingMode)
708 {
709     if (tilingMode == m_tilingMode)
710         return;
711     bool wasZooming = (m_tilingMode == Zooming);
712     m_tilingMode = tilingMode;
713
714     if ((m_pendingZoomedOutScale || m_pendingScale) && m_tilingMode != Disabled)
715         commitScaleChange();
716     else if (wasZooming) {
717         LockHolder locker(m_tileMutex);
718         bringActiveTileGridToFront();
719     }
720
721     if (m_hasPendingUpdateTilingMode)
722         return;
723     m_hasPendingUpdateTilingMode = true;
724
725     LegacyTileCacheTombstone *tombstone = m_tombstone.get();
726     WebThreadRun(^{
727         if ([tombstone isDead])
728             return;
729         m_hasPendingUpdateTilingMode = false;
730         updateTilingMode();
731     });
732 }
733
734 void LegacyTileCache::setTilingDirection(TilingDirection tilingDirection)
735 {
736     m_tilingDirection = tilingDirection;
737 }
738
739 LegacyTileCache::TilingDirection LegacyTileCache::tilingDirection() const
740 {
741     return m_tilingDirection;
742 }
743     
744 float LegacyTileCache::zoomedOutScale() const
745 {
746     return m_zoomedOutTileGrid->scale();
747 }
748
749 float LegacyTileCache::currentScale() const
750 {
751     return m_currentScale;
752 }
753
754 void LegacyTileCache::doPendingRepaints()
755 {
756     if (m_savedDisplayRects.isEmpty())
757         return;
758     if (isTileInvalidationSuspended())
759         return;
760     LockHolder locker(m_tileMutex);
761     flushSavedDisplayRects();
762 }
763
764 void LegacyTileCache::flushSavedDisplayRects()
765 {
766     ASSERT(!m_tileMutex.tryLock());
767     ASSERT(!m_savedDisplayRects.isEmpty());
768
769     Vector<IntRect> rects;
770     {
771         LockHolder locker(m_savedDisplayRectMutex);
772         m_savedDisplayRects.swap(rects);
773     }
774     size_t size = rects.size();
775     for (size_t n = 0; n < size; ++n)
776         invalidateTiles(rects[n]);
777 }
778
779 void LegacyTileCache::setSpeculativeTileCreationEnabled(bool enabled)
780 {
781     if (m_isSpeculativeTileCreationEnabled == enabled)
782         return;
783     m_isSpeculativeTileCreationEnabled = enabled;
784     if (m_isSpeculativeTileCreationEnabled)
785         m_tileCreationTimer.startOneShot(0);
786 }
787
788 bool LegacyTileCache::hasPendingDraw() const
789 {
790     return !m_savedDisplayRects.isEmpty();
791 }
792
793 void LegacyTileCache::prepareToDraw()
794 {
795     // This will trigger document relayout if needed.
796     [[m_window contentView] viewWillDraw];
797
798     if (!m_savedDisplayRects.isEmpty()) {
799         LockHolder locker(m_tileMutex);
800         flushSavedDisplayRects();
801     }
802 }
803
804 void LegacyTileCache::setLayerPoolCapacity(unsigned capacity)
805 {
806     LegacyTileLayerPool::sharedPool()->setCapacity(capacity);
807 }
808
809 void LegacyTileCache::drainLayerPool()
810 {
811     LegacyTileLayerPool::sharedPool()->drain();
812 }
813
814 void LegacyTileCache::dumpTiles()
815 {
816     NSLog(@"=================");
817     NSLog(@"ZOOMED OUT");
818     if (m_zoomedOutTileGrid.get() == activeTileGrid())
819         NSLog(@"<ACTIVE>");
820     m_zoomedOutTileGrid->dumpTiles();
821     NSLog(@"=================");
822     if (m_zoomedInTileGrid) {
823         NSLog(@"ZOOMED IN");
824         if (m_zoomedInTileGrid.get() == activeTileGrid())
825             NSLog(@"<ACTIVE>");
826         m_zoomedInTileGrid->dumpTiles();
827         NSLog(@"=================");
828     }
829 }
830
831 } // namespace WebCore
832
833 #endif // PLATFORM(IOS)