• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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 "LocalStorageArea.h"
28 
29 #include "CString.h"
30 #include "EventNames.h"
31 #include "Frame.h"
32 #include "FrameTree.h"
33 #include "LocalStorage.h"
34 #include "LocalStorageTask.h"
35 #include "LocalStorageThread.h"
36 #include "Page.h"
37 #include "PageGroup.h"
38 #include "PlatformString.h"
39 #include "SecurityOrigin.h"
40 #include "SQLiteStatement.h"
41 
42 namespace WebCore {
43 
44 // If the LocalStorageArea undergoes rapid changes, don't sync each change to disk.
45 // Instead, queue up a batch of items to sync and actually do the sync at the following interval.
46 static const double LocalStorageSyncInterval = 1.0;
47 
LocalStorageArea(SecurityOrigin * origin,LocalStorage * localStorage)48 LocalStorageArea::LocalStorageArea(SecurityOrigin* origin, LocalStorage* localStorage)
49     : StorageArea(origin)
50     , m_syncTimer(this, &LocalStorageArea::syncTimerFired)
51     , m_itemsCleared(false)
52     , m_finalSyncScheduled(false)
53     , m_localStorage(localStorage)
54     , m_clearItemsWhileSyncing(false)
55     , m_syncScheduled(false)
56     , m_importComplete(false)
57 {
58     ASSERT(m_localStorage);
59 
60     if (!m_localStorage->scheduleImport(this))
61         m_importComplete = true;
62 }
63 
~LocalStorageArea()64 LocalStorageArea::~LocalStorageArea()
65 {
66     ASSERT(!m_syncTimer.isActive());
67 }
68 
scheduleFinalSync()69 void LocalStorageArea::scheduleFinalSync()
70 {
71     m_syncTimer.stop();
72     syncTimerFired(&m_syncTimer);
73     m_finalSyncScheduled = true;
74 }
75 
length() const76 unsigned LocalStorageArea::length() const
77 {
78     ASSERT(isMainThread());
79 
80     if (m_importComplete)
81         return internalLength();
82 
83     MutexLocker locker(m_importLock);
84     if (m_importComplete)
85         return internalLength();
86 
87     while (!m_importComplete)
88         m_importCondition.wait(m_importLock);
89     ASSERT(m_importComplete);
90 
91     return internalLength();
92 }
93 
key(unsigned index,ExceptionCode & ec) const94 String LocalStorageArea::key(unsigned index, ExceptionCode& ec) const
95 {
96     ASSERT(isMainThread());
97 
98     if (m_importComplete)
99         return internalKey(index, ec);
100 
101     MutexLocker locker(m_importLock);
102     if (m_importComplete)
103         return internalKey(index, ec);
104 
105     while (!m_importComplete)
106         m_importCondition.wait(m_importLock);
107     ASSERT(m_importComplete);
108 
109     return internalKey(index, ec);
110 }
111 
getItem(const String & key) const112 String LocalStorageArea::getItem(const String& key) const
113 {
114     ASSERT(isMainThread());
115 
116     if (m_importComplete)
117         return internalGetItem(key);
118 
119     MutexLocker locker(m_importLock);
120     if (m_importComplete)
121         return internalGetItem(key);
122 
123     String item = internalGetItem(key);
124     if (!item.isNull())
125         return item;
126 
127     while (!m_importComplete)
128         m_importCondition.wait(m_importLock);
129     ASSERT(m_importComplete);
130 
131     return internalGetItem(key);
132 }
133 
setItem(const String & key,const String & value,ExceptionCode & ec,Frame * frame)134 void LocalStorageArea::setItem(const String& key, const String& value, ExceptionCode& ec, Frame* frame)
135 {
136     ASSERT(isMainThread());
137 
138     if (m_importComplete) {
139         internalSetItem(key, value, ec, frame);
140         return;
141     }
142 
143     MutexLocker locker(m_importLock);
144     internalSetItem(key, value, ec, frame);
145 }
146 
removeItem(const String & key,Frame * frame)147 void LocalStorageArea::removeItem(const String& key, Frame* frame)
148 {
149     ASSERT(isMainThread());
150 
151     if (m_importComplete) {
152         internalRemoveItem(key, frame);
153         return;
154     }
155 
156     MutexLocker locker(m_importLock);
157     internalRemoveItem(key, frame);
158 }
159 
contains(const String & key) const160 bool LocalStorageArea::contains(const String& key) const
161 {
162     ASSERT(isMainThread());
163 
164     if (m_importComplete)
165         return internalContains(key);
166 
167     MutexLocker locker(m_importLock);
168     if (m_importComplete)
169         return internalContains(key);
170 
171     bool contained = internalContains(key);
172     if (contained)
173         return true;
174 
175     while (!m_importComplete)
176         m_importCondition.wait(m_importLock);
177     ASSERT(m_importComplete);
178 
179     return internalContains(key);
180 }
181 
itemChanged(const String & key,const String & oldValue,const String & newValue,Frame * sourceFrame)182 void LocalStorageArea::itemChanged(const String& key, const String& oldValue, const String& newValue, Frame* sourceFrame)
183 {
184     ASSERT(isMainThread());
185 
186     scheduleItemForSync(key, newValue);
187     dispatchStorageEvent(key, oldValue, newValue, sourceFrame);
188 }
189 
itemRemoved(const String & key,const String & oldValue,Frame * sourceFrame)190 void LocalStorageArea::itemRemoved(const String& key, const String& oldValue, Frame* sourceFrame)
191 {
192     ASSERT(isMainThread());
193 
194     scheduleItemForSync(key, String());
195     dispatchStorageEvent(key, oldValue, String(), sourceFrame);
196 }
197 
areaCleared(Frame * sourceFrame)198 void LocalStorageArea::areaCleared(Frame* sourceFrame)
199 {
200     ASSERT(isMainThread());
201 
202     scheduleClear();
203     dispatchStorageEvent(String(), String(), String(), sourceFrame);
204 }
205 
dispatchStorageEvent(const String & key,const String & oldValue,const String & newValue,Frame * sourceFrame)206 void LocalStorageArea::dispatchStorageEvent(const String& key, const String& oldValue, const String& newValue, Frame* sourceFrame)
207 {
208     ASSERT(isMainThread());
209 
210     Page* page = sourceFrame->page();
211     if (!page)
212         return;
213 
214     // Need to copy all relevant frames from every page to a vector, since sending the event to one frame might mutate the frame tree
215     // of any given page in the group, or mutate the page group itself
216     Vector<RefPtr<Frame> > frames;
217     const HashSet<Page*>& pages = page->group().pages();
218 
219     HashSet<Page*>::const_iterator end = pages.end();
220     for (HashSet<Page*>::const_iterator it = pages.begin(); it != end; ++it) {
221         for (Frame* frame = (*it)->mainFrame(); frame; frame = frame->tree()->traverseNext()) {
222             if (Document* document = frame->document())
223                 if (document->securityOrigin()->equal(securityOrigin()))
224                     frames.append(frame);
225         }
226     }
227 
228     for (unsigned i = 0; i < frames.size(); ++i) {
229         if (HTMLElement* body = frames[i]->document()->body())
230             body->dispatchStorageEvent(eventNames().storageEvent, key, oldValue, newValue, sourceFrame);
231     }
232 }
233 
scheduleItemForSync(const String & key,const String & value)234 void LocalStorageArea::scheduleItemForSync(const String& key, const String& value)
235 {
236     ASSERT(isMainThread());
237     ASSERT(!m_finalSyncScheduled);
238 
239     m_changedItems.set(key, value);
240     if (!m_syncTimer.isActive())
241         m_syncTimer.startOneShot(LocalStorageSyncInterval);
242 }
243 
scheduleClear()244 void LocalStorageArea::scheduleClear()
245 {
246     ASSERT(isMainThread());
247     ASSERT(!m_finalSyncScheduled);
248 
249     m_changedItems.clear();
250     m_itemsCleared = true;
251     if (!m_syncTimer.isActive())
252         m_syncTimer.startOneShot(LocalStorageSyncInterval);
253 }
254 
syncTimerFired(Timer<LocalStorageArea> *)255 void LocalStorageArea::syncTimerFired(Timer<LocalStorageArea>*)
256 {
257     ASSERT(isMainThread());
258 
259     HashMap<String, String>::iterator it = m_changedItems.begin();
260     HashMap<String, String>::iterator end = m_changedItems.end();
261 
262     {
263         MutexLocker locker(m_syncLock);
264 
265         if (m_itemsCleared) {
266             m_itemsPendingSync.clear();
267             m_clearItemsWhileSyncing = true;
268             m_itemsCleared = false;
269         }
270 
271         for (; it != end; ++it)
272             m_itemsPendingSync.set(it->first.copy(), it->second.copy());
273 
274         if (!m_syncScheduled) {
275             m_syncScheduled = true;
276             m_localStorage->scheduleSync(this);
277         }
278     }
279 
280     m_changedItems.clear();
281 }
282 
performImport()283 void LocalStorageArea::performImport()
284 {
285     ASSERT(!isMainThread());
286     ASSERT(!m_database.isOpen());
287 
288     String databaseFilename = m_localStorage->fullDatabaseFilename(securityOrigin());
289 
290     if (databaseFilename.isEmpty()) {
291         LOG_ERROR("Filename for local storage database is empty - cannot open for persistent storage");
292         markImported();
293         return;
294     }
295 
296     if (!m_database.open(databaseFilename)) {
297         LOG_ERROR("Failed to open database file %s for local storage", databaseFilename.utf8().data());
298         markImported();
299         return;
300     }
301 
302     if (!m_database.executeCommand("CREATE TABLE IF NOT EXISTS ItemTable (key TEXT UNIQUE ON CONFLICT REPLACE, value TEXT NOT NULL ON CONFLICT FAIL)")) {
303         LOG_ERROR("Failed to create table ItemTable for local storage");
304         markImported();
305         return;
306     }
307 
308     SQLiteStatement query(m_database, "SELECT key, value FROM ItemTable");
309     if (query.prepare() != SQLResultOk) {
310         LOG_ERROR("Unable to select items from ItemTable for local storage");
311         markImported();
312         return;
313     }
314 
315     HashMap<String, String> itemMap;
316 
317     int result = query.step();
318     while (result == SQLResultRow) {
319         itemMap.set(query.getColumnText(0), query.getColumnText(1));
320         result = query.step();
321     }
322 
323     if (result != SQLResultDone) {
324         LOG_ERROR("Error reading items from ItemTable for local storage");
325         markImported();
326         return;
327     }
328 
329     MutexLocker locker(m_importLock);
330 
331     HashMap<String, String>::iterator it = itemMap.begin();
332     HashMap<String, String>::iterator end = itemMap.end();
333 
334     for (; it != end; ++it)
335         importItem(it->first, it->second);
336 
337     m_importComplete = true;
338     m_importCondition.signal();
339 }
340 
markImported()341 void LocalStorageArea::markImported()
342 {
343     ASSERT(!isMainThread());
344 
345     MutexLocker locker(m_importLock);
346     m_importComplete = true;
347     m_importCondition.signal();
348 }
349 
performSync()350 void LocalStorageArea::performSync()
351 {
352     ASSERT(!isMainThread());
353 
354     if (!m_database.isOpen())
355         return;
356 
357     HashMap<String, String> items;
358     bool clearFirst = false;
359     {
360         MutexLocker locker(m_syncLock);
361         m_itemsPendingSync.swap(items);
362         clearFirst = m_clearItemsWhileSyncing;
363         m_clearItemsWhileSyncing = false;
364         m_syncScheduled = false;
365     }
366 
367     // If the clear flag is marked, then we clear all items out before we write any new ones in
368     if (clearFirst) {
369         SQLiteStatement clear(m_database, "DELETE FROM ItemTable");
370         if (clear.prepare() != SQLResultOk) {
371             LOG_ERROR("Failed to prepare clear statement - cannot write to local storage database");
372             return;
373         }
374 
375         int result = clear.step();
376         if (result != SQLResultDone) {
377             LOG_ERROR("Failed to clear all items in the local storage database - %i", result);
378             return;
379         }
380     }
381 
382     SQLiteStatement insert(m_database, "INSERT INTO ItemTable VALUES (?, ?)");
383     if (insert.prepare() != SQLResultOk) {
384         LOG_ERROR("Failed to prepare insert statement - cannot write to local storage database");
385         return;
386     }
387 
388     SQLiteStatement remove(m_database, "DELETE FROM ItemTable WHERE key=?");
389     if (remove.prepare() != SQLResultOk) {
390         LOG_ERROR("Failed to prepare delete statement - cannot write to local storage database");
391         return;
392     }
393 
394     HashMap<String, String>::iterator end = items.end();
395 
396     for (HashMap<String, String>::iterator it = items.begin(); it != end; ++it) {
397         // Based on the null-ness of the second argument, decide whether this is an insert or a delete
398         SQLiteStatement& query = it->second.isNull() ? remove : insert;
399 
400         query.bindText(1, it->first);
401 
402         // If the second argument is non-null, we're doing an insert, so bind it as the value.
403         if (!it->second.isNull())
404             query.bindText(2, it->second);
405 
406         int result = query.step();
407         if (result != SQLResultDone) {
408             LOG_ERROR("Failed to update item in the local storage database - %i", result);
409             break;
410         }
411 
412         query.reset();
413     }
414 }
415 
416 } // namespace WebCore
417