1/*
2 * Copyright (C) 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#import "config.h"
27#import "ViewSnapshotStore.h"
28
29#import "WebBackForwardList.h"
30#import "WebPageProxy.h"
31#import <CoreGraphics/CoreGraphics.h>
32#import <WebCore/IOSurface.h>
33
34#if PLATFORM(IOS)
35#import <QuartzCore/QuartzCorePrivate.h>
36#endif
37
38using namespace WebCore;
39
40#if USE_IOSURFACE_VIEW_SNAPSHOTS
41static const size_t maximumSnapshotCacheSize = 400 * (1024 * 1024);
42#elif USE_RENDER_SERVER_VIEW_SNAPSHOTS
43// Because render server snapshots are not purgeable, we should keep fewer around.
44static const size_t maximumSnapshotCacheSize = 50 * (1024 * 1024);
45#endif
46
47namespace WebKit {
48
49ViewSnapshotStore::ViewSnapshotStore()
50    : m_snapshotCacheSize(0)
51{
52}
53
54ViewSnapshotStore::~ViewSnapshotStore()
55{
56    discardSnapshotImages();
57}
58
59ViewSnapshotStore& ViewSnapshotStore::shared()
60{
61    static ViewSnapshotStore& store = *new ViewSnapshotStore;
62    return store;
63}
64
65#if USE_RENDER_SERVER_VIEW_SNAPSHOTS
66CAContext *ViewSnapshotStore::snapshottingContext()
67{
68    static CAContext *context;
69    static dispatch_once_t onceToken;
70    dispatch_once(&onceToken, ^{
71        NSDictionary *options = @{
72            kCAContextDisplayName: @"WebKitSnapshotting",
73            kCAContextIgnoresHitTest: @YES,
74            kCAContextDisplayId : @20000
75        };
76        context = [[CAContext remoteContextWithOptions:options] retain];
77    });
78
79    return context;
80}
81#endif
82
83void ViewSnapshotStore::didAddImageToSnapshot(ViewSnapshot& snapshot)
84{
85    bool isNewEntry = m_snapshotsWithImages.add(&snapshot).isNewEntry;
86    ASSERT_UNUSED(isNewEntry, isNewEntry);
87    m_snapshotCacheSize += snapshot.imageSizeInBytes();
88}
89
90void ViewSnapshotStore::willRemoveImageFromSnapshot(ViewSnapshot& snapshot)
91{
92    bool removed = m_snapshotsWithImages.remove(&snapshot);
93    ASSERT_UNUSED(removed, removed);
94    m_snapshotCacheSize -= snapshot.imageSizeInBytes();
95}
96
97void ViewSnapshotStore::pruneSnapshots(WebPageProxy& webPageProxy)
98{
99    if (m_snapshotCacheSize <= maximumSnapshotCacheSize)
100        return;
101
102    ASSERT(!m_snapshotsWithImages.isEmpty());
103
104    // FIXME: We have enough information to do smarter-than-LRU eviction (making use of the back-forward lists, etc.)
105
106    m_snapshotsWithImages.first()->clearImage();
107}
108
109void ViewSnapshotStore::recordSnapshot(WebPageProxy& webPageProxy)
110{
111    if (webPageProxy.isShowingNavigationGestureSnapshot())
112        return;
113
114    WebBackForwardListItem* item = webPageProxy.backForwardList().currentItem();
115
116    if (!item)
117        return;
118
119    pruneSnapshots(webPageProxy);
120
121    webPageProxy.willRecordNavigationSnapshot(*item);
122
123    RefPtr<ViewSnapshot> snapshot = webPageProxy.takeViewSnapshot();
124    if (!snapshot || !snapshot->hasImage())
125        return;
126
127    snapshot->setRenderTreeSize(webPageProxy.renderTreeSize());
128    snapshot->setDeviceScaleFactor(webPageProxy.deviceScaleFactor());
129    snapshot->setBackgroundColor(webPageProxy.pageExtendedBackgroundColor());
130
131    item->setSnapshot(snapshot.release());
132}
133
134void ViewSnapshotStore::discardSnapshotImages()
135{
136    while (!m_snapshotsWithImages.isEmpty())
137        m_snapshotsWithImages.first()->clearImage();
138}
139
140
141#if USE_IOSURFACE_VIEW_SNAPSHOTS
142PassRefPtr<ViewSnapshot> ViewSnapshot::create(IOSurface* surface, IntSize size, size_t imageSizeInBytes)
143{
144    return adoptRef(new ViewSnapshot(surface, size, imageSizeInBytes));
145}
146#elif USE_RENDER_SERVER_VIEW_SNAPSHOTS
147PassRefPtr<ViewSnapshot> ViewSnapshot::create(uint32_t slotID, IntSize size, size_t imageSizeInBytes)
148{
149    return adoptRef(new ViewSnapshot(slotID, size, imageSizeInBytes));
150}
151#endif
152
153#if USE_IOSURFACE_VIEW_SNAPSHOTS
154ViewSnapshot::ViewSnapshot(IOSurface* surface, IntSize size, size_t imageSizeInBytes)
155    : m_surface(surface)
156#elif USE_RENDER_SERVER_VIEW_SNAPSHOTS
157ViewSnapshot::ViewSnapshot(uint32_t slotID, IntSize size, size_t imageSizeInBytes)
158    : m_slotID(slotID)
159#endif
160    , m_imageSizeInBytes(imageSizeInBytes)
161    , m_size(size)
162{
163    if (hasImage())
164        ViewSnapshotStore::shared().didAddImageToSnapshot(*this);
165}
166
167ViewSnapshot::~ViewSnapshot()
168{
169    clearImage();
170}
171
172bool ViewSnapshot::hasImage() const
173{
174#if USE_IOSURFACE_VIEW_SNAPSHOTS
175    return m_surface;
176#elif USE_RENDER_SERVER_VIEW_SNAPSHOTS
177    return m_slotID;
178#endif
179}
180
181void ViewSnapshot::clearImage()
182{
183    if (!hasImage())
184        return;
185
186    ViewSnapshotStore::shared().willRemoveImageFromSnapshot(*this);
187
188#if USE_IOSURFACE_VIEW_SNAPSHOTS
189    m_surface = nullptr;
190#elif USE_RENDER_SERVER_VIEW_SNAPSHOTS
191    [ViewSnapshotStore::snapshottingContext() deleteSlot:m_slotID];
192    m_slotID = 0;
193#endif
194    m_imageSizeInBytes = 0;
195}
196
197id ViewSnapshot::asLayerContents()
198{
199#if USE_IOSURFACE_VIEW_SNAPSHOTS
200    if (!m_surface)
201        return nullptr;
202
203    if (m_surface->setIsVolatile(false) != IOSurface::SurfaceState::Valid) {
204        clearImage();
205        return nullptr;
206    }
207
208    return (id)m_surface->surface();
209#elif USE_RENDER_SERVER_VIEW_SNAPSHOTS
210    return [CAContext objectForSlot:m_slotID];
211#endif
212}
213
214} // namespace WebKit
215