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