1/*
2 * Copyright (C) 2013 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. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "StorageAreaMap.h"
28
29#include "StorageAreaImpl.h"
30#include "StorageAreaMapMessages.h"
31#include "StorageManagerMessages.h"
32#include "StorageNamespaceImpl.h"
33#include "WebPage.h"
34#include "WebPageGroupProxy.h"
35#include "WebProcess.h"
36#include <WebCore/DOMWindow.h>
37#include <WebCore/Document.h>
38#include <WebCore/Frame.h>
39#include <WebCore/Page.h>
40#include <WebCore/PageGroup.h>
41#include <WebCore/SecurityOriginData.h>
42#include <WebCore/Storage.h>
43#include <WebCore/StorageEventDispatcher.h>
44#include <WebCore/StorageMap.h>
45#include <WebCore/StorageType.h>
46
47namespace WebKit {
48using namespace WebCore;
49
50static uint64_t generateStorageMapID()
51{
52 static uint64_t storageMapID;
53 return ++storageMapID;
54}
55
56Ref<StorageAreaMap> StorageAreaMap::create(StorageNamespaceImpl* storageNamespace, Ref<WebCore::SecurityOrigin>&& securityOrigin)
57{
58 return adoptRef(*new StorageAreaMap(storageNamespace, WTFMove(securityOrigin)));
59}
60
61StorageAreaMap::StorageAreaMap(StorageNamespaceImpl* storageNamespace, Ref<WebCore::SecurityOrigin>&& securityOrigin)
62 : m_storageNamespace(*storageNamespace)
63 , m_storageMapID(generateStorageMapID())
64 , m_storageType(storageNamespace->storageType())
65 , m_storageNamespaceID(storageNamespace->storageNamespaceID())
66 , m_quotaInBytes(storageNamespace->quotaInBytes())
67 , m_securityOrigin(WTFMove(securityOrigin))
68 , m_currentSeed(0)
69 , m_hasPendingClear(false)
70 , m_hasPendingGetValues(false)
71{
72 switch (m_storageType) {
73 case StorageType::Local:
74 case StorageType::TransientLocal:
75 if (SecurityOrigin* topLevelOrigin = storageNamespace->topLevelOrigin())
76 WebProcess::singleton().parentProcessConnection()->send(Messages::StorageManager::CreateTransientLocalStorageMap(m_storageMapID, storageNamespace->storageNamespaceID(), topLevelOrigin->data(), m_securityOrigin->data()), 0);
77 else
78 WebProcess::singleton().parentProcessConnection()->send(Messages::StorageManager::CreateLocalStorageMap(m_storageMapID, storageNamespace->storageNamespaceID(), m_securityOrigin->data()), 0);
79
80 break;
81
82 case StorageType::Session:
83 WebProcess::singleton().parentProcessConnection()->send(Messages::StorageManager::CreateSessionStorageMap(m_storageMapID, storageNamespace->storageNamespaceID(), m_securityOrigin->data()), 0);
84 break;
85
86 case StorageType::EphemeralLocal:
87 ASSERT_NOT_REACHED();
88 return;
89 }
90
91 WebProcess::singleton().addMessageReceiver(Messages::StorageAreaMap::messageReceiverName(), m_storageMapID, *this);
92}
93
94StorageAreaMap::~StorageAreaMap()
95{
96 if (m_storageType != StorageType::EphemeralLocal) {
97 WebProcess::singleton().parentProcessConnection()->send(Messages::StorageManager::DestroyStorageMap(m_storageMapID), 0);
98 WebProcess::singleton().removeMessageReceiver(Messages::StorageAreaMap::messageReceiverName(), m_storageMapID);
99 }
100
101 m_storageNamespace->didDestroyStorageAreaMap(*this);
102}
103
104unsigned StorageAreaMap::length()
105{
106 loadValuesIfNeeded();
107
108 return m_storageMap->length();
109}
110
111String StorageAreaMap::key(unsigned index)
112{
113 loadValuesIfNeeded();
114
115 return m_storageMap->key(index);
116}
117
118String StorageAreaMap::item(const String& key)
119{
120 loadValuesIfNeeded();
121
122 return m_storageMap->getItem(key);
123}
124
125void StorageAreaMap::setItem(Frame* sourceFrame, StorageAreaImpl* sourceArea, const String& key, const String& value, bool& quotaException)
126{
127 loadValuesIfNeeded();
128
129 ASSERT(m_storageMap->hasOneRef());
130
131 String oldValue;
132 quotaException = false;
133 m_storageMap->setItem(key, value, oldValue, quotaException);
134 if (quotaException)
135 return;
136
137 if (oldValue == value)
138 return;
139
140 m_pendingValueChanges.add(key);
141
142 WebProcess::singleton().parentProcessConnection()->send(Messages::StorageManager::SetItem(m_securityOrigin->data(), m_storageMapID, sourceArea->storageAreaID(), m_currentSeed, key, value, sourceFrame->document()->url()), 0);
143}
144
145void StorageAreaMap::removeItem(WebCore::Frame* sourceFrame, StorageAreaImpl* sourceArea, const String& key)
146{
147 loadValuesIfNeeded();
148 ASSERT(m_storageMap->hasOneRef());
149
150 String oldValue;
151 m_storageMap->removeItem(key, oldValue);
152
153 if (oldValue.isNull())
154 return;
155
156 m_pendingValueChanges.add(key);
157
158 WebProcess::singleton().parentProcessConnection()->send(Messages::StorageManager::RemoveItem(m_securityOrigin->data(), m_storageMapID, sourceArea->storageAreaID(), m_currentSeed, key, sourceFrame->document()->url()), 0);
159}
160
161void StorageAreaMap::clear(WebCore::Frame* sourceFrame, StorageAreaImpl* sourceArea)
162{
163 resetValues();
164
165 m_hasPendingClear = true;
166 m_storageMap = StorageMap::create(m_quotaInBytes);
167 WebProcess::singleton().parentProcessConnection()->send(Messages::StorageManager::Clear(m_securityOrigin->data(), m_storageMapID, sourceArea->storageAreaID(), m_currentSeed, sourceFrame->document()->url()), 0);
168}
169
170bool StorageAreaMap::contains(const String& key)
171{
172 loadValuesIfNeeded();
173
174 return m_storageMap->contains(key);
175}
176
177void StorageAreaMap::resetValues()
178{
179 m_storageMap = nullptr;
180
181 m_pendingValueChanges.clear();
182 m_hasPendingClear = false;
183 m_hasPendingGetValues = false;
184 m_currentSeed++;
185}
186
187void StorageAreaMap::loadValuesIfNeeded()
188{
189 if (m_storageMap)
190 return;
191
192 HashMap<String, String> values;
193 // FIXME: This should use a special sendSync flag to indicate that we don't want to process incoming messages while waiting for a reply.
194 // (This flag does not yet exist). Since loadValuesIfNeeded() ends up being called from within JavaScript code, processing incoming synchronous messages
195 // could lead to weird reentrency bugs otherwise.
196 WebProcess::singleton().parentProcessConnection()->sendSync(Messages::StorageManager::GetValues(m_securityOrigin->data(), m_storageMapID, m_currentSeed), Messages::StorageManager::GetValues::Reply(values), 0);
197
198 m_storageMap = StorageMap::create(m_quotaInBytes);
199 m_storageMap->importItems(values);
200
201 // We want to ignore all changes until we get the DidGetValues message.
202 m_hasPendingGetValues = true;
203}
204
205void StorageAreaMap::didGetValues(uint64_t storageMapSeed)
206{
207 if (m_currentSeed != storageMapSeed)
208 return;
209
210 ASSERT(m_hasPendingGetValues);
211 m_hasPendingGetValues = false;
212}
213
214void StorageAreaMap::didSetItem(uint64_t storageMapSeed, const String& key, bool quotaError)
215{
216 if (m_currentSeed != storageMapSeed)
217 return;
218
219 ASSERT(m_pendingValueChanges.contains(key));
220
221 if (quotaError) {
222 resetValues();
223 return;
224 }
225
226 m_pendingValueChanges.remove(key);
227}
228
229void StorageAreaMap::didRemoveItem(uint64_t storageMapSeed, const String& key)
230{
231 if (m_currentSeed != storageMapSeed)
232 return;
233
234 ASSERT(m_pendingValueChanges.contains(key));
235 m_pendingValueChanges.remove(key);
236}
237
238void StorageAreaMap::didClear(uint64_t storageMapSeed)
239{
240 if (m_currentSeed != storageMapSeed)
241 return;
242
243 ASSERT(m_hasPendingClear);
244 m_hasPendingClear = false;
245}
246
247bool StorageAreaMap::shouldApplyChangeForKey(const String& key) const
248{
249 // We have not yet loaded anything from this storage map.
250 if (!m_storageMap)
251 return false;
252
253 // Check if this storage area is currently waiting for the storage manager to update the given key.
254 // If that is the case, we don't want to apply any changes made by other storage areas, since
255 // our change was made last.
256 if (m_pendingValueChanges.contains(key))
257 return false;
258
259 return true;
260}
261
262void StorageAreaMap::applyChange(const String& key, const String& newValue)
263{
264 ASSERT(!m_storageMap || m_storageMap->hasOneRef());
265
266 // There's a clear pending or getValues pending we don't want to apply any changes until we get the corresponding DidClear/DidGetValues messages.
267 if (m_hasPendingClear || m_hasPendingGetValues)
268 return;
269
270 if (!key) {
271 // A null key means clear.
272 auto newStorageMap = StorageMap::create(m_quotaInBytes);
273
274 // Any changes that were made locally after the clear must still be kept around in the new map.
275 for (auto it = m_pendingValueChanges.begin().keys(), end = m_pendingValueChanges.end().keys(); it != end; ++it) {
276 const String& key = *it;
277
278 String value = m_storageMap->getItem(key);
279 if (!value) {
280 // This change must have been a pending remove, ignore it.
281 continue;
282 }
283
284 String oldValue;
285 newStorageMap->setItemIgnoringQuota(key, oldValue);
286 }
287
288 m_storageMap = WTFMove(newStorageMap);
289 return;
290 }
291
292 if (!shouldApplyChangeForKey(key))
293 return;
294
295 if (!newValue) {
296 // A null new value means that the item should be removed.
297 String oldValue;
298 m_storageMap->removeItem(key, oldValue);
299 return;
300 }
301
302 m_storageMap->setItemIgnoringQuota(key, newValue);
303}
304
305void StorageAreaMap::dispatchStorageEvent(uint64_t sourceStorageAreaID, const String& key, const String& oldValue, const String& newValue, const String& urlString)
306{
307 if (!sourceStorageAreaID) {
308 // This storage event originates from another process so we need to apply the change to our storage area map.
309 applyChange(key, newValue);
310 }
311
312 if (storageType() == StorageType::Session)
313 dispatchSessionStorageEvent(sourceStorageAreaID, key, oldValue, newValue, urlString);
314 else
315 dispatchLocalStorageEvent(sourceStorageAreaID, key, oldValue, newValue, urlString);
316}
317
318void StorageAreaMap::clearCache()
319{
320 resetValues();
321}
322
323void StorageAreaMap::dispatchSessionStorageEvent(uint64_t sourceStorageAreaID, const String& key, const String& oldValue, const String& newValue, const String& urlString)
324{
325 ASSERT(storageType() == StorageType::Session);
326
327 // Namespace IDs for session storage namespaces are equivalent to web page IDs
328 // so we can get the right page here.
329 WebPage* webPage = WebProcess::singleton().webPage(m_storageNamespaceID);
330 if (!webPage)
331 return;
332
333 Vector<RefPtr<Frame>> frames;
334
335 Page* page = webPage->corePage();
336 for (Frame* frame = &page->mainFrame(); frame; frame = frame->tree().traverseNext()) {
337 Document* document = frame->document();
338 if (!document->securityOrigin().equal(m_securityOrigin.ptr()))
339 continue;
340
341 Storage* storage = document->domWindow()->optionalSessionStorage();
342 if (!storage)
343 continue;
344
345 StorageAreaImpl& storageArea = static_cast<StorageAreaImpl&>(storage->area());
346 if (storageArea.storageAreaID() == sourceStorageAreaID) {
347 // This is the storage area that caused the event to be dispatched.
348 continue;
349 }
350
351 frames.append(frame);
352 }
353
354 StorageEventDispatcher::dispatchSessionStorageEventsToFrames(*page, frames, key, oldValue, newValue, urlString, m_securityOrigin->data());
355}
356
357void StorageAreaMap::dispatchLocalStorageEvent(uint64_t sourceStorageAreaID, const String& key, const String& oldValue, const String& newValue, const String& urlString)
358{
359 ASSERT(isLocalStorage(storageType()));
360
361 Vector<RefPtr<Frame>> frames;
362
363 PageGroup& pageGroup = *WebProcess::singleton().webPageGroup(m_storageNamespaceID)->corePageGroup();
364 const HashSet<Page*>& pages = pageGroup.pages();
365 for (HashSet<Page*>::const_iterator it = pages.begin(), end = pages.end(); it != end; ++it) {
366 for (Frame* frame = &(*it)->mainFrame(); frame; frame = frame->tree().traverseNext()) {
367 Document* document = frame->document();
368 if (!document->securityOrigin().equal(m_securityOrigin.ptr()))
369 continue;
370
371 Storage* storage = document->domWindow()->optionalLocalStorage();
372 if (!storage)
373 continue;
374
375 StorageAreaImpl& storageArea = static_cast<StorageAreaImpl&>(storage->area());
376 if (storageArea.storageAreaID() == sourceStorageAreaID) {
377 // This is the storage area that caused the event to be dispatched.
378 continue;
379 }
380
381 frames.append(frame);
382 }
383 }
384
385 StorageEventDispatcher::dispatchLocalStorageEventsToFrames(pageGroup, frames, key, oldValue, newValue, urlString, m_securityOrigin->data());
386}
387
388} // namespace WebKit
389