Home | History | Annotate | Download | only in dom_storage
      1 // Copyright 2013 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_namespace.h"
      6 
      7 #include <set>
      8 #include <utility>
      9 
     10 #include "base/basictypes.h"
     11 #include "base/bind.h"
     12 #include "base/location.h"
     13 #include "base/logging.h"
     14 #include "base/stl_util.h"
     15 #include "content/browser/dom_storage/dom_storage_area.h"
     16 #include "content/browser/dom_storage/dom_storage_context_impl.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/common/child_process_host.h"
     21 
     22 namespace content {
     23 
     24 namespace {
     25 
     26 static const unsigned int kMaxTransactionLogEntries = 8 * 1024;
     27 
     28 }  // namespace
     29 
     30 DOMStorageNamespace::DOMStorageNamespace(
     31     const base::FilePath& directory,
     32     DOMStorageTaskRunner* task_runner)
     33     : namespace_id_(kLocalStorageNamespaceId),
     34       directory_(directory),
     35       task_runner_(task_runner),
     36       num_aliases_(0),
     37       old_master_for_close_area_(NULL),
     38       master_alias_count_decremented_(false),
     39       ready_for_deletion_pending_aliases_(false),
     40       must_persist_at_shutdown_(false) {
     41 }
     42 
     43 DOMStorageNamespace::DOMStorageNamespace(
     44     int64 namespace_id,
     45     const std::string& persistent_namespace_id,
     46     SessionStorageDatabase* session_storage_database,
     47     DOMStorageTaskRunner* task_runner)
     48     : namespace_id_(namespace_id),
     49       persistent_namespace_id_(persistent_namespace_id),
     50       task_runner_(task_runner),
     51       session_storage_database_(session_storage_database),
     52       num_aliases_(0),
     53       old_master_for_close_area_(NULL),
     54       master_alias_count_decremented_(false),
     55       ready_for_deletion_pending_aliases_(false),
     56       must_persist_at_shutdown_(false) {
     57   DCHECK_NE(kLocalStorageNamespaceId, namespace_id);
     58 }
     59 
     60 DOMStorageNamespace::~DOMStorageNamespace() {
     61   STLDeleteValues(&transactions_);
     62   DecrementMasterAliasCount();
     63 }
     64 
     65 DOMStorageArea* DOMStorageNamespace::OpenStorageArea(const GURL& origin) {
     66   if (alias_master_namespace_.get())
     67     return alias_master_namespace_->OpenStorageArea(origin);
     68   if (AreaHolder* holder = GetAreaHolder(origin)) {
     69     ++(holder->open_count_);
     70     return holder->area_.get();
     71   }
     72   DOMStorageArea* area;
     73   if (namespace_id_ == kLocalStorageNamespaceId) {
     74     area = new DOMStorageArea(origin, directory_, task_runner_.get());
     75   } else {
     76     area = new DOMStorageArea(
     77         namespace_id_, persistent_namespace_id_, origin,
     78         session_storage_database_.get(), task_runner_.get());
     79   }
     80   areas_[origin] = AreaHolder(area, 1);
     81   return area;
     82 }
     83 
     84 void DOMStorageNamespace::CloseStorageArea(DOMStorageArea* area) {
     85   AreaHolder* holder = GetAreaHolder(area->origin());
     86   if (alias_master_namespace_.get()) {
     87     DCHECK(!holder);
     88     if (old_master_for_close_area_)
     89       old_master_for_close_area_->CloseStorageArea(area);
     90     else
     91       alias_master_namespace_->CloseStorageArea(area);
     92     return;
     93   }
     94   DCHECK(holder);
     95   DCHECK_EQ(holder->area_.get(), area);
     96   --(holder->open_count_);
     97   // TODO(michaeln): Clean up areas that aren't needed in memory anymore.
     98   // The in-process-webkit based impl didn't do this either, but would be nice.
     99 }
    100 
    101 DOMStorageArea* DOMStorageNamespace::GetOpenStorageArea(const GURL& origin) {
    102   if (alias_master_namespace_.get())
    103     return alias_master_namespace_->GetOpenStorageArea(origin);
    104   AreaHolder* holder = GetAreaHolder(origin);
    105   if (holder && holder->open_count_)
    106     return holder->area_.get();
    107   return NULL;
    108 }
    109 
    110 DOMStorageNamespace* DOMStorageNamespace::Clone(
    111     int64 clone_namespace_id,
    112     const std::string& clone_persistent_namespace_id) {
    113   if (alias_master_namespace_.get()) {
    114     return alias_master_namespace_->Clone(clone_namespace_id,
    115                                           clone_persistent_namespace_id);
    116   }
    117   DCHECK_NE(kLocalStorageNamespaceId, namespace_id_);
    118   DCHECK_NE(kLocalStorageNamespaceId, clone_namespace_id);
    119   DOMStorageNamespace* clone = new DOMStorageNamespace(
    120       clone_namespace_id, clone_persistent_namespace_id,
    121       session_storage_database_.get(), task_runner_.get());
    122   AreaMap::const_iterator it = areas_.begin();
    123   // Clone the in-memory structures.
    124   for (; it != areas_.end(); ++it) {
    125     DOMStorageArea* area = it->second.area_->ShallowCopy(
    126         clone_namespace_id, clone_persistent_namespace_id);
    127     clone->areas_[it->first] = AreaHolder(area, 0);
    128   }
    129   // And clone the on-disk structures, too.
    130   if (session_storage_database_.get()) {
    131     task_runner_->PostShutdownBlockingTask(
    132         FROM_HERE,
    133         DOMStorageTaskRunner::COMMIT_SEQUENCE,
    134         base::Bind(base::IgnoreResult(&SessionStorageDatabase::CloneNamespace),
    135                    session_storage_database_.get(), persistent_namespace_id_,
    136                    clone_persistent_namespace_id));
    137   }
    138   return clone;
    139 }
    140 
    141 DOMStorageNamespace* DOMStorageNamespace::CreateAlias(
    142     int64 alias_namespace_id) {
    143   // Creates an alias of the current DOMStorageNamespace.
    144   // The alias will have a reference to this namespace (called the master),
    145   // and all operations will be redirected to the master (in particular,
    146   // the alias will never open any areas of its own, but always redirect
    147   // to the master). Accordingly, an alias will also never undergo the shutdown
    148   // procedure which initiates persisting to disk, since there is never any data
    149   // of its own to persist to disk. DOMStorageContextImpl is the place where
    150   // shutdowns are initiated, but only for non-alias DOMStorageNamespaces.
    151   DCHECK_NE(kLocalStorageNamespaceId, namespace_id_);
    152   DCHECK_NE(kLocalStorageNamespaceId, alias_namespace_id);
    153   DOMStorageNamespace* alias = new DOMStorageNamespace(
    154       alias_namespace_id, persistent_namespace_id_,
    155       session_storage_database_.get(), task_runner_.get());
    156   if (alias_master_namespace_.get() != NULL) {
    157     DCHECK(alias_master_namespace_->alias_master_namespace_.get() == NULL);
    158     alias->alias_master_namespace_ = alias_master_namespace_;
    159   } else {
    160     alias->alias_master_namespace_ = this;
    161   }
    162   alias->alias_master_namespace_->num_aliases_++;
    163   return alias;
    164 }
    165 
    166 void DOMStorageNamespace::DeleteLocalStorageOrigin(const GURL& origin) {
    167   DCHECK(!session_storage_database_.get());
    168   DCHECK(!alias_master_namespace_.get());
    169   AreaHolder* holder = GetAreaHolder(origin);
    170   if (holder) {
    171     holder->area_->DeleteOrigin();
    172     return;
    173   }
    174   if (!directory_.empty()) {
    175     scoped_refptr<DOMStorageArea> area =
    176         new DOMStorageArea(origin, directory_, task_runner_.get());
    177     area->DeleteOrigin();
    178   }
    179 }
    180 
    181 void DOMStorageNamespace::DeleteSessionStorageOrigin(const GURL& origin) {
    182   if (alias_master_namespace_.get()) {
    183     alias_master_namespace_->DeleteSessionStorageOrigin(origin);
    184     return;
    185   }
    186   DOMStorageArea* area = OpenStorageArea(origin);
    187   area->FastClear();
    188   CloseStorageArea(area);
    189 }
    190 
    191 void DOMStorageNamespace::PurgeMemory(PurgeOption option) {
    192   if (alias_master_namespace_.get()) {
    193     alias_master_namespace_->PurgeMemory(option);
    194     return;
    195   }
    196   if (directory_.empty())
    197     return;  // We can't purge w/o backing on disk.
    198   AreaMap::iterator it = areas_.begin();
    199   while (it != areas_.end()) {
    200     // Leave it alone if changes are pending
    201     if (it->second.area_->HasUncommittedChanges()) {
    202       ++it;
    203       continue;
    204     }
    205 
    206     // If not in use, we can shut it down and remove
    207     // it from our collection entirely.
    208     if (it->second.open_count_ == 0) {
    209       it->second.area_->Shutdown();
    210       areas_.erase(it++);
    211       continue;
    212     }
    213 
    214     if (option == PURGE_AGGRESSIVE) {
    215       // If aggressive is true, we clear caches and such
    216       // for opened areas.
    217       it->second.area_->PurgeMemory();
    218     }
    219 
    220     ++it;
    221   }
    222 }
    223 
    224 void DOMStorageNamespace::Shutdown() {
    225   AreaMap::const_iterator it = areas_.begin();
    226   for (; it != areas_.end(); ++it)
    227     it->second.area_->Shutdown();
    228 }
    229 
    230 unsigned int DOMStorageNamespace::CountInMemoryAreas() const {
    231   if (alias_master_namespace_.get())
    232     return alias_master_namespace_->CountInMemoryAreas();
    233   unsigned int area_count = 0;
    234   for (AreaMap::const_iterator it = areas_.begin(); it != areas_.end(); ++it) {
    235     if (it->second.area_->IsLoadedInMemory())
    236       ++area_count;
    237   }
    238   return area_count;
    239 }
    240 
    241 DOMStorageNamespace::AreaHolder*
    242 DOMStorageNamespace::GetAreaHolder(const GURL& origin) {
    243   AreaMap::iterator found = areas_.find(origin);
    244   if (found == areas_.end())
    245     return NULL;
    246   return &(found->second);
    247 }
    248 
    249 void DOMStorageNamespace::AddTransactionLogProcessId(int process_id) {
    250   DCHECK(process_id != ChildProcessHost::kInvalidUniqueID);
    251   DCHECK(transactions_.count(process_id) == 0);
    252   TransactionData* transaction_data = new TransactionData;
    253   transactions_[process_id] = transaction_data;
    254 }
    255 
    256 void DOMStorageNamespace::RemoveTransactionLogProcessId(int process_id) {
    257   DCHECK(process_id != ChildProcessHost::kInvalidUniqueID);
    258   DCHECK(transactions_.count(process_id) == 1);
    259   delete transactions_[process_id];
    260   transactions_.erase(process_id);
    261 }
    262 
    263 SessionStorageNamespace::MergeResult DOMStorageNamespace::Merge(
    264     bool actually_merge,
    265     int process_id,
    266     DOMStorageNamespace* other,
    267     DOMStorageContextImpl* context) {
    268   if (!alias_master_namespace())
    269     return SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_ALIAS;
    270   if (transactions_.count(process_id) < 1)
    271     return SessionStorageNamespace::MERGE_RESULT_NOT_LOGGING;
    272   TransactionData* data = transactions_[process_id];
    273   if (data->max_log_size_exceeded)
    274     return SessionStorageNamespace::MERGE_RESULT_TOO_MANY_TRANSACTIONS;
    275   if (data->log.size() < 1) {
    276     if (actually_merge)
    277       SwitchToNewAliasMaster(other, context);
    278     return SessionStorageNamespace::MERGE_RESULT_NO_TRANSACTIONS;
    279   }
    280 
    281   // skip_areas and skip_keys store areas and (area, key) pairs, respectively,
    282   // that have already been handled previously. Any further modifications to
    283   // them will not change the result of the hypothetical merge.
    284   std::set<GURL> skip_areas;
    285   typedef std::pair<GURL, base::string16> OriginKey;
    286   std::set<OriginKey> skip_keys;
    287   // Indicates whether we could still merge the namespaces preserving all
    288   // individual transactions.
    289   for (unsigned int i = 0; i < data->log.size(); i++) {
    290     TransactionRecord& transaction = data->log[i];
    291     if (transaction.transaction_type == TRANSACTION_CLEAR) {
    292       skip_areas.insert(transaction.origin);
    293       continue;
    294     }
    295     if (skip_areas.find(transaction.origin) != skip_areas.end())
    296       continue;
    297     if (skip_keys.find(OriginKey(transaction.origin, transaction.key))
    298         != skip_keys.end()) {
    299       continue;
    300     }
    301     if (transaction.transaction_type == TRANSACTION_REMOVE ||
    302         transaction.transaction_type == TRANSACTION_WRITE) {
    303       skip_keys.insert(OriginKey(transaction.origin, transaction.key));
    304       continue;
    305     }
    306     if (transaction.transaction_type == TRANSACTION_READ) {
    307       DOMStorageArea* area = other->OpenStorageArea(transaction.origin);
    308       base::NullableString16 other_value = area->GetItem(transaction.key);
    309       other->CloseStorageArea(area);
    310       if (transaction.value != other_value)
    311         return SessionStorageNamespace::MERGE_RESULT_NOT_MERGEABLE;
    312       continue;
    313     }
    314     NOTREACHED();
    315   }
    316   if (!actually_merge)
    317     return SessionStorageNamespace::MERGE_RESULT_MERGEABLE;
    318 
    319   // Actually perform the merge.
    320 
    321   for (unsigned int i = 0; i < data->log.size(); i++) {
    322     TransactionRecord& transaction = data->log[i];
    323     if (transaction.transaction_type == TRANSACTION_READ)
    324       continue;
    325     DOMStorageArea* area = other->OpenStorageArea(transaction.origin);
    326     if (transaction.transaction_type == TRANSACTION_CLEAR) {
    327       area->Clear();
    328       if (context)
    329         context->NotifyAreaCleared(area, transaction.page_url);
    330     }
    331     if (transaction.transaction_type == TRANSACTION_REMOVE) {
    332       base::string16 old_value;
    333       area->RemoveItem(transaction.key, &old_value);
    334       if (context) {
    335         context->NotifyItemRemoved(area, transaction.key, old_value,
    336                                    transaction.page_url);
    337       }
    338     }
    339     if (transaction.transaction_type == TRANSACTION_WRITE) {
    340       base::NullableString16 old_value;
    341       area->SetItem(transaction.key, base::string16(transaction.value.string()),
    342                     &old_value);
    343       if (context) {
    344         context->NotifyItemSet(area, transaction.key,transaction.value.string(),
    345                                old_value, transaction.page_url);
    346       }
    347     }
    348     other->CloseStorageArea(area);
    349   }
    350 
    351   SwitchToNewAliasMaster(other, context);
    352   return SessionStorageNamespace::MERGE_RESULT_MERGEABLE;
    353 }
    354 
    355 bool DOMStorageNamespace::IsLoggingRenderer(int process_id) {
    356   DCHECK(process_id != ChildProcessHost::kInvalidUniqueID);
    357   if (transactions_.count(process_id) < 1)
    358     return false;
    359   return !transactions_[process_id]->max_log_size_exceeded;
    360 }
    361 
    362 void DOMStorageNamespace::AddTransaction(
    363     int process_id, const TransactionRecord& transaction) {
    364   if (!IsLoggingRenderer(process_id))
    365     return;
    366   TransactionData* transaction_data = transactions_[process_id];
    367   DCHECK(transaction_data);
    368   if (transaction_data->max_log_size_exceeded)
    369     return;
    370   transaction_data->log.push_back(transaction);
    371   if (transaction_data->log.size() > kMaxTransactionLogEntries) {
    372     transaction_data->max_log_size_exceeded = true;
    373     transaction_data->log.clear();
    374   }
    375 }
    376 
    377 bool DOMStorageNamespace::DecrementMasterAliasCount() {
    378   if (!alias_master_namespace_.get() || master_alias_count_decremented_)
    379     return false;
    380   DCHECK_GT(alias_master_namespace_->num_aliases_, 0);
    381   alias_master_namespace_->num_aliases_--;
    382   master_alias_count_decremented_ = true;
    383   return (alias_master_namespace_->num_aliases_ == 0);
    384 }
    385 
    386 void DOMStorageNamespace::SwitchToNewAliasMaster(
    387     DOMStorageNamespace* new_master,
    388     DOMStorageContextImpl* context) {
    389   DCHECK(alias_master_namespace());
    390   scoped_refptr<DOMStorageNamespace> old_master = alias_master_namespace();
    391   if (new_master->alias_master_namespace())
    392     new_master = new_master->alias_master_namespace();
    393   DCHECK(!new_master->alias_master_namespace());
    394   DCHECK(old_master.get() != this);
    395   DCHECK(old_master.get() != new_master);
    396   DecrementMasterAliasCount();
    397   alias_master_namespace_ = new_master;
    398   alias_master_namespace_->num_aliases_++;
    399   master_alias_count_decremented_ = false;
    400   // There are three things that we need to clean up:
    401   // -- the old master may ready for shutdown, if its last alias has disappeared
    402   // -- The dom_storage hosts need to close and reopen their areas, so that
    403   // they point to the correct new areas.
    404   // -- The renderers will need to reset their local caches.
    405   // All three of these things are accomplished with the following call below.
    406   // |context| will be NULL in unit tests, which is when this will
    407   // not apply, of course.
    408   // During this call, open areas will be closed & reopened, so that they now
    409   // come from the correct new master. Therefore, we must send close operations
    410   // to the old master.
    411   old_master_for_close_area_ = old_master.get();
    412   if (context)
    413     context->NotifyAliasSessionMerged(namespace_id(), old_master.get());
    414   old_master_for_close_area_ = NULL;
    415 }
    416 
    417 DOMStorageNamespace::TransactionData::TransactionData()
    418     : max_log_size_exceeded(false) {
    419 }
    420 
    421 DOMStorageNamespace::TransactionData::~TransactionData() {
    422 }
    423 
    424 DOMStorageNamespace::TransactionRecord::TransactionRecord() {
    425 }
    426 
    427 DOMStorageNamespace::TransactionRecord::~TransactionRecord() {
    428 }
    429 
    430 // AreaHolder
    431 
    432 DOMStorageNamespace::AreaHolder::AreaHolder()
    433     : open_count_(0) {
    434 }
    435 
    436 DOMStorageNamespace::AreaHolder::AreaHolder(
    437     DOMStorageArea* area, int count)
    438     : area_(area), open_count_(count) {
    439 }
    440 
    441 DOMStorageNamespace::AreaHolder::~AreaHolder() {
    442 }
    443 
    444 }  // namespace content
    445