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