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