Bug 683777 - In an attempt to fix a crash in nsDocShell::InternalLoad, re-land bug 646641 with an extra null-check. r=smaug
authorJustin Lebar <justin.lebar@gmail.com>
Fri, 21 Oct 2011 11:26:34 -0400
changeset 79099 051b3d8ed54547bacc6036a1386754c4e385fb61
parent 79098 c36c8203eefeb47cb8446cc2345066354477ff90
child 79100 b22e5232952c8bac4d05864b9ae00012a524e19a
push id247
push usertim.taubert@gmx.de
push dateSat, 22 Oct 2011 19:08:15 +0000
treeherderfx-team@72bb20c484a2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs683777, 646641
milestone10.0a1
Bug 683777 - In an attempt to fix a crash in nsDocShell::InternalLoad, re-land bug 646641 with an extra null-check. r=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
mobile/chrome/content/bindings/browser.js
modules/libpref/src/init/all.js
--- a/browser/components/sessionstore/src/nsSessionStore.js
+++ b/browser/components/sessionstore/src/nsSessionStore.js
@@ -1915,19 +1915,17 @@ SessionStoreService.prototype = {
         // We can stop doing base64 encoding once our serialization into JSON
         // is guaranteed to handle all chars in strings, including embedded
         // nulls.
         entry.owner_b64 = btoa(String.fromCharCode.apply(null, ownerBytes));
       }
       catch (ex) { debug(ex); }
     }
 
-    if (aEntry.docIdentifier) {
-      entry.docIdentifier = aEntry.docIdentifier;
-    }
+    entry.docIdentifier = aEntry.BFCacheEntry.ID;
 
     if (aEntry.stateData != null) {
       entry.structuredCloneState = aEntry.stateData.getDataAsBase64();
       entry.structuredCloneVersion = aEntry.stateData.formatVersion;
     }
 
     if (!(aEntry instanceof Ci.nsISHContainer)) {
       return entry;
@@ -3019,26 +3017,21 @@ SessionStoreService.prototype = {
     if (activeIndex >= tabData.entries.length)
       activeIndex = tabData.entries.length - 1;
     // Reset currentURI.  This creates a new session history entry with a new
     // doc identifier, so we need to explicitly save and restore the old doc
     // identifier (corresponding to the SHEntry at activeIndex) below.
     browser.webNavigation.setCurrentURI(this._getURIFromString("about:blank"));
     // Attach data that will be restored on "load" event, after tab is restored.
     if (activeIndex > -1) {
-      let curSHEntry = browser.webNavigation.sessionHistory.
-                       getEntryAtIndex(activeIndex, false).
-                       QueryInterface(Ci.nsISHEntry);
-
       // restore those aspects of the currently active documents which are not
       // preserved in the plain history entries (mainly scroll state and text data)
       browser.__SS_restore_data = tabData.entries[activeIndex] || {};
       browser.__SS_restore_pageStyle = tabData.pageStyle || "";
       browser.__SS_restore_tab = aTab;
-      browser.__SS_restore_docIdentifier = curSHEntry.docIdentifier;
       didStartLoad = true;
       try {
         // In order to work around certain issues in session history, we need to
         // force session history to update its internal index and call reload
         // instead of gotoIndex. See bug 597315.
         browser.webNavigation.sessionHistory.getEntryAtIndex(activeIndex, true);
         browser.webNavigation.sessionHistory.reloadCurrentEntry();
       }
@@ -3183,34 +3176,26 @@ SessionStoreService.prototype = {
       var postdata = atob(aEntry.postdata_b64);
       var stream = Cc["@mozilla.org/io/string-input-stream;1"].
                    createInstance(Ci.nsIStringInputStream);
       stream.setData(postdata, postdata.length);
       shEntry.postData = stream;
     }
 
     if (aEntry.docIdentifier) {
-      // Get a new document identifier for this entry to ensure that history
-      // entries after a session restore are considered to have different
-      // documents from the history entries before the session restore.
-      // Document identifiers are 64-bit ints, so JS will loose precision and
-      // start assigning all entries the same doc identifier if these ever get
-      // large enough.
-      //
-      // It's a potential security issue if document identifiers aren't
-      // globally unique, but shEntry.setUniqueDocIdentifier() below guarantees
-      // that we won't re-use a doc identifier within a given instance of the
-      // application.
-      let ident = aDocIdentMap[aEntry.docIdentifier];
-      if (!ident) {
-        shEntry.setUniqueDocIdentifier();
-        aDocIdentMap[aEntry.docIdentifier] = shEntry.docIdentifier;
+      // If we have a serialized document identifier, try to find an SHEntry
+      // which matches that doc identifier and adopt that SHEntry's
+      // BFCacheEntry.  If we don't find a match, insert shEntry as the match
+      // for the document identifier.
+      let matchingEntry = aDocIdentMap[aEntry.docIdentifier];
+      if (!matchingEntry) {
+        aDocIdentMap[aEntry.docIdentifier] = shEntry;
       }
       else {
-        shEntry.docIdentifier = ident;
+        shEntry.adoptBFCacheEntry(matchingEntry);
       }
     }
 
     if (aEntry.owner_b64) {
       var ownerInput = Cc["@mozilla.org/io/string-input-stream;1"].
                        createInstance(Ci.nsIStringInputStream);
       var binaryData = atob(aEntry.owner_b64);
       ownerInput.setData(binaryData, binaryData.length);
@@ -3336,29 +3321,22 @@ SessionStoreService.prototype = {
     // don't restore text data and scrolling state if the user has navigated
     // away before the loading completed (except for in-page navigation)
     if (hasExpectedURL(aEvent.originalTarget, aBrowser.__SS_restore_data.url)) {
       var content = aEvent.originalTarget.defaultView;
       restoreTextDataAndScrolling(content, aBrowser.__SS_restore_data, "");
       aBrowser.markupDocumentViewer.authorStyleDisabled = selectedPageStyle == "_nostyle";
     }
 
-    if (aBrowser.__SS_restore_docIdentifier) {
-      let sh = aBrowser.webNavigation.sessionHistory;
-      sh.getEntryAtIndex(sh.index, false).QueryInterface(Ci.nsISHEntry).
-         docIdentifier = aBrowser.__SS_restore_docIdentifier;
-    }
-
     // notify the tabbrowser that this document has been completely restored
     this._sendTabRestoredNotification(aBrowser.__SS_restore_tab);
 
     delete aBrowser.__SS_restore_data;
     delete aBrowser.__SS_restore_pageStyle;
     delete aBrowser.__SS_restore_tab;
-    delete aBrowser.__SS_restore_docIdentifier;
   },
 
   /**
    * Restore visibility and dimension features to a window
    * @param aWindow
    *        Window reference
    * @param aWinData
    *        Object containing session data for the window
--- a/browser/components/sessionstore/test/browser/browser_500328.js
+++ b/browser/components/sessionstore/test/browser/browser_500328.js
@@ -111,23 +111,23 @@ function test() {
 
     tabBrowser.loadURI("http://example.com", null, null);
 
     tabBrowser.addEventListener("load", function(aEvent) {
       tabBrowser.removeEventListener("load", arguments.callee, true);
 
       // After these push/replaceState calls, the window should have three
       // history entries:
-      //   testURL (state object: null)      <-- oldest
-      //   testURL (state object: {obj1:1})
-      //   page2   (state object: {obj3:/^a$/})  <-- newest
+      //   testURL        (state object: null)          <-- oldest
+      //   testURL        (state object: {obj1:1})
+      //   testURL?page2  (state object: {obj3:/^a$/})  <-- newest
       let contentWindow = tab.linkedBrowser.contentWindow;
       let history = contentWindow.history;
       history.pushState({obj1:1}, "title-obj1");
-      history.pushState({obj2:2}, "title-obj2", "page2");
+      history.pushState({obj2:2}, "title-obj2", "?page2");
       history.replaceState({obj3:/^a$/}, "title-obj3");
 
       let state = ss.getTabState(tab);
       gBrowser.removeTab(tab);
 
       // Restore the state into a new tab.  Things don't work well when we
       // restore into the old tab, but that's not a real use case anyway.
       let tab2 = gBrowser.addTab("about:blank");
--- a/content/base/public/nsIDocument.h
+++ b/content/base/public/nsIDocument.h
@@ -64,16 +64,17 @@
 #ifdef MOZ_SMIL
 #include "nsSMILAnimationController.h"
 #endif // MOZ_SMIL
 #include "nsIScriptGlobalObject.h"
 #include "nsIDocumentEncoder.h"
 #include "nsIAnimationFrameListener.h"
 #include "nsEventStates.h"
 #include "nsIStructuredCloneContainer.h"
+#include "nsIBFCacheEntry.h"
 #include "nsDOMMemoryReporter.h"
 
 class nsIContent;
 class nsPresContext;
 class nsIPresShell;
 class nsIDocShell;
 class nsStyleSet;
 class nsIStyleSheet;
@@ -120,18 +121,18 @@ class Loader;
 
 namespace dom {
 class Link;
 class Element;
 } // namespace dom
 } // namespace mozilla
 
 #define NS_IDOCUMENT_IID      \
-{ 0x4114a7c7, 0xb2f4, 0x4dea, \
- { 0xac, 0x78, 0x20, 0xab, 0xda, 0x6f, 0xb2, 0xaf } }
+{ 0x448c396a, 0x013c, 0x47b8, \
+ { 0x95, 0xf4, 0x56, 0x68, 0x0f, 0x5f, 0x12, 0xf8 } }
 
 // 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)
@@ -474,21 +475,25 @@ public:
                                nsIPresShell** aInstancePtrResult) = 0;
   virtual void DeleteShell() = 0;
 
   nsIPresShell* GetShell() const
   {
     return GetBFCacheEntry() ? nsnull : mPresShell;
   }
 
-  void SetBFCacheEntry(nsISHEntry* aSHEntry) {
-    mSHEntry = aSHEntry;
+  void SetBFCacheEntry(nsIBFCacheEntry* aEntry)
+  {
+    mBFCacheEntry = aEntry;
   }
 
-  nsISHEntry* GetBFCacheEntry() const { return mSHEntry; }
+  nsIBFCacheEntry* GetBFCacheEntry() const
+  {
+    return mBFCacheEntry;
+  }
 
   /**
    * Return the parent document of this document. Will return null
    * unless this document is within a compound document and has a
    * parent. Note that this parent chain may cross chrome boundaries.
    */
   nsIDocument *GetParentDocument() const
   {
@@ -1781,19 +1786,19 @@ protected:
   // Weak reference to mScriptGlobalObject QI:d to nsPIDOMWindow,
   // updated on every set of mSecriptGlobalObject.
   nsPIDOMWindow *mWindow;
 
   nsCOMPtr<nsIDocumentEncoder> mCachedEncoder;
 
   AnimationListenerList mAnimationFrameListeners;
 
-  // The session history entry in which we're currently bf-cached. Non-null
-  // if and only if we're currently in the bfcache.
-  nsISHEntry* mSHEntry;
+  // This object allows us to evict ourself from the back/forward cache.  The
+  // pointer is non-null iff we're currently in the bfcache.
+  nsIBFCacheEntry *mBFCacheEntry;
 
   // Our base target.
   nsString mBaseTarget;
 
   nsCOMPtr<nsIStructuredCloneContainer> mStateObjectContainer;
   nsCOMPtr<nsIVariant> mStateObjectCached;
 };
 
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -4121,20 +4121,20 @@ nsDocShell::LoadErrorPage(nsIURI *aURI, 
                 spec.get(), NS_ConvertUTF16toUTF8(aURL).get(), chanName.get()));
     }
 #endif
     mFailedChannel = aFailedChannel;
     mFailedURI = aURI;
     mFailedLoadType = mLoadType;
 
     if (mLSHE) {
-        // If we don't give mLSHE a new doc identifier here, when we go back or
-        // forward to another SHEntry with the same doc identifier, the error
-        // page will persist.
-        mLSHE->SetUniqueDocIdentifier();
+        // Abandon mLSHE's BFCache entry and create a new one.  This way, if
+        // we go back or forward to another SHEntry with the same doc
+        // identifier, the error page won't persist.
+        mLSHE->AbandonBFCacheEntry();
     }
 
     nsCAutoString url;
     nsCAutoString charset;
     if (aURI)
     {
         nsresult rv = aURI->GetSpec(url);
         rv |= aURI->GetOriginCharset(charset);
@@ -4397,20 +4397,20 @@ nsDocShell::LoadPage(nsISupports *aPageD
     }
 
     // Now clone shEntryIn, since we might end up modifying it later on, and we
     // want a page descriptor to be reusable.
     nsCOMPtr<nsISHEntry> shEntry;
     nsresult rv = shEntryIn->Clone(getter_AddRefs(shEntry));
     NS_ENSURE_SUCCESS(rv, rv);
 
-    // Give our cloned shEntry a new document identifier so this load is
-    // independent of all other loads.  (This is important, in particular,
-    // for bugs 582795 and 585298.)
-    rv = shEntry->SetUniqueDocIdentifier();
+    // Give our cloned shEntry a new bfcache entry so this load is independent
+    // of all other loads.  (This is important, in particular, for bugs 582795
+    // and 585298.)
+    rv = shEntry->AbandonBFCacheEntry();
     NS_ENSURE_SUCCESS(rv, rv);
 
     //
     // load the page as view-source
     //
     if (nsIWebPageDescriptor::DISPLAY_AS_SOURCE == aDisplayType) {
         nsCOMPtr<nsIURI> oldUri, newUri;
         nsCString spec, newSpec;
@@ -8275,27 +8275,25 @@ 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);
 
-        bool sameDocIdent = false;
+        // XXX rename
+        bool sameDocument = false;
         if (mOSHE && aSHEntry) {
             // We're doing a history load.
 
-            PRUint64 ourDocIdent, otherDocIdent;
-            mOSHE->GetDocIdentifier(&ourDocIdent);
-            aSHEntry->GetDocIdentifier(&otherDocIdent);
-            sameDocIdent = (ourDocIdent == otherDocIdent);
+            mOSHE->SharesDocumentWith(aSHEntry, &sameDocument);
 
 #ifdef DEBUG
-            if (sameDocIdent) {
+            if (sameDocument) {
                 nsCOMPtr<nsIInputStream> currentPostData;
                 mOSHE->GetPostData(getter_AddRefs(currentPostData));
                 NS_ASSERTION(currentPostData == aPostData,
                              "Different POST data for entries for the same page?");
             }
 #endif
         }
 
@@ -8308,17 +8306,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 = (sameDocIdent && mOSHE != aSHEntry) ||
+        bool doShortCircuitedLoad = (sameDocument && mOSHE != aSHEntry) ||
                                       (!aSHEntry && aPostData == nsnull &&
                                        sameExceptHashes && !newHash.IsEmpty());
 
         // Fire a hashchange event if we're doing a short-circuited load and the
         // URIs differ only in their hashes.
         bool doHashchange = doShortCircuitedLoad &&
                               sameExceptHashes &&
                               !curHash.Equals(newHash);
@@ -8359,32 +8357,36 @@ 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, true, true, 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.
+                    if (mLSHE)
+                        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);
@@ -8394,21 +8396,16 @@ nsDocShell::InternalLoad(nsIURI * aURI,
                 // page will restore the appropriate postData.
                 if (postData)
                     mOSHE->SetPostData(postData);
 
                 // Make sure we won't just repost without hitting the
                 // cache first
                 if (cacheKey)
                     mOSHE->SetCacheKey(cacheKey);
-
-                // Propagate our document identifier to the new mOSHE so that
-                // we'll know it's related by an anchor navigation or pushState.
-                if (docIdent != PRUint64(-1))
-                    mOSHE->SetDocIdentifier(docIdent);
             }
 
             /* restore previous position of scroller(s), if we're moving
              * back in history (bug 59774)
              */
             if (mOSHE && (aLoadType == LOAD_HISTORY || aLoadType == LOAD_RELOAD_NORMAL))
             {
                 nscoord bx, by;
@@ -8442,37 +8439,37 @@ nsDocShell::InternalLoad(nsIURI * aURI,
                 if (history) {
                     history->SetURITitle(aURI, mTitle);
                 }
                 else if (mGlobalHistory) {
                     mGlobalHistory->SetPageTitle(aURI, mTitle);
                 }
             }
 
-            if (sameDocIdent) {
+            if (sameDocument) {
                 // Set the doc's URI according to the new history entry's URI
                 nsCOMPtr<nsIURI> newURI;
                 mOSHE->GetURI(getter_AddRefs(newURI));
                 NS_ENSURE_TRUE(newURI, NS_ERROR_FAILURE);
                 nsCOMPtr<nsIDocument> doc =
                   do_GetInterface(GetAsSupports(this));
                 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
 
                 doc->SetDocumentURI(newURI);
             }
 
             SetDocCurrentStateObj(mOSHE);
 
             // Dispatch the popstate and hashchange events, as appropriate.
             nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(mScriptGlobal);
             if (window) {
-                // Need the doHashchange check here since sameDocIdent is
+                // Need the doHashchange check here since sameDocument is
                 // false if we're navigating to a new shentry (i.e. a aSHEntry
                 // is null), such as when clicking a <a href="#foo">.
-                if (sameDocIdent || doHashchange) {
+                if (sameDocument || doHashchange) {
                   window->DispatchSyncPopState();
                 }
 
                 if (doHashchange) {
                   // Make sure to use oldURI here, not mCurrentURI, because by
                   // now, mCurrentURI has changed!
                   window->DispatchAsyncHashchange(oldURI, aURI);
                 }
@@ -9245,21 +9242,21 @@ nsDocShell::OnNewURI(nsIURI * aURI, nsIC
 
         if (httpChannel) {
             nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel));
             if (uploadChannel) {
                 uploadChannel->GetUploadStream(getter_AddRefs(inputStream));
             }
 
             // If the response status indicates an error, unlink this session
-            // history entry from any entries sharing its doc ident.
+            // history entry from any entries sharing its document.
             PRUint32 responseStatus;
             nsresult rv = httpChannel->GetResponseStatus(&responseStatus);
             if (mLSHE && NS_SUCCEEDED(rv) && responseStatus >= 400) {
-                mLSHE->SetUniqueDocIdentifier();
+                mLSHE->AbandonBFCacheEntry();
             }
         }
     }
     /* Create SH Entry (mLSHE) only if there is a  SessionHistory object (mSessionHistory) in
      * the current frame or in the root docshell
      */
     nsCOMPtr<nsISHistory> rootSH = mSessionHistory;
     if (!rootSH) {
@@ -9478,19 +9475,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 EvictContentViewers 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 EvictOutOfRangeContentViewers 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);
 
@@ -9660,21 +9657,19 @@ nsDocShell::AddState(nsIVariant *aData, 
         // Since we're not changing which page we have loaded, pass
         // true for aCloneChildren.
         rv = AddToSessionHistory(newURI, nsnull, nsnull, true,
                                  getter_AddRefs(newSHEntry));
         NS_ENSURE_SUCCESS(rv, rv);
 
         NS_ENSURE_TRUE(newSHEntry, NS_ERROR_FAILURE);
 
-        // Set the new SHEntry's document identifier, if we can.
-        PRUint64 ourDocIdent;
-        NS_ENSURE_SUCCESS(oldOSHE->GetDocIdentifier(&ourDocIdent),
-                          NS_ERROR_FAILURE);
-        NS_ENSURE_SUCCESS(newSHEntry->SetDocIdentifier(ourDocIdent),
+        // Link the new SHEntry to the old SHEntry's BFCache entry, since the
+        // two entries correspond to the same document.
+        NS_ENSURE_SUCCESS(newSHEntry->AdoptBFCacheEntry(oldOSHE),
                           NS_ERROR_FAILURE);
 
         // Set the new SHEntry's title (bug 655273).
         nsString title;
         mOSHE->GetTitle(getter_Copies(title));
         newSHEntry->SetTitle(title);
 
         // AddToSessionHistory may not modify mOSHE.  In case it doesn't,
@@ -9710,17 +9705,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->EvictContentViewers(curIndex - 1, curIndex);
+            internalSH->EvictOutOfRangeContentViewers(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,16 +61,17 @@
 #include "nsExternalSharingAppService.h"
 #endif
 #if defined(ANDROID)
 #include "nsExternalURLHandlerService.h"
 #endif
 
 // session history
 #include "nsSHEntry.h"
+#include "nsSHEntryShared.h"
 #include "nsSHistory.h"
 #include "nsSHTransaction.h"
 
 // download history
 #include "nsDownloadHistory.h"
 
 static bool gInitialized = false;
 
@@ -82,25 +83,25 @@ Initialize()
   if (gInitialized) {
     return NS_OK;
   }
   gInitialized = true;
 
   nsresult rv = nsSHistory::Startup();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = nsSHEntry::Startup();
-  return rv;
+  nsSHEntryShared::Startup();
+  return NS_OK;
 }
 
 static void
 Shutdown()
 {
   nsSHistory::Shutdown();
-  nsSHEntry::Shutdown();
+  nsSHEntryShared::Shutdown();
   gInitialized = false;
 }
 
 // docshell
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsDocShell, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsDefaultURIFixup)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsWebNavigationInfo, Init)
 
--- a/docshell/shistory/public/Makefile.in
+++ b/docshell/shistory/public/Makefile.in
@@ -51,12 +51,13 @@ SDK_XPIDLSRCS   = \
                   nsISHistoryListener.idl   \
                   $(NULL)
 
 XPIDLSRCS	= \
 		  nsISHEntry.idl        \
                   nsISHContainer.idl    \
                   nsISHTransaction.idl  \
                   nsISHistoryInternal.idl \
+		  nsIBFCacheEntry.idl \
                   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
new file mode 100644
--- /dev/null
+++ b/docshell/shistory/public/nsIBFCacheEntry.idl
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface lets you evict a document from the back/forward cache.
+ */
+[scriptable, uuid(a576060e-c7df-4d81-aa8c-5b52bd6ad25d)]
+interface nsIBFCacheEntry : nsISupports
+{
+  void RemoveFromBFCacheSync();
+  void RemoveFromBFCacheAsync();
+
+  readonly attribute unsigned long long ID;
+};
--- a/docshell/shistory/public/nsISHEntry.idl
+++ b/docshell/shistory/public/nsISHEntry.idl
@@ -46,25 +46,28 @@
 
 interface nsILayoutHistoryState;
 interface nsIContentViewer;
 interface nsIURI;
 interface nsIInputStream;
 interface nsIDocShellTreeItem;
 interface nsISupportsArray;
 interface nsIStructuredCloneContainer;
+interface nsIBFCacheEntry;
+
 %{C++
 struct nsIntRect;
 class nsDocShellEditorData;
+class nsSHEntryShared;
 %}
 [ref] native nsIntRect(nsIntRect);
 [ptr] native nsDocShellEditorDataPtr(nsDocShellEditorData);
-
+[ptr] native nsSHEntryShared(nsSHEntryShared);
 
-[scriptable, uuid(b92d403e-f5ec-4b81-b0e3-6e6c241cef2d)]
+[scriptable, uuid(6443FD72-A50F-4B8B-BB82-BB1FA04CB15D)]
 interface nsISHEntry : nsIHistoryEntry
 {
     /** URI for the document */
     void setURI(in nsIURI aURI);
 
     /** Referrer URI */
     attribute nsIURI referrerURI;
 
@@ -135,31 +138,16 @@ interface nsISHEntry : nsIHistoryEntry
     attribute unsigned long loadType;
 
     /**
      * An ID to help identify this entry from others during
      * subframe navigation
      */
     attribute unsigned long ID;
 
-    /**
-     * docIdentifier is an integer that should be the same for two entries
-     * attached to the same docshell if and only if the two entries are entries
-     * for the same document.  In practice, two entries A and B will have the
-     * same docIdentifier if we arrived at B by clicking an anchor link in A or
-     * if B was created by A's calling history.pushState().
-     */
-    attribute unsigned long long docIdentifier;
-
-    /**
-     * Changes this entry's doc identifier to a new value which is unique
-     * among those of all other entries.
-     */
-    void setUniqueDocIdentifier();
-
     /** attribute to set and get the cache key for the entry */
     attribute nsISupports cacheKey;
 
     /** attribute to indicate whether layoutHistoryState should be saved */
     attribute boolean saveLayoutStateFlag;
 
     /** attribute to indicate whether the page is already expired in cache */
     attribute boolean expirationStatus;
@@ -247,28 +235,69 @@ interface nsISHEntry : nsIHistoryEntry
      * when isDynamicallyAdded is called on it.
      */
     boolean hasDynamicallyAddedChild();
 
     /**
      * The history ID of the docshell.
      */
     attribute unsigned long long docshellID;
+
+    readonly attribute nsIBFCacheEntry BFCacheEntry;
+
+    /**
+     * Does this SHEntry point to the given BFCache entry?  If so, evicting
+     * the BFCache entry will evict the SHEntry, since the two entries
+     * correspond to the same document.
+     */
+    [notxpcom, noscript]
+    boolean hasBFCacheEntry(in nsIBFCacheEntry aEntry);
+
+    /**
+     * Adopt aEntry's BFCacheEntry, so now both this and aEntry point to
+     * aEntry's BFCacheEntry.
+     */
+    void adoptBFCacheEntry(in nsISHEntry aEntry);
+
+    /**
+     * Create a new BFCache entry and drop our reference to our old one.  This
+     * call unlinks this SHEntry from any other SHEntries for its document.
+     */
+    void abandonBFCacheEntry();
+
+    /**
+     * Does this SHEntry correspond to the same document as aEntry?  This is
+     * true iff the two SHEntries have the same BFCacheEntry.  So in
+     * particular, sharesDocumentWith(aEntry) is guaranteed to return true if
+     * it's preceeded by a call to adoptBFCacheEntry(aEntry).
+     */
+    boolean sharesDocumentWith(in nsISHEntry aEntry);
 };
 
 [scriptable, uuid(bb66ac35-253b-471f-a317-3ece940f04c5)]
 interface nsISHEntryInternal : nsISupports
 {
     [notxpcom] void RemoveFromBFCacheAsync();
     [notxpcom] void RemoveFromBFCacheSync();
 
     /**
      * A number that is assigned by the sHistory when the entry is activated
      */
     attribute unsigned long lastTouched;
+
+    /**
+     * Some state, particularly that related to the back/forward cache, is
+     * shared between SHEntries which correspond to the same document.  This
+     * method gets a pointer to that shared state.
+     *
+     * This shared state is the SHEntry's BFCacheEntry.  So
+     * hasBFCacheEntry(getSharedState()) is guaranteed to return true.
+     */
+    [noscript, notxpcom]
+    nsSHEntryShared getSharedState();
 };
 
 %{ C++
 // {BFD1A791-AD9F-11d3-BDC7-0050040A9B44}
 #define NS_SHENTRY_CID \
 {0xbfd1a791, 0xad9f, 0x11d3, {0xbd, 0xc7, 0x0, 0x50, 0x4, 0xa, 0x9b, 0x44}}
 
 #define NS_SHENTRY_CONTRACTID \
--- a/docshell/shistory/public/nsISHistoryInternal.idl
+++ b/docshell/shistory/public/nsISHistoryInternal.idl
@@ -52,17 +52,17 @@ interface nsIDocShell;
 #define NS_SHISTORY_INTERNAL_CONTRACTID "@mozilla.org/browser/shistory-internal;1"
 
 template<class E, class A> class nsTArray;
 struct nsTArrayDefaultAllocator;
 %}
 
 [ref] native nsDocshellIDArray(nsTArray<PRUint64, nsTArrayDefaultAllocator>);
 
-[scriptable, uuid(2dede933-25e1-47a3-8f61-0127c785ea01)]
+[scriptable, uuid(e27cf38e-c19f-4294-bd31-d7e0916e7fa2)]
 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,30 +91,35 @@ interface nsISHistoryInternal: nsISuppor
    */
    void replaceEntry(in long aIndex, in nsISHEntry aReplaceEntry);
 
   /** 
    * Get handle to the history listener
    */
    readonly attribute nsISHistoryListener listener;
 
-  /**
-   * 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 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 the content viewer associated with a session history entry
+    * Evict the content viewer associated with a bfcache entry
     * that has timed out.
     */
-   void evictExpiredContentViewerForEntry(in nsISHEntry aEntry);
+   void evictExpiredContentViewerForEntry(in nsIBFCacheEntry aEntry);
 
    /**
     * Evict all the content viewers in this session history
     */
    void evictAllContentViewers();
 
    /**
     * Removes entries from the history if their docshellID is in
--- a/docshell/shistory/src/Makefile.in
+++ b/docshell/shistory/src/Makefile.in
@@ -43,17 +43,20 @@ VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE		= shistory
 LIBRARY_NAME	= shistory_s
 FORCE_STATIC_LIB = 1
 LIBXUL_LIBRARY	= 1
 
+EXPORTS		= nsSHEntryShared.h \
+		  $(NULL)
 
 CPPSRCS		= nsSHEntry.cpp        \
             nsSHTransaction.cpp   \
             nsSHistory.cpp \
+	    nsSHEntryShared.cpp \
             $(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
 LOCAL_INCLUDES += -I$(srcdir)/../../base
--- a/docshell/shistory/src/nsSHEntry.cpp
+++ b/docshell/shistory/src/nsSHEntry.cpp
@@ -31,171 +31,87 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
-#ifdef DEBUG_bryner
-#define DEBUG_PAGE_CACHE
-#endif
-
 // Local Includes
 #include "nsSHEntry.h"
 #include "nsXPIDLString.h"
 #include "nsReadableUtils.h"
 #include "nsIDocShellLoadInfo.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsIDocument.h"
 #include "nsIDOMDocument.h"
-#include "nsAutoPtr.h"
-#include "nsThreadUtils.h"
-#include "nsIWebNavigation.h"
 #include "nsISHistory.h"
 #include "nsISHistoryInternal.h"
 #include "nsDocShellEditorData.h"
-#include "nsIDocShell.h"
+#include "nsSHEntryShared.h"
+#include "nsILayoutHistoryState.h"
+#include "nsIContentViewer.h"
+#include "nsISupportsArray.h"
 
 namespace dom = mozilla::dom;
 
-// Hardcode this to time out unused content viewers after 30 minutes
-#define CONTENT_VIEWER_TIMEOUT_SECONDS 30*60
-
-typedef nsExpirationTracker<nsSHEntry,3> HistoryTrackerBase;
-class HistoryTracker : public HistoryTrackerBase {
-public:
-  // Expire cached contentviewers after 20-30 minutes in the cache.
-  HistoryTracker() : HistoryTrackerBase((CONTENT_VIEWER_TIMEOUT_SECONDS/2)*1000) {}
-  
-protected:
-  virtual void NotifyExpired(nsSHEntry* aObj) {
-    RemoveObject(aObj);
-    aObj->Expire();
-  }
-};
-
-static HistoryTracker *gHistoryTracker = nsnull;
 static PRUint32 gEntryID = 0;
-static PRUint64 gEntryDocIdentifier = 0;
-
-nsresult nsSHEntry::Startup()
-{
-  gHistoryTracker = new HistoryTracker();
-  return gHistoryTracker ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
-}
-
-void nsSHEntry::Shutdown()
-{
-  delete gHistoryTracker;
-  gHistoryTracker = nsnull;
-}
-
-static void StopTrackingEntry(nsSHEntry *aEntry)
-{
-  if (aEntry->GetExpirationState()->IsTracked()) {
-    gHistoryTracker->RemoveObject(aEntry);
-  }
-}
 
 //*****************************************************************************
 //***    nsSHEntry: Object Management
 //*****************************************************************************
 
 
-nsSHEntry::nsSHEntry() 
+nsSHEntry::nsSHEntry()
   : mLoadType(0)
   , mID(gEntryID++)
-  , mDocIdentifier(gEntryDocIdentifier++)
   , mScrollPositionX(0)
   , mScrollPositionY(0)
   , mURIWasModified(false)
-  , mIsFrameNavigation(false)
-  , mSaveLayoutState(true)
-  , mExpired(false)
-  , mSticky(true)
-  , mDynamicallyCreated(false)
-  , mParent(nsnull)
-  , mViewerBounds(0, 0, 0, 0)
-  , mDocShellID(0)
-  , mLastTouched(0)
 {
+  mShared = new nsSHEntryShared();
 }
 
 nsSHEntry::nsSHEntry(const nsSHEntry &other)
-  : mURI(other.mURI)
+  : mShared(other.mShared)
+  , mURI(other.mURI)
   , mReferrerURI(other.mReferrerURI)
-  // XXX why not copy mDocument?
   , mTitle(other.mTitle)
   , mPostData(other.mPostData)
-  , mLayoutHistoryState(other.mLayoutHistoryState)
   , mLoadType(0)         // XXX why not copy?
   , mID(other.mID)
-  , mDocIdentifier(other.mDocIdentifier)
   , mScrollPositionX(0)  // XXX why not copy?
   , mScrollPositionY(0)  // XXX why not copy?
   , mURIWasModified(other.mURIWasModified)
-  , mIsFrameNavigation(other.mIsFrameNavigation)
-  , mSaveLayoutState(other.mSaveLayoutState)
-  , mExpired(other.mExpired)
-  , mSticky(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 true;
 }
 
 nsSHEntry::~nsSHEntry()
 {
-  StopTrackingEntry(this);
-
-  // Since we never really remove kids from SHEntrys, we need to null
-  // out the mParent pointers on all our kids.
+  // Null out the mParent pointers on all our kids.
   mChildren.EnumerateForwards(ClearParentPtr, nsnull);
-  mChildren.Clear();
-
-  if (mContentViewer) {
-    // RemoveFromBFCacheSync is virtual, so call the nsSHEntry version
-    // explicitly
-    nsSHEntry::RemoveFromBFCacheSync();
-  }
-
-  mEditorData = nsnull;
-
-#ifdef DEBUG
-  // This is not happening as far as I can tell from breakpad as of early November 2007
-  nsExpirationTracker<nsSHEntry,3>::Iterator iterator(gHistoryTracker);
-  nsSHEntry* elem;
-  while ((elem = iterator.Next()) != nsnull) {
-    NS_ASSERTION(elem != this, "Found dead entry still in the tracker!");
-  }
-#endif
 }
 
 //*****************************************************************************
 //    nsSHEntry: nsISupports
 //*****************************************************************************
 
-NS_IMPL_ISUPPORTS5(nsSHEntry, nsISHContainer, nsISHEntry, nsIHistoryEntry,
-                   nsIMutationObserver, nsISHEntryInternal)
+NS_IMPL_ISUPPORTS4(nsSHEntry, nsISHContainer, nsISHEntry, nsIHistoryEntry,
+                   nsISHEntryInternal)
 
 //*****************************************************************************
 //    nsSHEntry: nsISHEntry
 //*****************************************************************************
 
 NS_IMETHODIMP nsSHEntry::SetScrollPosition(PRInt32 x, PRInt32 y)
 {
   mScrollPositionX = x;
@@ -246,45 +162,23 @@ NS_IMETHODIMP nsSHEntry::SetReferrerURI(
 {
   mReferrerURI = aReferrerURI;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetContentViewer(nsIContentViewer *aViewer)
 {
-  NS_PRECONDITION(!aViewer || !mContentViewer, "SHEntry already contains viewer");
-
-  if (mContentViewer || !aViewer) {
-    DropPresentationState();
-  }
-
-  mContentViewer = aViewer;
-
-  if (mContentViewer) {
-    gHistoryTracker->AddObject(this);
-
-    nsCOMPtr<nsIDOMDocument> domDoc;
-    mContentViewer->GetDOMDocument(getter_AddRefs(domDoc));
-    // Store observed document in strong pointer in case it is removed from
-    // the contentviewer
-    mDocument = do_QueryInterface(domDoc);
-    if (mDocument) {
-      mDocument->SetBFCacheEntry(this);
-      mDocument->AddMutationObserver(this);
-    }
-  }
-
-  return NS_OK;
+  return mShared->SetContentViewer(aViewer);
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetContentViewer(nsIContentViewer **aResult)
 {
-  *aResult = mContentViewer;
+  *aResult = mShared->mContentViewer;
   NS_IF_ADDREF(*aResult);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetAnyContentViewer(nsISHEntry **aOwnerEntry,
                                nsIContentViewer **aResult)
 {
@@ -314,24 +208,24 @@ nsSHEntry::GetAnyContentViewer(nsISHEntr
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetSticky(bool aSticky)
 {
-  mSticky = aSticky;
+  mShared->mSticky = aSticky;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetSticky(bool *aSticky)
 {
-  *aSticky = mSticky;
+  *aSticky = mShared->mSticky;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsSHEntry::GetTitle(PRUnichar** aTitle)
 {
   // Check for empty title...
   if (mTitle.IsEmpty() && mURI) {
     // Default title is the URL.
@@ -360,26 +254,28 @@ NS_IMETHODIMP nsSHEntry::GetPostData(nsI
 NS_IMETHODIMP nsSHEntry::SetPostData(nsIInputStream* aPostData)
 {
   mPostData = aPostData;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsSHEntry::GetLayoutHistoryState(nsILayoutHistoryState** aResult)
 {
-  *aResult = mLayoutHistoryState;
+  *aResult = mShared->mLayoutHistoryState;
   NS_IF_ADDREF(*aResult);
   return NS_OK;
 }
 
 NS_IMETHODIMP nsSHEntry::SetLayoutHistoryState(nsILayoutHistoryState* aState)
 {
-  mLayoutHistoryState = aState;
-  if (mLayoutHistoryState)
-    mLayoutHistoryState->SetScrollPositionOnly(!mSaveLayoutState);
+  mShared->mLayoutHistoryState = aState;
+  if (mShared->mLayoutHistoryState) {
+    mShared->mLayoutHistoryState->
+      SetScrollPositionOnly(!mShared->mSaveLayoutState);
+  }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP nsSHEntry::GetLoadType(PRUint32 * aResult)
 {
   *aResult = mLoadType;
   return NS_OK;
@@ -398,209 +294,236 @@ NS_IMETHODIMP nsSHEntry::GetID(PRUint32 
 }
 
 NS_IMETHODIMP nsSHEntry::SetID(PRUint32  aID)
 {
   mID = aID;
   return NS_OK;
 }
 
-NS_IMETHODIMP nsSHEntry::GetDocIdentifier(PRUint64 * aResult)
-{
-  *aResult = mDocIdentifier;
-  return NS_OK;
-}
-
-NS_IMETHODIMP nsSHEntry::SetDocIdentifier(PRUint64 aDocIdentifier)
+nsSHEntryShared* nsSHEntry::GetSharedState()
 {
-  // This ensures that after a session restore, gEntryDocIdentifier is greater
-  // than all SHEntries' docIdentifiers, which ensures that we'll never repeat
-  // a doc identifier.
-  if (aDocIdentifier >= gEntryDocIdentifier)
-    gEntryDocIdentifier = aDocIdentifier + 1;
-
-  mDocIdentifier = aDocIdentifier;
-  return NS_OK;
-}
-
-NS_IMETHODIMP nsSHEntry::SetUniqueDocIdentifier()
-{
-  mDocIdentifier = gEntryDocIdentifier++;
-  return NS_OK;
+  return mShared;
 }
 
 NS_IMETHODIMP nsSHEntry::GetIsSubFrame(bool * aFlag)
 {
-  *aFlag = mIsFrameNavigation;
+  *aFlag = mShared->mIsFrameNavigation;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsSHEntry::SetIsSubFrame(bool    aFlag)
 {
-  mIsFrameNavigation = aFlag;
+  mShared->mIsFrameNavigation = aFlag;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsSHEntry::GetCacheKey(nsISupports** aResult)
 {
-  *aResult = mCacheKey;
+  *aResult = mShared->mCacheKey;
   NS_IF_ADDREF(*aResult);
   return NS_OK;
 }
 
 NS_IMETHODIMP nsSHEntry::SetCacheKey(nsISupports* aCacheKey)
 {
-  mCacheKey = aCacheKey;
+  mShared->mCacheKey = aCacheKey;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsSHEntry::GetSaveLayoutStateFlag(bool * aFlag)
 {
-  *aFlag = mSaveLayoutState;
+  *aFlag = mShared->mSaveLayoutState;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsSHEntry::SetSaveLayoutStateFlag(bool    aFlag)
 {
-  mSaveLayoutState = aFlag;
-  if (mLayoutHistoryState)
-    mLayoutHistoryState->SetScrollPositionOnly(!aFlag);
+  mShared->mSaveLayoutState = aFlag;
+  if (mShared->mLayoutHistoryState) {
+    mShared->mLayoutHistoryState->SetScrollPositionOnly(!aFlag);
+  }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP nsSHEntry::GetExpirationStatus(bool * aFlag)
 {
-  *aFlag = mExpired;
+  *aFlag = mShared->mExpired;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsSHEntry::SetExpirationStatus(bool    aFlag)
 {
-  mExpired = aFlag;
+  mShared->mExpired = aFlag;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsSHEntry::GetContentType(nsACString& aContentType)
 {
-  aContentType = mContentType;
+  aContentType = mShared->mContentType;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsSHEntry::SetContentType(const nsACString& aContentType)
 {
-  mContentType = aContentType;
+  mShared->mContentType = aContentType;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::Create(nsIURI * aURI, const nsAString &aTitle,
                   nsIInputStream * aInputStream,
                   nsILayoutHistoryState * aLayoutHistoryState,
                   nsISupports * aCacheKey, const nsACString& aContentType,
                   nsISupports* aOwner,
                   PRUint64 aDocShellID, 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.
-  mIsFrameNavigation = false;
+  mShared->mIsFrameNavigation = false;
 
   // By default we save LayoutHistoryState
-  mSaveLayoutState = true;
-  mLayoutHistoryState = aLayoutHistoryState;
+  mShared->mSaveLayoutState = true;
+  mShared->mLayoutHistoryState = aLayoutHistoryState;
 
   //By default the page is not expired
-  mExpired = false;
+  mShared->mExpired = false;
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::Clone(nsISHEntry ** aResult)
 {
   *aResult = new nsSHEntry(*this);
-  if (!*aResult)
-    return NS_ERROR_OUT_OF_MEMORY;
   NS_ADDREF(*aResult);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetParent(nsISHEntry ** aResult)
 {
   NS_ENSURE_ARG_POINTER(aResult);
-  *aResult = mParent;
+  *aResult = mShared->mParent;
   NS_IF_ADDREF(*aResult);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetParent(nsISHEntry * aParent)
 {
   /* parent not Addrefed on purpose to avoid cyclic reference
    * Null parent is OK
    *
    * XXX this method should not be scriptable if this is the case!!
    */
-  mParent = aParent;
+  mShared->mParent = aParent;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetWindowState(nsISupports *aState)
 {
-  mWindowState = aState;
+  mShared->mWindowState = aState;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetWindowState(nsISupports **aState)
 {
-  NS_IF_ADDREF(*aState = mWindowState);
+  NS_IF_ADDREF(*aState = mShared->mWindowState);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetViewerBounds(const nsIntRect &aBounds)
 {
-  mViewerBounds = aBounds;
+  mShared->mViewerBounds = aBounds;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetViewerBounds(nsIntRect &aBounds)
 {
-  aBounds = mViewerBounds;
+  aBounds = mShared->mViewerBounds;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetOwner(nsISupports **aOwner)
 {
-  NS_IF_ADDREF(*aOwner = mOwner);
+  NS_IF_ADDREF(*aOwner = mShared->mOwner);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetOwner(nsISupports *aOwner)
 {
-  mOwner = aOwner;
+  mShared->mOwner = aOwner;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetBFCacheEntry(nsIBFCacheEntry **aEntry)
+{
+  NS_ENSURE_ARG_POINTER(aEntry);
+  NS_IF_ADDREF(*aEntry = mShared);
+  return NS_OK;
+}
+
+bool
+nsSHEntry::HasBFCacheEntry(nsIBFCacheEntry *aEntry)
+{
+  return static_cast<nsIBFCacheEntry*>(mShared) == aEntry;
+}
+
+NS_IMETHODIMP
+nsSHEntry::AdoptBFCacheEntry(nsISHEntry *aEntry)
+{
+  nsCOMPtr<nsISHEntryInternal> shEntry = do_QueryInterface(aEntry);
+  NS_ENSURE_STATE(shEntry);
+
+  nsSHEntryShared *shared = shEntry->GetSharedState();
+  NS_ENSURE_STATE(shared);
+
+  mShared = shared;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SharesDocumentWith(nsISHEntry *aEntry, bool *aOut)
+{
+  NS_ENSURE_ARG_POINTER(aOut);
+
+  nsCOMPtr<nsISHEntryInternal> internal = do_QueryInterface(aEntry); 
+  NS_ENSURE_STATE(internal);
+
+  *aOut = mShared == internal->GetSharedState();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::AbandonBFCacheEntry()
+{
+  mShared = nsSHEntryShared::Duplicate(mShared);
   return NS_OK;
 }
 
 //*****************************************************************************
 //    nsSHEntry: nsISHContainer
 //*****************************************************************************
 
 NS_IMETHODIMP 
@@ -748,266 +671,87 @@ nsSHEntry::GetChildAt(PRInt32 aIndex, ns
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::AddChildShell(nsIDocShellTreeItem *aShell)
 {
   NS_ASSERTION(aShell, "Null child shell added to history entry");
-  mChildShells.AppendObject(aShell);
+  mShared->mChildShells.AppendObject(aShell);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::ChildShellAt(PRInt32 aIndex, nsIDocShellTreeItem **aShell)
 {
-  NS_IF_ADDREF(*aShell = mChildShells.SafeObjectAt(aIndex));
+  NS_IF_ADDREF(*aShell = mShared->mChildShells.SafeObjectAt(aIndex));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::ClearChildShells()
 {
-  mChildShells.Clear();
+  mShared->mChildShells.Clear();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetRefreshURIList(nsISupportsArray **aList)
 {
-  NS_IF_ADDREF(*aList = mRefreshURIList);
+  NS_IF_ADDREF(*aList = mShared->mRefreshURIList);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetRefreshURIList(nsISupportsArray *aList)
 {
-  mRefreshURIList = aList;
+  mShared->mRefreshURIList = aList;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SyncPresentationState()
 {
-  if (mContentViewer && mWindowState) {
-    // If we have a content viewer and a window state, we should be ok.
-    return NS_OK;
-  }
-
-  DropPresentationState();
-
-  return NS_OK;
-}
-
-void
-nsSHEntry::DropPresentationState()
-{
-  nsRefPtr<nsSHEntry> kungFuDeathGrip = this;
-
-  if (mDocument) {
-    mDocument->SetBFCacheEntry(nsnull);
-    mDocument->RemoveMutationObserver(this);
-    mDocument = nsnull;
-  }
-  if (mContentViewer)
-    mContentViewer->ClearHistoryEntry();
-
-  StopTrackingEntry(this);
-  mContentViewer = nsnull;
-  mSticky = true;
-  mWindowState = nsnull;
-  mViewerBounds.SetRect(0, 0, 0, 0);
-  mChildShells.Clear();
-  mRefreshURIList = nsnull;
-  mEditorData = nsnull;
-}
-
-void
-nsSHEntry::Expire()
-{
-  // This entry has timed out. If we still have a content viewer, we need to
-  // get it evicted.
-  if (!mContentViewer)
-    return;
-  nsCOMPtr<nsISupports> container;
-  mContentViewer->GetContainer(getter_AddRefs(container));
-  nsCOMPtr<nsIDocShellTreeItem> treeItem = do_QueryInterface(container);
-  if (!treeItem)
-    return;
-  // We need to find the root DocShell since only that object has an
-  // SHistory and we need the SHistory to evict content viewers
-  nsCOMPtr<nsIDocShellTreeItem> root;
-  treeItem->GetSameTypeRootTreeItem(getter_AddRefs(root));
-  nsCOMPtr<nsIWebNavigation> webNav = do_QueryInterface(root);
-  nsCOMPtr<nsISHistory> history;
-  webNav->GetSessionHistory(getter_AddRefs(history));
-  nsCOMPtr<nsISHistoryInternal> historyInt = do_QueryInterface(history);
-  if (!historyInt)
-    return;
-  historyInt->EvictExpiredContentViewerForEntry(this);
-}
-
-//*****************************************************************************
-//    nsSHEntry: nsIMutationObserver
-//*****************************************************************************
-
-void
-nsSHEntry::NodeWillBeDestroyed(const nsINode* aNode)
-{
-  NS_NOTREACHED("Document destroyed while we're holding a strong ref to it");
-}
-
-void
-nsSHEntry::CharacterDataWillChange(nsIDocument* aDocument,
-                                   nsIContent* aContent,
-                                   CharacterDataChangeInfo* aInfo)
-{
+  return mShared->SyncPresentationState();
 }
 
 void
-nsSHEntry::CharacterDataChanged(nsIDocument* aDocument,
-                                nsIContent* aContent,
-                                CharacterDataChangeInfo* aInfo)
-{
-  RemoveFromBFCacheAsync();
-}
-
-void
-nsSHEntry::AttributeWillChange(nsIDocument* aDocument,
-                               dom::Element* aContent,
-                               PRInt32 aNameSpaceID,
-                               nsIAtom* aAttribute,
-                               PRInt32 aModType)
-{
-}
-
-void
-nsSHEntry::AttributeChanged(nsIDocument* aDocument,
-                            dom::Element* aElement,
-                            PRInt32 aNameSpaceID,
-                            nsIAtom* aAttribute,
-                            PRInt32 aModType)
-{
-  RemoveFromBFCacheAsync();
-}
-
-void
-nsSHEntry::ContentAppended(nsIDocument* aDocument,
-                           nsIContent* aContainer,
-                           nsIContent* aFirstNewContent,
-                           PRInt32 /* unused */)
-{
-  RemoveFromBFCacheAsync();
-}
-
-void
-nsSHEntry::ContentInserted(nsIDocument* aDocument,
-                           nsIContent* aContainer,
-                           nsIContent* aChild,
-                           PRInt32 /* unused */)
-{
-  RemoveFromBFCacheAsync();
-}
-
-void
-nsSHEntry::ContentRemoved(nsIDocument* aDocument,
-                          nsIContent* aContainer,
-                          nsIContent* aChild,
-                          PRInt32 aIndexInContainer,
-                          nsIContent* aPreviousSibling)
-{
-  RemoveFromBFCacheAsync();
-}
-
-void
-nsSHEntry::ParentChainChanged(nsIContent *aContent)
-{
-}
-
-class DestroyViewerEvent : public nsRunnable
-{
-public:
-  DestroyViewerEvent(nsIContentViewer* aViewer, nsIDocument* aDocument)
-    : mViewer(aViewer),
-      mDocument(aDocument)
-  {}
-
-  NS_IMETHOD Run()
-  {
-    if (mViewer)
-      mViewer->Destroy();
-    return NS_OK;
-  }
-
-  nsCOMPtr<nsIContentViewer> mViewer;
-  nsCOMPtr<nsIDocument> mDocument;
-};
-
-void
 nsSHEntry::RemoveFromBFCacheSync()
 {
-  NS_ASSERTION(mContentViewer && mDocument,
-               "we're not in the bfcache!");
-
-  nsCOMPtr<nsIContentViewer> viewer = mContentViewer;
-  DropPresentationState();
-
-  // Warning! The call to DropPresentationState could have dropped the last
-  // reference to this nsSHEntry, so no accessing members beyond here.
-
-  if (viewer) {
-    viewer->Destroy();
-  }
+  mShared->RemoveFromBFCacheSync();
 }
 
 void
 nsSHEntry::RemoveFromBFCacheAsync()
 {
-  NS_ASSERTION(mContentViewer && mDocument,
-               "we're not in the bfcache!");
-
-  // Release the reference to the contentviewer asynchronously so that the
-  // document doesn't get nuked mid-mutation.
-
-  nsCOMPtr<nsIRunnable> evt =
-      new DestroyViewerEvent(mContentViewer, mDocument);
-  nsresult rv = NS_DispatchToCurrentThread(evt);
-  if (NS_FAILED(rv)) {
-    NS_WARNING("failed to dispatch DestroyViewerEvent");
-  }
-  else {
-    // Drop presentation. Also ensures that we don't post more then one
-    // PLEvent. Only do this if we succeeded in posting the event since
-    // otherwise the document could be torn down mid mutation causing crashes.
-    DropPresentationState();
-  }
-  // Warning! The call to DropPresentationState could have dropped the last
-  // reference to this nsSHEntry, so no accessing members beyond here.
+  mShared->RemoveFromBFCacheAsync();
 }
 
 nsDocShellEditorData*
 nsSHEntry::ForgetEditorData()
 {
-  return mEditorData.forget();
+  // XXX jlebar Check how this is used.
+  return mShared->mEditorData.forget();
 }
 
 void
 nsSHEntry::SetEditorData(nsDocShellEditorData* aData)
 {
-  NS_ASSERTION(!(aData && mEditorData),
+  NS_ASSERTION(!(aData && mShared->mEditorData),
                "We're going to overwrite an owning ref!");
-  if (mEditorData != aData)
-    mEditorData = aData;
+  if (mShared->mEditorData != aData) {
+    mShared->mEditorData = aData;
+  }
 }
 
 bool
 nsSHEntry::HasDetachedEditor()
 {
-  return mEditorData != nsnull;
+  return mShared->mEditorData != nsnull;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetStateData(nsIStructuredCloneContainer **aContainer)
 {
   NS_ENSURE_ARG_POINTER(aContainer);
   NS_IF_ADDREF(*aContainer = mStateData);
   return NS_OK;
@@ -1018,17 +762,17 @@ nsSHEntry::SetStateData(nsIStructuredClo
 {
   mStateData = aContainer;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::IsDynamicallyAdded(bool* aAdded)
 {
-  *aAdded = mDynamicallyCreated;
+  *aAdded = mShared->mDynamicallyCreated;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::HasDynamicallyAddedChild(bool* aAdded)
 {
   *aAdded = false;
   for (PRInt32 i = 0; i < mChildren.Count(); ++i) {
@@ -1041,34 +785,33 @@ nsSHEntry::HasDynamicallyAddedChild(bool
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetDocshellID(PRUint64* aID)
 {
-  *aID = mDocShellID;
+  *aID = mShared->mDocShellID;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetDocshellID(PRUint64 aID)
 {
-  mDocShellID = aID;
+  mShared->mDocShellID = aID;
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsSHEntry::GetLastTouched(PRUint32 *aLastTouched)
 {
-  *aLastTouched = mLastTouched;
+  *aLastTouched = mShared->mLastTouched;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetLastTouched(PRUint32 aLastTouched)
 {
-  mLastTouched = aLastTouched;
+  mShared->mLastTouched = aLastTouched;
   return NS_OK;
 }
-
--- a/docshell/shistory/src/nsSHEntry.h
+++ b/docshell/shistory/src/nsSHEntry.h
@@ -37,90 +37,62 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef nsSHEntry_h
 #define nsSHEntry_h
 
 // Helper Classes
 #include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
 #include "nsCOMArray.h"
 #include "nsString.h"
-#include "nsAutoPtr.h"
 
 // Interfaces needed
-#include "nsIContentViewer.h"
 #include "nsIInputStream.h"
-#include "nsILayoutHistoryState.h"
 #include "nsISHEntry.h"
 #include "nsISHContainer.h"
 #include "nsIURI.h"
-#include "nsIEnumerator.h"
 #include "nsIHistoryEntry.h"
-#include "nsRect.h"
-#include "nsISupportsArray.h"
-#include "nsIMutationObserver.h"
-#include "nsExpirationTracker.h"
-#include "nsDocShellEditorData.h"
+
+class nsSHEntryShared;
 
 class nsSHEntry : public nsISHEntry,
                   public nsISHContainer,
-                  public nsIMutationObserver,
                   public nsISHEntryInternal
 {
 public: 
   nsSHEntry();
   nsSHEntry(const nsSHEntry &other);
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIHISTORYENTRY
   NS_DECL_NSISHENTRY
   NS_DECL_NSISHENTRYINTERNAL
   NS_DECL_NSISHCONTAINER
-  NS_DECL_NSIMUTATIONOBSERVER
 
   void DropPresentationState();
 
-  void Expire();
-  
-  nsExpirationState *GetExpirationState() { return &mExpirationState; }
-  
   static nsresult Startup();
   static void Shutdown();
   
 private:
   ~nsSHEntry();
 
-  nsCOMPtr<nsIURI>                mURI;
-  nsCOMPtr<nsIURI>                mReferrerURI;
-  nsCOMPtr<nsIContentViewer>      mContentViewer;
-  nsCOMPtr<nsIDocument>           mDocument; // document currently being observed
-  nsString                        mTitle;
-  nsCOMPtr<nsIInputStream>        mPostData;
-  nsCOMPtr<nsILayoutHistoryState> mLayoutHistoryState;
-  nsCOMArray<nsISHEntry>          mChildren;
-  PRUint32                        mLoadType;
-  PRUint32                        mID;
-  PRInt64                         mDocIdentifier;
-  PRInt32                         mScrollPositionX;
-  PRInt32                         mScrollPositionY;
-  PRPackedBool                    mURIWasModified;
-  PRPackedBool                    mIsFrameNavigation;
-  PRPackedBool                    mSaveLayoutState;
-  PRPackedBool                    mExpired;
-  PRPackedBool                    mSticky;
-  PRPackedBool                    mDynamicallyCreated;
-  nsCString                       mContentType;
-  nsCOMPtr<nsISupports>           mCacheKey;
-  nsISHEntry *                    mParent;  // weak reference
-  nsCOMPtr<nsISupports>           mWindowState;
-  nsIntRect                       mViewerBounds;
-  nsCOMArray<nsIDocShellTreeItem> mChildShells;
-  nsCOMPtr<nsISupportsArray>      mRefreshURIList;
-  nsCOMPtr<nsISupports>           mOwner;
-  nsExpirationState               mExpirationState;
-  nsAutoPtr<nsDocShellEditorData> mEditorData;
-  PRUint64                        mDocShellID;
-  PRUint32                        mLastTouched;
+  // We share the state in here with other SHEntries which correspond to the
+  // same document.
+  nsRefPtr<nsSHEntryShared> mShared;
+
+  // See nsSHEntry.idl for comments on these members.
+  nsCOMPtr<nsIURI>         mURI;
+  nsCOMPtr<nsIURI>         mReferrerURI;
+  nsString                 mTitle;
+  nsCOMPtr<nsIInputStream> mPostData;
+  PRUint32                 mLoadType;
+  PRUint32                 mID;
+  PRInt32                  mScrollPositionX;
+  PRInt32                  mScrollPositionY;
+  nsCOMArray<nsISHEntry>   mChildren;
+  bool                     mURIWasModified;
   nsCOMPtr<nsIStructuredCloneContainer> mStateData;
 };
 
 #endif /* nsSHEntry_h */
new file mode 100644
--- /dev/null
+++ b/docshell/shistory/src/nsSHEntryShared.cpp
@@ -0,0 +1,400 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * 
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Justin Lebar <justin.lebar@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsSHEntryShared.h"
+#include "nsISHistory.h"
+#include "nsISHistoryInternal.h"
+#include "nsIDocument.h"
+#include "nsIWebNavigation.h"
+#include "nsIContentViewer.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsISupportsArray.h"
+#include "nsDocShellEditorData.h"
+#include "nsThreadUtils.h"
+#include "nsILayoutHistoryState.h"
+#include "prprf.h"
+
+namespace dom = mozilla::dom;
+
+namespace {
+
+PRUint64 gSHEntrySharedID = 0;
+
+} // anonymous namespace
+
+// Hardcode this to time out unused content viewers after 30 minutes
+// XXX jlebar shouldn't this be a pref?
+#define CONTENT_VIEWER_TIMEOUT_SECONDS (30*60)
+
+typedef nsExpirationTracker<nsSHEntryShared, 3> HistoryTrackerBase;
+class HistoryTracker : public HistoryTrackerBase {
+public:
+  // Expire cached contentviewers after 20-30 minutes in the cache.
+  HistoryTracker() 
+    : HistoryTrackerBase(1000 * CONTENT_VIEWER_TIMEOUT_SECONDS / 2)
+  {
+  }
+  
+protected:
+  virtual void NotifyExpired(nsSHEntryShared *aObj) {
+    RemoveObject(aObj);
+    aObj->Expire();
+  }
+};
+
+static HistoryTracker *gHistoryTracker = nsnull;
+
+void
+nsSHEntryShared::Startup()
+{
+  gHistoryTracker = new HistoryTracker();
+}
+
+void
+nsSHEntryShared::Shutdown()
+{
+  delete gHistoryTracker;
+  gHistoryTracker = nsnull;
+}
+
+nsSHEntryShared::nsSHEntryShared()
+  : mDocShellID(0)
+  , mParent(nsnull)
+  , mIsFrameNavigation(false)
+  , mSaveLayoutState(true)
+  , mSticky(true)
+  , mDynamicallyCreated(false)
+  , mLastTouched(0)
+  , mID(gSHEntrySharedID++)
+  , mExpired(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 = true;
+  mWindowState = nsnull;
+  mViewerBounds.SetRect(0, 0, 0, 0);
+  mChildShells.Clear();
+  mRefreshURIList = nsnull;
+  mEditorData = nsnull;
+}
+
+void
+nsSHEntryShared::Expire()
+{
+  // This entry has timed out. If we still have a content viewer, we need to
+  // evict it.
+  if (!mContentViewer) {
+    return;
+  }
+  nsCOMPtr<nsISupports> container;
+  mContentViewer->GetContainer(getter_AddRefs(container));
+  nsCOMPtr<nsIDocShellTreeItem> treeItem = do_QueryInterface(container);
+  if (!treeItem) {
+    return;
+  }
+  // We need to find the root DocShell since only that object has an
+  // SHistory and we need the SHistory to evict content viewers
+  nsCOMPtr<nsIDocShellTreeItem> root;
+  treeItem->GetSameTypeRootTreeItem(getter_AddRefs(root));
+  nsCOMPtr<nsIWebNavigation> webNav = do_QueryInterface(root);
+  nsCOMPtr<nsISHistory> history;
+  webNav->GetSessionHistory(getter_AddRefs(history));
+  nsCOMPtr<nsISHistoryInternal> historyInt = do_QueryInterface(history);
+  if (!historyInt) {
+    return;
+  }
+  historyInt->EvictExpiredContentViewerForEntry(this);
+}
+
+nsresult
+nsSHEntryShared::SetContentViewer(nsIContentViewer *aViewer)
+{
+  NS_PRECONDITION(!aViewer || !mContentViewer,
+                  "SHEntryShared already contains viewer");
+
+  if (mContentViewer || !aViewer) {
+    DropPresentationState();
+  }
+
+  mContentViewer = aViewer;
+
+  if (mContentViewer) {
+    gHistoryTracker->AddObject(this);
+
+    nsCOMPtr<nsIDOMDocument> domDoc;
+    mContentViewer->GetDOMDocument(getter_AddRefs(domDoc));
+    // Store observed document in strong pointer in case it is removed from
+    // the contentviewer
+    mDocument = do_QueryInterface(domDoc);
+    if (mDocument) {
+      mDocument->SetBFCacheEntry(this);
+      mDocument->AddMutationObserver(this);
+    }
+  }
+
+  return NS_OK;
+}
+
+nsresult
+nsSHEntryShared::RemoveFromBFCacheSync()
+{
+  NS_ASSERTION(mContentViewer && mDocument,
+               "we're not in the bfcache!");
+
+  nsCOMPtr<nsIContentViewer> viewer = mContentViewer;
+  DropPresentationState();
+
+  // Warning! The call to DropPresentationState could have dropped the last
+  // reference to this object, so don't access members beyond here.
+
+  if (viewer) {
+    viewer->Destroy();
+  }
+
+  return NS_OK;
+}
+
+class DestroyViewerEvent : public nsRunnable
+{
+public:
+  DestroyViewerEvent(nsIContentViewer* aViewer, nsIDocument* aDocument)
+    : mViewer(aViewer),
+      mDocument(aDocument)
+  {}
+
+  NS_IMETHOD Run()
+  {
+    if (mViewer) {
+      mViewer->Destroy();
+    }
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIContentViewer> mViewer;
+  nsCOMPtr<nsIDocument> mDocument;
+};
+
+nsresult
+nsSHEntryShared::RemoveFromBFCacheAsync()
+{
+  NS_ASSERTION(mContentViewer && mDocument,
+               "we're not in the bfcache!");
+
+  // Release the reference to the contentviewer asynchronously so that the
+  // document doesn't get nuked mid-mutation.
+
+  nsCOMPtr<nsIRunnable> evt =
+    new DestroyViewerEvent(mContentViewer, mDocument);
+  nsresult rv = NS_DispatchToCurrentThread(evt);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("failed to dispatch DestroyViewerEvent");
+  } else {
+    // Drop presentation. Only do this if we succeeded in posting the event
+    // since otherwise the document could be torn down mid-mutation, causing
+    // crashes.
+    DropPresentationState();
+  }
+
+  // Careful! The call to DropPresentationState could have dropped the last
+  // reference to this nsSHEntryShared, so don't access members beyond here.
+
+  return NS_OK;
+}
+
+nsresult
+nsSHEntryShared::GetID(PRUint64 *aID)
+{
+  *aID = mID;
+  return NS_OK;
+}
+
+//*****************************************************************************
+//    nsSHEntryShared: nsIMutationObserver
+//*****************************************************************************
+
+void
+nsSHEntryShared::NodeWillBeDestroyed(const nsINode* aNode)
+{
+  NS_NOTREACHED("Document destroyed while we're holding a strong ref to it");
+}
+
+void
+nsSHEntryShared::CharacterDataWillChange(nsIDocument* aDocument,
+                                         nsIContent* aContent,
+                                         CharacterDataChangeInfo* aInfo)
+{
+}
+
+void
+nsSHEntryShared::CharacterDataChanged(nsIDocument* aDocument,
+                                      nsIContent* aContent,
+                                      CharacterDataChangeInfo* aInfo)
+{
+  RemoveFromBFCacheAsync();
+}
+
+void
+nsSHEntryShared::AttributeWillChange(nsIDocument* aDocument,
+                                     dom::Element* aContent,
+                                     PRInt32 aNameSpaceID,
+                                     nsIAtom* aAttribute,
+                                     PRInt32 aModType)
+{
+}
+
+void
+nsSHEntryShared::AttributeChanged(nsIDocument* aDocument,
+                                  dom::Element* aElement,
+                                  PRInt32 aNameSpaceID,
+                                  nsIAtom* aAttribute,
+                                  PRInt32 aModType)
+{
+  RemoveFromBFCacheAsync();
+}
+
+void
+nsSHEntryShared::ContentAppended(nsIDocument* aDocument,
+                                 nsIContent* aContainer,
+                                 nsIContent* aFirstNewContent,
+                                 PRInt32 /* unused */)
+{
+  RemoveFromBFCacheAsync();
+}
+
+void
+nsSHEntryShared::ContentInserted(nsIDocument* aDocument,
+                                 nsIContent* aContainer,
+                                 nsIContent* aChild,
+                                 PRInt32 /* unused */)
+{
+  RemoveFromBFCacheAsync();
+}
+
+void
+nsSHEntryShared::ContentRemoved(nsIDocument* aDocument,
+                                nsIContent* aContainer,
+                                nsIContent* aChild,
+                                PRInt32 aIndexInContainer,
+                                nsIContent* aPreviousSibling)
+{
+  RemoveFromBFCacheAsync();
+}
+
+void
+nsSHEntryShared::ParentChainChanged(nsIContent *aContent)
+{
+}
new file mode 100644
--- /dev/null
+++ b/docshell/shistory/src/nsSHEntryShared.h
@@ -0,0 +1,124 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * 
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Justin Lebar <justin.lebar@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef nsSHEntryShared_h__
+#define nsSHEntryShared_h__
+
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsCOMArray.h"
+#include "nsIBFCacheEntry.h"
+#include "nsIMutationObserver.h"
+#include "nsExpirationTracker.h"
+#include "nsRect.h"
+
+class nsSHEntry;
+class nsISHEntry;
+class nsIDocument;
+class nsIContentViewer;
+class nsIDocShellTreeItem;
+class nsILayoutHistoryState;
+class nsISupportsArray;
+class nsDocShellEditorData;
+
+// A document may have multiple SHEntries, either due to hash navigations or
+// calls to history.pushState.  SHEntries corresponding to the same document
+// share many members; in particular, they share state related to the
+// back/forward cache.
+//
+// nsSHEntryShared is the vehicle for this sharing.
+class nsSHEntryShared : public nsIBFCacheEntry,
+                        public nsIMutationObserver
+{
+  public:
+    static void Startup();
+    static void Shutdown();
+
+    nsSHEntryShared();
+    ~nsSHEntryShared();
+
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIMUTATIONOBSERVER
+    NS_DECL_NSIBFCACHEENTRY
+
+  private:
+    friend class nsSHEntry;
+
+    friend class HistoryTracker;
+    friend class nsExpirationTracker<nsSHEntryShared, 3>;
+    nsExpirationState *GetExpirationState() { return &mExpirationState; }
+
+    static already_AddRefed<nsSHEntryShared> Duplicate(nsSHEntryShared *aEntry);
+
+    void RemoveFromExpirationTracker();
+    void Expire();
+    nsresult SyncPresentationState();
+    void DropPresentationState();
+
+    nsresult SetContentViewer(nsIContentViewer *aViewer);
+
+    // See nsISHEntry.idl for an explanation of these members.
+
+    // These members are copied by nsSHEntryShared::Duplicate().  If you add a
+    // member here, be sure to update the Duplicate() implementation.
+    PRUint64                        mDocShellID;
+    nsCOMArray<nsIDocShellTreeItem> mChildShells;
+    nsCOMPtr<nsISupports>           mOwner;
+    nsISHEntry*                     mParent;
+    nsCString                       mContentType;
+    bool                            mIsFrameNavigation;
+    bool                            mSaveLayoutState;
+    bool                            mSticky;
+    bool                            mDynamicallyCreated;
+    nsCOMPtr<nsISupports>           mCacheKey;
+    PRUint32                        mLastTouched;
+
+    // These members aren't copied by nsSHEntryShared::Duplicate() because
+    // they're specific to a particular content viewer.
+    PRUint64                        mID;
+    nsCOMPtr<nsIContentViewer>      mContentViewer;
+    nsCOMPtr<nsIDocument>           mDocument;
+    nsCOMPtr<nsILayoutHistoryState> mLayoutHistoryState;
+    bool                            mExpired;
+    nsCOMPtr<nsISupports>           mWindowState;
+    nsIntRect                       mViewerBounds;
+    nsCOMPtr<nsISupportsArray>      mRefreshURIList;
+    nsExpirationState               mExpirationState;
+    nsAutoPtr<nsDocShellEditorData> mEditorData;
+};
+
+#endif
--- a/docshell/shistory/src/nsSHistory.cpp
+++ b/docshell/shistory/src/nsSHistory.cpp
@@ -67,44 +67,72 @@
 // 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 bool gOptimizeEviction = 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
 } ;
 
 //*****************************************************************************
@@ -129,25 +157,70 @@ 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::EvictGlobalContentViewer();
+    nsSHistory::GloballyEvictContentViewers();
   } else if (!strcmp(aTopic, NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID) ||
              !strcmp(aTopic, "memory-pressure")) {
-    nsSHistory::EvictAllContentViewersGlobally();
+    nsSHistory::GloballyEvictAllContentViewers();
   }
 
   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);
@@ -235,17 +308,16 @@ 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
@@ -684,31 +756,38 @@ 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::EvictContentViewers(PRInt32 aPreviousIndex, PRInt32 aIndex)
+nsSHistory::EvictOutOfRangeContentViewers(PRInt32 aIndex)
 {
   // Check our per SHistory object limit in the currently navigated SHistory
-  EvictWindowContentViewers(aPreviousIndex, aIndex);
+  EvictOutOfRangeWindowContentViewers(aIndex);
   // Check our total limit across all SHistory objects
-  EvictGlobalContentViewer();
+  GloballyEvictContentViewers();
   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.
-  EvictContentViewersInRange(0, mLength);
+  nsCOMPtr<nsISHTransaction> trans = mListRoot;
+  while (trans) {
+    EvictContentViewerForTransaction(trans);
+
+    nsISHTransaction *temp = trans;
+    temp->GetNext(getter_AddRefs(trans));
+  }
+
   return NS_OK;
 }
 
 
 
 //*****************************************************************************
 //    nsSHistory: nsIWebNavigation
 //*****************************************************************************
@@ -830,302 +909,287 @@ nsSHistory::ReloadCurrentEntry()
   }
   if (!canNavigate)
     return NS_OK;
 
   return LoadEntry(mIndex, nsIDocShellLoadInfo::loadHistory, HIST_CMD_RELOAD);
 }
 
 void
-nsSHistory::EvictWindowContentViewers(PRInt32 aFromIndex, PRInt32 aToIndex)
+nsSHistory::EvictOutOfRangeWindowContentViewers(PRInt32 aIndex)
 {
-  // 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
+  // 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:
+  //
+  //   A A A A B
+  //     +     *
   //
-  // 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.
+  // where the letters are content viewers and + and * denote the beginning and
+  // end of the range aIndex +/- gHistoryMaxViewers.
+  //
+  // Although one copy of the content viewer A exists outside the range, we
+  // don't want to evict A, because it has other copies in range!
+  //
+  // We therefore adjust our eviction strategy to read:
+  //
+  //   Evict each content viewer outside the range aIndex -/+
+  //   gHistoryMaxViewers, unless that content viewer also appears within the
+  //   range.
+  //
+  // (Note that it's entirely legal to have two copies of one content viewer
+  // separated by a different content viewer -- call pushState twice, go back
+  // once, and refresh -- so we can't rely on identical viewers only appearing
+  // adjacent to one another.)
 
-  // This can happen on the first load of a page in a particular window
-  if (aFromIndex < 0 || aToIndex < 0) {
+  if (aIndex < 0) {
     return;
   }
-  NS_ASSERTION(aFromIndex < mLength, "aFromIndex is out of range");
-  NS_ASSERTION(aToIndex < mLength, "aToIndex is out of range");
-  if (aFromIndex >= mLength || aToIndex >= mLength) {
+  NS_ASSERTION(aIndex < mLength, "aIndex is out of range");
+  if (aIndex >= mLength) {
     return;
   }
 
-  // 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);
-  }
+  // 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)); 
 
-#ifdef DEBUG
+  // The content viewers in range aIndex -/+ gHistoryMaxViewers will not be
+  // evicted.  Collect a set of them so we don't accidentally evict one of them
+  // if it appears outside this range.
+  nsCOMArray<nsIContentViewer> safeViewers;
   nsCOMPtr<nsISHTransaction> trans;
-  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");
-    }
-
+  GetTransactionAtIndex(startSafeIndex, getter_AddRefs(trans));
+  for (PRUint32 i = startSafeIndex; trans && i <= endSafeIndex; i++) {
+    nsCOMPtr<nsIContentViewer> viewer = GetContentViewerForTransaction(trans);
+    safeViewers.AppendObject(viewer);
     nsISHTransaction *temp = trans;
     temp->GetNext(getter_AddRefs(trans));
   }
-#endif
 
-  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();
+  // 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);
     }
 
     nsISHTransaction *temp = trans;
     temp->GetNext(getter_AddRefs(trans));
   }
 }
 
-// static
-void
-nsSHistory::EvictGlobalContentViewer()
+namespace {
+
+class TransactionAndDistance
 {
-  // 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.
-  bool shouldTryEviction = 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));
+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
 
-      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));
+//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;
 
-        PRUint32 entryLastTouched = 0;
-        if (gOptimizeEviction) {
-          nsCOMPtr<nsISHEntryInternal> entryInternal = do_QueryInterface(entry);
-          if (entryInternal) {
-            // Find when this entry was last activated
-            entryInternal->GetLastTouched(&entryLastTouched);
+    // 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 = true;
+            break;
           }
         }
 
-#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());
+        // 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);
         }
-#endif
-        
-        // This SHEntry has a ContentViewer, so check how far away it is from
-        // the currently used SHEntry within this SHistory object
-        if (viewer) {
-          PRInt32 distance = NS_ABS(shist->mIndex - i);
-          
-#ifdef DEBUG_PAGE_CACHE
-          printf("Has a cached content viewer: %s\n", spec.get());
-          printf("mIndex: %d i: %d\n", shist->mIndex, i);
-#endif
-          totalContentViewers++;
+      }
 
-          // If this entry is further away from focus than any previously found
-          // or at the same distance but it is longer time since it was activated
-          // then take this entry as the new candiate for eviction
-          if (distance > distanceFromFocus || (distance == distanceFromFocus && candidateLastTouched > entryLastTouched)) {
-
-#ifdef DEBUG_PAGE_CACHE
-            printf("Choosing as new eviction candidate: %s\n", spec.get());
-#endif
-            candidateLastTouched = entryLastTouched;
-            distanceFromFocus = distance;
-            evictFromSHE = ownerEntry;
-            evictViewer = viewer;
-          }
-        }
-        nsISHTransaction* temp = trans;
-        temp->GetNext(getter_AddRefs(trans));
-      }
-      shist = static_cast<nsSHistory*>(PR_NEXT_LINK(shist));
+      nsISHTransaction *temp = trans;
+      temp->GetNext(getter_AddRefs(trans));
     }
 
-#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'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));
+  }
 
-    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
+  // 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;
+  }
 
-      // Drop the presentation state before destroying the viewer, so that
-      // document teardown is able to correctly persist the state.
-      evictFromSHE->SetContentViewer(nsnull);
-      evictFromSHE->SyncPresentationState();
-      evictViewer->Destroy();
+  // If we 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();
 
-      // 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 = false;
-      }
-    } else {
-      // couldn't find a content viewer to evict, so we are done
-      shouldTryEviction = false;
-    }
-  }  // while shouldTryEviction
+  for (PRInt32 i = transactions.Length() - 1;
+       i >= sHistoryMaxTotalViewers; --i) {
+
+    EvictContentViewerForTransaction(transactions[i].mTransaction);
+
+  }
 }
 
-NS_IMETHODIMP
-nsSHistory::EvictExpiredContentViewerForEntry(nsISHEntry *aEntry)
+nsresult
+nsSHistory::EvictExpiredContentViewerForEntry(nsIBFCacheEntry *aEntry)
 {
   PRInt32 startIndex = NS_MAX(0, mIndex - gHistoryMaxViewers);
   PRInt32 endIndex = NS_MIN(mLength - 1,
                             mIndex + gHistoryMaxViewers);
   nsCOMPtr<nsISHTransaction> trans;
   GetTransactionAtIndex(startIndex, getter_AddRefs(trans));
 
   PRInt32 i;
   for (i = startIndex; trans && i <= endIndex; ++i) {
     nsCOMPtr<nsISHEntry> entry;
     trans->GetSHEntry(getter_AddRefs(entry));
-    if (entry == aEntry)
+
+    // Does entry have the same BFCacheEntry as the argument to this method?
+    if (entry->HasBFCacheEntry(aEntry)) {
       break;
+    }
 
     nsISHTransaction *temp = trans;
     temp->GetNext(getter_AddRefs(trans));
   }
   if (i > endIndex)
     return NS_OK;
   
-  NS_ASSERTION(i != mIndex, "How did the current session entry expire?");
-  if (i == mIndex)
+  if (i == mIndex) {
+    NS_WARNING("How did the current SHEntry expire?");
     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::EvictAllContentViewersGlobally()
+nsSHistory::GloballyEvictAllContentViewers()
 {
   PRInt32 maxViewers = sHistoryMaxTotalViewers;
   sHistoryMaxTotalViewers = 0;
-  EvictGlobalContentViewer();
+  GloballyEvictContentViewers();
   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,22 +96,21 @@ protected:
    nsresult InitiateLoad(nsISHEntry * aFrameEntry, nsIDocShell * aFrameDS, long aLoadType);
 
    NS_IMETHOD LoadEntry(PRInt32 aIndex, long aLoadType, PRUint32 histCmd);
 
 #ifdef DEBUG
    nsresult PrintHistory();
 #endif
 
-  // 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();
+  // 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();
 
   // 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,16 +114,17 @@ include $(topsrcdir)/config/rules.mk
 		test_bug570341.html \
 		bug570341_recordevents.html \
 		test_bug668513.html \
 		bug668513_redirect.html \
 		bug668513_redirect.html^headers^ \
 		test_bug669671.html \
 		file_bug669671.sjs \
 		test_bug675587.html \
+		test_bfcache_plus_hash.html \
 		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,16 +45,19 @@
         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);
     }
@@ -91,16 +94,33 @@
     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);
     }
 
new file mode 100644
--- /dev/null
+++ b/docshell/test/test_bfcache_plus_hash.html
@@ -0,0 +1,120 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=646641
+-->
+<head>
+  <title>Test for Bug 646641</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=646641">Mozilla Bug 646641</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script type="application/javascript;version=1.7">
+
+/** Test for Bug 646641 **/
+
+/* 
+ * In a popup (because navigating the main frame confuses Mochitest), do the
+ * following:
+ *
+ *  * Call history.pushState().
+ *  * Navigate to a new page.
+ *  * Go back two history entries.
+ *
+ * Check that we go back, we retrieve the document from bfcache.
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+function debug(msg) {
+  // Wrap dump so we can turn debug messages on and off easily.
+  dump(msg + '\n');
+}
+
+var expectedLoadNum = -1;
+function childLoad(n) {
+  if (n == expectedLoadNum) {
+    debug('Got load ' + n);
+    expectedLoadNum = -1;
+
+    // Spin the event loop before calling gGen.next() so the generator runs
+    // outside the onload handler.  This prevents us from encountering all
+    // sorts of docshell quirks.
+    //
+    // (I don't know why I need to wrap gGen.next() in a function, but it
+    // throws an error otherwise.)
+    setTimeout(function() { gGen.next() }, 0);
+  }
+  else {
+    debug('Got unexpected load ' + n);
+    ok(false, 'Got unexpected load ' + n);
+  }
+}
+
+var expectedPageshowNum = -1;
+function childPageshow(n) {
+  if (n == expectedPageshowNum) {
+    debug('Got expected pageshow ' + n);
+    expectedPageshowNum = -1;
+    ok(true, 'Got expected pageshow ' + n);
+    setTimeout(function() { gGen.next() }, 0);
+  }
+  else {
+    debug('Got pageshow ' + n);
+  }
+
+  // Since a pageshow comes along with an onload, don't fail the test if we get
+  // an unexpected pageshow.
+}
+
+function waitForLoad(n) {
+  debug('Waiting for load ' + n);
+  expectedLoadNum = n;
+}
+
+function waitForShow(n) {
+  debug('Waiting for show ' + n);
+  expectedPageshowNum = n;
+}
+
+function test() {
+  var popup = window.open('data:text/html,' +
+                          '<html><body onload="opener.childLoad(1)" ' +
+                                      'onpageshow="opener.childPageshow(1)">' +
+                                'Popup 1' +
+                                '</body></html>');
+  waitForLoad(1);
+  yield;
+
+  popup.history.pushState('', '', '');
+
+  popup.location = 'data:text/html,<html><body onload="opener.childLoad(2)">Popup 2</body></html>';
+  waitForLoad(2);
+  yield;
+
+  // Now go back 2.  The first page should be retrieved from bfcache.
+  popup.history.go(-2);
+  waitForShow(1);
+  yield;
+
+  popup.close();
+  SimpleTest.finish();
+  
+  // Yield once more so we don't throw a StopIteration exception.
+  yield;
+}
+
+var gGen = test();
+gGen.next();
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/indexedDB/IndexedDatabaseManager.cpp
+++ b/dom/indexedDB/IndexedDatabaseManager.cpp
@@ -223,22 +223,19 @@ public:
       nsRefPtr<IDBDatabase>& database = mWaitingDatabases[index];
 
       if (database->IsClosed()) {
         continue;
       }
 
       // First check if the document the IDBDatabase is part of is bfcached
       nsCOMPtr<nsIDocument> ownerDoc = database->GetOwnerDocument();
-      nsISHEntry* shEntry;
-      if (ownerDoc && (shEntry = ownerDoc->GetBFCacheEntry())) {
-        nsCOMPtr<nsISHEntryInternal> sheInternal = do_QueryInterface(shEntry);
-        if (sheInternal) {
-          sheInternal->RemoveFromBFCacheSync();
-        }
+      nsIBFCacheEntry* bfCacheEntry;
+      if (ownerDoc && (bfCacheEntry = ownerDoc->GetBFCacheEntry())) {
+        bfCacheEntry->RemoveFromBFCacheSync();
         NS_ASSERTION(database->IsClosed(),
                      "Kicking doc out of bfcache should have closed database");
         continue;
       }
 
       // Otherwise fire a versionchange event.
       nsRefPtr<nsDOMEvent> event = 
         IDBVersionChangeEvent::Create(mOldVersion, mNewVersion);
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -1954,17 +1954,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->EvictContentViewers(prevIndex, loadedIndex);
+        historyInt->EvictOutOfRangeContentViewers(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,17 +135,16 @@ 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/mobile/chrome/content/bindings/browser.js
+++ b/mobile/chrome/content/bindings/browser.js
@@ -263,43 +263,40 @@ let WebNavigation =  {
         aIdMap.used[id] = true;
       }
       shEntry.ID = id;
     }
 
     if (aEntry.docshellID)
       shEntry.docshellID = aEntry.docshellID;
 
-    if (aEntry.stateData)
-      shEntry.stateData = aEntry.stateData;
+    if (aEntry.structuredCloneState && aEntry.structuredCloneVersion) {
+      shEntry.stateData =
+        Cc["@mozilla.org/docshell/structured-clone-container;1"].
+        createInstance(Ci.nsIStructuredCloneContainer);
+
+      shEntry.stateData.initFromBase64(aEntry.structuredCloneState, aEntry.structuredCloneVersion);
+    }
 
     if (aEntry.scroll) {
       let scrollPos = aEntry.scroll.split(",");
       scrollPos = [parseInt(scrollPos[0]) || 0, parseInt(scrollPos[1]) || 0];
       shEntry.setScrollPosition(scrollPos[0], scrollPos[1]);
     }
 
     if (aEntry.docIdentifier) {
-      // Get a new document identifier for this entry to ensure that history
-      // entries after a session restore are considered to have different
-      // documents from the history entries before the session restore.
-      // Document identifiers are 64-bit ints, so JS will loose precision and
-      // start assigning all entries the same doc identifier if these ever get
-      // large enough.
-      //
-      // It's a potential security issue if document identifiers aren't
-      // globally unique, but shEntry.setUniqueDocIdentifier() below guarantees
-      // that we won't re-use a doc identifier within a given instance of the
-      // application.
-      let ident = aDocIdentMap[aEntry.docIdentifier];
-      if (!ident) {
-        shEntry.setUniqueDocIdentifier();
-        aDocIdentMap[aEntry.docIdentifier] = shEntry.docIdentifier;
+      // If we have a serialized document identifier, try to find an SHEntry
+      // which matches that doc identifier and adopt that SHEntry's
+      // BFCacheEntry.  If we don't find a match, insert shEntry as the match
+      // for the document identifier.
+      let matchingEntry = aDocIdentMap[aEntry.docIdentifier];
+      if (!matchingEntry) {
+        aDocIdentMap[aEntry.docIdentifier] = shEntry;
       } else {
-        shEntry.docIdentifier = ident;
+        shEntry.adoptBFCacheEntry(matchingEntry);
       }
     }
 
     if (aEntry.owner_b64) {
       let ownerInput = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
       let binaryData = atob(aEntry.owner_b64);
       ownerInput.setData(binaryData, binaryData.length);
       let binaryStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIObjectInputStream);
@@ -374,21 +371,22 @@ let WebNavigation =  {
         let ownerBytes = scriptableStream.readByteArray(scriptableStream.available());
         // 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 (e) { dump(e); }
     }
 
-    if (aEntry.docIdentifier)
-      entry.docIdentifier = aEntry.docIdentifier;
+    entry.docIdentifier = aEntry.BFCacheEntry.ID;
 
-    if (aEntry.stateData)
-      entry.stateData = aEntry.stateData;
+    if (aEntry.stateData != null) {
+      entry.structuredCloneState = aEntry.stateData.getDataAsBase64();
+      entry.structuredCloneVersion = aEntry.stateData.formatVersion;
+    }
 
     if (!(aEntry instanceof Ci.nsISHContainer))
       return entry;
 
     if (aEntry.childCount > 0) {
       entry.children = [];
       for (let i = 0; i < aEntry.childCount; i++) {
         let child = aEntry.GetChildAt(i);
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -104,18 +104,16 @@ 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