Back out bug 683777 (backout of bug 646641) due to moth orange.

This commit is contained in:
Justin Lebar 2011-10-12 23:03:00 -04:00
Родитель c4f7adf6f1
Коммит 1e241eedd7
23 изменённых файлов: 1291 добавлений и 787 удалений

Просмотреть файл

@ -1920,9 +1920,7 @@ SessionStoreService.prototype = {
catch (ex) { debug(ex); } catch (ex) { debug(ex); }
} }
if (aEntry.docIdentifier) { entry.docIdentifier = aEntry.BFCacheEntry.ID;
entry.docIdentifier = aEntry.docIdentifier;
}
if (aEntry.stateData != null) { if (aEntry.stateData != null) {
entry.structuredCloneState = aEntry.stateData.getDataAsBase64(); entry.structuredCloneState = aEntry.stateData.getDataAsBase64();
@ -3029,7 +3027,6 @@ SessionStoreService.prototype = {
browser.__SS_restore_data = tabData.entries[activeIndex] || {}; browser.__SS_restore_data = tabData.entries[activeIndex] || {};
browser.__SS_restore_pageStyle = tabData.pageStyle || ""; browser.__SS_restore_pageStyle = tabData.pageStyle || "";
browser.__SS_restore_tab = aTab; browser.__SS_restore_tab = aTab;
browser.__SS_restore_docIdentifier = curSHEntry.docIdentifier;
didStartLoad = true; didStartLoad = true;
try { try {
// In order to work around certain issues in session history, we need to // In order to work around certain issues in session history, we need to
@ -3184,24 +3181,16 @@ SessionStoreService.prototype = {
} }
if (aEntry.docIdentifier) { if (aEntry.docIdentifier) {
// Get a new document identifier for this entry to ensure that history // If we have a serialized document identifier, try to find an SHEntry
// entries after a session restore are considered to have different // which matches that doc identifier and adopt that SHEntry's
// documents from the history entries before the session restore. // BFCacheEntry. If we don't find a match, insert shEntry as the match
// Document identifiers are 64-bit ints, so JS will loose precision and // for the document identifier.
// start assigning all entries the same doc identifier if these ever get let matchingEntry = aDocIdentMap[aEntry.docIdentifier];
// large enough. if (!matchingEntry) {
// aDocIdentMap[aEntry.docIdentifier] = shEntry;
// 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;
} }
else { else {
shEntry.docIdentifier = ident; shEntry.adoptBFCacheEntry(matchingEntry);
} }
} }
@ -3337,19 +3326,12 @@ SessionStoreService.prototype = {
aBrowser.markupDocumentViewer.authorStyleDisabled = selectedPageStyle == "_nostyle"; 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 // notify the tabbrowser that this document has been completely restored
this._sendTabRestoredNotification(aBrowser.__SS_restore_tab); this._sendTabRestoredNotification(aBrowser.__SS_restore_tab);
delete aBrowser.__SS_restore_data; delete aBrowser.__SS_restore_data;
delete aBrowser.__SS_restore_pageStyle; delete aBrowser.__SS_restore_pageStyle;
delete aBrowser.__SS_restore_tab; delete aBrowser.__SS_restore_tab;
delete aBrowser.__SS_restore_docIdentifier;
}, },
/** /**

Просмотреть файл

@ -118,11 +118,11 @@ function test() {
// history entries: // history entries:
// testURL (state object: null) <-- oldest // testURL (state object: null) <-- oldest
// testURL (state object: {obj1:1}) // testURL (state object: {obj1:1})
// page2 (state object: {obj3:/^a$/}) <-- newest // testURL?page2 (state object: {obj3:/^a$/}) <-- newest
let contentWindow = tab.linkedBrowser.contentWindow; let contentWindow = tab.linkedBrowser.contentWindow;
let history = contentWindow.history; let history = contentWindow.history;
history.pushState({obj1:1}, "title-obj1"); 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"); history.replaceState({obj3:/^a$/}, "title-obj3");
let state = ss.getTabState(tab); let state = ss.getTabState(tab);

Просмотреть файл

@ -69,6 +69,7 @@
#include "nsIAnimationFrameListener.h" #include "nsIAnimationFrameListener.h"
#include "nsEventStates.h" #include "nsEventStates.h"
#include "nsIStructuredCloneContainer.h" #include "nsIStructuredCloneContainer.h"
#include "nsIBFCacheEntry.h"
#include "nsDOMMemoryReporter.h" #include "nsDOMMemoryReporter.h"
class nsIContent; class nsIContent;
@ -125,8 +126,8 @@ class Element;
} // namespace mozilla } // namespace mozilla
#define NS_IDOCUMENT_IID \ #define NS_IDOCUMENT_IID \
{ 0x4114a7c7, 0xb2f4, 0x4dea, \ { 0x448c396a, 0x013c, 0x47b8, \
{ 0xac, 0x78, 0x20, 0xab, 0xda, 0x6f, 0xb2, 0xaf } } { 0x95, 0xf4, 0x56, 0x68, 0x0f, 0x5f, 0x12, 0xf8 } }
// Flag for AddStyleSheet(). // Flag for AddStyleSheet().
#define NS_STYLESHEET_FROM_CATALOG (1 << 0) #define NS_STYLESHEET_FROM_CATALOG (1 << 0)
@ -479,11 +480,15 @@ public:
return GetBFCacheEntry() ? nsnull : mPresShell; return GetBFCacheEntry() ? nsnull : mPresShell;
} }
void SetBFCacheEntry(nsISHEntry* aSHEntry) { void SetBFCacheEntry(nsIBFCacheEntry* aEntry)
mSHEntry = aSHEntry; {
mBFCacheEntry = aEntry;
} }
nsISHEntry* GetBFCacheEntry() const { return mSHEntry; } nsIBFCacheEntry* GetBFCacheEntry() const
{
return mBFCacheEntry;
}
/** /**
* Return the parent document of this document. Will return null * Return the parent document of this document. Will return null
@ -1786,9 +1791,9 @@ protected:
AnimationListenerList mAnimationFrameListeners; AnimationListenerList mAnimationFrameListeners;
// The session history entry in which we're currently bf-cached. Non-null // This object allows us to evict ourself from the back/forward cache. The
// if and only if we're currently in the bfcache. // pointer is non-null iff we're currently in the bfcache.
nsISHEntry* mSHEntry; nsIBFCacheEntry *mBFCacheEntry;
// Our base target. // Our base target.
nsString mBaseTarget; nsString mBaseTarget;

Просмотреть файл

@ -4135,10 +4135,10 @@ nsDocShell::LoadErrorPage(nsIURI *aURI, const PRUnichar *aURL,
mFailedLoadType = mLoadType; mFailedLoadType = mLoadType;
if (mLSHE) { if (mLSHE) {
// If we don't give mLSHE a new doc identifier here, when we go back or // Abandon mLSHE's BFCache entry and create a new one. This way, if
// forward to another SHEntry with the same doc identifier, the error // we go back or forward to another SHEntry with the same doc
// page will persist. // identifier, the error page won't persist.
mLSHE->SetUniqueDocIdentifier(); mLSHE->AbandonBFCacheEntry();
} }
nsCAutoString url; nsCAutoString url;
@ -4411,10 +4411,10 @@ nsDocShell::LoadPage(nsISupports *aPageDescriptor, PRUint32 aDisplayType)
nsresult rv = shEntryIn->Clone(getter_AddRefs(shEntry)); nsresult rv = shEntryIn->Clone(getter_AddRefs(shEntry));
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
// Give our cloned shEntry a new document identifier so this load is // Give our cloned shEntry a new bfcache entry so this load is independent
// independent of all other loads. (This is important, in particular, // of all other loads. (This is important, in particular, for bugs 582795
// for bugs 582795 and 585298.) // and 585298.)
rv = shEntry->SetUniqueDocIdentifier(); rv = shEntry->AbandonBFCacheEntry();
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
// //
@ -8409,17 +8409,15 @@ nsDocShell::InternalLoad(nsIURI * aURI,
NS_SUCCEEDED(splitRv2) && NS_SUCCEEDED(splitRv2) &&
curBeforeHash.Equals(newBeforeHash); curBeforeHash.Equals(newBeforeHash);
bool sameDocIdent = PR_FALSE; // XXX rename
bool sameDocument = false;
if (mOSHE && aSHEntry) { if (mOSHE && aSHEntry) {
// We're doing a history load. // We're doing a history load.
PRUint64 ourDocIdent, otherDocIdent; mOSHE->SharesDocumentWith(aSHEntry, &sameDocument);
mOSHE->GetDocIdentifier(&ourDocIdent);
aSHEntry->GetDocIdentifier(&otherDocIdent);
sameDocIdent = (ourDocIdent == otherDocIdent);
#ifdef DEBUG #ifdef DEBUG
if (sameDocIdent) { if (sameDocument) {
nsCOMPtr<nsIInputStream> currentPostData; nsCOMPtr<nsIInputStream> currentPostData;
mOSHE->GetPostData(getter_AddRefs(currentPostData)); mOSHE->GetPostData(getter_AddRefs(currentPostData));
NS_ASSERTION(currentPostData == aPostData, NS_ASSERTION(currentPostData == aPostData,
@ -8442,7 +8440,7 @@ nsDocShell::InternalLoad(nsIURI * aURI,
// The restriction tha the SHEntries in (a) must be different ensures // The restriction tha the SHEntries in (a) must be different ensures
// that history.go(0) and the like trigger full refreshes, rather than // that history.go(0) and the like trigger full refreshes, rather than
// short-circuited loads. // short-circuited loads.
bool doShortCircuitedLoad = (sameDocIdent && mOSHE != aSHEntry) || bool doShortCircuitedLoad = (sameDocument && mOSHE != aSHEntry) ||
(!aSHEntry && aPostData == nsnull && (!aSHEntry && aPostData == nsnull &&
sameExceptHashes && !newHash.IsEmpty()); sameExceptHashes && !newHash.IsEmpty());
@ -8493,7 +8491,6 @@ nsDocShell::InternalLoad(nsIURI * aURI,
OnNewURI(aURI, nsnull, owner, mLoadType, PR_TRUE, PR_TRUE, PR_TRUE); OnNewURI(aURI, nsnull, owner, mLoadType, PR_TRUE, PR_TRUE, PR_TRUE);
nsCOMPtr<nsIInputStream> postData; nsCOMPtr<nsIInputStream> postData;
PRUint64 docIdent = PRUint64(-1);
nsCOMPtr<nsISupports> cacheKey; nsCOMPtr<nsISupports> cacheKey;
if (mOSHE) { if (mOSHE) {
@ -8507,8 +8504,12 @@ nsDocShell::InternalLoad(nsIURI * aURI,
// wouldn't want here. // wouldn't want here.
if (aLoadType & LOAD_CMD_NORMAL) { if (aLoadType & LOAD_CMD_NORMAL) {
mOSHE->GetPostData(getter_AddRefs(postData)); mOSHE->GetPostData(getter_AddRefs(postData));
mOSHE->GetDocIdentifier(&docIdent);
mOSHE->GetCacheKey(getter_AddRefs(cacheKey)); 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);
} }
} }
@ -8528,11 +8529,6 @@ nsDocShell::InternalLoad(nsIURI * aURI,
// cache first // cache first
if (cacheKey) if (cacheKey)
mOSHE->SetCacheKey(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 /* restore previous position of scroller(s), if we're moving
@ -8576,7 +8572,7 @@ nsDocShell::InternalLoad(nsIURI * aURI,
} }
} }
if (sameDocIdent) { if (sameDocument) {
// Set the doc's URI according to the new history entry's URI // Set the doc's URI according to the new history entry's URI
nsCOMPtr<nsIURI> newURI; nsCOMPtr<nsIURI> newURI;
mOSHE->GetURI(getter_AddRefs(newURI)); mOSHE->GetURI(getter_AddRefs(newURI));
@ -8593,10 +8589,10 @@ nsDocShell::InternalLoad(nsIURI * aURI,
// Dispatch the popstate and hashchange events, as appropriate. // Dispatch the popstate and hashchange events, as appropriate.
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(mScriptGlobal); nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(mScriptGlobal);
if (window) { 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 // false if we're navigating to a new shentry (i.e. a aSHEntry
// is null), such as when clicking a <a href="#foo">. // is null), such as when clicking a <a href="#foo">.
if (sameDocIdent || doHashchange) { if (sameDocument || doHashchange) {
window->DispatchSyncPopState(); window->DispatchSyncPopState();
} }
@ -9379,11 +9375,11 @@ nsDocShell::OnNewURI(nsIURI * aURI, nsIChannel * aChannel, nsISupports* aOwner,
} }
// If the response status indicates an error, unlink this session // 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; PRUint32 responseStatus;
nsresult rv = httpChannel->GetResponseStatus(&responseStatus); nsresult rv = httpChannel->GetResponseStatus(&responseStatus);
if (mLSHE && NS_SUCCEEDED(rv) && responseStatus >= 400) { if (mLSHE && NS_SUCCEEDED(rv) && responseStatus >= 400) {
mLSHE->SetUniqueDocIdentifier(); mLSHE->AbandonBFCacheEntry();
} }
} }
} }
@ -9612,9 +9608,9 @@ nsDocShell::AddState(nsIVariant *aData, const nsAString& aTitle,
// It's important that this function not run arbitrary scripts after step 1 // It's important that this function not run arbitrary scripts after step 1
// and before completing step 5. For example, if a script called // and before completing step 5. For example, if a script called
// history.back() before we completed step 5, bfcache might destroy an // history.back() before we completed step 5, bfcache might destroy an
// active content viewer. Since EvictContentViewers at the end of step 5 // active content viewer. Since EvictOutOfRangeContentViewers at the end of
// might run script, we can't just put a script blocker around the critical // step 5 might run script, we can't just put a script blocker around the
// section. // critical section.
// //
// Note that we completely ignore the aTitle parameter. // Note that we completely ignore the aTitle parameter.
@ -9794,11 +9790,9 @@ nsDocShell::AddState(nsIVariant *aData, const nsAString& aTitle,
NS_ENSURE_TRUE(newSHEntry, NS_ERROR_FAILURE); NS_ENSURE_TRUE(newSHEntry, NS_ERROR_FAILURE);
// Set the new SHEntry's document identifier, if we can. // Link the new SHEntry to the old SHEntry's BFCache entry, since the
PRUint64 ourDocIdent; // two entries correspond to the same document.
NS_ENSURE_SUCCESS(oldOSHE->GetDocIdentifier(&ourDocIdent), NS_ENSURE_SUCCESS(newSHEntry->AdoptBFCacheEntry(oldOSHE),
NS_ERROR_FAILURE);
NS_ENSURE_SUCCESS(newSHEntry->SetDocIdentifier(ourDocIdent),
NS_ERROR_FAILURE); NS_ERROR_FAILURE);
// Set the new SHEntry's title (bug 655273). // Set the new SHEntry's title (bug 655273).
@ -9844,7 +9838,7 @@ nsDocShell::AddState(nsIVariant *aData, const nsAString& aTitle,
PRInt32 curIndex = -1; PRInt32 curIndex = -1;
rv = rootSH->GetIndex(&curIndex); rv = rootSH->GetIndex(&curIndex);
if (NS_SUCCEEDED(rv) && curIndex > -1) { if (NS_SUCCEEDED(rv) && curIndex > -1) {
internalSH->EvictContentViewers(curIndex - 1, curIndex); internalSH->EvictOutOfRangeContentViewers(curIndex);
} }
} }

Просмотреть файл

@ -66,6 +66,7 @@
// session history // session history
#include "nsSHEntry.h" #include "nsSHEntry.h"
#include "nsSHEntryShared.h"
#include "nsSHistory.h" #include "nsSHistory.h"
#include "nsSHTransaction.h" #include "nsSHTransaction.h"
@ -87,15 +88,15 @@ Initialize()
nsresult rv = nsSHistory::Startup(); nsresult rv = nsSHistory::Startup();
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
rv = nsSHEntry::Startup(); nsSHEntryShared::Startup();
return rv; return NS_OK;
} }
static void static void
Shutdown() Shutdown()
{ {
nsSHistory::Shutdown(); nsSHistory::Shutdown();
nsSHEntry::Shutdown(); nsSHEntryShared::Shutdown();
gInitialized = PR_FALSE; gInitialized = PR_FALSE;
} }

Просмотреть файл

@ -56,6 +56,7 @@ XPIDLSRCS = \
nsISHContainer.idl \ nsISHContainer.idl \
nsISHTransaction.idl \ nsISHTransaction.idl \
nsISHistoryInternal.idl \ nsISHistoryInternal.idl \
nsIBFCacheEntry.idl \
$(NULL) $(NULL)
include $(topsrcdir)/config/rules.mk include $(topsrcdir)/config/rules.mk

Просмотреть файл

@ -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;
};

Просмотреть файл

@ -51,15 +51,18 @@ interface nsIInputStream;
interface nsIDocShellTreeItem; interface nsIDocShellTreeItem;
interface nsISupportsArray; interface nsISupportsArray;
interface nsIStructuredCloneContainer; interface nsIStructuredCloneContainer;
interface nsIBFCacheEntry;
%{C++ %{C++
struct nsIntRect; struct nsIntRect;
class nsDocShellEditorData; class nsDocShellEditorData;
class nsSHEntryShared;
%} %}
[ref] native nsIntRect(nsIntRect); [ref] native nsIntRect(nsIntRect);
[ptr] native nsDocShellEditorDataPtr(nsDocShellEditorData); [ptr] native nsDocShellEditorDataPtr(nsDocShellEditorData);
[ptr] native nsSHEntryShared(nsSHEntryShared);
[scriptable, uuid(6443FD72-A50F-4B8B-BB82-BB1FA04CB15D)]
[scriptable, uuid(b92d403e-f5ec-4b81-b0e3-6e6c241cef2d)]
interface nsISHEntry : nsIHistoryEntry interface nsISHEntry : nsIHistoryEntry
{ {
/** URI for the document */ /** URI for the document */
@ -140,21 +143,6 @@ interface nsISHEntry : nsIHistoryEntry
*/ */
attribute unsigned long ID; 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 to set and get the cache key for the entry */
attribute nsISupports cacheKey; attribute nsISupports cacheKey;
@ -252,6 +240,36 @@ interface nsISHEntry : nsIHistoryEntry
* The history ID of the docshell. * The history ID of the docshell.
*/ */
attribute unsigned long long docshellID; 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)] [scriptable, uuid(bb66ac35-253b-471f-a317-3ece940f04c5)]
@ -264,6 +282,17 @@ interface nsISHEntryInternal : nsISupports
* A number that is assigned by the sHistory when the entry is activated * A number that is assigned by the sHistory when the entry is activated
*/ */
attribute unsigned long lastTouched; 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++ %{ C++

Просмотреть файл

@ -57,7 +57,7 @@ struct nsTArrayDefaultAllocator;
[ref] native nsDocshellIDArray(nsTArray<PRUint64, nsTArrayDefaultAllocator>); [ref] native nsDocshellIDArray(nsTArray<PRUint64, nsTArrayDefaultAllocator>);
[scriptable, uuid(2dede933-25e1-47a3-8f61-0127c785ea01)] [scriptable, uuid(e27cf38e-c19f-4294-bd31-d7e0916e7fa2)]
interface nsISHistoryInternal: nsISupports interface nsISHistoryInternal: nsISupports
{ {
/** /**
@ -97,19 +97,24 @@ interface nsISHistoryInternal: nsISupports
readonly attribute nsISHistoryListener listener; readonly attribute nsISHistoryListener listener;
/** /**
* Evict content viewers until the number of content viewers per tab * Evict content viewers which don't lie in the "safe" range around aIndex.
* is no more than gHistoryMaxViewers. Also, count * In practice, this should leave us with no more than gHistoryMaxViewers
* total number of content viewers globally and evict one if we are over * viewers associated with this SHistory object.
* our total max. This is always called in Show(), after we destroy *
* the previous viewer. * Also make sure that the total number of content viewers in all windows is
* not greater than our global max; if it is, evict viewers as appropriate.
*
* @param aIndex - The index around which the "safe" range is centered. In
* general, if you just navigated the history, aIndex should be the index
* history was navigated to.
*/ */
void evictContentViewers(in long previousIndex, in long index); void evictOutOfRangeContentViewers(in long aIndex);
/** /**
* Evict the content viewer associated with a session history entry * Evict the content viewer associated with a bfcache entry
* that has timed out. * that has timed out.
*/ */
void evictExpiredContentViewerForEntry(in nsISHEntry aEntry); void evictExpiredContentViewerForEntry(in nsIBFCacheEntry aEntry);
/** /**
* Evict all the content viewers in this session history * Evict all the content viewers in this session history

Просмотреть файл

@ -48,10 +48,13 @@ LIBRARY_NAME = shistory_s
FORCE_STATIC_LIB = 1 FORCE_STATIC_LIB = 1
LIBXUL_LIBRARY = 1 LIBXUL_LIBRARY = 1
EXPORTS = nsSHEntryShared.h \
$(NULL)
CPPSRCS = nsSHEntry.cpp \ CPPSRCS = nsSHEntry.cpp \
nsSHTransaction.cpp \ nsSHTransaction.cpp \
nsSHistory.cpp \ nsSHistory.cpp \
nsSHEntryShared.cpp \
$(NULL) $(NULL)
include $(topsrcdir)/config/rules.mk include $(topsrcdir)/config/rules.mk

Просмотреть файл

@ -36,10 +36,6 @@
* *
* ***** END LICENSE BLOCK ***** */ * ***** END LICENSE BLOCK ***** */
#ifdef DEBUG_bryner
#define DEBUG_PAGE_CACHE
#endif
// Local Includes // Local Includes
#include "nsSHEntry.h" #include "nsSHEntry.h"
#include "nsXPIDLString.h" #include "nsXPIDLString.h"
@ -48,54 +44,17 @@
#include "nsIDocShellTreeItem.h" #include "nsIDocShellTreeItem.h"
#include "nsIDocument.h" #include "nsIDocument.h"
#include "nsIDOMDocument.h" #include "nsIDOMDocument.h"
#include "nsAutoPtr.h"
#include "nsThreadUtils.h"
#include "nsIWebNavigation.h"
#include "nsISHistory.h" #include "nsISHistory.h"
#include "nsISHistoryInternal.h" #include "nsISHistoryInternal.h"
#include "nsDocShellEditorData.h" #include "nsDocShellEditorData.h"
#include "nsIDocShell.h" #include "nsSHEntryShared.h"
#include "nsILayoutHistoryState.h"
#include "nsIContentViewer.h"
#include "nsISupportsArray.h"
namespace dom = mozilla::dom; 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 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: Object Management
@ -105,46 +64,24 @@ static void StopTrackingEntry(nsSHEntry *aEntry)
nsSHEntry::nsSHEntry() nsSHEntry::nsSHEntry()
: mLoadType(0) : mLoadType(0)
, mID(gEntryID++) , mID(gEntryID++)
, mDocIdentifier(gEntryDocIdentifier++)
, mScrollPositionX(0) , mScrollPositionX(0)
, mScrollPositionY(0) , mScrollPositionY(0)
, mURIWasModified(PR_FALSE) , 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) nsSHEntry::nsSHEntry(const nsSHEntry &other)
: mURI(other.mURI) : mShared(other.mShared)
, mURI(other.mURI)
, mReferrerURI(other.mReferrerURI) , mReferrerURI(other.mReferrerURI)
// XXX why not copy mDocument?
, mTitle(other.mTitle) , mTitle(other.mTitle)
, mPostData(other.mPostData) , mPostData(other.mPostData)
, mLayoutHistoryState(other.mLayoutHistoryState)
, mLoadType(0) // XXX why not copy? , mLoadType(0) // XXX why not copy?
, mID(other.mID) , mID(other.mID)
, mDocIdentifier(other.mDocIdentifier)
, mScrollPositionX(0) // XXX why not copy? , mScrollPositionX(0) // XXX why not copy?
, mScrollPositionY(0) // XXX why not copy? , mScrollPositionY(0) // XXX why not copy?
, mURIWasModified(other.mURIWasModified) , 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) , mStateData(other.mStateData)
{ {
} }
@ -160,37 +97,16 @@ ClearParentPtr(nsISHEntry* aEntry, void* /* aData */)
nsSHEntry::~nsSHEntry() nsSHEntry::~nsSHEntry()
{ {
StopTrackingEntry(this); // Null out the mParent pointers on all our kids.
// Since we never really remove kids from SHEntrys, we need to null
// out the mParent pointers on all our kids.
mChildren.EnumerateForwards(ClearParentPtr, nsnull); 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 // nsSHEntry: nsISupports
//***************************************************************************** //*****************************************************************************
NS_IMPL_ISUPPORTS5(nsSHEntry, nsISHContainer, nsISHEntry, nsIHistoryEntry, NS_IMPL_ISUPPORTS4(nsSHEntry, nsISHContainer, nsISHEntry, nsIHistoryEntry,
nsIMutationObserver, nsISHEntryInternal) nsISHEntryInternal)
//***************************************************************************** //*****************************************************************************
// nsSHEntry: nsISHEntry // nsSHEntry: nsISHEntry
@ -251,35 +167,13 @@ NS_IMETHODIMP nsSHEntry::SetReferrerURI(nsIURI *aReferrerURI)
NS_IMETHODIMP NS_IMETHODIMP
nsSHEntry::SetContentViewer(nsIContentViewer *aViewer) nsSHEntry::SetContentViewer(nsIContentViewer *aViewer)
{ {
NS_PRECONDITION(!aViewer || !mContentViewer, "SHEntry already contains viewer"); return mShared->SetContentViewer(aViewer);
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;
} }
NS_IMETHODIMP NS_IMETHODIMP
nsSHEntry::GetContentViewer(nsIContentViewer **aResult) nsSHEntry::GetContentViewer(nsIContentViewer **aResult)
{ {
*aResult = mContentViewer; *aResult = mShared->mContentViewer;
NS_IF_ADDREF(*aResult); NS_IF_ADDREF(*aResult);
return NS_OK; return NS_OK;
} }
@ -319,14 +213,14 @@ nsSHEntry::GetAnyContentViewer(nsISHEntry **aOwnerEntry,
NS_IMETHODIMP NS_IMETHODIMP
nsSHEntry::SetSticky(bool aSticky) nsSHEntry::SetSticky(bool aSticky)
{ {
mSticky = aSticky; mShared->mSticky = aSticky;
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP NS_IMETHODIMP
nsSHEntry::GetSticky(bool *aSticky) nsSHEntry::GetSticky(bool *aSticky)
{ {
*aSticky = mSticky; *aSticky = mShared->mSticky;
return NS_OK; return NS_OK;
} }
@ -365,16 +259,18 @@ NS_IMETHODIMP nsSHEntry::SetPostData(nsIInputStream* aPostData)
NS_IMETHODIMP nsSHEntry::GetLayoutHistoryState(nsILayoutHistoryState** aResult) NS_IMETHODIMP nsSHEntry::GetLayoutHistoryState(nsILayoutHistoryState** aResult)
{ {
*aResult = mLayoutHistoryState; *aResult = mShared->mLayoutHistoryState;
NS_IF_ADDREF(*aResult); NS_IF_ADDREF(*aResult);
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP nsSHEntry::SetLayoutHistoryState(nsILayoutHistoryState* aState) NS_IMETHODIMP nsSHEntry::SetLayoutHistoryState(nsILayoutHistoryState* aState)
{ {
mLayoutHistoryState = aState; mShared->mLayoutHistoryState = aState;
if (mLayoutHistoryState) if (mShared->mLayoutHistoryState) {
mLayoutHistoryState->SetScrollPositionOnly(!mSaveLayoutState); mShared->mLayoutHistoryState->
SetScrollPositionOnly(!mShared->mSaveLayoutState);
}
return NS_OK; return NS_OK;
} }
@ -403,91 +299,73 @@ NS_IMETHODIMP nsSHEntry::SetID(PRUint32 aID)
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP nsSHEntry::GetDocIdentifier(PRUint64 * aResult) nsSHEntryShared* nsSHEntry::GetSharedState()
{ {
*aResult = mDocIdentifier; return mShared;
return NS_OK;
}
NS_IMETHODIMP nsSHEntry::SetDocIdentifier(PRUint64 aDocIdentifier)
{
// 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;
} }
NS_IMETHODIMP nsSHEntry::GetIsSubFrame(bool * aFlag) NS_IMETHODIMP nsSHEntry::GetIsSubFrame(bool * aFlag)
{ {
*aFlag = mIsFrameNavigation; *aFlag = mShared->mIsFrameNavigation;
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP nsSHEntry::SetIsSubFrame(bool aFlag) NS_IMETHODIMP nsSHEntry::SetIsSubFrame(bool aFlag)
{ {
mIsFrameNavigation = aFlag; mShared->mIsFrameNavigation = aFlag;
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP nsSHEntry::GetCacheKey(nsISupports** aResult) NS_IMETHODIMP nsSHEntry::GetCacheKey(nsISupports** aResult)
{ {
*aResult = mCacheKey; *aResult = mShared->mCacheKey;
NS_IF_ADDREF(*aResult); NS_IF_ADDREF(*aResult);
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP nsSHEntry::SetCacheKey(nsISupports* aCacheKey) NS_IMETHODIMP nsSHEntry::SetCacheKey(nsISupports* aCacheKey)
{ {
mCacheKey = aCacheKey; mShared->mCacheKey = aCacheKey;
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP nsSHEntry::GetSaveLayoutStateFlag(bool * aFlag) NS_IMETHODIMP nsSHEntry::GetSaveLayoutStateFlag(bool * aFlag)
{ {
*aFlag = mSaveLayoutState; *aFlag = mShared->mSaveLayoutState;
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP nsSHEntry::SetSaveLayoutStateFlag(bool aFlag) NS_IMETHODIMP nsSHEntry::SetSaveLayoutStateFlag(bool aFlag)
{ {
mSaveLayoutState = aFlag; mShared->mSaveLayoutState = aFlag;
if (mLayoutHistoryState) if (mShared->mLayoutHistoryState) {
mLayoutHistoryState->SetScrollPositionOnly(!aFlag); mShared->mLayoutHistoryState->SetScrollPositionOnly(!aFlag);
}
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP nsSHEntry::GetExpirationStatus(bool * aFlag) NS_IMETHODIMP nsSHEntry::GetExpirationStatus(bool * aFlag)
{ {
*aFlag = mExpired; *aFlag = mShared->mExpired;
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP nsSHEntry::SetExpirationStatus(bool aFlag) NS_IMETHODIMP nsSHEntry::SetExpirationStatus(bool aFlag)
{ {
mExpired = aFlag; mShared->mExpired = aFlag;
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP nsSHEntry::GetContentType(nsACString& aContentType) NS_IMETHODIMP nsSHEntry::GetContentType(nsACString& aContentType)
{ {
aContentType = mContentType; aContentType = mShared->mContentType;
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP nsSHEntry::SetContentType(const nsACString& aContentType) NS_IMETHODIMP nsSHEntry::SetContentType(const nsACString& aContentType)
{ {
mContentType = aContentType; mShared->mContentType = aContentType;
return NS_OK; return NS_OK;
} }
@ -502,26 +380,27 @@ nsSHEntry::Create(nsIURI * aURI, const nsAString &aTitle,
mURI = aURI; mURI = aURI;
mTitle = aTitle; mTitle = aTitle;
mPostData = aInputStream; mPostData = aInputStream;
mCacheKey = aCacheKey;
mContentType = aContentType;
mOwner = aOwner;
mDocShellID = aDocShellID;
mDynamicallyCreated = aDynamicCreation;
// Set the LoadType by default to loadHistory during creation // Set the LoadType by default to loadHistory during creation
mLoadType = (PRUint32) nsIDocShellLoadInfo::loadHistory; 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. // By default all entries are set false for subframe flag.
// nsDocShell::CloneAndReplace() which creates entries for // nsDocShell::CloneAndReplace() which creates entries for
// all subframe navigations, sets the flag to true. // all subframe navigations, sets the flag to true.
mIsFrameNavigation = PR_FALSE; mShared->mIsFrameNavigation = PR_FALSE;
// By default we save LayoutHistoryState // By default we save LayoutHistoryState
mSaveLayoutState = PR_TRUE; mShared->mSaveLayoutState = PR_TRUE;
mLayoutHistoryState = aLayoutHistoryState; mShared->mLayoutHistoryState = aLayoutHistoryState;
//By default the page is not expired //By default the page is not expired
mExpired = PR_FALSE; mShared->mExpired = PR_FALSE;
return NS_OK; return NS_OK;
} }
@ -530,8 +409,6 @@ NS_IMETHODIMP
nsSHEntry::Clone(nsISHEntry ** aResult) nsSHEntry::Clone(nsISHEntry ** aResult)
{ {
*aResult = new nsSHEntry(*this); *aResult = new nsSHEntry(*this);
if (!*aResult)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(*aResult); NS_ADDREF(*aResult);
return NS_OK; return NS_OK;
} }
@ -540,7 +417,7 @@ NS_IMETHODIMP
nsSHEntry::GetParent(nsISHEntry ** aResult) nsSHEntry::GetParent(nsISHEntry ** aResult)
{ {
NS_ENSURE_ARG_POINTER(aResult); NS_ENSURE_ARG_POINTER(aResult);
*aResult = mParent; *aResult = mShared->mParent;
NS_IF_ADDREF(*aResult); NS_IF_ADDREF(*aResult);
return NS_OK; return NS_OK;
} }
@ -553,49 +430,95 @@ nsSHEntry::SetParent(nsISHEntry * aParent)
* *
* XXX this method should not be scriptable if this is the case!! * XXX this method should not be scriptable if this is the case!!
*/ */
mParent = aParent; mShared->mParent = aParent;
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP NS_IMETHODIMP
nsSHEntry::SetWindowState(nsISupports *aState) nsSHEntry::SetWindowState(nsISupports *aState)
{ {
mWindowState = aState; mShared->mWindowState = aState;
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP NS_IMETHODIMP
nsSHEntry::GetWindowState(nsISupports **aState) nsSHEntry::GetWindowState(nsISupports **aState)
{ {
NS_IF_ADDREF(*aState = mWindowState); NS_IF_ADDREF(*aState = mShared->mWindowState);
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP NS_IMETHODIMP
nsSHEntry::SetViewerBounds(const nsIntRect &aBounds) nsSHEntry::SetViewerBounds(const nsIntRect &aBounds)
{ {
mViewerBounds = aBounds; mShared->mViewerBounds = aBounds;
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP NS_IMETHODIMP
nsSHEntry::GetViewerBounds(nsIntRect &aBounds) nsSHEntry::GetViewerBounds(nsIntRect &aBounds)
{ {
aBounds = mViewerBounds; aBounds = mShared->mViewerBounds;
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP NS_IMETHODIMP
nsSHEntry::GetOwner(nsISupports **aOwner) nsSHEntry::GetOwner(nsISupports **aOwner)
{ {
NS_IF_ADDREF(*aOwner = mOwner); NS_IF_ADDREF(*aOwner = mShared->mOwner);
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP NS_IMETHODIMP
nsSHEntry::SetOwner(nsISupports *aOwner) 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;
}
bool
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, bool *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; return NS_OK;
} }
@ -753,256 +676,77 @@ NS_IMETHODIMP
nsSHEntry::AddChildShell(nsIDocShellTreeItem *aShell) nsSHEntry::AddChildShell(nsIDocShellTreeItem *aShell)
{ {
NS_ASSERTION(aShell, "Null child shell added to history entry"); NS_ASSERTION(aShell, "Null child shell added to history entry");
mChildShells.AppendObject(aShell); mShared->mChildShells.AppendObject(aShell);
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP NS_IMETHODIMP
nsSHEntry::ChildShellAt(PRInt32 aIndex, nsIDocShellTreeItem **aShell) nsSHEntry::ChildShellAt(PRInt32 aIndex, nsIDocShellTreeItem **aShell)
{ {
NS_IF_ADDREF(*aShell = mChildShells.SafeObjectAt(aIndex)); NS_IF_ADDREF(*aShell = mShared->mChildShells.SafeObjectAt(aIndex));
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP NS_IMETHODIMP
nsSHEntry::ClearChildShells() nsSHEntry::ClearChildShells()
{ {
mChildShells.Clear(); mShared->mChildShells.Clear();
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP NS_IMETHODIMP
nsSHEntry::GetRefreshURIList(nsISupportsArray **aList) nsSHEntry::GetRefreshURIList(nsISupportsArray **aList)
{ {
NS_IF_ADDREF(*aList = mRefreshURIList); NS_IF_ADDREF(*aList = mShared->mRefreshURIList);
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP NS_IMETHODIMP
nsSHEntry::SetRefreshURIList(nsISupportsArray *aList) nsSHEntry::SetRefreshURIList(nsISupportsArray *aList)
{ {
mRefreshURIList = aList; mShared->mRefreshURIList = aList;
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP NS_IMETHODIMP
nsSHEntry::SyncPresentationState() nsSHEntry::SyncPresentationState()
{ {
if (mContentViewer && mWindowState) { return mShared->SyncPresentationState();
// 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)
{
}
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 void
nsSHEntry::RemoveFromBFCacheSync() nsSHEntry::RemoveFromBFCacheSync()
{ {
NS_ASSERTION(mContentViewer && mDocument, mShared->RemoveFromBFCacheSync();
"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 void
nsSHEntry::RemoveFromBFCacheAsync() nsSHEntry::RemoveFromBFCacheAsync()
{ {
NS_ASSERTION(mContentViewer && mDocument, mShared->RemoveFromBFCacheAsync();
"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.
} }
nsDocShellEditorData* nsDocShellEditorData*
nsSHEntry::ForgetEditorData() nsSHEntry::ForgetEditorData()
{ {
return mEditorData.forget(); // XXX jlebar Check how this is used.
return mShared->mEditorData.forget();
} }
void void
nsSHEntry::SetEditorData(nsDocShellEditorData* aData) nsSHEntry::SetEditorData(nsDocShellEditorData* aData)
{ {
NS_ASSERTION(!(aData && mEditorData), NS_ASSERTION(!(aData && mShared->mEditorData),
"We're going to overwrite an owning ref!"); "We're going to overwrite an owning ref!");
if (mEditorData != aData) if (mShared->mEditorData != aData) {
mEditorData = aData; mShared->mEditorData = aData;
}
} }
bool bool
nsSHEntry::HasDetachedEditor() nsSHEntry::HasDetachedEditor()
{ {
return mEditorData != nsnull; return mShared->mEditorData != nsnull;
} }
NS_IMETHODIMP NS_IMETHODIMP
@ -1023,7 +767,7 @@ nsSHEntry::SetStateData(nsIStructuredCloneContainer *aContainer)
NS_IMETHODIMP NS_IMETHODIMP
nsSHEntry::IsDynamicallyAdded(bool* aAdded) nsSHEntry::IsDynamicallyAdded(bool* aAdded)
{ {
*aAdded = mDynamicallyCreated; *aAdded = mShared->mDynamicallyCreated;
return NS_OK; return NS_OK;
} }
@ -1046,14 +790,14 @@ nsSHEntry::HasDynamicallyAddedChild(bool* aAdded)
NS_IMETHODIMP NS_IMETHODIMP
nsSHEntry::GetDocshellID(PRUint64* aID) nsSHEntry::GetDocshellID(PRUint64* aID)
{ {
*aID = mDocShellID; *aID = mShared->mDocShellID;
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP NS_IMETHODIMP
nsSHEntry::SetDocshellID(PRUint64 aID) nsSHEntry::SetDocshellID(PRUint64 aID)
{ {
mDocShellID = aID; mShared->mDocShellID = aID;
return NS_OK; return NS_OK;
} }
@ -1061,14 +805,13 @@ nsSHEntry::SetDocshellID(PRUint64 aID)
NS_IMETHODIMP NS_IMETHODIMP
nsSHEntry::GetLastTouched(PRUint32 *aLastTouched) nsSHEntry::GetLastTouched(PRUint32 *aLastTouched)
{ {
*aLastTouched = mLastTouched; *aLastTouched = mShared->mLastTouched;
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP NS_IMETHODIMP
nsSHEntry::SetLastTouched(PRUint32 aLastTouched) nsSHEntry::SetLastTouched(PRUint32 aLastTouched)
{ {
mLastTouched = aLastTouched; mShared->mLastTouched = aLastTouched;
return NS_OK; return NS_OK;
} }

Просмотреть файл

@ -42,28 +42,21 @@
// Helper Classes // Helper Classes
#include "nsCOMPtr.h" #include "nsCOMPtr.h"
#include "nsAutoPtr.h"
#include "nsCOMArray.h" #include "nsCOMArray.h"
#include "nsString.h" #include "nsString.h"
#include "nsAutoPtr.h"
// Interfaces needed // Interfaces needed
#include "nsIContentViewer.h"
#include "nsIInputStream.h" #include "nsIInputStream.h"
#include "nsILayoutHistoryState.h"
#include "nsISHEntry.h" #include "nsISHEntry.h"
#include "nsISHContainer.h" #include "nsISHContainer.h"
#include "nsIURI.h" #include "nsIURI.h"
#include "nsIEnumerator.h"
#include "nsIHistoryEntry.h" #include "nsIHistoryEntry.h"
#include "nsRect.h"
#include "nsISupportsArray.h" class nsSHEntryShared;
#include "nsIMutationObserver.h"
#include "nsExpirationTracker.h"
#include "nsDocShellEditorData.h"
class nsSHEntry : public nsISHEntry, class nsSHEntry : public nsISHEntry,
public nsISHContainer, public nsISHContainer,
public nsIMutationObserver,
public nsISHEntryInternal public nsISHEntryInternal
{ {
public: public:
@ -75,51 +68,30 @@ public:
NS_DECL_NSISHENTRY NS_DECL_NSISHENTRY
NS_DECL_NSISHENTRYINTERNAL NS_DECL_NSISHENTRYINTERNAL
NS_DECL_NSISHCONTAINER NS_DECL_NSISHCONTAINER
NS_DECL_NSIMUTATIONOBSERVER
void DropPresentationState(); void DropPresentationState();
void Expire();
nsExpirationState *GetExpirationState() { return &mExpirationState; }
static nsresult Startup(); static nsresult Startup();
static void Shutdown(); static void Shutdown();
private: private:
~nsSHEntry(); ~nsSHEntry();
// 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> mURI;
nsCOMPtr<nsIURI> mReferrerURI; nsCOMPtr<nsIURI> mReferrerURI;
nsCOMPtr<nsIContentViewer> mContentViewer;
nsCOMPtr<nsIDocument> mDocument; // document currently being observed
nsString mTitle; nsString mTitle;
nsCOMPtr<nsIInputStream> mPostData; nsCOMPtr<nsIInputStream> mPostData;
nsCOMPtr<nsILayoutHistoryState> mLayoutHistoryState;
nsCOMArray<nsISHEntry> mChildren;
PRUint32 mLoadType; PRUint32 mLoadType;
PRUint32 mID; PRUint32 mID;
PRInt64 mDocIdentifier;
PRInt32 mScrollPositionX; PRInt32 mScrollPositionX;
PRInt32 mScrollPositionY; PRInt32 mScrollPositionY;
PRPackedBool mURIWasModified; nsCOMArray<nsISHEntry> mChildren;
PRPackedBool mIsFrameNavigation; bool mURIWasModified;
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;
nsCOMPtr<nsIStructuredCloneContainer> mStateData; nsCOMPtr<nsIStructuredCloneContainer> mStateData;
}; };

Просмотреть файл

@ -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)
{
}

Просмотреть файл

@ -0,0 +1,124 @@
/* ***** 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 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;
bool mIsFrameNavigation;
bool mSaveLayoutState;
bool mSticky;
bool 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;
bool mExpired;
nsCOMPtr<nsISupports> mWindowState;
nsIntRect mViewerBounds;
nsCOMPtr<nsISupportsArray> mRefreshURIList;
nsExpirationState mExpirationState;
nsAutoPtr<nsDocShellEditorData> mEditorData;
};
#endif

Просмотреть файл

@ -72,12 +72,10 @@ using namespace mozilla;
#define PREF_SHISTORY_SIZE "browser.sessionhistory.max_entries" #define PREF_SHISTORY_SIZE "browser.sessionhistory.max_entries"
#define PREF_SHISTORY_MAX_TOTAL_VIEWERS "browser.sessionhistory.max_total_viewers" #define PREF_SHISTORY_MAX_TOTAL_VIEWERS "browser.sessionhistory.max_total_viewers"
#define PREF_SHISTORY_OPTIMIZE_EVICTION "browser.sessionhistory.optimize_eviction"
static const char* kObservedPrefs[] = { static const char* kObservedPrefs[] = {
PREF_SHISTORY_SIZE, PREF_SHISTORY_SIZE,
PREF_SHISTORY_MAX_TOTAL_VIEWERS, PREF_SHISTORY_MAX_TOTAL_VIEWERS,
PREF_SHISTORY_OPTIMIZE_EVICTION,
nsnull nsnull
}; };
@ -90,16 +88,46 @@ static PRCList gSHistoryList;
// means we will calculate how many viewers to cache based on total memory // means we will calculate how many viewers to cache based on total memory
PRInt32 nsSHistory::sHistoryMaxTotalViewers = -1; PRInt32 nsSHistory::sHistoryMaxTotalViewers = -1;
// Whether we should optimize the search for which entry to evict,
// by evicting older entries first. See entryLastTouched in
// nsSHistory::EvictGlobalContentViewer().
// NB: After 4.0, we should remove this option and the corresponding
// pref - optimization should always be used
static PRBool gOptimizeEviction = PR_FALSE;
// A counter that is used to be able to know the order in which // A counter that is used to be able to know the order in which
// entries were touched, so that we can evict older entries first. // entries were touched, so that we can evict older entries first.
static PRUint32 gTouchCounter = 0; static PRUint32 gTouchCounter = 0;
static PRLogModuleInfo* gLogModule = PR_LOG_DEFINE("nsSHistory");
#define LOG(format) PR_LOG(gLogModule, PR_LOG_DEBUG, format)
// This macro makes it easier to print a log message which includes a URI's
// spec. Example use:
//
// nsIURI *uri = [...];
// LOG_SPEC(("The URI is %s.", _spec), uri);
//
#define LOG_SPEC(format, uri) \
PR_BEGIN_MACRO \
if (PR_LOG_TEST(gLogModule, PR_LOG_DEBUG)) { \
nsCAutoString _specStr(NS_LITERAL_CSTRING("(null)"));\
if (uri) { \
uri->GetSpec(_specStr); \
} \
const char* _spec = _specStr.get(); \
LOG(format); \
} \
PR_END_MACRO
// This macro makes it easy to log a message including an SHEntry's URI.
// For example:
//
// nsCOMPtr<nsISHEntry> shentry = [...];
// LOG_SHENTRY_SPEC(("shentry %p has uri %s.", shentry.get(), _spec), shentry);
//
#define LOG_SHENTRY_SPEC(format, shentry) \
PR_BEGIN_MACRO \
if (PR_LOG_TEST(gLogModule, PR_LOG_DEBUG)) { \
nsCOMPtr<nsIURI> uri; \
shentry->GetURI(getter_AddRefs(uri)); \
LOG_SPEC(format, uri); \
} \
PR_END_MACRO
enum HistCmd{ enum HistCmd{
HIST_CMD_BACK, HIST_CMD_BACK,
HIST_CMD_FORWARD, HIST_CMD_FORWARD,
@ -134,15 +162,60 @@ nsSHistoryObserver::Observe(nsISupports *aSubject, const char *aTopic,
{ {
if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
nsSHistory::UpdatePrefs(); nsSHistory::UpdatePrefs();
nsSHistory::EvictGlobalContentViewer(); nsSHistory::GloballyEvictContentViewers();
} else if (!strcmp(aTopic, NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID) || } else if (!strcmp(aTopic, NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID) ||
!strcmp(aTopic, "memory-pressure")) { !strcmp(aTopic, "memory-pressure")) {
nsSHistory::EvictAllContentViewersGlobally(); nsSHistory::GloballyEvictAllContentViewers();
} }
return NS_OK; return NS_OK;
} }
namespace {
already_AddRefed<nsIContentViewer>
GetContentViewerForTransaction(nsISHTransaction *aTrans)
{
nsCOMPtr<nsISHEntry> entry;
aTrans->GetSHEntry(getter_AddRefs(entry));
if (!entry) {
return nsnull;
}
nsCOMPtr<nsISHEntry> ownerEntry;
nsCOMPtr<nsIContentViewer> viewer;
entry->GetAnyContentViewer(getter_AddRefs(ownerEntry),
getter_AddRefs(viewer));
return viewer.forget();
}
void
EvictContentViewerForTransaction(nsISHTransaction *aTrans)
{
nsCOMPtr<nsISHEntry> entry;
aTrans->GetSHEntry(getter_AddRefs(entry));
nsCOMPtr<nsIContentViewer> viewer;
nsCOMPtr<nsISHEntry> ownerEntry;
entry->GetAnyContentViewer(getter_AddRefs(ownerEntry),
getter_AddRefs(viewer));
if (viewer) {
NS_ASSERTION(ownerEntry,
"Content viewer exists but its SHEntry is null");
LOG_SHENTRY_SPEC(("Evicting content viewer 0x%p for "
"owning SHEntry 0x%p at %s.",
viewer.get(), ownerEntry.get(), _spec), ownerEntry);
// Drop the presentation state before destroying the viewer, so that
// document teardown is able to correctly persist the state.
ownerEntry->SetContentViewer(nsnull);
ownerEntry->SyncPresentationState();
viewer->Destroy();
}
}
} // anonymous namespace
//***************************************************************************** //*****************************************************************************
//*** nsSHistory: Object Management //*** nsSHistory: Object Management
//***************************************************************************** //*****************************************************************************
@ -240,7 +313,6 @@ nsSHistory::UpdatePrefs()
Preferences::GetInt(PREF_SHISTORY_SIZE, &gHistoryMaxSize); Preferences::GetInt(PREF_SHISTORY_SIZE, &gHistoryMaxSize);
Preferences::GetInt(PREF_SHISTORY_MAX_TOTAL_VIEWERS, Preferences::GetInt(PREF_SHISTORY_MAX_TOTAL_VIEWERS,
&sHistoryMaxTotalViewers); &sHistoryMaxTotalViewers);
Preferences::GetBool(PREF_SHISTORY_OPTIMIZE_EVICTION, &gOptimizeEviction);
// If the pref is negative, that means we calculate how many viewers // If the pref is negative, that means we calculate how many viewers
// we think we should cache, based on total memory // we think we should cache, based on total memory
if (sHistoryMaxTotalViewers < 0) { if (sHistoryMaxTotalViewers < 0) {
@ -689,12 +761,12 @@ nsSHistory::GetListener(nsISHistoryListener ** aListener)
} }
NS_IMETHODIMP NS_IMETHODIMP
nsSHistory::EvictContentViewers(PRInt32 aPreviousIndex, PRInt32 aIndex) nsSHistory::EvictOutOfRangeContentViewers(PRInt32 aIndex)
{ {
// Check our per SHistory object limit in the currently navigated SHistory // Check our per SHistory object limit in the currently navigated SHistory
EvictWindowContentViewers(aPreviousIndex, aIndex); EvictOutOfRangeWindowContentViewers(aIndex);
// Check our total limit across all SHistory objects // Check our total limit across all SHistory objects
EvictGlobalContentViewer(); GloballyEvictContentViewers();
return NS_OK; return NS_OK;
} }
@ -703,7 +775,14 @@ nsSHistory::EvictAllContentViewers()
{ {
// XXXbz we don't actually do a good job of evicting things as we should, so // XXXbz we don't actually do a good job of evicting things as we should, so
// we might have viewers quite far from mIndex. So just evict everything. // we might have viewers quite far from mIndex. So just evict everything.
EvictContentViewersInRange(0, mLength); nsCOMPtr<nsISHTransaction> trans = mListRoot;
while (trans) {
EvictContentViewerForTransaction(trans);
nsISHTransaction *temp = trans;
temp->GetNext(getter_AddRefs(trans));
}
return NS_OK; return NS_OK;
} }
@ -835,242 +914,232 @@ nsSHistory::ReloadCurrentEntry()
} }
void void
nsSHistory::EvictWindowContentViewers(PRInt32 aFromIndex, PRInt32 aToIndex) nsSHistory::EvictOutOfRangeWindowContentViewers(PRInt32 aIndex)
{ {
// To enforce the per SHistory object limit on cached content viewers, we // XXX rename method to EvictContentViewersExceptAroundIndex, or something.
// need to release all of the content viewers that are no longer in the
// "window" that now ends/begins at aToIndex. Existing content viewers // We need to release all content viewers that are no longer in the range
// should be in the window from
// aFromIndex - gHistoryMaxViewers to aFromIndex + gHistoryMaxViewers
// //
// We make the assumption that entries outside this range have no viewers so // aIndex - gHistoryMaxViewers to aIndex + gHistoryMaxViewers
// that we don't have to walk the whole entire session history checking for //
// content viewers. // to ensure that this SHistory object isn't responsible for more than
// gHistoryMaxViewers content viewers. But our job is complicated by the
// fact that two transactions which are related by either hash navigations or
// history.pushState will have the same content viewer.
//
// To illustrate the issue, suppose gHistoryMaxViewers = 3 and we have four
// linked transactions in our history. Suppose we then add a new content
// viewer and call into this function. So the history looks like:
//
// A A A A B
// + *
//
// where the letters are content viewers and + and * denote the beginning and
// end of the range aIndex +/- gHistoryMaxViewers.
//
// Although one copy of the content viewer A exists outside the range, we
// don't want to evict A, because it has other copies in range!
//
// We therefore adjust our eviction strategy to read:
//
// Evict each content viewer outside the range aIndex -/+
// gHistoryMaxViewers, unless that content viewer also appears within the
// range.
//
// (Note that it's entirely legal to have two copies of one content viewer
// separated by a different content viewer -- call pushState twice, go back
// once, and refresh -- so we can't rely on identical viewers only appearing
// adjacent to one another.)
// This can happen on the first load of a page in a particular window if (aIndex < 0) {
if (aFromIndex < 0 || aToIndex < 0) {
return; return;
} }
NS_ASSERTION(aFromIndex < mLength, "aFromIndex is out of range"); NS_ASSERTION(aIndex < mLength, "aIndex is out of range");
NS_ASSERTION(aToIndex < mLength, "aToIndex is out of range"); if (aIndex >= mLength) {
if (aFromIndex >= mLength || aToIndex >= mLength) {
return; return;
} }
// These indices give the range of SHEntries whose content viewers will be // Calculate the range that's safe from eviction.
// evicted PRInt32 startSafeIndex = PR_MAX(0, aIndex - gHistoryMaxViewers);
PRInt32 startIndex, endIndex; PRInt32 endSafeIndex = PR_MIN(mLength, aIndex + gHistoryMaxViewers);
if (aToIndex > aFromIndex) { // going forward
endIndex = aToIndex - gHistoryMaxViewers;
if (endIndex <= 0) {
return;
}
startIndex = NS_MAX(0, aFromIndex - gHistoryMaxViewers);
} else { // going backward
startIndex = aToIndex + gHistoryMaxViewers + 1;
if (startIndex >= mLength) {
return;
}
endIndex = NS_MIN(mLength, aFromIndex + gHistoryMaxViewers + 1);
}
#ifdef DEBUG LOG(("EvictOutOfRangeWindowContentViewers(index=%d), "
"mLength=%d. Safe range [%d, %d]",
aIndex, mLength, startSafeIndex, endSafeIndex));
// The content viewers in range aIndex -/+ gHistoryMaxViewers will not be
// evicted. Collect a set of them so we don't accidentally evict one of them
// if it appears outside this range.
nsCOMArray<nsIContentViewer> safeViewers;
nsCOMPtr<nsISHTransaction> trans; nsCOMPtr<nsISHTransaction> trans;
GetTransactionAtIndex(startSafeIndex, getter_AddRefs(trans));
for (PRUint32 i = startSafeIndex; trans && i <= endSafeIndex; i++) {
nsCOMPtr<nsIContentViewer> viewer = GetContentViewerForTransaction(trans);
safeViewers.AppendObject(viewer);
nsISHTransaction *temp = trans;
temp->GetNext(getter_AddRefs(trans));
}
// Walk the SHistory list and evict any content viewers that aren't safe.
GetTransactionAtIndex(0, getter_AddRefs(trans)); GetTransactionAtIndex(0, getter_AddRefs(trans));
while (trans) {
// Walk the full session history and check that entries outside the window nsCOMPtr<nsIContentViewer> viewer = GetContentViewerForTransaction(trans);
// around aFromIndex have no content viewers if (safeViewers.IndexOf(viewer) == -1) {
for (PRInt32 i = 0; trans && i < mLength; ++i) { EvictContentViewerForTransaction(trans);
if (i < aFromIndex - gHistoryMaxViewers ||
i > aFromIndex + gHistoryMaxViewers) {
nsCOMPtr<nsISHEntry> entry;
trans->GetSHEntry(getter_AddRefs(entry));
nsCOMPtr<nsIContentViewer> viewer;
nsCOMPtr<nsISHEntry> ownerEntry;
entry->GetAnyContentViewer(getter_AddRefs(ownerEntry),
getter_AddRefs(viewer));
NS_WARN_IF_FALSE(!viewer,
"ContentViewer exists outside gHistoryMaxViewer range");
} }
nsISHTransaction *temp = trans; nsISHTransaction *temp = trans;
temp->GetNext(getter_AddRefs(trans)); temp->GetNext(getter_AddRefs(trans));
} }
#endif
EvictContentViewersInRange(startIndex, endIndex);
} }
void namespace {
nsSHistory::EvictContentViewersInRange(PRInt32 aStart, PRInt32 aEnd)
class TransactionAndDistance
{ {
nsCOMPtr<nsISHTransaction> trans; public:
GetTransactionAtIndex(aStart, getter_AddRefs(trans)); TransactionAndDistance(nsISHTransaction *aTrans, PRUint32 aDist)
: mTransaction(aTrans)
, mDistance(aDist)
{
mViewer = GetContentViewerForTransaction(aTrans);
NS_ASSERTION(mViewer, "Transaction should have a content viewer");
for (PRInt32 i = aStart; trans && i < aEnd; ++i) { nsCOMPtr<nsISHEntry> shentry;
nsCOMPtr<nsISHEntry> entry; mTransaction->GetSHEntry(getter_AddRefs(shentry));
trans->GetSHEntry(getter_AddRefs(entry));
nsCOMPtr<nsIContentViewer> viewer;
nsCOMPtr<nsISHEntry> ownerEntry;
entry->GetAnyContentViewer(getter_AddRefs(ownerEntry),
getter_AddRefs(viewer));
if (viewer) {
NS_ASSERTION(ownerEntry,
"ContentViewer exists but its SHEntry is null");
#ifdef DEBUG_PAGE_CACHE
nsCOMPtr<nsIURI> uri;
ownerEntry->GetURI(getter_AddRefs(uri));
nsCAutoString spec;
if (uri)
uri->GetSpec(spec);
printf("per SHistory limit: evicting content viewer: %s\n", spec.get()); nsCOMPtr<nsISHEntryInternal> shentryInternal = do_QueryInterface(shentry);
#endif if (shentryInternal) {
shentryInternal->GetLastTouched(&mLastTouched);
// Drop the presentation state before destroying the viewer, so that } else {
// document teardown is able to correctly persist the state. NS_WARNING("Can't cast to nsISHEntryInternal?");
ownerEntry->SetContentViewer(nsnull); mLastTouched = 0;
ownerEntry->SyncPresentationState();
viewer->Destroy();
}
nsISHTransaction *temp = trans;
temp->GetNext(getter_AddRefs(trans));
} }
} }
bool operator<(const TransactionAndDistance &aOther) const
{
// Compare distances first, and fall back to last-accessed times.
if (aOther.mDistance != this->mDistance) {
return this->mDistance < aOther.mDistance;
}
return this->mLastTouched < aOther.mLastTouched;
}
bool operator==(const TransactionAndDistance &aOther) const
{
// This is a little silly; we need == so the default comaprator can be
// instantiated, but this function is never actually called when we sort
// the list of TransactionAndDistance objects.
return aOther.mDistance == this->mDistance &&
aOther.mLastTouched == this->mLastTouched;
}
nsCOMPtr<nsISHTransaction> mTransaction;
nsCOMPtr<nsIContentViewer> mViewer;
PRUint32 mLastTouched;
PRInt32 mDistance;
};
} // anonymous namespace
//static //static
void void
nsSHistory::EvictGlobalContentViewer() nsSHistory::GloballyEvictContentViewers()
{ {
// true until the total number of content viewers is <= total max // First, collect from each SHistory object the transactions which have a
// The usual case is that we only need to evict one content viewer. // cached content viewer. Associate with each transaction its distance from
// However, if somebody resets the pref value, we might occasionally // its SHistory's current index.
// need to evict more than one.
PRBool shouldTryEviction = PR_TRUE; nsTArray<TransactionAndDistance> transactions;
while (shouldTryEviction) {
// Walk through our list of SHistory objects, looking for content nsSHistory *shist = static_cast<nsSHistory*>(PR_LIST_HEAD(&gSHistoryList));
// viewers in the possible active window of all of the SHEntry objects.
// Keep track of the SHEntry object that has a ContentViewer and is
// farthest from the current focus in any SHistory object. The
// ContentViewer associated with that SHEntry will be evicted
PRInt32 distanceFromFocus = 0;
PRUint32 candidateLastTouched = 0;
nsCOMPtr<nsISHEntry> evictFromSHE;
nsCOMPtr<nsIContentViewer> evictViewer;
PRInt32 totalContentViewers = 0;
nsSHistory* shist = static_cast<nsSHistory*>
(PR_LIST_HEAD(&gSHistoryList));
while (shist != &gSHistoryList) { while (shist != &gSHistoryList) {
// Calculate the window of SHEntries that could possibly have a content
// viewer. There could be up to gHistoryMaxViewers content viewers, // Maintain a list of the transactions which have viewers and belong to
// but we don't know whether they are before or after the mIndex position // this particular shist object. We'll add this list to the global list,
// in the SHEntry list. Just check both sides, to be safe. // |transactions|, eventually.
nsTArray<TransactionAndDistance> shTransactions;
// Content viewers are likely to exist only within shist->mIndex -/+
// gHistoryMaxViewers, so only search within that range.
//
// A content viewer might exist outside that range due to either:
//
// * history.pushState or hash navigations, in which case a copy of the
// content viewer should exist within the range, or
//
// * bugs which cause us not to call nsSHistory::EvictContentViewers()
// often enough. Once we do call EvictContentViewers() for the
// SHistory object in question, we'll do a full search of its history
// and evict the out-of-range content viewers, so we don't bother here.
//
PRInt32 startIndex = NS_MAX(0, shist->mIndex - gHistoryMaxViewers); PRInt32 startIndex = NS_MAX(0, shist->mIndex - gHistoryMaxViewers);
PRInt32 endIndex = NS_MIN(shist->mLength - 1, PRInt32 endIndex = NS_MIN(shist->mLength - 1,
shist->mIndex + gHistoryMaxViewers); shist->mIndex + gHistoryMaxViewers);
nsCOMPtr<nsISHTransaction> trans; nsCOMPtr<nsISHTransaction> trans;
shist->GetTransactionAtIndex(startIndex, getter_AddRefs(trans)); shist->GetTransactionAtIndex(startIndex, getter_AddRefs(trans));
for (PRInt32 i = startIndex; trans && i <= endIndex; i++) {
nsCOMPtr<nsIContentViewer> contentViewer =
GetContentViewerForTransaction(trans);
for (PRInt32 i = startIndex; trans && i <= endIndex; ++i) { if (contentViewer) {
nsCOMPtr<nsISHEntry> entry; // Because one content viewer might belong to multiple SHEntries, we
trans->GetSHEntry(getter_AddRefs(entry)); // have to search through shTransactions to see if we already know
nsCOMPtr<nsIContentViewer> viewer; // about this content viewer. If we find the viewer, update its
nsCOMPtr<nsISHEntry> ownerEntry; // distance from the SHistory's index and continue.
entry->GetAnyContentViewer(getter_AddRefs(ownerEntry), bool found = false;
getter_AddRefs(viewer)); for (PRUint32 j = 0; j < shTransactions.Length(); j++) {
TransactionAndDistance &container = shTransactions[j];
PRUint32 entryLastTouched = 0; if (container.mViewer == contentViewer) {
if (gOptimizeEviction) { container.mDistance = PR_MIN(container.mDistance,
nsCOMPtr<nsISHEntryInternal> entryInternal = do_QueryInterface(entry); PR_ABS(i - shist->mIndex));
if (entryInternal) { found = PR_TRUE;
// Find when this entry was last activated break;
entryInternal->GetLastTouched(&entryLastTouched);
} }
} }
#ifdef DEBUG_PAGE_CACHE // If we didn't find a TransactionAndDistance for this content viewer, make a new
nsCOMPtr<nsIURI> uri; // one.
if (ownerEntry) { if (!found) {
ownerEntry->GetURI(getter_AddRefs(uri)); TransactionAndDistance container(trans, PR_ABS(i - shist->mIndex));
} else { shTransactions.AppendElement(container);
entry->GetURI(getter_AddRefs(uri));
}
nsCAutoString spec;
if (uri) {
uri->GetSpec(spec);
printf("Considering for eviction: %s\n", spec.get());
}
#endif
// This SHEntry has a ContentViewer, so check how far away it is from
// the currently used SHEntry within this SHistory object
if (viewer) {
PRInt32 distance = NS_ABS(shist->mIndex - i);
#ifdef DEBUG_PAGE_CACHE
printf("Has a cached content viewer: %s\n", spec.get());
printf("mIndex: %d i: %d\n", shist->mIndex, i);
#endif
totalContentViewers++;
// If this entry is further away from focus than any previously found
// or at the same distance but it is longer time since it was activated
// then take this entry as the new candiate for eviction
if (distance > distanceFromFocus || (distance == distanceFromFocus && candidateLastTouched > entryLastTouched)) {
#ifdef DEBUG_PAGE_CACHE
printf("Choosing as new eviction candidate: %s\n", spec.get());
#endif
candidateLastTouched = entryLastTouched;
distanceFromFocus = distance;
evictFromSHE = ownerEntry;
evictViewer = viewer;
} }
} }
nsISHTransaction *temp = trans; nsISHTransaction *temp = trans;
temp->GetNext(getter_AddRefs(trans)); temp->GetNext(getter_AddRefs(trans));
} }
// We've found all the transactions belonging to shist which have viewers.
// Add those transactions to our global list and move on.
transactions.AppendElements(shTransactions);
shist = static_cast<nsSHistory*>(PR_NEXT_LINK(shist)); shist = static_cast<nsSHistory*>(PR_NEXT_LINK(shist));
} }
#ifdef DEBUG_PAGE_CACHE // We now have collected all cached content viewers. First check that we
printf("Distance from focus: %d\n", distanceFromFocus); // have enough that we actually need to evict some.
printf("Total max viewers: %d\n", sHistoryMaxTotalViewers); if ((PRInt32)transactions.Length() <= sHistoryMaxTotalViewers) {
printf("Total number of viewers: %d\n", totalContentViewers); return;
#endif
if (totalContentViewers > sHistoryMaxTotalViewers && evictViewer) {
#ifdef DEBUG_PAGE_CACHE
nsCOMPtr<nsIURI> uri;
evictFromSHE->GetURI(getter_AddRefs(uri));
nsCAutoString spec;
if (uri) {
uri->GetSpec(spec);
printf("Evicting content viewer: %s\n", spec.get());
}
#endif
// Drop the presentation state before destroying the viewer, so that
// document teardown is able to correctly persist the state.
evictFromSHE->SetContentViewer(nsnull);
evictFromSHE->SyncPresentationState();
evictViewer->Destroy();
// If we only needed to evict one content viewer, then we are done.
// Otherwise, continue evicting until we reach the max total limit.
if (totalContentViewers - sHistoryMaxTotalViewers == 1) {
shouldTryEviction = PR_FALSE;
}
} else {
// couldn't find a content viewer to evict, so we are done
shouldTryEviction = PR_FALSE;
}
} // while shouldTryEviction
} }
NS_IMETHODIMP // If we need to evict, sort our list of transactions and evict the largest
nsSHistory::EvictExpiredContentViewerForEntry(nsISHEntry *aEntry) // ones. (We could of course get better algorithmic complexity here by using
// a heap or something more clever. But sHistoryMaxTotalViewers isn't large,
// so let's not worry about it.)
transactions.Sort();
for (PRInt32 i = transactions.Length() - 1;
i >= sHistoryMaxTotalViewers; --i) {
EvictContentViewerForTransaction(transactions[i].mTransaction);
}
}
nsresult
nsSHistory::EvictExpiredContentViewerForEntry(nsIBFCacheEntry *aEntry)
{ {
PRInt32 startIndex = NS_MAX(0, mIndex - gHistoryMaxViewers); PRInt32 startIndex = NS_MAX(0, mIndex - gHistoryMaxViewers);
PRInt32 endIndex = NS_MIN(mLength - 1, PRInt32 endIndex = NS_MIN(mLength - 1,
@ -1082,8 +1151,11 @@ nsSHistory::EvictExpiredContentViewerForEntry(nsISHEntry *aEntry)
for (i = startIndex; trans && i <= endIndex; ++i) { for (i = startIndex; trans && i <= endIndex; ++i) {
nsCOMPtr<nsISHEntry> entry; nsCOMPtr<nsISHEntry> entry;
trans->GetSHEntry(getter_AddRefs(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; break;
}
nsISHTransaction *temp = trans; nsISHTransaction *temp = trans;
temp->GetNext(getter_AddRefs(trans)); temp->GetNext(getter_AddRefs(trans));
@ -1091,21 +1163,13 @@ nsSHistory::EvictExpiredContentViewerForEntry(nsISHEntry *aEntry)
if (i > endIndex) if (i > endIndex)
return NS_OK; return NS_OK;
NS_ASSERTION(i != mIndex, "How did the current session entry expire?"); if (i == mIndex) {
if (i == mIndex) NS_WARNING("How did the current SHEntry expire?");
return NS_OK; return NS_OK;
// We evict content viewers for the expired entry and any other entries that
// we would have to go through the expired entry to get to (i.e. the entries
// 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);
} }
EvictContentViewerForTransaction(trans);
return NS_OK; return NS_OK;
} }
@ -1116,11 +1180,11 @@ nsSHistory::EvictExpiredContentViewerForEntry(nsISHEntry *aEntry)
//static //static
void void
nsSHistory::EvictAllContentViewersGlobally() nsSHistory::GloballyEvictAllContentViewers()
{ {
PRInt32 maxViewers = sHistoryMaxTotalViewers; PRInt32 maxViewers = sHistoryMaxTotalViewers;
sHistoryMaxTotalViewers = 0; sHistoryMaxTotalViewers = 0;
EvictGlobalContentViewer(); GloballyEvictContentViewers();
sHistoryMaxTotalViewers = maxViewers; sHistoryMaxTotalViewers = maxViewers;
} }

Просмотреть файл

@ -101,12 +101,11 @@ protected:
nsresult PrintHistory(); nsresult PrintHistory();
#endif #endif
// Evict the viewers at indices between aStartIndex and aEndIndex, // Evict content viewers in this window which don't lie in the "safe" range
// including aStartIndex but not aEndIndex. // around aIndex.
void EvictContentViewersInRange(PRInt32 aStartIndex, PRInt32 aEndIndex); void EvictOutOfRangeWindowContentViewers(PRInt32 aIndex);
void EvictWindowContentViewers(PRInt32 aFromIndex, PRInt32 aToIndex); static void GloballyEvictContentViewers();
static void EvictGlobalContentViewer(); static void GloballyEvictAllContentViewers();
static void EvictAllContentViewersGlobally();
// Calculates a max number of total // Calculates a max number of total
// content viewers to cache, based on amount of total memory // content viewers to cache, based on amount of total memory

Просмотреть файл

@ -119,6 +119,7 @@ _TEST_FILES = \
test_bug669671.html \ test_bug669671.html \
file_bug669671.sjs \ file_bug669671.sjs \
test_bug675587.html \ test_bug675587.html \
test_bfcache_plus_hash.html \
test_bug680257.html \ test_bug680257.html \
file_bug680257.html \ file_bug680257.html \
$(NULL) $(NULL)

Просмотреть файл

@ -50,6 +50,9 @@
const LISTEN_EVENTS = ["pageshow"]; const LISTEN_EVENTS = ["pageshow"];
const Cc = Components.classes;
const Ci = Components.interfaces;
var gBrowser; var gBrowser;
var gTestCount = 0; var gTestCount = 0;
var gTestsIterator; var gTestsIterator;
@ -96,6 +99,23 @@
QueryInterface(Components.interfaces.nsISHEntry); QueryInterface(Components.interfaces.nsISHEntry);
is(!!shEntry.contentViewer, gExpected[i], "content viewer "+i+", test "+gTestCount); is(!!shEntry.contentViewer, gExpected[i], "content viewer "+i+", test "+gTestCount);
} }
// Make sure none of the SHEntries share bfcache entries with one
// another.
for (var i = 0; i < history.count; i++) {
for (var j = 0; j < history.count; j++) {
if (j == i)
continue;
let shentry1 = history.getEntryAtIndex(i, false)
.QueryInterface(Ci.nsISHEntry);
let shentry2 = history.getEntryAtIndex(j, false)
.QueryInterface(Ci.nsISHEntry);
ok(!shentry1.sharesDocumentWith(shentry2),
'Test ' + gTestCount + ': shentry[' + i + "] shouldn't " +
"share document with shentry[" + j + ']');
}
}
} }
else { else {
is(history.count, gExpected.length, "Wrong history length in test "+gTestCount); is(history.count, gExpected.length, "Wrong history length in test "+gTestCount);

Просмотреть файл

@ -0,0 +1,120 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=646641
-->
<head>
<title>Test for Bug 646641</title>
<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>

Просмотреть файл

@ -218,12 +218,9 @@ public:
// First check if the document the IDBDatabase is part of is bfcached // First check if the document the IDBDatabase is part of is bfcached
nsCOMPtr<nsIDocument> ownerDoc = database->GetOwnerDocument(); nsCOMPtr<nsIDocument> ownerDoc = database->GetOwnerDocument();
nsISHEntry* shEntry; nsIBFCacheEntry* bfCacheEntry;
if (ownerDoc && (shEntry = ownerDoc->GetBFCacheEntry())) { if (ownerDoc && (bfCacheEntry = ownerDoc->GetBFCacheEntry())) {
nsCOMPtr<nsISHEntryInternal> sheInternal = do_QueryInterface(shEntry); bfCacheEntry->RemoveFromBFCacheSync();
if (sheInternal) {
sheInternal->RemoveFromBFCacheSync();
}
NS_ASSERTION(database->IsClosed(), NS_ASSERTION(database->IsClosed(),
"Kicking doc out of bfcache should have closed database"); "Kicking doc out of bfcache should have closed database");
continue; continue;

Просмотреть файл

@ -1980,7 +1980,7 @@ DocumentViewerImpl::Show(void)
printf("About to evict content viewers: prev=%d, loaded=%d\n", printf("About to evict content viewers: prev=%d, loaded=%d\n",
prevIndex, loadedIndex); prevIndex, loadedIndex);
#endif #endif
historyInt->EvictContentViewers(prevIndex, loadedIndex); historyInt->EvictOutOfRangeContentViewers(loadedIndex);
} }
} }
} }

Просмотреть файл

@ -140,7 +140,6 @@ pref("browser.display.remotetabs.timeout", 10);
/* session history */ /* session history */
pref("browser.sessionhistory.max_total_viewers", 1); pref("browser.sessionhistory.max_total_viewers", 1);
pref("browser.sessionhistory.max_entries", 50); pref("browser.sessionhistory.max_entries", 50);
pref("browser.sessionhistory.optimize_eviction", true);
/* session store */ /* session store */
pref("browser.sessionstore.resume_session_once", false); pref("browser.sessionstore.resume_session_once", false);

Просмотреть файл

@ -109,8 +109,6 @@ pref("dom.enable_performance", true);
// of content viewers to cache based on the amount of available memory. // of content viewers to cache based on the amount of available memory.
pref("browser.sessionhistory.max_total_viewers", -1); pref("browser.sessionhistory.max_total_viewers", -1);
pref("browser.sessionhistory.optimize_eviction", true);
pref("ui.use_native_colors", true); pref("ui.use_native_colors", true);
pref("ui.click_hold_context_menus", false); pref("ui.click_hold_context_menus", false);
pref("browser.display.use_document_fonts", 1); // 0 = never, 1 = quick, 2 = always pref("browser.display.use_document_fonts", 1); // 0 = never, 1 = quick, 2 = always