Bug 605019: Evict entries in the bfcache if they are holding open a database which is being upgraded. r=bent/smaug a=blocker

This commit is contained in:
Jonas Sicking 2010-11-23 13:24:57 -08:00
parent bcb3935aa9
commit 5ec63c5bb2
12 changed files with 221 additions and 30 deletions

View File

@ -107,6 +107,7 @@ struct JSObject;
class nsFrameLoader;
class nsIBoxObject;
class imgIRequest;
class nsISHEntry;
namespace mozilla {
namespace css {
@ -450,11 +451,16 @@ public:
nsIPresShell* GetShell() const
{
return mShellIsHidden ? nsnull : mPresShell;
return GetBFCacheEntry() ? nsnull : mPresShell;
}
void SetShellHidden(PRBool aHide) { mShellIsHidden = aHide; }
PRBool ShellIsHidden() const { return mShellIsHidden; }
void SetBFCacheEntry(nsISHEntry* aSHEntry) {
mSHEntry = aSHEntry;
// Doing this just to keep binary compat for the gecko 2.0 release
mShellIsHidden = !!aSHEntry;
}
nsISHEntry* GetBFCacheEntry() const { return mSHEntry; }
/**
* Return the parent document of this document. Will return null
@ -1594,6 +1600,8 @@ protected:
// document in it.
PRPackedBool mIsInitialDocumentInWindow;
// True if we're currently bfcached. This is only here for binary compat.
// Remove once the gecko 2.0 has branched and just use mSHEntry instead.
PRPackedBool mShellIsHidden;
PRPackedBool mIsRegularHTML;
@ -1706,6 +1714,10 @@ protected:
nsCOMPtr<nsIDocumentEncoder> mCachedEncoder;
AnimationListenerList mAnimationFrameListeners;
// The session history entry in which we're currently bf-cached. Non-null
// if and only if we're currently in the bfcache.
nsISHEntry* mSHEntry;
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsIDocument, NS_IDOCUMENT_IID)

View File

@ -3176,7 +3176,7 @@ nsDocument::doCreateShell(nsPresContext* aContext,
NS_ASSERTION(!mPresShell, "We have a presshell already!");
NS_ENSURE_FALSE(mShellIsHidden, NS_ERROR_FAILURE);
NS_ENSURE_FALSE(GetBFCacheEntry(), NS_ERROR_FAILURE);
FillStyleSet(aStyleSet);

View File

@ -250,6 +250,12 @@ interface nsISHEntry : nsIHistoryEntry
attribute unsigned long long docshellID;
};
[scriptable, uuid(e0b0ac6d-cb29-4be2-b685-1755d6301a69)]
interface nsISHEntryInternal : nsISupports
{
[notxpcom] void RemoveFromBFCacheAsync();
[notxpcom] void RemoveFromBFCacheSync();
};
%{ C++
// {BFD1A791-AD9F-11d3-BDC7-0050040A9B44}

View File

@ -165,10 +165,10 @@ nsSHEntry::~nsSHEntry()
mChildren.EnumerateForwards(ClearParentPtr, nsnull);
mChildren.Clear();
nsCOMPtr<nsIContentViewer> viewer = mContentViewer;
DropPresentationState();
if (viewer) {
viewer->Destroy();
if (mContentViewer) {
// RemoveFromBFCacheSync is virtual, so call the nsSHEntry version
// explicitly
nsSHEntry::RemoveFromBFCacheSync();
}
mEditorData = nsnull;
@ -187,8 +187,8 @@ nsSHEntry::~nsSHEntry()
// nsSHEntry: nsISupports
//*****************************************************************************
NS_IMPL_ISUPPORTS4(nsSHEntry, nsISHContainer, nsISHEntry, nsIHistoryEntry,
nsIMutationObserver)
NS_IMPL_ISUPPORTS5(nsSHEntry, nsISHContainer, nsISHEntry, nsIHistoryEntry,
nsIMutationObserver, nsISHEntryInternal)
//*****************************************************************************
// nsSHEntry: nsISHEntry
@ -254,7 +254,7 @@ nsSHEntry::SetContentViewer(nsIContentViewer *aViewer)
// the contentviewer
mDocument = do_QueryInterface(domDoc);
if (mDocument) {
mDocument->SetShellHidden(PR_TRUE);
mDocument->SetBFCacheEntry(this);
mDocument->AddMutationObserver(this);
}
}
@ -747,7 +747,7 @@ nsSHEntry::DropPresentationState()
nsRefPtr<nsSHEntry> kungFuDeathGrip = this;
if (mDocument) {
mDocument->SetShellHidden(PR_FALSE);
mDocument->SetBFCacheEntry(nsnull);
mDocument->RemoveMutationObserver(this);
mDocument = nsnull;
}
@ -810,7 +810,7 @@ nsSHEntry::CharacterDataChanged(nsIDocument* aDocument,
nsIContent* aContent,
CharacterDataChangeInfo* aInfo)
{
DocumentMutated();
RemoveFromBFCacheAsync();
}
void
@ -829,7 +829,7 @@ nsSHEntry::AttributeChanged(nsIDocument* aDocument,
nsIAtom* aAttribute,
PRInt32 aModType)
{
DocumentMutated();
RemoveFromBFCacheAsync();
}
void
@ -838,7 +838,7 @@ nsSHEntry::ContentAppended(nsIDocument* aDocument,
nsIContent* aFirstNewContent,
PRInt32 /* unused */)
{
DocumentMutated();
RemoveFromBFCacheAsync();
}
void
@ -847,7 +847,7 @@ nsSHEntry::ContentInserted(nsIDocument* aDocument,
nsIContent* aChild,
PRInt32 /* unused */)
{
DocumentMutated();
RemoveFromBFCacheAsync();
}
void
@ -857,7 +857,7 @@ nsSHEntry::ContentRemoved(nsIDocument* aDocument,
PRInt32 aIndexInContainer,
nsIContent* aPreviousSibling)
{
DocumentMutated();
RemoveFromBFCacheAsync();
}
void
@ -885,10 +885,27 @@ public:
};
void
nsSHEntry::DocumentMutated()
nsSHEntry::RemoveFromBFCacheSync()
{
NS_ASSERTION(mContentViewer && mDocument,
"we shouldn't still be observing the doc");
"we're not in the bfcache!");
nsCOMPtr<nsIContentViewer> viewer = mContentViewer;
DropPresentationState();
// Warning! The call to DropPresentationState could have dropped the last
// reference to this nsSHEntry, so no accessing members beyond here.
if (viewer) {
viewer->Destroy();
}
}
void
nsSHEntry::RemoveFromBFCacheAsync()
{
NS_ASSERTION(mContentViewer && mDocument,
"we're not in the bfcache!");
// Release the reference to the contentviewer asynchronously so that the
// document doesn't get nuked mid-mutation.

View File

@ -63,7 +63,8 @@
class nsSHEntry : public nsISHEntry,
public nsISHContainer,
public nsIMutationObserver
public nsIMutationObserver,
public nsISHEntryInternal
{
public:
nsSHEntry();
@ -72,6 +73,7 @@ public:
NS_DECL_ISUPPORTS
NS_DECL_NSIHISTORYENTRY
NS_DECL_NSISHENTRY
NS_DECL_NSISHENTRYINTERNAL
NS_DECL_NSISHCONTAINER
NS_DECL_NSIMUTATIONOBSERVER
@ -86,7 +88,6 @@ public:
private:
~nsSHEntry();
void DocumentMutated();
nsCOMPtr<nsIURI> mURI;
nsCOMPtr<nsIURI> mReferrerURI;

View File

@ -47,6 +47,7 @@
#include "nsCycleCollectionParticipant.h"
#include "nsDOMEventTargetHelper.h"
#include "nsDOMLists.h"
#include "nsIDocument.h"
class nsIScriptContext;
class nsPIDOMWindow;
@ -109,6 +110,13 @@ public:
return mOwner;
}
already_AddRefed<nsIDocument> GetOwnerDocument()
{
NS_ASSERTION(mOwner, "This should never be null!");
nsCOMPtr<nsIDocument> doc = do_QueryInterface(mOwner->GetExtantDocument());
return doc.forget();
}
bool IsQuotaDisabled();
nsCString& Origin()

View File

@ -56,6 +56,7 @@
#include "IDBFactory.h"
#include "LazyIdleThread.h"
#include "TransactionThreadPool.h"
#include "nsISHEntry.h"
// The amount of time, in milliseconds, that our IO thread will stay alive
// after the last event it processes.
@ -162,18 +163,33 @@ public:
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
// Fire version change events at all of the databases that are not already
// closed.
// closed. Also kick bfcached documents out of bfcache.
for (PRUint32 index = 0; index < mWaitingDatabases.Length(); index++) {
nsRefPtr<IDBDatabase>& database = mWaitingDatabases[index];
if (!database->IsClosed()) {
nsCOMPtr<nsIDOMEvent> event(IDBVersionChangeEvent::Create(mVersion));
NS_ENSURE_TRUE(event, NS_ERROR_FAILURE);
PRBool dummy;
database->DispatchEvent(event, &dummy);
if (database->IsClosed()) {
continue;
}
// First check if the document the IDBDatabase is part of is bfcached
nsCOMPtr<nsIDocument> ownerDoc = database->GetOwnerDocument();
nsISHEntry* shEntry;
if (ownerDoc && (shEntry = ownerDoc->GetBFCacheEntry())) {
nsCOMPtr<nsISHEntryInternal> sheInternal = do_QueryInterface(shEntry);
if (sheInternal) {
sheInternal->RemoveFromBFCacheSync();
}
NS_ASSERTION(database->IsClosed(),
"Kicking doc out of bfcache should have closed database");
continue;
}
// Otherwise fire a versionchange event.
nsCOMPtr<nsIDOMEvent> event(IDBVersionChangeEvent::Create(mVersion));
NS_ENSURE_TRUE(event, NS_ERROR_FAILURE);
PRBool dummy;
database->DispatchEvent(event, &dummy);
}
// Now check to see if any didn't close. If there are some running still

View File

@ -45,10 +45,13 @@ include $(DEPTH)/config/autoconf.mk
include $(topsrcdir)/config/rules.mk
TEST_FILES = \
bfcache_iframe1.html \
bfcache_iframe2.html \
event_propagation_iframe.html \
helpers.js \
test_add_twice_failure.html \
test_bad_keypath.html \
test_bfcache.html \
test_clear.html \
test_create_index.html \
test_create_objectStore.html \

View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<script>
moz_indexedDB.open(parent.location).onsuccess = function(e) {
var db = e.result;
// This should never be called
db.onversionchange = function(e) {
db.transaction(["mystore"]).objectStore("mystore").put({ hello: "fail" }, 42);
}
db.setVersion("1.0").onsuccess = function(e) {
trans = e.transaction;
if (db.objectStoreNames.contains("mystore")) {
db.deleteObjectStore("mystore");
}
var store = db.createObjectStore("mystore", "");
store.add({ hello: "world" }, 42);
trans.oncomplete = function() {
parent.postMessage("go", "http://mochi.test:8888");
}
};
};
</script>
</head>
<body>
This is page one.
</body>
</html>

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<script>
var res = {};
moz_indexedDB.open(parent.location).onsuccess = function(e) {
var db = e.result;
res.version = db.version;
res.storeCount = db.objectStoreNames.length;
req = db.setVersion("2.0");
req.onblocked = function() {
res.blockedFired = true;
}
req.onsuccess = function(e) {
var trans = e.transaction;
trans.objectStore("mystore").get(42).onsuccess = function(e) {
res.value = JSON.stringify(e.result);
}
trans.oncomplete = function() {
parent.postMessage(JSON.stringify(res), "http://mochi.test:8888");
}
}
};
</script>
</head>
<body>
This is page two.
</body>
</html>

View File

@ -0,0 +1,65 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<html>
<head>
<title>Indexed Database Property 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"/>
<script type="text/javascript;version=1.7">
var gOrigMaxTotalViewers = undefined;
function setCachePref(enabled) {
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var prefBranch = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch);
if (enabled) {
is(typeof gOrigMaxTotalViewers, "undefined", "don't double-enable bfcache");
prefBranch.setBoolPref("browser.sessionhistory.cache_subframes", true);
gOrigMaxTotalViewers = prefBranch.getIntPref("browser.sessionhistory.max_total_viewers");
prefBranch.setIntPref("browser.sessionhistory.max_total_viewers", 10);
}
else {
is(typeof gOrigMaxTotalViewers, "number", "don't double-disable bfcache");
prefBranch.setIntPref("browser.sessionhistory.max_total_viewers", gOrigMaxTotalViewers);
gOrigMaxTotalViewers = undefined;
try {
prefBranch.clearUserPref("browser.sessionhistory.cache_subframes");
} catch (e) { /* Pref didn't exist, meh */ }
}
}
function testSteps()
{
var iframe = $("iframe");
setCachePref(true);
window.onmessage = grabEventAndContinueHandler;
iframe.src = "bfcache_iframe1.html";
var event = yield;
is(event.data, "go", "set up database successfully");
iframe.src = "bfcache_iframe2.html";
res = JSON.parse((yield).data);
is(res.version, "1.0", "version was set correctly");
is(res.storeCount, 1, "correct set of stores");
ok(!("blockedFired" in res), "blocked shouldn't fire");
is(res.value, JSON.stringify({ hello: "world" }),
"correct value found in store");
setCachePref(false);
finishTest();
yield;
}
</script>
<script type="text/javascript;version=1.7" src="helpers.js"></script>
</head>
<body onload="runTest();">
<iframe id="iframe"></iframe>
</body>
</html>

View File

@ -1539,6 +1539,7 @@ DocumentViewerImpl::Destroy()
nsresult rv = mDocument->Sanitize();
if (NS_FAILED(rv)) {
// If we failed to sanitize, don't save presentation.
// XXX Shouldn't we run all the stuff after the |if (mSHEntry)| then?
savePresentation = PR_FALSE;
}
}
@ -1560,8 +1561,9 @@ DocumentViewerImpl::Destroy()
// When the presentation is restored, Open() and InitInternal() will reset
// these pointers to their original values.
if (mDocument)
if (mDocument) {
mDocument->SetContainer(nsnull);
}
if (mPresContext) {
mPresContext->SetLinkHandler(nsnull);
mPresContext->SetContainer(nsnull);
@ -1581,6 +1583,8 @@ DocumentViewerImpl::Destroy()
return NS_OK;
}
// The document was not put in the bfcache
if (mDocument) {
mDocument->Destroy();
mDocument = nsnull;