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 |