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