Commit 884a9b66 authored by Chase Phillips's avatar Chase Phillips Committed by Commit Bot

IndexedDB: Add stable DB compatibility tests

Verify that the current version of IndexedDB can read an IndexedDB
instance created by a previous version of Chrome stable.

These DBs were created with Chrome stable version 70.0.3538.77
(Official Build) (64-bit) on Linux.  The webapp used to create the
instances was provided in https://crbug.com/899446 by Victor Costan
<pwnall@chromium.org>.  The noai version is modified from that case
to disable autoincrement.

Bug: 717812
Bug: 899446
Change-Id: I0b293f68cde4d637c53faaabd52b3b27a0ac147c
Reviewed-on: https://chromium-review.googlesource.com/c/1320693Reviewed-by: default avatarDaniel Murphy <dmurph@chromium.org>
Commit-Queue: Chase Phillips <cmp@chromium.org>
Cr-Commit-Position: refs/heads/master@{#605908}
parent ec08175d
...@@ -447,6 +447,39 @@ IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithMissingSSTFile, ...@@ -447,6 +447,39 @@ IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithMissingSSTFile,
EXPECT_NE(original_size, new_size); EXPECT_NE(original_size, new_size);
} }
// IndexedDBBrowserTestWithCrbug899446* capture IDB instances from Chrome stable
// to verify that the current code can read those instances. For more info on
// a case when Chrome canary couldn't read stable's IDB instances, see
// https://crbug.com/899446.
class IndexedDBBrowserTestWithCrbug899446
: public IndexedDBBrowserTestWithPreexistingLevelDB {
std::string EnclosingLevelDBDir() override { return "crbug899446"; }
};
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithCrbug899446, StableTest) {
int64_t original_size = RequestUsage(kFileOrigin);
EXPECT_GT(original_size, 0);
SimpleTest(GetTestUrl("indexeddb", "crbug899446.html"));
int64_t new_size = RequestUsage(kFileOrigin);
EXPECT_GT(new_size, 0);
EXPECT_NE(original_size, new_size);
}
class IndexedDBBrowserTestWithCrbug899446Noai
: public IndexedDBBrowserTestWithPreexistingLevelDB {
std::string EnclosingLevelDBDir() override { return "crbug899446_noai"; }
};
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithCrbug899446Noai, StableTest) {
int64_t original_size = RequestUsage(kFileOrigin);
EXPECT_GT(original_size, 0);
SimpleTest(GetTestUrl("indexeddb", "crbug899446_noai.html"));
int64_t new_size = RequestUsage(kFileOrigin);
EXPECT_GT(new_size, 0);
EXPECT_NE(original_size, new_size);
}
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, LevelDBLogFileTest) { IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, LevelDBLogFileTest) {
// Any page that opens an IndexedDB will work here. // Any page that opens an IndexedDB will work here.
SimpleTest(GetTestUrl("indexeddb", "database_test.html")); SimpleTest(GetTestUrl("indexeddb", "database_test.html"));
......
<html>
<head>
<title>IndexedDB crbug899446</title>
<script type="text/javascript" src="common.js"></script>
<script type="text/javascript" src="crbug899446.js"></script>
<script>
async function test() {
const db = await Database.open('crbug899446');
if (!db) {
fail("db failed to open");
return;
}
try {
if (!await db.read()) {
fail("db.read() failed");
return;
}
} catch (e) {
fail(e);
return;
}
done();
}
</script>
</head>
<body onLoad="test()">
<div id="status">Starting...</div>
</body>
</html>
// Copyright (c) 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
class DatabaseUtils {
/** Wrap an IndexedDB request into a Promise.
*
* This should not be used for open().
*
* @param {IDBRequest} request the request to be wrapped
* @returns {Promise<Object>} promise that resolves with the request's result,
* or rejects with an error
*/
static async promiseForRequest(request) {
return new Promise((resolve, reject) => {
request.onsuccess = event => { resolve(event.target.result); };
request.onerror = event => { reject(event.target.error); };
request.onblocked = event => {
reject(event.target.error ||
(new Error("blocked by other database connections")));
};
request.onupgradeneeded = event => {
reject(event.target.error ||
(new Error("unexpected upgradeneeded event")));
};
});
}
/** Wrap an IndexedDB database open request into a Promise.
*
* This is intended to be used by open().
*
* @param {IDBOpenDBRequest} request the request to be wrapped
* @returns {Promise<{database: idbDatabase, transaction: IDBTransaction?}>}
* promise that resolves with an object whose "database" property is the
* newly opened database; if an upgradeneeded event is received, the
* "transaction" property holds the upgrade transaction
*/
static async promiseForOpenRequest(request) {
return new Promise((resolve, reject) => {
request.onsuccess = event => {
resolve({ database: event.target.result, transaction: null });
};
request.onerror = event => { reject(event.target.error); };
request.onblocked = event => {
reject(event.target.error ||
(new Error("blocked by other database connections")));
};
request.onupgradeneeded = event => {
resolve({
database: event.target.result,
transaction: event.target.transaction
});
};
});
}
/** Wrap an IndexedDB transaction into a Promise.
*
* @param {IDBTransaction} transaction the transaction to be wrapped
* @returns {Promise<Object>} promise that resolves with undefined when the
* transaction is completed, or rejects with an error if the transaction
* is aborted or errors out
*/
static async promiseForTransaction(transaction) {
return new Promise((resolve, reject) => {
transaction.oncomplete = () => { resolve(); };
transaction.onabort = event => { reject(event.target.error); };
transaction.onerror = event => { reject(event.target.error); };
});
}
}
class Database {
/** Open a database.
*
* @param {string} dbName the name of the database to be opened
* @return {Promise<Database>} promise that resolves with a new Database
* instance for the open database
*/
static async open(dbName) {
const request = indexedDB.open(dbName, 1);
const result = await DatabaseUtils.promiseForOpenRequest(request);
if (result.transaction !== null) {
fail("expected db to exist");
return;
}
return new Database(dbName, result.database);
}
/** Do not instantiate directly. Use Database.open() instead.
*
* @param {string} dbName the database's name
* @param {IDBDatabase} idbDatabase the IndexedDB instance wrapped by this
*/
constructor(dbName, idbDatabase) {
this._dbName = dbName;
this._idbDatabase = idbDatabase;
}
/** Closes the underlying database. All future operations will fail. */
close() {
this._idbDatabase.close();
}
/** Reads from a store by iterating a cursor.
*
* @param {string} storeName the name of the store being read
* @param {{index?: string, range?: IDBKeyRange}} query narrows down the data
* being read
* @param {function(IDBCursor): boolean} cursorCallback called for each cursor
* yielded by the iteration; must return a truthy value to continue
* iteration, or a falsey value to stop iterating
*/
async iterateCursor(storeName, query, cursorCallback) {
const transaction = this._idbDatabase.transaction([storeName], 'readonly');
const transactionPromise = DatabaseUtils.promiseForTransaction(transaction);
const objectStore = transaction.objectStore(storeName);
const dataSource = ('index' in query) ? objectStore.index(query.index)
: objectStore;
const request = ('range' in query) ? dataSource.openCursor(query.range)
: dataSource.openCursor();
while (true) {
const cursor = await DatabaseUtils.promiseForRequest(request);
if (!cursor)
break; // The iteration completed.
const willContinue = cursorCallback(cursor);
if (!willContinue)
break;
cursor.continue();
}
await transactionPromise;
return true;
}
async read() {
const status = document.getElementById('status');
status.textContent = 'Started reading items';
let i = 0;
const result = await this.iterateCursor('store', {}, cursor => {
i += 1;
if (cursor.primaryKey !== i) {
status.textContent =
`Incorrect primaryKey - wanted ${i} got ${cursor.primaryKey}`;
return false;
}
if (cursor.key !== i) {
status.textContent = `Incorrect key - wanted ${i} got ${cursor.key}`;
return false;
}
if (cursor.value.id !== i) {
status.textContent =
`Incorrect value.id - wanted ${i} got ${cursor.key}`;
return false;
}
return true;
});
if (!result) {
status.textContent = `Failed to read items`;
return false;
}
status.textContent = `Done reading ${i} items`;
return true;
}
};
2018/11/05-18:36:30.850 27021 Reusing MANIFEST /usr/local/google/home/cmp/co/chromium-latest-linux/user-data-dir/Default/IndexedDB/file__0.indexeddb.leveldb/MANIFEST-000001
<html>
<head>
<title>IndexedDB crbug899446 no autoincrement</title>
<script type="text/javascript" src="common.js"></script>
<script type="text/javascript" src="crbug899446.js"></script>
<script>
async function test() {
const db = await Database.open('crbug899446');
if (!db) {
fail("db failed to open");
return;
}
try {
if (!await db.read()) {
fail("db.read() failed");
return;
}
} catch (e) {
fail(e);
return;
}
done();
}
</script>
</head>
<body onLoad="test()">
<div id="status">Starting...</div>
</body>
</html>
2018/11/05-18:38:00.286 28064 Reusing MANIFEST /usr/local/google/home/cmp/co/chromium-latest-linux/user-data-dir/Default/IndexedDB/file__0.indexeddb.leveldb/MANIFEST-000001
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