Supervised user import: Listen for profile creation/deletion
[chromium-blink-merge.git] / content / browser / dom_storage / dom_storage_area.cc
blob56a1fd008a65538bc45e707c59f530d66ab24837
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.
5 #include "content/browser/dom_storage/dom_storage_area.h"
7 #include <algorithm>
9 #include "base/bind.h"
10 #include "base/location.h"
11 #include "base/logging.h"
12 #include "base/metrics/histogram.h"
13 #include "base/process/process_info.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/time/time.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/local_storage_database_adapter.h"
19 #include "content/browser/dom_storage/session_storage_database.h"
20 #include "content/browser/dom_storage/session_storage_database_adapter.h"
21 #include "content/common/dom_storage/dom_storage_map.h"
22 #include "content/common/dom_storage/dom_storage_types.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "storage/browser/database/database_util.h"
25 #include "storage/common/database/database_identifier.h"
26 #include "storage/common/fileapi/file_system_util.h"
28 using storage::DatabaseUtil;
30 namespace content {
32 namespace {
34 // Delay for a moment after a value is set in anticipation
35 // of other values being set, so changes are batched.
36 const int kCommitDefaultDelaySecs = 5;
38 // To avoid excessive IO we apply limits to the amount of data being written
39 // and the frequency of writes. The specific values used are somewhat arbitrary.
40 const int kMaxBytesPerDay = kPerStorageAreaQuota * 2;
41 const int kMaxCommitsPerHour = 6;
43 } // namespace
45 DOMStorageArea::RateLimiter::RateLimiter(size_t desired_rate,
46 base::TimeDelta time_quantum)
47 : rate_(desired_rate), samples_(0), time_quantum_(time_quantum) {
48 DCHECK_GT(desired_rate, 0ul);
51 base::TimeDelta DOMStorageArea::RateLimiter::ComputeTimeNeeded() const {
52 return time_quantum_ * (samples_ / rate_);
55 base::TimeDelta DOMStorageArea::RateLimiter::ComputeDelayNeeded(
56 const base::TimeDelta elapsed_time) const {
57 base::TimeDelta time_needed = ComputeTimeNeeded();
58 if (time_needed > elapsed_time)
59 return time_needed - elapsed_time;
60 return base::TimeDelta();
63 DOMStorageArea::CommitBatch::CommitBatch() : clear_all_first(false) {
65 DOMStorageArea::CommitBatch::~CommitBatch() {}
67 size_t DOMStorageArea::CommitBatch::GetDataSize() const {
68 return DOMStorageMap::CountBytes(changed_values);
71 // static
72 const base::FilePath::CharType DOMStorageArea::kDatabaseFileExtension[] =
73 FILE_PATH_LITERAL(".localstorage");
75 // static
76 base::FilePath DOMStorageArea::DatabaseFileNameFromOrigin(const GURL& origin) {
77 std::string filename = storage::GetIdentifierFromOrigin(origin);
78 // There is no base::FilePath.AppendExtension() method, so start with just the
79 // extension as the filename, and then InsertBeforeExtension the desired
80 // name.
81 return base::FilePath().Append(kDatabaseFileExtension).
82 InsertBeforeExtensionASCII(filename);
85 // static
86 GURL DOMStorageArea::OriginFromDatabaseFileName(const base::FilePath& name) {
87 DCHECK(name.MatchesExtension(kDatabaseFileExtension));
88 std::string origin_id =
89 name.BaseName().RemoveExtension().MaybeAsASCII();
90 return storage::GetOriginFromIdentifier(origin_id);
93 DOMStorageArea::DOMStorageArea(const GURL& origin,
94 const base::FilePath& directory,
95 DOMStorageTaskRunner* task_runner)
96 : namespace_id_(kLocalStorageNamespaceId),
97 origin_(origin),
98 directory_(directory),
99 task_runner_(task_runner),
100 map_(new DOMStorageMap(kPerStorageAreaQuota +
101 kPerStorageAreaOverQuotaAllowance)),
102 is_initial_import_done_(true),
103 is_shutdown_(false),
104 commit_batches_in_flight_(0),
105 start_time_(base::TimeTicks::Now()),
106 data_rate_limiter_(kMaxBytesPerDay, base::TimeDelta::FromHours(24)),
107 commit_rate_limiter_(kMaxCommitsPerHour, base::TimeDelta::FromHours(1)) {
108 if (!directory.empty()) {
109 base::FilePath path = directory.Append(DatabaseFileNameFromOrigin(origin_));
110 backing_.reset(new LocalStorageDatabaseAdapter(path));
111 is_initial_import_done_ = false;
115 DOMStorageArea::DOMStorageArea(int64 namespace_id,
116 const std::string& persistent_namespace_id,
117 const GURL& origin,
118 SessionStorageDatabase* session_storage_backing,
119 DOMStorageTaskRunner* task_runner)
120 : namespace_id_(namespace_id),
121 persistent_namespace_id_(persistent_namespace_id),
122 origin_(origin),
123 task_runner_(task_runner),
124 map_(new DOMStorageMap(kPerStorageAreaQuota +
125 kPerStorageAreaOverQuotaAllowance)),
126 session_storage_backing_(session_storage_backing),
127 is_initial_import_done_(true),
128 is_shutdown_(false),
129 commit_batches_in_flight_(0),
130 start_time_(base::TimeTicks::Now()),
131 data_rate_limiter_(kMaxBytesPerDay, base::TimeDelta::FromHours(24)),
132 commit_rate_limiter_(kMaxCommitsPerHour, base::TimeDelta::FromHours(1)) {
133 DCHECK(namespace_id != kLocalStorageNamespaceId);
134 if (session_storage_backing) {
135 backing_.reset(new SessionStorageDatabaseAdapter(
136 session_storage_backing, persistent_namespace_id, origin));
137 is_initial_import_done_ = false;
141 DOMStorageArea::~DOMStorageArea() {
144 void DOMStorageArea::ExtractValues(DOMStorageValuesMap* map) {
145 if (is_shutdown_)
146 return;
147 InitialImportIfNeeded();
148 map_->ExtractValues(map);
151 unsigned DOMStorageArea::Length() {
152 if (is_shutdown_)
153 return 0;
154 InitialImportIfNeeded();
155 return map_->Length();
158 base::NullableString16 DOMStorageArea::Key(unsigned index) {
159 if (is_shutdown_)
160 return base::NullableString16();
161 InitialImportIfNeeded();
162 return map_->Key(index);
165 base::NullableString16 DOMStorageArea::GetItem(const base::string16& key) {
166 if (is_shutdown_)
167 return base::NullableString16();
168 InitialImportIfNeeded();
169 return map_->GetItem(key);
172 bool DOMStorageArea::SetItem(const base::string16& key,
173 const base::string16& value,
174 base::NullableString16* old_value) {
175 if (is_shutdown_)
176 return false;
177 InitialImportIfNeeded();
178 if (!map_->HasOneRef())
179 map_ = map_->DeepCopy();
180 bool success = map_->SetItem(key, value, old_value);
181 if (success && backing_ &&
182 (old_value->is_null() || old_value->string() != value)) {
183 CommitBatch* commit_batch = CreateCommitBatchIfNeeded();
184 commit_batch->changed_values[key] = base::NullableString16(value, false);
186 return success;
189 bool DOMStorageArea::RemoveItem(const base::string16& key,
190 base::string16* old_value) {
191 if (is_shutdown_)
192 return false;
193 InitialImportIfNeeded();
194 if (!map_->HasOneRef())
195 map_ = map_->DeepCopy();
196 bool success = map_->RemoveItem(key, old_value);
197 if (success && backing_) {
198 CommitBatch* commit_batch = CreateCommitBatchIfNeeded();
199 commit_batch->changed_values[key] = base::NullableString16();
201 return success;
204 bool DOMStorageArea::Clear() {
205 if (is_shutdown_)
206 return false;
207 InitialImportIfNeeded();
208 if (map_->Length() == 0)
209 return false;
211 map_ = new DOMStorageMap(kPerStorageAreaQuota +
212 kPerStorageAreaOverQuotaAllowance);
214 if (backing_) {
215 CommitBatch* commit_batch = CreateCommitBatchIfNeeded();
216 commit_batch->clear_all_first = true;
217 commit_batch->changed_values.clear();
220 return true;
223 void DOMStorageArea::FastClear() {
224 // TODO(marja): Unify clearing localStorage and sessionStorage. The problem is
225 // to make the following 3 to work together: 1) FastClear, 2) PurgeMemory and
226 // 3) not creating events when clearing an empty area.
227 if (is_shutdown_)
228 return;
230 map_ = new DOMStorageMap(kPerStorageAreaQuota +
231 kPerStorageAreaOverQuotaAllowance);
232 // This ensures no import will happen while we're waiting to clear the data
233 // from the database. This mechanism fails if PurgeMemory is called.
234 is_initial_import_done_ = true;
236 if (backing_) {
237 CommitBatch* commit_batch = CreateCommitBatchIfNeeded();
238 commit_batch->clear_all_first = true;
239 commit_batch->changed_values.clear();
243 DOMStorageArea* DOMStorageArea::ShallowCopy(
244 int64 destination_namespace_id,
245 const std::string& destination_persistent_namespace_id) {
246 DCHECK_NE(kLocalStorageNamespaceId, namespace_id_);
247 DCHECK_NE(kLocalStorageNamespaceId, destination_namespace_id);
249 DOMStorageArea* copy = new DOMStorageArea(
250 destination_namespace_id, destination_persistent_namespace_id, origin_,
251 session_storage_backing_.get(), task_runner_.get());
252 copy->map_ = map_;
253 copy->is_shutdown_ = is_shutdown_;
254 copy->is_initial_import_done_ = true;
256 // All the uncommitted changes to this area need to happen before the actual
257 // shallow copy is made (scheduled by the upper layer sometime after return).
258 if (commit_batch_)
259 ScheduleImmediateCommit();
260 return copy;
263 bool DOMStorageArea::HasUncommittedChanges() const {
264 return commit_batch_.get() || commit_batches_in_flight_;
267 void DOMStorageArea::ScheduleImmediateCommit() {
268 DCHECK(HasUncommittedChanges());
269 PostCommitTask();
272 void DOMStorageArea::DeleteOrigin() {
273 DCHECK(!is_shutdown_);
274 // This function shouldn't be called for sessionStorage.
275 DCHECK(!session_storage_backing_.get());
276 if (HasUncommittedChanges()) {
277 // TODO(michaeln): This logically deletes the data immediately,
278 // and in a matter of a second, deletes the rows from the backing
279 // database file, but the file itself will linger until shutdown
280 // or purge time. Ideally, this should delete the file more
281 // quickly.
282 Clear();
283 return;
285 map_ = new DOMStorageMap(kPerStorageAreaQuota +
286 kPerStorageAreaOverQuotaAllowance);
287 if (backing_) {
288 is_initial_import_done_ = false;
289 backing_->Reset();
290 backing_->DeleteFiles();
294 void DOMStorageArea::PurgeMemory() {
295 DCHECK(!is_shutdown_);
296 // Purging sessionStorage is not supported; it won't work with FastClear.
297 DCHECK(!session_storage_backing_.get());
298 if (!is_initial_import_done_ || // We're not using any memory.
299 !backing_.get() || // We can't purge anything.
300 HasUncommittedChanges()) // We leave things alone with changes pending.
301 return;
303 // Drop the in memory cache, we'll reload when needed.
304 is_initial_import_done_ = false;
305 map_ = new DOMStorageMap(kPerStorageAreaQuota +
306 kPerStorageAreaOverQuotaAllowance);
308 // Recreate the database object, this frees up the open sqlite connection
309 // and its page cache.
310 backing_->Reset();
313 void DOMStorageArea::Shutdown() {
314 DCHECK(!is_shutdown_);
315 is_shutdown_ = true;
316 map_ = NULL;
317 if (!backing_)
318 return;
320 bool success = task_runner_->PostShutdownBlockingTask(
321 FROM_HERE,
322 DOMStorageTaskRunner::COMMIT_SEQUENCE,
323 base::Bind(&DOMStorageArea::ShutdownInCommitSequence, this));
324 DCHECK(success);
327 void DOMStorageArea::InitialImportIfNeeded() {
328 if (is_initial_import_done_)
329 return;
331 DCHECK(backing_.get());
333 base::TimeTicks before = base::TimeTicks::Now();
334 DOMStorageValuesMap initial_values;
335 backing_->ReadAllValues(&initial_values);
336 map_->SwapValues(&initial_values);
337 is_initial_import_done_ = true;
338 base::TimeDelta time_to_import = base::TimeTicks::Now() - before;
339 UMA_HISTOGRAM_TIMES("LocalStorage.BrowserTimeToPrimeLocalStorage",
340 time_to_import);
342 size_t local_storage_size_kb = map_->bytes_used() / 1024;
343 // Track localStorage size, from 0-6MB. Note that the maximum size should be
344 // 5MB, but we add some slop since we want to make sure the max size is always
345 // above what we see in practice, since histograms can't change.
346 UMA_HISTOGRAM_CUSTOM_COUNTS("LocalStorage.BrowserLocalStorageSizeInKB",
347 local_storage_size_kb,
348 0, 6 * 1024, 50);
349 if (local_storage_size_kb < 100) {
350 UMA_HISTOGRAM_TIMES(
351 "LocalStorage.BrowserTimeToPrimeLocalStorageUnder100KB",
352 time_to_import);
353 } else if (local_storage_size_kb < 1000) {
354 UMA_HISTOGRAM_TIMES(
355 "LocalStorage.BrowserTimeToPrimeLocalStorage100KBTo1MB",
356 time_to_import);
357 } else {
358 UMA_HISTOGRAM_TIMES(
359 "LocalStorage.BrowserTimeToPrimeLocalStorage1MBTo5MB",
360 time_to_import);
364 DOMStorageArea::CommitBatch* DOMStorageArea::CreateCommitBatchIfNeeded() {
365 DCHECK(!is_shutdown_);
366 if (!commit_batch_) {
367 commit_batch_.reset(new CommitBatch());
368 BrowserThread::PostAfterStartupTask(
369 FROM_HERE, task_runner_,
370 base::Bind(&DOMStorageArea::StartCommitTimer, this));
372 return commit_batch_.get();
375 void DOMStorageArea::StartCommitTimer() {
376 if (is_shutdown_ || !commit_batch_)
377 return;
379 // Start a timer to commit any changes that accrue in the batch, but only if
380 // no commits are currently in flight. In that case the timer will be
381 // started after the commits have happened.
382 if (commit_batches_in_flight_)
383 return;
385 task_runner_->PostDelayedTask(
386 FROM_HERE, base::Bind(&DOMStorageArea::OnCommitTimer, this),
387 ComputeCommitDelay());
390 base::TimeDelta DOMStorageArea::ComputeCommitDelay() const {
391 base::TimeDelta elapsed_time = base::TimeTicks::Now() - start_time_;
392 base::TimeDelta delay = std::max(
393 base::TimeDelta::FromSeconds(kCommitDefaultDelaySecs),
394 std::max(commit_rate_limiter_.ComputeDelayNeeded(elapsed_time),
395 data_rate_limiter_.ComputeDelayNeeded(elapsed_time)));
396 UMA_HISTOGRAM_LONG_TIMES("LocalStorage.CommitDelay", delay);
397 return delay;
400 void DOMStorageArea::OnCommitTimer() {
401 if (is_shutdown_)
402 return;
404 // It's possible that there is nothing to commit if an immediate
405 // commit occured after the timer was scheduled but before it fired.
406 if (!commit_batch_)
407 return;
409 PostCommitTask();
412 void DOMStorageArea::PostCommitTask() {
413 if (is_shutdown_ || !commit_batch_)
414 return;
416 DCHECK(backing_.get());
418 commit_rate_limiter_.add_samples(1);
419 data_rate_limiter_.add_samples(commit_batch_->GetDataSize());
421 // This method executes on the primary sequence, we schedule
422 // a task for immediate execution on the commit sequence.
423 DCHECK(task_runner_->IsRunningOnPrimarySequence());
424 bool success = task_runner_->PostShutdownBlockingTask(
425 FROM_HERE,
426 DOMStorageTaskRunner::COMMIT_SEQUENCE,
427 base::Bind(&DOMStorageArea::CommitChanges, this,
428 base::Owned(commit_batch_.release())));
429 ++commit_batches_in_flight_;
430 DCHECK(success);
433 void DOMStorageArea::CommitChanges(const CommitBatch* commit_batch) {
434 // This method executes on the commit sequence.
435 DCHECK(task_runner_->IsRunningOnCommitSequence());
436 backing_->CommitChanges(commit_batch->clear_all_first,
437 commit_batch->changed_values);
438 // TODO(michaeln): what if CommitChanges returns false (e.g., we're trying to
439 // commit to a DB which is in an inconsistent state?)
440 task_runner_->PostTask(
441 FROM_HERE,
442 base::Bind(&DOMStorageArea::OnCommitComplete, this));
445 void DOMStorageArea::OnCommitComplete() {
446 // We're back on the primary sequence in this method.
447 DCHECK(task_runner_->IsRunningOnPrimarySequence());
448 --commit_batches_in_flight_;
449 if (is_shutdown_)
450 return;
451 if (commit_batch_.get() && !commit_batches_in_flight_) {
452 // More changes have accrued, restart the timer.
453 task_runner_->PostDelayedTask(
454 FROM_HERE, base::Bind(&DOMStorageArea::OnCommitTimer, this),
455 ComputeCommitDelay());
459 void DOMStorageArea::ShutdownInCommitSequence() {
460 // This method executes on the commit sequence.
461 DCHECK(task_runner_->IsRunningOnCommitSequence());
462 DCHECK(backing_.get());
463 if (commit_batch_) {
464 // Commit any changes that accrued prior to the timer firing.
465 bool success = backing_->CommitChanges(
466 commit_batch_->clear_all_first,
467 commit_batch_->changed_values);
468 DCHECK(success);
470 commit_batch_.reset();
471 backing_.reset();
472 session_storage_backing_ = NULL;
475 } // namespace content