--- a/browser/components/sessionstore/src/nsSessionStore.js
+++ b/browser/components/sessionstore/src/nsSessionStore.js
@@ -1867,19 +1867,17 @@ SessionStoreService.prototype = {
// We can stop doing base64 encoding once our serialization into JSON
// is guaranteed to handle all chars in strings, including embedded
// nulls.
entry.owner_b64 = btoa(String.fromCharCode.apply(null, ownerBytes));
}
catch (ex) { debug(ex); }
}
- if (aEntry.docIdentifier) {
- entry.docIdentifier = aEntry.docIdentifier;
- }
+ entry.docIdentifier = aEntry.BFCacheEntry.ID;
if (aEntry.stateData != null) {
entry.structuredCloneState = aEntry.stateData.getDataAsBase64();
entry.structuredCloneVersion = aEntry.stateData.formatVersion;
}
if (!(aEntry instanceof Ci.nsISHContainer)) {
return entry;
@@ -2977,17 +2975,16 @@ SessionStoreService.prototype = {
getEntryAtIndex(activeIndex, false).
QueryInterface(Ci.nsISHEntry);
// restore those aspects of the currently active documents which are not
// preserved in the plain history entries (mainly scroll state and text data)
browser.__SS_restore_data = tabData.entries[activeIndex] || {};
browser.__SS_restore_pageStyle = tabData.pageStyle || "";
browser.__SS_restore_tab = aTab;
- browser.__SS_restore_docIdentifier = curSHEntry.docIdentifier;
didStartLoad = true;
try {
// In order to work around certain issues in session history, we need to
// force session history to update its internal index and call reload
// instead of gotoIndex. See bug 597315.
browser.webNavigation.sessionHistory.getEntryAtIndex(activeIndex, true);
browser.webNavigation.sessionHistory.reloadCurrentEntry();
@@ -3130,34 +3127,26 @@ SessionStoreService.prototype = {
var postdata = atob(aEntry.postdata_b64);
var stream = Cc["@mozilla.org/io/string-input-stream;1"].
createInstance(Ci.nsIStringInputStream);
stream.setData(postdata, postdata.length);
shEntry.postData = stream;
}
if (aEntry.docIdentifier) {
- // Get a new document identifier for this entry to ensure that history
- // entries after a session restore are considered to have different
- // documents from the history entries before the session restore.
- // Document identifiers are 64-bit ints, so JS will loose precision and
- // start assigning all entries the same doc identifier if these ever get
- // large enough.
- //
- // It's a potential security issue if document identifiers aren't
- // globally unique, but shEntry.setUniqueDocIdentifier() below guarantees
- // that we won't re-use a doc identifier within a given instance of the
- // application.
- let ident = aDocIdentMap[aEntry.docIdentifier];
- if (!ident) {
- shEntry.setUniqueDocIdentifier();
- aDocIdentMap[aEntry.docIdentifier] = shEntry.docIdentifier;
+ // If we have a serialized document identifier, try to find an SHEntry
+ // which matches that doc identifier and adopt that SHEntry's
+ // BFCacheEntry. If we don't find a match, insert shEntry as the match
+ // for the document identifier.
+ let matchingEntry = aDocIdentMap[aEntry.docIdentifier];
+ if (!matchingEntry) {
+ aDocIdentMap[aEntry.docIdentifier] = shEntry;
}
else {
- shEntry.docIdentifier = ident;
+ shEntry.adoptBFCacheEntry(matchingEntry);
}
}
if (aEntry.owner_b64) {
var ownerInput = Cc["@mozilla.org/io/string-input-stream;1"].
createInstance(Ci.nsIStringInputStream);
var binaryData = atob(aEntry.owner_b64);
ownerInput.setData(binaryData, binaryData.length);
@@ -3283,29 +3272,22 @@ SessionStoreService.prototype = {
// don't restore text data and scrolling state if the user has navigated
// away before the loading completed (except for in-page navigation)
if (hasExpectedURL(aEvent.originalTarget, aBrowser.__SS_restore_data.url)) {
var content = aEvent.originalTarget.defaultView;
restoreTextDataAndScrolling(content, aBrowser.__SS_restore_data, "");
aBrowser.markupDocumentViewer.authorStyleDisabled = selectedPageStyle == "_nostyle";
}
- if (aBrowser.__SS_restore_docIdentifier) {
- let sh = aBrowser.webNavigation.sessionHistory;
- sh.getEntryAtIndex(sh.index, false).QueryInterface(Ci.nsISHEntry).
- docIdentifier = aBrowser.__SS_restore_docIdentifier;
- }
-
// notify the tabbrowser that this document has been completely restored
this._sendTabRestoredNotification(aBrowser.__SS_restore_tab);
delete aBrowser.__SS_restore_data;
delete aBrowser.__SS_restore_pageStyle;
delete aBrowser.__SS_restore_tab;
- delete aBrowser.__SS_restore_docIdentifier;
},
/**
* Restore visibility and dimension features to a window
* @param aWindow
* Window reference
* @param aWinData
* Object containing session data for the window
--- a/browser/components/sessionstore/test/browser/browser_500328.js
+++ b/browser/components/sessionstore/test/browser/browser_500328.js
@@ -111,23 +111,23 @@ function test() {
tabBrowser.loadURI("http://example.com", null, null);
tabBrowser.addEventListener("load", function(aEvent) {
tabBrowser.removeEventListener("load", arguments.callee, true);
// After these push/replaceState calls, the window should have three
// history entries:
- // testURL (state object: null) <-- oldest
- // testURL (state object: {obj1:1})
- // page2 (state object: {obj3:/^a$/}) <-- newest
+ // testURL (state object: null) <-- oldest
+ // testURL (state object: {obj1:1})
+ // testURL?page2 (state object: {obj3:/^a$/}) <-- newest
let contentWindow = tab.linkedBrowser.contentWindow;
let history = contentWindow.history;
history.pushState({obj1:1}, "title-obj1");
- history.pushState({obj2:2}, "title-obj2", "page2");
+ history.pushState({obj2:2}, "title-obj2", "?page2");
history.replaceState({obj3:/^a$/}, "title-obj3");
let state = ss.getTabState(tab);
gBrowser.removeTab(tab);
// Restore the state into a new tab. Things don't work well when we
// restore into the old tab, but that's not a real use case anyway.
let tab2 = gBrowser.addTab("about:blank");
--- a/content/base/public/nsIDocument.h
+++ b/content/base/public/nsIDocument.h
@@ -64,16 +64,17 @@
#ifdef MOZ_SMIL
#include "nsSMILAnimationController.h"
#endif // MOZ_SMIL
#include "nsIScriptGlobalObject.h"
#include "nsIDocumentEncoder.h"
#include "nsIAnimationFrameListener.h"
#include "nsEventStates.h"
#include "nsIStructuredCloneContainer.h"
+#include "nsIBFCacheEntry.h"
#include "nsDOMMemoryReporter.h"
class nsIContent;
class nsPresContext;
class nsIPresShell;
class nsIDocShell;
class nsStyleSet;
class nsIStyleSheet;
@@ -120,19 +121,19 @@ class Loader;
namespace dom {
class Link;
class Element;
} // namespace dom
} // namespace mozilla
-#define NS_IDOCUMENT_IID \
-{ 0xfac563fb, 0x2b6a, 0x4ac8, \
- { 0x85, 0xf7, 0xd5, 0x14, 0x4b, 0x3e, 0xce, 0x78 } }
+#define NS_IDOCUMENT_IID \
+{ 0x455e4d79, 0x756b, 0x4f73, \
+ { 0x95, 0xea, 0x3f, 0xf6, 0x0c, 0x6a, 0x8c, 0xa6 } }
// Flag for AddStyleSheet().
#define NS_STYLESHEET_FROM_CATALOG (1 << 0)
// Document states
// RTL locale: specific to the XUL localedir attribute
#define NS_DOCUMENT_STATE_RTL_LOCALE NS_DEFINE_EVENT_STATE_MACRO(0)
@@ -475,21 +476,25 @@ public:
nsIPresShell** aInstancePtrResult) = 0;
virtual void DeleteShell() = 0;
nsIPresShell* GetShell() const
{
return GetBFCacheEntry() ? nsnull : mPresShell;
}
- void SetBFCacheEntry(nsISHEntry* aSHEntry) {
- mSHEntry = aSHEntry;
+ void SetBFCacheEntry(nsIBFCacheEntry* aEntry)
+ {
+ mBFCacheEntry = aEntry;
}
- nsISHEntry* GetBFCacheEntry() const { return mSHEntry; }
+ nsIBFCacheEntry* GetBFCacheEntry() const
+ {
+ return mBFCacheEntry;
+ }
/**
* Return the parent document of this document. Will return null
* unless this document is within a compound document and has a
* parent. Note that this parent chain may cross chrome boundaries.
*/
nsIDocument *GetParentDocument() const
{
@@ -1733,19 +1738,19 @@ protected:
// Weak reference to mScriptGlobalObject QI:d to nsPIDOMWindow,
// updated on every set of mSecriptGlobalObject.
nsPIDOMWindow *mWindow;
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;
+ // This object allows us to evict ourself from the back/forward cache. The
+ // pointer is non-null iff we're currently in the bfcache.
+ nsIBFCacheEntry *mBFCacheEntry;
// Our base target.
nsString mBaseTarget;
nsCOMPtr<nsIStructuredCloneContainer> mStateObjectContainer;
nsCOMPtr<nsIVariant> mStateObjectCached;
};
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -4131,20 +4131,20 @@ nsDocShell::LoadErrorPage(nsIURI *aURI,
spec.get(), NS_ConvertUTF16toUTF8(aURL).get(), chanName.get()));
}
#endif
mFailedChannel = aFailedChannel;
mFailedURI = aURI;
mFailedLoadType = mLoadType;
if (mLSHE) {
- // If we don't give mLSHE a new doc identifier here, when we go back or
- // forward to another SHEntry with the same doc identifier, the error
- // page will persist.
- mLSHE->SetUniqueDocIdentifier();
+ // Abandon mLSHE's BFCache entry and create a new one. This way, if
+ // we go back or forward to another SHEntry with the same doc
+ // identifier, the error page won't persist.
+ mLSHE->AbandonBFCacheEntry();
}
nsCAutoString url;
nsCAutoString charset;
if (aURI)
{
nsresult rv = aURI->GetSpec(url);
rv |= aURI->GetOriginCharset(charset);
@@ -4407,20 +4407,20 @@ nsDocShell::LoadPage(nsISupports *aPageD
}
// Now clone shEntryIn, since we might end up modifying it later on, and we
// want a page descriptor to be reusable.
nsCOMPtr<nsISHEntry> shEntry;
nsresult rv = shEntryIn->Clone(getter_AddRefs(shEntry));
NS_ENSURE_SUCCESS(rv, rv);
- // Give our cloned shEntry a new document identifier so this load is
- // independent of all other loads. (This is important, in particular,
- // for bugs 582795 and 585298.)
- rv = shEntry->SetUniqueDocIdentifier();
+ // Give our cloned shEntry a new bfcache entry so this load is independent
+ // of all other loads. (This is important, in particular, for bugs 582795
+ // and 585298.)
+ rv = shEntry->AbandonBFCacheEntry();
NS_ENSURE_SUCCESS(rv, rv);
//
// load the page as view-source
//
if (nsIWebPageDescriptor::DISPLAY_AS_SOURCE == aDisplayType) {
nsCOMPtr<nsIURI> oldUri, newUri;
nsCString spec, newSpec;
@@ -8398,27 +8398,25 @@ nsDocShell::InternalLoad(nsIURI * aURI,
curBeforeHash, curHash) :
NS_ERROR_FAILURE;
splitRv2 = nsContentUtils::SplitURIAtHash(aURI, newBeforeHash, newHash);
PRBool sameExceptHashes = NS_SUCCEEDED(splitRv1) &&
NS_SUCCEEDED(splitRv2) &&
curBeforeHash.Equals(newBeforeHash);
- PRBool sameDocIdent = PR_FALSE;
+ // XXX rename
+ PRBool sameDocument = PR_FALSE;
if (mOSHE && aSHEntry) {
// We're doing a history load.
- PRUint64 ourDocIdent, otherDocIdent;
- mOSHE->GetDocIdentifier(&ourDocIdent);
- aSHEntry->GetDocIdentifier(&otherDocIdent);
- sameDocIdent = (ourDocIdent == otherDocIdent);
+ mOSHE->SharesDocumentWith(aSHEntry, &sameDocument);
#ifdef DEBUG
- if (sameDocIdent) {
+ if (sameDocument) {
nsCOMPtr<nsIInputStream> currentPostData;
mOSHE->GetPostData(getter_AddRefs(currentPostData));
NS_ASSERTION(currentPostData == aPostData,
"Different POST data for entries for the same page?");
}
#endif
}
@@ -8431,17 +8429,17 @@ nsDocShell::InternalLoad(nsIURI * aURI,
//
// b) we're navigating to a new shentry whose URI differs from the
// current URI only in its hash, the new hash is non-empty, and
// we're not doing a POST.
//
// The restriction tha the SHEntries in (a) must be different ensures
// that history.go(0) and the like trigger full refreshes, rather than
// short-circuited loads.
- PRBool doShortCircuitedLoad = (sameDocIdent && mOSHE != aSHEntry) ||
+ PRBool doShortCircuitedLoad = (sameDocument && mOSHE != aSHEntry) ||
(!aSHEntry && aPostData == nsnull &&
sameExceptHashes && !newHash.IsEmpty());
// Fire a hashchange event if we're doing a short-circuited load and the
// URIs differ only in their hashes.
PRBool doHashchange = doShortCircuitedLoad &&
sameExceptHashes &&
!curHash.Equals(newHash);
@@ -8482,32 +8480,35 @@ nsDocShell::InternalLoad(nsIURI * aURI,
mOSHE->GetOwner(getter_AddRefs(owner));
}
// Pass true for aCloneSHChildren, since we're not
// changing documents here, so all of our subframes are
// still relevant to the new session history entry.
OnNewURI(aURI, nsnull, owner, mLoadType, PR_TRUE, PR_TRUE, PR_TRUE);
nsCOMPtr<nsIInputStream> postData;
- PRUint64 docIdent = PRUint64(-1);
nsCOMPtr<nsISupports> cacheKey;
if (mOSHE) {
/* save current position of scroller(s) (bug 59774) */
mOSHE->SetScrollPosition(cx, cy);
// Get the postdata and page ident from the current page, if
// the new load is being done via normal means. Note that
// "normal means" can be checked for just by checking for
// LOAD_CMD_NORMAL, given the loadType and allowScroll check
// above -- it filters out some LOAD_CMD_NORMAL cases that we
// wouldn't want here.
if (aLoadType & LOAD_CMD_NORMAL) {
mOSHE->GetPostData(getter_AddRefs(postData));
- mOSHE->GetDocIdentifier(&docIdent);
mOSHE->GetCacheKey(getter_AddRefs(cacheKey));
+
+ // Link our new SHEntry to the old SHEntry's back/forward
+ // cache data, since the two SHEntries correspond to the
+ // same document.
+ mLSHE->AdoptBFCacheEntry(mOSHE);
}
}
/* Assign mOSHE to mLSHE. This will either be a new entry created
* by OnNewURI() for normal loads or aSHEntry for history loads.
*/
if (mLSHE) {
SetHistoryEntry(&mOSHE, mLSHE);
@@ -8517,21 +8518,16 @@ nsDocShell::InternalLoad(nsIURI * aURI,
// page will restore the appropriate postData.
if (postData)
mOSHE->SetPostData(postData);
// Make sure we won't just repost without hitting the
// cache first
if (cacheKey)
mOSHE->SetCacheKey(cacheKey);
-
- // Propagate our document identifier to the new mOSHE so that
- // we'll know it's related by an anchor navigation or pushState.
- if (docIdent != PRUint64(-1))
- mOSHE->SetDocIdentifier(docIdent);
}
/* restore previous position of scroller(s), if we're moving
* back in history (bug 59774)
*/
if (mOSHE && (aLoadType == LOAD_HISTORY || aLoadType == LOAD_RELOAD_NORMAL))
{
nscoord bx, by;
@@ -8565,37 +8561,37 @@ nsDocShell::InternalLoad(nsIURI * aURI,
if (history) {
history->SetURITitle(aURI, mTitle);
}
else if (mGlobalHistory) {
mGlobalHistory->SetPageTitle(aURI, mTitle);
}
}
- if (sameDocIdent) {
+ if (sameDocument) {
// Set the doc's URI according to the new history entry's URI
nsCOMPtr<nsIURI> newURI;
mOSHE->GetURI(getter_AddRefs(newURI));
NS_ENSURE_TRUE(newURI, NS_ERROR_FAILURE);
nsCOMPtr<nsIDocument> doc =
do_GetInterface(GetAsSupports(this));
NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
doc->SetDocumentURI(newURI);
}
SetDocCurrentStateObj(mOSHE);
// Dispatch the popstate and hashchange events, as appropriate.
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(mScriptGlobal);
if (window) {
- // Need the doHashchange check here since sameDocIdent is
+ // Need the doHashchange check here since sameDocument is
// false if we're navigating to a new shentry (i.e. a aSHEntry
// is null), such as when clicking a <a href="#foo">.
- if (sameDocIdent || doHashchange) {
+ if (sameDocument || doHashchange) {
window->DispatchSyncPopState();
}
if (doHashchange) {
// Make sure to use oldURI here, not mCurrentURI, because by
// now, mCurrentURI has changed!
window->DispatchAsyncHashchange(oldURI, aURI);
}
@@ -9415,21 +9411,21 @@ nsDocShell::OnNewURI(nsIURI * aURI, nsIC
if (httpChannel) {
nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel));
if (uploadChannel) {
uploadChannel->GetUploadStream(getter_AddRefs(inputStream));
}
// If the response status indicates an error, unlink this session
- // history entry from any entries sharing its doc ident.
+ // history entry from any entries sharing its document.
PRUint32 responseStatus;
nsresult rv = httpChannel->GetResponseStatus(&responseStatus);
if (mLSHE && NS_SUCCEEDED(rv) && responseStatus >= 400) {
- mLSHE->SetUniqueDocIdentifier();
+ mLSHE->AbandonBFCacheEntry();
}
}
}
/* Create SH Entry (mLSHE) only if there is a SessionHistory object (mSessionHistory) in
* the current frame or in the root docshell
*/
nsCOMPtr<nsISHistory> rootSH = mSessionHistory;
if (!rootSH) {
@@ -9830,21 +9826,19 @@ nsDocShell::AddState(nsIVariant *aData,
// Since we're not changing which page we have loaded, pass
// true for aCloneChildren.
rv = AddToSessionHistory(newURI, nsnull, nsnull, PR_TRUE,
getter_AddRefs(newSHEntry));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(newSHEntry, NS_ERROR_FAILURE);
- // Set the new SHEntry's document identifier, if we can.
- PRUint64 ourDocIdent;
- NS_ENSURE_SUCCESS(oldOSHE->GetDocIdentifier(&ourDocIdent),
- NS_ERROR_FAILURE);
- NS_ENSURE_SUCCESS(newSHEntry->SetDocIdentifier(ourDocIdent),
+ // Link the new SHEntry to the old SHEntry's BFCache entry, since the
+ // two entries correspond to the same document.
+ NS_ENSURE_SUCCESS(newSHEntry->AdoptBFCacheEntry(oldOSHE),
NS_ERROR_FAILURE);
// Set the new SHEntry's title (bug 655273).
nsString title;
mOSHE->GetTitle(getter_Copies(title));
newSHEntry->SetTitle(title);
// AddToSessionHistory may not modify mOSHE. In case it doesn't,
--- a/docshell/build/nsDocShellModule.cpp
+++ b/docshell/build/nsDocShellModule.cpp
@@ -61,16 +61,17 @@
#include "nsExternalSharingAppService.h"
#endif
#if defined(ANDROID)
#include "nsExternalURLHandlerService.h"
#endif
// session history
#include "nsSHEntry.h"
+#include "nsSHEntryShared.h"
#include "nsSHistory.h"
#include "nsSHTransaction.h"
// download history
#include "nsDownloadHistory.h"
static PRBool gInitialized = PR_FALSE;
@@ -82,25 +83,25 @@ Initialize()
if (gInitialized) {
return NS_OK;
}
gInitialized = PR_TRUE;
nsresult rv = nsSHistory::Startup();
NS_ENSURE_SUCCESS(rv, rv);
- rv = nsSHEntry::Startup();
- return rv;
+ nsSHEntryShared::Startup();
+ return NS_OK;
}
static void
Shutdown()
{
nsSHistory::Shutdown();
- nsSHEntry::Shutdown();
+ nsSHEntryShared::Shutdown();
gInitialized = PR_FALSE;
}
// docshell
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsDocShell, Init)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsDefaultURIFixup)
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsWebNavigationInfo, Init)
--- a/docshell/shistory/public/Makefile.in
+++ b/docshell/shistory/public/Makefile.in
@@ -51,12 +51,13 @@ SDK_XPIDLSRCS = \
nsISHistoryListener.idl \
$(NULL)
XPIDLSRCS = \
nsISHEntry.idl \
nsISHContainer.idl \
nsISHTransaction.idl \
nsISHistoryInternal.idl \
+ nsIBFCacheEntry.idl \
$(NULL)
include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/docshell/shistory/public/nsIBFCacheEntry.idl
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface lets you evict a document from the back/forward cache.
+ */
+[scriptable, uuid(a576060e-c7df-4d81-aa8c-5b52bd6ad25d)]
+interface nsIBFCacheEntry : nsISupports
+{
+ void RemoveFromBFCacheSync();
+ void RemoveFromBFCacheAsync();
+
+ readonly attribute unsigned long long ID;
+};
--- a/docshell/shistory/public/nsISHEntry.idl
+++ b/docshell/shistory/public/nsISHEntry.idl
@@ -46,25 +46,28 @@
interface nsILayoutHistoryState;
interface nsIContentViewer;
interface nsIURI;
interface nsIInputStream;
interface nsIDocShellTreeItem;
interface nsISupportsArray;
interface nsIStructuredCloneContainer;
+interface nsIBFCacheEntry;
+
%{C++
struct nsIntRect;
class nsDocShellEditorData;
+class nsSHEntryShared;
%}
[ref] native nsIntRect(nsIntRect);
[ptr] native nsDocShellEditorDataPtr(nsDocShellEditorData);
-
+[ptr] native nsSHEntryShared(nsSHEntryShared);
-[scriptable, uuid(b92d403e-f5ec-4b81-b0e3-6e6c241cef2d)]
+[scriptable, uuid(6443FD72-A50F-4B8B-BB82-BB1FA04CB15D)]
interface nsISHEntry : nsIHistoryEntry
{
/** URI for the document */
void setURI(in nsIURI aURI);
/** Referrer URI */
attribute nsIURI referrerURI;
@@ -135,31 +138,16 @@ interface nsISHEntry : nsIHistoryEntry
attribute unsigned long loadType;
/**
* An ID to help identify this entry from others during
* subframe navigation
*/
attribute unsigned long ID;
- /**
- * docIdentifier is an integer that should be the same for two entries
- * attached to the same docshell if and only if the two entries are entries
- * for the same document. In practice, two entries A and B will have the
- * same docIdentifier if we arrived at B by clicking an anchor link in A or
- * if B was created by A's calling history.pushState().
- */
- attribute unsigned long long docIdentifier;
-
- /**
- * Changes this entry's doc identifier to a new value which is unique
- * among those of all other entries.
- */
- void setUniqueDocIdentifier();
-
/** attribute to set and get the cache key for the entry */
attribute nsISupports cacheKey;
/** attribute to indicate whether layoutHistoryState should be saved */
attribute boolean saveLayoutStateFlag;
/** attribute to indicate whether the page is already expired in cache */
attribute boolean expirationStatus;
@@ -247,28 +235,69 @@ interface nsISHEntry : nsIHistoryEntry
* when isDynamicallyAdded is called on it.
*/
boolean hasDynamicallyAddedChild();
/**
* The history ID of the docshell.
*/
attribute unsigned long long docshellID;
+
+ readonly attribute nsIBFCacheEntry BFCacheEntry;
+
+ /**
+ * Does this SHEntry point to the given BFCache entry? If so, evicting
+ * the BFCache entry will evict the SHEntry, since the two entries
+ * correspond to the same document.
+ */
+ [notxpcom, noscript]
+ boolean hasBFCacheEntry(in nsIBFCacheEntry aEntry);
+
+ /**
+ * Adopt aEntry's BFCacheEntry, so now both this and aEntry point to
+ * aEntry's BFCacheEntry.
+ */
+ void adoptBFCacheEntry(in nsISHEntry aEntry);
+
+ /**
+ * Create a new BFCache entry and drop our reference to our old one. This
+ * call unlinks this SHEntry from any other SHEntries for its document.
+ */
+ void abandonBFCacheEntry();
+
+ /**
+ * Does this SHEntry correspond to the same document as aEntry? This is
+ * true iff the two SHEntries have the same BFCacheEntry. So in
+ * particular, sharesDocumentWith(aEntry) is guaranteed to return true if
+ * it's preceeded by a call to adoptBFCacheEntry(aEntry).
+ */
+ boolean sharesDocumentWith(in nsISHEntry aEntry);
};
[scriptable, uuid(bb66ac35-253b-471f-a317-3ece940f04c5)]
interface nsISHEntryInternal : nsISupports
{
[notxpcom] void RemoveFromBFCacheAsync();
[notxpcom] void RemoveFromBFCacheSync();
/**
* A number that is assigned by the sHistory when the entry is activated
*/
attribute unsigned long lastTouched;
+
+ /**
+ * Some state, particularly that related to the back/forward cache, is
+ * shared between SHEntries which correspond to the same document. This
+ * method gets a pointer to that shared state.
+ *
+ * This shared state is the SHEntry's BFCacheEntry. So
+ * hasBFCacheEntry(getSharedState()) is guaranteed to return true.
+ */
+ [noscript, notxpcom]
+ nsSHEntryShared getSharedState();
};
%{ C++
// {BFD1A791-AD9F-11d3-BDC7-0050040A9B44}
#define NS_SHENTRY_CID \
{0xbfd1a791, 0xad9f, 0x11d3, {0xbd, 0xc7, 0x0, 0x50, 0x4, 0xa, 0x9b, 0x44}}
#define NS_SHENTRY_CONTRACTID \
--- a/docshell/shistory/public/nsISHistoryInternal.idl
+++ b/docshell/shistory/public/nsISHistoryInternal.idl
@@ -52,17 +52,17 @@ interface nsIDocShell;
#define NS_SHISTORY_INTERNAL_CONTRACTID "@mozilla.org/browser/shistory-internal;1"
template<class E, class A> class nsTArray;
struct nsTArrayDefaultAllocator;
%}
[ref] native nsDocshellIDArray(nsTArray<PRUint64, nsTArrayDefaultAllocator>);
-[scriptable, uuid(2dede933-25e1-47a3-8f61-0127c785ea01)]
+[scriptable, uuid(AFE77866-8859-4492-B131-4C58167E1630)]
interface nsISHistoryInternal: nsISupports
{
/**
* Add a new Entry to the History List
* @param aEntry - The entry to add
* @param aPersist - If true this specifies that the entry should persist
* in the list. If false, this means that when new entries are added
* this element will not appear in the session history list.
@@ -101,20 +101,20 @@ interface nsISHistoryInternal: nsISuppor
* is no more than gHistoryMaxViewers. Also, count
* total number of content viewers globally and evict one if we are over
* our total max. This is always called in Show(), after we destroy
* the previous viewer.
*/
void evictContentViewers(in long previousIndex, in long index);
/**
- * Evict the content viewer associated with a session history entry
+ * Evict the content viewer associated with a bfcache entry
* that has timed out.
*/
- void evictExpiredContentViewerForEntry(in nsISHEntry aEntry);
+ void evictExpiredContentViewerForEntry(in nsIBFCacheEntry aEntry);
/**
* Evict all the content viewers in this session history
*/
void evictAllContentViewers();
/**
* Removes entries from the history if their docshellID is in
--- a/docshell/shistory/src/Makefile.in
+++ b/docshell/shistory/src/Makefile.in
@@ -43,17 +43,20 @@ VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
MODULE = shistory
LIBRARY_NAME = shistory_s
FORCE_STATIC_LIB = 1
LIBXUL_LIBRARY = 1
+EXPORTS = nsSHEntryShared.h \
+ $(NULL)
CPPSRCS = nsSHEntry.cpp \
nsSHTransaction.cpp \
nsSHistory.cpp \
+ nsSHEntryShared.cpp \
$(NULL)
include $(topsrcdir)/config/rules.mk
LOCAL_INCLUDES += -I$(srcdir)/../../base
--- a/docshell/shistory/src/nsSHEntry.cpp
+++ b/docshell/shistory/src/nsSHEntry.cpp
@@ -31,171 +31,87 @@
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
-#ifdef DEBUG_bryner
-#define DEBUG_PAGE_CACHE
-#endif
-
// Local Includes
#include "nsSHEntry.h"
#include "nsXPIDLString.h"
#include "nsReadableUtils.h"
#include "nsIDocShellLoadInfo.h"
#include "nsIDocShellTreeItem.h"
#include "nsIDocument.h"
#include "nsIDOMDocument.h"
-#include "nsAutoPtr.h"
-#include "nsThreadUtils.h"
-#include "nsIWebNavigation.h"
#include "nsISHistory.h"
#include "nsISHistoryInternal.h"
#include "nsDocShellEditorData.h"
-#include "nsIDocShell.h"
+#include "nsSHEntryShared.h"
+#include "nsILayoutHistoryState.h"
+#include "nsIContentViewer.h"
+#include "nsISupportsArray.h"
namespace dom = mozilla::dom;
-// Hardcode this to time out unused content viewers after 30 minutes
-#define CONTENT_VIEWER_TIMEOUT_SECONDS 30*60
-
-typedef nsExpirationTracker<nsSHEntry,3> HistoryTrackerBase;
-class HistoryTracker : public HistoryTrackerBase {
-public:
- // Expire cached contentviewers after 20-30 minutes in the cache.
- HistoryTracker() : HistoryTrackerBase((CONTENT_VIEWER_TIMEOUT_SECONDS/2)*1000) {}
-
-protected:
- virtual void NotifyExpired(nsSHEntry* aObj) {
- RemoveObject(aObj);
- aObj->Expire();
- }
-};
-
-static HistoryTracker *gHistoryTracker = nsnull;
static PRUint32 gEntryID = 0;
-static PRUint64 gEntryDocIdentifier = 0;
-
-nsresult nsSHEntry::Startup()
-{
- gHistoryTracker = new HistoryTracker();
- return gHistoryTracker ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
-}
-
-void nsSHEntry::Shutdown()
-{
- delete gHistoryTracker;
- gHistoryTracker = nsnull;
-}
-
-static void StopTrackingEntry(nsSHEntry *aEntry)
-{
- if (aEntry->GetExpirationState()->IsTracked()) {
- gHistoryTracker->RemoveObject(aEntry);
- }
-}
//*****************************************************************************
//*** nsSHEntry: Object Management
//*****************************************************************************
-nsSHEntry::nsSHEntry()
+nsSHEntry::nsSHEntry()
: mLoadType(0)
, mID(gEntryID++)
- , mDocIdentifier(gEntryDocIdentifier++)
, mScrollPositionX(0)
, mScrollPositionY(0)
, mURIWasModified(PR_FALSE)
- , mIsFrameNavigation(PR_FALSE)
- , mSaveLayoutState(PR_TRUE)
- , mExpired(PR_FALSE)
- , mSticky(PR_TRUE)
- , mDynamicallyCreated(PR_FALSE)
- , mParent(nsnull)
- , mViewerBounds(0, 0, 0, 0)
- , mDocShellID(0)
- , mLastTouched(0)
{
+ mShared = new nsSHEntryShared();
}
nsSHEntry::nsSHEntry(const nsSHEntry &other)
- : mURI(other.mURI)
+ : mShared(other.mShared)
+ , mURI(other.mURI)
, mReferrerURI(other.mReferrerURI)
- // XXX why not copy mDocument?
, mTitle(other.mTitle)
, mPostData(other.mPostData)
- , mLayoutHistoryState(other.mLayoutHistoryState)
, mLoadType(0) // XXX why not copy?
, mID(other.mID)
- , mDocIdentifier(other.mDocIdentifier)
, mScrollPositionX(0) // XXX why not copy?
, mScrollPositionY(0) // XXX why not copy?
, mURIWasModified(other.mURIWasModified)
- , mIsFrameNavigation(other.mIsFrameNavigation)
- , mSaveLayoutState(other.mSaveLayoutState)
- , mExpired(other.mExpired)
- , mSticky(PR_TRUE)
- , mDynamicallyCreated(other.mDynamicallyCreated)
- // XXX why not copy mContentType?
- , mCacheKey(other.mCacheKey)
- , mParent(other.mParent)
- , mViewerBounds(0, 0, 0, 0)
- , mOwner(other.mOwner)
- , mDocShellID(other.mDocShellID)
, mStateData(other.mStateData)
{
}
static PRBool
ClearParentPtr(nsISHEntry* aEntry, void* /* aData */)
{
if (aEntry) {
aEntry->SetParent(nsnull);
}
return PR_TRUE;
}
nsSHEntry::~nsSHEntry()
{
- StopTrackingEntry(this);
-
- // Since we never really remove kids from SHEntrys, we need to null
- // out the mParent pointers on all our kids.
+ // Null out the mParent pointers on all our kids.
mChildren.EnumerateForwards(ClearParentPtr, nsnull);
- mChildren.Clear();
-
- if (mContentViewer) {
- // RemoveFromBFCacheSync is virtual, so call the nsSHEntry version
- // explicitly
- nsSHEntry::RemoveFromBFCacheSync();
- }
-
- mEditorData = nsnull;
-
-#ifdef DEBUG
- // This is not happening as far as I can tell from breakpad as of early November 2007
- nsExpirationTracker<nsSHEntry,3>::Iterator iterator(gHistoryTracker);
- nsSHEntry* elem;
- while ((elem = iterator.Next()) != nsnull) {
- NS_ASSERTION(elem != this, "Found dead entry still in the tracker!");
- }
-#endif
}
//*****************************************************************************
// nsSHEntry: nsISupports
//*****************************************************************************
-NS_IMPL_ISUPPORTS5(nsSHEntry, nsISHContainer, nsISHEntry, nsIHistoryEntry,
- nsIMutationObserver, nsISHEntryInternal)
+NS_IMPL_ISUPPORTS4(nsSHEntry, nsISHContainer, nsISHEntry, nsIHistoryEntry,
+ nsISHEntryInternal)
//*****************************************************************************
// nsSHEntry: nsISHEntry
//*****************************************************************************
NS_IMETHODIMP nsSHEntry::SetScrollPosition(PRInt32 x, PRInt32 y)
{
mScrollPositionX = x;
@@ -246,45 +162,23 @@ NS_IMETHODIMP nsSHEntry::SetReferrerURI(
{
mReferrerURI = aReferrerURI;
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::SetContentViewer(nsIContentViewer *aViewer)
{
- NS_PRECONDITION(!aViewer || !mContentViewer, "SHEntry already contains viewer");
-
- if (mContentViewer || !aViewer) {
- DropPresentationState();
- }
-
- mContentViewer = aViewer;
-
- if (mContentViewer) {
- gHistoryTracker->AddObject(this);
-
- nsCOMPtr<nsIDOMDocument> domDoc;
- mContentViewer->GetDOMDocument(getter_AddRefs(domDoc));
- // Store observed document in strong pointer in case it is removed from
- // the contentviewer
- mDocument = do_QueryInterface(domDoc);
- if (mDocument) {
- mDocument->SetBFCacheEntry(this);
- mDocument->AddMutationObserver(this);
- }
- }
-
- return NS_OK;
+ return mShared->SetContentViewer(aViewer);
}
NS_IMETHODIMP
nsSHEntry::GetContentViewer(nsIContentViewer **aResult)
{
- *aResult = mContentViewer;
+ *aResult = mShared->mContentViewer;
NS_IF_ADDREF(*aResult);
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::GetAnyContentViewer(nsISHEntry **aOwnerEntry,
nsIContentViewer **aResult)
{
@@ -314,24 +208,24 @@ nsSHEntry::GetAnyContentViewer(nsISHEntr
}
}
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::SetSticky(PRBool aSticky)
{
- mSticky = aSticky;
+ mShared->mSticky = aSticky;
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::GetSticky(PRBool *aSticky)
{
- *aSticky = mSticky;
+ *aSticky = mShared->mSticky;
return NS_OK;
}
NS_IMETHODIMP nsSHEntry::GetTitle(PRUnichar** aTitle)
{
// Check for empty title...
if (mTitle.IsEmpty() && mURI) {
// Default title is the URL.
@@ -360,26 +254,28 @@ NS_IMETHODIMP nsSHEntry::GetPostData(nsI
NS_IMETHODIMP nsSHEntry::SetPostData(nsIInputStream* aPostData)
{
mPostData = aPostData;
return NS_OK;
}
NS_IMETHODIMP nsSHEntry::GetLayoutHistoryState(nsILayoutHistoryState** aResult)
{
- *aResult = mLayoutHistoryState;
+ *aResult = mShared->mLayoutHistoryState;
NS_IF_ADDREF(*aResult);
return NS_OK;
}
NS_IMETHODIMP nsSHEntry::SetLayoutHistoryState(nsILayoutHistoryState* aState)
{
- mLayoutHistoryState = aState;
- if (mLayoutHistoryState)
- mLayoutHistoryState->SetScrollPositionOnly(!mSaveLayoutState);
+ mShared->mLayoutHistoryState = aState;
+ if (mShared->mLayoutHistoryState) {
+ mShared->mLayoutHistoryState->
+ SetScrollPositionOnly(!mShared->mSaveLayoutState);
+ }
return NS_OK;
}
NS_IMETHODIMP nsSHEntry::GetLoadType(PRUint32 * aResult)
{
*aResult = mLoadType;
return NS_OK;
@@ -398,209 +294,236 @@ NS_IMETHODIMP nsSHEntry::GetID(PRUint32
}
NS_IMETHODIMP nsSHEntry::SetID(PRUint32 aID)
{
mID = aID;
return NS_OK;
}
-NS_IMETHODIMP nsSHEntry::GetDocIdentifier(PRUint64 * aResult)
-{
- *aResult = mDocIdentifier;
- return NS_OK;
-}
-
-NS_IMETHODIMP nsSHEntry::SetDocIdentifier(PRUint64 aDocIdentifier)
+nsSHEntryShared* nsSHEntry::GetSharedState()
{
- // This ensures that after a session restore, gEntryDocIdentifier is greater
- // than all SHEntries' docIdentifiers, which ensures that we'll never repeat
- // a doc identifier.
- if (aDocIdentifier >= gEntryDocIdentifier)
- gEntryDocIdentifier = aDocIdentifier + 1;
-
- mDocIdentifier = aDocIdentifier;
- return NS_OK;
-}
-
-NS_IMETHODIMP nsSHEntry::SetUniqueDocIdentifier()
-{
- mDocIdentifier = gEntryDocIdentifier++;
- return NS_OK;
+ return mShared;
}
NS_IMETHODIMP nsSHEntry::GetIsSubFrame(PRBool * aFlag)
{
- *aFlag = mIsFrameNavigation;
+ *aFlag = mShared->mIsFrameNavigation;
return NS_OK;
}
NS_IMETHODIMP nsSHEntry::SetIsSubFrame(PRBool aFlag)
{
- mIsFrameNavigation = aFlag;
+ mShared->mIsFrameNavigation = aFlag;
return NS_OK;
}
NS_IMETHODIMP nsSHEntry::GetCacheKey(nsISupports** aResult)
{
- *aResult = mCacheKey;
+ *aResult = mShared->mCacheKey;
NS_IF_ADDREF(*aResult);
return NS_OK;
}
NS_IMETHODIMP nsSHEntry::SetCacheKey(nsISupports* aCacheKey)
{
- mCacheKey = aCacheKey;
+ mShared->mCacheKey = aCacheKey;
return NS_OK;
}
NS_IMETHODIMP nsSHEntry::GetSaveLayoutStateFlag(PRBool * aFlag)
{
- *aFlag = mSaveLayoutState;
+ *aFlag = mShared->mSaveLayoutState;
return NS_OK;
}
NS_IMETHODIMP nsSHEntry::SetSaveLayoutStateFlag(PRBool aFlag)
{
- mSaveLayoutState = aFlag;
- if (mLayoutHistoryState)
- mLayoutHistoryState->SetScrollPositionOnly(!aFlag);
+ mShared->mSaveLayoutState = aFlag;
+ if (mShared->mLayoutHistoryState) {
+ mShared->mLayoutHistoryState->SetScrollPositionOnly(!aFlag);
+ }
return NS_OK;
}
NS_IMETHODIMP nsSHEntry::GetExpirationStatus(PRBool * aFlag)
{
- *aFlag = mExpired;
+ *aFlag = mShared->mExpired;
return NS_OK;
}
NS_IMETHODIMP nsSHEntry::SetExpirationStatus(PRBool aFlag)
{
- mExpired = aFlag;
+ mShared->mExpired = aFlag;
return NS_OK;
}
NS_IMETHODIMP nsSHEntry::GetContentType(nsACString& aContentType)
{
- aContentType = mContentType;
+ aContentType = mShared->mContentType;
return NS_OK;
}
NS_IMETHODIMP nsSHEntry::SetContentType(const nsACString& aContentType)
{
- mContentType = aContentType;
+ mShared->mContentType = aContentType;
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::Create(nsIURI * aURI, const nsAString &aTitle,
nsIInputStream * aInputStream,
nsILayoutHistoryState * aLayoutHistoryState,
nsISupports * aCacheKey, const nsACString& aContentType,
nsISupports* aOwner,
PRUint64 aDocShellID, PRBool aDynamicCreation)
{
mURI = aURI;
mTitle = aTitle;
mPostData = aInputStream;
- mCacheKey = aCacheKey;
- mContentType = aContentType;
- mOwner = aOwner;
- mDocShellID = aDocShellID;
- mDynamicallyCreated = aDynamicCreation;
// Set the LoadType by default to loadHistory during creation
mLoadType = (PRUint32) nsIDocShellLoadInfo::loadHistory;
+ mShared->mCacheKey = aCacheKey;
+ mShared->mContentType = aContentType;
+ mShared->mOwner = aOwner;
+ mShared->mDocShellID = aDocShellID;
+ mShared->mDynamicallyCreated = aDynamicCreation;
+
// By default all entries are set false for subframe flag.
// nsDocShell::CloneAndReplace() which creates entries for
// all subframe navigations, sets the flag to true.
- mIsFrameNavigation = PR_FALSE;
+ mShared->mIsFrameNavigation = PR_FALSE;
// By default we save LayoutHistoryState
- mSaveLayoutState = PR_TRUE;
- mLayoutHistoryState = aLayoutHistoryState;
+ mShared->mSaveLayoutState = PR_TRUE;
+ mShared->mLayoutHistoryState = aLayoutHistoryState;
//By default the page is not expired
- mExpired = PR_FALSE;
+ mShared->mExpired = PR_FALSE;
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::Clone(nsISHEntry ** aResult)
{
*aResult = new nsSHEntry(*this);
- if (!*aResult)
- return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(*aResult);
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::GetParent(nsISHEntry ** aResult)
{
NS_ENSURE_ARG_POINTER(aResult);
- *aResult = mParent;
+ *aResult = mShared->mParent;
NS_IF_ADDREF(*aResult);
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::SetParent(nsISHEntry * aParent)
{
/* parent not Addrefed on purpose to avoid cyclic reference
* Null parent is OK
*
* XXX this method should not be scriptable if this is the case!!
*/
- mParent = aParent;
+ mShared->mParent = aParent;
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::SetWindowState(nsISupports *aState)
{
- mWindowState = aState;
+ mShared->mWindowState = aState;
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::GetWindowState(nsISupports **aState)
{
- NS_IF_ADDREF(*aState = mWindowState);
+ NS_IF_ADDREF(*aState = mShared->mWindowState);
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::SetViewerBounds(const nsIntRect &aBounds)
{
- mViewerBounds = aBounds;
+ mShared->mViewerBounds = aBounds;
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::GetViewerBounds(nsIntRect &aBounds)
{
- aBounds = mViewerBounds;
+ aBounds = mShared->mViewerBounds;
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::GetOwner(nsISupports **aOwner)
{
- NS_IF_ADDREF(*aOwner = mOwner);
+ NS_IF_ADDREF(*aOwner = mShared->mOwner);
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::SetOwner(nsISupports *aOwner)
{
- mOwner = aOwner;
+ mShared->mOwner = aOwner;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetBFCacheEntry(nsIBFCacheEntry **aEntry)
+{
+ NS_ENSURE_ARG_POINTER(aEntry);
+ NS_IF_ADDREF(*aEntry = mShared);
+ return NS_OK;
+}
+
+PRBool
+nsSHEntry::HasBFCacheEntry(nsIBFCacheEntry *aEntry)
+{
+ return static_cast<nsIBFCacheEntry*>(mShared) == aEntry;
+}
+
+NS_IMETHODIMP
+nsSHEntry::AdoptBFCacheEntry(nsISHEntry *aEntry)
+{
+ nsCOMPtr<nsISHEntryInternal> shEntry = do_QueryInterface(aEntry);
+ NS_ENSURE_STATE(shEntry);
+
+ nsSHEntryShared *shared = shEntry->GetSharedState();
+ NS_ENSURE_STATE(shared);
+
+ mShared = shared;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SharesDocumentWith(nsISHEntry *aEntry, PRBool *aOut)
+{
+ NS_ENSURE_ARG_POINTER(aOut);
+
+ nsCOMPtr<nsISHEntryInternal> internal = do_QueryInterface(aEntry);
+ NS_ENSURE_STATE(internal);
+
+ *aOut = mShared == internal->GetSharedState();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::AbandonBFCacheEntry()
+{
+ mShared = nsSHEntryShared::Duplicate(mShared);
return NS_OK;
}
//*****************************************************************************
// nsSHEntry: nsISHContainer
//*****************************************************************************
NS_IMETHODIMP
@@ -748,266 +671,87 @@ nsSHEntry::GetChildAt(PRInt32 aIndex, ns
}
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::AddChildShell(nsIDocShellTreeItem *aShell)
{
NS_ASSERTION(aShell, "Null child shell added to history entry");
- mChildShells.AppendObject(aShell);
+ mShared->mChildShells.AppendObject(aShell);
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::ChildShellAt(PRInt32 aIndex, nsIDocShellTreeItem **aShell)
{
- NS_IF_ADDREF(*aShell = mChildShells.SafeObjectAt(aIndex));
+ NS_IF_ADDREF(*aShell = mShared->mChildShells.SafeObjectAt(aIndex));
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::ClearChildShells()
{
- mChildShells.Clear();
+ mShared->mChildShells.Clear();
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::GetRefreshURIList(nsISupportsArray **aList)
{
- NS_IF_ADDREF(*aList = mRefreshURIList);
+ NS_IF_ADDREF(*aList = mShared->mRefreshURIList);
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::SetRefreshURIList(nsISupportsArray *aList)
{
- mRefreshURIList = aList;
+ mShared->mRefreshURIList = aList;
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::SyncPresentationState()
{
- if (mContentViewer && mWindowState) {
- // If we have a content viewer and a window state, we should be ok.
- return NS_OK;
- }
-
- DropPresentationState();
-
- return NS_OK;
-}
-
-void
-nsSHEntry::DropPresentationState()
-{
- nsRefPtr<nsSHEntry> kungFuDeathGrip = this;
-
- if (mDocument) {
- mDocument->SetBFCacheEntry(nsnull);
- mDocument->RemoveMutationObserver(this);
- mDocument = nsnull;
- }
- if (mContentViewer)
- mContentViewer->ClearHistoryEntry();
-
- StopTrackingEntry(this);
- mContentViewer = nsnull;
- mSticky = PR_TRUE;
- mWindowState = nsnull;
- mViewerBounds.SetRect(0, 0, 0, 0);
- mChildShells.Clear();
- mRefreshURIList = nsnull;
- mEditorData = nsnull;
-}
-
-void
-nsSHEntry::Expire()
-{
- // This entry has timed out. If we still have a content viewer, we need to
- // get it evicted.
- if (!mContentViewer)
- return;
- nsCOMPtr<nsISupports> container;
- mContentViewer->GetContainer(getter_AddRefs(container));
- nsCOMPtr<nsIDocShellTreeItem> treeItem = do_QueryInterface(container);
- if (!treeItem)
- return;
- // We need to find the root DocShell since only that object has an
- // SHistory and we need the SHistory to evict content viewers
- nsCOMPtr<nsIDocShellTreeItem> root;
- treeItem->GetSameTypeRootTreeItem(getter_AddRefs(root));
- nsCOMPtr<nsIWebNavigation> webNav = do_QueryInterface(root);
- nsCOMPtr<nsISHistory> history;
- webNav->GetSessionHistory(getter_AddRefs(history));
- nsCOMPtr<nsISHistoryInternal> historyInt = do_QueryInterface(history);
- if (!historyInt)
- return;
- historyInt->EvictExpiredContentViewerForEntry(this);
-}
-
-//*****************************************************************************
-// nsSHEntry: nsIMutationObserver
-//*****************************************************************************
-
-void
-nsSHEntry::NodeWillBeDestroyed(const nsINode* aNode)
-{
- NS_NOTREACHED("Document destroyed while we're holding a strong ref to it");
-}
-
-void
-nsSHEntry::CharacterDataWillChange(nsIDocument* aDocument,
- nsIContent* aContent,
- CharacterDataChangeInfo* aInfo)
-{
+ return mShared->SyncPresentationState();
}
void
-nsSHEntry::CharacterDataChanged(nsIDocument* aDocument,
- nsIContent* aContent,
- CharacterDataChangeInfo* aInfo)
-{
- RemoveFromBFCacheAsync();
-}
-
-void
-nsSHEntry::AttributeWillChange(nsIDocument* aDocument,
- dom::Element* aContent,
- PRInt32 aNameSpaceID,
- nsIAtom* aAttribute,
- PRInt32 aModType)
-{
-}
-
-void
-nsSHEntry::AttributeChanged(nsIDocument* aDocument,
- dom::Element* aElement,
- PRInt32 aNameSpaceID,
- nsIAtom* aAttribute,
- PRInt32 aModType)
-{
- RemoveFromBFCacheAsync();
-}
-
-void
-nsSHEntry::ContentAppended(nsIDocument* aDocument,
- nsIContent* aContainer,
- nsIContent* aFirstNewContent,
- PRInt32 /* unused */)
-{
- RemoveFromBFCacheAsync();
-}
-
-void
-nsSHEntry::ContentInserted(nsIDocument* aDocument,
- nsIContent* aContainer,
- nsIContent* aChild,
- PRInt32 /* unused */)
-{
- RemoveFromBFCacheAsync();
-}
-
-void
-nsSHEntry::ContentRemoved(nsIDocument* aDocument,
- nsIContent* aContainer,
- nsIContent* aChild,
- PRInt32 aIndexInContainer,
- nsIContent* aPreviousSibling)
-{
- RemoveFromBFCacheAsync();
-}
-
-void
-nsSHEntry::ParentChainChanged(nsIContent *aContent)
-{
-}
-
-class DestroyViewerEvent : public nsRunnable
-{
-public:
- DestroyViewerEvent(nsIContentViewer* aViewer, nsIDocument* aDocument)
- : mViewer(aViewer),
- mDocument(aDocument)
- {}
-
- NS_IMETHOD Run()
- {
- if (mViewer)
- mViewer->Destroy();
- return NS_OK;
- }
-
- nsCOMPtr<nsIContentViewer> mViewer;
- nsCOMPtr<nsIDocument> mDocument;
-};
-
-void
nsSHEntry::RemoveFromBFCacheSync()
{
- NS_ASSERTION(mContentViewer && mDocument,
- "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();
- }
+ mShared->RemoveFromBFCacheSync();
}
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.
-
- nsCOMPtr<nsIRunnable> evt =
- new DestroyViewerEvent(mContentViewer, mDocument);
- nsresult rv = NS_DispatchToCurrentThread(evt);
- if (NS_FAILED(rv)) {
- NS_WARNING("failed to dispatch DestroyViewerEvent");
- }
- else {
- // Drop presentation. Also ensures that we don't post more then one
- // PLEvent. Only do this if we succeeded in posting the event since
- // otherwise the document could be torn down mid mutation causing crashes.
- DropPresentationState();
- }
- // Warning! The call to DropPresentationState could have dropped the last
- // reference to this nsSHEntry, so no accessing members beyond here.
+ mShared->RemoveFromBFCacheAsync();
}
nsDocShellEditorData*
nsSHEntry::ForgetEditorData()
{
- return mEditorData.forget();
+ // XXX jlebar Check how this is used.
+ return mShared->mEditorData.forget();
}
void
nsSHEntry::SetEditorData(nsDocShellEditorData* aData)
{
- NS_ASSERTION(!(aData && mEditorData),
+ NS_ASSERTION(!(aData && mShared->mEditorData),
"We're going to overwrite an owning ref!");
- if (mEditorData != aData)
- mEditorData = aData;
+ if (mShared->mEditorData != aData) {
+ mShared->mEditorData = aData;
+ }
}
PRBool
nsSHEntry::HasDetachedEditor()
{
- return mEditorData != nsnull;
+ return mShared->mEditorData != nsnull;
}
NS_IMETHODIMP
nsSHEntry::GetStateData(nsIStructuredCloneContainer **aContainer)
{
NS_ENSURE_ARG_POINTER(aContainer);
NS_IF_ADDREF(*aContainer = mStateData);
return NS_OK;
@@ -1018,17 +762,17 @@ nsSHEntry::SetStateData(nsIStructuredClo
{
mStateData = aContainer;
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::IsDynamicallyAdded(PRBool* aAdded)
{
- *aAdded = mDynamicallyCreated;
+ *aAdded = mShared->mDynamicallyCreated;
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::HasDynamicallyAddedChild(PRBool* aAdded)
{
*aAdded = PR_FALSE;
for (PRInt32 i = 0; i < mChildren.Count(); ++i) {
@@ -1041,34 +785,33 @@ nsSHEntry::HasDynamicallyAddedChild(PRBo
}
}
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::GetDocshellID(PRUint64* aID)
{
- *aID = mDocShellID;
+ *aID = mShared->mDocShellID;
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::SetDocshellID(PRUint64 aID)
{
- mDocShellID = aID;
+ mShared->mDocShellID = aID;
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::GetLastTouched(PRUint32 *aLastTouched)
{
- *aLastTouched = mLastTouched;
+ *aLastTouched = mShared->mLastTouched;
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::SetLastTouched(PRUint32 aLastTouched)
{
- mLastTouched = aLastTouched;
+ mShared->mLastTouched = aLastTouched;
return NS_OK;
}
-
--- a/docshell/shistory/src/nsSHEntry.h
+++ b/docshell/shistory/src/nsSHEntry.h
@@ -37,90 +37,62 @@
*
* ***** END LICENSE BLOCK ***** */
#ifndef nsSHEntry_h
#define nsSHEntry_h
// Helper Classes
#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
#include "nsCOMArray.h"
#include "nsString.h"
-#include "nsAutoPtr.h"
// Interfaces needed
-#include "nsIContentViewer.h"
#include "nsIInputStream.h"
-#include "nsILayoutHistoryState.h"
#include "nsISHEntry.h"
#include "nsISHContainer.h"
#include "nsIURI.h"
-#include "nsIEnumerator.h"
#include "nsIHistoryEntry.h"
-#include "nsRect.h"
-#include "nsISupportsArray.h"
-#include "nsIMutationObserver.h"
-#include "nsExpirationTracker.h"
-#include "nsDocShellEditorData.h"
+
+class nsSHEntryShared;
class nsSHEntry : public nsISHEntry,
public nsISHContainer,
- public nsIMutationObserver,
public nsISHEntryInternal
{
public:
nsSHEntry();
nsSHEntry(const nsSHEntry &other);
NS_DECL_ISUPPORTS
NS_DECL_NSIHISTORYENTRY
NS_DECL_NSISHENTRY
NS_DECL_NSISHENTRYINTERNAL
NS_DECL_NSISHCONTAINER
- NS_DECL_NSIMUTATIONOBSERVER
void DropPresentationState();
- void Expire();
-
- nsExpirationState *GetExpirationState() { return &mExpirationState; }
-
static nsresult Startup();
static void Shutdown();
private:
~nsSHEntry();
- nsCOMPtr<nsIURI> mURI;
- nsCOMPtr<nsIURI> mReferrerURI;
- nsCOMPtr<nsIContentViewer> mContentViewer;
- nsCOMPtr<nsIDocument> mDocument; // document currently being observed
- nsString mTitle;
- nsCOMPtr<nsIInputStream> mPostData;
- nsCOMPtr<nsILayoutHistoryState> mLayoutHistoryState;
- nsCOMArray<nsISHEntry> mChildren;
- PRUint32 mLoadType;
- PRUint32 mID;
- PRInt64 mDocIdentifier;
- PRInt32 mScrollPositionX;
- PRInt32 mScrollPositionY;
- PRPackedBool mURIWasModified;
- PRPackedBool mIsFrameNavigation;
- PRPackedBool mSaveLayoutState;
- PRPackedBool mExpired;
- PRPackedBool mSticky;
- PRPackedBool mDynamicallyCreated;
- nsCString mContentType;
- nsCOMPtr<nsISupports> mCacheKey;
- nsISHEntry * mParent; // weak reference
- nsCOMPtr<nsISupports> mWindowState;
- nsIntRect mViewerBounds;
- nsCOMArray<nsIDocShellTreeItem> mChildShells;
- nsCOMPtr<nsISupportsArray> mRefreshURIList;
- nsCOMPtr<nsISupports> mOwner;
- nsExpirationState mExpirationState;
- nsAutoPtr<nsDocShellEditorData> mEditorData;
- PRUint64 mDocShellID;
- PRUint32 mLastTouched;
+ // We share the state in here with other SHEntries which correspond to the
+ // same document.
+ nsRefPtr<nsSHEntryShared> mShared;
+
+ // See nsSHEntry.idl for comments on these members.
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mReferrerURI;
+ nsString mTitle;
+ nsCOMPtr<nsIInputStream> mPostData;
+ PRUint32 mLoadType;
+ PRUint32 mID;
+ PRInt32 mScrollPositionX;
+ PRInt32 mScrollPositionY;
+ nsCOMArray<nsISHEntry> mChildren;
+ PRPackedBool mURIWasModified;
nsCOMPtr<nsIStructuredCloneContainer> mStateData;
};
#endif /* nsSHEntry_h */
new file mode 100644
--- /dev/null
+++ b/docshell/shistory/src/nsSHEntryShared.cpp
@@ -0,0 +1,400 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ *
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Justin Lebar <justin.lebar@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsSHEntryShared.h"
+#include "nsISHistory.h"
+#include "nsISHistoryInternal.h"
+#include "nsIDocument.h"
+#include "nsIWebNavigation.h"
+#include "nsIContentViewer.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsISupportsArray.h"
+#include "nsDocShellEditorData.h"
+#include "nsThreadUtils.h"
+#include "nsILayoutHistoryState.h"
+#include "prprf.h"
+
+namespace dom = mozilla::dom;
+
+namespace {
+
+PRUint64 gSHEntrySharedID = 0;
+
+} // anonymous namespace
+
+// Hardcode this to time out unused content viewers after 30 minutes
+// XXX jlebar shouldn't this be a pref?
+#define CONTENT_VIEWER_TIMEOUT_SECONDS (30*60)
+
+typedef nsExpirationTracker<nsSHEntryShared, 3> HistoryTrackerBase;
+class HistoryTracker : public HistoryTrackerBase {
+public:
+ // Expire cached contentviewers after 20-30 minutes in the cache.
+ HistoryTracker()
+ : HistoryTrackerBase(1000 * CONTENT_VIEWER_TIMEOUT_SECONDS / 2)
+ {
+ }
+
+protected:
+ virtual void NotifyExpired(nsSHEntryShared *aObj) {
+ RemoveObject(aObj);
+ aObj->Expire();
+ }
+};
+
+static HistoryTracker *gHistoryTracker = nsnull;
+
+void
+nsSHEntryShared::Startup()
+{
+ gHistoryTracker = new HistoryTracker();
+}
+
+void
+nsSHEntryShared::Shutdown()
+{
+ delete gHistoryTracker;
+ gHistoryTracker = nsnull;
+}
+
+nsSHEntryShared::nsSHEntryShared()
+ : mDocShellID(0)
+ , mParent(nsnull)
+ , mIsFrameNavigation(PR_FALSE)
+ , mSaveLayoutState(PR_TRUE)
+ , mSticky(PR_TRUE)
+ , mDynamicallyCreated(PR_FALSE)
+ , mLastTouched(0)
+ , mID(gSHEntrySharedID++)
+ , mExpired(PR_FALSE)
+ , mViewerBounds(0, 0, 0, 0)
+{
+}
+
+nsSHEntryShared::~nsSHEntryShared()
+{
+ RemoveFromExpirationTracker();
+
+#ifdef DEBUG
+ // Check that we're not still on track to expire. We shouldn't be, because
+ // we just removed ourselves!
+ nsExpirationTracker<nsSHEntryShared, 3>::Iterator
+ iterator(gHistoryTracker);
+
+ nsSHEntryShared *elem;
+ while ((elem = iterator.Next()) != nsnull) {
+ NS_ASSERTION(elem != this, "Found dead entry still in the tracker!");
+ }
+#endif
+
+ if (mContentViewer) {
+ RemoveFromBFCacheSync();
+ }
+}
+
+NS_IMPL_ISUPPORTS2(nsSHEntryShared, nsIBFCacheEntry, nsIMutationObserver)
+
+already_AddRefed<nsSHEntryShared>
+nsSHEntryShared::Duplicate(nsSHEntryShared *aEntry)
+{
+ nsRefPtr<nsSHEntryShared> newEntry = new nsSHEntryShared();
+
+ newEntry->mDocShellID = aEntry->mDocShellID;
+ newEntry->mChildShells.AppendObjects(aEntry->mChildShells);
+ newEntry->mOwner = aEntry->mOwner;
+ newEntry->mParent = aEntry->mParent;
+ newEntry->mContentType.Assign(aEntry->mContentType);
+ newEntry->mIsFrameNavigation = aEntry->mIsFrameNavigation;
+ newEntry->mSaveLayoutState = aEntry->mSaveLayoutState;
+ newEntry->mSticky = aEntry->mSticky;
+ newEntry->mDynamicallyCreated = aEntry->mDynamicallyCreated;
+ newEntry->mCacheKey = aEntry->mCacheKey;
+ newEntry->mLastTouched = aEntry->mLastTouched;
+
+ return newEntry.forget();
+}
+
+void nsSHEntryShared::RemoveFromExpirationTracker()
+{
+ if (GetExpirationState()->IsTracked()) {
+ gHistoryTracker->RemoveObject(this);
+ }
+}
+
+nsresult
+nsSHEntryShared::SyncPresentationState()
+{
+ if (mContentViewer && mWindowState) {
+ // If we have a content viewer and a window state, we should be ok.
+ return NS_OK;
+ }
+
+ DropPresentationState();
+
+ return NS_OK;
+}
+
+void
+nsSHEntryShared::DropPresentationState()
+{
+ nsRefPtr<nsSHEntryShared> kungFuDeathGrip = this;
+
+ if (mDocument) {
+ mDocument->SetBFCacheEntry(nsnull);
+ mDocument->RemoveMutationObserver(this);
+ mDocument = nsnull;
+ }
+ if (mContentViewer) {
+ mContentViewer->ClearHistoryEntry();
+ }
+
+ RemoveFromExpirationTracker();
+ mContentViewer = nsnull;
+ mSticky = PR_TRUE;
+ mWindowState = nsnull;
+ mViewerBounds.SetRect(0, 0, 0, 0);
+ mChildShells.Clear();
+ mRefreshURIList = nsnull;
+ mEditorData = nsnull;
+}
+
+void
+nsSHEntryShared::Expire()
+{
+ // This entry has timed out. If we still have a content viewer, we need to
+ // evict it.
+ if (!mContentViewer) {
+ return;
+ }
+ nsCOMPtr<nsISupports> container;
+ mContentViewer->GetContainer(getter_AddRefs(container));
+ nsCOMPtr<nsIDocShellTreeItem> treeItem = do_QueryInterface(container);
+ if (!treeItem) {
+ return;
+ }
+ // We need to find the root DocShell since only that object has an
+ // SHistory and we need the SHistory to evict content viewers
+ nsCOMPtr<nsIDocShellTreeItem> root;
+ treeItem->GetSameTypeRootTreeItem(getter_AddRefs(root));
+ nsCOMPtr<nsIWebNavigation> webNav = do_QueryInterface(root);
+ nsCOMPtr<nsISHistory> history;
+ webNav->GetSessionHistory(getter_AddRefs(history));
+ nsCOMPtr<nsISHistoryInternal> historyInt = do_QueryInterface(history);
+ if (!historyInt) {
+ return;
+ }
+ historyInt->EvictExpiredContentViewerForEntry(this);
+}
+
+nsresult
+nsSHEntryShared::SetContentViewer(nsIContentViewer *aViewer)
+{
+ NS_PRECONDITION(!aViewer || !mContentViewer,
+ "SHEntryShared already contains viewer");
+
+ if (mContentViewer || !aViewer) {
+ DropPresentationState();
+ }
+
+ mContentViewer = aViewer;
+
+ if (mContentViewer) {
+ gHistoryTracker->AddObject(this);
+
+ nsCOMPtr<nsIDOMDocument> domDoc;
+ mContentViewer->GetDOMDocument(getter_AddRefs(domDoc));
+ // Store observed document in strong pointer in case it is removed from
+ // the contentviewer
+ mDocument = do_QueryInterface(domDoc);
+ if (mDocument) {
+ mDocument->SetBFCacheEntry(this);
+ mDocument->AddMutationObserver(this);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsSHEntryShared::RemoveFromBFCacheSync()
+{
+ NS_ASSERTION(mContentViewer && mDocument,
+ "we're not in the bfcache!");
+
+ nsCOMPtr<nsIContentViewer> viewer = mContentViewer;
+ DropPresentationState();
+
+ // Warning! The call to DropPresentationState could have dropped the last
+ // reference to this object, so don't access members beyond here.
+
+ if (viewer) {
+ viewer->Destroy();
+ }
+
+ return NS_OK;
+}
+
+class DestroyViewerEvent : public nsRunnable
+{
+public:
+ DestroyViewerEvent(nsIContentViewer* aViewer, nsIDocument* aDocument)
+ : mViewer(aViewer),
+ mDocument(aDocument)
+ {}
+
+ NS_IMETHOD Run()
+ {
+ if (mViewer) {
+ mViewer->Destroy();
+ }
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIContentViewer> mViewer;
+ nsCOMPtr<nsIDocument> mDocument;
+};
+
+nsresult
+nsSHEntryShared::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.
+
+ nsCOMPtr<nsIRunnable> evt =
+ new DestroyViewerEvent(mContentViewer, mDocument);
+ nsresult rv = NS_DispatchToCurrentThread(evt);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to dispatch DestroyViewerEvent");
+ } else {
+ // Drop presentation. Only do this if we succeeded in posting the event
+ // since otherwise the document could be torn down mid-mutation, causing
+ // crashes.
+ DropPresentationState();
+ }
+
+ // Careful! The call to DropPresentationState could have dropped the last
+ // reference to this nsSHEntryShared, so don't access members beyond here.
+
+ return NS_OK;
+}
+
+nsresult
+nsSHEntryShared::GetID(PRUint64 *aID)
+{
+ *aID = mID;
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsSHEntryShared: nsIMutationObserver
+//*****************************************************************************
+
+void
+nsSHEntryShared::NodeWillBeDestroyed(const nsINode* aNode)
+{
+ NS_NOTREACHED("Document destroyed while we're holding a strong ref to it");
+}
+
+void
+nsSHEntryShared::CharacterDataWillChange(nsIDocument* aDocument,
+ nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+}
+
+void
+nsSHEntryShared::CharacterDataChanged(nsIDocument* aDocument,
+ nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+ RemoveFromBFCacheAsync();
+}
+
+void
+nsSHEntryShared::AttributeWillChange(nsIDocument* aDocument,
+ dom::Element* aContent,
+ PRInt32 aNameSpaceID,
+ nsIAtom* aAttribute,
+ PRInt32 aModType)
+{
+}
+
+void
+nsSHEntryShared::AttributeChanged(nsIDocument* aDocument,
+ dom::Element* aElement,
+ PRInt32 aNameSpaceID,
+ nsIAtom* aAttribute,
+ PRInt32 aModType)
+{
+ RemoveFromBFCacheAsync();
+}
+
+void
+nsSHEntryShared::ContentAppended(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ PRInt32 /* unused */)
+{
+ RemoveFromBFCacheAsync();
+}
+
+void
+nsSHEntryShared::ContentInserted(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ PRInt32 /* unused */)
+{
+ RemoveFromBFCacheAsync();
+}
+
+void
+nsSHEntryShared::ContentRemoved(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ PRInt32 aIndexInContainer,
+ nsIContent* aPreviousSibling)
+{
+ RemoveFromBFCacheAsync();
+}
+
+void
+nsSHEntryShared::ParentChainChanged(nsIContent *aContent)
+{
+}
new file mode 100644
--- /dev/null
+++ b/docshell/shistory/src/nsSHEntryShared.h
@@ -0,0 +1,125 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ *
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Justin Lebar <justin.lebar@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef nsSHEntryShared_h__
+#define nsSHEntryShared_h__
+
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsCOMArray.h"
+#include "nsIBFCacheEntry.h"
+#include "nsIMutationObserver.h"
+#include "nsExpirationTracker.h"
+#include "nsRect.h"
+
+class nsSHEntry;
+class nsISHEntry;
+class nsIDocument;
+class nsIContentViewer;
+class nsIDocShellTreeItem;
+class nsILayoutHistoryState;
+class nsISupportsArray;
+class nsDocShellEditorData;
+
+// A document may have multiple SHEntries, either due to hash navigations or
+// calls to history.pushState. SHEntries corresponding to the same document
+// share many members; in particular, they share state related to the
+// back/forward cache.
+//
+// nsSHEntryShared is the vehicle for this sharing.
+class nsSHEntryShared : public nsIBFCacheEntry,
+ public nsIMutationObserver
+{
+ public:
+ static void Startup();
+ static void Shutdown();
+
+ nsSHEntryShared();
+ ~nsSHEntryShared();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMUTATIONOBSERVER
+ NS_DECL_NSIBFCACHEENTRY
+
+ private:
+ friend class nsSHEntry;
+
+ friend class HistoryTracker;
+ friend class nsExpirationTracker<nsSHEntryShared, 3>;
+ nsExpirationState *GetExpirationState() { return &mExpirationState; }
+
+ static already_AddRefed<nsSHEntryShared> Duplicate(nsSHEntryShared *aEntry);
+ void SetDocIdentifier(PRUint64 aDocIdentifier);
+
+ void RemoveFromExpirationTracker();
+ void Expire();
+ nsresult SyncPresentationState();
+ void DropPresentationState();
+
+ nsresult SetContentViewer(nsIContentViewer *aViewer);
+
+ // See nsISHEntry.idl for an explanation of these members.
+
+ // These members are copied by nsSHEntryShared::Duplicate(). If you add a
+ // member here, be sure to update the Duplicate() implementation.
+ PRUint64 mDocShellID;
+ nsCOMArray<nsIDocShellTreeItem> mChildShells;
+ nsCOMPtr<nsISupports> mOwner;
+ nsISHEntry* mParent;
+ nsCString mContentType;
+ PRPackedBool mIsFrameNavigation;
+ PRPackedBool mSaveLayoutState;
+ PRPackedBool mSticky;
+ PRPackedBool mDynamicallyCreated;
+ nsCOMPtr<nsISupports> mCacheKey;
+ PRUint32 mLastTouched;
+
+ // These members aren't copied by nsSHEntryShared::Duplicate() because
+ // they're specific to a particular content viewer.
+ PRUint64 mID;
+ nsCOMPtr<nsIContentViewer> mContentViewer;
+ nsCOMPtr<nsIDocument> mDocument;
+ nsCOMPtr<nsILayoutHistoryState> mLayoutHistoryState;
+ PRPackedBool mExpired;
+ nsCOMPtr<nsISupports> mWindowState;
+ nsIntRect mViewerBounds;
+ nsCOMPtr<nsISupportsArray> mRefreshURIList;
+ nsExpirationState mExpirationState;
+ nsAutoPtr<nsDocShellEditorData> mEditorData;
+};
+
+#endif
--- a/docshell/shistory/src/nsSHistory.cpp
+++ b/docshell/shistory/src/nsSHistory.cpp
@@ -1076,31 +1076,34 @@ nsSHistory::EvictGlobalContentViewer()
}
} else {
// couldn't find a content viewer to evict, so we are done
shouldTryEviction = PR_FALSE;
}
} // while shouldTryEviction
}
-NS_IMETHODIMP
-nsSHistory::EvictExpiredContentViewerForEntry(nsISHEntry *aEntry)
+nsresult
+nsSHistory::EvictExpiredContentViewerForEntry(nsIBFCacheEntry *aEntry)
{
PRInt32 startIndex = NS_MAX(0, mIndex - gHistoryMaxViewers);
PRInt32 endIndex = NS_MIN(mLength - 1,
mIndex + gHistoryMaxViewers);
nsCOMPtr<nsISHTransaction> trans;
GetTransactionAtIndex(startIndex, getter_AddRefs(trans));
PRInt32 i;
for (i = startIndex; trans && i <= endIndex; ++i) {
nsCOMPtr<nsISHEntry> entry;
trans->GetSHEntry(getter_AddRefs(entry));
- if (entry == aEntry)
+
+ // Does entry have the same BFCacheEntry as the argument to this method?
+ if (entry->HasBFCacheEntry(aEntry)) {
break;
+ }
nsISHTransaction *temp = trans;
temp->GetNext(getter_AddRefs(trans));
}
if (i > endIndex)
return NS_OK;
NS_ASSERTION(i != mIndex, "How did the current session entry expire?");
@@ -1112,17 +1115,17 @@ nsSHistory::EvictExpiredContentViewerFor
// that have the expired entry between them and the current entry). Those
// other entries should have timed out already, actually, but this is just
// to be on the safe side.
if (i < mIndex) {
EvictContentViewersInRange(startIndex, i + 1);
} else {
EvictContentViewersInRange(i, endIndex + 1);
}
-
+
return NS_OK;
}
// Evicts all content viewers in all history objects. This is very
// inefficient, because it requires a linear search through all SHistory
// objects for each viewer to be evicted. However, this method is called
// infrequently -- only when the disk or memory cache is cleared.
--- a/docshell/test/Makefile.in
+++ b/docshell/test/Makefile.in
@@ -114,16 +114,17 @@ include $(topsrcdir)/config/rules.mk
test_bug570341.html \
bug570341_recordevents.html \
test_bug668513.html \
bug668513_redirect.html \
bug668513_redirect.html^headers^ \
test_bug669671.html \
file_bug669671.sjs \
test_bug675587.html \
+ test_bfcache_plus_hash.html \
$(NULL)
ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
_TEST_FILES += \
test_bug511449.html \
file_bug511449.html \
$(NULL)
endif
new file mode 100644
--- /dev/null
+++ b/docshell/test/test_bfcache_plus_hash.html
@@ -0,0 +1,121 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=646641
+-->
+<head>
+ <title>Test for Bug 646641</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=646641">Mozilla Bug 646641</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript;version=1.7">
+
+/** Test for Bug 646641 **/
+
+/*
+ * In a popup (because navigating the main frame confuses Mochitest), do the
+ * following:
+ *
+ * * Call history.pushState().
+ * * Navigate to a new page.
+ * * Go back two history entries.
+ *
+ * Check that we go back, we retrieve the document from bfcache.
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+function debug(msg) {
+ // Wrap dump so we can turn debug messages on and off easily.
+ dump(msg + '\n');
+}
+
+var expectedLoadNum = -1;
+function childLoad(n) {
+ if (n == expectedLoadNum) {
+ debug('Got load ' + n);
+ expectedLoadNum = -1;
+
+ // Spin the event loop before calling gGen.next() so the generator runs
+ // outside the onload handler. This prevents us from encountering all
+ // sorts of docshell quirks.
+ //
+ // (I don't know why I need to wrap gGen.next() in a function, but it
+ // throws an error otherwise.)
+ setTimeout(function() { gGen.next() }, 0);
+ }
+ else {
+ debug('Got unexpected load ' + n);
+ ok(false, 'Got unexpected load ' + n);
+ }
+}
+
+var expectedPageshowNum = -1;
+function childPageshow(n) {
+ if (n == expectedPageshowNum) {
+ debug('Got expected pageshow ' + n);
+ expectedPageshowNum = -1;
+ ok(true, 'Got expected pageshow ' + n);
+ setTimeout(function() { gGen.next() }, 0);
+ }
+ else {
+ debug('Got pageshow ' + n);
+ }
+
+ // Since a pageshow comes along with an onload, don't fail the test if we get
+ // an unexpected pageshow.
+}
+
+function waitForLoad(n) {
+ debug('Waiting for load ' + n);
+ expectedLoadNum = n;
+}
+
+function waitForShow(n) {
+ debug('Waiting for show ' + n);
+ expectedPageshowNum = n;
+}
+
+function test() {
+ var popup = window.open('data:text/html,' +
+ '<html><body onload="opener.childLoad(1)" ' +
+ 'onpageshow="opener.childPageshow(1)">' +
+ 'Popup 1' +
+ '</body></html>');
+ waitForLoad(1);
+ yield;
+
+ popup.history.pushState('', '', '');
+
+ popup.location = 'data:text/html,<html><body onload="opener.childLoad(2)">Popup 2</body></html>';
+ waitForLoad(2);
+ yield;
+
+ // Now go back 2. The first page should be retrieved from bfcache.
+ popup.history.go(-2);
+ waitForShow(1);
+ yield;
+
+ popup.close();
+ SimpleTest.finish();
+
+ // Yield once more so we don't throw a StopIteration exception.
+ yield;
+}
+
+var gGen = test();
+gGen.next();
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/indexedDB/IndexedDatabaseManager.cpp
+++ b/dom/indexedDB/IndexedDatabaseManager.cpp
@@ -168,22 +168,19 @@ public:
nsRefPtr<IDBDatabase>& database = mWaitingDatabases[index];
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();
- }
+ nsIBFCacheEntry* bfCacheEntry;
+ if (ownerDoc && (bfCacheEntry = ownerDoc->GetBFCacheEntry())) {
+ bfCacheEntry->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);