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(
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() {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
......
......@@ -577,6 +577,11 @@ class CONTENT_EXPORT IndexedDBBackingStore
// Stops the journal_cleaning_timer_ and runs its pending task.
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:
friend class base::RefCounted<IndexedDBBackingStore>;
......
......@@ -405,6 +405,17 @@ void IndexedDBContextImpl::ForceClose(const Origin 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) {
DCHECK(TaskRunner()->RunsTasksInCurrentSequence());
if (!HasOrigin(origin))
......
......@@ -47,6 +47,7 @@ class CONTENT_EXPORT IndexedDBContextImpl : public IndexedDBContext {
FORCE_CLOSE_BACKING_STORE_FAILURE,
FORCE_CLOSE_INTERNALS_PAGE,
FORCE_CLOSE_COPY_ORIGIN,
FORCE_SCHEMA_DOWNGRADE_INTERNALS_PAGE,
// Append new values here and update IDBContextForcedCloseReason in
// enums.xml.
FORCE_CLOSE_REASON_MAX
......@@ -122,6 +123,7 @@ class CONTENT_EXPORT IndexedDBContextImpl : public IndexedDBContext {
// ForceClose takes a value rather than a reference since it may release the
// owning object.
void ForceClose(const url::Origin origin, ForceCloseReason reason);
void ForceSchemaDowngrade(const url::Origin& origin);
// GetStoragePaths returns all paths owned by this database, in arbitrary
// order.
std::vector<base::FilePath> GetStoragePaths(const url::Origin& origin) const;
......
......@@ -79,6 +79,7 @@ class CONTENT_EXPORT IndexedDBFactory
const url::Origin& origin) const = 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.
virtual void ContextDestroyed() = 0;
......
......@@ -338,6 +338,17 @@ void IndexedDBFactoryImpl::ForceClose(const Origin& origin) {
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() {
// Timers on backing stores hold a reference to this factory. When the
// context (which nominally owns this factory) is destroyed during thread
......
......@@ -87,6 +87,7 @@ class CONTENT_EXPORT IndexedDBFactoryImpl : public IndexedDBFactory {
OriginDBs GetOpenDatabasesForOrigin(const url::Origin& origin) const 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.
void ContextDestroyed() override;
......
......@@ -62,6 +62,10 @@ IndexedDBInternalsUI::IndexedDBInternalsUI(WebUI* web_ui)
web_ui->RegisterMessageCallback(
"forceClose", base::BindRepeating(&IndexedDBInternalsUI::ForceCloseOrigin,
base::Unretained(this)));
web_ui->RegisterMessageCallback(
"forceSchemaDowngrade",
base::BindRepeating(&IndexedDBInternalsUI::ForceSchemaDowngradeOrigin,
base::Unretained(this)));
WebUIDataSource* source =
WebUIDataSource::Create(kChromeUIIndexedDBInternalsHost);
......@@ -210,6 +214,23 @@ void IndexedDBInternalsUI::ForceCloseOrigin(const base::ListValue* args) {
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(
const base::FilePath& partition_path,
const scoped_refptr<IndexedDBContextImpl> context,
......@@ -260,10 +281,33 @@ void IndexedDBInternalsUI::ForceCloseOriginOnIndexedDBThread(
context->ForceClose(origin, IndexedDBContextImpl::FORCE_CLOSE_INTERNALS_PAGE);
size_t connection_count = context->GetConnectionCount(origin);
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::BindOnce(&IndexedDBInternalsUI::OnForcedClose,
base::Unretained(this), partition_path,
origin, connection_count));
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&IndexedDBInternalsUI::OnForcedSchemaDowngrade,
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,
......@@ -275,6 +319,16 @@ void IndexedDBInternalsUI::OnForcedClose(const base::FilePath& partition_path,
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(
const base::FilePath& partition_path,
const Origin& origin,
......
......@@ -73,6 +73,16 @@ class IndexedDBInternalsUI : public WebUIController {
void OnForcedClose(const base::FilePath& partition_path,
const url::Origin& origin,
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,
const url::Origin& origin,
scoped_refptr<IndexedDBContextImpl>* context);
......
......@@ -47,6 +47,7 @@ enum IndexedDBBackingStoreErrorSource {
GET_BLOB_KEY_GENERATOR_CURRENT_NUMBER = 28,
GET_BLOB_INFO_FOR_RECORD = 29,
UPGRADING_SCHEMA_CORRUPTED_BLOBS = 30,
REVERT_SCHEMA_TO_V2 = 31,
INTERNAL_ERROR_MAX,
};
......
......@@ -78,6 +78,7 @@ class MockIndexedDBFactory : public IndexedDBFactory {
// deal with std::pair's. This means we can't use GoogleMock for this method
OriginDBs GetOpenDatabasesForOrigin(const url::Origin& origin) const override;
MOCK_METHOD1(ForceClose, void(const url::Origin& origin));
MOCK_METHOD1(ForceSchemaDowngrade, void(const url::Origin& origin));
MOCK_METHOD0(ContextDestroyed, void());
MOCK_METHOD1(DatabaseDeleted,
void(const IndexedDBDatabase::Identifier& identifier));
......
......@@ -48,6 +48,10 @@
jsvalues=".idb_origin_url:url;.idb_partition_path:$partition_path">Force close</a>
<a href="#" class="download"
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>
</div>
<div class="indexeddb-database" jsselect="$this.databases">
......
......@@ -29,6 +29,14 @@ cr.define('indexeddb', function() {
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) {
var links = document.querySelectorAll(selector);
for (var i = 0; i < links.length; ++i) {
......@@ -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
// IndexedDB metadata.
function onOriginsReady(origins, partition_path) {
......@@ -76,11 +96,18 @@ cr.define('indexeddb', function() {
for (i = 0; i < forceCloseLinks.length; ++i) {
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 {
initialize: initialize,
onForcedClose: onForcedClose,
onForcedSchemaDowngrade: onForcedSchemaDowngrade,
onOriginDownloadReady: onOriginDownloadReady,
onOriginsReady: onOriginsReady,
};
......
......@@ -24488,6 +24488,9 @@ Called by update_gpu_driver_bug_workaround_entries.py.-->
<int value="3" label="CopyOrigin">
The database is force closed so that it can be copied.
</int>
<int value="4" label="ForceSchemaDowngradeInternalsPage">
A forced schema downgrade was requested from the indexeddb-internals page.
</int>
</enum>
<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