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