1/*
2 * Copyright (C) 2017 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 "DOMCacheStorage.h"
28
29#include "CacheQueryOptions.h"
30#include "ClientOrigin.h"
31#include "JSDOMCache.h"
32#include "JSFetchResponse.h"
33#include "ScriptExecutionContext.h"
34
35
36namespace WebCore {
37using namespace WebCore::DOMCacheEngine;
38
39DOMCacheStorage::DOMCacheStorage(ScriptExecutionContext& context, Ref<CacheStorageConnection>&& connection)
40 : ActiveDOMObject(&context)
41 , m_connection(WTFMove(connection))
42{
43 suspendIfNeeded();
44}
45
46Optional<ClientOrigin> DOMCacheStorage::origin() const
47{
48 auto* origin = scriptExecutionContext() ? scriptExecutionContext()->securityOrigin() : nullptr;
49 if (!origin)
50 return WTF::nullopt;
51
52 return ClientOrigin { scriptExecutionContext()->topOrigin().data(), origin->data() };
53}
54
55static void doSequentialMatch(size_t index, Vector<Ref<DOMCache>>&& caches, DOMCache::RequestInfo&& info, CacheQueryOptions&& options, DOMCache::MatchCallback&& completionHandler)
56{
57 if (index >= caches.size()) {
58 completionHandler(nullptr);
59 return;
60 }
61
62 auto& cache = caches[index].get();
63 cache.doMatch(WTFMove(info), WTFMove(options), [caches = WTFMove(caches), info, options, completionHandler = WTFMove(completionHandler), index](ExceptionOr<FetchResponse*>&& result) mutable {
64 if (result.hasException()) {
65 completionHandler(result.releaseException());
66 return;
67 }
68 if (result.returnValue()) {
69 completionHandler(result.returnValue());
70 return;
71 }
72 doSequentialMatch(++index, WTFMove(caches), WTFMove(info), WTFMove(options), WTFMove(completionHandler));
73 });
74}
75
76static inline void startSequentialMatch(Vector<Ref<DOMCache>>&& caches, DOMCache::RequestInfo&& info, CacheQueryOptions&& options, DOMCache::MatchCallback&& completionHandler)
77{
78 doSequentialMatch(0, WTFMove(caches), WTFMove(info), WTFMove(options), WTFMove(completionHandler));
79}
80
81static inline Ref<DOMCache> copyCache(const Ref<DOMCache>& cache)
82{
83 return cache.copyRef();
84}
85
86void DOMCacheStorage::doSequentialMatch(DOMCache::RequestInfo&& info, CacheQueryOptions&& options, Ref<DeferredPromise>&& promise)
87{
88 startSequentialMatch(WTF::map(m_caches, copyCache), WTFMove(info), WTFMove(options), [this, pendingActivity = makePendingActivity(*this), promise = WTFMove(promise)](ExceptionOr<FetchResponse*>&& result) mutable {
89 if (m_isStopped)
90 return;
91 if (result.hasException()) {
92 promise->reject(result.releaseException());
93 return;
94 }
95 if (!result.returnValue()) {
96 promise->resolve();
97 return;
98 }
99 promise->resolve<IDLInterface<FetchResponse>>(*result.returnValue());
100 });
101}
102
103void DOMCacheStorage::match(DOMCache::RequestInfo&& info, CacheQueryOptions&& options, Ref<DeferredPromise>&& promise)
104{
105 retrieveCaches([this, info = WTFMove(info), options = WTFMove(options), promise = WTFMove(promise)](Optional<Exception>&& exception) mutable {
106 if (exception) {
107 promise->reject(WTFMove(exception.value()));
108 return;
109 }
110
111 if (!options.cacheName.isNull()) {
112 auto position = m_caches.findMatching([&](auto& item) { return item->name() == options.cacheName; });
113 if (position != notFound) {
114 m_caches[position]->match(WTFMove(info), WTFMove(options), WTFMove(promise));
115 return;
116 }
117 promise->resolve();
118 return;
119 }
120
121 this->doSequentialMatch(WTFMove(info), WTFMove(options), WTFMove(promise));
122 });
123}
124
125void DOMCacheStorage::has(const String& name, DOMPromiseDeferred<IDLBoolean>&& promise)
126{
127 retrieveCaches([this, name, promise = WTFMove(promise)](Optional<Exception>&& exception) mutable {
128 if (exception) {
129 promise.reject(WTFMove(exception.value()));
130 return;
131 }
132 promise.resolve(m_caches.findMatching([&](auto& item) { return item->name() == name; }) != notFound);
133 });
134}
135
136Ref<DOMCache> DOMCacheStorage::findCacheOrCreate(CacheInfo&& info)
137{
138 auto position = m_caches.findMatching([&] (const auto& cache) { return info.identifier == cache->identifier(); });
139 if (position != notFound)
140 return m_caches[position].copyRef();
141 return DOMCache::create(*scriptExecutionContext(), WTFMove(info.name), info.identifier, m_connection.copyRef());
142}
143
144void DOMCacheStorage::retrieveCaches(WTF::Function<void(Optional<Exception>&&)>&& callback)
145{
146 auto origin = this->origin();
147 if (!origin)
148 return;
149
150 m_connection->retrieveCaches(*origin, m_updateCounter, [this, callback = WTFMove(callback), pendingActivity = makePendingActivity(*this)](CacheInfosOrError&& result) mutable {
151 if (!m_isStopped) {
152 if (!result.has_value()) {
153 callback(DOMCacheEngine::convertToExceptionAndLog(scriptExecutionContext(), result.error()));
154 return;
155 }
156
157 auto& cachesInfo = result.value();
158
159 if (m_updateCounter != cachesInfo.updateCounter) {
160 m_updateCounter = cachesInfo.updateCounter;
161
162 m_caches = WTF::map(WTFMove(cachesInfo.infos), [this] (CacheInfo&& info) {
163 return findCacheOrCreate(WTFMove(info));
164 });
165 }
166 callback(WTF::nullopt);
167 }
168 });
169}
170
171static void logConsolePersistencyError(ScriptExecutionContext* context, const String& cacheName)
172{
173 if (!context)
174 return;
175
176 context->addConsoleMessage(MessageSource::JS, MessageLevel::Error, makeString("There was an error making ", cacheName, " persistent on the filesystem"));
177}
178
179void DOMCacheStorage::open(const String& name, DOMPromiseDeferred<IDLInterface<DOMCache>>&& promise)
180{
181 retrieveCaches([this, name, promise = WTFMove(promise)](Optional<Exception>&& exception) mutable {
182 if (exception) {
183 promise.reject(WTFMove(exception.value()));
184 return;
185 }
186 doOpen(name, WTFMove(promise));
187 });
188}
189
190void DOMCacheStorage::doOpen(const String& name, DOMPromiseDeferred<IDLInterface<DOMCache>>&& promise)
191{
192 auto position = m_caches.findMatching([&](auto& item) { return item->name() == name; });
193 if (position != notFound) {
194 auto& cache = m_caches[position];
195 promise.resolve(DOMCache::create(*scriptExecutionContext(), String { cache->name() }, cache->identifier(), m_connection.copyRef()));
196 return;
197 }
198
199 m_connection->open(*origin(), name, [this, name, promise = WTFMove(promise), pendingActivity = makePendingActivity(*this)](const CacheIdentifierOrError& result) mutable {
200 if (!m_isStopped) {
201 if (!result.has_value())
202 promise.reject(DOMCacheEngine::convertToExceptionAndLog(scriptExecutionContext(), result.error()));
203 else {
204 if (result.value().hadStorageError)
205 logConsolePersistencyError(scriptExecutionContext(), name);
206
207 auto cache = DOMCache::create(*scriptExecutionContext(), String { name }, result.value().identifier, m_connection.copyRef());
208 promise.resolve(cache);
209 m_caches.append(WTFMove(cache));
210 }
211 }
212 });
213}
214
215void DOMCacheStorage::remove(const String& name, DOMPromiseDeferred<IDLBoolean>&& promise)
216{
217 retrieveCaches([this, name, promise = WTFMove(promise)](Optional<Exception>&& exception) mutable {
218 if (exception) {
219 promise.reject(WTFMove(exception.value()));
220 return;
221 }
222 doRemove(name, WTFMove(promise));
223 });
224}
225
226void DOMCacheStorage::doRemove(const String& name, DOMPromiseDeferred<IDLBoolean>&& promise)
227{
228 auto position = m_caches.findMatching([&](auto& item) { return item->name() == name; });
229 if (position == notFound) {
230 promise.resolve(false);
231 return;
232 }
233
234 m_connection->remove(m_caches[position]->identifier(), [this, name, promise = WTFMove(promise), pendingActivity = makePendingActivity(*this)](const CacheIdentifierOrError& result) mutable {
235 if (!m_isStopped) {
236 if (!result.has_value())
237 promise.reject(DOMCacheEngine::convertToExceptionAndLog(scriptExecutionContext(), result.error()));
238 else {
239 if (result.value().hadStorageError)
240 logConsolePersistencyError(scriptExecutionContext(), name);
241 promise.resolve(!!result.value().identifier);
242 }
243 }
244 });
245}
246
247void DOMCacheStorage::keys(KeysPromise&& promise)
248{
249 retrieveCaches([this, promise = WTFMove(promise)](Optional<Exception>&& exception) mutable {
250 if (exception) {
251 promise.reject(WTFMove(exception.value()));
252 return;
253 }
254
255 promise.resolve(WTF::map(m_caches, [] (const auto& cache) {
256 return cache->name();
257 }));
258 });
259}
260
261void DOMCacheStorage::stop()
262{
263 m_isStopped = true;
264}
265
266const char* DOMCacheStorage::activeDOMObjectName() const
267{
268 return "CacheStorage";
269}
270
271bool DOMCacheStorage::canSuspendForDocumentSuspension() const
272{
273 return !hasPendingActivity();
274}
275
276} // namespace WebCore
277