Bug 618590 Part 3: When a page is destroyed, abort any running indexedDB transactions. r=bent a=blocker

This commit is contained in:
Jonas Sicking 2011-01-27 13:47:36 -08:00
parent ddbaa55a08
commit 7df9624ca1
9 changed files with 171 additions and 12 deletions

View File

@ -1229,7 +1229,7 @@ nsGlobalWindow::FreeInnerObjects(PRBool aClearScope)
indexedDB::IndexedDatabaseManager* idbManager =
indexedDB::IndexedDatabaseManager::Get();
if (idbManager) {
idbManager->CloseDatabasesForWindow(this);
idbManager->AbortCloseDatabasesForWindow(this);
}
ClearAllTimeouts();

View File

@ -545,7 +545,7 @@ IndexedDatabaseManager::SetDatabaseVersion(IDBDatabase* aDatabase,
}
void
IndexedDatabaseManager::CloseDatabasesForWindow(nsPIDOMWindow* aWindow)
IndexedDatabaseManager::AbortCloseDatabasesForWindow(nsPIDOMWindow* aWindow)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(aWindow, "Null pointer!");
@ -553,10 +553,18 @@ IndexedDatabaseManager::CloseDatabasesForWindow(nsPIDOMWindow* aWindow)
nsAutoTArray<IDBDatabase*, 50> liveDatabases;
mLiveDatabases.EnumerateRead(EnumerateToTArray, &liveDatabases);
TransactionThreadPool* pool = TransactionThreadPool::Get();
for (PRUint32 index = 0; index < liveDatabases.Length(); index++) {
IDBDatabase*& database = liveDatabases[index];
if (database->Owner() == aWindow && NS_FAILED(database->Close())) {
NS_WARNING("Failed to close database for dying window!");
if (database->Owner() == aWindow) {
if (NS_FAILED(database->Close())) {
NS_WARNING("Failed to close database for dying window!");
}
if (pool) {
pool->AbortTransactionsForDatabase(database);
}
}
}
}

View File

@ -100,9 +100,10 @@ public:
const nsAString& aVersion,
AsyncConnectionHelper* aHelper);
// Called when a window is being purged from the bfcache in order to force any
// live database objects to close themselves.
void CloseDatabasesForWindow(nsPIDOMWindow* aWindow);
// Called when a window is being purged from the bfcache or the user leaves
// a page which isn't going into the bfcache. Forces any live database
// objects to close themselves and aborts any running transactions.
void AbortCloseDatabasesForWindow(nsPIDOMWindow* aWindow);
// Used to check if there are running transactions in a given window.
bool HasOpenTransactions(nsPIDOMWindow* aWindow);

View File

@ -517,6 +517,55 @@ TransactionThreadPool::WaitForAllDatabasesToComplete(
return true;
}
void
TransactionThreadPool::AbortTransactionsForDatabase(IDBDatabase* aDatabase)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(aDatabase, "Null pointer!");
// Get list of transactions for this database id
DatabaseTransactionInfo* dbTransactionInfo;
if (!mTransactionsInProgress.Get(aDatabase->Id(), &dbTransactionInfo)) {
// If there are no running transactions, there can't be any pending ones
return;
}
nsAutoTArray<nsRefPtr<IDBTransaction>, 50> transactions;
// Collect any running transactions
nsTArray<TransactionInfo>& transactionsInProgress =
dbTransactionInfo->transactions;
PRUint32 transactionCount = transactionsInProgress.Length();
NS_ASSERTION(transactionCount, "Should never be 0!");
for (PRUint32 index = 0; index < transactionCount; index++) {
// See if any transaction belongs to this IDBDatabase instance
IDBTransaction* transaction = transactionsInProgress[index].transaction;
if (transaction->Database() == aDatabase) {
transactions.AppendElement(transaction);
}
}
// Collect any pending transactions.
for (PRUint32 index = 0; index < mDelayedDispatchQueue.Length(); index++) {
// See if any transaction belongs to this IDBDatabase instance
IDBTransaction* transaction = mDelayedDispatchQueue[index].transaction;
if (transaction->Database() == aDatabase) {
transactions.AppendElement(transaction);
}
}
// Abort transactions. Do this after collecting the transactions in case
// calling Abort() modifies the data structures we're iterating above.
for (PRUint32 index = 0; index < transactions.Length(); index++) {
// This can fail, for example if the transaction is in the process of
// being comitted. That is expected and fine, so we ignore any returned
// errors.
transactions[index]->Abort();
}
}
bool
TransactionThreadPool::HasTransactionsForDatabase(IDBDatabase* aDatabase)
{

View File

@ -84,7 +84,11 @@ public:
nsTArray<nsRefPtr<IDBDatabase> >& aDatabases,
nsIRunnable* aCallback);
// Returns true iff there are running or pending transactions for aDatabase
// Abort all transactions, unless they are already in the process of being
// committed, for aDatabase.
void AbortTransactionsForDatabase(IDBDatabase* aDatabase);
// Returns true iff there are running or pending transactions for aDatabase.
bool HasTransactionsForDatabase(IDBDatabase* aDatabase);
protected:

View File

@ -51,6 +51,7 @@ TEST_FILES = \
event_propagation_iframe.html \
exceptions_in_success_events_iframe.html \
helpers.js \
leaving_page_iframe.html \
test_add_twice_failure.html \
test_bad_keypath.html \
test_bfcache.html \
@ -73,6 +74,7 @@ TEST_FILES = \
test_indexes.html \
test_indexes_bad_values.html \
test_key_requirements.html \
test_leaving_page.html \
test_objectCursors.html \
test_objectStore_inline_autoincrement_key_added_on_put.html \
test_objectStore_remove_values.html \

View File

@ -40,10 +40,11 @@
request.onerror = errorHandler;
request.onsuccess = function(event) {
db.deleteObjectStore("foo");
testResult = "finished";
testException = undefined;
finishTest();
request.transaction.oncomplete = function(event) {
testResult = "finished";
testException = undefined;
finishTest();
}
}
}

View File

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html>
<head>
<script>
var db;
function startDBWork() {
mozIndexedDB.open(parent.location).onsuccess = function(e) {
db = e.target.result;
db.setVersion("1.0").onsuccess = function(e) {
var trans = e.target.transaction;
if (db.objectStoreNames.contains("mystore")) {
db.deleteObjectStore("mystore");
}
var store = db.createObjectStore("mystore");
store.add({ hello: "world" }, 42);
trans.oncomplete = madeMod;
};
};
}
function madeMod() {
var trans = db.transaction(["mystore"], IDBTransaction.READ_WRITE);
var store = trans.
objectStore("mystore");
trans.oncomplete = function() {
parent.postMessage("didcommit", "*");
}
store.put({ hello: "officer" }, 42).onsuccess = function(e) {
// Make this transaction run until the end of time or until the page is
// navigated away, whichever comes first.
function doGet() {
store.get(42).onsuccess = doGet;
}
doGet();
document.location = "about:blank";
}
}
</script>
</head>
<body onload="startDBWork();">
This is page one.
</body>
</html>

View File

@ -0,0 +1,49 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<html>
<head>
<title>Indexed Database Leaving Page Test</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body onload="runTest();">
<iframe id="inner"></iframe>
<a id="a" href="leaving_page_iframe.html"></a>
<script type="text/javascript;version=1.7">
onmessage = function(e) {
ok(false, "gotmessage: " + e.data);
}
function testSteps()
{
var iframe = $("inner");
iframe.src = "leaving_page_iframe.html";
iframe.onload = continueToNextStep;
yield;
is(iframe.contentWindow.location.href, $("a").href,
"should navigate to iframe page");
yield;
is(iframe.contentWindow.location.href, "about:blank",
"should nagivate to about:blank");
let request = mozIndexedDB.open(location);
request.onsuccess = grabEventAndContinueHandler;
let event = yield;
let db = event.target.result;
db.transaction(["mystore"]).objectStore("mystore").get(42).onsuccess =
grabEventAndContinueHandler;
event = yield;
is(event.target.result.hello, "world", "second modification rolled back");
finishTest();
yield;
}
</script>
<script type="text/javascript;version=1.7" src="helpers.js"></script>
</html>