1 | /* |
2 | * Copyright (C) 2012 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. ``AS IS'' AND ANY |
14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include "DatabaseManager.h" |
28 | |
29 | #include "Database.h" |
30 | #include "DatabaseCallback.h" |
31 | #include "DatabaseContext.h" |
32 | #include "DatabaseTask.h" |
33 | #include "DatabaseTracker.h" |
34 | #include "InspectorInstrumentation.h" |
35 | #include "Logging.h" |
36 | #include "PlatformStrategies.h" |
37 | #include "ScriptController.h" |
38 | #include "ScriptExecutionContext.h" |
39 | #include "SecurityOrigin.h" |
40 | #include "SecurityOriginData.h" |
41 | #include <wtf/NeverDestroyed.h> |
42 | |
43 | namespace WebCore { |
44 | |
45 | class DatabaseManager::ProposedDatabase { |
46 | public: |
47 | ProposedDatabase(DatabaseManager&, SecurityOrigin&, const String& name, const String& displayName, unsigned long estimatedSize); |
48 | ~ProposedDatabase(); |
49 | |
50 | SecurityOrigin& origin() { return m_origin; } |
51 | DatabaseDetails& details() { return m_details; } |
52 | |
53 | private: |
54 | DatabaseManager& m_manager; |
55 | Ref<SecurityOrigin> m_origin; |
56 | DatabaseDetails m_details; |
57 | }; |
58 | |
59 | DatabaseManager::ProposedDatabase::ProposedDatabase(DatabaseManager& manager, SecurityOrigin& origin, const String& name, const String& displayName, unsigned long estimatedSize) |
60 | : m_manager(manager) |
61 | , m_origin(origin.isolatedCopy()) |
62 | , m_details(name.isolatedCopy(), displayName.isolatedCopy(), estimatedSize, 0, WTF::nullopt, WTF::nullopt) |
63 | { |
64 | m_manager.addProposedDatabase(*this); |
65 | } |
66 | |
67 | inline DatabaseManager::ProposedDatabase::~ProposedDatabase() |
68 | { |
69 | m_manager.removeProposedDatabase(*this); |
70 | } |
71 | |
72 | DatabaseManager& DatabaseManager::singleton() |
73 | { |
74 | static NeverDestroyed<DatabaseManager> instance; |
75 | return instance; |
76 | } |
77 | |
78 | void DatabaseManager::initialize(const String& databasePath) |
79 | { |
80 | platformInitialize(databasePath); |
81 | DatabaseTracker::initializeTracker(databasePath); |
82 | } |
83 | |
84 | void DatabaseManager::setClient(DatabaseManagerClient* client) |
85 | { |
86 | m_client = client; |
87 | DatabaseTracker::singleton().setClient(client); |
88 | } |
89 | |
90 | bool DatabaseManager::isAvailable() |
91 | { |
92 | return m_databaseIsAvailable; |
93 | } |
94 | |
95 | void DatabaseManager::setIsAvailable(bool available) |
96 | { |
97 | m_databaseIsAvailable = available; |
98 | } |
99 | |
100 | Ref<DatabaseContext> DatabaseManager::databaseContext(ScriptExecutionContext& context) |
101 | { |
102 | if (auto databaseContext = context.databaseContext()) |
103 | return *databaseContext; |
104 | return adoptRef(*new DatabaseContext(context)); |
105 | } |
106 | |
107 | #if LOG_DISABLED |
108 | |
109 | static inline void logOpenDatabaseError(ScriptExecutionContext&, const String&) |
110 | { |
111 | } |
112 | |
113 | #else |
114 | |
115 | static void logOpenDatabaseError(ScriptExecutionContext& context, const String& name) |
116 | { |
117 | LOG(StorageAPI, "Database %s for origin %s not allowed to be established" , name.utf8().data(), context.securityOrigin()->toString().utf8().data()); |
118 | } |
119 | |
120 | #endif |
121 | |
122 | ExceptionOr<Ref<Database>> DatabaseManager::openDatabaseBackend(ScriptExecutionContext& context, const String& name, const String& expectedVersion, const String& displayName, unsigned estimatedSize, bool setVersionInNewDatabase) |
123 | { |
124 | auto backend = tryToOpenDatabaseBackend(context, name, expectedVersion, displayName, estimatedSize, setVersionInNewDatabase, FirstTryToOpenDatabase); |
125 | |
126 | if (backend.hasException()) { |
127 | if (backend.exception().code() == QuotaExceededError) { |
128 | // Notify the client that we've exceeded the database quota. |
129 | // The client may want to increase the quota, and we'll give it |
130 | // one more try after if that is the case. |
131 | { |
132 | // FIXME: What guarantees context.securityOrigin() is non-null? |
133 | ProposedDatabase proposedDatabase { *this, *context.securityOrigin(), name, displayName, estimatedSize }; |
134 | this->databaseContext(context)->databaseExceededQuota(name, proposedDatabase.details()); |
135 | } |
136 | backend = tryToOpenDatabaseBackend(context, name, expectedVersion, displayName, estimatedSize, setVersionInNewDatabase, RetryOpenDatabase); |
137 | } |
138 | } |
139 | |
140 | if (backend.hasException()) { |
141 | if (backend.exception().code() == InvalidStateError) |
142 | logErrorMessage(context, backend.exception().message()); |
143 | else |
144 | logOpenDatabaseError(context, name); |
145 | } |
146 | |
147 | return backend; |
148 | } |
149 | |
150 | ExceptionOr<Ref<Database>> DatabaseManager::tryToOpenDatabaseBackend(ScriptExecutionContext& scriptContext, const String& name, const String& expectedVersion, const String& displayName, unsigned estimatedSize, bool setVersionInNewDatabase, |
151 | OpenAttempt attempt) |
152 | { |
153 | if (is<Document>(&scriptContext)) { |
154 | auto* page = downcast<Document>(scriptContext).page(); |
155 | if (!page || page->usesEphemeralSession()) |
156 | return Exception { SecurityError }; |
157 | } |
158 | |
159 | if (scriptContext.isWorkerGlobalScope()) { |
160 | ASSERT_NOT_REACHED(); |
161 | return Exception { SecurityError }; |
162 | } |
163 | |
164 | auto backendContext = this->databaseContext(scriptContext); |
165 | |
166 | ExceptionOr<void> preflightResult; |
167 | switch (attempt) { |
168 | case FirstTryToOpenDatabase: |
169 | preflightResult = DatabaseTracker::singleton().canEstablishDatabase(backendContext, name, estimatedSize); |
170 | break; |
171 | case RetryOpenDatabase: |
172 | preflightResult = DatabaseTracker::singleton().retryCanEstablishDatabase(backendContext, name, estimatedSize); |
173 | break; |
174 | } |
175 | if (preflightResult.hasException()) |
176 | return preflightResult.releaseException(); |
177 | |
178 | auto database = adoptRef(*new Database(backendContext, name, expectedVersion, displayName, estimatedSize)); |
179 | |
180 | auto openResult = database->openAndVerifyVersion(setVersionInNewDatabase); |
181 | if (openResult.hasException()) |
182 | return openResult.releaseException(); |
183 | |
184 | // FIXME: What guarantees backendContext.securityOrigin() is non-null? |
185 | DatabaseTracker::singleton().setDatabaseDetails(backendContext->securityOrigin(), name, displayName, estimatedSize); |
186 | return database; |
187 | } |
188 | |
189 | void DatabaseManager::addProposedDatabase(ProposedDatabase& database) |
190 | { |
191 | std::lock_guard<Lock> lock { m_proposedDatabasesMutex }; |
192 | m_proposedDatabases.add(&database); |
193 | } |
194 | |
195 | void DatabaseManager::removeProposedDatabase(ProposedDatabase& database) |
196 | { |
197 | std::lock_guard<Lock> lock { m_proposedDatabasesMutex }; |
198 | m_proposedDatabases.remove(&database); |
199 | } |
200 | |
201 | ExceptionOr<Ref<Database>> DatabaseManager::openDatabase(ScriptExecutionContext& context, const String& name, const String& expectedVersion, const String& displayName, unsigned estimatedSize, RefPtr<DatabaseCallback>&& creationCallback) |
202 | { |
203 | ScriptController::initializeThreading(); |
204 | |
205 | bool setVersionInNewDatabase = !creationCallback; |
206 | auto openResult = openDatabaseBackend(context, name, expectedVersion, displayName, estimatedSize, setVersionInNewDatabase); |
207 | if (openResult.hasException()) |
208 | return openResult.releaseException(); |
209 | |
210 | RefPtr<Database> database = openResult.releaseReturnValue(); |
211 | |
212 | auto databaseContext = this->databaseContext(context); |
213 | databaseContext->setHasOpenDatabases(); |
214 | InspectorInstrumentation::didOpenDatabase(*database); |
215 | |
216 | if (database->isNew() && creationCallback.get()) { |
217 | LOG(StorageAPI, "Scheduling DatabaseCreationCallbackTask for database %p\n" , database.get()); |
218 | database->setHasPendingCreationEvent(true); |
219 | database->m_scriptExecutionContext->postTask([creationCallback, database] (ScriptExecutionContext&) { |
220 | creationCallback->handleEvent(*database); |
221 | database->setHasPendingCreationEvent(false); |
222 | }); |
223 | } |
224 | |
225 | return database.releaseNonNull(); |
226 | } |
227 | |
228 | bool DatabaseManager::hasOpenDatabases(ScriptExecutionContext& context) |
229 | { |
230 | auto databaseContext = context.databaseContext(); |
231 | return databaseContext && databaseContext->hasOpenDatabases(); |
232 | } |
233 | |
234 | void DatabaseManager::stopDatabases(ScriptExecutionContext& context, DatabaseTaskSynchronizer* synchronizer) |
235 | { |
236 | auto databaseContext = context.databaseContext(); |
237 | if (!databaseContext || !databaseContext->stopDatabases(synchronizer)) { |
238 | if (synchronizer) |
239 | synchronizer->taskCompleted(); |
240 | } |
241 | } |
242 | |
243 | String DatabaseManager::fullPathForDatabase(SecurityOrigin& origin, const String& name, bool createIfDoesNotExist) |
244 | { |
245 | { |
246 | std::lock_guard<Lock> lock { m_proposedDatabasesMutex }; |
247 | for (auto* proposedDatabase : m_proposedDatabases) { |
248 | if (proposedDatabase->details().name() == name && proposedDatabase->origin().equal(&origin)) |
249 | return String(); |
250 | } |
251 | } |
252 | return DatabaseTracker::singleton().fullPathForDatabase(origin.data(), name, createIfDoesNotExist); |
253 | } |
254 | |
255 | DatabaseDetails DatabaseManager::detailsForNameAndOrigin(const String& name, SecurityOrigin& origin) |
256 | { |
257 | { |
258 | std::lock_guard<Lock> lock { m_proposedDatabasesMutex }; |
259 | for (auto* proposedDatabase : m_proposedDatabases) { |
260 | if (proposedDatabase->details().name() == name && proposedDatabase->origin().equal(&origin)) { |
261 | ASSERT(&proposedDatabase->details().thread() == &Thread::current() || isMainThread()); |
262 | return proposedDatabase->details(); |
263 | } |
264 | } |
265 | } |
266 | |
267 | return DatabaseTracker::singleton().detailsForNameAndOrigin(name, origin.data()); |
268 | } |
269 | |
270 | void DatabaseManager::logErrorMessage(ScriptExecutionContext& context, const String& message) |
271 | { |
272 | context.addConsoleMessage(MessageSource::Storage, MessageLevel::Error, message); |
273 | } |
274 | |
275 | #if !PLATFORM(COCOA) |
276 | void DatabaseManager::platformInitialize(const String& databasePath) |
277 | { |
278 | UNUSED_PARAM(databasePath); |
279 | } |
280 | #endif |
281 | |
282 | } // namespace WebCore |
283 | |