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