Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 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 | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "webkit/dom_storage/session_storage_database.h" | |
| 6 | |
| 7 #include "base/file_util.h" | |
| 8 #include "base/stringprintf.h" | |
| 9 #include "base/string_number_conversions.h" | |
| 10 #include "base/utf_string_conversions.h" | |
| 11 #include "third_party/leveldatabase/src/include/leveldb/db.h" | |
| 12 #include "third_party/leveldatabase/src/include/leveldb/iterator.h" | |
| 13 #include "third_party/leveldatabase/src/include/leveldb/status.h" | |
| 14 #include "third_party/leveldatabase/src/include/leveldb/options.h" | |
| 15 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h" | |
| 16 | |
| 17 // Layout of the database: | |
| 18 // | key | value | | |
| 19 // --------------------------------------------------- | |
| 20 // | area-1 (1 = namespace id) | dummy | start of area-1-* keys | |
| 21 // | area-1-origin1 | 1 (mapid) | | |
| 22 // | area-1-origin2 | 2 | | |
| 23 // | area-2 | dummy | | |
| 24 // | area-2-origin1 | 1 | shallow copy | |
| 25 // | area-3 | | | |
| 26 // | area-3-origin1 | 3 | deep copy | |
| 27 // | map-1 | 2 (refcount) | start of map-1-* keys | |
| 28 // | map-1-a | b | a = b in map 1 | |
| 29 // | ... | | | |
| 30 // | next-map-id | 4 | | |
|
michaeln
2012/04/12 01:48:46
Do we need to ensure that session namespace_ids in
marja
2012/04/19 10:20:50
Done. In the latest patch, I made it so that Sessi
michaeln
2012/04/22 21:43:34
Sounds like a plausible approach.
| |
| 31 | |
| 32 namespace { | |
| 33 | |
| 34 // Helper functions for creating the keys needed for the schema. | |
| 35 std::string AreaStartKey(const std::string& namespace_id) { | |
|
michaeln
2012/04/12 01:48:46
Maybe NamespaceStartKey() for clarity?
marja
2012/04/19 10:20:50
Done.
| |
| 36 return base::StringPrintf("area-%s", namespace_id.c_str()); | |
| 37 } | |
| 38 | |
| 39 std::string AreaStartKey(int64 namespace_id) { | |
|
michaeln
2012/04/12 01:48:46
ditto
marja
2012/04/19 10:20:50
Done.
| |
| 40 return AreaStartKey(base::Int64ToString(namespace_id)); | |
| 41 } | |
| 42 | |
| 43 std::string AreaKey(const std::string& namespace_id, | |
| 44 const std::string& origin) { | |
| 45 return base::StringPrintf("area-%s-%s", namespace_id.c_str(), origin.c_str()); | |
| 46 } | |
| 47 | |
| 48 std::string AreaKey(int64 namespace_id, const GURL& origin) { | |
| 49 return AreaKey(base::Int64ToString(namespace_id), origin.spec()); | |
| 50 } | |
| 51 | |
| 52 std::string MapStartKey(const std::string& map_id) { | |
|
michaeln
2012/04/12 01:48:46
Maybe MapRefcountKey for clarity?
marja
2012/04/19 10:20:50
Done.
| |
| 53 return base::StringPrintf("map-%s", map_id.c_str()); | |
| 54 } | |
| 55 | |
| 56 std::string MapKey(const std::string& map_id, const std::string& key) { | |
| 57 return base::StringPrintf("map-%s-%s", map_id.c_str(), key.c_str()); | |
| 58 } | |
| 59 | |
| 60 std::string NextMapIdKey() { | |
| 61 return "next-map-id"; | |
| 62 } | |
| 63 | |
| 64 } // namespace | |
| 65 | |
| 66 namespace dom_storage { | |
| 67 | |
| 68 SessionStorageDatabase::SessionStorageDatabase(const FilePath& file_path) | |
| 69 : DomStorageDatabase(file_path) { } | |
| 70 | |
| 71 SessionStorageDatabase::~SessionStorageDatabase() { } | |
| 72 | |
| 73 void SessionStorageDatabase::ReadAllValues(int64 namespace_id, | |
| 74 const GURL& origin, | |
| 75 ValuesMap* result) { | |
| 76 // We don't create a database if it doesn't exist. In that case, there is | |
| 77 // nothing to be added to the result. | |
| 78 if (!LazyOpen(false)) | |
| 79 return; | |
| 80 // Check if there is map for namespace_id-origin. | |
| 81 std::string area_key = AreaKey(namespace_id, origin); | |
| 82 std::string map_id; | |
| 83 leveldb::Status s = db_->Get(leveldb::ReadOptions(), area_key, &map_id); | |
|
michaeln
2012/04/12 01:48:46
How should we handle genuine leveldb errors in thi
marja
2012/04/19 10:20:50
Yes, I'll add proper error handling when I'm more
| |
| 84 if (!s.ok() || map_id.empty()) | |
| 85 return; | |
| 86 ReadValuesInMap(map_id, result); | |
| 87 } | |
| 88 | |
| 89 bool SessionStorageDatabase::CommitChanges(int64 namespace_id, | |
| 90 const GURL& origin, | |
| 91 bool clear_all_first, | |
| 92 const ValuesMap& changes) { | |
| 93 if (!LazyOpen(!changes.empty())) { | |
| 94 // If we're being asked to commit changes that will result in an | |
| 95 // empty database, we return true if the database file doesn't exist. | |
| 96 return clear_all_first && changes.empty() && | |
| 97 !file_util::PathExists(file_path_); | |
| 98 } | |
| 99 leveldb::WriteBatch batch; | |
| 100 // Ensure that the key area-N (see the schema above) exists. | |
| 101 batch.Put(AreaStartKey(namespace_id), ""); | |
| 102 | |
| 103 std::string area_key = AreaKey(namespace_id, origin); | |
| 104 std::string map_id; | |
| 105 leveldb::Status s; | |
| 106 s = db_->Get(leveldb::ReadOptions(), area_key, &map_id); | |
| 107 DCHECK(s.ok() || s.IsNotFound()); | |
| 108 if (s.IsNotFound()) { | |
|
michaeln
2012/04/12 01:48:46
style nit: dont need {}'s for one-liners
marja
2012/04/19 10:20:50
Done.
| |
| 109 CreateNewMap(area_key, &batch, &map_id); | |
| 110 } else if (clear_all_first) { | |
| 111 DeleteValuesInMap(map_id, &batch); | |
| 112 } | |
| 113 | |
| 114 WriteValuesToMap(map_id, changes, &batch); | |
| 115 | |
| 116 s = db_->Write(leveldb::WriteOptions(), &batch); | |
| 117 DCHECK(s.ok()); | |
| 118 return true; | |
| 119 } | |
| 120 | |
| 121 bool SessionStorageDatabase::ShallowCopy(int64 namespace_id, | |
| 122 const GURL& origin, | |
| 123 int64 new_namespace_id) { | |
| 124 // When doing a shallow copy, an existing map is associated with |origin| in | |
| 125 // |namespace_id| and its ref count is increased. | |
|
michaeln
2012/04/12 01:48:46
When we a Clone() a session namespace, we're going
marja
2012/04/19 10:20:50
Done. For this, I needed an uglyish hack for commi
| |
| 126 | |
| 127 // Example, data before shallow copy: | |
| 128 // | area-1 (1 = namespace id) | dummy | | |
| 129 // | area-1-origin1 | 1 (mapid) | | |
| 130 // | map-1 | 1 (refcount) | | |
| 131 // | map-1-a | b | a = b in map 1 | |
| 132 | |
| 133 // Example, data after shallow copy: | |
| 134 // | area-1 (1 = namespace id) | dummy | | |
| 135 // | area-1-origin1 | 1 (mapid) | | |
| 136 // | area-2 | dummy | | |
| 137 // | area-2-origin1 | 1 (mapid) | references the same map | |
| 138 // | map-1 | 2 (inc. refcount) | | |
| 139 // | map-1-a | b | a = b in map 1 | |
| 140 | |
| 141 if (!LazyOpen(true)) { | |
| 142 return false; | |
| 143 } | |
| 144 leveldb::Status s; | |
| 145 std::string old_area_key = AreaKey(namespace_id, origin); | |
| 146 std::string new_area_key = AreaKey(new_namespace_id, origin); | |
| 147 leveldb::WriteBatch batch; | |
| 148 batch.Put(AreaStartKey(new_namespace_id), ""); | |
| 149 std::string map_id; | |
| 150 s = db_->Get(leveldb::ReadOptions(), old_area_key, &map_id); | |
| 151 DCHECK(s.ok()); | |
| 152 batch.Put(new_area_key, map_id); | |
| 153 // Increase the ref count for the map. | |
| 154 std::string map_key = MapStartKey(map_id); | |
| 155 std::string old_ref_count_string; | |
| 156 s = db_->Get(leveldb::ReadOptions(), map_key, &old_ref_count_string); | |
| 157 DCHECK(s.ok()); | |
| 158 int64 old_ref_count; | |
| 159 bool conversion_ok = | |
| 160 base::StringToInt64(old_ref_count_string, &old_ref_count); | |
| 161 DCHECK(conversion_ok); | |
| 162 batch.Put(map_key, base::Int64ToString(++old_ref_count)); | |
| 163 s = db_->Write(leveldb::WriteOptions(), &batch); | |
| 164 DCHECK(s.ok()); | |
| 165 return true; | |
| 166 } | |
| 167 | |
| 168 bool SessionStorageDatabase::DeepCopy(int64 namespace_id, | |
| 169 const GURL& origin) { | |
| 170 // When doing a shallow copy, an existing map is associated with |origin| in | |
| 171 // |namespace_id| and its ref count is increased. | |
| 172 | |
| 173 // Example, data before shallow copy: | |
| 174 // | area-1 (1 = namespace id) | dummy | | |
| 175 // | area-1-origin1 | 1 (mapid) | | |
| 176 // | area-2 | dummy | | |
| 177 // | area-2-origin1 | 1 (mapid) | references the same map | |
| 178 // | map-1 | 2 (refcount) | | |
| 179 // | map-1-a | b | a = b in map 1 | |
| 180 | |
| 181 // Example, data before shallow copy: | |
| 182 // | area-1 (1 = namespace id) | dummy | | |
| 183 // | area-1-origin1 | 1 (mapid) | | |
| 184 // | area-2 | dummy | | |
| 185 // | area-2-origin1 | 2 (mapid) | references the new map | |
| 186 // | map-1 | 1 (dec. refcount) | | |
| 187 // | map-1-a | b | a = b in map 1 | |
| 188 // | map-2 | 1 (refcount) | | |
| 189 // | map-2-a | b | a = b in map 2 | |
| 190 | |
| 191 if (!LazyOpen(true)) { | |
| 192 return false; | |
| 193 } | |
| 194 | |
| 195 leveldb::Status s; | |
| 196 std::string area_key = AreaKey(namespace_id, origin); | |
| 197 leveldb::WriteBatch batch; | |
| 198 std::string old_map_id; | |
| 199 s = db_->Get(leveldb::ReadOptions(), area_key, &old_map_id); | |
| 200 DCHECK(s.ok()); | |
| 201 std::string new_map_id; | |
| 202 CreateNewMap(area_key, &batch, &new_map_id); | |
| 203 | |
| 204 DecreaseRefCount(old_map_id, &batch); | |
| 205 | |
| 206 // Copy the values in the map. | |
| 207 ValuesMap values; | |
| 208 ReadValuesInMap(old_map_id, &values); | |
| 209 WriteValuesToMap(new_map_id, values, &batch); | |
| 210 | |
| 211 s = db_->Write(leveldb::WriteOptions(), &batch); | |
| 212 DCHECK(s.ok()); | |
| 213 return true; | |
| 214 } | |
| 215 | |
| 216 void SessionStorageDatabase::DeleteOrigin(int64 namespace_id, | |
| 217 const GURL& origin) { | |
| 218 if (!LazyOpen(false)) { | |
| 219 // No need to create the database if it doesn't exist. | |
| 220 return; | |
| 221 } | |
| 222 leveldb::WriteBatch batch; | |
| 223 DeleteOrigin(base::Int64ToString(namespace_id), origin.spec(), &batch); | |
| 224 leveldb::Status s = db_->Write(leveldb::WriteOptions(), &batch); | |
| 225 DCHECK(s.ok()); | |
| 226 } | |
| 227 | |
| 228 void SessionStorageDatabase::DeleteNamespace(int64 namespace_id) { | |
| 229 if (!LazyOpen(false)) { | |
| 230 // No need to create the database if it doesn't exist. | |
| 231 return; | |
| 232 } | |
| 233 leveldb::WriteBatch batch; | |
| 234 DeleteNamespace(base::Int64ToString(namespace_id), &batch); | |
| 235 leveldb::Status s = db_->Write(leveldb::WriteOptions(), &batch); | |
| 236 DCHECK(s.ok()); | |
| 237 } | |
| 238 | |
| 239 void SessionStorageDatabase::DeleteLeftoverData() { | |
|
michaeln
2012/04/12 01:48:46
As coded, this method appears to delete everything
marja
2012/04/19 10:20:50
I was planning to do the "protect some origins fro
| |
| 240 if (!LazyOpen(false)) { | |
| 241 // No need to create the database if it doesn't exist. | |
| 242 return; | |
| 243 } | |
| 244 leveldb::WriteBatch batch; | |
| 245 scoped_ptr<leveldb::Iterator> it(db_->NewIterator(leveldb::ReadOptions())); | |
| 246 for (it->SeekToFirst(); it->Valid(); it->Next()) { | |
| 247 // If is of the form "area-<namespaceid>", delete the corresponding | |
| 248 // namespace. | |
| 249 std::string key = it->key().ToString(); | |
| 250 if (key.find("area-") != 0) { | |
| 251 // Itereated past the namespaces. | |
| 252 break; | |
| 253 } | |
| 254 size_t second_dash = key.find('-', 5); | |
| 255 if (second_dash == std::string::npos) { | |
| 256 std::string namespace_id = key.substr(5); | |
| 257 DeleteNamespace(namespace_id, &batch); | |
| 258 } | |
| 259 } | |
| 260 leveldb::Status s = db_->Write(leveldb::WriteOptions(), &batch); | |
| 261 DCHECK(s.ok()); | |
| 262 } | |
| 263 | |
| 264 bool SessionStorageDatabase::LazyOpen(bool create_if_needed) { | |
| 265 if (failed_to_open_) { | |
| 266 // Don't try to open a database that we know has failed | |
| 267 // already. | |
| 268 return false; | |
| 269 } | |
| 270 | |
| 271 if (IsOpen()) | |
| 272 return true; | |
| 273 | |
| 274 bool directory_exists = file_util::PathExists(file_path_); | |
| 275 | |
| 276 if (!directory_exists && !create_if_needed) { | |
| 277 // If the directory doesn't exist already and we haven't been asked to | |
| 278 // create a file on disk, then we don't bother opening the database. This | |
| 279 // means we wait until we absolutely need to put something onto disk before | |
| 280 // we do so. | |
| 281 return false; | |
| 282 } | |
| 283 | |
| 284 leveldb::Options options; | |
| 285 // The directory exists but a valid leveldb database might not exist inside it | |
| 286 // (e.g., a subset of the needed fiels might be missing). Handle this | |
| 287 // situation gracefully by creating the database now. | |
|
michaeln
2012/04/12 01:48:46
tzik recently added logic to handle this sort of s
marja
2012/04/19 10:20:50
Do you mean the recovery options in FileSystemDire
| |
| 288 options.create_if_missing = true; | |
| 289 leveldb::Status s; | |
| 290 leveldb::DB* db; | |
| 291 #if defined(OS_WIN) | |
| 292 s = leveldb::DB::Open(options, WideToUTF8(file_path_.value()), &db); | |
| 293 #elif defined(OS_POSIX) | |
| 294 s = leveldb::DB::Open(options, file_path_.value(), &db); | |
| 295 #endif | |
| 296 if (!s.ok()) { | |
| 297 LOG(WARNING) << "Failed to open leveldb in " << file_path_.value() | |
| 298 << ", error: " << s.ToString(); | |
| 299 DCHECK(db == NULL); | |
| 300 failed_to_open_ = true; | |
| 301 return false; | |
| 302 } | |
| 303 | |
| 304 db_.reset(db); | |
| 305 return true; | |
| 306 } | |
| 307 | |
| 308 bool SessionStorageDatabase::IsOpen() const { | |
| 309 return db_.get(); | |
| 310 } | |
| 311 | |
| 312 void SessionStorageDatabase::ReadValuesInMap(const std::string& map_id, | |
| 313 ValuesMap* result) { | |
| 314 scoped_ptr<leveldb::Iterator> it(db_->NewIterator(leveldb::ReadOptions())); | |
| 315 std::string map_start_key = MapStartKey(map_id); | |
| 316 // Skip the dummy entry, then start iterating the keys in the map. | |
| 317 for (it->Seek(map_start_key), it->Next(); it->Valid(); it->Next()) { | |
|
michaeln
2012/04/12 01:48:46
is it valid to call Next() on an invalid iterator,
marja
2012/04/19 10:20:50
Done. (It's not ok to call Next() on an invalid it
| |
| 318 // Key is of the form "map-<mapid>-<key>". | |
| 319 std::string key = it->key().ToString(); | |
| 320 size_t second_dash = key.find('-', 4); | |
| 321 if (second_dash == std::string::npos || | |
| 322 key.substr(4, second_dash - 4) != map_id) { | |
| 323 // Iterated beyond the keys in this map. | |
| 324 break; | |
| 325 } | |
| 326 string16 key16 = UTF8ToUTF16(key.substr(second_dash + 1)); | |
| 327 // Convert the raw data stored in std::string (it->value()) to raw data | |
| 328 // stored in string16. | |
| 329 // FIXME(marja): Add tests. | |
| 330 string16 value; | |
| 331 size_t len = it->value().size() / sizeof(char16); | |
| 332 value.resize(len); | |
| 333 value.assign(reinterpret_cast<const char16*>(it->value().data()), len); | |
| 334 (*result)[key16] = NullableString16(value, false); | |
| 335 } | |
| 336 } | |
| 337 | |
| 338 bool SessionStorageDatabase::CreateNewMap(const std::string& area_key, | |
| 339 leveldb::WriteBatch* batch, | |
| 340 std::string* map_id) { | |
| 341 // Create a new map. | |
| 342 std::string next_map_id_key = NextMapIdKey(); | |
| 343 leveldb::Status s = db_->Get(leveldb::ReadOptions(), next_map_id_key, map_id); | |
| 344 DCHECK(s.ok() || s.IsNotFound()); | |
| 345 int64 next_map_id = 0; | |
| 346 if (s.IsNotFound()) { | |
| 347 *map_id = "0"; | |
| 348 } else { | |
| 349 bool conversion_ok = base::StringToInt64(*map_id, &next_map_id); | |
| 350 DCHECK(conversion_ok); | |
| 351 // FIXME(marja): What to do if the database is corrupt? | |
| 352 } | |
| 353 batch->Put(next_map_id_key, base::Int64ToString(++next_map_id)); | |
| 354 batch->Put(area_key, *map_id); | |
| 355 batch->Put(MapStartKey(*map_id), base::Int64ToString(1)); | |
| 356 return true; | |
| 357 } | |
| 358 | |
| 359 void SessionStorageDatabase::WriteValuesToMap(const std::string& map_id, | |
| 360 const ValuesMap& values, | |
| 361 leveldb::WriteBatch* batch) { | |
| 362 for(ValuesMap::const_iterator it = values.begin(); it != values.end(); ++it) { | |
| 363 NullableString16 value = it->second; | |
| 364 std::string key = MapKey(map_id, UTF16ToUTF8(it->first)); | |
| 365 if (value.is_null()) { | |
| 366 batch->Delete(key); | |
| 367 } else { | |
| 368 // Convert the raw data stored in string16 to raw data stored in | |
| 369 // std::string. | |
| 370 // FIXME(marja): Add tests. | |
| 371 const char* data = reinterpret_cast<const char*>(value.string().data()); | |
| 372 size_t size = value.string().size() * 2; | |
| 373 batch->Put(key, std::string(data, size)); | |
| 374 } | |
| 375 } | |
| 376 } | |
| 377 | |
| 378 void SessionStorageDatabase::DecreaseRefCount(const std::string& map_id, | |
| 379 leveldb::WriteBatch* batch) { | |
| 380 // Decrease the ref count for the map. | |
| 381 std::string map_key = MapStartKey(map_id); | |
| 382 std::string ref_count_string; | |
| 383 leveldb::Status s = | |
| 384 db_->Get(leveldb::ReadOptions(), map_key, &ref_count_string); | |
| 385 DCHECK(s.ok()); | |
| 386 int64 ref_count; | |
| 387 bool conversion_ok = base::StringToInt64(ref_count_string, &ref_count); | |
| 388 DCHECK(conversion_ok); | |
| 389 if (--ref_count > 0) { | |
| 390 batch->Put(map_key, base::Int64ToString(ref_count)); | |
| 391 } else { | |
| 392 // Clear all keys in the map. | |
| 393 DeleteValuesInMap(map_id, batch); | |
|
michaeln
2012/04/12 01:48:46
This method name is a little decieving since it al
marja
2012/04/19 10:20:50
Done. DeleteValuesInMap -> ClearMap.
| |
| 394 batch->Delete(map_key); | |
| 395 } | |
| 396 } | |
| 397 | |
| 398 void SessionStorageDatabase::DeleteValuesInMap(const std::string& map_id, | |
| 399 leveldb::WriteBatch* batch) { | |
| 400 ValuesMap values; | |
| 401 ReadValuesInMap(map_id, &values); | |
|
michaeln
2012/04/12 01:48:46
Might be nice to read the keys only since that's a
marja
2012/04/19 10:20:50
Done. (Only reading the keys, not the RangeDelete.
| |
| 402 for (ValuesMap::const_iterator it = values.begin(); it != values.end(); ++it) | |
| 403 batch->Delete(MapKey(map_id, UTF16ToUTF8(it->first))); | |
| 404 } | |
| 405 | |
| 406 void SessionStorageDatabase::DeleteOrigin(const std::string& namespace_id, | |
| 407 const std::string& origin, | |
| 408 leveldb::WriteBatch* batch) { | |
| 409 std::string area_key = AreaKey(namespace_id, origin); | |
| 410 std::string map_id; | |
| 411 leveldb::Status s = db_->Get(leveldb::ReadOptions(), area_key, &map_id); | |
| 412 DCHECK(s.ok() || s.IsNotFound()); | |
| 413 if (s.IsNotFound()) | |
| 414 return; // Nothing to delete. | |
| 415 DecreaseRefCount(map_id, batch); | |
| 416 batch->Delete(area_key); | |
| 417 } | |
| 418 | |
| 419 void SessionStorageDatabase::DeleteNamespace(const std::string& namespace_id, | |
| 420 leveldb::WriteBatch* batch) { | |
| 421 std::string area_start_key = AreaStartKey(namespace_id); | |
| 422 // Skip the dummy entry, then start iterating the origins in the area. | |
|
michaeln
2012/04/12 01:48:46
The "origins in the area" is a confusing comment s
marja
2012/04/19 10:20:50
Done. (Fixed the comment, AreaStartKey is now Name
| |
| 423 scoped_ptr<leveldb::Iterator> it(db_->NewIterator(leveldb::ReadOptions())); | |
| 424 for (it->Seek(area_start_key), it->Next(); it->Valid(); it->Next()) { | |
| 425 // Key is of the form "area-<namespaceid>-<origin>". | |
| 426 std::string key = it->key().ToString(); | |
| 427 size_t second_dash = key.find('-', 5); | |
| 428 if (second_dash == std::string::npos || | |
| 429 key.substr(5, second_dash - 5) != namespace_id) { | |
| 430 // Iterated beyond the keys in this map. | |
| 431 break; | |
| 432 } | |
| 433 std::string origin = key.substr(second_dash + 1); | |
| 434 DeleteOrigin(namespace_id, origin, batch); | |
| 435 } | |
| 436 batch->Delete(area_start_key); | |
| 437 } | |
| 438 | |
| 439 // FIXME(marja): Remove this (or dump more intelligently). | |
| 440 void SessionStorageDatabase::DumpData() const { | |
| 441 scoped_ptr<leveldb::Iterator> it(db_->NewIterator(leveldb::ReadOptions())); | |
| 442 LOG(WARNING) << "Dumping session storage"; | |
| 443 for (it->SeekToFirst(); it->Valid(); it->Next()) | |
| 444 LOG(WARNING) << it->key().ToString() << ": " << it->value().ToString(); | |
| 445 LOG(WARNING) << "Dumping session storage complete"; | |
| 446 } | |
| 447 | |
| 448 } // namespace dom_storage | |
| OLD | NEW |