2e992b91351edadb81c58d847253d1a46ad5e599
[WebKit-https.git] / Source / WebCore / Modules / webdatabase / DatabaseManager.cpp
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 "ExceptionCode.h"
35 #include "InspectorInstrumentation.h"
36 #include "Logging.h"
37 #include "PlatformStrategies.h"
38 #include "ScriptController.h"
39 #include "ScriptExecutionContext.h"
40 #include "SecurityOrigin.h"
41 #include "SecurityOriginData.h"
42 #include <wtf/NeverDestroyed.h>
43
44 namespace WebCore {
45
46 class DatabaseManager::ProposedDatabase {
47 public:
48     ProposedDatabase(DatabaseManager&, SecurityOrigin&, const String& name, const String& displayName, unsigned long estimatedSize);
49     ~ProposedDatabase();
50
51     SecurityOrigin& origin() { return m_origin; }
52     DatabaseDetails& details() { return m_details; }
53
54 private:
55     DatabaseManager& m_manager;
56     Ref<SecurityOrigin> m_origin;
57     DatabaseDetails m_details;
58 };
59
60 DatabaseManager::ProposedDatabase::ProposedDatabase(DatabaseManager& manager, SecurityOrigin& origin, const String& name, const String& displayName, unsigned long estimatedSize)
61     : m_manager(manager)
62     , m_origin(origin.isolatedCopy())
63     , m_details(name.isolatedCopy(), displayName.isolatedCopy(), estimatedSize, 0, 0, 0)
64 {
65     m_manager.addProposedDatabase(*this);
66 }
67
68 inline DatabaseManager::ProposedDatabase::~ProposedDatabase()
69 {
70     m_manager.removeProposedDatabase(*this);
71 }
72
73 DatabaseManager& DatabaseManager::singleton()
74 {
75     static NeverDestroyed<DatabaseManager> instance;
76     return instance;
77 }
78
79 void DatabaseManager::initialize(const String& databasePath)
80 {
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 databaseContext = this->databaseContext(context);
125
126     auto backend = tryToOpenDatabaseBackend(databaseContext, name, expectedVersion, displayName, estimatedSize, setVersionInNewDatabase, FirstTryToOpenDatabase);
127
128     if (backend.hasException()) {
129         if (backend.exception().code() == QUOTA_EXCEEDED_ERR) {
130             // Notify the client that we've exceeded the database quota.
131             // The client may want to increase the quota, and we'll give it
132             // one more try after if that is the case.
133             {
134                 // FIXME: What guarantees context.securityOrigin() is non-null?
135                 ProposedDatabase proposedDatabase { *this, *context.securityOrigin(), name, displayName, estimatedSize };
136                 databaseContext->databaseExceededQuota(name, proposedDatabase.details());
137             }
138             backend = tryToOpenDatabaseBackend(databaseContext, name, expectedVersion, displayName, estimatedSize, setVersionInNewDatabase, RetryOpenDatabase);
139         }
140     }
141
142     if (backend.hasException()) {
143         if (backend.exception().code() == INVALID_STATE_ERR)
144             logErrorMessage(context, backend.exception().message());
145         else
146             logOpenDatabaseError(context, name);
147     }
148
149     return backend;
150 }
151
152 ExceptionOr<Ref<Database>> DatabaseManager::tryToOpenDatabaseBackend(DatabaseContext& backendContext, const String& name, const String& expectedVersion, const String& displayName, unsigned estimatedSize, bool setVersionInNewDatabase,
153     OpenAttempt attempt)
154 {
155     ExceptionOr<void> preflightResult;
156     switch (attempt) {
157     case FirstTryToOpenDatabase:
158         preflightResult = DatabaseTracker::singleton().canEstablishDatabase(backendContext, name, estimatedSize);
159         break;
160     case RetryOpenDatabase:
161         preflightResult = DatabaseTracker::singleton().retryCanEstablishDatabase(backendContext, name, estimatedSize);
162         break;
163     }
164     if (preflightResult.hasException())
165         return preflightResult.releaseException();
166
167     auto database = adoptRef(*new Database(backendContext, name, expectedVersion, displayName, estimatedSize));
168
169     auto openResult = database->openAndVerifyVersion(setVersionInNewDatabase);
170     if (openResult.hasException())
171         return openResult.releaseException();
172
173     // FIXME: What guarantees backendContext.securityOrigin() is non-null?
174     DatabaseTracker::singleton().setDatabaseDetails(backendContext.securityOrigin(), name, displayName, estimatedSize);
175     return WTFMove(database);
176 }
177
178 void DatabaseManager::addProposedDatabase(ProposedDatabase& database)
179 {
180     std::lock_guard<Lock> lock { m_proposedDatabasesMutex };
181     m_proposedDatabases.add(&database);
182 }
183
184 void DatabaseManager::removeProposedDatabase(ProposedDatabase& database)
185 {
186     std::lock_guard<Lock> lock { m_proposedDatabasesMutex };
187     m_proposedDatabases.remove(&database);
188 }
189
190 ExceptionOr<Ref<Database>> DatabaseManager::openDatabase(ScriptExecutionContext& context, const String& name, const String& expectedVersion, const String& displayName, unsigned estimatedSize, RefPtr<DatabaseCallback>&& creationCallback)
191 {
192     ScriptController::initializeThreading();
193
194     bool setVersionInNewDatabase = !creationCallback;
195     auto openResult = openDatabaseBackend(context, name, expectedVersion, displayName, estimatedSize, setVersionInNewDatabase);
196     if (openResult.hasException())
197         return openResult.releaseException();
198
199     RefPtr<Database> database = openResult.releaseReturnValue();
200
201     auto databaseContext = this->databaseContext(context);
202     databaseContext->setHasOpenDatabases();
203     InspectorInstrumentation::didOpenDatabase(&context, database.copyRef(), context.securityOrigin()->host(), name, expectedVersion);
204
205     if (database->isNew() && creationCallback.get()) {
206         LOG(StorageAPI, "Scheduling DatabaseCreationCallbackTask for database %p\n", database.get());
207         database->setHasPendingCreationEvent(true);
208         database->m_scriptExecutionContext->postTask([creationCallback, database] (ScriptExecutionContext&) {
209             creationCallback->handleEvent(database.get());
210             database->setHasPendingCreationEvent(false);
211         });
212     }
213
214     return database.releaseNonNull();
215 }
216
217 bool DatabaseManager::hasOpenDatabases(ScriptExecutionContext& context)
218 {
219     auto databaseContext = context.databaseContext();
220     return databaseContext && databaseContext->hasOpenDatabases();
221 }
222
223 void DatabaseManager::stopDatabases(ScriptExecutionContext& context, DatabaseTaskSynchronizer* synchronizer)
224 {
225     auto databaseContext = context.databaseContext();
226     if (!databaseContext || !databaseContext->stopDatabases(synchronizer)) {
227         if (synchronizer)
228             synchronizer->taskCompleted();
229     }
230 }
231
232 String DatabaseManager::fullPathForDatabase(SecurityOrigin& origin, const String& name, bool createIfDoesNotExist)
233 {
234     {
235         std::lock_guard<Lock> lock { m_proposedDatabasesMutex };
236         for (auto* proposedDatabase : m_proposedDatabases) {
237             if (proposedDatabase->details().name() == name && proposedDatabase->origin().equal(&origin))
238                 return String();
239         }
240     }
241     return DatabaseTracker::singleton().fullPathForDatabase(SecurityOriginData::fromSecurityOrigin(origin), name, createIfDoesNotExist);
242 }
243
244 DatabaseDetails DatabaseManager::detailsForNameAndOrigin(const String& name, SecurityOrigin& origin)
245 {
246     {
247         std::lock_guard<Lock> lock { m_proposedDatabasesMutex };
248         for (auto* proposedDatabase : m_proposedDatabases) {
249             if (proposedDatabase->details().name() == name && proposedDatabase->origin().equal(&origin)) {
250                 ASSERT(proposedDatabase->details().threadID() == std::this_thread::get_id() || isMainThread());
251                 return proposedDatabase->details();
252             }
253         }
254     }
255
256     return DatabaseTracker::singleton().detailsForNameAndOrigin(name, SecurityOriginData::fromSecurityOrigin(origin));
257 }
258
259 void DatabaseManager::logErrorMessage(ScriptExecutionContext& context, const String& message)
260 {
261     context.addConsoleMessage(MessageSource::Storage, MessageLevel::Error, message);
262 }
263
264 } // namespace WebCore