Commit ff82a486 authored by Chase Phillips's avatar Chase Phillips Committed by Commit Bot

IndexedDB: Add "Force schema downgrade" option on chrome://indexeddb-internals/

As part of on-going cleanup to dispatch an event on corruption
detection, add a "Force schema downgrade" option on an internal
page so that web developers can trigger corruption in a way
that will allow them to test and verify their apps correctly
handle the event.

Bug: 876787
Change-Id: If03d47ce8ec3bc930e9a5a8a7e04835116cf4a36
Reviewed-on: https://chromium-review.googlesource.com/1183586
Commit-Queue: Chase Phillips <cmp@chromium.org>
Reviewed-by: default avatarVictor Costan <pwnall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#586434}
parent 1a3c4bba
...@@ -687,6 +687,19 @@ Status IndexedDBBackingStore::AnyDatabaseContainsBlobs( ...@@ -687,6 +687,19 @@ Status IndexedDBBackingStore::AnyDatabaseContainsBlobs(
return Status::OK(); return Status::OK();
} }
Status IndexedDBBackingStore::RevertSchemaToV2() {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
const std::string schema_version_key = SchemaVersionKey::Encode();
scoped_refptr<LevelDBTransaction> transaction =
IndexedDBClassFactory::Get()->CreateLevelDBTransaction(db_.get());
PutInt(transaction.get(), schema_version_key, 2);
Status s = transaction->Commit();
if (!s.ok())
INTERNAL_WRITE_ERROR_UNTESTED(REVERT_SCHEMA_TO_V2);
return s;
}
WARN_UNUSED_RESULT Status IndexedDBBackingStore::SetUpMetadata() { WARN_UNUSED_RESULT Status IndexedDBBackingStore::SetUpMetadata() {
DCHECK(task_runner_->RunsTasksInCurrentSequence()); DCHECK(task_runner_->RunsTasksInCurrentSequence());
......
...@@ -577,6 +577,11 @@ class CONTENT_EXPORT IndexedDBBackingStore ...@@ -577,6 +577,11 @@ class CONTENT_EXPORT IndexedDBBackingStore
// Stops the journal_cleaning_timer_ and runs its pending task. // Stops the journal_cleaning_timer_ and runs its pending task.
void ForceRunBlobCleanup(); void ForceRunBlobCleanup();
// RevertSchemaToV2() updates a backing store state on disk to override its
// metadata version to 2. This allows triggering https://crbug.com/829141 on
// an otherwise healthy backing store.
leveldb::Status RevertSchemaToV2();
protected: protected:
friend class base::RefCounted<IndexedDBBackingStore>; friend class base::RefCounted<IndexedDBBackingStore>;
......
...@@ -405,6 +405,17 @@ void IndexedDBContextImpl::ForceClose(const Origin origin, ...@@ -405,6 +405,17 @@ void IndexedDBContextImpl::ForceClose(const Origin origin,
DCHECK_EQ(0UL, GetConnectionCount(origin)); DCHECK_EQ(0UL, GetConnectionCount(origin));
} }
void IndexedDBContextImpl::ForceSchemaDowngrade(const Origin& origin) {
DCHECK(TaskRunner()->RunsTasksInCurrentSequence());
if (data_path_.empty() || !HasOrigin(origin))
return;
if (factory_.get())
factory_->ForceSchemaDowngrade(origin);
DCHECK_EQ(0UL, GetConnectionCount(origin));
}
size_t IndexedDBContextImpl::GetConnectionCount(const Origin& origin) { size_t IndexedDBContextImpl::GetConnectionCount(const Origin& origin) {
DCHECK(TaskRunner()->RunsTasksInCurrentSequence()); DCHECK(TaskRunner()->RunsTasksInCurrentSequence());
if (!HasOrigin(origin)) if (!HasOrigin(origin))
......
...@@ -47,6 +47,7 @@ class CONTENT_EXPORT IndexedDBContextImpl : public IndexedDBContext { ...@@ -47,6 +47,7 @@ class CONTENT_EXPORT IndexedDBContextImpl : public IndexedDBContext {
FORCE_CLOSE_BACKING_STORE_FAILURE, FORCE_CLOSE_BACKING_STORE_FAILURE,
FORCE_CLOSE_INTERNALS_PAGE, FORCE_CLOSE_INTERNALS_PAGE,
FORCE_CLOSE_COPY_ORIGIN, FORCE_CLOSE_COPY_ORIGIN,
FORCE_SCHEMA_DOWNGRADE_INTERNALS_PAGE,
// Append new values here and update IDBContextForcedCloseReason in // Append new values here and update IDBContextForcedCloseReason in
// enums.xml. // enums.xml.
FORCE_CLOSE_REASON_MAX FORCE_CLOSE_REASON_MAX
...@@ -122,6 +123,7 @@ class CONTENT_EXPORT IndexedDBContextImpl : public IndexedDBContext { ...@@ -122,6 +123,7 @@ class CONTENT_EXPORT IndexedDBContextImpl : public IndexedDBContext {
// ForceClose takes a value rather than a reference since it may release the // ForceClose takes a value rather than a reference since it may release the
// owning object. // owning object.
void ForceClose(const url::Origin origin, ForceCloseReason reason); void ForceClose(const url::Origin origin, ForceCloseReason reason);
void ForceSchemaDowngrade(const url::Origin& origin);
// GetStoragePaths returns all paths owned by this database, in arbitrary // GetStoragePaths returns all paths owned by this database, in arbitrary
// order. // order.
std::vector<base::FilePath> GetStoragePaths(const url::Origin& origin) const; std::vector<base::FilePath> GetStoragePaths(const url::Origin& origin) const;
......
...@@ -79,6 +79,7 @@ class CONTENT_EXPORT IndexedDBFactory ...@@ -79,6 +79,7 @@ class CONTENT_EXPORT IndexedDBFactory
const url::Origin& origin) const = 0; const url::Origin& origin) const = 0;
virtual void ForceClose(const url::Origin& origin) = 0; virtual void ForceClose(const url::Origin& origin) = 0;
virtual void ForceSchemaDowngrade(const url::Origin& origin) = 0;
// Called by the IndexedDBContext destructor so the factory can do cleanup. // Called by the IndexedDBContext destructor so the factory can do cleanup.
virtual void ContextDestroyed() = 0; virtual void ContextDestroyed() = 0;
......
...@@ -338,6 +338,17 @@ void IndexedDBFactoryImpl::ForceClose(const Origin& origin) { ...@@ -338,6 +338,17 @@ void IndexedDBFactoryImpl::ForceClose(const Origin& origin) {
ReleaseBackingStore(origin, true /* immediate */); ReleaseBackingStore(origin, true /* immediate */);
} }
void IndexedDBFactoryImpl::ForceSchemaDowngrade(const Origin& origin) {
OriginDBs range = GetOpenDatabasesForOrigin(origin);
while (range.first != range.second) {
IndexedDBDatabase* db = range.first->second;
++range.first;
leveldb::Status s = db->backing_store()->RevertSchemaToV2();
DLOG_IF(ERROR, !s.ok()) << "Unable to force downgrade: " << s.ToString();
}
}
void IndexedDBFactoryImpl::ContextDestroyed() { void IndexedDBFactoryImpl::ContextDestroyed() {
// Timers on backing stores hold a reference to this factory. When the // Timers on backing stores hold a reference to this factory. When the
// context (which nominally owns this factory) is destroyed during thread // context (which nominally owns this factory) is destroyed during thread
......
...@@ -87,6 +87,7 @@ class CONTENT_EXPORT IndexedDBFactoryImpl : public IndexedDBFactory { ...@@ -87,6 +87,7 @@ class CONTENT_EXPORT IndexedDBFactoryImpl : public IndexedDBFactory {
OriginDBs GetOpenDatabasesForOrigin(const url::Origin& origin) const override; OriginDBs GetOpenDatabasesForOrigin(const url::Origin& origin) const override;
void ForceClose(const url::Origin& origin) override; void ForceClose(const url::Origin& origin) override;
void ForceSchemaDowngrade(const url::Origin& origin) override;
// Called by the IndexedDBContext destructor so the factory can do cleanup. // Called by the IndexedDBContext destructor so the factory can do cleanup.
void ContextDestroyed() override; void ContextDestroyed() override;
......
...@@ -62,6 +62,10 @@ IndexedDBInternalsUI::IndexedDBInternalsUI(WebUI* web_ui) ...@@ -62,6 +62,10 @@ IndexedDBInternalsUI::IndexedDBInternalsUI(WebUI* web_ui)
web_ui->RegisterMessageCallback( web_ui->RegisterMessageCallback(
"forceClose", base::BindRepeating(&IndexedDBInternalsUI::ForceCloseOrigin, "forceClose", base::BindRepeating(&IndexedDBInternalsUI::ForceCloseOrigin,
base::Unretained(this))); base::Unretained(this)));
web_ui->RegisterMessageCallback(
"forceSchemaDowngrade",
base::BindRepeating(&IndexedDBInternalsUI::ForceSchemaDowngradeOrigin,
base::Unretained(this)));
WebUIDataSource* source = WebUIDataSource* source =
WebUIDataSource::Create(kChromeUIIndexedDBInternalsHost); WebUIDataSource::Create(kChromeUIIndexedDBInternalsHost);
...@@ -210,6 +214,23 @@ void IndexedDBInternalsUI::ForceCloseOrigin(const base::ListValue* args) { ...@@ -210,6 +214,23 @@ void IndexedDBInternalsUI::ForceCloseOrigin(const base::ListValue* args) {
base::Unretained(this), partition_path, context, origin)); base::Unretained(this), partition_path, context, origin));
} }
void IndexedDBInternalsUI::ForceSchemaDowngradeOrigin(
const base::ListValue* args) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
base::FilePath partition_path;
Origin origin;
scoped_refptr<IndexedDBContextImpl> context;
if (!GetOriginData(args, &partition_path, &origin, &context))
return;
context->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
&IndexedDBInternalsUI::ForceSchemaDowngradeOriginOnIndexedDBThread,
base::Unretained(this), partition_path, context, origin));
}
void IndexedDBInternalsUI::DownloadOriginDataOnIndexedDBThread( void IndexedDBInternalsUI::DownloadOriginDataOnIndexedDBThread(
const base::FilePath& partition_path, const base::FilePath& partition_path,
const scoped_refptr<IndexedDBContextImpl> context, const scoped_refptr<IndexedDBContextImpl> context,
...@@ -260,10 +281,33 @@ void IndexedDBInternalsUI::ForceCloseOriginOnIndexedDBThread( ...@@ -260,10 +281,33 @@ void IndexedDBInternalsUI::ForceCloseOriginOnIndexedDBThread(
context->ForceClose(origin, IndexedDBContextImpl::FORCE_CLOSE_INTERNALS_PAGE); context->ForceClose(origin, IndexedDBContextImpl::FORCE_CLOSE_INTERNALS_PAGE);
size_t connection_count = context->GetConnectionCount(origin); size_t connection_count = context->GetConnectionCount(origin);
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, BrowserThread::PostTask(
base::BindOnce(&IndexedDBInternalsUI::OnForcedClose, BrowserThread::UI, FROM_HERE,
base::Unretained(this), partition_path, base::BindOnce(&IndexedDBInternalsUI::OnForcedSchemaDowngrade,
origin, connection_count)); base::Unretained(this), partition_path, origin,
connection_count));
}
void IndexedDBInternalsUI::ForceSchemaDowngradeOriginOnIndexedDBThread(
const base::FilePath& partition_path,
const scoped_refptr<IndexedDBContextImpl> context,
const Origin& origin) {
DCHECK(context->TaskRunner()->RunsTasksInCurrentSequence());
// Make sure the database hasn't been deleted since the page was loaded.
if (!context->HasOrigin(origin))
return;
context->ForceSchemaDowngrade(origin);
context->ForceClose(
origin, IndexedDBContextImpl::FORCE_SCHEMA_DOWNGRADE_INTERNALS_PAGE);
size_t connection_count = context->GetConnectionCount(origin);
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&IndexedDBInternalsUI::OnForcedSchemaDowngrade,
base::Unretained(this), partition_path, origin,
connection_count));
} }
void IndexedDBInternalsUI::OnForcedClose(const base::FilePath& partition_path, void IndexedDBInternalsUI::OnForcedClose(const base::FilePath& partition_path,
...@@ -275,6 +319,16 @@ void IndexedDBInternalsUI::OnForcedClose(const base::FilePath& partition_path, ...@@ -275,6 +319,16 @@ void IndexedDBInternalsUI::OnForcedClose(const base::FilePath& partition_path,
base::Value(static_cast<double>(connection_count))); base::Value(static_cast<double>(connection_count)));
} }
void IndexedDBInternalsUI::OnForcedSchemaDowngrade(
const base::FilePath& partition_path,
const Origin& origin,
size_t connection_count) {
web_ui()->CallJavascriptFunctionUnsafe(
"indexeddb.onForcedSchemaDowngrade", base::Value(partition_path.value()),
base::Value(origin.Serialize()),
base::Value(static_cast<double>(connection_count)));
}
void IndexedDBInternalsUI::OnDownloadDataReady( void IndexedDBInternalsUI::OnDownloadDataReady(
const base::FilePath& partition_path, const base::FilePath& partition_path,
const Origin& origin, const Origin& origin,
......
...@@ -73,6 +73,16 @@ class IndexedDBInternalsUI : public WebUIController { ...@@ -73,6 +73,16 @@ class IndexedDBInternalsUI : public WebUIController {
void OnForcedClose(const base::FilePath& partition_path, void OnForcedClose(const base::FilePath& partition_path,
const url::Origin& origin, const url::Origin& origin,
size_t connection_count); size_t connection_count);
void ForceSchemaDowngradeOrigin(const base::ListValue* args);
void ForceSchemaDowngradeOriginOnIndexedDBThread(
const base::FilePath& partition_path,
const scoped_refptr<IndexedDBContextImpl> context,
const url::Origin& origin);
void OnForcedSchemaDowngrade(const base::FilePath& partition_path,
const url::Origin& origin,
size_t connection_count);
bool GetOriginContext(const base::FilePath& path, bool GetOriginContext(const base::FilePath& path,
const url::Origin& origin, const url::Origin& origin,
scoped_refptr<IndexedDBContextImpl>* context); scoped_refptr<IndexedDBContextImpl>* context);
......
...@@ -47,6 +47,7 @@ enum IndexedDBBackingStoreErrorSource { ...@@ -47,6 +47,7 @@ enum IndexedDBBackingStoreErrorSource {
GET_BLOB_KEY_GENERATOR_CURRENT_NUMBER = 28, GET_BLOB_KEY_GENERATOR_CURRENT_NUMBER = 28,
GET_BLOB_INFO_FOR_RECORD = 29, GET_BLOB_INFO_FOR_RECORD = 29,
UPGRADING_SCHEMA_CORRUPTED_BLOBS = 30, UPGRADING_SCHEMA_CORRUPTED_BLOBS = 30,
REVERT_SCHEMA_TO_V2 = 31,
INTERNAL_ERROR_MAX, INTERNAL_ERROR_MAX,
}; };
......
...@@ -78,6 +78,7 @@ class MockIndexedDBFactory : public IndexedDBFactory { ...@@ -78,6 +78,7 @@ class MockIndexedDBFactory : public IndexedDBFactory {
// deal with std::pair's. This means we can't use GoogleMock for this method // deal with std::pair's. This means we can't use GoogleMock for this method
OriginDBs GetOpenDatabasesForOrigin(const url::Origin& origin) const override; OriginDBs GetOpenDatabasesForOrigin(const url::Origin& origin) const override;
MOCK_METHOD1(ForceClose, void(const url::Origin& origin)); MOCK_METHOD1(ForceClose, void(const url::Origin& origin));
MOCK_METHOD1(ForceSchemaDowngrade, void(const url::Origin& origin));
MOCK_METHOD0(ContextDestroyed, void()); MOCK_METHOD0(ContextDestroyed, void());
MOCK_METHOD1(DatabaseDeleted, MOCK_METHOD1(DatabaseDeleted,
void(const IndexedDBDatabase::Identifier& identifier)); void(const IndexedDBDatabase::Identifier& identifier));
......
...@@ -48,6 +48,10 @@ ...@@ -48,6 +48,10 @@
jsvalues=".idb_origin_url:url;.idb_partition_path:$partition_path">Force close</a> jsvalues=".idb_origin_url:url;.idb_partition_path:$partition_path">Force close</a>
<a href="#" class="download" <a href="#" class="download"
jsvalues=".idb_origin_url:url;.idb_partition_path:$partition_path">Download</a> jsvalues=".idb_origin_url:url;.idb_partition_path:$partition_path">Download</a>
<a href="#" class="force-schema-downgrade"
jsvalues=".idb_origin_url:url;.idb_partition_path:$partition_path">Force schema downgrade</a>
<a href="https://crbug.com/829141"
target="_blank">?</a>
<span class="download-status" style="display: none">Loading...</span> <span class="download-status" style="display: none">Loading...</span>
</div> </div>
<div class="indexeddb-database" jsselect="$this.databases"> <div class="indexeddb-database" jsselect="$this.databases">
......
...@@ -29,6 +29,14 @@ cr.define('indexeddb', function() { ...@@ -29,6 +29,14 @@ cr.define('indexeddb', function() {
return false; return false;
} }
function forceSchemaDowngrade(event) {
var link = event.target;
progressNodeFor(link).style.display = 'inline';
chrome.send('forceSchemaDowngrade', [link.idb_partition_path,
link.idb_origin_url]);
return false;
}
function withNode(selector, partition_path, origin_url, callback) { function withNode(selector, partition_path, origin_url, callback) {
var links = document.querySelectorAll(selector); var links = document.querySelectorAll(selector);
for (var i = 0; i < links.length; ++i) { for (var i = 0; i < links.length; ++i) {
...@@ -59,6 +67,18 @@ cr.define('indexeddb', function() { ...@@ -59,6 +67,18 @@ cr.define('indexeddb', function() {
}); });
} }
function onForcedSchemaDowngrade(partition_path,
origin_url,
connection_count) {
withNode('a.force-schema-downgrade', partition_path, origin_url,
function(link) {
progressNodeFor(link).style.display = 'none';
});
withNode('.connection-count', partition_path, origin_url, function(span) {
span.innerText = connection_count;
});
}
// Fired from the backend with a single partition's worth of // Fired from the backend with a single partition's worth of
// IndexedDB metadata. // IndexedDB metadata.
function onOriginsReady(origins, partition_path) { function onOriginsReady(origins, partition_path) {
...@@ -76,11 +96,18 @@ cr.define('indexeddb', function() { ...@@ -76,11 +96,18 @@ cr.define('indexeddb', function() {
for (i = 0; i < forceCloseLinks.length; ++i) { for (i = 0; i < forceCloseLinks.length; ++i) {
forceCloseLinks[i].addEventListener('click', forceClose, false); forceCloseLinks[i].addEventListener('click', forceClose, false);
} }
var forceSchemaDowngradeLinks =
container.querySelectorAll('a.force-schema-downgrade');
for (i = 0; i < forceSchemaDowngradeLinks.length; ++i) {
forceSchemaDowngradeLinks[i].addEventListener(
'click', forceSchemaDowngrade, false);
}
} }
return { return {
initialize: initialize, initialize: initialize,
onForcedClose: onForcedClose, onForcedClose: onForcedClose,
onForcedSchemaDowngrade: onForcedSchemaDowngrade,
onOriginDownloadReady: onOriginDownloadReady, onOriginDownloadReady: onOriginDownloadReady,
onOriginsReady: onOriginsReady, onOriginsReady: onOriginsReady,
}; };
......
...@@ -24488,6 +24488,9 @@ Called by update_gpu_driver_bug_workaround_entries.py.--> ...@@ -24488,6 +24488,9 @@ Called by update_gpu_driver_bug_workaround_entries.py.-->
<int value="3" label="CopyOrigin"> <int value="3" label="CopyOrigin">
The database is force closed so that it can be copied. The database is force closed so that it can be copied.
</int> </int>
<int value="4" label="ForceSchemaDowngradeInternalsPage">
A forced schema downgrade was requested from the indexeddb-internals page.
</int>
</enum> </enum>
<enum name="IDBException"> <enum name="IDBException">
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment