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 (!base::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::Shutdown() {
    164   is_shutdown_ = true;
    165   StorageNamespaceMap::const_iterator it = namespaces_.begin();
    166   for (; it != namespaces_.end(); ++it)
    167     it->second->Shutdown();
    168 
    169   if (localstorage_directory_.empty() && !session_storage_database_.get())
    170     return;
    171 
    172   // Respect the content policy settings about what to
    173   // keep and what to discard.
    174   if (force_keep_session_state_)
    175     return;  // Keep everything.
    176 
    177   bool has_session_only_origins =
    178       special_storage_policy_.get() &&
    179       special_storage_policy_->HasSessionOnlyOrigins();
    180 
    181   if (has_session_only_origins) {
    182     // We may have to delete something. We continue on the
    183     // commit sequence after area shutdown tasks have cycled
    184     // thru that sequence (and closed their database files).
    185     bool success = task_runner_->PostShutdownBlockingTask(
    186         FROM_HERE,
    187         DOMStorageTaskRunner::COMMIT_SEQUENCE,
    188         base::Bind(&DOMStorageContextImpl::ClearSessionOnlyOrigins, this));
    189     DCHECK(success);
    190   }
    191 }
    192 
    193 void DOMStorageContextImpl::AddEventObserver(EventObserver* observer) {
    194   event_observers_.AddObserver(observer);
    195 }
    196 
    197 void DOMStorageContextImpl::RemoveEventObserver(EventObserver* observer) {
    198   event_observers_.RemoveObserver(observer);
    199 }
    200 
    201 void DOMStorageContextImpl::NotifyItemSet(
    202     const DOMStorageArea* area,
    203     const base::string16& key,
    204     const base::string16& new_value,
    205     const base::NullableString16& old_value,
    206     const GURL& page_url) {
    207   FOR_EACH_OBSERVER(
    208       EventObserver, event_observers_,
    209       OnDOMStorageItemSet(area, key, new_value, old_value, page_url));
    210 }
    211 
    212 void DOMStorageContextImpl::NotifyItemRemoved(
    213     const DOMStorageArea* area,
    214     const base::string16& key,
    215     const base::string16& old_value,
    216     const GURL& page_url) {
    217   FOR_EACH_OBSERVER(
    218       EventObserver, event_observers_,
    219       OnDOMStorageItemRemoved(area, key, old_value, page_url));
    220 }
    221 
    222 void DOMStorageContextImpl::NotifyAreaCleared(
    223     const DOMStorageArea* area,
    224     const GURL& page_url) {
    225   FOR_EACH_OBSERVER(
    226       EventObserver, event_observers_,
    227       OnDOMStorageAreaCleared(area, page_url));
    228 }
    229 
    230 void DOMStorageContextImpl::NotifyAliasSessionMerged(
    231     int64 namespace_id,
    232     DOMStorageNamespace* old_alias_master_namespace) {
    233   FOR_EACH_OBSERVER(
    234       EventObserver, event_observers_,
    235       OnDOMSessionStorageReset(namespace_id));
    236   if (old_alias_master_namespace)
    237     MaybeShutdownSessionNamespace(old_alias_master_namespace);
    238 }
    239 
    240 std::string DOMStorageContextImpl::AllocatePersistentSessionId() {
    241   std::string guid = base::GenerateGUID();
    242   std::replace(guid.begin(), guid.end(), '-', '_');
    243   return guid;
    244 }
    245 
    246 void DOMStorageContextImpl::CreateSessionNamespace(
    247     int64 namespace_id,
    248     const std::string& persistent_namespace_id) {
    249   if (is_shutdown_)
    250     return;
    251   DCHECK(namespace_id != kLocalStorageNamespaceId);
    252   DCHECK(namespaces_.find(namespace_id) == namespaces_.end());
    253   namespaces_[namespace_id] = new DOMStorageNamespace(
    254       namespace_id, persistent_namespace_id, session_storage_database_.get(),
    255       task_runner_.get());
    256   persistent_namespace_id_to_namespace_id_[persistent_namespace_id] =
    257       namespace_id;
    258 }
    259 
    260 void DOMStorageContextImpl::DeleteSessionNamespace(
    261     int64 namespace_id, bool should_persist_data) {
    262   DCHECK_NE(kLocalStorageNamespaceId, namespace_id);
    263   StorageNamespaceMap::const_iterator it = namespaces_.find(namespace_id);
    264   if (it == namespaces_.end() ||
    265       it->second->ready_for_deletion_pending_aliases()) {
    266     return;
    267   }
    268   it->second->set_ready_for_deletion_pending_aliases(true);
    269   DOMStorageNamespace* alias_master = it->second->alias_master_namespace();
    270   if (alias_master) {
    271     DCHECK(it->second->num_aliases() == 0);
    272     DCHECK(alias_master->alias_master_namespace() == NULL);
    273     if (should_persist_data)
    274       alias_master->set_must_persist_at_shutdown(true);
    275     if (it->second->DecrementMasterAliasCount())
    276       MaybeShutdownSessionNamespace(alias_master);
    277     namespaces_.erase(namespace_id);
    278   } else {
    279     if (should_persist_data)
    280       it->second->set_must_persist_at_shutdown(true);
    281     MaybeShutdownSessionNamespace(it->second);
    282   }
    283 }
    284 
    285 void DOMStorageContextImpl::MaybeShutdownSessionNamespace(
    286     DOMStorageNamespace* ns) {
    287   if (ns->num_aliases() > 0 || !ns->ready_for_deletion_pending_aliases())
    288     return;
    289   DCHECK_EQ(ns->num_aliases(), 0);
    290   DCHECK(ns->alias_master_namespace() == NULL);
    291   std::string persistent_namespace_id =  ns->persistent_namespace_id();
    292   if (session_storage_database_.get()) {
    293     if (!ns->must_persist_at_shutdown()) {
    294       task_runner_->PostShutdownBlockingTask(
    295           FROM_HERE,
    296           DOMStorageTaskRunner::COMMIT_SEQUENCE,
    297           base::Bind(
    298               base::IgnoreResult(&SessionStorageDatabase::DeleteNamespace),
    299               session_storage_database_,
    300               persistent_namespace_id));
    301     } else {
    302       // Ensure that the data gets committed before we shut down.
    303       ns->Shutdown();
    304       if (!scavenging_started_) {
    305         // Protect the persistent namespace ID from scavenging.
    306         protected_persistent_session_ids_.insert(persistent_namespace_id);
    307       }
    308     }
    309   }
    310   persistent_namespace_id_to_namespace_id_.erase(persistent_namespace_id);
    311   namespaces_.erase(ns->namespace_id());
    312 }
    313 
    314 void DOMStorageContextImpl::CloneSessionNamespace(
    315     int64 existing_id, int64 new_id,
    316     const std::string& new_persistent_id) {
    317   if (is_shutdown_)
    318     return;
    319   DCHECK_NE(kLocalStorageNamespaceId, existing_id);
    320   DCHECK_NE(kLocalStorageNamespaceId, new_id);
    321   StorageNamespaceMap::iterator found = namespaces_.find(existing_id);
    322   if (found != namespaces_.end())
    323     namespaces_[new_id] = found->second->Clone(new_id, new_persistent_id);
    324   else
    325     CreateSessionNamespace(new_id, new_persistent_id);
    326 }
    327 
    328 void DOMStorageContextImpl::CreateAliasSessionNamespace(
    329     int64 existing_id, int64 new_id,
    330     const std::string& persistent_id) {
    331   if (is_shutdown_)
    332     return;
    333   DCHECK_NE(kLocalStorageNamespaceId, existing_id);
    334   DCHECK_NE(kLocalStorageNamespaceId, new_id);
    335   StorageNamespaceMap::iterator found = namespaces_.find(existing_id);
    336   if (found != namespaces_.end()) {
    337     namespaces_[new_id] = found->second->CreateAlias(new_id);
    338   } else {
    339     CreateSessionNamespace(new_id, persistent_id);
    340   }
    341 }
    342 
    343 void DOMStorageContextImpl::ClearSessionOnlyOrigins() {
    344   if (!localstorage_directory_.empty()) {
    345     std::vector<LocalStorageUsageInfo> infos;
    346     const bool kDontIncludeFileInfo = false;
    347     GetLocalStorageUsage(&infos, kDontIncludeFileInfo);
    348     for (size_t i = 0; i < infos.size(); ++i) {
    349       const GURL& origin = infos[i].origin;
    350       if (special_storage_policy_->IsStorageProtected(origin))
    351         continue;
    352       if (!special_storage_policy_->IsStorageSessionOnly(origin))
    353         continue;
    354 
    355       base::FilePath database_file_path = localstorage_directory_.Append(
    356           DOMStorageArea::DatabaseFileNameFromOrigin(origin));
    357       sql::Connection::Delete(database_file_path);
    358     }
    359   }
    360   if (session_storage_database_.get()) {
    361     std::vector<SessionStorageUsageInfo> infos;
    362     GetSessionStorageUsage(&infos);
    363     for (size_t i = 0; i < infos.size(); ++i) {
    364       const GURL& origin = infos[i].origin;
    365       if (special_storage_policy_->IsStorageProtected(origin))
    366         continue;
    367       if (!special_storage_policy_->IsStorageSessionOnly(origin))
    368         continue;
    369       session_storage_database_->DeleteArea(infos[i].persistent_namespace_id,
    370                                             origin);
    371     }
    372   }
    373 }
    374 
    375 void DOMStorageContextImpl::SetSaveSessionStorageOnDisk() {
    376   DCHECK(namespaces_.empty());
    377   if (!sessionstorage_directory_.empty()) {
    378     session_storage_database_ = new SessionStorageDatabase(
    379         sessionstorage_directory_);
    380   }
    381 }
    382 
    383 void DOMStorageContextImpl::StartScavengingUnusedSessionStorage() {
    384   if (session_storage_database_.get()) {
    385     task_runner_->PostDelayedTask(
    386         FROM_HERE, base::Bind(&DOMStorageContextImpl::FindUnusedNamespaces,
    387                               this),
    388         base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds));
    389   }
    390 }
    391 
    392 void DOMStorageContextImpl::FindUnusedNamespaces() {
    393   DCHECK(session_storage_database_.get());
    394   if (scavenging_started_)
    395     return;
    396   scavenging_started_ = true;
    397   std::set<std::string> namespace_ids_in_use;
    398   for (StorageNamespaceMap::const_iterator it = namespaces_.begin();
    399        it != namespaces_.end(); ++it)
    400     namespace_ids_in_use.insert(it->second->persistent_namespace_id());
    401   std::set<std::string> protected_persistent_session_ids;
    402   protected_persistent_session_ids.swap(protected_persistent_session_ids_);
    403   task_runner_->PostShutdownBlockingTask(
    404       FROM_HERE, DOMStorageTaskRunner::COMMIT_SEQUENCE,
    405       base::Bind(
    406           &DOMStorageContextImpl::FindUnusedNamespacesInCommitSequence,
    407           this, namespace_ids_in_use, protected_persistent_session_ids));
    408 }
    409 
    410 void DOMStorageContextImpl::FindUnusedNamespacesInCommitSequence(
    411     const std::set<std::string>& namespace_ids_in_use,
    412     const std::set<std::string>& protected_persistent_session_ids) {
    413   DCHECK(session_storage_database_.get());
    414   // Delete all namespaces which don't have an associated DOMStorageNamespace
    415   // alive.
    416   std::map<std::string, std::vector<GURL> > namespaces_and_origins;
    417   session_storage_database_->ReadNamespacesAndOrigins(&namespaces_and_origins);
    418   for (std::map<std::string, std::vector<GURL> >::const_iterator it =
    419            namespaces_and_origins.begin();
    420        it != namespaces_and_origins.end(); ++it) {
    421     if (namespace_ids_in_use.find(it->first) == namespace_ids_in_use.end() &&
    422         protected_persistent_session_ids.find(it->first) ==
    423         protected_persistent_session_ids.end()) {
    424       deletable_persistent_namespace_ids_.push_back(it->first);
    425     }
    426   }
    427   if (!deletable_persistent_namespace_ids_.empty()) {
    428     task_runner_->PostDelayedTask(
    429         FROM_HERE, base::Bind(
    430             &DOMStorageContextImpl::DeleteNextUnusedNamespace,
    431             this),
    432         base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds));
    433   }
    434 }
    435 
    436 void DOMStorageContextImpl::DeleteNextUnusedNamespace() {
    437   if (is_shutdown_)
    438     return;
    439   task_runner_->PostShutdownBlockingTask(
    440         FROM_HERE, DOMStorageTaskRunner::COMMIT_SEQUENCE,
    441         base::Bind(
    442             &DOMStorageContextImpl::DeleteNextUnusedNamespaceInCommitSequence,
    443             this));
    444 }
    445 
    446 void DOMStorageContextImpl::DeleteNextUnusedNamespaceInCommitSequence() {
    447   if (deletable_persistent_namespace_ids_.empty())
    448     return;
    449   const std::string& persistent_id = deletable_persistent_namespace_ids_.back();
    450   session_storage_database_->DeleteNamespace(persistent_id);
    451   deletable_persistent_namespace_ids_.pop_back();
    452   if (!deletable_persistent_namespace_ids_.empty()) {
    453     task_runner_->PostDelayedTask(
    454         FROM_HERE, base::Bind(
    455             &DOMStorageContextImpl::DeleteNextUnusedNamespace,
    456             this),
    457         base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds));
    458   }
    459 }
    460 
    461 void DOMStorageContextImpl::AddTransactionLogProcessId(int64 namespace_id,
    462                                                        int process_id) {
    463   DCHECK_NE(kLocalStorageNamespaceId, namespace_id);
    464   StorageNamespaceMap::const_iterator it = namespaces_.find(namespace_id);
    465   if (it == namespaces_.end())
    466     return;
    467   it->second->AddTransactionLogProcessId(process_id);
    468 }
    469 
    470 void DOMStorageContextImpl::RemoveTransactionLogProcessId(int64 namespace_id,
    471                                                        int process_id) {
    472   DCHECK_NE(kLocalStorageNamespaceId, namespace_id);
    473   StorageNamespaceMap::const_iterator it = namespaces_.find(namespace_id);
    474   if (it == namespaces_.end())
    475     return;
    476   it->second->RemoveTransactionLogProcessId(process_id);
    477 }
    478 
    479 SessionStorageNamespace::MergeResult
    480 DOMStorageContextImpl::MergeSessionStorage(
    481     int64 namespace1_id, bool actually_merge, int process_id,
    482     int64 namespace2_id) {
    483   DCHECK_NE(kLocalStorageNamespaceId, namespace1_id);
    484   DCHECK_NE(kLocalStorageNamespaceId, namespace2_id);
    485   StorageNamespaceMap::const_iterator it = namespaces_.find(namespace1_id);
    486   if (it == namespaces_.end())
    487     return SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_FOUND;
    488   DOMStorageNamespace* ns1 = it->second;
    489   it = namespaces_.find(namespace2_id);
    490   if (it == namespaces_.end())
    491     return SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_FOUND;
    492   DOMStorageNamespace* ns2 = it->second;
    493   return ns1->Merge(actually_merge, process_id, ns2, this);
    494 }
    495 
    496 }  // namespace content
    497