Home | History | Annotate | Download | only in storage
      1 /*
      2  * Copyright (C) 2008 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. ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 #include "StorageMap.h"
     28 
     29 #if ENABLE(DOM_STORAGE)
     30 
     31 namespace WebCore {
     32 
     33 PassRefPtr<StorageMap> StorageMap::create(unsigned quota)
     34 {
     35     return adoptRef(new StorageMap(quota));
     36 }
     37 
     38 StorageMap::StorageMap(unsigned quota)
     39     : m_iterator(m_map.end())
     40     , m_iteratorIndex(UINT_MAX)
     41     , m_quotaSize(quota)  // quota measured in bytes
     42     , m_currentLength(0)
     43 {
     44 }
     45 
     46 PassRefPtr<StorageMap> StorageMap::copy()
     47 {
     48     RefPtr<StorageMap> newMap = create(m_quotaSize);
     49     newMap->m_map = m_map;
     50     newMap->m_currentLength = m_currentLength;
     51     return newMap.release();
     52 }
     53 
     54 void StorageMap::invalidateIterator()
     55 {
     56     m_iterator = m_map.end();
     57     m_iteratorIndex = UINT_MAX;
     58 }
     59 
     60 void StorageMap::setIteratorToIndex(unsigned index)
     61 {
     62     // FIXME: Once we have bidirectional iterators for HashMap we can be more intelligent about this.
     63     // The requested index will be closest to begin(), our current iterator, or end(), and we
     64     // can take the shortest route.
     65     // Until that mechanism is available, we'll always increment our iterator from begin() or current.
     66 
     67     if (m_iteratorIndex == index)
     68         return;
     69 
     70     if (index < m_iteratorIndex) {
     71         m_iteratorIndex = 0;
     72         m_iterator = m_map.begin();
     73         ASSERT(m_iterator != m_map.end());
     74     }
     75 
     76     while (m_iteratorIndex < index) {
     77         ++m_iteratorIndex;
     78         ++m_iterator;
     79         ASSERT(m_iterator != m_map.end());
     80     }
     81 }
     82 
     83 unsigned StorageMap::length() const
     84 {
     85     return m_map.size();
     86 }
     87 
     88 String StorageMap::key(unsigned index)
     89 {
     90     if (index >= length())
     91         return String();
     92 
     93     setIteratorToIndex(index);
     94     return m_iterator->first;
     95 }
     96 
     97 String StorageMap::getItem(const String& key) const
     98 {
     99     return m_map.get(key);
    100 }
    101 
    102 PassRefPtr<StorageMap> StorageMap::setItem(const String& key, const String& value, String& oldValue, bool& quotaException)
    103 {
    104     ASSERT(!value.isNull());
    105     quotaException = false;
    106 
    107     // Implement copy-on-write semantics here.  We're guaranteed that the only refs of StorageMaps belong to Storage objects
    108     // so if more than one Storage object refs this map, copy it before mutating it.
    109     if (refCount() > 1) {
    110         RefPtr<StorageMap> newStorageMap = copy();
    111         newStorageMap->setItem(key, value, oldValue, quotaException);
    112         return newStorageMap.release();
    113     }
    114 
    115     // Quota tracking.  This is done in a couple of steps to keep the overflow tracking simple.
    116     unsigned newLength = m_currentLength;
    117     bool overflow = newLength + value.length() < newLength;
    118     newLength += value.length();
    119 
    120     oldValue = m_map.get(key);
    121     overflow |= newLength - oldValue.length() > newLength;
    122     newLength -= oldValue.length();
    123 
    124     unsigned adjustedKeyLength = oldValue.isNull() ? key.length() : 0;
    125     overflow |= newLength + adjustedKeyLength < newLength;
    126     newLength += adjustedKeyLength;
    127 
    128     ASSERT(!overflow);  // Overflow is bad...even if quotas are off.
    129     bool overQuota = newLength > m_quotaSize / sizeof(UChar);
    130     if (m_quotaSize != noQuota && (overflow || overQuota)) {
    131         quotaException = true;
    132         return 0;
    133     }
    134     m_currentLength = newLength;
    135 
    136     pair<HashMap<String, String>::iterator, bool> addResult = m_map.add(key, value);
    137     if (!addResult.second)
    138         addResult.first->second = value;
    139 
    140     invalidateIterator();
    141 
    142     return 0;
    143 }
    144 
    145 PassRefPtr<StorageMap> StorageMap::removeItem(const String& key, String& oldValue)
    146 {
    147     // Implement copy-on-write semantics here.  We're guaranteed that the only refs of StorageMaps belong to Storage objects
    148     // so if more than one Storage object refs this map, copy it before mutating it.
    149     if (refCount() > 1) {
    150         RefPtr<StorageMap> newStorage = copy();
    151         newStorage->removeItem(key, oldValue);
    152         return newStorage.release();
    153     }
    154 
    155     oldValue = m_map.take(key);
    156     if (!oldValue.isNull()) {
    157         invalidateIterator();
    158         ASSERT(m_currentLength - key.length() <= m_currentLength);
    159         m_currentLength -= key.length();
    160     }
    161     ASSERT(m_currentLength - oldValue.length() <= m_currentLength);
    162     m_currentLength -= oldValue.length();
    163 
    164     return 0;
    165 }
    166 
    167 bool StorageMap::contains(const String& key) const
    168 {
    169     return m_map.contains(key);
    170 }
    171 
    172 void StorageMap::importItem(const String& key, const String& value)
    173 {
    174     // Be sure to copy the keys/values as items imported on a background thread are destined
    175     // to cross a thread boundary
    176     pair<HashMap<String, String>::iterator, bool> result = m_map.add(key.threadsafeCopy(), value.threadsafeCopy());
    177     ASSERT(result.second);  // True if the key didn't exist previously.
    178 
    179     ASSERT(m_currentLength + key.length() >= m_currentLength);
    180     m_currentLength += key.length();
    181     ASSERT(m_currentLength + value.length() >= m_currentLength);
    182     m_currentLength += value.length();
    183 }
    184 
    185 }
    186 
    187 #endif // ENABLE(DOM_STORAGE)
    188