| /* |
| * Copyright (C) 2009, 2014 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "LegacyTileCache.h" |
| |
| #if PLATFORM(IOS_FAMILY) |
| |
| #include "FontAntialiasingStateSaver.h" |
| #include "LegacyTileGrid.h" |
| #include "LegacyTileGridTile.h" |
| #include "LegacyTileLayer.h" |
| #include "LegacyTileLayerPool.h" |
| #include "Logging.h" |
| #include "SystemMemory.h" |
| #include "WAKWindow.h" |
| #include "WKGraphics.h" |
| #include "WebCoreThreadRun.h" |
| #include <CoreText/CoreText.h> |
| #include <pal/spi/cocoa/QuartzCoreSPI.h> |
| #include <wtf/MemoryPressureHandler.h> |
| #include <wtf/RAMSize.h> |
| |
| @interface WAKView (WebViewExtras) |
| - (void)_dispatchTileDidDraw:(CALayer*)tile; |
| - (void)_willStartScrollingOrZooming; |
| - (void)_didFinishScrollingOrZooming; |
| - (void)_dispatchTileDidDraw; |
| - (void)_scheduleLayerFlushForPendingTileCacheRepaint; |
| @end |
| |
| @interface LegacyTileCacheTombstone : NSObject { |
| BOOL dead; |
| } |
| @property(getter=isDead) BOOL dead; |
| |
| @end |
| |
| @implementation LegacyTileCacheTombstone |
| |
| @synthesize dead; |
| |
| @end |
| |
| namespace WebCore { |
| |
| LegacyTileCache::LegacyTileCache(WAKWindow* window) |
| : m_window(window) |
| , m_tombstone(adoptNS([[LegacyTileCacheTombstone alloc] init])) |
| , m_zoomedOutTileGrid(makeUnique<LegacyTileGrid>(*this, m_tileSize)) |
| , m_tileCreationTimer(*this, &LegacyTileCache::tileCreationTimerFired) |
| { |
| [hostLayer() insertSublayer:m_zoomedOutTileGrid->tileHostLayer() atIndex:0]; |
| hostLayerSizeChanged(); |
| } |
| |
| LegacyTileCache::~LegacyTileCache() |
| { |
| [m_tombstone.get() setDead:true]; |
| } |
| |
| CGFloat LegacyTileCache::screenScale() const |
| { |
| return [m_window screenScale]; |
| } |
| |
| CALayer* LegacyTileCache::hostLayer() const |
| { |
| return [m_window hostLayer]; |
| } |
| |
| FloatRect LegacyTileCache::visibleRectInLayer(CALayer *layer) const |
| { |
| if (m_overrideVisibleRect) |
| return [layer convertRect:m_overrideVisibleRect.value() fromLayer:hostLayer()]; |
| |
| return [layer convertRect:[m_window extendedVisibleRect] fromLayer:hostLayer()]; |
| } |
| |
| bool LegacyTileCache::setOverrideVisibleRect(const FloatRect& rect) |
| { |
| m_overrideVisibleRect = rect; |
| auto coveredByExistingTiles = false; |
| if (activeTileGrid()) |
| coveredByExistingTiles = activeTileGrid()->tilesCover(enclosingIntRect(m_overrideVisibleRect.value())); |
| return coveredByExistingTiles; |
| } |
| |
| bool LegacyTileCache::tilesOpaque() const |
| { |
| return m_tilesOpaque; |
| } |
| |
| LegacyTileGrid* LegacyTileCache::activeTileGrid() const |
| { |
| if (!m_keepsZoomedOutTiles) |
| return m_zoomedOutTileGrid.get(); |
| if (m_tilingMode == Zooming) |
| return m_zoomedOutTileGrid.get(); |
| if (m_zoomedInTileGrid && m_currentScale == m_zoomedInTileGrid->scale()) |
| return m_zoomedInTileGrid.get(); |
| return m_zoomedOutTileGrid.get(); |
| } |
| |
| LegacyTileGrid* LegacyTileCache::inactiveTileGrid() const |
| { |
| return activeTileGrid() == m_zoomedOutTileGrid.get() ? m_zoomedInTileGrid.get() : m_zoomedOutTileGrid.get(); |
| } |
| |
| void LegacyTileCache::setTilesOpaque(bool opaque) |
| { |
| if (m_tilesOpaque == opaque) |
| return; |
| |
| LockHolder locker(m_tileMutex); |
| |
| m_tilesOpaque = opaque; |
| m_zoomedOutTileGrid->updateTileOpacity(); |
| if (m_zoomedInTileGrid) |
| m_zoomedInTileGrid->updateTileOpacity(); |
| } |
| |
| void LegacyTileCache::doLayoutTiles() |
| { |
| if (isTileCreationSuspended()) |
| return; |
| |
| LockHolder locker(m_tileMutex); |
| LegacyTileGrid* activeGrid = activeTileGrid(); |
| // Even though we aren't actually creating tiles in the inactive grid, we |
| // still need to drop invalid tiles in response to a layout. |
| // See <rdar://problem/9839867>. |
| if (LegacyTileGrid* inactiveGrid = inactiveTileGrid()) |
| inactiveGrid->dropInvalidTiles(); |
| if (activeGrid->checkDoSingleTileLayout()) |
| return; |
| createTilesInActiveGrid(CoverVisibleOnly); |
| } |
| |
| void LegacyTileCache::hostLayerSizeChanged() |
| { |
| m_zoomedOutTileGrid->updateHostLayerSize(); |
| if (m_zoomedInTileGrid) |
| m_zoomedInTileGrid->updateHostLayerSize(); |
| } |
| |
| void LegacyTileCache::setKeepsZoomedOutTiles(bool keep) |
| { |
| m_keepsZoomedOutTiles = keep; |
| } |
| |
| void LegacyTileCache::setCurrentScale(float scale) |
| { |
| ASSERT(scale > 0); |
| |
| if (currentScale() == scale) { |
| m_pendingScale = 0; |
| return; |
| } |
| m_pendingScale = scale; |
| if (m_tilingMode == Disabled) |
| return; |
| commitScaleChange(); |
| |
| if (!keepsZoomedOutTiles() && !isTileInvalidationSuspended()) { |
| // Tile invalidation is normally suspended during zooming by UIKit but some applications |
| // using custom scrollviews may zoom without triggering the callbacks. Invalidate the tiles explicitly. |
| LockHolder locker(m_tileMutex); |
| activeTileGrid()->dropAllTiles(); |
| activeTileGrid()->createTiles(CoverVisibleOnly); |
| } |
| } |
| |
| void LegacyTileCache::setZoomedOutScale(float scale) |
| { |
| ASSERT(scale > 0); |
| |
| if (zoomedOutScale() == scale) { |
| m_pendingZoomedOutScale = 0; |
| return; |
| } |
| m_pendingZoomedOutScale = scale; |
| if (m_tilingMode == Disabled) |
| return; |
| commitScaleChange(); |
| } |
| |
| void LegacyTileCache::commitScaleChange() |
| { |
| ASSERT(m_pendingZoomedOutScale || m_pendingScale); |
| ASSERT(m_tilingMode != Disabled); |
| |
| LockHolder locker(m_tileMutex); |
| |
| if (m_pendingZoomedOutScale) { |
| m_zoomedOutTileGrid->setScale(m_pendingZoomedOutScale); |
| m_pendingZoomedOutScale = 0; |
| } |
| |
| if (!m_keepsZoomedOutTiles) { |
| ASSERT(activeTileGrid() == m_zoomedOutTileGrid.get()); |
| if (m_pendingScale) { |
| m_currentScale = m_pendingScale; |
| m_zoomedOutTileGrid->setScale(m_currentScale); |
| } |
| m_pendingScale = 0; |
| return; |
| } |
| |
| if (m_pendingScale) { |
| m_currentScale = m_pendingScale; |
| m_pendingScale = 0; |
| } |
| |
| if (m_currentScale != m_zoomedOutTileGrid->scale()) { |
| if (!m_zoomedInTileGrid) { |
| m_zoomedInTileGrid = makeUnique<LegacyTileGrid>(*this, m_tileSize); |
| [hostLayer() addSublayer:m_zoomedInTileGrid->tileHostLayer()]; |
| hostLayerSizeChanged(); |
| } |
| m_zoomedInTileGrid->setScale(m_currentScale); |
| } |
| |
| // Keep the current ordering during zooming. |
| if (m_tilingMode != Zooming) |
| bringActiveTileGridToFront(); |
| |
| adjustTileGridTransforms(); |
| layoutTiles(); |
| } |
| |
| void LegacyTileCache::bringActiveTileGridToFront() |
| { |
| LegacyTileGrid* activeGrid = activeTileGrid(); |
| LegacyTileGrid* otherGrid = inactiveTileGrid(); |
| if (!otherGrid) |
| return; |
| CALayer* frontLayer = activeGrid->tileHostLayer(); |
| CALayer* otherLayer = otherGrid->tileHostLayer(); |
| [hostLayer() insertSublayer:frontLayer above:otherLayer]; |
| } |
| |
| void LegacyTileCache::adjustTileGridTransforms() |
| { |
| CALayer* zoomedOutHostLayer = m_zoomedOutTileGrid->tileHostLayer(); |
| float transformScale = currentScale() / zoomedOutScale(); |
| [zoomedOutHostLayer setTransform:CATransform3DMakeScale(transformScale, transformScale, 1.0f)]; |
| m_zoomedOutTileGrid->updateHostLayerSize(); |
| } |
| |
| void LegacyTileCache::layoutTiles() |
| { |
| if (m_hasPendingLayoutTiles) |
| return; |
| m_hasPendingLayoutTiles = true; |
| |
| LegacyTileCacheTombstone *tombstone = m_tombstone.get(); |
| WebThreadRun(^{ |
| if ([tombstone isDead]) |
| return; |
| m_hasPendingLayoutTiles = false; |
| doLayoutTiles(); |
| }); |
| } |
| |
| void LegacyTileCache::layoutTilesNow() |
| { |
| ASSERT(WebThreadIsLockedOrDisabled()); |
| |
| // layoutTilesNow() is called after a zoom, while the tile mode is still set to Zooming. |
| // If we checked for isTileCreationSuspended here, that would cause <rdar://problem/8434112> (Page flashes after zooming in/out). |
| if (m_tilingMode == Disabled) |
| return; |
| |
| // FIXME: layoutTilesNow should be called after state has been set to non-zooming and the active grid is the final one. |
| // Fix this in UIKit side (perhaps also getting rid of this call) and remove this code afterwards. |
| // <rdar://problem/9672993> |
| TilingMode savedTilingMode = m_tilingMode; |
| if (m_tilingMode == Zooming) |
| m_tilingMode = Minimal; |
| |
| LockHolder locker(m_tileMutex); |
| LegacyTileGrid* activeGrid = activeTileGrid(); |
| if (activeGrid->checkDoSingleTileLayout()) { |
| m_tilingMode = savedTilingMode; |
| return; |
| } |
| createTilesInActiveGrid(CoverVisibleOnly); |
| m_tilingMode = savedTilingMode; |
| } |
| |
| void LegacyTileCache::layoutTilesNowForRect(const IntRect& rect) |
| { |
| ASSERT(WebThreadIsLockedOrDisabled()); |
| LockHolder locker(m_tileMutex); |
| |
| activeTileGrid()->addTilesCoveringRect(rect); |
| } |
| |
| void LegacyTileCache::removeAllNonVisibleTiles() |
| { |
| LockHolder locker(m_tileMutex); |
| removeAllNonVisibleTilesInternal(); |
| } |
| |
| void LegacyTileCache::removeAllNonVisibleTilesInternal() |
| { |
| LegacyTileGrid* activeGrid = activeTileGrid(); |
| if (keepsZoomedOutTiles() && activeGrid == m_zoomedInTileGrid.get() && activeGrid->hasTiles()) |
| m_zoomedOutTileGrid->dropAllTiles(); |
| |
| IntRect activeTileBounds = activeGrid->bounds(); |
| if (activeTileBounds.width() <= m_tileSize.width() && activeTileBounds.height() <= m_tileSize.height()) { |
| // If the view is smaller than a tile, keep the tile even if it is not visible. |
| activeGrid->dropTilesOutsideRect(activeTileBounds); |
| return; |
| } |
| |
| activeGrid->dropTilesOutsideRect(activeGrid->visibleRect()); |
| } |
| |
| void LegacyTileCache::removeAllTiles() |
| { |
| LockHolder locker(m_tileMutex); |
| m_zoomedOutTileGrid->dropAllTiles(); |
| if (m_zoomedInTileGrid) |
| m_zoomedInTileGrid->dropAllTiles(); |
| } |
| |
| void LegacyTileCache::removeForegroundTiles() |
| { |
| LockHolder locker(m_tileMutex); |
| if (!keepsZoomedOutTiles()) |
| m_zoomedOutTileGrid->dropAllTiles(); |
| if (m_zoomedInTileGrid) |
| m_zoomedInTileGrid->dropAllTiles(); |
| } |
| |
| void LegacyTileCache::setContentReplacementImage(RetainPtr<CGImageRef> contentReplacementImage) |
| { |
| LockHolder locker(m_contentReplacementImageMutex); |
| m_contentReplacementImage = contentReplacementImage; |
| } |
| |
| RetainPtr<CGImageRef> LegacyTileCache::contentReplacementImage() const |
| { |
| LockHolder locker(m_contentReplacementImageMutex); |
| return m_contentReplacementImage; |
| } |
| |
| void LegacyTileCache::setTileBordersVisible(bool flag) |
| { |
| if (flag == m_tileBordersVisible) |
| return; |
| |
| m_tileBordersVisible = flag; |
| m_zoomedOutTileGrid->updateTileBorderVisibility(); |
| if (m_zoomedInTileGrid) |
| m_zoomedInTileGrid->updateTileBorderVisibility(); |
| } |
| |
| void LegacyTileCache::setTilePaintCountersVisible(bool flag) |
| { |
| m_tilePaintCountersVisible = flag; |
| // The numbers will show up the next time the tiles paint. |
| } |
| |
| void LegacyTileCache::finishedCreatingTiles(bool didCreateTiles, bool createMore) |
| { |
| // We need to ensure that all tiles are showing the same version of the content. |
| if (didCreateTiles && !m_savedDisplayRects.isEmpty()) |
| flushSavedDisplayRects(); |
| |
| if (keepsZoomedOutTiles()) { |
| if (m_zoomedInTileGrid && activeTileGrid() == m_zoomedOutTileGrid.get() && m_tilingMode != Zooming && m_zoomedInTileGrid->hasTiles()) { |
| // This CA transaction will cover the screen with top level tiles. |
| // We can remove zoomed-in tiles without flashing. |
| m_zoomedInTileGrid->dropAllTiles(); |
| } else if (activeTileGrid() == m_zoomedInTileGrid.get()) { |
| // Pass the minimum possible distance to consider all tiles, even visible ones. |
| m_zoomedOutTileGrid->dropDistantTiles(0, std::numeric_limits<double>::min()); |
| } |
| } |
| |
| // Keep creating tiles until the whole coverRect is covered. |
| if (createMore) |
| m_tileCreationTimer.startOneShot(0_s); |
| } |
| |
| void LegacyTileCache::tileCreationTimerFired() |
| { |
| if (isTileCreationSuspended()) |
| return; |
| LockHolder locker(m_tileMutex); |
| createTilesInActiveGrid(CoverSpeculative); |
| } |
| |
| void LegacyTileCache::createTilesInActiveGrid(SynchronousTileCreationMode mode) |
| { |
| if (MemoryPressureHandler::singleton().isUnderMemoryPressure()) { |
| LOG(MemoryPressure, "Under memory pressure at: %s", __PRETTY_FUNCTION__); |
| removeAllNonVisibleTilesInternal(); |
| } |
| activeTileGrid()->createTiles(mode); |
| } |
| |
| unsigned LegacyTileCache::tileCapacityForGrid(LegacyTileGrid* grid) |
| { |
| static unsigned capacity; |
| if (!capacity) { |
| size_t totalMemory = ramSize() / 1024 / 1024; |
| if (totalMemory >= 1024) |
| capacity = 128 * 1024 * 1024; |
| else if (totalMemory >= 512) |
| capacity = 64 * 1024 * 1024; |
| else |
| capacity = 32 * 1024 * 1024; |
| } |
| |
| int gridCapacity; |
| |
| int memoryLevel = systemMemoryLevel(); |
| if (memoryLevel < 15) |
| gridCapacity = capacity / 4; |
| else if (memoryLevel < 20) |
| gridCapacity = capacity / 2; |
| else if (memoryLevel < 30) |
| gridCapacity = capacity * 3 / 4; |
| else |
| gridCapacity = capacity; |
| |
| if (keepsZoomedOutTiles() && grid == m_zoomedOutTileGrid.get()) { |
| if (activeTileGrid() == m_zoomedOutTileGrid.get()) |
| return gridCapacity; |
| return gridCapacity / 4; |
| } |
| return gridCapacity * 3 / 4; |
| } |
| |
| Color LegacyTileCache::colorForGridTileBorder(LegacyTileGrid* grid) const |
| { |
| if (grid == m_zoomedOutTileGrid.get()) |
| return Color(.3f, .0f, 0.4f, 0.5f); |
| |
| return Color(.0f, .0f, 0.4f, 0.5f); |
| } |
| |
| static bool shouldRepaintInPieces(const CGRect& dirtyRect, CGSRegionObj dirtyRegion, CGFloat contentsScale) |
| { |
| // Estimate whether or not we should use the unioned rect or the individual rects. |
| // We do this by computing the percentage of "wasted space" in the union. If that wasted space |
| // is too large, then we will do individual rect painting instead. |
| float singlePixels = 0; |
| unsigned rectCount = 0; |
| |
| CGSRegionEnumeratorObj enumerator = CGSRegionEnumerator(dirtyRegion); |
| CGRect *subRect; |
| while ((subRect = CGSNextRect(enumerator))) { |
| ++rectCount; |
| singlePixels += subRect->size.width * subRect->size.height; |
| } |
| singlePixels /= (contentsScale * contentsScale); |
| CGSReleaseRegionEnumerator(enumerator); |
| |
| const unsigned cRectThreshold = 10; |
| if (rectCount < 2 || rectCount > cRectThreshold) |
| return false; |
| |
| const float cWastedSpaceThreshold = 0.50f; |
| float unionPixels = dirtyRect.size.width * dirtyRect.size.height; |
| float wastedSpace = 1.f - (singlePixels / unionPixels); |
| return wastedSpace > cWastedSpaceThreshold; |
| } |
| |
| void LegacyTileCache::drawReplacementImage(LegacyTileLayer* layer, CGContextRef context, CGImageRef image) |
| { |
| CGContextSetRGBFillColor(context, 1, 1, 1, 1); |
| CGContextFillRect(context, CGContextGetClipBoundingBox(context)); |
| |
| CGFloat contentsScale = [layer contentsScale]; |
| CGContextScaleCTM(context, 1 / contentsScale, -1 / contentsScale); |
| CGRect imageRect = CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)); |
| CGContextTranslateCTM(context, 0, -imageRect.size.height); |
| CGContextDrawImage(context, imageRect, image); |
| } |
| |
| void LegacyTileCache::drawWindowContent(LegacyTileLayer* layer, CGContextRef context, CGRect dirtyRect, DrawingFlags drawingFlags) |
| { |
| CGRect frame = [layer frame]; |
| FontAntialiasingStateSaver fontAntialiasingState(context, [m_window useOrientationDependentFontAntialiasing] && [layer isOpaque]); |
| fontAntialiasingState.setup([WAKWindow hasLandscapeOrientation]); |
| |
| if (drawingFlags == DrawingFlags::Snapshotting) |
| [m_window setIsInSnapshottingPaint:YES]; |
| |
| CGSRegionObj drawRegion = (CGSRegionObj)[layer regionBeingDrawn]; |
| CGFloat contentsScale = [layer contentsScale]; |
| |
| if (drawRegion && shouldRepaintInPieces(dirtyRect, drawRegion, contentsScale)) { |
| // Use fine grained repaint rectangles to minimize the amount of painted pixels. |
| CGSRegionEnumeratorObj enumerator = CGSRegionEnumerator(drawRegion); |
| CGRect *subRect; |
| while ((subRect = CGSNextRect(enumerator))) { |
| CGRect adjustedSubRect = *subRect; |
| adjustedSubRect.origin.x /= contentsScale; |
| adjustedSubRect.origin.y = frame.size.height - (adjustedSubRect.origin.y + adjustedSubRect.size.height) / contentsScale; |
| adjustedSubRect.size.width /= contentsScale; |
| adjustedSubRect.size.height /= contentsScale; |
| |
| CGRect subRectInSuper = [hostLayer() convertRect:adjustedSubRect fromLayer:layer]; |
| [m_window displayRect:subRectInSuper]; |
| } |
| CGSReleaseRegionEnumerator(enumerator); |
| } else { |
| // Simple repaint |
| CGRect dirtyRectInSuper = [hostLayer() convertRect:dirtyRect fromLayer:layer]; |
| [m_window displayRect:dirtyRectInSuper]; |
| } |
| |
| fontAntialiasingState.restore(); |
| |
| if (drawingFlags == DrawingFlags::Snapshotting) |
| [m_window setIsInSnapshottingPaint:NO]; |
| } |
| |
| void LegacyTileCache::drawLayer(LegacyTileLayer* layer, CGContextRef context, DrawingFlags drawingFlags) |
| { |
| // The web lock unlock observer runs after CA commit observer. |
| if (!WebThreadIsLockedOrDisabled()) { |
| LOG_ERROR("Drawing without holding the web thread lock"); |
| ASSERT_NOT_REACHED(); |
| } |
| |
| WKSetCurrentGraphicsContext(context); |
| |
| CGRect dirtyRect = CGContextGetClipBoundingBox(context); |
| CGRect frame = [layer frame]; |
| CGContextTranslateCTM(context, -frame.origin.x, -frame.origin.y); |
| CGRect scaledFrame = [hostLayer() convertRect:[layer bounds] fromLayer:layer]; |
| CGContextScaleCTM(context, frame.size.width / scaledFrame.size.width, frame.size.height / scaledFrame.size.height); |
| |
| if (RetainPtr<CGImage> contentReplacementImage = this->contentReplacementImage()) |
| drawReplacementImage(layer, context, contentReplacementImage.get()); |
| else |
| drawWindowContent(layer, context, dirtyRect, drawingFlags); |
| |
| ++layer.paintCount; |
| if (m_tilePaintCountersVisible) { |
| char text[16]; |
| snprintf(text, sizeof(text), "%d", layer.paintCount); |
| |
| CGContextSaveGState(context); |
| |
| CGContextTranslateCTM(context, frame.origin.x, frame.origin.y); |
| CGContextSetFillColorWithColor(context, cachedCGColor(colorForGridTileBorder([layer tileGrid]))); |
| |
| CGRect labelBounds = [layer bounds]; |
| labelBounds.size.width = 10 + 12 * strlen(text); |
| labelBounds.size.height = 25; |
| CGContextFillRect(context, labelBounds); |
| |
| if (acceleratedDrawingEnabled()) |
| CGContextSetRGBFillColor(context, 1, 0, 0, 0.4f); |
| else |
| CGContextSetRGBFillColor(context, 1, 1, 1, 0.6f); |
| |
| auto matrix = CGAffineTransformMakeScale(1, -1); |
| auto font = adoptCF(CTFontCreateWithName(CFSTR("Helvetica"), 25, &matrix)); |
| CFTypeRef keys[] = { kCTFontAttributeName, kCTForegroundColorFromContextAttributeName }; |
| CFTypeRef values[] = { font.get(), kCFBooleanTrue }; |
| auto attributes = adoptCF(CFDictionaryCreate(kCFAllocatorDefault, keys, values, WTF_ARRAY_LENGTH(keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); |
| auto string = adoptCF(CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, reinterpret_cast<const UInt8*>(text), strlen(text), kCFStringEncodingUTF8, false, kCFAllocatorNull)); |
| auto attributedString = adoptCF(CFAttributedStringCreate(kCFAllocatorDefault, string.get(), attributes.get())); |
| auto line = adoptCF(CTLineCreateWithAttributedString(attributedString.get())); |
| CGContextSetTextPosition(context, labelBounds.origin.x + 3, labelBounds.origin.y + 20); |
| CTLineDraw(line.get(), context); |
| |
| CGContextRestoreGState(context); |
| } |
| |
| WAKView* view = [m_window contentView]; |
| [view performSelector:@selector(_dispatchTileDidDraw:) withObject:layer afterDelay:0.0]; |
| } |
| |
| void LegacyTileCache::setNeedsDisplay() |
| { |
| setNeedsDisplayInRect(IntRect(0, 0, std::numeric_limits<int>::max(), std::numeric_limits<int>::max())); |
| } |
| |
| void LegacyTileCache::scheduleLayerFlushForPendingRepaint() |
| { |
| WAKView* view = [m_window contentView]; |
| [view _scheduleLayerFlushForPendingTileCacheRepaint]; |
| } |
| |
| void LegacyTileCache::setNeedsDisplayInRect(const IntRect& dirtyRect) |
| { |
| LockHolder locker(m_savedDisplayRectMutex); |
| bool addedFirstRect = m_savedDisplayRects.isEmpty(); |
| m_savedDisplayRects.append(dirtyRect); |
| if (!addedFirstRect) |
| return; |
| // Compositing layer flush will call back to doPendingRepaints(). The flush may be throttled and not happen immediately. |
| scheduleLayerFlushForPendingRepaint(); |
| } |
| |
| void LegacyTileCache::invalidateTiles(const IntRect& dirtyRect) |
| { |
| ASSERT(!m_tileMutex.tryLock()); |
| |
| LegacyTileGrid* activeGrid = activeTileGrid(); |
| if (!keepsZoomedOutTiles()) { |
| activeGrid->invalidateTiles(dirtyRect); |
| return; |
| } |
| FloatRect scaledRect = dirtyRect; |
| scaledRect.scale(zoomedOutScale() / currentScale()); |
| IntRect zoomedOutDirtyRect = enclosingIntRect(scaledRect); |
| if (activeGrid == m_zoomedOutTileGrid.get()) { |
| bool dummy; |
| IntRect coverRect = m_zoomedOutTileGrid->calculateCoverRect(m_zoomedOutTileGrid->visibleRect(), dummy); |
| // Instead of repainting a tile outside the cover rect, just remove it. |
| m_zoomedOutTileGrid->dropTilesBetweenRects(zoomedOutDirtyRect, coverRect); |
| m_zoomedOutTileGrid->invalidateTiles(zoomedOutDirtyRect); |
| // We need to invalidate zoomed in tiles as well while zooming, since |
| // we could switch back to the zoomed in grid without dropping its |
| // tiles. See <rdar://problem/9946759>. |
| if (m_tilingMode == Zooming && m_zoomedInTileGrid) |
| m_zoomedInTileGrid->invalidateTiles(dirtyRect); |
| return; |
| } |
| if (!m_zoomedInTileGrid->hasTiles()) { |
| // If no tiles have been created yet for the zoomed in grid, we can't drop the zoomed out tiles. |
| m_zoomedOutTileGrid->invalidateTiles(zoomedOutDirtyRect); |
| return; |
| } |
| m_zoomedOutTileGrid->dropTilesIntersectingRect(zoomedOutDirtyRect); |
| m_zoomedInTileGrid->invalidateTiles(dirtyRect); |
| } |
| |
| bool LegacyTileCache::isTileCreationSuspended() const |
| { |
| return (!keepsZoomedOutTiles() && m_tilingMode == Zooming) || m_tilingMode == Disabled; |
| } |
| |
| bool LegacyTileCache::isTileInvalidationSuspended() const |
| { |
| return m_tilingMode == Zooming || m_tilingMode == Panning || m_tilingMode == ScrollToTop || m_tilingMode == Disabled; |
| } |
| |
| void LegacyTileCache::updateTilingMode() |
| { |
| ASSERT(WebThreadIsCurrent() || !WebThreadIsEnabled()); |
| |
| WAKView* view = [m_window contentView]; |
| |
| if (m_tilingMode == Zooming || m_tilingMode == Panning || m_tilingMode == ScrollToTop) { |
| if (!m_didCallWillStartScrollingOrZooming) { |
| [view _willStartScrollingOrZooming]; |
| m_didCallWillStartScrollingOrZooming = true; |
| } |
| } else { |
| if (m_didCallWillStartScrollingOrZooming) { |
| [view _didFinishScrollingOrZooming]; |
| m_didCallWillStartScrollingOrZooming = false; |
| } |
| if (m_tilingMode == Disabled) |
| return; |
| |
| LockHolder locker(m_tileMutex); |
| createTilesInActiveGrid(CoverVisibleOnly); |
| |
| if (!m_savedDisplayRects.isEmpty()) |
| scheduleLayerFlushForPendingRepaint(); |
| } |
| } |
| |
| void LegacyTileCache::setTilingMode(TilingMode tilingMode) |
| { |
| if (tilingMode == m_tilingMode) |
| return; |
| bool wasZooming = (m_tilingMode == Zooming); |
| m_tilingMode = tilingMode; |
| |
| if ((m_pendingZoomedOutScale || m_pendingScale) && m_tilingMode != Disabled) |
| commitScaleChange(); |
| else if (wasZooming) { |
| LockHolder locker(m_tileMutex); |
| bringActiveTileGridToFront(); |
| } |
| |
| if (m_hasPendingUpdateTilingMode) |
| return; |
| m_hasPendingUpdateTilingMode = true; |
| |
| LegacyTileCacheTombstone *tombstone = m_tombstone.get(); |
| WebThreadRun(^{ |
| if ([tombstone isDead]) |
| return; |
| m_hasPendingUpdateTilingMode = false; |
| updateTilingMode(); |
| }); |
| } |
| |
| void LegacyTileCache::setTilingDirection(TilingDirection tilingDirection) |
| { |
| m_tilingDirection = tilingDirection; |
| } |
| |
| LegacyTileCache::TilingDirection LegacyTileCache::tilingDirection() const |
| { |
| return m_tilingDirection; |
| } |
| |
| float LegacyTileCache::zoomedOutScale() const |
| { |
| return m_zoomedOutTileGrid->scale(); |
| } |
| |
| float LegacyTileCache::currentScale() const |
| { |
| return m_currentScale; |
| } |
| |
| void LegacyTileCache::doPendingRepaints() |
| { |
| if (m_savedDisplayRects.isEmpty()) |
| return; |
| if (isTileInvalidationSuspended()) |
| return; |
| LockHolder locker(m_tileMutex); |
| flushSavedDisplayRects(); |
| } |
| |
| void LegacyTileCache::flushSavedDisplayRects() |
| { |
| ASSERT(!m_tileMutex.tryLock()); |
| ASSERT(!m_savedDisplayRects.isEmpty()); |
| |
| Vector<IntRect> rects; |
| { |
| LockHolder locker(m_savedDisplayRectMutex); |
| m_savedDisplayRects.swap(rects); |
| } |
| size_t size = rects.size(); |
| for (size_t n = 0; n < size; ++n) |
| invalidateTiles(rects[n]); |
| } |
| |
| void LegacyTileCache::setSpeculativeTileCreationEnabled(bool enabled) |
| { |
| if (m_isSpeculativeTileCreationEnabled == enabled) |
| return; |
| m_isSpeculativeTileCreationEnabled = enabled; |
| if (m_isSpeculativeTileCreationEnabled) |
| m_tileCreationTimer.startOneShot(0_s); |
| } |
| |
| bool LegacyTileCache::hasPendingDraw() const |
| { |
| return !m_savedDisplayRects.isEmpty(); |
| } |
| |
| void LegacyTileCache::prepareToDraw() |
| { |
| // This will trigger document relayout if needed. |
| [[m_window contentView] viewWillDraw]; |
| |
| if (!m_savedDisplayRects.isEmpty()) { |
| LockHolder locker(m_tileMutex); |
| flushSavedDisplayRects(); |
| } |
| } |
| |
| void LegacyTileCache::setLayerPoolCapacity(unsigned capacity) |
| { |
| LegacyTileLayerPool::sharedPool()->setCapacity(capacity); |
| } |
| |
| void LegacyTileCache::drainLayerPool() |
| { |
| LegacyTileLayerPool::sharedPool()->drain(); |
| } |
| |
| void LegacyTileCache::dumpTiles() |
| { |
| NSLog(@"================="); |
| NSLog(@"ZOOMED OUT"); |
| if (m_zoomedOutTileGrid.get() == activeTileGrid()) |
| NSLog(@"<ACTIVE>"); |
| m_zoomedOutTileGrid->dumpTiles(); |
| NSLog(@"================="); |
| if (m_zoomedInTileGrid) { |
| NSLog(@"ZOOMED IN"); |
| if (m_zoomedInTileGrid.get() == activeTileGrid()) |
| NSLog(@"<ACTIVE>"); |
| m_zoomedInTileGrid->dumpTiles(); |
| NSLog(@"================="); |
| } |
| } |
| |
| } // namespace WebCore |
| |
| #endif // PLATFORM(IOS_FAMILY) |