Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / content / browser / dom_storage / dom_storage_area.cc
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_area.h"
6
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"
12 #include "base/time/time.h"
13 #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"
20 #include "storage/browser/database/database_util.h"
21 #include "storage/common/database/database_identifier.h"
22 #include "storage/common/fileapi/file_system_util.h"
23
24 using storage::DatabaseUtil;
25
26 namespace content {
27
28 static const int kCommitTimerSeconds = 1;
29
30 DOMStorageArea::CommitBatch::CommitBatch()
31   : clear_all_first(false) {
32 }
33 DOMStorageArea::CommitBatch::~CommitBatch() {}
34
35
36 // static
37 const base::FilePath::CharType DOMStorageArea::kDatabaseFileExtension[] =
38     FILE_PATH_LITERAL(".localstorage");
39
40 // static
41 base::FilePath DOMStorageArea::DatabaseFileNameFromOrigin(const GURL& origin) {
42   std::string filename = storage::GetIdentifierFromOrigin(origin);
43   // 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).
47       InsertBeforeExtensionASCII(filename);
48 }
49
50 // static
51 GURL DOMStorageArea::OriginFromDatabaseFileName(const base::FilePath& name) {
52   DCHECK(name.MatchesExtension(kDatabaseFileExtension));
53   std::string origin_id =
54       name.BaseName().RemoveExtension().MaybeAsASCII();
55   return storage::GetOriginFromIdentifier(origin_id);
56 }
57
58 DOMStorageArea::DOMStorageArea(
59     const GURL& origin, const base::FilePath& directory,
60     DOMStorageTaskRunner* task_runner)
61     : namespace_id_(kLocalStorageNamespaceId), origin_(origin),
62       directory_(directory),
63       task_runner_(task_runner),
64       map_(new DOMStorageMap(kPerStorageAreaQuota +
65                              kPerStorageAreaOverQuotaAllowance)),
66       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
76 DOMStorageArea::DOMStorageArea(
77     int64 namespace_id,
78     const std::string& persistent_namespace_id,
79     const GURL& origin,
80     SessionStorageDatabase* session_storage_backing,
81     DOMStorageTaskRunner* task_runner)
82     : namespace_id_(namespace_id),
83       persistent_namespace_id_(persistent_namespace_id),
84       origin_(origin),
85       task_runner_(task_runner),
86       map_(new DOMStorageMap(kPerStorageAreaQuota +
87                              kPerStorageAreaOverQuotaAllowance)),
88       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
100 DOMStorageArea::~DOMStorageArea() {
101 }
102
103 void DOMStorageArea::ExtractValues(DOMStorageValuesMap* map) {
104   if (is_shutdown_)
105     return;
106   InitialImportIfNeeded();
107   map_->ExtractValues(map);
108 }
109
110 unsigned DOMStorageArea::Length() {
111   if (is_shutdown_)
112     return 0;
113   InitialImportIfNeeded();
114   return map_->Length();
115 }
116
117 base::NullableString16 DOMStorageArea::Key(unsigned index) {
118   if (is_shutdown_)
119     return base::NullableString16();
120   InitialImportIfNeeded();
121   return map_->Key(index);
122 }
123
124 base::NullableString16 DOMStorageArea::GetItem(const base::string16& key) {
125   if (is_shutdown_)
126     return base::NullableString16();
127   InitialImportIfNeeded();
128   return map_->GetItem(key);
129 }
130
131 bool DOMStorageArea::SetItem(const base::string16& key,
132                              const base::string16& value,
133                              base::NullableString16* old_value) {
134   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();
142     commit_batch->changed_values[key] = base::NullableString16(value, false);
143   }
144   return success;
145 }
146
147 bool DOMStorageArea::RemoveItem(const base::string16& key,
148                                 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();
157     commit_batch->changed_values[key] = base::NullableString16();
158   }
159   return success;
160 }
161
162 bool DOMStorageArea::Clear() {
163   if (is_shutdown_)
164     return false;
165   InitialImportIfNeeded();
166   if (map_->Length() == 0)
167     return false;
168
169   map_ = new DOMStorageMap(kPerStorageAreaQuota +
170                            kPerStorageAreaOverQuotaAllowance);
171
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
181 void DOMStorageArea::FastClear() {
182   // 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
188   map_ = new DOMStorageMap(kPerStorageAreaQuota +
189                            kPerStorageAreaOverQuotaAllowance);
190   // 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
201 DOMStorageArea* DOMStorageArea::ShallowCopy(
202     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
207   DOMStorageArea* copy = new DOMStorageArea(
208       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
223 bool DOMStorageArea::HasUncommittedChanges() const {
224   DCHECK(!is_shutdown_);
225   return commit_batch_.get() || commit_batches_in_flight_;
226 }
227
228 void DOMStorageArea::DeleteOrigin() {
229   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   }
241   map_ = new DOMStorageMap(kPerStorageAreaQuota +
242                            kPerStorageAreaOverQuotaAllowance);
243   if (backing_) {
244     is_initial_import_done_ = false;
245     backing_->Reset();
246     backing_->DeleteFiles();
247   }
248 }
249
250 void DOMStorageArea::PurgeMemory() {
251   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;
261   map_ = new DOMStorageMap(kPerStorageAreaQuota +
262                            kPerStorageAreaOverQuotaAllowance);
263
264   // Recreate the database object, this frees up the open sqlite connection
265   // and its page cache.
266   backing_->Reset();
267 }
268
269 void DOMStorageArea::Shutdown() {
270   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,
278       DOMStorageTaskRunner::COMMIT_SEQUENCE,
279       base::Bind(&DOMStorageArea::ShutdownInCommitSequence, this));
280   DCHECK(success);
281 }
282
283 void DOMStorageArea::InitialImportIfNeeded() {
284   if (is_initial_import_done_)
285     return;
286
287   DCHECK(backing_.get());
288
289   base::TimeTicks before = base::TimeTicks::Now();
290   DOMStorageValuesMap initial_values;
291   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
320 DOMStorageArea::CommitBatch* DOMStorageArea::CreateCommitBatchIfNeeded() {
321   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,
331           base::Bind(&DOMStorageArea::OnCommitTimer, this),
332           base::TimeDelta::FromSeconds(kCommitTimerSeconds));
333     }
334   }
335   return commit_batch_.get();
336 }
337
338 void DOMStorageArea::OnCommitTimer() {
339   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,
354       DOMStorageTaskRunner::COMMIT_SEQUENCE,
355       base::Bind(&DOMStorageArea::CommitChanges, this,
356                  base::Owned(commit_batch_.release())));
357   ++commit_batches_in_flight_;
358   DCHECK(success);
359 }
360
361 void DOMStorageArea::CommitChanges(const CommitBatch* commit_batch) {
362   // This method executes on the commit sequence.
363   DCHECK(task_runner_->IsRunningOnCommitSequence());
364   backing_->CommitChanges(commit_batch->clear_all_first,
365                                          commit_batch->changed_values);
366   // TODO(michaeln): what if CommitChanges returns false (e.g., we're trying to
367   // commit to a DB which is in an inconsistent state?)
368   task_runner_->PostTask(
369       FROM_HERE,
370       base::Bind(&DOMStorageArea::OnCommitComplete, this));
371 }
372
373 void DOMStorageArea::OnCommitComplete() {
374   // We're back on the primary sequence in this method.
375   DCHECK(task_runner_->IsRunningOnPrimarySequence());
376   --commit_batches_in_flight_;
377   if (is_shutdown_)
378     return;
379   if (commit_batch_.get() && !commit_batches_in_flight_) {
380     // More changes have accrued, restart the timer.
381     task_runner_->PostDelayedTask(
382         FROM_HERE,
383         base::Bind(&DOMStorageArea::OnCommitTimer, this),
384         base::TimeDelta::FromSeconds(kCommitTimerSeconds));
385   }
386 }
387
388 void DOMStorageArea::ShutdownInCommitSequence() {
389   // This method executes on the commit sequence.
390   DCHECK(task_runner_->IsRunningOnCommitSequence());
391   DCHECK(backing_.get());
392   if (commit_batch_) {
393     // Commit any changes that accrued prior to the timer firing.
394     bool success = backing_->CommitChanges(
395         commit_batch_->clear_all_first,
396         commit_batch_->changed_values);
397     DCHECK(success);
398   }
399   commit_batch_.reset();
400   backing_.reset();
401   session_storage_backing_ = NULL;
402 }
403
404 }  // namespace content