ed808fde662f22ccf1e91225d02da0ec8fb7ada3
[WebKit-https.git] / Source / WebCore / Modules / cache / CacheStorage.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 "CacheStorage.h"
28
29 #include "CacheQueryOptions.h"
30 #include "JSCache.h"
31 #include "JSFetchResponse.h"
32 #include "ScriptExecutionContext.h"
33
34 namespace WebCore {
35
36 CacheStorage::CacheStorage(ScriptExecutionContext& context, Ref<CacheStorageConnection>&& connection)
37     : ActiveDOMObject(&context)
38     , m_connection(WTFMove(connection))
39 {
40     suspendIfNeeded();
41 }
42
43 String CacheStorage::origin() const
44 {
45     // FIXME: Do we really need to check for origin being null?
46     auto* origin = scriptExecutionContext() ? scriptExecutionContext()->securityOrigin() : nullptr;
47     return origin ? origin->toString() : String();
48 }
49
50 static void doSequentialMatch(size_t index, Vector<Ref<Cache>>&& caches, Cache::RequestInfo&& info, CacheQueryOptions&& options, Cache::MatchCallback&& completionHandler)
51 {
52     if (index >= caches.size()) {
53         completionHandler(nullptr);
54         return;
55     }
56
57     caches[index]->doMatch(WTFMove(info), WTFMove(options), [caches = WTFMove(caches), info, options, completionHandler = WTFMove(completionHandler), index](FetchResponse* value) mutable {
58         if (value) {
59             completionHandler(value);
60             return;
61         }
62         doSequentialMatch(++index, WTFMove(caches), WTFMove(info), WTFMove(options), WTFMove(completionHandler));
63     });
64 }
65
66 static inline void startSequentialMatch(Vector<Ref<Cache>>&& caches, Cache::RequestInfo&& info, CacheQueryOptions&& options, Cache::MatchCallback&& completionHandler)
67 {
68     doSequentialMatch(0, WTFMove(caches), WTFMove(info), WTFMove(options), WTFMove(completionHandler));
69 }
70
71 static inline Vector<Ref<Cache>> copyCaches(const Vector<Ref<Cache>>& caches)
72 {
73     Vector<Ref<Cache>> copy;
74     copy.reserveInitialCapacity(caches.size());
75     for (auto& cache : caches)
76         copy.uncheckedAppend(cache.copyRef());
77     return copy;
78 }
79
80 void CacheStorage::match(Cache::RequestInfo&& info, CacheQueryOptions&& options, Ref<DeferredPromise>&& promise)
81 {
82     retrieveCaches([this, info = WTFMove(info), options = WTFMove(options), promise = WTFMove(promise)]() mutable {
83         if (!options.cacheName.isNull()) {
84             auto position = m_caches.findMatching([&](auto& item) { return item->name() == options.cacheName; });
85             if (position != notFound) {
86                 m_caches[position]->match(WTFMove(info), WTFMove(options), WTFMove(promise));
87                 return;
88             }
89             promise->resolve();
90             return;
91         }
92
93         setPendingActivity(this);
94         startSequentialMatch(copyCaches(m_caches), WTFMove(info), WTFMove(options), [this, promise = WTFMove(promise)](FetchResponse* result) mutable {
95             if (!m_isStopped) {
96                 if (!result)
97                     promise->resolve();
98                 else
99                     promise->resolve<IDLInterface<FetchResponse>>(*result);
100             }
101             unsetPendingActivity(this);
102         });
103     });
104 }
105
106 void CacheStorage::has(const String& name, DOMPromiseDeferred<IDLBoolean>&& promise)
107 {
108     retrieveCaches([this, name, promise = WTFMove(promise)]() mutable {
109         promise.resolve(m_caches.findMatching([&](auto& item) { return item->name() == name; }) != notFound);
110     });
111 }
112
113 void CacheStorage::retrieveCaches(WTF::Function<void()>&& callback)
114 {
115     String origin = this->origin();
116     if (origin.isNull())
117         return;
118
119     setPendingActivity(this);
120     m_connection->retrieveCaches(origin, [this, callback = WTFMove(callback)](Vector<CacheStorageConnection::CacheInfo>&& cachesInfo) {
121         if (!m_isStopped) {
122             ASSERT(scriptExecutionContext());
123             m_caches.removeAllMatching([&](auto& cache) {
124                 return cachesInfo.findMatching([&](const auto& info) { return info.identifier == cache->identifier(); }) == notFound;
125             });
126             for (auto& info : cachesInfo) {
127                 if (m_caches.findMatching([&](const auto& cache) { return info.identifier == cache->identifier(); }) == notFound)
128                     m_caches.append(Cache::create(*scriptExecutionContext(), WTFMove(info.name), info.identifier, m_connection.copyRef()));
129             }
130
131             std::sort(m_caches.begin(), m_caches.end(), [&](auto& a, auto& b) {
132                 return a->identifier() < b->identifier();
133             });
134
135             callback();
136         }
137         unsetPendingActivity(this);
138     });
139 }
140
141 void CacheStorage::open(const String& name, DOMPromiseDeferred<IDLInterface<Cache>>&& promise)
142 {
143     retrieveCaches([this, name, promise = WTFMove(promise)]() mutable {
144         auto position = m_caches.findMatching([&](auto& item) { return item->name() == name; });
145         if (position != notFound) {
146             auto& cache = m_caches[position];
147             promise.resolve(Cache::create(*scriptExecutionContext(), String { cache->name() }, cache->identifier(), m_connection.copyRef()));
148             return;
149         }
150
151         String origin = this->origin();
152         ASSERT(!origin.isNull());
153
154         setPendingActivity(this);
155         m_connection->open(origin, name, [this, name, promise = WTFMove(promise)](uint64_t cacheIdentifier, CacheStorageConnection::Error error) mutable {
156             if (!m_isStopped) {
157                 auto result = CacheStorageConnection::errorToException(error);
158                 if (result.hasException())
159                     promise.reject(result.releaseException());
160                 else {
161                     auto cache = Cache::create(*scriptExecutionContext(), String { name }, cacheIdentifier, m_connection.copyRef());
162                     promise.resolve(cache);
163                     m_caches.append(WTFMove(cache));
164                 }
165             }
166             unsetPendingActivity(this);
167         });
168     });
169 }
170
171 void CacheStorage::remove(const String& name, DOMPromiseDeferred<IDLBoolean>&& promise)
172 {
173     retrieveCaches([this, name, promise = WTFMove(promise)]() mutable {
174         auto position = m_caches.findMatching([&](auto& item) { return item->name() == name; });
175         if (position == notFound) {
176             promise.resolve(false);
177             return;
178         }
179
180         String origin = this->origin();
181         ASSERT(!origin.isNull());
182
183         setPendingActivity(this);
184         m_connection->remove(m_caches[position]->identifier(), [this, name, promise = WTFMove(promise)](uint64_t cacheIdentifier, CacheStorageConnection::Error error) mutable {
185             UNUSED_PARAM(cacheIdentifier);
186             if (!m_isStopped)
187                 promise.settle(CacheStorageConnection::exceptionOrResult(true, error));
188
189             unsetPendingActivity(this);
190         });
191         m_caches.remove(position);
192     });
193 }
194
195 void CacheStorage::keys(KeysPromise&& promise)
196 {
197     retrieveCaches([this, promise = WTFMove(promise)]() mutable {
198         Vector<String> keys;
199         keys.reserveInitialCapacity(m_caches.size());
200         for (auto& cache : m_caches)
201             keys.uncheckedAppend(cache->name());
202         promise.resolve(keys);
203     });
204 }
205
206 void CacheStorage::stop()
207 {
208     m_isStopped = true;
209 }
210
211 const char* CacheStorage::activeDOMObjectName() const
212 {
213     return "CacheStorage";
214 }
215
216 bool CacheStorage::canSuspendForDocumentSuspension() const
217 {
218     return !hasPendingActivity();
219 }
220
221 } // namespace WebCore