blob: e358301b1300a63b069ae078887080d3b952d533 [file] [log] [blame]
Ben Murdochbb1529c2013-08-08 10:24:53 +01001// Copyright 2013 The Chromium Authors. All rights reserved.
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Ben Murdochbb1529c2013-08-08 10:24:53 +01005#include "content/browser/dom_storage/dom_storage_area.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01006
7#include "base/bind.h"
8#include "base/location.h"
9#include "base/logging.h"
10#include "base/metrics/histogram.h"
11#include "base/strings/utf_string_conversions.h"
Ben Murdocheb525c52013-07-10 11:40:50 +010012#include "base/time/time.h"
Ben Murdochbb1529c2013-08-08 10:24:53 +010013#include "content/browser/dom_storage/dom_storage_namespace.h"
14#include "content/browser/dom_storage/dom_storage_task_runner.h"
15#include "content/browser/dom_storage/local_storage_database_adapter.h"
16#include "content/browser/dom_storage/session_storage_database.h"
17#include "content/browser/dom_storage/session_storage_database_adapter.h"
18#include "content/common/dom_storage/dom_storage_map.h"
19#include "content/common/dom_storage/dom_storage_types.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010020#include "webkit/browser/database/database_util.h"
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +010021#include "webkit/common/database/database_identifier.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010022#include "webkit/common/fileapi/file_system_util.h"
23
24using webkit_database::DatabaseUtil;
25
Ben Murdochbb1529c2013-08-08 10:24:53 +010026namespace content {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010027
28static const int kCommitTimerSeconds = 1;
29
Ben Murdochbb1529c2013-08-08 10:24:53 +010030DOMStorageArea::CommitBatch::CommitBatch()
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010031 : clear_all_first(false) {
32}
Ben Murdochbb1529c2013-08-08 10:24:53 +010033DOMStorageArea::CommitBatch::~CommitBatch() {}
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010034
35
36// static
Ben Murdochbb1529c2013-08-08 10:24:53 +010037const base::FilePath::CharType DOMStorageArea::kDatabaseFileExtension[] =
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010038 FILE_PATH_LITERAL(".localstorage");
39
40// static
Ben Murdochbb1529c2013-08-08 10:24:53 +010041base::FilePath DOMStorageArea::DatabaseFileNameFromOrigin(const GURL& origin) {
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +010042 std::string filename = webkit_database::GetIdentifierFromOrigin(origin);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010043 // There is no base::FilePath.AppendExtension() method, so start with just the
44 // extension as the filename, and then InsertBeforeExtension the desired
45 // name.
46 return base::FilePath().Append(kDatabaseFileExtension).
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +010047 InsertBeforeExtensionASCII(filename);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010048}
49
50// static
Ben Murdochbb1529c2013-08-08 10:24:53 +010051GURL DOMStorageArea::OriginFromDatabaseFileName(const base::FilePath& name) {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010052 DCHECK(name.MatchesExtension(kDatabaseFileExtension));
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +010053 std::string origin_id =
54 name.BaseName().RemoveExtension().MaybeAsASCII();
55 return webkit_database::GetOriginFromIdentifier(origin_id);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010056}
57
Ben Murdochbb1529c2013-08-08 10:24:53 +010058DOMStorageArea::DOMStorageArea(
59 const GURL& origin, const base::FilePath& directory,
60 DOMStorageTaskRunner* task_runner)
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010061 : namespace_id_(kLocalStorageNamespaceId), origin_(origin),
62 directory_(directory),
63 task_runner_(task_runner),
Ben Murdochbb1529c2013-08-08 10:24:53 +010064 map_(new DOMStorageMap(kPerStorageAreaQuota +
65 kPerStorageAreaOverQuotaAllowance)),
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010066 is_initial_import_done_(true),
67 is_shutdown_(false),
68 commit_batches_in_flight_(0) {
69 if (!directory.empty()) {
70 base::FilePath path = directory.Append(DatabaseFileNameFromOrigin(origin_));
71 backing_.reset(new LocalStorageDatabaseAdapter(path));
72 is_initial_import_done_ = false;
73 }
74}
75
Ben Murdochbb1529c2013-08-08 10:24:53 +010076DOMStorageArea::DOMStorageArea(
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010077 int64 namespace_id,
78 const std::string& persistent_namespace_id,
79 const GURL& origin,
80 SessionStorageDatabase* session_storage_backing,
Ben Murdochbb1529c2013-08-08 10:24:53 +010081 DOMStorageTaskRunner* task_runner)
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010082 : namespace_id_(namespace_id),
83 persistent_namespace_id_(persistent_namespace_id),
84 origin_(origin),
85 task_runner_(task_runner),
Ben Murdochbb1529c2013-08-08 10:24:53 +010086 map_(new DOMStorageMap(kPerStorageAreaQuota +
87 kPerStorageAreaOverQuotaAllowance)),
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010088 session_storage_backing_(session_storage_backing),
89 is_initial_import_done_(true),
90 is_shutdown_(false),
91 commit_batches_in_flight_(0) {
92 DCHECK(namespace_id != kLocalStorageNamespaceId);
93 if (session_storage_backing) {
94 backing_.reset(new SessionStorageDatabaseAdapter(
95 session_storage_backing, persistent_namespace_id, origin));
96 is_initial_import_done_ = false;
97 }
98}
99
Ben Murdochbb1529c2013-08-08 10:24:53 +0100100DOMStorageArea::~DOMStorageArea() {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100101}
102
Ben Murdochbb1529c2013-08-08 10:24:53 +0100103void DOMStorageArea::ExtractValues(DOMStorageValuesMap* map) {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100104 if (is_shutdown_)
105 return;
106 InitialImportIfNeeded();
107 map_->ExtractValues(map);
108}
109
Ben Murdochbb1529c2013-08-08 10:24:53 +0100110unsigned DOMStorageArea::Length() {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100111 if (is_shutdown_)
112 return 0;
113 InitialImportIfNeeded();
114 return map_->Length();
115}
116
Ben Murdochbb1529c2013-08-08 10:24:53 +0100117base::NullableString16 DOMStorageArea::Key(unsigned index) {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100118 if (is_shutdown_)
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100119 return base::NullableString16();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100120 InitialImportIfNeeded();
121 return map_->Key(index);
122}
123
Ben Murdochbb1529c2013-08-08 10:24:53 +0100124base::NullableString16 DOMStorageArea::GetItem(const base::string16& key) {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100125 if (is_shutdown_)
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100126 return base::NullableString16();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100127 InitialImportIfNeeded();
128 return map_->GetItem(key);
129}
130
Ben Murdochbb1529c2013-08-08 10:24:53 +0100131bool DOMStorageArea::SetItem(const base::string16& key,
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100132 const base::string16& value,
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100133 base::NullableString16* old_value) {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100134 if (is_shutdown_)
135 return false;
136 InitialImportIfNeeded();
137 if (!map_->HasOneRef())
138 map_ = map_->DeepCopy();
139 bool success = map_->SetItem(key, value, old_value);
140 if (success && backing_) {
141 CommitBatch* commit_batch = CreateCommitBatchIfNeeded();
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100142 commit_batch->changed_values[key] = base::NullableString16(value, false);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100143 }
144 return success;
145}
146
Ben Murdochbb1529c2013-08-08 10:24:53 +0100147bool DOMStorageArea::RemoveItem(const base::string16& key,
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100148 base::string16* old_value) {
149 if (is_shutdown_)
150 return false;
151 InitialImportIfNeeded();
152 if (!map_->HasOneRef())
153 map_ = map_->DeepCopy();
154 bool success = map_->RemoveItem(key, old_value);
155 if (success && backing_) {
156 CommitBatch* commit_batch = CreateCommitBatchIfNeeded();
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100157 commit_batch->changed_values[key] = base::NullableString16();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100158 }
159 return success;
160}
161
Ben Murdochbb1529c2013-08-08 10:24:53 +0100162bool DOMStorageArea::Clear() {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100163 if (is_shutdown_)
164 return false;
165 InitialImportIfNeeded();
166 if (map_->Length() == 0)
167 return false;
168
Ben Murdochbb1529c2013-08-08 10:24:53 +0100169 map_ = new DOMStorageMap(kPerStorageAreaQuota +
170 kPerStorageAreaOverQuotaAllowance);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100171
172 if (backing_) {
173 CommitBatch* commit_batch = CreateCommitBatchIfNeeded();
174 commit_batch->clear_all_first = true;
175 commit_batch->changed_values.clear();
176 }
177
178 return true;
179}
180
Ben Murdochbb1529c2013-08-08 10:24:53 +0100181void DOMStorageArea::FastClear() {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100182 // TODO(marja): Unify clearing localStorage and sessionStorage. The problem is
183 // to make the following 3 to work together: 1) FastClear, 2) PurgeMemory and
184 // 3) not creating events when clearing an empty area.
185 if (is_shutdown_)
186 return;
187
Ben Murdochbb1529c2013-08-08 10:24:53 +0100188 map_ = new DOMStorageMap(kPerStorageAreaQuota +
189 kPerStorageAreaOverQuotaAllowance);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100190 // This ensures no import will happen while we're waiting to clear the data
191 // from the database. This mechanism fails if PurgeMemory is called.
192 is_initial_import_done_ = true;
193
194 if (backing_) {
195 CommitBatch* commit_batch = CreateCommitBatchIfNeeded();
196 commit_batch->clear_all_first = true;
197 commit_batch->changed_values.clear();
198 }
199}
200
Ben Murdochbb1529c2013-08-08 10:24:53 +0100201DOMStorageArea* DOMStorageArea::ShallowCopy(
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100202 int64 destination_namespace_id,
203 const std::string& destination_persistent_namespace_id) {
204 DCHECK_NE(kLocalStorageNamespaceId, namespace_id_);
205 DCHECK_NE(kLocalStorageNamespaceId, destination_namespace_id);
206
Ben Murdochbb1529c2013-08-08 10:24:53 +0100207 DOMStorageArea* copy = new DOMStorageArea(
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100208 destination_namespace_id, destination_persistent_namespace_id, origin_,
209 session_storage_backing_.get(), task_runner_.get());
210 copy->map_ = map_;
211 copy->is_shutdown_ = is_shutdown_;
212 copy->is_initial_import_done_ = true;
213
214 // All the uncommitted changes to this area need to happen before the actual
215 // shallow copy is made (scheduled by the upper layer). Another OnCommitTimer
216 // call might be in the event queue at this point, but it's handled gracefully
217 // when it fires.
218 if (commit_batch_)
219 OnCommitTimer();
220 return copy;
221}
222
Ben Murdochbb1529c2013-08-08 10:24:53 +0100223bool DOMStorageArea::HasUncommittedChanges() const {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100224 DCHECK(!is_shutdown_);
225 return commit_batch_.get() || commit_batches_in_flight_;
226}
227
Ben Murdochbb1529c2013-08-08 10:24:53 +0100228void DOMStorageArea::DeleteOrigin() {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100229 DCHECK(!is_shutdown_);
230 // This function shouldn't be called for sessionStorage.
231 DCHECK(!session_storage_backing_.get());
232 if (HasUncommittedChanges()) {
233 // TODO(michaeln): This logically deletes the data immediately,
234 // and in a matter of a second, deletes the rows from the backing
235 // database file, but the file itself will linger until shutdown
236 // or purge time. Ideally, this should delete the file more
237 // quickly.
238 Clear();
239 return;
240 }
Ben Murdochbb1529c2013-08-08 10:24:53 +0100241 map_ = new DOMStorageMap(kPerStorageAreaQuota +
242 kPerStorageAreaOverQuotaAllowance);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100243 if (backing_) {
244 is_initial_import_done_ = false;
245 backing_->Reset();
246 backing_->DeleteFiles();
247 }
248}
249
Ben Murdochbb1529c2013-08-08 10:24:53 +0100250void DOMStorageArea::PurgeMemory() {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100251 DCHECK(!is_shutdown_);
252 // Purging sessionStorage is not supported; it won't work with FastClear.
253 DCHECK(!session_storage_backing_.get());
254 if (!is_initial_import_done_ || // We're not using any memory.
255 !backing_.get() || // We can't purge anything.
256 HasUncommittedChanges()) // We leave things alone with changes pending.
257 return;
258
259 // Drop the in memory cache, we'll reload when needed.
260 is_initial_import_done_ = false;
Ben Murdochbb1529c2013-08-08 10:24:53 +0100261 map_ = new DOMStorageMap(kPerStorageAreaQuota +
262 kPerStorageAreaOverQuotaAllowance);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100263
264 // Recreate the database object, this frees up the open sqlite connection
265 // and its page cache.
266 backing_->Reset();
267}
268
Ben Murdochbb1529c2013-08-08 10:24:53 +0100269void DOMStorageArea::Shutdown() {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100270 DCHECK(!is_shutdown_);
271 is_shutdown_ = true;
272 map_ = NULL;
273 if (!backing_)
274 return;
275
276 bool success = task_runner_->PostShutdownBlockingTask(
277 FROM_HERE,
Ben Murdochbb1529c2013-08-08 10:24:53 +0100278 DOMStorageTaskRunner::COMMIT_SEQUENCE,
279 base::Bind(&DOMStorageArea::ShutdownInCommitSequence, this));
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100280 DCHECK(success);
281}
282
Ben Murdochbb1529c2013-08-08 10:24:53 +0100283void DOMStorageArea::InitialImportIfNeeded() {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100284 if (is_initial_import_done_)
285 return;
286
287 DCHECK(backing_.get());
288
289 base::TimeTicks before = base::TimeTicks::Now();
Ben Murdochbb1529c2013-08-08 10:24:53 +0100290 DOMStorageValuesMap initial_values;
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100291 backing_->ReadAllValues(&initial_values);
292 map_->SwapValues(&initial_values);
293 is_initial_import_done_ = true;
294 base::TimeDelta time_to_import = base::TimeTicks::Now() - before;
295 UMA_HISTOGRAM_TIMES("LocalStorage.BrowserTimeToPrimeLocalStorage",
296 time_to_import);
297
298 size_t local_storage_size_kb = map_->bytes_used() / 1024;
299 // Track localStorage size, from 0-6MB. Note that the maximum size should be
300 // 5MB, but we add some slop since we want to make sure the max size is always
301 // above what we see in practice, since histograms can't change.
302 UMA_HISTOGRAM_CUSTOM_COUNTS("LocalStorage.BrowserLocalStorageSizeInKB",
303 local_storage_size_kb,
304 0, 6 * 1024, 50);
305 if (local_storage_size_kb < 100) {
306 UMA_HISTOGRAM_TIMES(
307 "LocalStorage.BrowserTimeToPrimeLocalStorageUnder100KB",
308 time_to_import);
309 } else if (local_storage_size_kb < 1000) {
310 UMA_HISTOGRAM_TIMES(
311 "LocalStorage.BrowserTimeToPrimeLocalStorage100KBTo1MB",
312 time_to_import);
313 } else {
314 UMA_HISTOGRAM_TIMES(
315 "LocalStorage.BrowserTimeToPrimeLocalStorage1MBTo5MB",
316 time_to_import);
317 }
318}
319
Ben Murdochbb1529c2013-08-08 10:24:53 +0100320DOMStorageArea::CommitBatch* DOMStorageArea::CreateCommitBatchIfNeeded() {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100321 DCHECK(!is_shutdown_);
322 if (!commit_batch_) {
323 commit_batch_.reset(new CommitBatch());
324
325 // Start a timer to commit any changes that accrue in the batch, but only if
326 // no commits are currently in flight. In that case the timer will be
327 // started after the commits have happened.
328 if (!commit_batches_in_flight_) {
329 task_runner_->PostDelayedTask(
330 FROM_HERE,
Ben Murdochbb1529c2013-08-08 10:24:53 +0100331 base::Bind(&DOMStorageArea::OnCommitTimer, this),
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100332 base::TimeDelta::FromSeconds(kCommitTimerSeconds));
333 }
334 }
335 return commit_batch_.get();
336}
337
Ben Murdochbb1529c2013-08-08 10:24:53 +0100338void DOMStorageArea::OnCommitTimer() {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100339 if (is_shutdown_)
340 return;
341
342 DCHECK(backing_.get());
343
344 // It's possible that there is nothing to commit, since a shallow copy occured
345 // before the timer fired.
346 if (!commit_batch_)
347 return;
348
349 // This method executes on the primary sequence, we schedule
350 // a task for immediate execution on the commit sequence.
351 DCHECK(task_runner_->IsRunningOnPrimarySequence());
352 bool success = task_runner_->PostShutdownBlockingTask(
353 FROM_HERE,
Ben Murdochbb1529c2013-08-08 10:24:53 +0100354 DOMStorageTaskRunner::COMMIT_SEQUENCE,
355 base::Bind(&DOMStorageArea::CommitChanges, this,
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100356 base::Owned(commit_batch_.release())));
357 ++commit_batches_in_flight_;
358 DCHECK(success);
359}
360
Ben Murdochbb1529c2013-08-08 10:24:53 +0100361void DOMStorageArea::CommitChanges(const CommitBatch* commit_batch) {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100362 // This method executes on the commit sequence.
363 DCHECK(task_runner_->IsRunningOnCommitSequence());
364 bool success = backing_->CommitChanges(commit_batch->clear_all_first,
365 commit_batch->changed_values);
366 DCHECK(success); // TODO(michaeln): what if it fails?
367 task_runner_->PostTask(
368 FROM_HERE,
Ben Murdochbb1529c2013-08-08 10:24:53 +0100369 base::Bind(&DOMStorageArea::OnCommitComplete, this));
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100370}
371
Ben Murdochbb1529c2013-08-08 10:24:53 +0100372void DOMStorageArea::OnCommitComplete() {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100373 // We're back on the primary sequence in this method.
374 DCHECK(task_runner_->IsRunningOnPrimarySequence());
375 --commit_batches_in_flight_;
376 if (is_shutdown_)
377 return;
378 if (commit_batch_.get() && !commit_batches_in_flight_) {
379 // More changes have accrued, restart the timer.
380 task_runner_->PostDelayedTask(
381 FROM_HERE,
Ben Murdochbb1529c2013-08-08 10:24:53 +0100382 base::Bind(&DOMStorageArea::OnCommitTimer, this),
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100383 base::TimeDelta::FromSeconds(kCommitTimerSeconds));
384 }
385}
386
Ben Murdochbb1529c2013-08-08 10:24:53 +0100387void DOMStorageArea::ShutdownInCommitSequence() {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100388 // This method executes on the commit sequence.
389 DCHECK(task_runner_->IsRunningOnCommitSequence());
390 DCHECK(backing_.get());
391 if (commit_batch_) {
392 // Commit any changes that accrued prior to the timer firing.
393 bool success = backing_->CommitChanges(
394 commit_batch_->clear_all_first,
395 commit_batch_->changed_values);
396 DCHECK(success);
397 }
398 commit_batch_.reset();
399 backing_.reset();
400 session_storage_backing_ = NULL;
401}
402
Ben Murdochbb1529c2013-08-08 10:24:53 +0100403} // namespace content