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 22namespace content { 23 24namespace { 25 26static const unsigned int kMaxTransactionLogEntries = 8 * 1024; 27 28} // namespace 29 30DOMStorageNamespace::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 43DOMStorageNamespace::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 60DOMStorageNamespace::~DOMStorageNamespace() { 61 STLDeleteValues(&transactions_); 62 DecrementMasterAliasCount(); 63} 64 65DOMStorageArea* 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 84void 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 101DOMStorageArea* 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 110DOMStorageNamespace* 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 141DOMStorageNamespace* 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 166void 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 181void 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 191void 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 224void DOMStorageNamespace::Shutdown() { 225 AreaMap::const_iterator it = areas_.begin(); 226 for (; it != areas_.end(); ++it) 227 it->second.area_->Shutdown(); 228} 229 230unsigned 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 241DOMStorageNamespace::AreaHolder* 242DOMStorageNamespace::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 249void 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 256void 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 263SessionStorageNamespace::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 355bool 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 362void 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 377bool 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 386void 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 417DOMStorageNamespace::TransactionData::TransactionData() 418 : max_log_size_exceeded(false) { 419} 420 421DOMStorageNamespace::TransactionData::~TransactionData() { 422} 423 424DOMStorageNamespace::TransactionRecord::TransactionRecord() { 425} 426 427DOMStorageNamespace::TransactionRecord::~TransactionRecord() { 428} 429 430// AreaHolder 431 432DOMStorageNamespace::AreaHolder::AreaHolder() 433 : open_count_(0) { 434} 435 436DOMStorageNamespace::AreaHolder::AreaHolder( 437 DOMStorageArea* area, int count) 438 : area_(area), open_count_(count) { 439} 440 441DOMStorageNamespace::AreaHolder::~AreaHolder() { 442} 443 444} // namespace content 445