Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(51)

Side by Side Diff: content/browser/indexed_db/indexed_db_backing_store.cc

Issue 264483002: This is the implementation of the primary and secondary blob journals for (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Export kAllBlobsKey Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2013 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 "content/browser/indexed_db/indexed_db_backing_store.h" 5 #include "content/browser/indexed_db/indexed_db_backing_store.h"
6 6
7 #include "base/file_util.h" 7 #include "base/file_util.h"
8 #include "base/files/file_path.h" 8 #include "base/files/file_path.h"
9 #include "base/format_macros.h" 9 #include "base/format_macros.h"
10 #include "base/json/json_reader.h" 10 #include "base/json/json_reader.h"
(...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after
119 SET_MAX_OBJECT_STORE_ID, 119 SET_MAX_OBJECT_STORE_ID,
120 SET_MAX_INDEX_ID, 120 SET_MAX_INDEX_ID,
121 GET_NEW_DATABASE_ID, 121 GET_NEW_DATABASE_ID,
122 GET_NEW_VERSION_NUMBER, 122 GET_NEW_VERSION_NUMBER,
123 CREATE_IDBDATABASE_METADATA, 123 CREATE_IDBDATABASE_METADATA,
124 DELETE_DATABASE, 124 DELETE_DATABASE,
125 TRANSACTION_COMMIT_METHOD, // TRANSACTION_COMMIT is a WinNT.h macro 125 TRANSACTION_COMMIT_METHOD, // TRANSACTION_COMMIT is a WinNT.h macro
126 GET_DATABASE_NAMES, 126 GET_DATABASE_NAMES,
127 DELETE_INDEX, 127 DELETE_INDEX,
128 CLEAR_OBJECT_STORE, 128 CLEAR_OBJECT_STORE,
129 READ_BLOB_JOURNAL,
130 DECODE_BLOB_JOURNAL,
129 INTERNAL_ERROR_MAX, 131 INTERNAL_ERROR_MAX,
130 }; 132 };
131 133
132 static void RecordInternalError(const char* type, 134 static void RecordInternalError(const char* type,
133 IndexedDBBackingStoreErrorSource location) { 135 IndexedDBBackingStoreErrorSource location) {
134 std::string name; 136 std::string name;
135 name.append("WebCore.IndexedDB.BackingStore.").append(type).append("Error"); 137 name.append("WebCore.IndexedDB.BackingStore.").append(type).append("Error");
136 base::Histogram::FactoryGet(name, 138 base::Histogram::FactoryGet(name,
137 1, 139 1,
138 INTERNAL_ERROR_MAX, 140 INTERNAL_ERROR_MAX,
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
185 // Was able to use LevelDB to read the data w/o error, but the data read was not 187 // Was able to use LevelDB to read the data w/o error, but the data read was not
186 // in the expected format. 188 // in the expected format.
187 static leveldb::Status InternalInconsistencyStatus() { 189 static leveldb::Status InternalInconsistencyStatus() {
188 return leveldb::Status::Corruption("Internal inconsistency"); 190 return leveldb::Status::Corruption("Internal inconsistency");
189 } 191 }
190 192
191 static leveldb::Status InvalidDBKeyStatus() { 193 static leveldb::Status InvalidDBKeyStatus() {
192 return leveldb::Status::InvalidArgument("Invalid database key ID"); 194 return leveldb::Status::InvalidArgument("Invalid database key ID");
193 } 195 }
194 196
197 static leveldb::Status IOErrorStatus() {
198 return leveldb::Status::IOError("IO Error");
199 }
200
195 template <typename DBOrTransaction> 201 template <typename DBOrTransaction>
196 static leveldb::Status GetInt(DBOrTransaction* db, 202 static leveldb::Status GetInt(DBOrTransaction* db,
197 const StringPiece& key, 203 const StringPiece& key,
198 int64* found_int, 204 int64* found_int,
199 bool* found) { 205 bool* found) {
200 std::string result; 206 std::string result;
201 leveldb::Status s = db->Get(key, &result, found); 207 leveldb::Status s = db->Get(key, &result, found);
202 if (!s.ok()) 208 if (!s.ok())
203 return s; 209 return s;
204 if (!*found) 210 if (!*found)
(...skipping 262 matching lines...) Expand 10 before | Expand all | Expand 10 after
467 scoped_ptr<LevelDBDatabase>* db, 473 scoped_ptr<LevelDBDatabase>* db,
468 bool* is_disk_full) OVERRIDE { 474 bool* is_disk_full) OVERRIDE {
469 return LevelDBDatabase::Open(file_name, comparator, db, is_disk_full); 475 return LevelDBDatabase::Open(file_name, comparator, db, is_disk_full);
470 } 476 }
471 virtual leveldb::Status DestroyLevelDB(const base::FilePath& file_name) 477 virtual leveldb::Status DestroyLevelDB(const base::FilePath& file_name)
472 OVERRIDE { 478 OVERRIDE {
473 return LevelDBDatabase::Destroy(file_name); 479 return LevelDBDatabase::Destroy(file_name);
474 } 480 }
475 }; 481 };
476 482
483 // TODO(ericu): Error recovery. If we persistently can't read the
484 // blob journal, the safe thing to do is to clear it and leak the blobs,
485 // though that may be costly. Still, database/directory deletion should always
486 // clean things up, and we can write an fsck that will do a full correction if
487 // need be.
488 template <typename T>
489 static leveldb::Status GetBlobJournal(const StringPiece& leveldb_key,
490 T* leveldb_transaction,
491 BlobJournalType* journal) {
492 std::string data;
493 bool found = false;
494 leveldb::Status s = leveldb_transaction->Get(leveldb_key, &data, &found);
495 if (!s.ok()) {
496 INTERNAL_READ_ERROR_UNTESTED(READ_BLOB_JOURNAL);
497 return s;
498 }
499 journal->clear();
500 if (!found || !data.size())
501 return leveldb::Status::OK();
502 StringPiece slice(data);
503 if (!DecodeBlobJournal(&slice, journal)) {
504 INTERNAL_READ_ERROR_UNTESTED(DECODE_BLOB_JOURNAL);
505 s = InternalInconsistencyStatus();
506 }
507 return s;
508 }
509
510 static void ClearBlobJournal(LevelDBTransaction* leveldb_transaction,
511 const std::string& level_db_key) {
512 leveldb_transaction->Remove(level_db_key);
513 }
514
515 static void UpdatePrimaryJournalWithBlobList(
516 LevelDBTransaction* leveldb_transaction,
517 const BlobJournalType& journal) {
518 const std::string leveldb_key = BlobJournalKey::Encode();
519 std::string data;
520 EncodeBlobJournal(journal, &data);
521 leveldb_transaction->Put(leveldb_key, &data);
522 }
523
524 static void UpdateLiveBlobJournalWithBlobList(
525 LevelDBTransaction* leveldb_transaction,
526 const BlobJournalType& journal) {
527 const std::string leveldb_key = LiveBlobJournalKey::Encode();
528 std::string data;
529 EncodeBlobJournal(journal, &data);
530 leveldb_transaction->Put(leveldb_key, &data);
531 }
532
533 static leveldb::Status MergeBlobsIntoLiveBlobJournal(
534 LevelDBTransaction* leveldb_transaction,
535 const BlobJournalType& journal) {
536 BlobJournalType old_journal;
537 const std::string key = LiveBlobJournalKey::Encode();
538 leveldb::Status s = GetBlobJournal(key, leveldb_transaction, &old_journal);
539 if (!s.ok())
540 return s;
541
542 old_journal.insert(old_journal.end(), journal.begin(), journal.end());
543
544 UpdateLiveBlobJournalWithBlobList(leveldb_transaction, old_journal);
545 return leveldb::Status::OK();
546 }
547
548 static void UpdateBlobJournalWithDatabase(
549 LevelDBDirectTransaction* leveldb_transaction,
550 int64 database_id) {
551 BlobJournalType journal;
552 journal.push_back(
553 std::make_pair(database_id, DatabaseMetaDataKey::kAllBlobsKey));
554 const std::string key = BlobJournalKey::Encode();
555 std::string data;
556 EncodeBlobJournal(journal, &data);
557 leveldb_transaction->Put(key, &data);
558 }
559
560 static leveldb::Status MergeDatabaseIntoLiveBlobJournal(
561 LevelDBDirectTransaction* leveldb_transaction,
562 int64 database_id) {
563 BlobJournalType journal;
564 const std::string key = LiveBlobJournalKey::Encode();
565 leveldb::Status s = GetBlobJournal(key, leveldb_transaction, &journal);
566 if (!s.ok())
567 return s;
568 journal.push_back(
569 std::make_pair(database_id, DatabaseMetaDataKey::kAllBlobsKey));
570 std::string data;
571 EncodeBlobJournal(journal, &data);
572 leveldb_transaction->Put(key, &data);
573 return leveldb::Status::OK();
574 }
575
477 IndexedDBBackingStore::IndexedDBBackingStore( 576 IndexedDBBackingStore::IndexedDBBackingStore(
478 IndexedDBFactory* indexed_db_factory, 577 IndexedDBFactory* indexed_db_factory,
479 const GURL& origin_url, 578 const GURL& origin_url,
480 const base::FilePath& blob_path, 579 const base::FilePath& blob_path,
481 net::URLRequestContext* request_context, 580 net::URLRequestContext* request_context,
482 scoped_ptr<LevelDBDatabase> db, 581 scoped_ptr<LevelDBDatabase> db,
483 scoped_ptr<LevelDBComparator> comparator, 582 scoped_ptr<LevelDBComparator> comparator,
484 base::TaskRunner* task_runner) 583 base::TaskRunner* task_runner)
485 : indexed_db_factory_(indexed_db_factory), 584 : indexed_db_factory_(indexed_db_factory),
486 origin_url_(origin_url), 585 origin_url_(origin_url),
(...skipping 604 matching lines...) Expand 10 before | Expand all | Expand 10 after
1091 s = it->Next()) 1190 s = it->Next())
1092 transaction->Remove(it->Key()); 1191 transaction->Remove(it->Key());
1093 if (!s.ok()) { 1192 if (!s.ok()) {
1094 INTERNAL_WRITE_ERROR_UNTESTED(DELETE_DATABASE); 1193 INTERNAL_WRITE_ERROR_UNTESTED(DELETE_DATABASE);
1095 return s; 1194 return s;
1096 } 1195 }
1097 1196
1098 const std::string key = DatabaseNameKey::Encode(origin_identifier_, name); 1197 const std::string key = DatabaseNameKey::Encode(origin_identifier_, name);
1099 transaction->Remove(key); 1198 transaction->Remove(key);
1100 1199
1200 // TODO(ericu): Put the real calls to the blob journal code here. For now,
1201 // I've inserted fake calls so that we don't get "you didn't use this static
1202 // function" compiler errors.
1203 if (false) {
1204 scoped_refptr<LevelDBTransaction> fake_transaction =
1205 new LevelDBTransaction(NULL);
1206 BlobJournalType fake_journal;
1207 MergeDatabaseIntoLiveBlobJournal(transaction.get(), metadata.id);
1208 UpdateBlobJournalWithDatabase(transaction.get(), metadata.id);
1209 MergeBlobsIntoLiveBlobJournal(fake_transaction.get(), fake_journal);
1210 }
1211
1101 s = transaction->Commit(); 1212 s = transaction->Commit();
1102 if (!s.ok()) { 1213 if (!s.ok()) {
1103 INTERNAL_WRITE_ERROR_UNTESTED(DELETE_DATABASE); 1214 INTERNAL_WRITE_ERROR_UNTESTED(DELETE_DATABASE);
1104 return s; 1215 return s;
1105 } 1216 }
1106 db_->Compact(start_key, stop_key); 1217 db_->Compact(start_key, stop_key);
1107 return s; 1218 return s;
1108 } 1219 }
1109 1220
1110 static bool CheckObjectStoreAndMetaDataType(const LevelDBIterator* it, 1221 static bool CheckObjectStoreAndMetaDataType(const LevelDBIterator* it,
(...skipping 816 matching lines...) Expand 10 before | Expand all | Expand 10 after
1927 FROM_HERE, 2038 FROM_HERE,
1928 base::Bind(&LocalWriteClosure::writeBlobToFileOnIOThread, 2039 base::Bind(&LocalWriteClosure::writeBlobToFileOnIOThread,
1929 write_closure.get(), 2040 write_closure.get(),
1930 path, 2041 path,
1931 descriptor.url(), 2042 descriptor.url(),
1932 request_context_)); 2043 request_context_));
1933 } 2044 }
1934 return true; 2045 return true;
1935 } 2046 }
1936 2047
2048 void IndexedDBBackingStore::ReportBlobUnused(int64 database_id,
2049 int64 blob_key) {
2050 DCHECK(KeyPrefix::IsValidDatabaseId(database_id));
2051 bool all_blobs = blob_key == DatabaseMetaDataKey::kAllBlobsKey;
2052 DCHECK(all_blobs || DatabaseMetaDataKey::IsValidBlobKey(blob_key));
2053 scoped_refptr<LevelDBTransaction> transaction =
2054 new LevelDBTransaction(db_.get());
2055
2056 std::string live_blob_key = LiveBlobJournalKey::Encode();
2057 BlobJournalType live_blob_journal;
2058 if (!GetBlobJournal(live_blob_key, transaction.get(), &live_blob_journal)
2059 .ok())
2060 return;
2061 DCHECK(live_blob_journal.size());
2062
2063 std::string primary_key = BlobJournalKey::Encode();
2064 BlobJournalType primary_journal;
2065 if (!GetBlobJournal(primary_key, transaction.get(), &primary_journal).ok())
2066 return;
2067
2068 // There are several cases to handle. If blob_key is kAllBlobsKey, we want to
2069 // remove all entries with database_id from the live_blob journal and add only
2070 // kAllBlobsKey to the primary journal. Otherwise if IsValidBlobKey(blob_key)
2071 // and we hit kAllBlobsKey for the right database_id in the journal, we leave
2072 // the kAllBlobsKey entry in the live_blob journal but add the specific blob
2073 // to the primary. Otherwise if IsValidBlobKey(blob_key) and we find a
2074 // matching (database_id, blob_key) tuple, we should move it to the primary
2075 // journal.
2076 BlobJournalType new_live_blob_journal;
2077 for (BlobJournalType::iterator journal_iter = live_blob_journal.begin();
2078 journal_iter != live_blob_journal.end();
2079 ++journal_iter) {
2080 int64 current_database_id = journal_iter->first;
2081 int64 current_blob_key = journal_iter->second;
2082 bool current_all_blobs =
2083 current_blob_key == DatabaseMetaDataKey::kAllBlobsKey;
2084 DCHECK(KeyPrefix::IsValidDatabaseId(current_database_id) ||
2085 current_all_blobs);
2086 if (current_database_id == database_id &&
2087 (all_blobs || current_all_blobs || blob_key == current_blob_key)) {
2088 if (!all_blobs) {
2089 primary_journal.push_back(
2090 std::make_pair(database_id, current_blob_key));
2091 if (current_all_blobs)
2092 new_live_blob_journal.push_back(*journal_iter);
2093 new_live_blob_journal.insert(new_live_blob_journal.end(),
2094 ++journal_iter,
2095 live_blob_journal.end()); // All the rest.
2096 break;
2097 }
2098 } else {
2099 new_live_blob_journal.push_back(*journal_iter);
2100 }
2101 }
2102 if (all_blobs) {
2103 primary_journal.push_back(
2104 std::make_pair(database_id, DatabaseMetaDataKey::kAllBlobsKey));
2105 }
2106 UpdatePrimaryJournalWithBlobList(transaction.get(), primary_journal);
2107 UpdateLiveBlobJournalWithBlobList(transaction.get(), new_live_blob_journal);
2108 transaction->Commit();
2109 // We could just do the deletions/cleaning here, but if there are a lot of
2110 // blobs about to be garbage collected, it'd be better to wait and do them all
2111 // at once.
2112 StartJournalCleaningTimer();
2113 }
2114
2115 // The this reference is a raw pointer that's declared Unretained inside the
2116 // timer code, so this won't confuse IndexedDBFactory's check for
2117 // HasLastBackingStoreReference. It's safe because if the backing store is
2118 // deleted, the timer will automatically be canceled on destruction.
2119 void IndexedDBBackingStore::StartJournalCleaningTimer() {
2120 journal_cleaning_timer_.Start(
2121 FROM_HERE,
2122 base::TimeDelta::FromSeconds(5),
2123 this,
2124 &IndexedDBBackingStore::CleanPrimaryJournalIgnoreReturn);
2125 }
2126
1937 // This assumes a file path of dbId/second-to-LSB-of-counter/counter. 2127 // This assumes a file path of dbId/second-to-LSB-of-counter/counter.
1938 FilePath IndexedDBBackingStore::GetBlobFileName(int64 database_id, int64 key) { 2128 FilePath IndexedDBBackingStore::GetBlobFileName(int64 database_id, int64 key) {
1939 return GetBlobFileNameForKey(blob_path_, database_id, key); 2129 return GetBlobFileNameForKey(blob_path_, database_id, key);
1940 } 2130 }
1941 2131
1942 static bool CheckIndexAndMetaDataKey(const LevelDBIterator* it, 2132 static bool CheckIndexAndMetaDataKey(const LevelDBIterator* it,
1943 const std::string& stop_key, 2133 const std::string& stop_key,
1944 int64 index_id, 2134 int64 index_id,
1945 unsigned char meta_data_type) { 2135 unsigned char meta_data_type) {
1946 if (!it->IsValid() || CompareKeys(it->Key(), stop_key) >= 0) 2136 if (!it->IsValid() || CompareKeys(it->Key(), stop_key) >= 0)
(...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after
2058 bool IndexedDBBackingStore::RemoveBlobFile(int64 database_id, int64 key) { 2248 bool IndexedDBBackingStore::RemoveBlobFile(int64 database_id, int64 key) {
2059 FilePath fileName = GetBlobFileName(database_id, key); 2249 FilePath fileName = GetBlobFileName(database_id, key);
2060 return base::DeleteFile(fileName, false); 2250 return base::DeleteFile(fileName, false);
2061 } 2251 }
2062 2252
2063 bool IndexedDBBackingStore::RemoveBlobDirectory(int64 database_id) { 2253 bool IndexedDBBackingStore::RemoveBlobDirectory(int64 database_id) {
2064 FilePath dirName = GetBlobDirectoryName(blob_path_, database_id); 2254 FilePath dirName = GetBlobDirectoryName(blob_path_, database_id);
2065 return base::DeleteFile(dirName, true); 2255 return base::DeleteFile(dirName, true);
2066 } 2256 }
2067 2257
2258 leveldb::Status IndexedDBBackingStore::CleanUpBlobJournal(
2259 const std::string& level_db_key) {
2260 scoped_refptr<LevelDBTransaction> journal_transaction =
2261 new LevelDBTransaction(db_.get());
2262 BlobJournalType journal;
2263 leveldb::Status s =
2264 GetBlobJournal(level_db_key, journal_transaction.get(), &journal);
2265 if (!s.ok())
2266 return s;
2267 if (!journal.size())
2268 return leveldb::Status::OK();
2269 BlobJournalType::iterator journal_iter;
2270 for (journal_iter = journal.begin(); journal_iter != journal.end();
2271 ++journal_iter) {
2272 int64 database_id = journal_iter->first;
2273 int64 blob_key = journal_iter->second;
2274 DCHECK(KeyPrefix::IsValidDatabaseId(database_id));
2275 if (blob_key == DatabaseMetaDataKey::kAllBlobsKey) {
2276 if (!RemoveBlobDirectory(database_id))
2277 return IOErrorStatus();
2278 } else {
2279 DCHECK(DatabaseMetaDataKey::IsValidBlobKey(blob_key));
2280 if (!RemoveBlobFile(database_id, blob_key))
2281 return IOErrorStatus();
2282 }
2283 }
2284 ClearBlobJournal(journal_transaction.get(), level_db_key);
2285 return journal_transaction->Commit();
2286 }
2287
2288 void IndexedDBBackingStore::CleanPrimaryJournalIgnoreReturn() {
2289 CleanUpBlobJournal(BlobJournalKey::Encode());
2290 }
2291
2068 WARN_UNUSED_RESULT static leveldb::Status SetMaxIndexId( 2292 WARN_UNUSED_RESULT static leveldb::Status SetMaxIndexId(
2069 LevelDBTransaction* transaction, 2293 LevelDBTransaction* transaction,
2070 int64 database_id, 2294 int64 database_id,
2071 int64 object_store_id, 2295 int64 object_store_id,
2072 int64 index_id) { 2296 int64 index_id) {
2073 int64 max_index_id = -1; 2297 int64 max_index_id = -1;
2074 const std::string max_index_id_key = ObjectStoreMetaDataKey::Encode( 2298 const std::string max_index_id_key = ObjectStoreMetaDataKey::Encode(
2075 database_id, object_store_id, ObjectStoreMetaDataKey::MAX_INDEX_ID); 2299 database_id, object_store_id, ObjectStoreMetaDataKey::MAX_INDEX_ID);
2076 bool found = false; 2300 bool found = false;
2077 leveldb::Status s = 2301 leveldb::Status s =
(...skipping 297 matching lines...) Expand 10 before | Expand all | Expand 10 after
2375 return InvalidDBKeyStatus(); 2599 return InvalidDBKeyStatus();
2376 } 2600 }
2377 2601
2378 StringPiece slice(found_encoded_primary_key); 2602 StringPiece slice(found_encoded_primary_key);
2379 if (DecodeIDBKey(&slice, found_primary_key) && slice.empty()) 2603 if (DecodeIDBKey(&slice, found_primary_key) && slice.empty())
2380 return s; 2604 return s;
2381 else 2605 else
2382 return InvalidDBKeyStatus(); 2606 return InvalidDBKeyStatus();
2383 } 2607 }
2384 2608
2385 void IndexedDBBackingStore::ReportBlobUnused(int64 database_id,
2386 int64 blob_key) {
2387 // TODO(ericu)
2388 }
2389
2390 IndexedDBBackingStore::Cursor::Cursor( 2609 IndexedDBBackingStore::Cursor::Cursor(
2391 const IndexedDBBackingStore::Cursor* other) 2610 const IndexedDBBackingStore::Cursor* other)
2392 : transaction_(other->transaction_), 2611 : transaction_(other->transaction_),
2393 cursor_options_(other->cursor_options_), 2612 cursor_options_(other->cursor_options_),
2394 current_key_(new IndexedDBKey(*other->current_key_)) { 2613 current_key_(new IndexedDBKey(*other->current_key_)) {
2395 if (other->iterator_) { 2614 if (other->iterator_) {
2396 iterator_ = transaction_->CreateIterator(); 2615 iterator_ = transaction_->CreateIterator();
2397 2616
2398 if (other->iterator_->IsValid()) { 2617 if (other->iterator_->IsValid()) {
2399 leveldb::Status s = iterator_->Seek(other->iterator_->Key()); 2618 leveldb::Status s = iterator_->Seek(other->iterator_->Key());
(...skipping 914 matching lines...) Expand 10 before | Expand all | Expand 10 after
3314 const GURL& url, 3533 const GURL& url,
3315 int64_t key) 3534 int64_t key)
3316 : is_file_(false), url_(url), key_(key) {} 3535 : is_file_(false), url_(url), key_(key) {}
3317 3536
3318 IndexedDBBackingStore::Transaction::WriteDescriptor::WriteDescriptor( 3537 IndexedDBBackingStore::Transaction::WriteDescriptor::WriteDescriptor(
3319 const FilePath& file_path, 3538 const FilePath& file_path,
3320 int64_t key) 3539 int64_t key)
3321 : is_file_(true), file_path_(file_path), key_(key) {} 3540 : is_file_(true), file_path_(file_path), key_(key) {}
3322 3541
3323 } // namespace content 3542 } // namespace content
OLDNEW
« no previous file with comments | « content/browser/indexed_db/indexed_db_backing_store.h ('k') | content/browser/indexed_db/indexed_db_leveldb_coding.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698