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_namespace.h"
10 #include "base/basictypes.h"
11 #include "base/bind.h"
12 #include "base/location.h"
13 #include "base/logging.h"
14 #include "base/stl_util.h"
15 #include "content/browser/dom_storage/dom_storage_area.h"
16 #include "content/browser/dom_storage/dom_storage_context_impl.h"
17 #include "content/browser/dom_storage/dom_storage_task_runner.h"
18 #include "content/browser/dom_storage/session_storage_database.h"
19 #include "content/common/dom_storage/dom_storage_types.h"
20 #include "content/public/common/child_process_host.h"
26 static const unsigned int kMaxTransactionLogEntries
= 8 * 1024;
30 DOMStorageNamespace::DOMStorageNamespace(
31 const base::FilePath
& directory
,
32 DOMStorageTaskRunner
* task_runner
)
33 : namespace_id_(kLocalStorageNamespaceId
),
34 directory_(directory
),
35 task_runner_(task_runner
),
37 old_master_for_close_area_(NULL
),
38 master_alias_count_decremented_(false),
39 ready_for_deletion_pending_aliases_(false),
40 must_persist_at_shutdown_(false) {
43 DOMStorageNamespace::DOMStorageNamespace(
45 const std::string
& persistent_namespace_id
,
46 SessionStorageDatabase
* session_storage_database
,
47 DOMStorageTaskRunner
* task_runner
)
48 : namespace_id_(namespace_id
),
49 persistent_namespace_id_(persistent_namespace_id
),
50 task_runner_(task_runner
),
51 session_storage_database_(session_storage_database
),
53 old_master_for_close_area_(NULL
),
54 master_alias_count_decremented_(false),
55 ready_for_deletion_pending_aliases_(false),
56 must_persist_at_shutdown_(false) {
57 DCHECK_NE(kLocalStorageNamespaceId
, namespace_id
);
60 DOMStorageNamespace::~DOMStorageNamespace() {
61 STLDeleteValues(&transactions_
);
62 DecrementMasterAliasCount();
65 DOMStorageArea
* DOMStorageNamespace::OpenStorageArea(const GURL
& origin
) {
66 if (alias_master_namespace_
)
67 return alias_master_namespace_
->OpenStorageArea(origin
);
68 if (AreaHolder
* holder
= GetAreaHolder(origin
)) {
69 ++(holder
->open_count_
);
70 return holder
->area_
.get();
73 if (namespace_id_
== kLocalStorageNamespaceId
) {
74 area
= new DOMStorageArea(origin
, directory_
, task_runner_
.get());
76 area
= new DOMStorageArea(
77 namespace_id_
, persistent_namespace_id_
, origin
,
78 session_storage_database_
.get(), task_runner_
.get());
80 areas_
[origin
] = AreaHolder(area
, 1);
84 void DOMStorageNamespace::CloseStorageArea(DOMStorageArea
* area
) {
85 AreaHolder
* holder
= GetAreaHolder(area
->origin());
86 if (alias_master_namespace_
) {
88 if (old_master_for_close_area_
)
89 old_master_for_close_area_
->CloseStorageArea(area
);
91 alias_master_namespace_
->CloseStorageArea(area
);
95 DCHECK_EQ(holder
->area_
.get(), area
);
96 --(holder
->open_count_
);
97 // TODO(michaeln): Clean up areas that aren't needed in memory anymore.
98 // The in-process-webkit based impl didn't do this either, but would be nice.
101 DOMStorageArea
* DOMStorageNamespace::GetOpenStorageArea(const GURL
& origin
) {
102 if (alias_master_namespace_
)
103 return alias_master_namespace_
->GetOpenStorageArea(origin
);
104 AreaHolder
* holder
= GetAreaHolder(origin
);
105 if (holder
&& holder
->open_count_
)
106 return holder
->area_
.get();
110 DOMStorageNamespace
* DOMStorageNamespace::Clone(
111 int64 clone_namespace_id
,
112 const std::string
& clone_persistent_namespace_id
) {
113 if (alias_master_namespace_
) {
114 return alias_master_namespace_
->Clone(clone_namespace_id
,
115 clone_persistent_namespace_id
);
117 DCHECK_NE(kLocalStorageNamespaceId
, namespace_id_
);
118 DCHECK_NE(kLocalStorageNamespaceId
, clone_namespace_id
);
119 DOMStorageNamespace
* clone
= new DOMStorageNamespace(
120 clone_namespace_id
, clone_persistent_namespace_id
,
121 session_storage_database_
.get(), task_runner_
.get());
122 AreaMap::const_iterator it
= areas_
.begin();
123 // Clone the in-memory structures.
124 for (; it
!= areas_
.end(); ++it
) {
125 DOMStorageArea
* area
= it
->second
.area_
->ShallowCopy(
126 clone_namespace_id
, clone_persistent_namespace_id
);
127 clone
->areas_
[it
->first
] = AreaHolder(area
, 0);
129 // And clone the on-disk structures, too.
130 if (session_storage_database_
.get()) {
131 task_runner_
->PostShutdownBlockingTask(
133 DOMStorageTaskRunner::COMMIT_SEQUENCE
,
134 base::Bind(base::IgnoreResult(&SessionStorageDatabase::CloneNamespace
),
135 session_storage_database_
.get(), persistent_namespace_id_
,
136 clone_persistent_namespace_id
));
141 DOMStorageNamespace
* DOMStorageNamespace::CreateAlias(
142 int64 alias_namespace_id
) {
143 // Creates an alias of the current DOMStorageNamespace.
144 // The alias will have a reference to this namespace (called the master),
145 // and all operations will be redirected to the master (in particular,
146 // the alias will never open any areas of its own, but always redirect
147 // to the master). Accordingly, an alias will also never undergo the shutdown
148 // procedure which initiates persisting to disk, since there is never any data
149 // of its own to persist to disk. DOMStorageContextImpl is the place where
150 // shutdowns are initiated, but only for non-alias DOMStorageNamespaces.
151 DCHECK_NE(kLocalStorageNamespaceId
, namespace_id_
);
152 DCHECK_NE(kLocalStorageNamespaceId
, alias_namespace_id
);
153 DOMStorageNamespace
* alias
= new DOMStorageNamespace(
154 alias_namespace_id
, persistent_namespace_id_
,
155 session_storage_database_
.get(), task_runner_
.get());
156 if (alias_master_namespace_
!= NULL
) {
157 DCHECK(alias_master_namespace_
->alias_master_namespace_
== NULL
);
158 alias
->alias_master_namespace_
= alias_master_namespace_
;
160 alias
->alias_master_namespace_
= this;
162 alias
->alias_master_namespace_
->num_aliases_
++;
166 void DOMStorageNamespace::DeleteLocalStorageOrigin(const GURL
& origin
) {
167 DCHECK(!session_storage_database_
.get());
168 DCHECK(!alias_master_namespace_
.get());
169 AreaHolder
* holder
= GetAreaHolder(origin
);
171 holder
->area_
->DeleteOrigin();
174 if (!directory_
.empty()) {
175 scoped_refptr
<DOMStorageArea
> area
=
176 new DOMStorageArea(origin
, directory_
, task_runner_
.get());
177 area
->DeleteOrigin();
181 void DOMStorageNamespace::DeleteSessionStorageOrigin(const GURL
& origin
) {
182 if (alias_master_namespace_
) {
183 alias_master_namespace_
->DeleteSessionStorageOrigin(origin
);
186 DOMStorageArea
* area
= OpenStorageArea(origin
);
188 CloseStorageArea(area
);
191 void DOMStorageNamespace::PurgeMemory(PurgeOption option
) {
192 if (alias_master_namespace_
) {
193 alias_master_namespace_
->PurgeMemory(option
);
196 if (directory_
.empty())
197 return; // We can't purge w/o backing on disk.
198 AreaMap::iterator it
= areas_
.begin();
199 while (it
!= areas_
.end()) {
200 // Leave it alone if changes are pending
201 if (it
->second
.area_
->HasUncommittedChanges()) {
206 // If not in use, we can shut it down and remove
207 // it from our collection entirely.
208 if (it
->second
.open_count_
== 0) {
209 it
->second
.area_
->Shutdown();
214 if (option
== PURGE_AGGRESSIVE
) {
215 // If aggressive is true, we clear caches and such
217 it
->second
.area_
->PurgeMemory();
224 void DOMStorageNamespace::Shutdown() {
225 AreaMap::const_iterator it
= areas_
.begin();
226 for (; it
!= areas_
.end(); ++it
)
227 it
->second
.area_
->Shutdown();
230 unsigned int DOMStorageNamespace::CountInMemoryAreas() const {
231 if (alias_master_namespace_
)
232 return alias_master_namespace_
->CountInMemoryAreas();
233 unsigned int area_count
= 0;
234 for (AreaMap::const_iterator it
= areas_
.begin(); it
!= areas_
.end(); ++it
) {
235 if (it
->second
.area_
->IsLoadedInMemory())
241 DOMStorageNamespace::AreaHolder
*
242 DOMStorageNamespace::GetAreaHolder(const GURL
& origin
) {
243 AreaMap::iterator found
= areas_
.find(origin
);
244 if (found
== areas_
.end())
246 return &(found
->second
);
249 void DOMStorageNamespace::AddTransactionLogProcessId(int process_id
) {
250 DCHECK(process_id
!= ChildProcessHost::kInvalidUniqueID
);
251 DCHECK(transactions_
.count(process_id
) == 0);
252 TransactionData
* transaction_data
= new TransactionData
;
253 transactions_
[process_id
] = transaction_data
;
256 void DOMStorageNamespace::RemoveTransactionLogProcessId(int process_id
) {
257 DCHECK(process_id
!= ChildProcessHost::kInvalidUniqueID
);
258 DCHECK(transactions_
.count(process_id
) == 1);
259 delete transactions_
[process_id
];
260 transactions_
.erase(process_id
);
263 SessionStorageNamespace::MergeResult
DOMStorageNamespace::Merge(
266 DOMStorageNamespace
* other
,
267 DOMStorageContextImpl
* context
) {
268 if (!alias_master_namespace())
269 return SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_ALIAS
;
270 if (transactions_
.count(process_id
) < 1)
271 return SessionStorageNamespace::MERGE_RESULT_NOT_LOGGING
;
272 TransactionData
* data
= transactions_
[process_id
];
273 if (data
->max_log_size_exceeded
)
274 return SessionStorageNamespace::MERGE_RESULT_TOO_MANY_TRANSACTIONS
;
275 if (data
->log
.size() < 1) {
277 SwitchToNewAliasMaster(other
, context
);
278 return SessionStorageNamespace::MERGE_RESULT_NO_TRANSACTIONS
;
281 // skip_areas and skip_keys store areas and (area, key) pairs, respectively,
282 // that have already been handled previously. Any further modifications to
283 // them will not change the result of the hypothetical merge.
284 std::set
<GURL
> skip_areas
;
285 typedef std::pair
<GURL
, base::string16
> OriginKey
;
286 std::set
<OriginKey
> skip_keys
;
287 // Indicates whether we could still merge the namespaces preserving all
288 // individual transactions.
289 for (unsigned int i
= 0; i
< data
->log
.size(); i
++) {
290 TransactionRecord
& transaction
= data
->log
[i
];
291 if (transaction
.transaction_type
== TRANSACTION_CLEAR
) {
292 skip_areas
.insert(transaction
.origin
);
295 if (skip_areas
.find(transaction
.origin
) != skip_areas
.end())
297 if (skip_keys
.find(OriginKey(transaction
.origin
, transaction
.key
))
298 != skip_keys
.end()) {
301 if (transaction
.transaction_type
== TRANSACTION_REMOVE
||
302 transaction
.transaction_type
== TRANSACTION_WRITE
) {
303 skip_keys
.insert(OriginKey(transaction
.origin
, transaction
.key
));
306 if (transaction
.transaction_type
== TRANSACTION_READ
) {
307 DOMStorageArea
* area
= other
->OpenStorageArea(transaction
.origin
);
308 base::NullableString16 other_value
= area
->GetItem(transaction
.key
);
309 other
->CloseStorageArea(area
);
310 if (transaction
.value
!= other_value
)
311 return SessionStorageNamespace::MERGE_RESULT_NOT_MERGEABLE
;
317 return SessionStorageNamespace::MERGE_RESULT_MERGEABLE
;
319 // Actually perform the merge.
321 for (unsigned int i
= 0; i
< data
->log
.size(); i
++) {
322 TransactionRecord
& transaction
= data
->log
[i
];
323 if (transaction
.transaction_type
== TRANSACTION_READ
)
325 DOMStorageArea
* area
= other
->OpenStorageArea(transaction
.origin
);
326 if (transaction
.transaction_type
== TRANSACTION_CLEAR
) {
329 context
->NotifyAreaCleared(area
, transaction
.page_url
);
331 if (transaction
.transaction_type
== TRANSACTION_REMOVE
) {
332 base::string16 old_value
;
333 area
->RemoveItem(transaction
.key
, &old_value
);
335 context
->NotifyItemRemoved(area
, transaction
.key
, old_value
,
336 transaction
.page_url
);
339 if (transaction
.transaction_type
== TRANSACTION_WRITE
) {
340 base::NullableString16 old_value
;
341 area
->SetItem(transaction
.key
, base::string16(transaction
.value
.string()),
344 context
->NotifyItemSet(area
, transaction
.key
,transaction
.value
.string(),
345 old_value
, transaction
.page_url
);
348 other
->CloseStorageArea(area
);
351 SwitchToNewAliasMaster(other
, context
);
352 return SessionStorageNamespace::MERGE_RESULT_MERGEABLE
;
355 bool DOMStorageNamespace::IsLoggingRenderer(int process_id
) {
356 DCHECK(process_id
!= ChildProcessHost::kInvalidUniqueID
);
357 if (transactions_
.count(process_id
) < 1)
359 return !transactions_
[process_id
]->max_log_size_exceeded
;
362 void DOMStorageNamespace::AddTransaction(
363 int process_id
, const TransactionRecord
& transaction
) {
364 if (!IsLoggingRenderer(process_id
))
366 TransactionData
* transaction_data
= transactions_
[process_id
];
367 DCHECK(transaction_data
);
368 if (transaction_data
->max_log_size_exceeded
)
370 transaction_data
->log
.push_back(transaction
);
371 if (transaction_data
->log
.size() > kMaxTransactionLogEntries
) {
372 transaction_data
->max_log_size_exceeded
= true;
373 transaction_data
->log
.clear();
377 bool DOMStorageNamespace::DecrementMasterAliasCount() {
378 if (!alias_master_namespace_
|| master_alias_count_decremented_
)
380 DCHECK_GT(alias_master_namespace_
->num_aliases_
, 0);
381 alias_master_namespace_
->num_aliases_
--;
382 master_alias_count_decremented_
= true;
383 return (alias_master_namespace_
->num_aliases_
== 0);
386 void DOMStorageNamespace::SwitchToNewAliasMaster(
387 DOMStorageNamespace
* new_master
,
388 DOMStorageContextImpl
* context
) {
389 DCHECK(alias_master_namespace());
390 scoped_refptr
<DOMStorageNamespace
> old_master
= alias_master_namespace();
391 if (new_master
->alias_master_namespace())
392 new_master
= new_master
->alias_master_namespace();
393 DCHECK(!new_master
->alias_master_namespace());
394 DCHECK(old_master
!= this);
395 DCHECK(old_master
!= new_master
);
396 DecrementMasterAliasCount();
397 alias_master_namespace_
= new_master
;
398 alias_master_namespace_
->num_aliases_
++;
399 master_alias_count_decremented_
= false;
400 // There are three things that we need to clean up:
401 // -- the old master may ready for shutdown, if its last alias has disappeared
402 // -- The dom_storage hosts need to close and reopen their areas, so that
403 // they point to the correct new areas.
404 // -- The renderers will need to reset their local caches.
405 // All three of these things are accomplished with the following call below.
406 // |context| will be NULL in unit tests, which is when this will
407 // not apply, of course.
408 // During this call, open areas will be closed & reopened, so that they now
409 // come from the correct new master. Therefore, we must send close operations
410 // to the old master.
411 old_master_for_close_area_
= old_master
.get();
413 context
->NotifyAliasSessionMerged(namespace_id(), old_master
.get());
414 old_master_for_close_area_
= NULL
;
417 DOMStorageNamespace::TransactionData::TransactionData()
418 : max_log_size_exceeded(false) {
421 DOMStorageNamespace::TransactionData::~TransactionData() {
424 DOMStorageNamespace::TransactionRecord::TransactionRecord() {
427 DOMStorageNamespace::TransactionRecord::~TransactionRecord() {
432 DOMStorageNamespace::AreaHolder::AreaHolder()
436 DOMStorageNamespace::AreaHolder::AreaHolder(
437 DOMStorageArea
* area
, int count
)
438 : area_(area
), open_count_(count
) {
441 DOMStorageNamespace::AreaHolder::~AreaHolder() {
444 } // namespace content