Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "webkit/dom_storage/dom_storage_area.h" | 5 #include "webkit/dom_storage/dom_storage_area.h" |
| 6 | 6 |
| 7 #include "base/bind.h" | 7 #include "base/bind.h" |
| 8 #include "base/logging.h" | 8 #include "base/logging.h" |
| 9 #include "base/time.h" | 9 #include "base/time.h" |
| 10 #include "base/tracked_objects.h" | 10 #include "base/tracked_objects.h" |
| 11 #include "webkit/dom_storage/dom_storage_map.h" | 11 #include "webkit/dom_storage/dom_storage_map.h" |
| 12 #include "webkit/dom_storage/dom_storage_namespace.h" | 12 #include "webkit/dom_storage/dom_storage_namespace.h" |
| 13 #include "webkit/dom_storage/dom_storage_task_runner.h" | 13 #include "webkit/dom_storage/dom_storage_task_runner.h" |
| 14 #include "webkit/dom_storage/dom_storage_types.h" | 14 #include "webkit/dom_storage/dom_storage_types.h" |
| 15 #include "webkit/fileapi/file_system_util.h" | 15 #include "webkit/fileapi/file_system_util.h" |
| 16 | 16 |
| 17 namespace dom_storage { | 17 namespace dom_storage { |
| 18 | 18 |
| 19 struct DomStorageArea::CommitBatch { | |
| 20 bool clear_all_first; | |
| 21 ValuesMap changed_values; | |
| 22 CommitBatch() : clear_all_first(false) {} | |
| 23 }; | |
| 24 | |
| 25 // static | |
| 26 const FilePath::CharType DomStorageArea::kDatabaseFileExtension[] = | |
| 27 FILE_PATH_LITERAL(".localstorage"); | |
| 28 | |
| 29 // static | |
| 30 FilePath DomStorageArea::DatabaseFileNameFromOrigin(const GURL& origin) { | |
| 31 std::string filename = fileapi::GetOriginIdentifierFromURL(origin); | |
| 32 return FilePath().Append(kDatabaseFileExtension). | |
| 33 InsertBeforeExtensionASCII(filename); | |
| 34 } | |
| 35 | |
| 19 DomStorageArea::DomStorageArea( | 36 DomStorageArea::DomStorageArea( |
| 20 int64 namespace_id, const GURL& origin, | 37 int64 namespace_id, const GURL& origin, |
| 21 const FilePath& directory, DomStorageTaskRunner* task_runner) | 38 const FilePath& directory, DomStorageTaskRunner* task_runner) |
| 22 : namespace_id_(namespace_id), origin_(origin), | 39 : namespace_id_(namespace_id), origin_(origin), |
| 23 directory_(directory), | 40 directory_(directory), |
| 24 task_runner_(task_runner), | 41 task_runner_(task_runner), |
| 25 map_(new DomStorageMap(kPerAreaQuota)), | 42 map_(new DomStorageMap(kPerAreaQuota)), |
| 26 backing_(NULL), | 43 backing_(NULL), |
| 27 initial_import_done_(false), | 44 is_initial_import_done_(true), |
| 28 clear_all_next_commit_(false), | 45 is_shutdown_(false) { |
| 29 commit_in_flight_(false) { | |
| 30 | |
| 31 if (namespace_id == kLocalStorageNamespaceId && !directory.empty()) { | 46 if (namespace_id == kLocalStorageNamespaceId && !directory.empty()) { |
| 32 FilePath path = directory.Append(DatabaseFileNameFromOrigin(origin_)); | 47 FilePath path = directory.Append(DatabaseFileNameFromOrigin(origin_)); |
| 33 backing_.reset(new DomStorageDatabase(path)); | 48 backing_.reset(new DomStorageDatabase(path)); |
| 34 } else { | 49 is_initial_import_done_ = false; |
| 35 // Not a local storage area or no directory specified for backing | |
| 36 // database, (i.e. it's an incognito profile). | |
| 37 initial_import_done_ = true; | |
| 38 } | 50 } |
| 39 } | 51 } |
| 40 | 52 |
| 41 DomStorageArea::~DomStorageArea() { | 53 DomStorageArea::~DomStorageArea() { |
| 42 if (clear_all_next_commit_ || !changed_values_.empty()) { | |
| 43 // Still some data left that was not committed to disk, try now. | |
| 44 // We do this regardless of whether we think a commit is in flight | |
| 45 // as there is no guarantee that that commit will actually get | |
| 46 // processed. For example the task_runner_'s message loop could | |
| 47 // unexpectedly quit before the delayed task is fired and leave the | |
| 48 // commit_in_flight_ flag set. But there's no way for us to determine | |
| 49 // that has happened so force a commit now. | |
| 50 | |
| 51 CommitChanges(); | |
| 52 | |
| 53 // TODO(benm): It's possible that the commit failed, and in | |
| 54 // that case we're going to lose data. Integrate with UMA | |
| 55 // to gather stats about how often this actually happens, | |
| 56 // so that we can figure out a contingency plan. | |
| 57 } | |
| 58 } | 54 } |
| 59 | 55 |
| 60 unsigned DomStorageArea::Length() { | 56 unsigned DomStorageArea::Length() { |
| 57 if (is_shutdown_) | |
| 58 return 0; | |
| 61 InitialImportIfNeeded(); | 59 InitialImportIfNeeded(); |
| 62 return map_->Length(); | 60 return map_->Length(); |
| 63 } | 61 } |
| 64 | 62 |
| 65 NullableString16 DomStorageArea::Key(unsigned index) { | 63 NullableString16 DomStorageArea::Key(unsigned index) { |
| 64 if (is_shutdown_) | |
| 65 return NullableString16(true); | |
| 66 InitialImportIfNeeded(); | 66 InitialImportIfNeeded(); |
| 67 return map_->Key(index); | 67 return map_->Key(index); |
| 68 } | 68 } |
| 69 | 69 |
| 70 NullableString16 DomStorageArea::GetItem(const string16& key) { | 70 NullableString16 DomStorageArea::GetItem(const string16& key) { |
| 71 if (is_shutdown_) | |
| 72 return NullableString16(true); | |
| 71 InitialImportIfNeeded(); | 73 InitialImportIfNeeded(); |
| 72 return map_->GetItem(key); | 74 return map_->GetItem(key); |
| 73 } | 75 } |
| 74 | 76 |
| 75 bool DomStorageArea::SetItem(const string16& key, | 77 bool DomStorageArea::SetItem(const string16& key, |
| 76 const string16& value, | 78 const string16& value, |
| 77 NullableString16* old_value) { | 79 NullableString16* old_value) { |
| 80 if (is_shutdown_) | |
| 81 return false; | |
| 78 InitialImportIfNeeded(); | 82 InitialImportIfNeeded(); |
| 79 | |
| 80 if (!map_->HasOneRef()) | 83 if (!map_->HasOneRef()) |
| 81 map_ = map_->DeepCopy(); | 84 map_ = map_->DeepCopy(); |
| 82 bool success = map_->SetItem(key, value, old_value); | 85 bool success = map_->SetItem(key, value, old_value); |
| 83 if (success && backing_.get()) { | 86 if (success && backing_.get()) { |
| 84 changed_values_[key] = NullableString16(value, false); | 87 CommitBatch* commit_batch = GetCommitBatch(); |
| 85 ScheduleCommitChanges(); | 88 commit_batch->changed_values[key] = NullableString16(value, false); |
| 86 } | 89 } |
| 87 return success; | 90 return success; |
| 88 } | 91 } |
| 89 | 92 |
| 90 bool DomStorageArea::RemoveItem(const string16& key, string16* old_value) { | 93 bool DomStorageArea::RemoveItem(const string16& key, string16* old_value) { |
| 94 if (is_shutdown_) | |
| 95 return false; | |
| 91 InitialImportIfNeeded(); | 96 InitialImportIfNeeded(); |
| 92 if (!map_->HasOneRef()) | 97 if (!map_->HasOneRef()) |
| 93 map_ = map_->DeepCopy(); | 98 map_ = map_->DeepCopy(); |
| 94 bool success = map_->RemoveItem(key, old_value); | 99 bool success = map_->RemoveItem(key, old_value); |
| 95 if (success && backing_.get()) { | 100 if (success && backing_.get()) { |
| 96 changed_values_[key] = NullableString16(true); | 101 CommitBatch* commit_batch = GetCommitBatch(); |
| 97 ScheduleCommitChanges(); | 102 commit_batch->changed_values[key] = NullableString16(true); |
| 98 } | 103 } |
| 99 return success; | 104 return success; |
| 100 } | 105 } |
| 101 | 106 |
| 102 bool DomStorageArea::Clear() { | 107 bool DomStorageArea::Clear() { |
| 108 if (is_shutdown_) | |
| 109 return false; | |
| 103 InitialImportIfNeeded(); | 110 InitialImportIfNeeded(); |
| 104 if (map_->Length() == 0) | 111 if (map_->Length() == 0) |
| 105 return false; | 112 return false; |
| 106 | 113 |
| 107 map_ = new DomStorageMap(kPerAreaQuota); | 114 map_ = new DomStorageMap(kPerAreaQuota); |
| 108 | 115 |
| 109 if (backing_.get()) { | 116 if (backing_.get()) { |
| 110 changed_values_.clear(); | 117 CommitBatch* commit_batch = GetCommitBatch(); |
| 111 clear_all_next_commit_ = true; | 118 commit_batch->clear_all_first = true; |
| 112 ScheduleCommitChanges(); | 119 commit_batch->changed_values.clear(); |
| 113 } | 120 } |
| 114 | 121 |
| 115 return true; | 122 return true; |
| 116 } | 123 } |
| 117 | 124 |
| 118 DomStorageArea* DomStorageArea::ShallowCopy(int64 destination_namespace_id) { | 125 DomStorageArea* DomStorageArea::ShallowCopy(int64 destination_namespace_id) { |
| 126 DCHECK(!is_shutdown_); | |
| 119 DCHECK_NE(kLocalStorageNamespaceId, namespace_id_); | 127 DCHECK_NE(kLocalStorageNamespaceId, namespace_id_); |
| 120 DCHECK_NE(kLocalStorageNamespaceId, destination_namespace_id); | 128 DCHECK_NE(kLocalStorageNamespaceId, destination_namespace_id); |
| 121 // SessionNamespaces aren't backed by files on disk. | 129 // SessionNamespaces aren't backed by files on disk. |
| 122 DCHECK(!backing_.get()); | 130 DCHECK(!backing_.get()); |
| 123 | 131 |
| 124 DomStorageArea* copy = new DomStorageArea(destination_namespace_id, origin_, | 132 DomStorageArea* copy = new DomStorageArea(destination_namespace_id, origin_, |
| 125 FilePath(), task_runner_); | 133 FilePath(), task_runner_); |
| 126 copy->map_ = map_; | 134 copy->map_ = map_; |
| 127 return copy; | 135 return copy; |
| 128 } | 136 } |
| 129 | 137 |
| 138 void DomStorageArea::Shutdown() { | |
| 139 DCHECK(!is_shutdown_); | |
| 140 is_shutdown_ = true; | |
| 141 | |
| 142 // If there are changes for which a commit is not yet in flight, | |
| 143 // we post a task now to flush them out. | |
| 144 if (!commit_batch_.get()) | |
| 145 return; | |
| 146 bool success = task_runner_->PostShutdownBlockingCommitTask( | |
| 147 FROM_HERE, base::Bind(&DomStorageArea::CommitShutdownChanges, this)); | |
| 148 DCHECK(success); | |
| 149 } | |
| 150 | |
| 130 void DomStorageArea::InitialImportIfNeeded() { | 151 void DomStorageArea::InitialImportIfNeeded() { |
| 131 if (initial_import_done_) | 152 if (is_initial_import_done_) |
| 132 return; | 153 return; |
| 133 | 154 |
| 134 DCHECK_EQ(kLocalStorageNamespaceId, namespace_id_); | 155 DCHECK_EQ(kLocalStorageNamespaceId, namespace_id_); |
| 135 DCHECK(backing_.get()); | 156 DCHECK(backing_.get()); |
| 136 | 157 |
| 137 ValuesMap initial_values; | 158 ValuesMap initial_values; |
| 138 backing_->ReadAllValues(&initial_values); | 159 backing_->ReadAllValues(&initial_values); |
| 139 map_->SwapValues(&initial_values); | 160 map_->SwapValues(&initial_values); |
| 140 initial_import_done_ = true; | 161 is_initial_import_done_ = true; |
| 141 } | 162 } |
| 142 | 163 |
| 143 void DomStorageArea::ScheduleCommitChanges() { | 164 DomStorageArea::CommitBatch* DomStorageArea::GetCommitBatch() { |
|
benm (inactive)
2012/03/19 14:22:53
As this method is more than a getter (it may kick
michaeln
2012/03/19 16:26:52
maybe StartCommitBatchIfNeeded()?
benm (inactive)
2012/03/19 17:08:45
sg, or maybe Schedule instead of Start, you decide
| |
| 165 DCHECK(is_shutdown_); | |
|
benm (inactive)
2012/03/19 14:22:53
!is_shutdown?
michaeln
2012/03/19 16:04:42
Done.
| |
| 166 if (!commit_batch_.get()) { | |
| 167 commit_batch_.reset(new CommitBatch()); | |
| 168 | |
| 169 // Start a timer to commit any changes that accrue in the batch, | |
| 170 // but only if a commit is not currently in flight. In that case | |
| 171 // the timer will be started after the current commit has happened. | |
| 172 if (!in_flight_commit_batch_.get()) { | |
| 173 task_runner_->PostDelayedTask( | |
| 174 FROM_HERE, | |
| 175 base::Bind(&DomStorageArea::OnCommitTimer, this), | |
| 176 base::TimeDelta::FromSeconds(1)); | |
|
benm (inactive)
2012/03/19 14:22:53
might be worth putting a constant in dom_storage_t
michaeln
2012/03/19 16:04:42
Done.
| |
| 177 } | |
| 178 } | |
| 179 return commit_batch_.get(); | |
| 180 } | |
| 181 | |
| 182 void DomStorageArea::OnCommitTimer() { | |
| 144 DCHECK_EQ(kLocalStorageNamespaceId, namespace_id_); | 183 DCHECK_EQ(kLocalStorageNamespaceId, namespace_id_); |
| 145 DCHECK(backing_.get()); | 184 DCHECK(backing_.get()); |
| 146 DCHECK(clear_all_next_commit_ || !changed_values_.empty()); | 185 DCHECK(commit_batch_.get()); |
| 147 DCHECK(task_runner_.get()); | 186 DCHECK(!in_flight_commit_batch_.get()); |
| 148 | 187 if (is_shutdown_) |
| 149 if (commit_in_flight_) | |
| 150 return; | 188 return; |
| 151 | 189 |
| 152 commit_in_flight_ = task_runner_->PostDelayedTask( | 190 // This method executes on the 'read' sequence, we schedule |
| 153 FROM_HERE, base::Bind(&DomStorageArea::CommitChanges, this), | 191 // a task for immediate exeuction on the 'write' sequence. |
|
benm (inactive)
2012/03/19 14:22:53
execution
michaeln
2012/03/19 16:04:42
Done.
| |
| 154 base::TimeDelta::FromSeconds(1)); | 192 in_flight_commit_batch_ = commit_batch_.Pass(); |
| 155 DCHECK(commit_in_flight_); | 193 bool success = task_runner_->PostShutdownBlockingCommitTask( |
| 194 FROM_HERE, base::Bind(&DomStorageArea::CommitChanges, this)); | |
| 195 DCHECK(success); | |
| 156 } | 196 } |
| 157 | 197 |
| 158 void DomStorageArea::CommitChanges() { | 198 void DomStorageArea::CommitChanges() { |
| 159 DCHECK(backing_.get()); | 199 // This method executes on the 'write' sequence. |
|
benm (inactive)
2012/03/19 14:22:53
Can we DCHECK that this is the case?
michaeln
2012/03/19 16:04:42
I wish we could, but right now there is no way to
| |
| 160 if (backing_->CommitChanges(clear_all_next_commit_, changed_values_)) { | 200 DCHECK(in_flight_commit_batch_.get()); |
| 161 clear_all_next_commit_ = false; | 201 bool success = backing_->CommitChanges( |
| 162 changed_values_.clear(); | 202 in_flight_commit_batch_->clear_all_first, |
| 163 } | 203 in_flight_commit_batch_->changed_values); |
| 164 commit_in_flight_ = false; | 204 DCHECK(success); // TODO(michaeln): what if it fails? |
| 205 task_runner_->PostTask( | |
| 206 FROM_HERE, | |
| 207 base::Bind(&DomStorageArea::OnCommitComplete, this)); | |
| 165 } | 208 } |
| 166 | 209 |
| 167 // static | 210 void DomStorageArea::OnCommitComplete() { |
| 168 FilePath DomStorageArea::DatabaseFileNameFromOrigin(const GURL& origin) { | 211 in_flight_commit_batch_.reset(); |
| 169 std::string filename = fileapi::GetOriginIdentifierFromURL(origin) | 212 if (commit_batch_.get() && !is_shutdown_) { |
| 170 + ".localstorage"; | 213 // More changes have accrued, restart the timer. |
| 171 return FilePath().AppendASCII(filename); | 214 task_runner_->PostDelayedTask( |
| 215 FROM_HERE, | |
| 216 base::Bind(&DomStorageArea::OnCommitTimer, this), | |
| 217 base::TimeDelta::FromSeconds(1)); | |
| 218 } | |
| 219 } | |
| 220 | |
| 221 void DomStorageArea::CommitShutdownChanges() { | |
| 222 // This method executes on the 'write' sequence. | |
| 223 DCHECK(commit_batch_.get()); | |
| 224 bool success = backing_->CommitChanges( | |
| 225 commit_batch_->clear_all_first, | |
| 226 commit_batch_->changed_values); | |
| 227 DCHECK(success); | |
| 172 } | 228 } |
| 173 | 229 |
| 174 } // namespace dom_storage | 230 } // namespace dom_storage |
| OLD | NEW |