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