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