Home | History | Annotate | Download | only in dom_storage
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "content/browser/dom_storage/dom_storage_context_impl.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/bind_helpers.h"
      9 #include "base/file_util.h"
     10 #include "base/files/file_enumerator.h"
     11 #include "base/guid.h"
     12 #include "base/location.h"
     13 #include "base/time/time.h"
     14 #include "content/browser/dom_storage/dom_storage_area.h"
     15 #include "content/browser/dom_storage/dom_storage_database.h"
     16 #include "content/browser/dom_storage/dom_storage_namespace.h"
     17 #include "content/browser/dom_storage/dom_storage_task_runner.h"
     18 #include "content/browser/dom_storage/session_storage_database.h"
     19 #include "content/common/dom_storage/dom_storage_types.h"
     20 #include "content/public/browser/dom_storage_context.h"
     21 #include "content/public/browser/local_storage_usage_info.h"
     22 #include "content/public/browser/session_storage_usage_info.h"
     23 #include "webkit/browser/quota/special_storage_policy.h"
     24 
     25 namespace content {
     26 
     27 static const int kSessionStoraceScavengingSeconds = 60;
     28 
     29 DOMStorageContextImpl::DOMStorageContextImpl(
     30     const base::FilePath& localstorage_directory,
     31     const base::FilePath& sessionstorage_directory,
     32     quota::SpecialStoragePolicy* special_storage_policy,
     33     DOMStorageTaskRunner* task_runner)
     34     : localstorage_directory_(localstorage_directory),
     35       sessionstorage_directory_(sessionstorage_directory),
     36       task_runner_(task_runner),
     37       is_shutdown_(false),
     38       force_keep_session_state_(false),
     39       special_storage_policy_(special_storage_policy),
     40       scavenging_started_(false) {
     41   // AtomicSequenceNum starts at 0 but we want to start session
     42   // namespace ids at one since zero is reserved for the
     43   // kLocalStorageNamespaceId.
     44   session_id_sequence_.GetNext();
     45 }
     46 
     47 DOMStorageContextImpl::~DOMStorageContextImpl() {
     48   if (session_storage_database_.get()) {
     49     // SessionStorageDatabase shouldn't be deleted right away: deleting it will
     50     // potentially involve waiting in leveldb::DBImpl::~DBImpl, and waiting
     51     // shouldn't happen on this thread.
     52     SessionStorageDatabase* to_release = session_storage_database_.get();
     53     to_release->AddRef();
     54     session_storage_database_ = NULL;
     55     task_runner_->PostShutdownBlockingTask(
     56         FROM_HERE,
     57         DOMStorageTaskRunner::COMMIT_SEQUENCE,
     58         base::Bind(&SessionStorageDatabase::Release,
     59                    base::Unretained(to_release)));
     60   }
     61 }
     62 
     63 DOMStorageNamespace* DOMStorageContextImpl::GetStorageNamespace(
     64     int64 namespace_id) {
     65   if (is_shutdown_)
     66     return NULL;
     67   StorageNamespaceMap::iterator found = namespaces_.find(namespace_id);
     68   if (found == namespaces_.end()) {
     69     if (namespace_id == kLocalStorageNamespaceId) {
     70       if (!localstorage_directory_.empty()) {
     71         if (!file_util::CreateDirectory(localstorage_directory_)) {
     72           LOG(ERROR) << "Failed to create 'Local Storage' directory,"
     73                         " falling back to in-memory only.";
     74           localstorage_directory_ = base::FilePath();
     75         }
     76       }
     77       DOMStorageNamespace* local =
     78           new DOMStorageNamespace(localstorage_directory_, task_runner_.get());
     79       namespaces_[kLocalStorageNamespaceId] = local;
     80       return local;
     81     }
     82     return NULL;
     83   }
     84   return found->second.get();
     85 }
     86 
     87 void DOMStorageContextImpl::GetLocalStorageUsage(
     88     std::vector<LocalStorageUsageInfo>* infos,
     89     bool include_file_info) {
     90   if (localstorage_directory_.empty())
     91     return;
     92   base::FileEnumerator enumerator(localstorage_directory_, false,
     93                                   base::FileEnumerator::FILES);
     94   for (base::FilePath path = enumerator.Next(); !path.empty();
     95        path = enumerator.Next()) {
     96     if (path.MatchesExtension(DOMStorageArea::kDatabaseFileExtension)) {
     97       LocalStorageUsageInfo info;
     98       info.origin = DOMStorageArea::OriginFromDatabaseFileName(path);
     99       if (include_file_info) {
    100         base::FileEnumerator::FileInfo find_info = enumerator.GetInfo();
    101         info.data_size = find_info.GetSize();
    102         info.last_modified = find_info.GetLastModifiedTime();
    103       }
    104       infos->push_back(info);
    105     }
    106   }
    107 }
    108 
    109 void DOMStorageContextImpl::GetSessionStorageUsage(
    110     std::vector<SessionStorageUsageInfo>* infos) {
    111   if (!session_storage_database_.get())
    112     return;
    113   std::map<std::string, std::vector<GURL> > namespaces_and_origins;
    114   session_storage_database_->ReadNamespacesAndOrigins(
    115       &namespaces_and_origins);
    116   for (std::map<std::string, std::vector<GURL> >::const_iterator it =
    117            namespaces_and_origins.begin();
    118        it != namespaces_and_origins.end(); ++it) {
    119     for (std::vector<GURL>::const_iterator origin_it = it->second.begin();
    120          origin_it != it->second.end(); ++origin_it) {
    121       SessionStorageUsageInfo info;
    122       info.persistent_namespace_id = it->first;
    123       info.origin = *origin_it;
    124       infos->push_back(info);
    125     }
    126   }
    127 }
    128 
    129 void DOMStorageContextImpl::DeleteLocalStorage(const GURL& origin) {
    130   DCHECK(!is_shutdown_);
    131   DOMStorageNamespace* local = GetStorageNamespace(kLocalStorageNamespaceId);
    132   local->DeleteLocalStorageOrigin(origin);
    133   // Synthesize a 'cleared' event if the area is open so CachedAreas in
    134   // renderers get emptied out too.
    135   DOMStorageArea* area = local->GetOpenStorageArea(origin);
    136   if (area)
    137     NotifyAreaCleared(area, origin);
    138 }
    139 
    140 void DOMStorageContextImpl::DeleteSessionStorage(
    141     const SessionStorageUsageInfo& usage_info) {
    142   DCHECK(!is_shutdown_);
    143   DOMStorageNamespace* dom_storage_namespace = NULL;
    144   std::map<std::string, int64>::const_iterator it =
    145       persistent_namespace_id_to_namespace_id_.find(
    146           usage_info.persistent_namespace_id);
    147   if (it != persistent_namespace_id_to_namespace_id_.end()) {
    148     dom_storage_namespace = GetStorageNamespace(it->second);
    149   } else {
    150     int64 namespace_id = AllocateSessionId();
    151     CreateSessionNamespace(namespace_id, usage_info.persistent_namespace_id);
    152     dom_storage_namespace = GetStorageNamespace(namespace_id);
    153   }
    154   dom_storage_namespace->DeleteSessionStorageOrigin(usage_info.origin);
    155   // Synthesize a 'cleared' event if the area is open so CachedAreas in
    156   // renderers get emptied out too.
    157   DOMStorageArea* area =
    158       dom_storage_namespace->GetOpenStorageArea(usage_info.origin);
    159   if (area)
    160     NotifyAreaCleared(area, usage_info.origin);
    161 }
    162 
    163 void DOMStorageContextImpl::PurgeMemory() {
    164   // We can only purge memory from the local storage namespace
    165   // which is backed by disk.
    166   // TODO(marja): Purge sessionStorage, too. (Requires changes to the FastClear
    167   // functionality.)
    168   StorageNamespaceMap::iterator found =
    169       namespaces_.find(kLocalStorageNamespaceId);
    170   if (found != namespaces_.end())
    171     found->second->PurgeMemory(DOMStorageNamespace::PURGE_AGGRESSIVE);
    172 }
    173 
    174 void DOMStorageContextImpl::Shutdown() {
    175   is_shutdown_ = true;
    176   StorageNamespaceMap::const_iterator it = namespaces_.begin();
    177   for (; it != namespaces_.end(); ++it)
    178     it->second->Shutdown();
    179 
    180   if (localstorage_directory_.empty() && !session_storage_database_.get())
    181     return;
    182 
    183   // Respect the content policy settings about what to
    184   // keep and what to discard.
    185   if (force_keep_session_state_)
    186     return;  // Keep everything.
    187 
    188   bool has_session_only_origins =
    189       special_storage_policy_.get() &&
    190       special_storage_policy_->HasSessionOnlyOrigins();
    191 
    192   if (has_session_only_origins) {
    193     // We may have to delete something. We continue on the
    194     // commit sequence after area shutdown tasks have cycled
    195     // thru that sequence (and closed their database files).
    196     bool success = task_runner_->PostShutdownBlockingTask(
    197         FROM_HERE,
    198         DOMStorageTaskRunner::COMMIT_SEQUENCE,
    199         base::Bind(&DOMStorageContextImpl::ClearSessionOnlyOrigins, this));
    200     DCHECK(success);
    201   }
    202 }
    203 
    204 void DOMStorageContextImpl::AddEventObserver(EventObserver* observer) {
    205   event_observers_.AddObserver(observer);
    206 }
    207 
    208 void DOMStorageContextImpl::RemoveEventObserver(EventObserver* observer) {
    209   event_observers_.RemoveObserver(observer);
    210 }
    211 
    212 void DOMStorageContextImpl::NotifyItemSet(
    213     const DOMStorageArea* area,
    214     const base::string16& key,
    215     const base::string16& new_value,
    216     const base::NullableString16& old_value,
    217     const GURL& page_url) {
    218   FOR_EACH_OBSERVER(
    219       EventObserver, event_observers_,
    220       OnDOMStorageItemSet(area, key, new_value, old_value, page_url));
    221 }
    222 
    223 void DOMStorageContextImpl::NotifyItemRemoved(
    224     const DOMStorageArea* area,
    225     const base::string16& key,
    226     const base::string16& old_value,
    227     const GURL& page_url) {
    228   FOR_EACH_OBSERVER(
    229       EventObserver, event_observers_,
    230       OnDOMStorageItemRemoved(area, key, old_value, page_url));
    231 }
    232 
    233 void DOMStorageContextImpl::NotifyAreaCleared(
    234     const DOMStorageArea* area,
    235     const GURL& page_url) {
    236   FOR_EACH_OBSERVER(
    237       EventObserver, event_observers_,
    238       OnDOMStorageAreaCleared(area, page_url));
    239 }
    240 
    241 std::string DOMStorageContextImpl::AllocatePersistentSessionId() {
    242   std::string guid = base::GenerateGUID();
    243   std::replace(guid.begin(), guid.end(), '-', '_');
    244   return guid;
    245 }
    246 
    247 void DOMStorageContextImpl::CreateSessionNamespace(
    248     int64 namespace_id,
    249     const std::string& persistent_namespace_id) {
    250   if (is_shutdown_)
    251     return;
    252   DCHECK(namespace_id != kLocalStorageNamespaceId);
    253   DCHECK(namespaces_.find(namespace_id) == namespaces_.end());
    254   namespaces_[namespace_id] = new DOMStorageNamespace(
    255       namespace_id, persistent_namespace_id, session_storage_database_.get(),
    256       task_runner_.get());
    257   persistent_namespace_id_to_namespace_id_[persistent_namespace_id] =
    258       namespace_id;
    259 }
    260 
    261 void DOMStorageContextImpl::DeleteSessionNamespace(
    262     int64 namespace_id, bool should_persist_data) {
    263   DCHECK_NE(kLocalStorageNamespaceId, namespace_id);
    264   StorageNamespaceMap::const_iterator it = namespaces_.find(namespace_id);
    265   if (it == namespaces_.end())
    266     return;
    267   std::string persistent_namespace_id = it->second->persistent_namespace_id();
    268   if (session_storage_database_.get()) {
    269     if (!should_persist_data) {
    270       task_runner_->PostShutdownBlockingTask(
    271           FROM_HERE,
    272           DOMStorageTaskRunner::COMMIT_SEQUENCE,
    273           base::Bind(
    274               base::IgnoreResult(&SessionStorageDatabase::DeleteNamespace),
    275               session_storage_database_,
    276               persistent_namespace_id));
    277     } else {
    278       // Ensure that the data gets committed before we shut down.
    279       it->second->Shutdown();
    280       if (!scavenging_started_) {
    281         // Protect the persistent namespace ID from scavenging.
    282         protected_persistent_session_ids_.insert(persistent_namespace_id);
    283       }
    284     }
    285   }
    286   persistent_namespace_id_to_namespace_id_.erase(persistent_namespace_id);
    287   namespaces_.erase(namespace_id);
    288 }
    289 
    290 void DOMStorageContextImpl::CloneSessionNamespace(
    291     int64 existing_id, int64 new_id,
    292     const std::string& new_persistent_id) {
    293   if (is_shutdown_)
    294     return;
    295   DCHECK_NE(kLocalStorageNamespaceId, existing_id);
    296   DCHECK_NE(kLocalStorageNamespaceId, new_id);
    297   StorageNamespaceMap::iterator found = namespaces_.find(existing_id);
    298   if (found != namespaces_.end())
    299     namespaces_[new_id] = found->second->Clone(new_id, new_persistent_id);
    300   else
    301     CreateSessionNamespace(new_id, new_persistent_id);
    302 }
    303 
    304 void DOMStorageContextImpl::ClearSessionOnlyOrigins() {
    305   if (!localstorage_directory_.empty()) {
    306     std::vector<LocalStorageUsageInfo> infos;
    307     const bool kDontIncludeFileInfo = false;
    308     GetLocalStorageUsage(&infos, kDontIncludeFileInfo);
    309     for (size_t i = 0; i < infos.size(); ++i) {
    310       const GURL& origin = infos[i].origin;
    311       if (special_storage_policy_->IsStorageProtected(origin))
    312         continue;
    313       if (!special_storage_policy_->IsStorageSessionOnly(origin))
    314         continue;
    315 
    316       base::FilePath database_file_path = localstorage_directory_.Append(
    317           DOMStorageArea::DatabaseFileNameFromOrigin(origin));
    318       sql::Connection::Delete(database_file_path);
    319     }
    320   }
    321   if (session_storage_database_.get()) {
    322     std::vector<SessionStorageUsageInfo> infos;
    323     GetSessionStorageUsage(&infos);
    324     for (size_t i = 0; i < infos.size(); ++i) {
    325       const GURL& origin = infos[i].origin;
    326       if (special_storage_policy_->IsStorageProtected(origin))
    327         continue;
    328       if (!special_storage_policy_->IsStorageSessionOnly(origin))
    329         continue;
    330       session_storage_database_->DeleteArea(infos[i].persistent_namespace_id,
    331                                             origin);
    332     }
    333   }
    334 }
    335 
    336 void DOMStorageContextImpl::SetSaveSessionStorageOnDisk() {
    337   DCHECK(namespaces_.empty());
    338   if (!sessionstorage_directory_.empty()) {
    339     session_storage_database_ = new SessionStorageDatabase(
    340         sessionstorage_directory_);
    341   }
    342 }
    343 
    344 void DOMStorageContextImpl::StartScavengingUnusedSessionStorage() {
    345   if (session_storage_database_.get()) {
    346     task_runner_->PostDelayedTask(
    347         FROM_HERE, base::Bind(&DOMStorageContextImpl::FindUnusedNamespaces,
    348                               this),
    349         base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds));
    350   }
    351 }
    352 
    353 void DOMStorageContextImpl::FindUnusedNamespaces() {
    354   DCHECK(session_storage_database_.get());
    355   if (scavenging_started_)
    356     return;
    357   scavenging_started_ = true;
    358   std::set<std::string> namespace_ids_in_use;
    359   for (StorageNamespaceMap::const_iterator it = namespaces_.begin();
    360        it != namespaces_.end(); ++it)
    361     namespace_ids_in_use.insert(it->second->persistent_namespace_id());
    362   std::set<std::string> protected_persistent_session_ids;
    363   protected_persistent_session_ids.swap(protected_persistent_session_ids_);
    364   task_runner_->PostShutdownBlockingTask(
    365       FROM_HERE, DOMStorageTaskRunner::COMMIT_SEQUENCE,
    366       base::Bind(
    367           &DOMStorageContextImpl::FindUnusedNamespacesInCommitSequence,
    368           this, namespace_ids_in_use, protected_persistent_session_ids));
    369 }
    370 
    371 void DOMStorageContextImpl::FindUnusedNamespacesInCommitSequence(
    372     const std::set<std::string>& namespace_ids_in_use,
    373     const std::set<std::string>& protected_persistent_session_ids) {
    374   DCHECK(session_storage_database_.get());
    375   // Delete all namespaces which don't have an associated DOMStorageNamespace
    376   // alive.
    377   std::map<std::string, std::vector<GURL> > namespaces_and_origins;
    378   session_storage_database_->ReadNamespacesAndOrigins(&namespaces_and_origins);
    379   for (std::map<std::string, std::vector<GURL> >::const_iterator it =
    380            namespaces_and_origins.begin();
    381        it != namespaces_and_origins.end(); ++it) {
    382     if (namespace_ids_in_use.find(it->first) == namespace_ids_in_use.end() &&
    383         protected_persistent_session_ids.find(it->first) ==
    384         protected_persistent_session_ids.end()) {
    385       deletable_persistent_namespace_ids_.push_back(it->first);
    386     }
    387   }
    388   if (!deletable_persistent_namespace_ids_.empty()) {
    389     task_runner_->PostDelayedTask(
    390         FROM_HERE, base::Bind(
    391             &DOMStorageContextImpl::DeleteNextUnusedNamespace,
    392             this),
    393         base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds));
    394   }
    395 }
    396 
    397 void DOMStorageContextImpl::DeleteNextUnusedNamespace() {
    398   if (is_shutdown_)
    399     return;
    400   task_runner_->PostShutdownBlockingTask(
    401         FROM_HERE, DOMStorageTaskRunner::COMMIT_SEQUENCE,
    402         base::Bind(
    403             &DOMStorageContextImpl::DeleteNextUnusedNamespaceInCommitSequence,
    404             this));
    405 }
    406 
    407 void DOMStorageContextImpl::DeleteNextUnusedNamespaceInCommitSequence() {
    408   if (deletable_persistent_namespace_ids_.empty())
    409     return;
    410   const std::string& persistent_id = deletable_persistent_namespace_ids_.back();
    411   session_storage_database_->DeleteNamespace(persistent_id);
    412   deletable_persistent_namespace_ids_.pop_back();
    413   if (!deletable_persistent_namespace_ids_.empty()) {
    414     task_runner_->PostDelayedTask(
    415         FROM_HERE, base::Bind(
    416             &DOMStorageContextImpl::DeleteNextUnusedNamespace,
    417             this),
    418         base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds));
    419   }
    420 }
    421 
    422 }  // namespace content
    423