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