1 /* 2 * Copyright (C) 2008, 2009 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 "StorageAreaSync.h" 28 29 #if ENABLE(DOM_STORAGE) 30 31 #include "CString.h" 32 #include "EventNames.h" 33 #include "HTMLElement.h" 34 #include "SecurityOrigin.h" 35 #include "SQLiteStatement.h" 36 #include "StorageAreaImpl.h" 37 #include "StorageSyncManager.h" 38 #include "SuddenTermination.h" 39 40 namespace WebCore { 41 42 // If the StorageArea undergoes rapid changes, don't sync each change to disk. 43 // Instead, queue up a batch of items to sync and actually do the sync at the following interval. 44 static const double StorageSyncInterval = 1.0; 45 46 PassRefPtr<StorageAreaSync> StorageAreaSync::create(PassRefPtr<StorageSyncManager> storageSyncManager, PassRefPtr<StorageAreaImpl> storageArea, String databaseIdentifier) 47 { 48 return adoptRef(new StorageAreaSync(storageSyncManager, storageArea, databaseIdentifier)); 49 } 50 51 StorageAreaSync::StorageAreaSync(PassRefPtr<StorageSyncManager> storageSyncManager, PassRefPtr<StorageAreaImpl> storageArea, String databaseIdentifier) 52 : m_syncTimer(this, &StorageAreaSync::syncTimerFired) 53 , m_itemsCleared(false) 54 , m_finalSyncScheduled(false) 55 , m_storageArea(storageArea) 56 , m_syncManager(storageSyncManager) 57 , m_databaseIdentifier(databaseIdentifier.crossThreadString()) 58 , m_clearItemsWhileSyncing(false) 59 , m_syncScheduled(false) 60 , m_importComplete(false) 61 { 62 ASSERT(isMainThread()); 63 ASSERT(m_storageArea); 64 ASSERT(m_syncManager); 65 66 // FIXME: If it can't import, then the default WebKit behavior should be that of private browsing, 67 // not silently ignoring it. https://bugs.webkit.org/show_bug.cgi?id=25894 68 if (!m_syncManager->scheduleImport(this)) 69 m_importComplete = true; 70 } 71 72 StorageAreaSync::~StorageAreaSync() 73 { 74 ASSERT(isMainThread()); 75 ASSERT(!m_syncTimer.isActive()); 76 ASSERT(m_finalSyncScheduled); 77 } 78 79 void StorageAreaSync::scheduleFinalSync() 80 { 81 ASSERT(isMainThread()); 82 // FIXME: We do this to avoid races, but it'd be better to make things safe without blocking. 83 blockUntilImportComplete(); 84 m_storageArea = 0; // This is done in blockUntilImportComplete() but this is here as a form of documentation that we must be absolutely sure the ref count cycle is broken. 85 86 if (m_syncTimer.isActive()) 87 m_syncTimer.stop(); 88 else { 89 // The following is balanced by the call to enableSuddenTermination in the 90 // syncTimerFired function. 91 disableSuddenTermination(); 92 } 93 // FIXME: This is synchronous. We should do it on the background process, but 94 // we should do it safely. 95 syncTimerFired(&m_syncTimer); 96 m_finalSyncScheduled = true; 97 } 98 99 void StorageAreaSync::scheduleItemForSync(const String& key, const String& value) 100 { 101 ASSERT(isMainThread()); 102 ASSERT(!m_finalSyncScheduled); 103 104 m_changedItems.set(key, value); 105 if (!m_syncTimer.isActive()) { 106 m_syncTimer.startOneShot(StorageSyncInterval); 107 108 // The following is balanced by the call to enableSuddenTermination in the 109 // syncTimerFired function. 110 disableSuddenTermination(); 111 } 112 } 113 114 void StorageAreaSync::scheduleClear() 115 { 116 ASSERT(isMainThread()); 117 ASSERT(!m_finalSyncScheduled); 118 119 m_changedItems.clear(); 120 m_itemsCleared = true; 121 if (!m_syncTimer.isActive()) { 122 m_syncTimer.startOneShot(StorageSyncInterval); 123 124 // The following is balanced by the call to enableSuddenTermination in the 125 // syncTimerFired function. 126 disableSuddenTermination(); 127 } 128 } 129 130 void StorageAreaSync::syncTimerFired(Timer<StorageAreaSync>*) 131 { 132 ASSERT(isMainThread()); 133 134 HashMap<String, String>::iterator it = m_changedItems.begin(); 135 HashMap<String, String>::iterator end = m_changedItems.end(); 136 137 { 138 MutexLocker locker(m_syncLock); 139 140 if (m_itemsCleared) { 141 m_itemsPendingSync.clear(); 142 m_clearItemsWhileSyncing = true; 143 m_itemsCleared = false; 144 } 145 146 for (; it != end; ++it) 147 m_itemsPendingSync.set(it->first.crossThreadString(), it->second.crossThreadString()); 148 149 if (!m_syncScheduled) { 150 m_syncScheduled = true; 151 152 // The following is balanced by the call to enableSuddenTermination in the 153 // performSync function. 154 disableSuddenTermination(); 155 156 m_syncManager->scheduleSync(this); 157 } 158 } 159 160 // The following is balanced by the calls to disableSuddenTermination in the 161 // scheduleItemForSync, scheduleClear, and scheduleFinalSync functions. 162 enableSuddenTermination(); 163 164 m_changedItems.clear(); 165 } 166 167 void StorageAreaSync::performImport() 168 { 169 ASSERT(!isMainThread()); 170 ASSERT(!m_database.isOpen()); 171 172 String databaseFilename = m_syncManager->fullDatabaseFilename(m_databaseIdentifier); 173 174 if (databaseFilename.isEmpty()) { 175 LOG_ERROR("Filename for local storage database is empty - cannot open for persistent storage"); 176 markImported(); 177 return; 178 } 179 180 if (!m_database.open(databaseFilename)) { 181 LOG_ERROR("Failed to open database file %s for local storage", databaseFilename.utf8().data()); 182 markImported(); 183 return; 184 } 185 186 if (!m_database.executeCommand("CREATE TABLE IF NOT EXISTS ItemTable (key TEXT UNIQUE ON CONFLICT REPLACE, value TEXT NOT NULL ON CONFLICT FAIL)")) { 187 LOG_ERROR("Failed to create table ItemTable for local storage"); 188 markImported(); 189 return; 190 } 191 192 SQLiteStatement query(m_database, "SELECT key, value FROM ItemTable"); 193 if (query.prepare() != SQLResultOk) { 194 LOG_ERROR("Unable to select items from ItemTable for local storage"); 195 markImported(); 196 return; 197 } 198 199 HashMap<String, String> itemMap; 200 201 int result = query.step(); 202 while (result == SQLResultRow) { 203 itemMap.set(query.getColumnText(0), query.getColumnText(1)); 204 result = query.step(); 205 } 206 207 if (result != SQLResultDone) { 208 LOG_ERROR("Error reading items from ItemTable for local storage"); 209 markImported(); 210 return; 211 } 212 213 HashMap<String, String>::iterator it = itemMap.begin(); 214 HashMap<String, String>::iterator end = itemMap.end(); 215 216 for (; it != end; ++it) 217 m_storageArea->importItem(it->first, it->second); 218 219 markImported(); 220 } 221 222 void StorageAreaSync::markImported() 223 { 224 MutexLocker locker(m_importLock); 225 m_importComplete = true; 226 m_importCondition.signal(); 227 } 228 229 // FIXME: In the future, we should allow use of StorageAreas while it's importing (when safe to do so). 230 // Blocking everything until the import is complete is by far the simplest and safest thing to do, but 231 // there is certainly room for safe optimization: Key/length will never be able to make use of such an 232 // optimization (since the order of iteration can change as items are being added). Get can return any 233 // item currently in the map. Get/remove can work whether or not it's in the map, but we'll need a list 234 // of items the import should not overwrite. Clear can also work, but it'll need to kill the import 235 // job first. 236 void StorageAreaSync::blockUntilImportComplete() 237 { 238 ASSERT(isMainThread()); 239 240 // Fast path. We set m_storageArea to 0 only after m_importComplete being true. 241 if (!m_storageArea) 242 return; 243 244 MutexLocker locker(m_importLock); 245 while (!m_importComplete) 246 m_importCondition.wait(m_importLock); 247 m_storageArea = 0; 248 } 249 250 void StorageAreaSync::sync(bool clearItems, const HashMap<String, String>& items) 251 { 252 ASSERT(!isMainThread()); 253 254 if (!m_database.isOpen()) 255 return; 256 257 // If the clear flag is set, then we clear all items out before we write any new ones in. 258 if (clearItems) { 259 SQLiteStatement clear(m_database, "DELETE FROM ItemTable"); 260 if (clear.prepare() != SQLResultOk) { 261 LOG_ERROR("Failed to prepare clear statement - cannot write to local storage database"); 262 return; 263 } 264 265 int result = clear.step(); 266 if (result != SQLResultDone) { 267 LOG_ERROR("Failed to clear all items in the local storage database - %i", result); 268 return; 269 } 270 } 271 272 SQLiteStatement insert(m_database, "INSERT INTO ItemTable VALUES (?, ?)"); 273 if (insert.prepare() != SQLResultOk) { 274 LOG_ERROR("Failed to prepare insert statement - cannot write to local storage database"); 275 return; 276 } 277 278 SQLiteStatement remove(m_database, "DELETE FROM ItemTable WHERE key=?"); 279 if (remove.prepare() != SQLResultOk) { 280 LOG_ERROR("Failed to prepare delete statement - cannot write to local storage database"); 281 return; 282 } 283 284 HashMap<String, String>::const_iterator end = items.end(); 285 286 for (HashMap<String, String>::const_iterator it = items.begin(); it != end; ++it) { 287 // Based on the null-ness of the second argument, decide whether this is an insert or a delete. 288 SQLiteStatement& query = it->second.isNull() ? remove : insert; 289 290 query.bindText(1, it->first); 291 292 // If the second argument is non-null, we're doing an insert, so bind it as the value. 293 if (!it->second.isNull()) 294 query.bindText(2, it->second); 295 296 int result = query.step(); 297 if (result != SQLResultDone) { 298 LOG_ERROR("Failed to update item in the local storage database - %i", result); 299 break; 300 } 301 302 query.reset(); 303 } 304 } 305 306 void StorageAreaSync::performSync() 307 { 308 ASSERT(!isMainThread()); 309 310 bool clearItems; 311 HashMap<String, String> items; 312 { 313 MutexLocker locker(m_syncLock); 314 315 ASSERT(m_syncScheduled); 316 317 clearItems = m_clearItemsWhileSyncing; 318 m_itemsPendingSync.swap(items); 319 320 m_clearItemsWhileSyncing = false; 321 m_syncScheduled = false; 322 } 323 324 sync(clearItems, items); 325 326 // The following is balanced by the call to disableSuddenTermination in the 327 // syncTimerFired function. 328 enableSuddenTermination(); 329 } 330 331 } // namespace WebCore 332 333 #endif // ENABLE(DOM_STORAGE) 334