Bug 683777 - Back out bug 646641 on suspicion of causing crashes. rs=smaug
authorJustin Lebar <justin.lebar@gmail.com>
Wed, 12 Oct 2011 20:15:28 -0400
changeset 78649 66f1de1d9cbaab1183aae2e28d6eeb493f171dd9
parent 78648 535331914716c95b8409cee01e65e076e9daad05
child 78650 ccd092244e4c41c9612ed957c0a30e64e85ee38b
push id2623
push userjlebar@mozilla.com
push dateThu, 13 Oct 2011 00:16:04 +0000
treeherdermozilla-inbound@66f1de1d9cba [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs683777, 646641
milestone10.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 683777 - Back out bug 646641 on suspicion of causing crashes. rs=smaug
browser/components/sessionstore/src/nsSessionStore.js
browser/components/sessionstore/test/browser/browser_500328.js
content/base/public/nsIDocument.h
docshell/base/nsDocShell.cpp
docshell/build/nsDocShellModule.cpp
docshell/shistory/public/Makefile.in
docshell/shistory/public/nsIBFCacheEntry.idl
docshell/shistory/public/nsISHEntry.idl
docshell/shistory/public/nsISHistoryInternal.idl
docshell/shistory/src/Makefile.in
docshell/shistory/src/nsSHEntry.cpp
docshell/shistory/src/nsSHEntry.h
docshell/shistory/src/nsSHEntryShared.cpp
docshell/shistory/src/nsSHEntryShared.h
docshell/shistory/src/nsSHistory.cpp
docshell/shistory/src/nsSHistory.h
docshell/test/Makefile.in
docshell/test/chrome/bug396519_window.xul
docshell/test/test_bfcache_plus_hash.html
dom/indexedDB/IndexedDatabaseManager.cpp
layout/base/nsDocumentViewer.cpp
mobile/app/mobile.js
modules/libpref/src/init/all.js
--- a/browser/components/sessionstore/src/nsSessionStore.js
+++ b/browser/components/sessionstore/src/nsSessionStore.js
@@ -1915,17 +1915,19 @@ SessionStoreService.prototype = {
         // We can stop doing base64 encoding once our serialization into JSON
         // is guaranteed to handle all chars in strings, including embedded
         // nulls.
         entry.owner_b64 = btoa(String.fromCharCode.apply(null, ownerBytes));
       }
       catch (ex) { debug(ex); }
     }
 
-    entry.docIdentifier = aEntry.BFCacheEntry.ID;
+    if (aEntry.docIdentifier) {
+      entry.docIdentifier = aEntry.docIdentifier;
+    }
 
     if (aEntry.stateData != null) {
       entry.structuredCloneState = aEntry.stateData.getDataAsBase64();
       entry.structuredCloneVersion = aEntry.stateData.formatVersion;
     }
 
     if (!(aEntry instanceof Ci.nsISHContainer)) {
       return entry;
@@ -3022,16 +3024,17 @@ SessionStoreService.prototype = {
     browser.webNavigation.setCurrentURI(this._getURIFromString("about:blank"));
     // Attach data that will be restored on "load" event, after tab is restored.
     if (activeIndex > -1) {
       // restore those aspects of the currently active documents which are not
       // preserved in the plain history entries (mainly scroll state and text data)
       browser.__SS_restore_data = tabData.entries[activeIndex] || {};
       browser.__SS_restore_pageStyle = tabData.pageStyle || "";
       browser.__SS_restore_tab = aTab;
+      browser.__SS_restore_docIdentifier = curSHEntry.docIdentifier;
       didStartLoad = true;
       try {
         // In order to work around certain issues in session history, we need to
         // force session history to update its internal index and call reload
         // instead of gotoIndex. See bug 597315.
         browser.webNavigation.sessionHistory.getEntryAtIndex(activeIndex, true);
         browser.webNavigation.sessionHistory.reloadCurrentEntry();
       }
@@ -3176,26 +3179,34 @@ SessionStoreService.prototype = {
       var postdata = atob(aEntry.postdata_b64);
       var stream = Cc["@mozilla.org/io/string-input-stream;1"].
                    createInstance(Ci.nsIStringInputStream);
       stream.setData(postdata, postdata.length);
       shEntry.postData = stream;
     }
 
     if (aEntry.docIdentifier) {
-      // If we have a serialized document identifier, try to find an SHEntry
-      // which matches that doc identifier and adopt that SHEntry's
-      // BFCacheEntry.  If we don't find a match, insert shEntry as the match
-      // for the document identifier.
-      let matchingEntry = aDocIdentMap[aEntry.docIdentifier];
-      if (!matchingEntry) {
-        aDocIdentMap[aEntry.docIdentifier] = shEntry;
+      // Get a new document identifier for this entry to ensure that history
+      // entries after a session restore are considered to have different
+      // documents from the history entries before the session restore.
+      // Document identifiers are 64-bit ints, so JS will loose precision and
+      // start assigning all entries the same doc identifier if these ever get
+      // large enough.
+      //
+      // It's a potential security issue if document identifiers aren't
+      // globally unique, but shEntry.setUniqueDocIdentifier() below guarantees
+      // that we won't re-use a doc identifier within a given instance of the
+      // application.
+      let ident = aDocIdentMap[aEntry.docIdentifier];
+      if (!ident) {
+        shEntry.setUniqueDocIdentifier();
+        aDocIdentMap[aEntry.docIdentifier] = shEntry.docIdentifier;
       }
       else {
-        shEntry.adoptBFCacheEntry(matchingEntry);
+        shEntry.docIdentifier = ident;
       }
     }
 
     if (aEntry.owner_b64) {
       var ownerInput = Cc["@mozilla.org/io/string-input-stream;1"].
                        createInstance(Ci.nsIStringInputStream);
       var binaryData = atob(aEntry.owner_b64);
       ownerInput.setData(binaryData, binaryData.length);
@@ -3321,22 +3332,29 @@ SessionStoreService.prototype = {
     // don't restore text data and scrolling state if the user has navigated
     // away before the loading completed (except for in-page navigation)
     if (hasExpectedURL(aEvent.originalTarget, aBrowser.__SS_restore_data.url)) {
       var content = aEvent.originalTarget.defaultView;
       restoreTextDataAndScrolling(content, aBrowser.__SS_restore_data, "");
       aBrowser.markupDocumentViewer.authorStyleDisabled = selectedPageStyle == "_nostyle";
     }
 
+    if (aBrowser.__SS_restore_docIdentifier) {
+      let sh = aBrowser.webNavigation.sessionHistory;
+      sh.getEntryAtIndex(sh.index, false).QueryInterface(Ci.nsISHEntry).
+         docIdentifier = aBrowser.__SS_restore_docIdentifier;
+    }
+
     // notify the tabbrowser that this document has been completely restored
     this._sendTabRestoredNotification(aBrowser.__SS_restore_tab);
 
     delete aBrowser.__SS_restore_data;
     delete aBrowser.__SS_restore_pageStyle;
     delete aBrowser.__SS_restore_tab;
+    delete aBrowser.__SS_restore_docIdentifier;
   },
 
   /**
    * Restore visibility and dimension features to a window
    * @param aWindow
    *        Window reference
    * @param aWinData
    *        Object containing session data for the window
--- a/browser/components/sessionstore/test/browser/browser_500328.js
+++ b/browser/components/sessionstore/test/browser/browser_500328.js
@@ -111,23 +111,23 @@ function test() {
 
     tabBrowser.loadURI("http://example.com", null, null);
 
     tabBrowser.addEventListener("load", function(aEvent) {
       tabBrowser.removeEventListener("load", arguments.callee, true);
 
       // After these push/replaceState calls, the window should have three
       // history entries:
-      //   testURL        (state object: null)          <-- oldest
-      //   testURL        (state object: {obj1:1})
-      //   testURL?page2  (state object: {obj3:/^a$/})  <-- newest
+      //   testURL (state object: null)      <-- oldest
+      //   testURL (state object: {obj1:1})
+      //   page2   (state object: {obj3:/^a$/})  <-- newest
       let contentWindow = tab.linkedBrowser.contentWindow;
       let history = contentWindow.history;
       history.pushState({obj1:1}, "title-obj1");
-      history.pushState({obj2:2}, "title-obj2", "?page2");
+      history.pushState({obj2:2}, "title-obj2", "page2");
       history.replaceState({obj3:/^a$/}, "title-obj3");
 
       let state = ss.getTabState(tab);
       gBrowser.removeTab(tab);
 
       // Restore the state into a new tab.  Things don't work well when we
       // restore into the old tab, but that's not a real use case anyway.
       let tab2 = gBrowser.addTab("about:blank");
--- a/content/base/public/nsIDocument.h
+++ b/content/base/public/nsIDocument.h
@@ -64,17 +64,16 @@
 #ifdef MOZ_SMIL
 #include "nsSMILAnimationController.h"
 #endif // MOZ_SMIL
 #include "nsIScriptGlobalObject.h"
 #include "nsIDocumentEncoder.h"
 #include "nsIAnimationFrameListener.h"
 #include "nsEventStates.h"
 #include "nsIStructuredCloneContainer.h"
-#include "nsIBFCacheEntry.h"
 #include "nsDOMMemoryReporter.h"
 
 class nsIContent;
 class nsPresContext;
 class nsIPresShell;
 class nsIDocShell;
 class nsStyleSet;
 class nsIStyleSheet;
@@ -121,18 +120,18 @@ class Loader;
 
 namespace dom {
 class Link;
 class Element;
 } // namespace dom
 } // namespace mozilla
 
 #define NS_IDOCUMENT_IID      \
-{ 0x448c396a, 0x013c, 0x47b8, \
- { 0x95, 0xf4, 0x56, 0x68, 0x0f, 0x5f, 0x12, 0xf8 } }
+{ 0x4114a7c7, 0xb2f4, 0x4dea, \
+ { 0xac, 0x78, 0x20, 0xab, 0xda, 0x6f, 0xb2, 0xaf } }
 
 // Flag for AddStyleSheet().
 #define NS_STYLESHEET_FROM_CATALOG                (1 << 0)
 
 // Document states
 
 // RTL locale: specific to the XUL localedir attribute
 #define NS_DOCUMENT_STATE_RTL_LOCALE              NS_DEFINE_EVENT_STATE_MACRO(0)
@@ -475,25 +474,21 @@ public:
                                nsIPresShell** aInstancePtrResult) = 0;
   virtual void DeleteShell() = 0;
 
   nsIPresShell* GetShell() const
   {
     return GetBFCacheEntry() ? nsnull : mPresShell;
   }
 
-  void SetBFCacheEntry(nsIBFCacheEntry* aEntry)
-  {
-    mBFCacheEntry = aEntry;
+  void SetBFCacheEntry(nsISHEntry* aSHEntry) {
+    mSHEntry = aSHEntry;
   }
 
-  nsIBFCacheEntry* GetBFCacheEntry() const
-  {
-    return mBFCacheEntry;
-  }
+  nsISHEntry* GetBFCacheEntry() const { return mSHEntry; }
 
   /**
    * Return the parent document of this document. Will return null
    * unless this document is within a compound document and has a
    * parent. Note that this parent chain may cross chrome boundaries.
    */
   nsIDocument *GetParentDocument() const
   {
@@ -1786,19 +1781,19 @@ protected:
   // Weak reference to mScriptGlobalObject QI:d to nsPIDOMWindow,
   // updated on every set of mSecriptGlobalObject.
   nsPIDOMWindow *mWindow;
 
   nsCOMPtr<nsIDocumentEncoder> mCachedEncoder;
 
   AnimationListenerList mAnimationFrameListeners;
 
-  // This object allows us to evict ourself from the back/forward cache.  The
-  // pointer is non-null iff we're currently in the bfcache.
-  nsIBFCacheEntry *mBFCacheEntry;
+  // The session history entry in which we're currently bf-cached. Non-null
+  // if and only if we're currently in the bfcache.
+  nsISHEntry* mSHEntry;
 
   // Our base target.
   nsString mBaseTarget;
 
   nsCOMPtr<nsIStructuredCloneContainer> mStateObjectContainer;
   nsCOMPtr<nsIVariant> mStateObjectCached;
 };
 
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -4130,20 +4130,20 @@ nsDocShell::LoadErrorPage(nsIURI *aURI, 
                 spec.get(), NS_ConvertUTF16toUTF8(aURL).get(), chanName.get()));
     }
 #endif
     mFailedChannel = aFailedChannel;
     mFailedURI = aURI;
     mFailedLoadType = mLoadType;
 
     if (mLSHE) {
-        // Abandon mLSHE's BFCache entry and create a new one.  This way, if
-        // we go back or forward to another SHEntry with the same doc
-        // identifier, the error page won't persist.
-        mLSHE->AbandonBFCacheEntry();
+        // If we don't give mLSHE a new doc identifier here, when we go back or
+        // forward to another SHEntry with the same doc identifier, the error
+        // page will persist.
+        mLSHE->SetUniqueDocIdentifier();
     }
 
     nsCAutoString url;
     nsCAutoString charset;
     if (aURI)
     {
         nsresult rv = aURI->GetSpec(url);
         rv |= aURI->GetOriginCharset(charset);
@@ -4406,20 +4406,20 @@ nsDocShell::LoadPage(nsISupports *aPageD
     }
 
     // Now clone shEntryIn, since we might end up modifying it later on, and we
     // want a page descriptor to be reusable.
     nsCOMPtr<nsISHEntry> shEntry;
     nsresult rv = shEntryIn->Clone(getter_AddRefs(shEntry));
     NS_ENSURE_SUCCESS(rv, rv);
 
-    // Give our cloned shEntry a new bfcache entry so this load is independent
-    // of all other loads.  (This is important, in particular, for bugs 582795
-    // and 585298.)
-    rv = shEntry->AbandonBFCacheEntry();
+    // Give our cloned shEntry a new document identifier so this load is
+    // independent of all other loads.  (This is important, in particular,
+    // for bugs 582795 and 585298.)
+    rv = shEntry->SetUniqueDocIdentifier();
     NS_ENSURE_SUCCESS(rv, rv);
 
     //
     // load the page as view-source
     //
     if (nsIWebPageDescriptor::DISPLAY_AS_SOURCE == aDisplayType) {
         nsCOMPtr<nsIURI> oldUri, newUri;
         nsCString spec, newSpec;
@@ -8404,25 +8404,27 @@ nsDocShell::InternalLoad(nsIURI * aURI,
                                            curBeforeHash, curHash) :
             NS_ERROR_FAILURE;
         splitRv2 = nsContentUtils::SplitURIAtHash(aURI, newBeforeHash, newHash);
 
         bool sameExceptHashes = NS_SUCCEEDED(splitRv1) &&
                                   NS_SUCCEEDED(splitRv2) &&
                                   curBeforeHash.Equals(newBeforeHash);
 
-        // XXX rename
-        bool sameDocument = false;
+        bool sameDocIdent = PR_FALSE;
         if (mOSHE && aSHEntry) {
             // We're doing a history load.
 
-            mOSHE->SharesDocumentWith(aSHEntry, &sameDocument);
+            PRUint64 ourDocIdent, otherDocIdent;
+            mOSHE->GetDocIdentifier(&ourDocIdent);
+            aSHEntry->GetDocIdentifier(&otherDocIdent);
+            sameDocIdent = (ourDocIdent == otherDocIdent);
 
 #ifdef DEBUG
-            if (sameDocument) {
+            if (sameDocIdent) {
                 nsCOMPtr<nsIInputStream> currentPostData;
                 mOSHE->GetPostData(getter_AddRefs(currentPostData));
                 NS_ASSERTION(currentPostData == aPostData,
                              "Different POST data for entries for the same page?");
             }
 #endif
         }
 
@@ -8435,17 +8437,17 @@ nsDocShell::InternalLoad(nsIURI * aURI,
         //
         //  b) we're navigating to a new shentry whose URI differs from the
         //     current URI only in its hash, the new hash is non-empty, and
         //     we're not doing a POST.
         //
         // The restriction tha the SHEntries in (a) must be different ensures
         // that history.go(0) and the like trigger full refreshes, rather than
         // short-circuited loads.
-        bool doShortCircuitedLoad = (sameDocument && mOSHE != aSHEntry) ||
+        bool doShortCircuitedLoad = (sameDocIdent && mOSHE != aSHEntry) ||
                                       (!aSHEntry && aPostData == nsnull &&
                                        sameExceptHashes && !newHash.IsEmpty());
 
         // Fire a hashchange event if we're doing a short-circuited load and the
         // URIs differ only in their hashes.
         bool doHashchange = doShortCircuitedLoad &&
                               sameExceptHashes &&
                               !curHash.Equals(newHash);
@@ -8486,35 +8488,32 @@ nsDocShell::InternalLoad(nsIURI * aURI,
                 mOSHE->GetOwner(getter_AddRefs(owner));
             }
             // Pass true for aCloneSHChildren, since we're not
             // changing documents here, so all of our subframes are
             // still relevant to the new session history entry.
             OnNewURI(aURI, nsnull, owner, mLoadType, PR_TRUE, PR_TRUE, PR_TRUE);
 
             nsCOMPtr<nsIInputStream> postData;
+            PRUint64 docIdent = PRUint64(-1);
             nsCOMPtr<nsISupports> cacheKey;
 
             if (mOSHE) {
                 /* save current position of scroller(s) (bug 59774) */
                 mOSHE->SetScrollPosition(cx, cy);
                 // Get the postdata and page ident from the current page, if
                 // the new load is being done via normal means.  Note that
                 // "normal means" can be checked for just by checking for
                 // LOAD_CMD_NORMAL, given the loadType and allowScroll check
                 // above -- it filters out some LOAD_CMD_NORMAL cases that we
                 // wouldn't want here.
                 if (aLoadType & LOAD_CMD_NORMAL) {
                     mOSHE->GetPostData(getter_AddRefs(postData));
+                    mOSHE->GetDocIdentifier(&docIdent);
                     mOSHE->GetCacheKey(getter_AddRefs(cacheKey));
-
-                    // Link our new SHEntry to the old SHEntry's back/forward
-                    // cache data, since the two SHEntries correspond to the
-                    // same document.
-                    mLSHE->AdoptBFCacheEntry(mOSHE);
                 }
             }
 
             /* Assign mOSHE to mLSHE. This will either be a new entry created
              * by OnNewURI() for normal loads or aSHEntry for history loads.
              */
             if (mLSHE) {
                 SetHistoryEntry(&mOSHE, mLSHE);
@@ -8524,16 +8523,21 @@ nsDocShell::InternalLoad(nsIURI * aURI,
                 // page will restore the appropriate postData.
                 if (postData)
                     mOSHE->SetPostData(postData);
 
                 // Make sure we won't just repost without hitting the
                 // cache first
                 if (cacheKey)
                     mOSHE->SetCacheKey(cacheKey);
+
+                // Propagate our document identifier to the new mOSHE so that
+                // we'll know it's related by an anchor navigation or pushState.
+                if (docIdent != PRUint64(-1))
+                    mOSHE->SetDocIdentifier(docIdent);
             }
 
             /* restore previous position of scroller(s), if we're moving
              * back in history (bug 59774)
              */
             if (mOSHE && (aLoadType == LOAD_HISTORY || aLoadType == LOAD_RELOAD_NORMAL))
             {
                 nscoord bx, by;
@@ -8567,37 +8571,37 @@ nsDocShell::InternalLoad(nsIURI * aURI,
                 if (history) {
                     history->SetURITitle(aURI, mTitle);
                 }
                 else if (mGlobalHistory) {
                     mGlobalHistory->SetPageTitle(aURI, mTitle);
                 }
             }
 
-            if (sameDocument) {
+            if (sameDocIdent) {
                 // Set the doc's URI according to the new history entry's URI
                 nsCOMPtr<nsIURI> newURI;
                 mOSHE->GetURI(getter_AddRefs(newURI));
                 NS_ENSURE_TRUE(newURI, NS_ERROR_FAILURE);
                 nsCOMPtr<nsIDocument> doc =
                   do_GetInterface(GetAsSupports(this));
                 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
 
                 doc->SetDocumentURI(newURI);
             }
 
             SetDocCurrentStateObj(mOSHE);
 
             // Dispatch the popstate and hashchange events, as appropriate.
             nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(mScriptGlobal);
             if (window) {
-                // Need the doHashchange check here since sameDocument is
+                // Need the doHashchange check here since sameDocIdent is
                 // false if we're navigating to a new shentry (i.e. a aSHEntry
                 // is null), such as when clicking a <a href="#foo">.
-                if (sameDocument || doHashchange) {
+                if (sameDocIdent || doHashchange) {
                   window->DispatchSyncPopState();
                 }
 
                 if (doHashchange) {
                   // Make sure to use oldURI here, not mCurrentURI, because by
                   // now, mCurrentURI has changed!
                   window->DispatchAsyncHashchange(oldURI, aURI);
                 }
@@ -9370,21 +9374,21 @@ nsDocShell::OnNewURI(nsIURI * aURI, nsIC
 
         if (httpChannel) {
             nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel));
             if (uploadChannel) {
                 uploadChannel->GetUploadStream(getter_AddRefs(inputStream));
             }
 
             // If the response status indicates an error, unlink this session
-            // history entry from any entries sharing its document.
+            // history entry from any entries sharing its doc ident.
             PRUint32 responseStatus;
             nsresult rv = httpChannel->GetResponseStatus(&responseStatus);
             if (mLSHE && NS_SUCCEEDED(rv) && responseStatus >= 400) {
-                mLSHE->AbandonBFCacheEntry();
+                mLSHE->SetUniqueDocIdentifier();
             }
         }
     }
     /* Create SH Entry (mLSHE) only if there is a  SessionHistory object (mSessionHistory) in
      * the current frame or in the root docshell
      */
     nsCOMPtr<nsISHistory> rootSH = mSessionHistory;
     if (!rootSH) {
@@ -9603,19 +9607,19 @@ nsDocShell::AddState(nsIVariant *aData, 
     // 5. If aReplace is false (i.e. we're doing a pushState instead of a
     //    replaceState), notify bfcache that we've navigated to a new page.
     // 6. If the third argument is present, set the document's current address
     //    to the absolute URL found in step 2.
     //
     // It's important that this function not run arbitrary scripts after step 1
     // and before completing step 5.  For example, if a script called
     // history.back() before we completed step 5, bfcache might destroy an
-    // active content viewer.  Since EvictOutOfRangeContentViewers at the end of
-    // step 5 might run script, we can't just put a script blocker around the
-    // critical section.
+    // active content viewer.  Since EvictContentViewers at the end of step 5
+    // might run script, we can't just put a script blocker around the critical
+    // section.
     //
     // Note that we completely ignore the aTitle parameter.
 
     nsresult rv;
 
     nsCOMPtr<nsIDocument> document = do_GetInterface(GetAsSupports(this));
     NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
 
@@ -9785,19 +9789,21 @@ nsDocShell::AddState(nsIVariant *aData, 
         // Since we're not changing which page we have loaded, pass
         // true for aCloneChildren.
         rv = AddToSessionHistory(newURI, nsnull, nsnull, PR_TRUE,
                                  getter_AddRefs(newSHEntry));
         NS_ENSURE_SUCCESS(rv, rv);
 
         NS_ENSURE_TRUE(newSHEntry, NS_ERROR_FAILURE);
 
-        // Link the new SHEntry to the old SHEntry's BFCache entry, since the
-        // two entries correspond to the same document.
-        NS_ENSURE_SUCCESS(newSHEntry->AdoptBFCacheEntry(oldOSHE),
+        // Set the new SHEntry's document identifier, if we can.
+        PRUint64 ourDocIdent;
+        NS_ENSURE_SUCCESS(oldOSHE->GetDocIdentifier(&ourDocIdent),
+                          NS_ERROR_FAILURE);
+        NS_ENSURE_SUCCESS(newSHEntry->SetDocIdentifier(ourDocIdent),
                           NS_ERROR_FAILURE);
 
         // Set the new SHEntry's title (bug 655273).
         nsString title;
         mOSHE->GetTitle(getter_Copies(title));
         newSHEntry->SetTitle(title);
 
         // AddToSessionHistory may not modify mOSHE.  In case it doesn't,
@@ -9833,17 +9839,17 @@ nsDocShell::AddState(nsIVariant *aData, 
 
         nsCOMPtr<nsISHistoryInternal> internalSH =
             do_QueryInterface(rootSH);
         NS_ENSURE_TRUE(internalSH, NS_ERROR_UNEXPECTED);
 
         PRInt32 curIndex = -1;
         rv = rootSH->GetIndex(&curIndex);
         if (NS_SUCCEEDED(rv) && curIndex > -1) {
-            internalSH->EvictOutOfRangeContentViewers(curIndex);
+            internalSH->EvictContentViewers(curIndex - 1, curIndex);
         }
     }
 
     // Step 6: If the document's URI changed, update document's URI and update
     // global history.
     //
     // We need to call FireOnLocationChange so that the browser's address bar
     // gets updated and the back button is enabled, but we only need to
--- a/docshell/build/nsDocShellModule.cpp
+++ b/docshell/build/nsDocShellModule.cpp
@@ -61,17 +61,16 @@
 #include "nsExternalSharingAppService.h"
 #endif
 #if defined(ANDROID)
 #include "nsExternalURLHandlerService.h"
 #endif
 
 // session history
 #include "nsSHEntry.h"
-#include "nsSHEntryShared.h"
 #include "nsSHistory.h"
 #include "nsSHTransaction.h"
 
 // download history
 #include "nsDownloadHistory.h"
 
 static bool gInitialized = false;
 
@@ -83,25 +82,25 @@ Initialize()
   if (gInitialized) {
     return NS_OK;
   }
   gInitialized = PR_TRUE;
 
   nsresult rv = nsSHistory::Startup();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsSHEntryShared::Startup();
-  return NS_OK;
+  rv = nsSHEntry::Startup();
+  return rv;
 }
 
 static void
 Shutdown()
 {
   nsSHistory::Shutdown();
-  nsSHEntryShared::Shutdown();
+  nsSHEntry::Shutdown();
   gInitialized = PR_FALSE;
 }
 
 // docshell
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsDocShell, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsDefaultURIFixup)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsWebNavigationInfo, Init)
 
--- a/docshell/shistory/public/Makefile.in
+++ b/docshell/shistory/public/Makefile.in
@@ -51,13 +51,12 @@ SDK_XPIDLSRCS   = \
                   nsISHistoryListener.idl   \
                   $(NULL)
 
 XPIDLSRCS	= \
 		  nsISHEntry.idl        \
                   nsISHContainer.idl    \
                   nsISHTransaction.idl  \
                   nsISHistoryInternal.idl \
-		  nsIBFCacheEntry.idl \
                   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
deleted file mode 100644
--- a/docshell/shistory/public/nsIBFCacheEntry.idl
+++ /dev/null
@@ -1,47 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is mozilla.org code.
- *
- * The Initial Developer of the Original Code is the Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2011
- * the Initial Developer. All Rights Reserved.
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either of the GNU General Public License Version 2 or later (the "GPL"),
- * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-#include "nsISupports.idl"
-
-/**
- * This interface lets you evict a document from the back/forward cache.
- */
-[scriptable, uuid(a576060e-c7df-4d81-aa8c-5b52bd6ad25d)]
-interface nsIBFCacheEntry : nsISupports
-{
-  void RemoveFromBFCacheSync();
-  void RemoveFromBFCacheAsync();
-
-  readonly attribute unsigned long long ID;
-};
--- a/docshell/shistory/public/nsISHEntry.idl
+++ b/docshell/shistory/public/nsISHEntry.idl
@@ -46,28 +46,25 @@
 
 interface nsILayoutHistoryState;
 interface nsIContentViewer;
 interface nsIURI;
 interface nsIInputStream;
 interface nsIDocShellTreeItem;
 interface nsISupportsArray;
 interface nsIStructuredCloneContainer;
-interface nsIBFCacheEntry;
-
 %{C++
 struct nsIntRect;
 class nsDocShellEditorData;
-class nsSHEntryShared;
 %}
 [ref] native nsIntRect(nsIntRect);
 [ptr] native nsDocShellEditorDataPtr(nsDocShellEditorData);
-[ptr] native nsSHEntryShared(nsSHEntryShared);
+
 
-[scriptable, uuid(6443FD72-A50F-4B8B-BB82-BB1FA04CB15D)]
+[scriptable, uuid(b92d403e-f5ec-4b81-b0e3-6e6c241cef2d)]
 interface nsISHEntry : nsIHistoryEntry
 {
     /** URI for the document */
     void setURI(in nsIURI aURI);
 
     /** Referrer URI */
     attribute nsIURI referrerURI;
 
@@ -138,16 +135,31 @@ interface nsISHEntry : nsIHistoryEntry
     attribute unsigned long loadType;
 
     /**
      * An ID to help identify this entry from others during
      * subframe navigation
      */
     attribute unsigned long ID;
 
+    /**
+     * docIdentifier is an integer that should be the same for two entries
+     * attached to the same docshell if and only if the two entries are entries
+     * for the same document.  In practice, two entries A and B will have the
+     * same docIdentifier if we arrived at B by clicking an anchor link in A or
+     * if B was created by A's calling history.pushState().
+     */
+    attribute unsigned long long docIdentifier;
+
+    /**
+     * Changes this entry's doc identifier to a new value which is unique
+     * among those of all other entries.
+     */
+    void setUniqueDocIdentifier();
+
     /** attribute to set and get the cache key for the entry */
     attribute nsISupports cacheKey;
 
     /** attribute to indicate whether layoutHistoryState should be saved */
     attribute boolean saveLayoutStateFlag;
 
     /** attribute to indicate whether the page is already expired in cache */
     attribute boolean expirationStatus;
@@ -235,69 +247,28 @@ interface nsISHEntry : nsIHistoryEntry
      * when isDynamicallyAdded is called on it.
      */
     boolean hasDynamicallyAddedChild();
 
     /**
      * The history ID of the docshell.
      */
     attribute unsigned long long docshellID;
-
-    readonly attribute nsIBFCacheEntry BFCacheEntry;
-
-    /**
-     * Does this SHEntry point to the given BFCache entry?  If so, evicting
-     * the BFCache entry will evict the SHEntry, since the two entries
-     * correspond to the same document.
-     */
-    [notxpcom, noscript]
-    boolean hasBFCacheEntry(in nsIBFCacheEntry aEntry);
-
-    /**
-     * Adopt aEntry's BFCacheEntry, so now both this and aEntry point to
-     * aEntry's BFCacheEntry.
-     */
-    void adoptBFCacheEntry(in nsISHEntry aEntry);
-
-    /**
-     * Create a new BFCache entry and drop our reference to our old one.  This
-     * call unlinks this SHEntry from any other SHEntries for its document.
-     */
-    void abandonBFCacheEntry();
-
-    /**
-     * Does this SHEntry correspond to the same document as aEntry?  This is
-     * true iff the two SHEntries have the same BFCacheEntry.  So in
-     * particular, sharesDocumentWith(aEntry) is guaranteed to return true if
-     * it's preceeded by a call to adoptBFCacheEntry(aEntry).
-     */
-    boolean sharesDocumentWith(in nsISHEntry aEntry);
 };
 
 [scriptable, uuid(bb66ac35-253b-471f-a317-3ece940f04c5)]
 interface nsISHEntryInternal : nsISupports
 {
     [notxpcom] void RemoveFromBFCacheAsync();
     [notxpcom] void RemoveFromBFCacheSync();
 
     /**
      * A number that is assigned by the sHistory when the entry is activated
      */
     attribute unsigned long lastTouched;
-
-    /**
-     * Some state, particularly that related to the back/forward cache, is
-     * shared between SHEntries which correspond to the same document.  This
-     * method gets a pointer to that shared state.
-     *
-     * This shared state is the SHEntry's BFCacheEntry.  So
-     * hasBFCacheEntry(getSharedState()) is guaranteed to return true.
-     */
-    [noscript, notxpcom]
-    nsSHEntryShared getSharedState();
 };
 
 %{ C++
 // {BFD1A791-AD9F-11d3-BDC7-0050040A9B44}
 #define NS_SHENTRY_CID \
 {0xbfd1a791, 0xad9f, 0x11d3, {0xbd, 0xc7, 0x0, 0x50, 0x4, 0xa, 0x9b, 0x44}}
 
 #define NS_SHENTRY_CONTRACTID \
--- a/docshell/shistory/public/nsISHistoryInternal.idl
+++ b/docshell/shistory/public/nsISHistoryInternal.idl
@@ -52,17 +52,17 @@ interface nsIDocShell;
 #define NS_SHISTORY_INTERNAL_CONTRACTID "@mozilla.org/browser/shistory-internal;1"
 
 template<class E, class A> class nsTArray;
 struct nsTArrayDefaultAllocator;
 %}
 
 [ref] native nsDocshellIDArray(nsTArray<PRUint64, nsTArrayDefaultAllocator>);
 
-[scriptable, uuid(e27cf38e-c19f-4294-bd31-d7e0916e7fa2)]
+[scriptable, uuid(2dede933-25e1-47a3-8f61-0127c785ea01)]
 interface nsISHistoryInternal: nsISupports
 {
   /**
    * Add a new Entry to the History List
    * @param aEntry - The entry to add
    * @param aPersist - If true this specifies that the entry should persist
    * in the list.  If false, this means that when new entries are added
    * this element will not appear in the session history list.
@@ -91,35 +91,30 @@ interface nsISHistoryInternal: nsISuppor
    */
    void replaceEntry(in long aIndex, in nsISHEntry aReplaceEntry);
 
   /** 
    * Get handle to the history listener
    */
    readonly attribute nsISHistoryListener listener;
 
-   /**
-    * Evict content viewers which don't lie in the "safe" range around aIndex.
-    * In practice, this should leave us with no more than gHistoryMaxViewers
-    * viewers associated with this SHistory object.
-    *
-    * 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 evictOutOfRangeContentViewers(in long aIndex);
+  /**
+   * Evict content viewers until the number of content viewers per tab
+   * is no more than gHistoryMaxViewers.  Also, count
+   * total number of content viewers globally and evict one if we are over
+   * our total max.  This is always called in Show(), after we destroy
+   * the previous viewer.
+   */
+   void evictContentViewers(in long previousIndex, in long index);
    
    /**
-    * Evict the content viewer associated with a bfcache entry
+    * Evict the content viewer associated with a session history entry
     * that has timed out.
     */
-   void evictExpiredContentViewerForEntry(in nsIBFCacheEntry aEntry);
+   void evictExpiredContentViewerForEntry(in nsISHEntry aEntry);
 
    /**
     * Evict all the content viewers in this session history
     */
    void evictAllContentViewers();
 
    /**
     * Removes entries from the history if their docshellID is in
--- a/docshell/shistory/src/Makefile.in
+++ b/docshell/shistory/src/Makefile.in
@@ -43,20 +43,17 @@ VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE		= shistory
 LIBRARY_NAME	= shistory_s
 FORCE_STATIC_LIB = 1
 LIBXUL_LIBRARY	= 1
 
-EXPORTS		= nsSHEntryShared.h \
-		  $(NULL)
 
 CPPSRCS		= nsSHEntry.cpp        \
             nsSHTransaction.cpp   \
             nsSHistory.cpp \
-	    nsSHEntryShared.cpp \
             $(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
 LOCAL_INCLUDES += -I$(srcdir)/../../base
--- a/docshell/shistory/src/nsSHEntry.cpp
+++ b/docshell/shistory/src/nsSHEntry.cpp
@@ -31,87 +31,171 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
+#ifdef DEBUG_bryner
+#define DEBUG_PAGE_CACHE
+#endif
+
 // Local Includes
 #include "nsSHEntry.h"
 #include "nsXPIDLString.h"
 #include "nsReadableUtils.h"
 #include "nsIDocShellLoadInfo.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsIDocument.h"
 #include "nsIDOMDocument.h"
+#include "nsAutoPtr.h"
+#include "nsThreadUtils.h"
+#include "nsIWebNavigation.h"
 #include "nsISHistory.h"
 #include "nsISHistoryInternal.h"
 #include "nsDocShellEditorData.h"
-#include "nsSHEntryShared.h"
-#include "nsILayoutHistoryState.h"
-#include "nsIContentViewer.h"
-#include "nsISupportsArray.h"
+#include "nsIDocShell.h"
 
 namespace dom = mozilla::dom;
 
+// Hardcode this to time out unused content viewers after 30 minutes
+#define CONTENT_VIEWER_TIMEOUT_SECONDS 30*60
+
+typedef nsExpirationTracker<nsSHEntry,3> HistoryTrackerBase;
+class HistoryTracker : public HistoryTrackerBase {
+public:
+  // Expire cached contentviewers after 20-30 minutes in the cache.
+  HistoryTracker() : HistoryTrackerBase((CONTENT_VIEWER_TIMEOUT_SECONDS/2)*1000) {}
+  
+protected:
+  virtual void NotifyExpired(nsSHEntry* aObj) {
+    RemoveObject(aObj);
+    aObj->Expire();
+  }
+};
+
+static HistoryTracker *gHistoryTracker = nsnull;
 static PRUint32 gEntryID = 0;
+static PRUint64 gEntryDocIdentifier = 0;
+
+nsresult nsSHEntry::Startup()
+{
+  gHistoryTracker = new HistoryTracker();
+  return gHistoryTracker ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+void nsSHEntry::Shutdown()
+{
+  delete gHistoryTracker;
+  gHistoryTracker = nsnull;
+}
+
+static void StopTrackingEntry(nsSHEntry *aEntry)
+{
+  if (aEntry->GetExpirationState()->IsTracked()) {
+    gHistoryTracker->RemoveObject(aEntry);
+  }
+}
 
 //*****************************************************************************
 //***    nsSHEntry: Object Management
 //*****************************************************************************
 
 
-nsSHEntry::nsSHEntry()
+nsSHEntry::nsSHEntry() 
   : mLoadType(0)
   , mID(gEntryID++)
+  , mDocIdentifier(gEntryDocIdentifier++)
   , mScrollPositionX(0)
   , mScrollPositionY(0)
   , mURIWasModified(PR_FALSE)
+  , mIsFrameNavigation(PR_FALSE)
+  , mSaveLayoutState(PR_TRUE)
+  , mExpired(PR_FALSE)
+  , mSticky(PR_TRUE)
+  , mDynamicallyCreated(PR_FALSE)
+  , mParent(nsnull)
+  , mViewerBounds(0, 0, 0, 0)
+  , mDocShellID(0)
+  , mLastTouched(0)
 {
-  mShared = new nsSHEntryShared();
 }
 
 nsSHEntry::nsSHEntry(const nsSHEntry &other)
-  : mShared(other.mShared)
-  , mURI(other.mURI)
+  : mURI(other.mURI)
   , mReferrerURI(other.mReferrerURI)
+  // XXX why not copy mDocument?
   , mTitle(other.mTitle)
   , mPostData(other.mPostData)
+  , mLayoutHistoryState(other.mLayoutHistoryState)
   , mLoadType(0)         // XXX why not copy?
   , mID(other.mID)
+  , mDocIdentifier(other.mDocIdentifier)
   , mScrollPositionX(0)  // XXX why not copy?
   , mScrollPositionY(0)  // XXX why not copy?
   , mURIWasModified(other.mURIWasModified)
+  , mIsFrameNavigation(other.mIsFrameNavigation)
+  , mSaveLayoutState(other.mSaveLayoutState)
+  , mExpired(other.mExpired)
+  , mSticky(PR_TRUE)
+  , mDynamicallyCreated(other.mDynamicallyCreated)
+  // XXX why not copy mContentType?
+  , mCacheKey(other.mCacheKey)
+  , mParent(other.mParent)
+  , mViewerBounds(0, 0, 0, 0)
+  , mOwner(other.mOwner)
+  , mDocShellID(other.mDocShellID)
   , mStateData(other.mStateData)
 {
 }
 
 static bool
 ClearParentPtr(nsISHEntry* aEntry, void* /* aData */)
 {
   if (aEntry) {
     aEntry->SetParent(nsnull);
   }
   return PR_TRUE;
 }
 
 nsSHEntry::~nsSHEntry()
 {
-  // Null out the mParent pointers on all our kids.
+  StopTrackingEntry(this);
+
+  // 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.Clear();
+
+  if (mContentViewer) {
+    // RemoveFromBFCacheSync is virtual, so call the nsSHEntry version
+    // explicitly
+    nsSHEntry::RemoveFromBFCacheSync();
+  }
+
+  mEditorData = nsnull;
+
+#ifdef DEBUG
+  // This is not happening as far as I can tell from breakpad as of early November 2007
+  nsExpirationTracker<nsSHEntry,3>::Iterator iterator(gHistoryTracker);
+  nsSHEntry* elem;
+  while ((elem = iterator.Next()) != nsnull) {
+    NS_ASSERTION(elem != this, "Found dead entry still in the tracker!");
+  }
+#endif
 }
 
 //*****************************************************************************
 //    nsSHEntry: nsISupports
 //*****************************************************************************
 
-NS_IMPL_ISUPPORTS4(nsSHEntry, nsISHContainer, nsISHEntry, nsIHistoryEntry,
-                   nsISHEntryInternal)
+NS_IMPL_ISUPPORTS5(nsSHEntry, nsISHContainer, nsISHEntry, nsIHistoryEntry,
+                   nsIMutationObserver, nsISHEntryInternal)
 
 //*****************************************************************************
 //    nsSHEntry: nsISHEntry
 //*****************************************************************************
 
 NS_IMETHODIMP nsSHEntry::SetScrollPosition(PRInt32 x, PRInt32 y)
 {
   mScrollPositionX = x;
@@ -162,23 +246,45 @@ NS_IMETHODIMP nsSHEntry::SetReferrerURI(
 {
   mReferrerURI = aReferrerURI;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetContentViewer(nsIContentViewer *aViewer)
 {
-  return mShared->SetContentViewer(aViewer);
+  NS_PRECONDITION(!aViewer || !mContentViewer, "SHEntry already contains viewer");
+
+  if (mContentViewer || !aViewer) {
+    DropPresentationState();
+  }
+
+  mContentViewer = aViewer;
+
+  if (mContentViewer) {
+    gHistoryTracker->AddObject(this);
+
+    nsCOMPtr<nsIDOMDocument> domDoc;
+    mContentViewer->GetDOMDocument(getter_AddRefs(domDoc));
+    // Store observed document in strong pointer in case it is removed from
+    // the contentviewer
+    mDocument = do_QueryInterface(domDoc);
+    if (mDocument) {
+      mDocument->SetBFCacheEntry(this);
+      mDocument->AddMutationObserver(this);
+    }
+  }
+
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetContentViewer(nsIContentViewer **aResult)
 {
-  *aResult = mShared->mContentViewer;
+  *aResult = mContentViewer;
   NS_IF_ADDREF(*aResult);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetAnyContentViewer(nsISHEntry **aOwnerEntry,
                                nsIContentViewer **aResult)
 {
@@ -208,24 +314,24 @@ nsSHEntry::GetAnyContentViewer(nsISHEntr
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetSticky(bool aSticky)
 {
-  mShared->mSticky = aSticky;
+  mSticky = aSticky;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetSticky(bool *aSticky)
 {
-  *aSticky = mShared->mSticky;
+  *aSticky = mSticky;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsSHEntry::GetTitle(PRUnichar** aTitle)
 {
   // Check for empty title...
   if (mTitle.IsEmpty() && mURI) {
     // Default title is the URL.
@@ -254,28 +360,26 @@ NS_IMETHODIMP nsSHEntry::GetPostData(nsI
 NS_IMETHODIMP nsSHEntry::SetPostData(nsIInputStream* aPostData)
 {
   mPostData = aPostData;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsSHEntry::GetLayoutHistoryState(nsILayoutHistoryState** aResult)
 {
-  *aResult = mShared->mLayoutHistoryState;
+  *aResult = mLayoutHistoryState;
   NS_IF_ADDREF(*aResult);
   return NS_OK;
 }
 
 NS_IMETHODIMP nsSHEntry::SetLayoutHistoryState(nsILayoutHistoryState* aState)
 {
-  mShared->mLayoutHistoryState = aState;
-  if (mShared->mLayoutHistoryState) {
-    mShared->mLayoutHistoryState->
-      SetScrollPositionOnly(!mShared->mSaveLayoutState);
-  }
+  mLayoutHistoryState = aState;
+  if (mLayoutHistoryState)
+    mLayoutHistoryState->SetScrollPositionOnly(!mSaveLayoutState);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP nsSHEntry::GetLoadType(PRUint32 * aResult)
 {
   *aResult = mLoadType;
   return NS_OK;
@@ -294,236 +398,209 @@ NS_IMETHODIMP nsSHEntry::GetID(PRUint32 
 }
 
 NS_IMETHODIMP nsSHEntry::SetID(PRUint32  aID)
 {
   mID = aID;
   return NS_OK;
 }
 
-nsSHEntryShared* nsSHEntry::GetSharedState()
+NS_IMETHODIMP nsSHEntry::GetDocIdentifier(PRUint64 * aResult)
+{
+  *aResult = mDocIdentifier;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsSHEntry::SetDocIdentifier(PRUint64 aDocIdentifier)
 {
-  return mShared;
+  // 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)
 {
-  *aFlag = mShared->mIsFrameNavigation;
+  *aFlag = mIsFrameNavigation;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsSHEntry::SetIsSubFrame(bool    aFlag)
 {
-  mShared->mIsFrameNavigation = aFlag;
+  mIsFrameNavigation = aFlag;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsSHEntry::GetCacheKey(nsISupports** aResult)
 {
-  *aResult = mShared->mCacheKey;
+  *aResult = mCacheKey;
   NS_IF_ADDREF(*aResult);
   return NS_OK;
 }
 
 NS_IMETHODIMP nsSHEntry::SetCacheKey(nsISupports* aCacheKey)
 {
-  mShared->mCacheKey = aCacheKey;
+  mCacheKey = aCacheKey;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsSHEntry::GetSaveLayoutStateFlag(bool * aFlag)
 {
-  *aFlag = mShared->mSaveLayoutState;
+  *aFlag = mSaveLayoutState;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsSHEntry::SetSaveLayoutStateFlag(bool    aFlag)
 {
-  mShared->mSaveLayoutState = aFlag;
-  if (mShared->mLayoutHistoryState) {
-    mShared->mLayoutHistoryState->SetScrollPositionOnly(!aFlag);
-  }
+  mSaveLayoutState = aFlag;
+  if (mLayoutHistoryState)
+    mLayoutHistoryState->SetScrollPositionOnly(!aFlag);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP nsSHEntry::GetExpirationStatus(bool * aFlag)
 {
-  *aFlag = mShared->mExpired;
+  *aFlag = mExpired;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsSHEntry::SetExpirationStatus(bool    aFlag)
 {
-  mShared->mExpired = aFlag;
+  mExpired = aFlag;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsSHEntry::GetContentType(nsACString& aContentType)
 {
-  aContentType = mShared->mContentType;
+  aContentType = mContentType;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsSHEntry::SetContentType(const nsACString& aContentType)
 {
-  mShared->mContentType = aContentType;
+  mContentType = aContentType;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::Create(nsIURI * aURI, const nsAString &aTitle,
                   nsIInputStream * aInputStream,
                   nsILayoutHistoryState * aLayoutHistoryState,
                   nsISupports * aCacheKey, const nsACString& aContentType,
                   nsISupports* aOwner,
                   PRUint64 aDocShellID, bool aDynamicCreation)
 {
   mURI = aURI;
   mTitle = aTitle;
   mPostData = aInputStream;
+  mCacheKey = aCacheKey;
+  mContentType = aContentType;
+  mOwner = aOwner;
+  mDocShellID = aDocShellID;
+  mDynamicallyCreated = aDynamicCreation;
 
   // Set the LoadType by default to loadHistory during creation
   mLoadType = (PRUint32) nsIDocShellLoadInfo::loadHistory;
 
-  mShared->mCacheKey = aCacheKey;
-  mShared->mContentType = aContentType;
-  mShared->mOwner = aOwner;
-  mShared->mDocShellID = aDocShellID;
-  mShared->mDynamicallyCreated = aDynamicCreation;
-
   // By default all entries are set false for subframe flag. 
   // nsDocShell::CloneAndReplace() which creates entries for
   // all subframe navigations, sets the flag to true.
-  mShared->mIsFrameNavigation = PR_FALSE;
+  mIsFrameNavigation = PR_FALSE;
 
   // By default we save LayoutHistoryState
-  mShared->mSaveLayoutState = PR_TRUE;
-  mShared->mLayoutHistoryState = aLayoutHistoryState;
+  mSaveLayoutState = PR_TRUE;
+  mLayoutHistoryState = aLayoutHistoryState;
 
   //By default the page is not expired
-  mShared->mExpired = PR_FALSE;
+  mExpired = PR_FALSE;
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::Clone(nsISHEntry ** aResult)
 {
   *aResult = new nsSHEntry(*this);
+  if (!*aResult)
+    return NS_ERROR_OUT_OF_MEMORY;
   NS_ADDREF(*aResult);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetParent(nsISHEntry ** aResult)
 {
   NS_ENSURE_ARG_POINTER(aResult);
-  *aResult = mShared->mParent;
+  *aResult = mParent;
   NS_IF_ADDREF(*aResult);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetParent(nsISHEntry * aParent)
 {
   /* parent not Addrefed on purpose to avoid cyclic reference
    * Null parent is OK
    *
    * XXX this method should not be scriptable if this is the case!!
    */
-  mShared->mParent = aParent;
+  mParent = aParent;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetWindowState(nsISupports *aState)
 {
-  mShared->mWindowState = aState;
+  mWindowState = aState;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetWindowState(nsISupports **aState)
 {
-  NS_IF_ADDREF(*aState = mShared->mWindowState);
+  NS_IF_ADDREF(*aState = mWindowState);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetViewerBounds(const nsIntRect &aBounds)
 {
-  mShared->mViewerBounds = aBounds;
+  mViewerBounds = aBounds;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetViewerBounds(nsIntRect &aBounds)
 {
-  aBounds = mShared->mViewerBounds;
+  aBounds = mViewerBounds;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetOwner(nsISupports **aOwner)
 {
-  NS_IF_ADDREF(*aOwner = mShared->mOwner);
+  NS_IF_ADDREF(*aOwner = mOwner);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetOwner(nsISupports *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);
+  mOwner = aOwner;
   return NS_OK;
 }
 
 //*****************************************************************************
 //    nsSHEntry: nsISHContainer
 //*****************************************************************************
 
 NS_IMETHODIMP 
@@ -671,87 +748,266 @@ nsSHEntry::GetChildAt(PRInt32 aIndex, ns
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::AddChildShell(nsIDocShellTreeItem *aShell)
 {
   NS_ASSERTION(aShell, "Null child shell added to history entry");
-  mShared->mChildShells.AppendObject(aShell);
+  mChildShells.AppendObject(aShell);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::ChildShellAt(PRInt32 aIndex, nsIDocShellTreeItem **aShell)
 {
-  NS_IF_ADDREF(*aShell = mShared->mChildShells.SafeObjectAt(aIndex));
+  NS_IF_ADDREF(*aShell = mChildShells.SafeObjectAt(aIndex));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::ClearChildShells()
 {
-  mShared->mChildShells.Clear();
+  mChildShells.Clear();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetRefreshURIList(nsISupportsArray **aList)
 {
-  NS_IF_ADDREF(*aList = mShared->mRefreshURIList);
+  NS_IF_ADDREF(*aList = mRefreshURIList);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetRefreshURIList(nsISupportsArray *aList)
 {
-  mShared->mRefreshURIList = aList;
+  mRefreshURIList = aList;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SyncPresentationState()
 {
-  return mShared->SyncPresentationState();
+  if (mContentViewer && mWindowState) {
+    // If we have a content viewer and a window state, we should be ok.
+    return NS_OK;
+  }
+
+  DropPresentationState();
+
+  return NS_OK;
+}
+
+void
+nsSHEntry::DropPresentationState()
+{
+  nsRefPtr<nsSHEntry> kungFuDeathGrip = this;
+
+  if (mDocument) {
+    mDocument->SetBFCacheEntry(nsnull);
+    mDocument->RemoveMutationObserver(this);
+    mDocument = nsnull;
+  }
+  if (mContentViewer)
+    mContentViewer->ClearHistoryEntry();
+
+  StopTrackingEntry(this);
+  mContentViewer = nsnull;
+  mSticky = PR_TRUE;
+  mWindowState = nsnull;
+  mViewerBounds.SetRect(0, 0, 0, 0);
+  mChildShells.Clear();
+  mRefreshURIList = nsnull;
+  mEditorData = nsnull;
+}
+
+void
+nsSHEntry::Expire()
+{
+  // This entry has timed out. If we still have a content viewer, we need to
+  // get it evicted.
+  if (!mContentViewer)
+    return;
+  nsCOMPtr<nsISupports> container;
+  mContentViewer->GetContainer(getter_AddRefs(container));
+  nsCOMPtr<nsIDocShellTreeItem> treeItem = do_QueryInterface(container);
+  if (!treeItem)
+    return;
+  // We need to find the root DocShell since only that object has an
+  // SHistory and we need the SHistory to evict content viewers
+  nsCOMPtr<nsIDocShellTreeItem> root;
+  treeItem->GetSameTypeRootTreeItem(getter_AddRefs(root));
+  nsCOMPtr<nsIWebNavigation> webNav = do_QueryInterface(root);
+  nsCOMPtr<nsISHistory> history;
+  webNav->GetSessionHistory(getter_AddRefs(history));
+  nsCOMPtr<nsISHistoryInternal> historyInt = do_QueryInterface(history);
+  if (!historyInt)
+    return;
+  historyInt->EvictExpiredContentViewerForEntry(this);
+}
+
+//*****************************************************************************
+//    nsSHEntry: nsIMutationObserver
+//*****************************************************************************
+
+void
+nsSHEntry::NodeWillBeDestroyed(const nsINode* aNode)
+{
+  NS_NOTREACHED("Document destroyed while we're holding a strong ref to it");
+}
+
+void
+nsSHEntry::CharacterDataWillChange(nsIDocument* aDocument,
+                                   nsIContent* aContent,
+                                   CharacterDataChangeInfo* aInfo)
+{
 }
 
 void
+nsSHEntry::CharacterDataChanged(nsIDocument* aDocument,
+                                nsIContent* aContent,
+                                CharacterDataChangeInfo* aInfo)
+{
+  RemoveFromBFCacheAsync();
+}
+
+void
+nsSHEntry::AttributeWillChange(nsIDocument* aDocument,
+                               dom::Element* aContent,
+                               PRInt32 aNameSpaceID,
+                               nsIAtom* aAttribute,
+                               PRInt32 aModType)
+{
+}
+
+void
+nsSHEntry::AttributeChanged(nsIDocument* aDocument,
+                            dom::Element* aElement,
+                            PRInt32 aNameSpaceID,
+                            nsIAtom* aAttribute,
+                            PRInt32 aModType)
+{
+  RemoveFromBFCacheAsync();
+}
+
+void
+nsSHEntry::ContentAppended(nsIDocument* aDocument,
+                           nsIContent* aContainer,
+                           nsIContent* aFirstNewContent,
+                           PRInt32 /* unused */)
+{
+  RemoveFromBFCacheAsync();
+}
+
+void
+nsSHEntry::ContentInserted(nsIDocument* aDocument,
+                           nsIContent* aContainer,
+                           nsIContent* aChild,
+                           PRInt32 /* unused */)
+{
+  RemoveFromBFCacheAsync();
+}
+
+void
+nsSHEntry::ContentRemoved(nsIDocument* aDocument,
+                          nsIContent* aContainer,
+                          nsIContent* aChild,
+                          PRInt32 aIndexInContainer,
+                          nsIContent* aPreviousSibling)
+{
+  RemoveFromBFCacheAsync();
+}
+
+void
+nsSHEntry::ParentChainChanged(nsIContent *aContent)
+{
+}
+
+class DestroyViewerEvent : public nsRunnable
+{
+public:
+  DestroyViewerEvent(nsIContentViewer* aViewer, nsIDocument* aDocument)
+    : mViewer(aViewer),
+      mDocument(aDocument)
+  {}
+
+  NS_IMETHOD Run()
+  {
+    if (mViewer)
+      mViewer->Destroy();
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIContentViewer> mViewer;
+  nsCOMPtr<nsIDocument> mDocument;
+};
+
+void
 nsSHEntry::RemoveFromBFCacheSync()
 {
-  mShared->RemoveFromBFCacheSync();
+  NS_ASSERTION(mContentViewer && mDocument,
+               "we're not in the bfcache!");
+
+  nsCOMPtr<nsIContentViewer> viewer = mContentViewer;
+  DropPresentationState();
+
+  // Warning! The call to DropPresentationState could have dropped the last
+  // reference to this nsSHEntry, so no accessing members beyond here.
+
+  if (viewer) {
+    viewer->Destroy();
+  }
 }
 
 void
 nsSHEntry::RemoveFromBFCacheAsync()
 {
-  mShared->RemoveFromBFCacheAsync();
+  NS_ASSERTION(mContentViewer && mDocument,
+               "we're not in the bfcache!");
+
+  // Release the reference to the contentviewer asynchronously so that the
+  // document doesn't get nuked mid-mutation.
+
+  nsCOMPtr<nsIRunnable> evt =
+      new DestroyViewerEvent(mContentViewer, mDocument);
+  nsresult rv = NS_DispatchToCurrentThread(evt);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("failed to dispatch DestroyViewerEvent");
+  }
+  else {
+    // Drop presentation. Also ensures that we don't post more then one
+    // PLEvent. Only do this if we succeeded in posting the event since
+    // otherwise the document could be torn down mid mutation causing crashes.
+    DropPresentationState();
+  }
+  // Warning! The call to DropPresentationState could have dropped the last
+  // reference to this nsSHEntry, so no accessing members beyond here.
 }
 
 nsDocShellEditorData*
 nsSHEntry::ForgetEditorData()
 {
-  // XXX jlebar Check how this is used.
-  return mShared->mEditorData.forget();
+  return mEditorData.forget();
 }
 
 void
 nsSHEntry::SetEditorData(nsDocShellEditorData* aData)
 {
-  NS_ASSERTION(!(aData && mShared->mEditorData),
+  NS_ASSERTION(!(aData && mEditorData),
                "We're going to overwrite an owning ref!");
-  if (mShared->mEditorData != aData) {
-    mShared->mEditorData = aData;
-  }
+  if (mEditorData != aData)
+    mEditorData = aData;
 }
 
 bool
 nsSHEntry::HasDetachedEditor()
 {
-  return mShared->mEditorData != nsnull;
+  return mEditorData != nsnull;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetStateData(nsIStructuredCloneContainer **aContainer)
 {
   NS_ENSURE_ARG_POINTER(aContainer);
   NS_IF_ADDREF(*aContainer = mStateData);
   return NS_OK;
@@ -762,17 +1018,17 @@ nsSHEntry::SetStateData(nsIStructuredClo
 {
   mStateData = aContainer;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::IsDynamicallyAdded(bool* aAdded)
 {
-  *aAdded = mShared->mDynamicallyCreated;
+  *aAdded = mDynamicallyCreated;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::HasDynamicallyAddedChild(bool* aAdded)
 {
   *aAdded = PR_FALSE;
   for (PRInt32 i = 0; i < mChildren.Count(); ++i) {
@@ -785,33 +1041,34 @@ nsSHEntry::HasDynamicallyAddedChild(bool
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetDocshellID(PRUint64* aID)
 {
-  *aID = mShared->mDocShellID;
+  *aID = mDocShellID;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetDocshellID(PRUint64 aID)
 {
-  mShared->mDocShellID = aID;
+  mDocShellID = aID;
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsSHEntry::GetLastTouched(PRUint32 *aLastTouched)
 {
-  *aLastTouched = mShared->mLastTouched;
+  *aLastTouched = mLastTouched;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetLastTouched(PRUint32 aLastTouched)
 {
-  mShared->mLastTouched = aLastTouched;
+  mLastTouched = aLastTouched;
   return NS_OK;
 }
+
--- a/docshell/shistory/src/nsSHEntry.h
+++ b/docshell/shistory/src/nsSHEntry.h
@@ -37,62 +37,90 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef nsSHEntry_h
 #define nsSHEntry_h
 
 // Helper Classes
 #include "nsCOMPtr.h"
-#include "nsAutoPtr.h"
 #include "nsCOMArray.h"
 #include "nsString.h"
+#include "nsAutoPtr.h"
 
 // Interfaces needed
+#include "nsIContentViewer.h"
 #include "nsIInputStream.h"
+#include "nsILayoutHistoryState.h"
 #include "nsISHEntry.h"
 #include "nsISHContainer.h"
 #include "nsIURI.h"
+#include "nsIEnumerator.h"
 #include "nsIHistoryEntry.h"
-
-class nsSHEntryShared;
+#include "nsRect.h"
+#include "nsISupportsArray.h"
+#include "nsIMutationObserver.h"
+#include "nsExpirationTracker.h"
+#include "nsDocShellEditorData.h"
 
 class nsSHEntry : public nsISHEntry,
                   public nsISHContainer,
+                  public nsIMutationObserver,
                   public nsISHEntryInternal
 {
 public: 
   nsSHEntry();
   nsSHEntry(const nsSHEntry &other);
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIHISTORYENTRY
   NS_DECL_NSISHENTRY
   NS_DECL_NSISHENTRYINTERNAL
   NS_DECL_NSISHCONTAINER
+  NS_DECL_NSIMUTATIONOBSERVER
 
   void DropPresentationState();
 
+  void Expire();
+  
+  nsExpirationState *GetExpirationState() { return &mExpirationState; }
+  
   static nsresult Startup();
   static void Shutdown();
   
 private:
   ~nsSHEntry();
 
-  // We share the state in here with other SHEntries which correspond to the
-  // same document.
-  nsRefPtr<nsSHEntryShared> mShared;
-
-  // See nsSHEntry.idl for comments on these members.
-  nsCOMPtr<nsIURI>         mURI;
-  nsCOMPtr<nsIURI>         mReferrerURI;
-  nsString                 mTitle;
-  nsCOMPtr<nsIInputStream> mPostData;
-  PRUint32                 mLoadType;
-  PRUint32                 mID;
-  PRInt32                  mScrollPositionX;
-  PRInt32                  mScrollPositionY;
-  nsCOMArray<nsISHEntry>   mChildren;
-  bool                     mURIWasModified;
+  nsCOMPtr<nsIURI>                mURI;
+  nsCOMPtr<nsIURI>                mReferrerURI;
+  nsCOMPtr<nsIContentViewer>      mContentViewer;
+  nsCOMPtr<nsIDocument>           mDocument; // document currently being observed
+  nsString                        mTitle;
+  nsCOMPtr<nsIInputStream>        mPostData;
+  nsCOMPtr<nsILayoutHistoryState> mLayoutHistoryState;
+  nsCOMArray<nsISHEntry>          mChildren;
+  PRUint32                        mLoadType;
+  PRUint32                        mID;
+  PRInt64                         mDocIdentifier;
+  PRInt32                         mScrollPositionX;
+  PRInt32                         mScrollPositionY;
+  PRPackedBool                    mURIWasModified;
+  PRPackedBool                    mIsFrameNavigation;
+  PRPackedBool                    mSaveLayoutState;
+  PRPackedBool                    mExpired;
+  PRPackedBool                    mSticky;
+  PRPackedBool                    mDynamicallyCreated;
+  nsCString                       mContentType;
+  nsCOMPtr<nsISupports>           mCacheKey;
+  nsISHEntry *                    mParent;  // weak reference
+  nsCOMPtr<nsISupports>           mWindowState;
+  nsIntRect                       mViewerBounds;
+  nsCOMArray<nsIDocShellTreeItem> mChildShells;
+  nsCOMPtr<nsISupportsArray>      mRefreshURIList;
+  nsCOMPtr<nsISupports>           mOwner;
+  nsExpirationState               mExpirationState;
+  nsAutoPtr<nsDocShellEditorData> mEditorData;
+  PRUint64                        mDocShellID;
+  PRUint32                        mLastTouched;
   nsCOMPtr<nsIStructuredCloneContainer> mStateData;
 };
 
 #endif /* nsSHEntry_h */
deleted file mode 100644
--- a/docshell/shistory/src/nsSHEntryShared.cpp
+++ /dev/null
@@ -1,400 +0,0 @@
-/* ***** 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)
-{
-}
deleted file mode 100644
--- a/docshell/shistory/src/nsSHEntryShared.h
+++ /dev/null
@@ -1,124 +0,0 @@
-/* ***** 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
--- a/docshell/shistory/src/nsSHistory.cpp
+++ b/docshell/shistory/src/nsSHistory.cpp
@@ -67,72 +67,44 @@
 // For calculating max history entries and max cachable contentviewers
 #include "nspr.h"
 #include <math.h>  // for log()
 
 using namespace mozilla;
 
 #define PREF_SHISTORY_SIZE "browser.sessionhistory.max_entries"
 #define PREF_SHISTORY_MAX_TOTAL_VIEWERS "browser.sessionhistory.max_total_viewers"
+#define PREF_SHISTORY_OPTIMIZE_EVICTION "browser.sessionhistory.optimize_eviction"
 
 static const char* kObservedPrefs[] = {
   PREF_SHISTORY_SIZE,
   PREF_SHISTORY_MAX_TOTAL_VIEWERS,
+  PREF_SHISTORY_OPTIMIZE_EVICTION,
   nsnull
 };
 
 static PRInt32  gHistoryMaxSize = 50;
 // Max viewers allowed per SHistory objects
 static const PRInt32  gHistoryMaxViewers = 3;
 // List of all SHistory objects, used for content viewer cache eviction
 static PRCList gSHistoryList;
 // Max viewers allowed total, across all SHistory objects - negative default
 // means we will calculate how many viewers to cache based on total memory
 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
 // entries were touched, so that we can evict older entries first.
 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{
   HIST_CMD_BACK,
   HIST_CMD_FORWARD,
   HIST_CMD_GOTOINDEX,
   HIST_CMD_RELOAD
 } ;
 
 //*****************************************************************************
@@ -157,70 +129,25 @@ static nsSHistoryObserver* gObserver = n
 NS_IMPL_ISUPPORTS1(nsSHistoryObserver, nsIObserver)
 
 NS_IMETHODIMP
 nsSHistoryObserver::Observe(nsISupports *aSubject, const char *aTopic,
                             const PRUnichar *aData)
 {
   if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
     nsSHistory::UpdatePrefs();
-    nsSHistory::GloballyEvictContentViewers();
+    nsSHistory::EvictGlobalContentViewer();
   } else if (!strcmp(aTopic, NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID) ||
              !strcmp(aTopic, "memory-pressure")) {
-    nsSHistory::GloballyEvictAllContentViewers();
+    nsSHistory::EvictAllContentViewersGlobally();
   }
 
   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::nsSHistory() : mListRoot(nsnull), mIndex(-1), mLength(0), mRequestedIndex(-1)
 {
   // Add this new SHistory object to the list
   PR_APPEND_LINK(this, &gSHistoryList);
@@ -308,16 +235,17 @@ nsSHistory::CalcMaxTotalViewers()
 
 // static
 void
 nsSHistory::UpdatePrefs()
 {
   Preferences::GetInt(PREF_SHISTORY_SIZE, &gHistoryMaxSize);
   Preferences::GetInt(PREF_SHISTORY_MAX_TOTAL_VIEWERS,
                       &sHistoryMaxTotalViewers);
+  Preferences::GetBool(PREF_SHISTORY_OPTIMIZE_EVICTION, &gOptimizeEviction);
   // If the pref is negative, that means we calculate how many viewers
   // we think we should cache, based on total memory
   if (sHistoryMaxTotalViewers < 0) {
     sHistoryMaxTotalViewers = CalcMaxTotalViewers();
   }
 }
 
 // static
@@ -756,38 +684,31 @@ nsSHistory::GetListener(nsISHistoryListe
   NS_ENSURE_ARG_POINTER(aListener);
   if (mListener) 
     CallQueryReferent(mListener.get(),  aListener);
   // Don't addref aListener. It is a weak pointer.
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsSHistory::EvictOutOfRangeContentViewers(PRInt32 aIndex)
+nsSHistory::EvictContentViewers(PRInt32 aPreviousIndex, PRInt32 aIndex)
 {
   // Check our per SHistory object limit in the currently navigated SHistory
-  EvictOutOfRangeWindowContentViewers(aIndex);
+  EvictWindowContentViewers(aPreviousIndex, aIndex);
   // Check our total limit across all SHistory objects
-  GloballyEvictContentViewers();
+  EvictGlobalContentViewer();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHistory::EvictAllContentViewers()
 {
   // 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.
-  nsCOMPtr<nsISHTransaction> trans = mListRoot;
-  while (trans) {
-    EvictContentViewerForTransaction(trans);
-
-    nsISHTransaction *temp = trans;
-    temp->GetNext(getter_AddRefs(trans));
-  }
-
+  EvictContentViewersInRange(0, mLength);
   return NS_OK;
 }
 
 
 
 //*****************************************************************************
 //    nsSHistory: nsIWebNavigation
 //*****************************************************************************
@@ -909,287 +830,302 @@ nsSHistory::ReloadCurrentEntry()
   }
   if (!canNavigate)
     return NS_OK;
 
   return LoadEntry(mIndex, nsIDocShellLoadInfo::loadHistory, HIST_CMD_RELOAD);
 }
 
 void
-nsSHistory::EvictOutOfRangeWindowContentViewers(PRInt32 aIndex)
+nsSHistory::EvictWindowContentViewers(PRInt32 aFromIndex, PRInt32 aToIndex)
 {
-  // XXX rename method to EvictContentViewersExceptAroundIndex, or something.
-
-  // We need to release all content viewers that are no longer in the range
-  //
-  //  aIndex - gHistoryMaxViewers to aIndex + gHistoryMaxViewers
-  //
-  // 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:
+  // To enforce the per SHistory object limit on cached content viewers, we
+  // need to release all of the content viewers that are no longer in the
+  // "window" that now ends/begins at aToIndex.  Existing content viewers
+  // should be in the window from
+  // aFromIndex - gHistoryMaxViewers to aFromIndex + gHistoryMaxViewers
   //
-  //   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.)
+  // We make the assumption that entries outside this range have no viewers so
+  // that we don't have to walk the whole entire session history checking for
+  // content viewers.
 
-  if (aIndex < 0) {
+  // This can happen on the first load of a page in a particular window
+  if (aFromIndex < 0 || aToIndex < 0) {
     return;
   }
-  NS_ASSERTION(aIndex < mLength, "aIndex is out of range");
-  if (aIndex >= mLength) {
+  NS_ASSERTION(aFromIndex < mLength, "aFromIndex is out of range");
+  NS_ASSERTION(aToIndex < mLength, "aToIndex is out of range");
+  if (aFromIndex >= mLength || aToIndex >= mLength) {
     return;
   }
 
-  // Calculate the range that's safe from eviction.
-  PRInt32 startSafeIndex = PR_MAX(0, aIndex - gHistoryMaxViewers);
-  PRInt32 endSafeIndex = PR_MIN(mLength, aIndex + gHistoryMaxViewers);
-
-  LOG(("EvictOutOfRangeWindowContentViewers(index=%d), "
-       "mLength=%d. Safe range [%d, %d]",
-       aIndex, mLength, startSafeIndex, endSafeIndex)); 
+  // These indices give the range of SHEntries whose content viewers will be
+  // evicted
+  PRInt32 startIndex, endIndex;
+  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);
+  }
 
-  // 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;
+#ifdef DEBUG
   nsCOMPtr<nsISHTransaction> trans;
-  GetTransactionAtIndex(startSafeIndex, getter_AddRefs(trans));
-  for (PRUint32 i = startSafeIndex; trans && i <= endSafeIndex; i++) {
-    nsCOMPtr<nsIContentViewer> viewer = GetContentViewerForTransaction(trans);
-    safeViewers.AppendObject(viewer);
+  GetTransactionAtIndex(0, getter_AddRefs(trans));
+
+  // Walk the full session history and check that entries outside the window
+  // around aFromIndex have no content viewers
+  for (PRInt32 i = 0; trans && i < mLength; ++i) {
+    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;
     temp->GetNext(getter_AddRefs(trans));
   }
+#endif
 
-  // Walk the SHistory list and evict any content viewers that aren't safe.
-  GetTransactionAtIndex(0, getter_AddRefs(trans));
-  while (trans) {
-    nsCOMPtr<nsIContentViewer> viewer = GetContentViewerForTransaction(trans);
-    if (safeViewers.IndexOf(viewer) == -1) {
-      EvictContentViewerForTransaction(trans);
+  EvictContentViewersInRange(startIndex, endIndex);
+}
+
+void
+nsSHistory::EvictContentViewersInRange(PRInt32 aStart, PRInt32 aEnd)
+{
+  nsCOMPtr<nsISHTransaction> trans;
+  GetTransactionAtIndex(aStart, getter_AddRefs(trans));
+
+  for (PRInt32 i = aStart; trans && i < aEnd; ++i) {
+    nsCOMPtr<nsISHEntry> entry;
+    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());
+#endif
+
+      // 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();
     }
 
     nsISHTransaction *temp = trans;
     temp->GetNext(getter_AddRefs(trans));
   }
 }
 
-namespace {
-
-class TransactionAndDistance
+// static
+void
+nsSHistory::EvictGlobalContentViewer()
 {
-public:
-  TransactionAndDistance(nsISHTransaction *aTrans, PRUint32 aDist)
-    : mTransaction(aTrans)
-    , mDistance(aDist)
-  {
-    mViewer = GetContentViewerForTransaction(aTrans);
-    NS_ASSERTION(mViewer, "Transaction should have a content viewer");
-
-    nsCOMPtr<nsISHEntry> shentry;
-    mTransaction->GetSHEntry(getter_AddRefs(shentry));
-
-    nsCOMPtr<nsISHEntryInternal> shentryInternal = do_QueryInterface(shentry);
-    if (shentryInternal) {
-      shentryInternal->GetLastTouched(&mLastTouched);
-    } else {
-      NS_WARNING("Can't cast to nsISHEntryInternal?");
-      mLastTouched = 0;
-    }
-  }
-
-  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
+  // true until the total number of content viewers is <= total max
+  // The usual case is that we only need to evict one content viewer.
+  // However, if somebody resets the pref value, we might occasionally
+  // need to evict more than one.
+  PRBool shouldTryEviction = PR_TRUE;
+  while (shouldTryEviction) {
+    // Walk through our list of SHistory objects, looking for content
+    // 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) {
+      // Calculate the window of SHEntries that could possibly have a content
+      // viewer.  There could be up to gHistoryMaxViewers content viewers,
+      // but we don't know whether they are before or after the mIndex position
+      // in the SHEntry list.  Just check both sides, to be safe.
+      PRInt32 startIndex = NS_MAX(0, shist->mIndex - gHistoryMaxViewers);
+      PRInt32 endIndex = NS_MIN(shist->mLength - 1,
+                                shist->mIndex + gHistoryMaxViewers);
+      nsCOMPtr<nsISHTransaction> trans;
+      shist->GetTransactionAtIndex(startIndex, getter_AddRefs(trans));
 
-//static
-void
-nsSHistory::GloballyEvictContentViewers()
-{
-  // First, collect from each SHistory object the transactions which have a
-  // cached content viewer.  Associate with each transaction its distance from
-  // its SHistory's current index.
-
-  nsTArray<TransactionAndDistance> transactions;
-
-  nsSHistory *shist = static_cast<nsSHistory*>(PR_LIST_HEAD(&gSHistoryList));
-  while (shist != &gSHistoryList) {
-
-    // Maintain a list of the transactions which have viewers and belong to
-    // this particular shist object.  We'll add this list to the global list,
-    // |transactions|, eventually.
-    nsTArray<TransactionAndDistance> shTransactions;
+      for (PRInt32 i = startIndex; trans && i <= endIndex; ++i) {
+        nsCOMPtr<nsISHEntry> entry;
+        trans->GetSHEntry(getter_AddRefs(entry));
+        nsCOMPtr<nsIContentViewer> viewer;
+        nsCOMPtr<nsISHEntry> ownerEntry;
+        entry->GetAnyContentViewer(getter_AddRefs(ownerEntry),
+                                   getter_AddRefs(viewer));
 
-    // 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 endIndex = NS_MIN(shist->mLength - 1,
-                              shist->mIndex + gHistoryMaxViewers);
-    nsCOMPtr<nsISHTransaction> trans;
-    shist->GetTransactionAtIndex(startIndex, getter_AddRefs(trans));
-    for (PRInt32 i = startIndex; trans && i <= endIndex; i++) {
-      nsCOMPtr<nsIContentViewer> contentViewer =
-        GetContentViewerForTransaction(trans);
-
-      if (contentViewer) {
-        // Because one content viewer might belong to multiple SHEntries, we
-        // have to search through shTransactions to see if we already know
-        // about this content viewer.  If we find the viewer, update its
-        // distance from the SHistory's index and continue.
-        bool found = false;
-        for (PRUint32 j = 0; j < shTransactions.Length(); j++) {
-          TransactionAndDistance &container = shTransactions[j];
-          if (container.mViewer == contentViewer) {
-            container.mDistance = PR_MIN(container.mDistance,
-                                         PR_ABS(i - shist->mIndex));
-            found = PR_TRUE;
-            break;
+        PRUint32 entryLastTouched = 0;
+        if (gOptimizeEviction) {
+          nsCOMPtr<nsISHEntryInternal> entryInternal = do_QueryInterface(entry);
+          if (entryInternal) {
+            // Find when this entry was last activated
+            entryInternal->GetLastTouched(&entryLastTouched);
           }
         }
 
-        // If we didn't find a TransactionAndDistance for this content viewer, make a new
-        // one.
-        if (!found) {
-          TransactionAndDistance container(trans, PR_ABS(i - shist->mIndex));
-          shTransactions.AppendElement(container);
+#ifdef DEBUG_PAGE_CACHE
+        nsCOMPtr<nsIURI> uri;
+        if (ownerEntry) {
+          ownerEntry->GetURI(getter_AddRefs(uri));
+        } else {
+          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++;
 
-      nsISHTransaction *temp = trans;
-      temp->GetNext(getter_AddRefs(trans));
+          // 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;
+        temp->GetNext(getter_AddRefs(trans));
+      }
+      shist = static_cast<nsSHistory*>(PR_NEXT_LINK(shist));
     }
 
-    // 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));
-  }
+#ifdef DEBUG_PAGE_CACHE
+    printf("Distance from focus: %d\n", distanceFromFocus);
+    printf("Total max viewers: %d\n", sHistoryMaxTotalViewers);
+    printf("Total number of viewers: %d\n", totalContentViewers);
+#endif
 
-  // We now have collected all cached content viewers.  First check that we
-  // have enough that we actually need to evict some.
-  if ((PRInt32)transactions.Length() <= sHistoryMaxTotalViewers) {
-    return;
-  }
+    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
 
-  // If we need to evict, sort our list of transactions and evict the largest
-  // 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();
+      // 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();
 
-  for (PRInt32 i = transactions.Length() - 1;
-       i >= sHistoryMaxTotalViewers; --i) {
-
-    EvictContentViewerForTransaction(transactions[i].mTransaction);
-
-  }
+      // 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
 }
 
-nsresult
-nsSHistory::EvictExpiredContentViewerForEntry(nsIBFCacheEntry *aEntry)
+NS_IMETHODIMP
+nsSHistory::EvictExpiredContentViewerForEntry(nsISHEntry *aEntry)
 {
   PRInt32 startIndex = NS_MAX(0, mIndex - gHistoryMaxViewers);
   PRInt32 endIndex = NS_MIN(mLength - 1,
                             mIndex + gHistoryMaxViewers);
   nsCOMPtr<nsISHTransaction> trans;
   GetTransactionAtIndex(startIndex, getter_AddRefs(trans));
 
   PRInt32 i;
   for (i = startIndex; trans && i <= endIndex; ++i) {
     nsCOMPtr<nsISHEntry> entry;
     trans->GetSHEntry(getter_AddRefs(entry));
-
-    // Does entry have the same BFCacheEntry as the argument to this method?
-    if (entry->HasBFCacheEntry(aEntry)) {
+    if (entry == aEntry)
       break;
-    }
 
     nsISHTransaction *temp = trans;
     temp->GetNext(getter_AddRefs(trans));
   }
   if (i > endIndex)
     return NS_OK;
   
-  if (i == mIndex) {
-    NS_WARNING("How did the current SHEntry expire?");
+  NS_ASSERTION(i != mIndex, "How did the current session entry expire?");
+  if (i == mIndex)
     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;
 }
 
 // Evicts all content viewers in all history objects.  This is very
 // inefficient, because it requires a linear search through all SHistory
 // objects for each viewer to be evicted.  However, this method is called
 // infrequently -- only when the disk or memory cache is cleared.
 
 //static
 void
-nsSHistory::GloballyEvictAllContentViewers()
+nsSHistory::EvictAllContentViewersGlobally()
 {
   PRInt32 maxViewers = sHistoryMaxTotalViewers;
   sHistoryMaxTotalViewers = 0;
-  GloballyEvictContentViewers();
+  EvictGlobalContentViewer();
   sHistoryMaxTotalViewers = maxViewers;
 }
 
 void GetDynamicChildren(nsISHContainer* aContainer,
                         nsTArray<PRUint64>& aDocshellIDs,
                         bool aOnlyTopLevelDynamic)
 {
   PRInt32 count = 0;
--- a/docshell/shistory/src/nsSHistory.h
+++ b/docshell/shistory/src/nsSHistory.h
@@ -96,21 +96,22 @@ protected:
    nsresult InitiateLoad(nsISHEntry * aFrameEntry, nsIDocShell * aFrameDS, long aLoadType);
 
    NS_IMETHOD LoadEntry(PRInt32 aIndex, long aLoadType, PRUint32 histCmd);
 
 #ifdef DEBUG
    nsresult PrintHistory();
 #endif
 
-  // Evict content viewers in this window which don't lie in the "safe" range
-  // around aIndex.
-  void EvictOutOfRangeWindowContentViewers(PRInt32 aIndex);
-  static void GloballyEvictContentViewers();
-  static void GloballyEvictAllContentViewers();
+  // Evict the viewers at indices between aStartIndex and aEndIndex,
+  // including aStartIndex but not aEndIndex.
+  void EvictContentViewersInRange(PRInt32 aStartIndex, PRInt32 aEndIndex);
+  void EvictWindowContentViewers(PRInt32 aFromIndex, PRInt32 aToIndex);
+  static void EvictGlobalContentViewer();
+  static void EvictAllContentViewersGlobally();
 
   // Calculates a max number of total
   // content viewers to cache, based on amount of total memory
   static PRUint32 CalcMaxTotalViewers();
 
   void RemoveDynEntries(PRInt32 aOldIndex, PRInt32 aNewIndex);
 
   nsresult LoadNextPossibleEntry(PRInt32 aNewIndex, long aLoadType, PRUint32 aHistCmd);
--- a/docshell/test/Makefile.in
+++ b/docshell/test/Makefile.in
@@ -114,17 +114,16 @@ include $(topsrcdir)/config/rules.mk
 		test_bug570341.html \
 		bug570341_recordevents.html \
 		test_bug668513.html \
 		bug668513_redirect.html \
 		bug668513_redirect.html^headers^ \
 		test_bug669671.html \
 		file_bug669671.sjs \
 		test_bug675587.html \
-		test_bfcache_plus_hash.html \
 		test_bug680257.html \
 		file_bug680257.html \
 		$(NULL)
 
 ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
 _TEST_FILES += \
 		test_bug511449.html \
 		file_bug511449.html \
--- a/docshell/test/chrome/bug396519_window.xul
+++ b/docshell/test/chrome/bug396519_window.xul
@@ -45,19 +45,16 @@
         height="600"
         onload="onLoad();"
         title="396519 test">
 
   <script type="application/javascript"><![CDATA[
 
     const LISTEN_EVENTS = ["pageshow"];
 
-    const Cc = Components.classes;
-    const Ci = Components.interfaces;
-
     var gBrowser;
     var gTestCount = 0;
     var gTestsIterator;
     var gExpected = [];
 
     function ok(condition, message) {
       window.opener.wrappedJSObject.SimpleTest.ok(condition, message);
     }
@@ -94,33 +91,16 @@
     function doTest() {
       var history = gBrowser.webNavigation.sessionHistory;
       if (history.count == gExpected.length) {
         for (var i=0; i<history.count; i++) {
           var shEntry = history.getEntryAtIndex(i,false).
                           QueryInterface(Components.interfaces.nsISHEntry);
           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 {
         is(history.count, gExpected.length, "Wrong history length in test "+gTestCount);
       }
 
       setTimeout(nextTest, 0);
     }
 
deleted file mode 100644
--- a/docshell/test/test_bfcache_plus_hash.html
+++ /dev/null
@@ -1,120 +0,0 @@
-<!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>
--- a/dom/indexedDB/IndexedDatabaseManager.cpp
+++ b/dom/indexedDB/IndexedDatabaseManager.cpp
@@ -213,19 +213,22 @@ public:
       nsRefPtr<IDBDatabase>& database = mWaitingDatabases[index];
 
       if (database->IsClosed()) {
         continue;
       }
 
       // First check if the document the IDBDatabase is part of is bfcached
       nsCOMPtr<nsIDocument> ownerDoc = database->GetOwnerDocument();
-      nsIBFCacheEntry* bfCacheEntry;
-      if (ownerDoc && (bfCacheEntry = ownerDoc->GetBFCacheEntry())) {
-        bfCacheEntry->RemoveFromBFCacheSync();
+      nsISHEntry* shEntry;
+      if (ownerDoc && (shEntry = ownerDoc->GetBFCacheEntry())) {
+        nsCOMPtr<nsISHEntryInternal> sheInternal = do_QueryInterface(shEntry);
+        if (sheInternal) {
+          sheInternal->RemoveFromBFCacheSync();
+        }
         NS_ASSERTION(database->IsClosed(),
                      "Kicking doc out of bfcache should have closed database");
         continue;
       }
 
       // Otherwise fire a versionchange event.
       nsCOMPtr<nsIDOMEvent> event(IDBVersionChangeEvent::Create(mVersion));
       NS_ENSURE_TRUE(event, NS_ERROR_FAILURE);
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -1975,17 +1975,17 @@ DocumentViewerImpl::Show(void)
         PRInt32 prevIndex,loadedIndex;
         nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(treeItem);
         docShell->GetPreviousTransIndex(&prevIndex);
         docShell->GetLoadedTransIndex(&loadedIndex);
 #ifdef DEBUG_PAGE_CACHE
         printf("About to evict content viewers: prev=%d, loaded=%d\n",
                prevIndex, loadedIndex);
 #endif
-        historyInt->EvictOutOfRangeContentViewers(loadedIndex);
+        historyInt->EvictContentViewers(prevIndex, loadedIndex);
       }
     }
   }
 
   if (mWindow) {
     // When attached to a top level xul window, we do not need to call
     // Show on the widget. Underlying window management code handles
     // this when the window is initialized.
--- a/mobile/app/mobile.js
+++ b/mobile/app/mobile.js
@@ -135,16 +135,17 @@ pref("network.buffer.cache.size",  16384
 pref("browser.display.history.maxresults", 100);
 
 /* How many times should have passed before the remote tabs list is refreshed */
 pref("browser.display.remotetabs.timeout", 10);
 
 /* session history */
 pref("browser.sessionhistory.max_total_viewers", 1);
 pref("browser.sessionhistory.max_entries", 50);
+pref("browser.sessionhistory.optimize_eviction", true);
 
 /* session store */
 pref("browser.sessionstore.resume_session_once", false);
 pref("browser.sessionstore.resume_from_crash", true);
 pref("browser.sessionstore.resume_from_crash_timeout", 60); // minutes
 pref("browser.sessionstore.interval", 10000); // milliseconds
 pref("browser.sessionstore.max_tabs_undo", 1);
 
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -104,16 +104,18 @@ pref("dom.workers.maxPerDomain", 20);
 
 // Whether window.performance is enabled
 pref("dom.enable_performance", true);
 
 // Fastback caching - if this pref is negative, then we calculate the number
 // of content viewers to cache based on the amount of available memory.
 pref("browser.sessionhistory.max_total_viewers", -1);
 
+pref("browser.sessionhistory.optimize_eviction", true);
+
 pref("ui.use_native_colors", true);
 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_colors", true);
 pref("browser.display.use_system_colors",   false);
 pref("browser.display.foreground_color",    "#000000");
 pref("browser.display.background_color",    "#FFFFFF");
 pref("browser.display.force_inline_alttext", false); // true = force ALT text for missing images to be layed out inline