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
43namespace WebCore {
44
45class DatabaseManager::ProposedDatabase {
46public:
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
53private:
54 DatabaseManager& m_manager;
55 Ref<SecurityOrigin> m_origin;
56 DatabaseDetails m_details;
57};
58
59DatabaseManager::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
67inline DatabaseManager::ProposedDatabase::~ProposedDatabase()
68{
69 m_manager.removeProposedDatabase(*this);
70}
71
72DatabaseManager& DatabaseManager::singleton()
73{
74 static NeverDestroyed<DatabaseManager> instance;
75 return instance;
76}
77
78void DatabaseManager::initialize(const String& databasePath)
79{
80 platformInitialize(databasePath);
81 DatabaseTracker::initializeTracker(databasePath);
82}
83
84void DatabaseManager::setClient(DatabaseManagerClient* client)
85{
86 m_client = client;
87 DatabaseTracker::singleton().setClient(client);
88}
89
90bool DatabaseManager::isAvailable()
91{
92 return m_databaseIsAvailable;
93}
94
95void DatabaseManager::setIsAvailable(bool available)
96{
97 m_databaseIsAvailable = available;
98}
99
100Ref<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
109static inline void logOpenDatabaseError(ScriptExecutionContext&, const String&)
110{
111}
112
113#else
114
115static 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
122ExceptionOr<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
150ExceptionOr<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
189void DatabaseManager::addProposedDatabase(ProposedDatabase& database)
190{
191 std::lock_guard<Lock> lock { m_proposedDatabasesMutex };
192 m_proposedDatabases.add(&database);
193}
194
195void DatabaseManager::removeProposedDatabase(ProposedDatabase& database)
196{
197 std::lock_guard<Lock> lock { m_proposedDatabasesMutex };
198 m_proposedDatabases.remove(&database);
199}
200
201ExceptionOr<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
228bool DatabaseManager::hasOpenDatabases(ScriptExecutionContext& context)
229{
230 auto databaseContext = context.databaseContext();
231 return databaseContext && databaseContext->hasOpenDatabases();
232}
233
234void 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
243String 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
255DatabaseDetails 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
270void DatabaseManager::logErrorMessage(ScriptExecutionContext& context, const String& message)
271{
272 context.addConsoleMessage(MessageSource::Storage, MessageLevel::Error, message);
273}
274
275#if !PLATFORM(COCOA)
276void DatabaseManager::platformInitialize(const String& databasePath)
277{
278 UNUSED_PARAM(databasePath);
279}
280#endif
281
282} // namespace WebCore
283