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