summaryrefslogtreecommitdiffstats
path: root/docshell/shistory
diff options
context:
space:
mode:
Diffstat (limited to 'docshell/shistory')
-rw-r--r--docshell/shistory/moz.build40
-rw-r--r--docshell/shistory/nsIBFCacheEntry.idl18
-rw-r--r--docshell/shistory/nsIGroupedSHistory.idl47
-rw-r--r--docshell/shistory/nsIPartialSHistory.idl62
-rw-r--r--docshell/shistory/nsIPartialSHistoryListener.idl24
-rw-r--r--docshell/shistory/nsISHContainer.idl49
-rw-r--r--docshell/shistory/nsISHEntry.idl352
-rw-r--r--docshell/shistory/nsISHTransaction.idl56
-rw-r--r--docshell/shistory/nsISHistory.idl221
-rw-r--r--docshell/shistory/nsISHistoryInternal.idl104
-rw-r--r--docshell/shistory/nsISHistoryListener.idl108
-rw-r--r--docshell/shistory/nsSHEntry.cpp944
-rw-r--r--docshell/shistory/nsSHEntry.h71
-rw-r--r--docshell/shistory/nsSHEntryShared.cpp385
-rw-r--r--docshell/shistory/nsSHEntryShared.h97
-rw-r--r--docshell/shistory/nsSHTransaction.cpp107
-rw-r--r--docshell/shistory/nsSHTransaction.h37
-rw-r--r--docshell/shistory/nsSHistory.cpp1932
-rw-r--r--docshell/shistory/nsSHistory.h133
19 files changed, 4787 insertions, 0 deletions
diff --git a/docshell/shistory/moz.build b/docshell/shistory/moz.build
new file mode 100644
index 000000000..c85c3a3cb
--- /dev/null
+++ b/docshell/shistory/moz.build
@@ -0,0 +1,40 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsIBFCacheEntry.idl',
+ 'nsIGroupedSHistory.idl',
+ 'nsIPartialSHistory.idl',
+ 'nsIPartialSHistoryListener.idl',
+ 'nsISHContainer.idl',
+ 'nsISHEntry.idl',
+ 'nsISHistory.idl',
+ 'nsISHistoryInternal.idl',
+ 'nsISHistoryListener.idl',
+ 'nsISHTransaction.idl',
+]
+
+XPIDL_MODULE = 'shistory'
+
+EXPORTS += [
+ 'nsSHEntryShared.h',
+]
+
+UNIFIED_SOURCES += [
+ 'nsSHEntry.cpp',
+ 'nsSHEntryShared.cpp',
+ 'nsSHistory.cpp',
+ 'nsSHTransaction.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '/docshell/base',
+]
+
+FINAL_LIBRARY = 'xul'
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/docshell/shistory/nsIBFCacheEntry.idl b/docshell/shistory/nsIBFCacheEntry.idl
new file mode 100644
index 000000000..e16f1a302
--- /dev/null
+++ b/docshell/shistory/nsIBFCacheEntry.idl
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#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;
+};
diff --git a/docshell/shistory/nsIGroupedSHistory.idl b/docshell/shistory/nsIGroupedSHistory.idl
new file mode 100644
index 000000000..4001dcc34
--- /dev/null
+++ b/docshell/shistory/nsIGroupedSHistory.idl
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIFrameLoader;
+interface nsIPartialSHistory;
+
+/**
+ * nsIGroupedSHistory represent a combined session history across multiple
+ * root docshells (usually browser tabs). The participating nsISHistory can
+ * either be in chrome process or in content process, but nsIGroupedSHistory
+ * itself lives in chrome process. The communication is proxyed through
+ * nsIPartialSHistory.
+ */
+[scriptable, builtinclass, uuid(813e498d-73a8-449a-be09-6187e62c5352)]
+interface nsIGroupedSHistory : nsISupports
+{
+ // The total number of entries of all its partial session histories.
+ [infallible] readonly attribute unsigned long count;
+
+ /**
+ * Remove all partial histories after currently active one (if any) and then
+ * append the given partial session history to the end of the list.
+ */
+ void appendPartialSessionHistory(in nsIPartialSHistory aPartialHistory);
+
+ /**
+ * Notify the grouped session history that the active partial session history
+ * has been modified. All partial session histories after the active one
+ * will be removed and destroy.
+ */
+ void onPartialSessionHistoryChange(in nsIPartialSHistory aPartialHistory);
+
+ /**
+ * Find the proper partial session history and navigate to the entry
+ * corresponding to the given global index. Note it doesn't swap frameloaders,
+ * but rather return the target loader for the caller to swap.
+ *
+ * @param aGlobalIndex The global index to navigate to.
+ * @param aTargetLoaderToSwap The owner frameloader of the to-be-navigate
+ * partial session history.
+ */
+ void gotoIndex(in unsigned long aGlobalIndex, out nsIFrameLoader aTargetLoaderToSwap);
+};
diff --git a/docshell/shistory/nsIPartialSHistory.idl b/docshell/shistory/nsIPartialSHistory.idl
new file mode 100644
index 000000000..99359794e
--- /dev/null
+++ b/docshell/shistory/nsIPartialSHistory.idl
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIFrameLoader;
+
+/**
+ * nsIPartialSHistory represents a part of nsIGroupedSHistory. It associates to
+ * a "partial" nsISHistory in either local or remote process.
+ */
+[scriptable, builtinclass, uuid(5cd75e28-838c-4a0a-972e-6005f736ef7a)]
+interface nsIPartialSHistory : nsISupports
+{
+ // The number of entries of its corresponding nsISHistory.
+ [infallible] readonly attribute unsigned long count;
+
+ // If it's part of a grouped session history, globalIndexOffset denotes the
+ // number of entries ahead.
+ [infallible] readonly attribute unsigned long globalIndexOffset;
+
+ // The frameloader which owns this partial session history.
+ readonly attribute nsIFrameLoader ownerFrameLoader;
+
+ /**
+ * Notify that it's been added to a grouped session history. It also implies
+ * it's becoming the active partial history of the group.
+ *
+ * @param aOffset The number of entries in preceding partial
+ * session histories.
+ */
+ void onAttachGroupedSessionHistory(in unsigned long aOffset);
+
+ /**
+ * Notify that one or more entries in its associated nsISHistory object
+ * have been changed (i.e. add / remove / replace). It's mainly used for
+ * cross-process case, since in the in-process case we can just register an
+ * nsISHistoryListener instead.
+ *
+ * @param aCount The number of entries in the associated session history.
+ * It can be the same as the old value if entries were replaced.
+ */
+ void onSessionHistoryChange(in unsigned long aCount);
+
+ /**
+ * Notify that the partial session history has been swapped in as the active
+ * session history. Only an active session history can possibly add / remove /
+ * replace its history entries.
+ *
+ * @param aGlobalLength The up-to-date global length.
+ * @param aTargetLocalIndex The local index to navigate to.
+ */
+ void onActive(in unsigned long aGlobalLength, in unsigned long aTargetLocalIndex);
+
+ /**
+ * Notify that the partial session history has been swapped out and is no
+ * longer active.
+ */
+ void onDeactive();
+};
diff --git a/docshell/shistory/nsIPartialSHistoryListener.idl b/docshell/shistory/nsIPartialSHistoryListener.idl
new file mode 100644
index 000000000..f0cdfd7e4
--- /dev/null
+++ b/docshell/shistory/nsIPartialSHistoryListener.idl
@@ -0,0 +1,24 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "nsISupports.idl"
+
+/**
+ * Listener to handle cross partial nsISHistory navigation requests.
+ */
+[scriptable, uuid(be0cd2b6-6f03-4366-9fe2-184c914ff3df)]
+interface nsIPartialSHistoryListener : nsISupports
+{
+ /**
+ * Called when the navigation target belongs to another nsISHistory within
+ * the same nsIGroupedSHistory, and it needs to initiate cross nsISHistory
+ * navigation.
+ *
+ * @param aIndex The index of complete history to navigate to.
+ */
+ void onRequestCrossBrowserNavigation(in unsigned long aIndex);
+};
diff --git a/docshell/shistory/nsISHContainer.idl b/docshell/shistory/nsISHContainer.idl
new file mode 100644
index 000000000..942603d87
--- /dev/null
+++ b/docshell/shistory/nsISHContainer.idl
@@ -0,0 +1,49 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsISHEntry;
+
+/**
+ * The nsISHEntryContainer. The interface to access child entries
+ * of an nsISHEntry.
+ *
+ */
+
+[scriptable, uuid(67dd0357-8372-4122-bff6-217435e8b7e4)]
+interface nsISHContainer : nsISupports
+{
+ /**
+ * The current number of nsISHEntries which are immediate children of the
+ * current SHEntry
+ */
+ readonly attribute long childCount;
+
+ /**
+ * Add a new child SHEntry. If offset is -1 adds to the end of the list.
+ */
+ void AddChild(in nsISHEntry child, in long offset);
+
+ /**
+ * Removes a child SHEntry
+ */
+ void RemoveChild(in nsISHEntry child);
+
+ /**
+ * Get child at an index
+ */
+ nsISHEntry GetChildAt(in long index);
+
+ /**
+ * Replaces a child which is for the same docshell as aNewChild
+ * with aNewChild.
+ * @throw if nothing was replaced.
+ */
+ void ReplaceChild(in nsISHEntry aNewChild);
+
+};
+
diff --git a/docshell/shistory/nsISHEntry.idl b/docshell/shistory/nsISHEntry.idl
new file mode 100644
index 000000000..05dbf7642
--- /dev/null
+++ b/docshell/shistory/nsISHEntry.idl
@@ -0,0 +1,352 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * The interface to nsISHentry. Each document or subframe in
+ * Session History will have a nsISHEntry associated with it which will
+ * hold all information required to recreate the document from history
+ *
+ */
+
+#include "nsISupports.idl"
+
+interface nsIMutableArray;
+interface nsILayoutHistoryState;
+interface nsIContentViewer;
+interface nsIURI;
+interface nsIInputStream;
+interface nsIDocShellTreeItem;
+interface nsIStructuredCloneContainer;
+interface nsIBFCacheEntry;
+interface nsIPrincipal;
+
+%{C++
+#include "nsRect.h"
+class nsDocShellEditorData;
+class nsSHEntryShared;
+%}
+[ref] native nsIntRect(nsIntRect);
+[ptr] native nsDocShellEditorDataPtr(nsDocShellEditorData);
+[ptr] native nsSHEntryShared(nsSHEntryShared);
+
+[scriptable, uuid(0dad26b8-a259-42c7-93f1-2fa7fc076e45)]
+interface nsISHEntry : nsISupports
+{
+ /**
+ * A readonly property that returns the URI
+ * of the current entry. The object returned is
+ * of type nsIURI
+ */
+ readonly attribute nsIURI URI;
+
+ /**
+ * A readonly property that returns the original URI of the current entry.
+ * If an entry is the result of a redirect this attribute holds original
+ * URI. The object returned is of type nsIURI
+ */
+ attribute nsIURI originalURI;
+
+ /**
+ * This flag remembers whether channel has LOAD_REPLACE set.
+ */
+ attribute boolean loadReplace;
+
+ /**
+ * A readonly property that returns the title
+ * of the current entry. The object returned
+ * is a encoded string
+ */
+ readonly attribute wstring title;
+
+ /**
+ * A readonly property that returns a boolean
+ * flag which indicates if the entry was created as a
+ * result of a subframe navigation. This flag will be
+ * 'false' when a frameset page is visited for
+ * the first time. This flag will be 'true' for all
+ * history entries created as a result of a subframe
+ * navigation.
+ */
+ readonly attribute boolean isSubFrame;
+
+ /** URI for the document */
+ void setURI(in nsIURI aURI);
+
+ /** Referrer URI */
+ attribute nsIURI referrerURI;
+
+ /** Referrer policy, holding one of the values (REFERRER_POLICY_*)
+ * defined in nsIHttpChannel.
+ */
+ attribute unsigned long referrerPolicy;
+
+ /** Content viewer, for fast restoration of presentation */
+ attribute nsIContentViewer contentViewer;
+
+ /** Whether the content viewer is marked "sticky" */
+ attribute boolean sticky;
+
+ /** Saved state of the global window object */
+ attribute nsISupports windowState;
+
+ /**
+ * Saved position and dimensions of the content viewer; we must adjust the
+ * root view's widget accordingly if this has changed when the presentation
+ * is restored.
+ */
+ [noscript] void getViewerBounds(in nsIntRect bounds);
+ [noscript] void setViewerBounds([const] in nsIntRect bounds);
+
+ /**
+ * Saved child docshells corresponding to contentViewer. The child shells
+ * are restored as children of the parent docshell, in this order, when the
+ * parent docshell restores a saved presentation.
+ */
+
+ /** Append a child shell to the end of our list. */
+ void addChildShell(in nsIDocShellTreeItem shell);
+
+ /**
+ * Get the child shell at |index|; returns null if |index| is out of bounds.
+ */
+ nsIDocShellTreeItem childShellAt(in long index);
+
+ /**
+ * Clear the child shell list.
+ */
+ void clearChildShells();
+
+ /** Saved refresh URI list for the content viewer */
+ attribute nsIMutableArray refreshURIList;
+
+ /**
+ * Ensure that the cached presentation members are self-consistent.
+ * If either |contentViewer| or |windowState| are null, then all of the
+ * following members are cleared/reset:
+ * contentViewer, sticky, windowState, viewerBounds, childShells,
+ * refreshURIList.
+ */
+ void syncPresentationState();
+
+ /** Title for the document */
+ void setTitle(in AString aTitle);
+
+ /** Post Data for the document */
+ attribute nsIInputStream postData;
+
+ /** LayoutHistoryState for scroll position and form values */
+ attribute nsILayoutHistoryState layoutHistoryState;
+
+ /** parent of this entry */
+ attribute nsISHEntry parent;
+
+ /**
+ * The loadType for this entry. This is typically loadHistory except
+ * when reload is pressed, it has the appropriate reload flag
+ */
+ attribute unsigned long loadType;
+
+ /**
+ * An ID to help identify this entry from others during
+ * subframe navigation
+ */
+ attribute unsigned long ID;
+
+ /** 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;
+
+ /**
+ * attribute to indicate the content-type of the document that this
+ * is a session history entry for
+ */
+ attribute ACString contentType;
+
+ /**
+ * If we created this SHEntry via history.pushState or modified it via
+ * history.replaceState, and if we changed the SHEntry's URI via the
+ * push/replaceState call, and if the SHEntry's new URI differs from its
+ * old URI by more than just the hash, then we set this field to true.
+ *
+ * Additionally, if this SHEntry was created by calling pushState from a
+ * SHEntry whose URI was modified, this SHEntry's URIWasModified field is
+ * true.
+ *
+ */
+ attribute boolean URIWasModified;
+
+ /** Set/Get scrollers' positon in anchored pages */
+ void setScrollPosition(in long x, in long y);
+ void getScrollPosition(out long x, out long y);
+
+ /** Additional ways to create an entry */
+ [noscript] void create(in nsIURI URI, in AString title,
+ in nsIInputStream inputStream,
+ in nsILayoutHistoryState layoutHistoryState,
+ in nsISupports cacheKey, in ACString contentType,
+ in nsIPrincipal triggeringPrincipal,
+ in nsIPrincipal principalToInherit,
+ in unsigned long long docshellID,
+ in boolean dynamicCreation);
+
+ nsISHEntry clone();
+
+ /** Attribute that indicates if this entry is for a subframe navigation */
+ void setIsSubFrame(in boolean aFlag);
+
+ /** Return any content viewer present in or below this node in the
+ nsSHEntry tree. This will differ from contentViewer in the case
+ where a child nsSHEntry has the content viewer for this tree. */
+ nsIContentViewer getAnyContentViewer(out nsISHEntry ownerEntry);
+
+ /**
+ * Get the principal, if any, that was associated with the channel
+ * that the document that was loaded to create this history entry
+ * came from.
+ */
+ attribute nsIPrincipal triggeringPrincipal;
+
+ /**
+ * Get the principal, if any, that is used when the inherit flag
+ * is set.
+ */
+ attribute nsIPrincipal principalToInherit;
+
+ /**
+ * Get/set data associated with this history state via a pushState() call,
+ * serialized using structured clone.
+ **/
+ attribute nsIStructuredCloneContainer stateData;
+
+ /**
+ * Gets the owning pointer to the editor data assosicated with
+ * this shistory entry. This forgets its pointer, so free it when
+ * you're done.
+ */
+ [noscript, notxpcom] nsDocShellEditorDataPtr forgetEditorData();
+
+ /**
+ * Sets the owning pointer to the editor data assosicated with
+ * this shistory entry. Unless forgetEditorData() is called, this
+ * shentry will destroy the editor data when it's destroyed.
+ */
+ [noscript, notxpcom] void setEditorData(in nsDocShellEditorDataPtr aData);
+
+ /** Returns true if this shistory entry is storing a detached editor. */
+ [noscript, notxpcom] boolean hasDetachedEditor();
+
+ /**
+ * Returns true if the related docshell was added because of
+ * dynamic addition of an iframe/frame.
+ */
+ boolean isDynamicallyAdded();
+
+ /**
+ * Returns true if any of the child entries returns true
+ * 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);
+
+ /**
+ * True if this SHEntry corresponds to a document created by a srcdoc iframe.
+ * Set when a value is assigned to srcdocData.
+ */
+ readonly attribute boolean isSrcdocEntry;
+
+ /**
+ * Contents of the srcdoc attribute in a srcdoc iframe to be loaded instead
+ * of the URI. Similar to a Data URI, this information is needed to
+ * recreate the document at a later stage.
+ * Setting this sets isSrcdocEntry to true
+ */
+ attribute AString srcdocData;
+
+ /**
+ * When isSrcdocEntry is true, this contains the baseURI of the srcdoc
+ * document for use in situations where it cannot otherwise be determined,
+ * for example with view-source.
+ */
+ attribute nsIURI baseURI;
+
+ /**
+ * Sets/gets the current scroll restoration state,
+ * if true == "manual", false == "auto".
+ */
+ attribute boolean scrollRestorationIsManual;
+};
+
+[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 \
+ "@mozilla.org/browser/session-history-entry;1"
+
+%}
+
diff --git a/docshell/shistory/nsISHTransaction.idl b/docshell/shistory/nsISHTransaction.idl
new file mode 100644
index 000000000..f5467cfb9
--- /dev/null
+++ b/docshell/shistory/nsISHTransaction.idl
@@ -0,0 +1,56 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsISHEntry;
+
+/**
+ * The nsISHTransaction.
+ */
+
+
+[scriptable, uuid(2EDF705F-D252-4971-9F09-71DD0F760DC6)]
+interface nsISHTransaction : nsISupports
+{
+ /**
+ * The nsISHEntry for the current transaction
+ */
+ attribute nsISHEntry sHEntry;
+
+ /**
+ * The parent of this transaction
+ */
+ attribute nsISHTransaction prev;
+
+ /**
+ * The legitimate child of this transaction
+ */
+ attribute nsISHTransaction next;
+
+ /**
+ * Specifies if this transaction should persist. If not it will be replaced
+ * by new additions to the list.
+ */
+ attribute boolean persist;
+
+ /**
+ * Create a transaction with parent and History Entry
+ */
+ void create(in nsISHEntry aSHEntry, in nsISHTransaction aPrev);
+};
+
+%{ C++
+// {BFD1A792-AD9F-11d3-BDC7-0050040A9B44}
+
+
+#define NS_SHTRANSACTION_CID \
+{0xbfd1a792, 0xad9f, 0x11d3, {0xbd, 0xc7, 0x0, 0x50, 0x4, 0xa, 0x9b, 0x44}}
+
+#define NS_SHTRANSACTION_CONTRACTID \
+ "@mozilla.org/browser/session-history-transaction;1"
+
+%}
diff --git a/docshell/shistory/nsISHistory.idl b/docshell/shistory/nsISHistory.idl
new file mode 100644
index 000000000..71da2e1ab
--- /dev/null
+++ b/docshell/shistory/nsISHistory.idl
@@ -0,0 +1,221 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsISHEntry;
+interface nsISHistoryListener;
+interface nsISimpleEnumerator;
+interface nsIPartialSHistoryListener;
+
+/**
+ * An interface to the primary properties of the Session History
+ * component. In an embedded browser environment, the nsIWebBrowser
+ * object creates an instance of session history for each open window.
+ * A handle to the session history object can be obtained from
+ * nsIWebNavigation. In a non-embedded situation, the owner of the
+ * session history component must create a instance of it and set
+ * it in the nsIWebNavigation object.
+ * This interface is accessible from javascript.
+ */
+
+
+%{C++
+#define NS_SHISTORY_CID \
+{0x7b807041, 0xe60a, 0x4384, {0x93, 0x5f, 0xaf, 0x30, 0x61, 0xd8, 0xb8, 0x15}}
+
+#define NS_SHISTORY_CONTRACTID "@mozilla.org/browser/shistory;1"
+%}
+
+[scriptable, uuid(7b807041-e60a-4384-935f-af3061d8b815)]
+interface nsISHistory: nsISupports
+{
+ /**
+ * An attribute denoting whether the nsISHistory is associated to a grouped
+ * session history.
+ *
+ * The abstraction of grouped session history is implemented at
+ * nsIWebNavigation level, so those canGoBack / canGoForward / gotoIndex
+ * functions work transparently;
+ *
+ * On the other hand, nsISHistory works on partial session history directly.
+ * Unless otherwise specified, count / index attributes and parameters all
+ * indicate local count / index, so we won't mess up docshell.
+ */
+ readonly attribute bool isPartial;
+
+ /**
+ * A readonly property of the interface that returns
+ * the number of toplevel documents currently available
+ * in session history.
+ */
+ readonly attribute long count;
+
+ /**
+ * If isPartial, globalCount denotes the total number of entries in the
+ * grouped session history; Otherwise it has the same value as count.
+ */
+ readonly attribute long globalCount;
+
+ /**
+ * A readonly property which represents the difference between global indices
+ * of grouped session history and local indices of this particular session
+ * history object.
+ */
+ readonly attribute long globalIndexOffset;
+
+ /**
+ * A readonly property of the interface that returns
+ * the index of the current document in session history.
+ */
+ readonly attribute long index;
+
+ /**
+ * A readonly property of the interface that returns
+ * the index of the last document that started to load and
+ * didn't finished yet. When document finishes the loading
+ * value -1 is returned.
+ */
+ readonly attribute long requestedIndex;
+
+ /**
+ * A read/write property of the interface, used to Get/Set
+ * the maximum number of toplevel documents, session history
+ * can hold for each instance.
+ */
+ attribute long maxLength;
+
+ /**
+ * Called to obtain handle to the history entry at a
+ * given index.
+ *
+ * @param index The index value whose entry is requested.
+ * The oldest entry is located at index == 0.
+ * @param modifyIndex A boolean flag that indicates if the current
+ * index of session history should be modified
+ * to the parameter index.
+ *
+ * @return <code>NS_OK</code> history entry for
+ * the index is obtained successfully.
+ * <code>NS_ERROR_FAILURE</code> Error in obtaining
+ * history entry for the given index.
+ */
+ nsISHEntry getEntryAtIndex(in long index, in boolean modifyIndex);
+
+
+ /**
+ * Called to purge older documents from history.
+ * Documents can be removed from session history for various
+ * reasons. For example to control memory usage of the browser, to
+ * prevent users from loading documents from history, to erase evidence of
+ * prior page loads etc...
+ *
+ * @param numEntries The number of toplevel documents to be
+ * purged from history. During purge operation,
+ * the latest documents are maintained and older
+ * 'numEntries' documents are removed from history.
+ * @throws <code>NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA</code> Purge was vetod.
+ * @throws <code>NS_ERROR_FAILURE</code> numEntries is
+ * invalid or out of bounds with the size of history.
+ *
+ */
+ void PurgeHistory(in long numEntries);
+
+ /**
+ * Called to register a listener for the session history component.
+ * Listeners are notified when pages are loaded or purged from history.
+ *
+ * @param aListener Listener object to be notified for all
+ * page loads that initiate in session history.
+ *
+ * @note A listener object must implement
+ * nsISHistoryListener and nsSupportsWeakReference
+ *
+ * @see nsISHistoryListener
+ * @see nsSupportsWeakReference
+ */
+ void addSHistoryListener(in nsISHistoryListener aListener);
+
+ /**
+ * Called to remove a listener for the session history component.
+ * Listeners are notified when pages are loaded from history.
+ *
+ * @param aListener Listener object to be removed from
+ * session history.
+ *
+ * @note A listener object must implement
+ * nsISHistoryListener and nsSupportsWeakReference
+ * @see nsISHistoryListener
+ * @see nsSupportsWeakReference
+ */
+ void removeSHistoryListener(in nsISHistoryListener aListener);
+
+ /**
+ * Set the listener to handle cross nsISHistory navigation when it works
+ * in "partial" mode.
+ */
+ void setPartialSHistoryListener(in nsIPartialSHistoryListener aListener);
+
+ /**
+ * Called to obtain a enumerator for all the documents stored in
+ * session history. The enumerator object thus returned by this method
+ * can be traversed using nsISimpleEnumerator.
+ *
+ * @note To access individual history entries of the enumerator, perform the
+ * following steps:
+ * 1) Call nsISHistory->GetSHistoryEnumerator() to obtain handle
+ * the nsISimpleEnumerator object.
+ * 2) Use nsISimpleEnumerator->GetNext() on the object returned
+ * by step #1 to obtain handle to the next object in the list.
+ * The object returned by this step is of type nsISupports.
+ * 3) Perform a QueryInterface on the object returned by step #2
+ * to nsISHEntry.
+ * 4) Use nsISHEntry to access properties of each history entry.
+ *
+ * @see nsISimpleEnumerator
+ * @see nsISHEntry
+ * @see QueryInterface()
+ * @see do_QueryInterface()
+ */
+ readonly attribute nsISimpleEnumerator SHistoryEnumerator;
+
+ void reloadCurrentEntry();
+
+ /**
+ * Called to obtain the index to a given history entry.
+ *
+ * @param aEntry The entry to obtain the index of.
+ *
+ * @return <code>NS_OK</code> index for the history entry
+ * is obtained successfully.
+ * <code>NS_ERROR_FAILURE</code> Error in obtaining
+ * index for the given history entry.
+ */
+ long getIndexOfEntry(in nsISHEntry aEntry);
+
+ /**
+ * Called when this nsISHistory has became the active history of a grouped
+ * session history.
+ *
+ * @param globalLength The up to date number of entries in the grouped
+ * session history.
+ * @param targetIndex The local index to navigate to.
+ */
+ void onPartialSessionHistoryActive(in long globalLength, in long targetIndex);
+
+ /**
+ * Called when this nsISHistory has became inactive history of a grouped
+ * session history.
+ */
+ void onPartialSessionHistoryDeactive();
+
+ /**
+ * Called when it's attached to a nsIGroupedSHistory instance.
+ *
+ * @param offset The number of entries in the grouped session
+ * history before this session history object.
+ */
+ void onAttachGroupedSessionHistory(in long offset);
+};
diff --git a/docshell/shistory/nsISHistoryInternal.idl b/docshell/shistory/nsISHistoryInternal.idl
new file mode 100644
index 000000000..b7b900547
--- /dev/null
+++ b/docshell/shistory/nsISHistoryInternal.idl
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIBFCacheEntry;
+interface nsISHEntry;
+interface nsISHistoryListener;
+interface nsISHTransaction;
+interface nsIDocShell;
+interface nsIURI;
+
+%{C++
+#define NS_SHISTORY_INTERNAL_CID \
+{ 0x3dfb2f54, 0x378d, 0x4d3c, \
+ { 0xa9, 0xf9, 0x95, 0xdd, 0x26, 0x73, 0x24, 0x8c } }
+
+#define NS_SHISTORY_INTERNAL_CONTRACTID "@mozilla.org/browser/shistory-internal;1"
+
+#include "nsTArrayForwardDeclare.h"
+%}
+
+[ref] native nsDocshellIDArray(nsTArray<uint64_t>);
+
+[scriptable, uuid(3dfb2f54-378d-4d3c-a9f9-95dd2673248c)]
+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.
+ */
+ void addEntry(in nsISHEntry aEntry, in boolean aPersist);
+
+ /**
+ * Get the root transaction
+ */
+ readonly attribute nsISHTransaction rootTransaction;
+
+ /**
+ * Sets the toplevel docshell object to which this SHistory object belongs to.
+ */
+ void setRootDocShell(in nsIDocShell rootDocShell);
+
+ /**
+ * Update the index maintained by sessionHistory
+ */
+ void updateIndex();
+
+ /**
+ * Replace the nsISHEntry at a particular index
+ * @param aIndex - The index at which the entry should be replaced
+ * @param aReplaceEntry - The replacement entry for the index.
+ */
+ void replaceEntry(in long aIndex, in nsISHEntry aReplaceEntry);
+
+ /**
+ * Notifies all registered session history listeners about an impending
+ * reload.
+ *
+ * @param aReloadURI The URI of the document to be reloaded.
+ * @param aReloadFlags Flags that indicate how the document is to be
+ * refreshed. See constants on the nsIWebNavigation
+ * interface.
+ * @return Whether the operation can proceed.
+ */
+ boolean notifyOnHistoryReload(in nsIURI aReloadURI, in unsigned long aReloadFlags);
+
+ /**
+ * 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 bfcache entry
+ * that has timed out.
+ */
+ 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
+ * aIDs array.
+ */
+ [noscript, notxpcom] void RemoveEntries(in nsDocshellIDArray aIDs,
+ in long aStartIndex);
+};
diff --git a/docshell/shistory/nsISHistoryListener.idl b/docshell/shistory/nsISHistoryListener.idl
new file mode 100644
index 000000000..38552d60e
--- /dev/null
+++ b/docshell/shistory/nsISHistoryListener.idl
@@ -0,0 +1,108 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+
+/**
+ * nsISHistoryListener defines the interface one can implement to receive
+ * notifications about activities in session history and to be able to
+ * cancel them.
+ *
+ * A session history listener will be notified when pages are added, removed
+ * and loaded from session history. It can prevent any action (except adding
+ * a new session history entry) from happening by returning false from the
+ * corresponding callback method.
+ *
+ * A session history listener can be registered on a particular nsISHistory
+ * instance via the nsISHistory::addSHistoryListener() method.
+ */
+[scriptable, uuid(125c0833-746a-400e-9b89-d2d18545c08a)]
+interface nsISHistoryListener : nsISupports
+{
+ /**
+ * Called when a new document is added to session history. New documents are
+ * added to session history by docshell when new pages are loaded in a frame
+ * or content area, for example via nsIWebNavigation::loadURI()
+ *
+ * @param aNewURI The URI of the document to be added to session history.
+ * @param aOldIndex The index of the current history item before the operation.
+ */
+ void OnHistoryNewEntry(in nsIURI aNewURI, in long aOldIndex);
+
+ /**
+ * Called when navigating to a previous session history entry, for example
+ * due to a nsIWebNavigation::goBack() call.
+ *
+ * @param aBackURI The URI of the session history entry being navigated to.
+ * @return Whether the operation can proceed.
+ */
+ boolean OnHistoryGoBack(in nsIURI aBackURI);
+
+ /**
+ * Called when navigating to a next session history entry, for example
+ * due to a nsIWebNavigation::goForward() call.
+ *
+ * @param aForwardURI The URI of the session history entry being navigated to.
+ * @return Whether the operation can proceed.
+ */
+ boolean OnHistoryGoForward(in nsIURI aForwardURI);
+
+ /**
+ * Called when the current document is reloaded, for example due to a
+ * nsIWebNavigation::reload() call.
+ *
+ * @param aReloadURI The URI of the document to be reloaded.
+ * @param aReloadFlags Flags that indicate how the document is to be
+ * refreshed. See constants on the nsIWebNavigation
+ * interface.
+ * @return Whether the operation can proceed.
+ *
+ * @see nsIWebNavigation
+ */
+ boolean OnHistoryReload(in nsIURI aReloadURI, in unsigned long aReloadFlags);
+
+ /**
+ * Called when navigating to a session history entry by index, for example,
+ * when nsIWebNavigation::gotoIndex() is called.
+ *
+ * @param aIndex The index in session history of the entry to be loaded.
+ * @param aGotoURI The URI of the session history entry to be loaded.
+ * @return Whether the operation can proceed.
+ */
+ boolean OnHistoryGotoIndex(in long aIndex, in nsIURI aGotoURI);
+
+ /**
+ * Called when entries are removed from session history. Entries can be
+ * removed from session history for various reasons, for example to control
+ * the memory usage of the browser, to prevent users from loading documents
+ * from history, to erase evidence of prior page loads, etc.
+ *
+ * To purge documents from session history call nsISHistory::PurgeHistory()
+ *
+ * @param aNumEntries The number of entries to be removed from session history.
+ * @return Whether the operation can proceed.
+ */
+ boolean OnHistoryPurge(in long aNumEntries);
+
+ /**
+ * Called when an entry is replaced in the session history. Entries are
+ * replaced when navigating away from non-persistent history entries (such as
+ * about pages) and when history.replaceState is called.
+ *
+ * @param aIndex The index in session history of the entry being
+ * replaced
+ */
+ void OnHistoryReplaceEntry(in long aIndex);
+
+ /**
+ * Called when nsISHistory::count has been updated. Unlike OnHistoryNewEntry
+ * and OnHistoryPurge which happen before the modifications are actually done
+ * and maybe cancellable, this function is called after these modifications.
+ */
+ void OnLengthChange(in long aCount);
+};
diff --git a/docshell/shistory/nsSHEntry.cpp b/docshell/shistory/nsSHEntry.cpp
new file mode 100644
index 000000000..9d972136f
--- /dev/null
+++ b/docshell/shistory/nsSHEntry.cpp
@@ -0,0 +1,944 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSHEntry.h"
+#include "nsIDocShellLoadInfo.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsDocShellEditorData.h"
+#include "nsSHEntryShared.h"
+#include "nsILayoutHistoryState.h"
+#include "nsIContentViewer.h"
+#include "nsIStructuredCloneContainer.h"
+#include "nsIInputStream.h"
+#include "nsIURI.h"
+#include "mozilla/net/ReferrerPolicy.h"
+#include <algorithm>
+
+namespace dom = mozilla::dom;
+
+static uint32_t gEntryID = 0;
+
+nsSHEntry::nsSHEntry()
+ : mShared(new nsSHEntryShared())
+ , mLoadReplace(false)
+ , mReferrerPolicy(mozilla::net::RP_Default)
+ , mLoadType(0)
+ , mID(gEntryID++)
+ , mScrollPositionX(0)
+ , mScrollPositionY(0)
+ , mParent(nullptr)
+ , mURIWasModified(false)
+ , mIsSrcdocEntry(false)
+ , mScrollRestorationIsManual(false)
+{
+}
+
+nsSHEntry::nsSHEntry(const nsSHEntry& aOther)
+ : mShared(aOther.mShared)
+ , mURI(aOther.mURI)
+ , mOriginalURI(aOther.mOriginalURI)
+ , mLoadReplace(aOther.mLoadReplace)
+ , mReferrerURI(aOther.mReferrerURI)
+ , mReferrerPolicy(aOther.mReferrerPolicy)
+ , mTitle(aOther.mTitle)
+ , mPostData(aOther.mPostData)
+ , mLoadType(0) // XXX why not copy?
+ , mID(aOther.mID)
+ , mScrollPositionX(0) // XXX why not copy?
+ , mScrollPositionY(0) // XXX why not copy?
+ , mParent(aOther.mParent)
+ , mURIWasModified(aOther.mURIWasModified)
+ , mStateData(aOther.mStateData)
+ , mIsSrcdocEntry(aOther.mIsSrcdocEntry)
+ , mScrollRestorationIsManual(false)
+ , mSrcdocData(aOther.mSrcdocData)
+ , mBaseURI(aOther.mBaseURI)
+{
+}
+
+static bool
+ClearParentPtr(nsISHEntry* aEntry, void* /* aData */)
+{
+ if (aEntry) {
+ aEntry->SetParent(nullptr);
+ }
+ return true;
+}
+
+nsSHEntry::~nsSHEntry()
+{
+ // Null out the mParent pointers on all our kids.
+ mChildren.EnumerateForwards(ClearParentPtr, nullptr);
+}
+
+NS_IMPL_ISUPPORTS(nsSHEntry, nsISHContainer, nsISHEntry, nsISHEntryInternal)
+
+NS_IMETHODIMP
+nsSHEntry::SetScrollPosition(int32_t aX, int32_t aY)
+{
+ mScrollPositionX = aX;
+ mScrollPositionY = aY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetScrollPosition(int32_t* aX, int32_t* aY)
+{
+ *aX = mScrollPositionX;
+ *aY = mScrollPositionY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetURIWasModified(bool* aOut)
+{
+ *aOut = mURIWasModified;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetURIWasModified(bool aIn)
+{
+ mURIWasModified = aIn;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetURI(nsIURI** aURI)
+{
+ *aURI = mURI;
+ NS_IF_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetURI(nsIURI* aURI)
+{
+ mURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetOriginalURI(nsIURI** aOriginalURI)
+{
+ *aOriginalURI = mOriginalURI;
+ NS_IF_ADDREF(*aOriginalURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetOriginalURI(nsIURI* aOriginalURI)
+{
+ mOriginalURI = aOriginalURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetLoadReplace(bool* aLoadReplace)
+{
+ *aLoadReplace = mLoadReplace;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetLoadReplace(bool aLoadReplace)
+{
+ mLoadReplace = aLoadReplace;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetReferrerURI(nsIURI** aReferrerURI)
+{
+ *aReferrerURI = mReferrerURI;
+ NS_IF_ADDREF(*aReferrerURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetReferrerURI(nsIURI* aReferrerURI)
+{
+ mReferrerURI = aReferrerURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetReferrerPolicy(uint32_t* aReferrerPolicy)
+{
+ *aReferrerPolicy = mReferrerPolicy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetReferrerPolicy(uint32_t aReferrerPolicy)
+{
+ mReferrerPolicy = aReferrerPolicy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetContentViewer(nsIContentViewer* aViewer)
+{
+ return mShared->SetContentViewer(aViewer);
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetContentViewer(nsIContentViewer** aResult)
+{
+ *aResult = mShared->mContentViewer;
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetAnyContentViewer(nsISHEntry** aOwnerEntry,
+ nsIContentViewer** aResult)
+{
+ // Find a content viewer in the root node or any of its children,
+ // assuming that there is only one content viewer total in any one
+ // nsSHEntry tree
+ GetContentViewer(aResult);
+ if (*aResult) {
+#ifdef DEBUG_PAGE_CACHE
+ printf("Found content viewer\n");
+#endif
+ *aOwnerEntry = this;
+ NS_ADDREF(*aOwnerEntry);
+ return NS_OK;
+ }
+ // The root SHEntry doesn't have a ContentViewer, so check child nodes
+ for (int32_t i = 0; i < mChildren.Count(); i++) {
+ nsISHEntry* child = mChildren[i];
+ if (child) {
+#ifdef DEBUG_PAGE_CACHE
+ printf("Evaluating SHEntry child %d\n", i);
+#endif
+ child->GetAnyContentViewer(aOwnerEntry, aResult);
+ if (*aResult) {
+ return NS_OK;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetSticky(bool aSticky)
+{
+ mShared->mSticky = aSticky;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetSticky(bool* aSticky)
+{
+ *aSticky = mShared->mSticky;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetTitle(char16_t** aTitle)
+{
+ // Check for empty title...
+ if (mTitle.IsEmpty() && mURI) {
+ // Default title is the URL.
+ nsAutoCString spec;
+ if (NS_SUCCEEDED(mURI->GetSpec(spec))) {
+ AppendUTF8toUTF16(spec, mTitle);
+ }
+ }
+
+ *aTitle = ToNewUnicode(mTitle);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetTitle(const nsAString& aTitle)
+{
+ mTitle = aTitle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetPostData(nsIInputStream** aResult)
+{
+ *aResult = mPostData;
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetPostData(nsIInputStream* aPostData)
+{
+ mPostData = aPostData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetLayoutHistoryState(nsILayoutHistoryState** aResult)
+{
+ *aResult = mShared->mLayoutHistoryState;
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetLayoutHistoryState(nsILayoutHistoryState* aState)
+{
+ mShared->mLayoutHistoryState = aState;
+ if (mShared->mLayoutHistoryState) {
+ mShared->mLayoutHistoryState->SetScrollPositionOnly(
+ !mShared->mSaveLayoutState);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetLoadType(uint32_t* aResult)
+{
+ *aResult = mLoadType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetLoadType(uint32_t aLoadType)
+{
+ mLoadType = aLoadType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetID(uint32_t* aResult)
+{
+ *aResult = mID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetID(uint32_t aID)
+{
+ mID = aID;
+ return NS_OK;
+}
+
+nsSHEntryShared*
+nsSHEntry::GetSharedState()
+{
+ return mShared;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetIsSubFrame(bool* aFlag)
+{
+ *aFlag = mShared->mIsFrameNavigation;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetIsSubFrame(bool aFlag)
+{
+ mShared->mIsFrameNavigation = aFlag;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetCacheKey(nsISupports** aResult)
+{
+ *aResult = mShared->mCacheKey;
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetCacheKey(nsISupports* aCacheKey)
+{
+ mShared->mCacheKey = aCacheKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetSaveLayoutStateFlag(bool* aFlag)
+{
+ *aFlag = mShared->mSaveLayoutState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetSaveLayoutStateFlag(bool aFlag)
+{
+ mShared->mSaveLayoutState = aFlag;
+ if (mShared->mLayoutHistoryState) {
+ mShared->mLayoutHistoryState->SetScrollPositionOnly(!aFlag);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetExpirationStatus(bool* aFlag)
+{
+ *aFlag = mShared->mExpired;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetExpirationStatus(bool aFlag)
+{
+ mShared->mExpired = aFlag;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetContentType(nsACString& aContentType)
+{
+ aContentType = mShared->mContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetContentType(const nsACString& 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,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ uint64_t aDocShellID,
+ bool aDynamicCreation)
+{
+ mURI = aURI;
+ mTitle = aTitle;
+ mPostData = aInputStream;
+
+ // Set the LoadType by default to loadHistory during creation
+ mLoadType = (uint32_t)nsIDocShellLoadInfo::loadHistory;
+
+ mShared->mCacheKey = aCacheKey;
+ mShared->mContentType = aContentType;
+ mShared->mTriggeringPrincipal = aTriggeringPrincipal;
+ mShared->mPrincipalToInherit = aPrincipalToInherit;
+ mShared->mDocShellID = aDocShellID;
+ mShared->mDynamicallyCreated = aDynamicCreation;
+
+ // By default all entries are set false for subframe flag.
+ // nsDocShell::CloneAndReplace() which creates entries for
+ // all subframe navigations, sets the flag to true.
+ mShared->mIsFrameNavigation = false;
+
+ // By default we save LayoutHistoryState
+ mShared->mSaveLayoutState = true;
+ mShared->mLayoutHistoryState = aLayoutHistoryState;
+
+ // By default the page is not expired
+ mShared->mExpired = false;
+
+ mIsSrcdocEntry = false;
+ mSrcdocData = NullString();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::Clone(nsISHEntry** aResult)
+{
+ *aResult = new nsSHEntry(*this);
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetParent(nsISHEntry** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mParent;
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetParent(nsISHEntry* aParent)
+{
+ /* parent not Addrefed on purpose to avoid cyclic reference
+ * Null parent is OK
+ *
+ * XXX this method should not be scriptable if this is the case!!
+ */
+ mParent = aParent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetWindowState(nsISupports* aState)
+{
+ mShared->mWindowState = aState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetWindowState(nsISupports** aState)
+{
+ NS_IF_ADDREF(*aState = mShared->mWindowState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetViewerBounds(const nsIntRect& aBounds)
+{
+ mShared->mViewerBounds = aBounds;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetViewerBounds(nsIntRect& aBounds)
+{
+ aBounds = mShared->mViewerBounds;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetTriggeringPrincipal(nsIPrincipal** aTriggeringPrincipal)
+{
+ NS_IF_ADDREF(*aTriggeringPrincipal = mShared->mTriggeringPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetTriggeringPrincipal(nsIPrincipal* aTriggeringPrincipal)
+{
+ mShared->mTriggeringPrincipal = aTriggeringPrincipal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetPrincipalToInherit(nsIPrincipal** aPrincipalToInherit)
+{
+ NS_IF_ADDREF(*aPrincipalToInherit = mShared->mPrincipalToInherit);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit)
+{
+ mShared->mPrincipalToInherit = aPrincipalToInherit;
+ 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;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetIsSrcdocEntry(bool* aIsSrcdocEntry)
+{
+ *aIsSrcdocEntry = mIsSrcdocEntry;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetSrcdocData(nsAString& aSrcdocData)
+{
+ aSrcdocData = mSrcdocData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetSrcdocData(const nsAString& aSrcdocData)
+{
+ mSrcdocData = aSrcdocData;
+ mIsSrcdocEntry = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetBaseURI(nsIURI** aBaseURI)
+{
+ *aBaseURI = mBaseURI;
+ NS_IF_ADDREF(*aBaseURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetBaseURI(nsIURI* aBaseURI)
+{
+ mBaseURI = aBaseURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetScrollRestorationIsManual(bool* aIsManual)
+{
+ *aIsManual = mScrollRestorationIsManual;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetScrollRestorationIsManual(bool aIsManual)
+{
+ mScrollRestorationIsManual = aIsManual;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetChildCount(int32_t* aCount)
+{
+ *aCount = mChildren.Count();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::AddChild(nsISHEntry* aChild, int32_t aOffset)
+{
+ if (aChild) {
+ NS_ENSURE_SUCCESS(aChild->SetParent(this), NS_ERROR_FAILURE);
+ }
+
+ if (aOffset < 0) {
+ mChildren.AppendObject(aChild);
+ return NS_OK;
+ }
+
+ //
+ // Bug 52670: Ensure children are added in order.
+ //
+ // Later frames in the child list may load faster and get appended
+ // before earlier frames, causing session history to be scrambled.
+ // By growing the list here, they are added to the right position.
+ //
+ // Assert that aOffset will not be so high as to grow us a lot.
+ //
+ NS_ASSERTION(aOffset < (mChildren.Count() + 1023), "Large frames array!\n");
+
+ bool newChildIsDyn = false;
+ if (aChild) {
+ aChild->IsDynamicallyAdded(&newChildIsDyn);
+ }
+
+ // If the new child is dynamically added, try to add it to aOffset, but if
+ // there are non-dynamically added children, the child must be after those.
+ if (newChildIsDyn) {
+ int32_t lastNonDyn = aOffset - 1;
+ for (int32_t i = aOffset; i < mChildren.Count(); ++i) {
+ nsISHEntry* entry = mChildren[i];
+ if (entry) {
+ bool dyn = false;
+ entry->IsDynamicallyAdded(&dyn);
+ if (dyn) {
+ break;
+ } else {
+ lastNonDyn = i;
+ }
+ }
+ }
+ // InsertObjectAt allows only appending one object.
+ // If aOffset is larger than Count(), we must first manually
+ // set the capacity.
+ if (aOffset > mChildren.Count()) {
+ mChildren.SetCount(aOffset);
+ }
+ if (!mChildren.InsertObjectAt(aChild, lastNonDyn + 1)) {
+ NS_WARNING("Adding a child failed!");
+ aChild->SetParent(nullptr);
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ // If the new child isn't dynamically added, it should be set to aOffset.
+ // If there are dynamically added children before that, those must be
+ // moved to be after aOffset.
+ if (mChildren.Count() > 0) {
+ int32_t start = std::min(mChildren.Count() - 1, aOffset);
+ int32_t dynEntryIndex = -1;
+ nsISHEntry* dynEntry = nullptr;
+ for (int32_t i = start; i >= 0; --i) {
+ nsISHEntry* entry = mChildren[i];
+ if (entry) {
+ bool dyn = false;
+ entry->IsDynamicallyAdded(&dyn);
+ if (dyn) {
+ dynEntryIndex = i;
+ dynEntry = entry;
+ } else {
+ break;
+ }
+ }
+ }
+
+ if (dynEntry) {
+ nsCOMArray<nsISHEntry> tmp;
+ tmp.SetCount(aOffset - dynEntryIndex + 1);
+ mChildren.InsertObjectsAt(tmp, dynEntryIndex);
+ NS_ASSERTION(mChildren[aOffset + 1] == dynEntry, "Whaat?");
+ }
+ }
+
+ // Make sure there isn't anything at aOffset.
+ if (aOffset < mChildren.Count()) {
+ nsISHEntry* oldChild = mChildren[aOffset];
+ if (oldChild && oldChild != aChild) {
+ NS_ERROR("Adding a child where we already have a child? This may misbehave");
+ oldChild->SetParent(nullptr);
+ }
+ }
+
+ mChildren.ReplaceObjectAt(aChild, aOffset);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::RemoveChild(nsISHEntry* aChild)
+{
+ NS_ENSURE_TRUE(aChild, NS_ERROR_FAILURE);
+ bool childRemoved = false;
+ bool dynamic = false;
+ aChild->IsDynamicallyAdded(&dynamic);
+ if (dynamic) {
+ childRemoved = mChildren.RemoveObject(aChild);
+ } else {
+ int32_t index = mChildren.IndexOfObject(aChild);
+ if (index >= 0) {
+ mChildren.ReplaceObjectAt(nullptr, index);
+ childRemoved = true;
+ }
+ }
+ if (childRemoved) {
+ aChild->SetParent(nullptr);
+
+ // reduce the child count, i.e. remove empty children at the end
+ for (int32_t i = mChildren.Count() - 1; i >= 0 && !mChildren[i]; --i) {
+ if (!mChildren.RemoveObjectAt(i)) {
+ break;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetChildAt(int32_t aIndex, nsISHEntry** aResult)
+{
+ if (aIndex >= 0 && aIndex < mChildren.Count()) {
+ *aResult = mChildren[aIndex];
+ // yes, mChildren can have holes in it. AddChild's offset parameter makes
+ // that possible.
+ NS_IF_ADDREF(*aResult);
+ } else {
+ *aResult = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::ReplaceChild(nsISHEntry* aNewEntry)
+{
+ NS_ENSURE_STATE(aNewEntry);
+
+ uint64_t docshellID;
+ aNewEntry->GetDocshellID(&docshellID);
+
+ uint64_t otherID;
+ for (int32_t i = 0; i < mChildren.Count(); ++i) {
+ if (mChildren[i] && NS_SUCCEEDED(mChildren[i]->GetDocshellID(&otherID)) &&
+ docshellID == otherID) {
+ mChildren[i]->SetParent(nullptr);
+ mChildren.ReplaceObjectAt(aNewEntry, i);
+ return aNewEntry->SetParent(this);
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSHEntry::AddChildShell(nsIDocShellTreeItem* aShell)
+{
+ NS_ASSERTION(aShell, "Null child shell added to history entry");
+ mShared->mChildShells.AppendObject(aShell);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::ChildShellAt(int32_t aIndex, nsIDocShellTreeItem** aShell)
+{
+ NS_IF_ADDREF(*aShell = mShared->mChildShells.SafeObjectAt(aIndex));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::ClearChildShells()
+{
+ mShared->mChildShells.Clear();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetRefreshURIList(nsIMutableArray** aList)
+{
+ NS_IF_ADDREF(*aList = mShared->mRefreshURIList);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetRefreshURIList(nsIMutableArray* aList)
+{
+ mShared->mRefreshURIList = aList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SyncPresentationState()
+{
+ return mShared->SyncPresentationState();
+}
+
+void
+nsSHEntry::RemoveFromBFCacheSync()
+{
+ mShared->RemoveFromBFCacheSync();
+}
+
+void
+nsSHEntry::RemoveFromBFCacheAsync()
+{
+ mShared->RemoveFromBFCacheAsync();
+}
+
+nsDocShellEditorData*
+nsSHEntry::ForgetEditorData()
+{
+ // XXX jlebar Check how this is used.
+ return mShared->mEditorData.forget();
+}
+
+void
+nsSHEntry::SetEditorData(nsDocShellEditorData* aData)
+{
+ NS_ASSERTION(!(aData && mShared->mEditorData),
+ "We're going to overwrite an owning ref!");
+ if (mShared->mEditorData != aData) {
+ mShared->mEditorData = aData;
+ }
+}
+
+bool
+nsSHEntry::HasDetachedEditor()
+{
+ return mShared->mEditorData != nullptr;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetStateData(nsIStructuredCloneContainer** aContainer)
+{
+ NS_ENSURE_ARG_POINTER(aContainer);
+ NS_IF_ADDREF(*aContainer = mStateData);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetStateData(nsIStructuredCloneContainer* aContainer)
+{
+ mStateData = aContainer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::IsDynamicallyAdded(bool* aAdded)
+{
+ *aAdded = mShared->mDynamicallyCreated;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::HasDynamicallyAddedChild(bool* aAdded)
+{
+ *aAdded = false;
+ for (int32_t i = 0; i < mChildren.Count(); ++i) {
+ nsISHEntry* entry = mChildren[i];
+ if (entry) {
+ entry->IsDynamicallyAdded(aAdded);
+ if (*aAdded) {
+ break;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetDocshellID(uint64_t* aID)
+{
+ *aID = mShared->mDocShellID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetDocshellID(uint64_t aID)
+{
+ mShared->mDocShellID = aID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetLastTouched(uint32_t* aLastTouched)
+{
+ *aLastTouched = mShared->mLastTouched;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetLastTouched(uint32_t aLastTouched)
+{
+ mShared->mLastTouched = aLastTouched;
+ return NS_OK;
+}
diff --git a/docshell/shistory/nsSHEntry.h b/docshell/shistory/nsSHEntry.h
new file mode 100644
index 000000000..2383e38c4
--- /dev/null
+++ b/docshell/shistory/nsSHEntry.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSHEntry_h
+#define nsSHEntry_h
+
+// Helper Classes
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+
+// Interfaces needed
+#include "nsISHEntry.h"
+#include "nsISHContainer.h"
+
+class nsSHEntryShared;
+class nsIInputStream;
+class nsIURI;
+
+class nsSHEntry final : public nsISHEntry,
+ public nsISHContainer,
+ public nsISHEntryInternal
+{
+public:
+ nsSHEntry();
+ nsSHEntry(const nsSHEntry& aOther);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISHENTRY
+ NS_DECL_NSISHENTRYINTERNAL
+ NS_DECL_NSISHCONTAINER
+
+ void DropPresentationState();
+
+ static nsresult Startup();
+ static void Shutdown();
+
+private:
+ ~nsSHEntry();
+
+ // We share the state in here with other SHEntries which correspond to the
+ // same document.
+ RefPtr<nsSHEntryShared> mShared;
+
+ // See nsSHEntry.idl for comments on these members.
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ bool mLoadReplace;
+ nsCOMPtr<nsIURI> mReferrerURI;
+ uint32_t mReferrerPolicy;
+ nsString mTitle;
+ nsCOMPtr<nsIInputStream> mPostData;
+ uint32_t mLoadType;
+ uint32_t mID;
+ int32_t mScrollPositionX;
+ int32_t mScrollPositionY;
+ nsISHEntry* mParent;
+ nsCOMArray<nsISHEntry> mChildren;
+ bool mURIWasModified;
+ nsCOMPtr<nsIStructuredCloneContainer> mStateData;
+ bool mIsSrcdocEntry;
+ bool mScrollRestorationIsManual;
+ nsString mSrcdocData;
+ nsCOMPtr<nsIURI> mBaseURI;
+};
+
+#endif /* nsSHEntry_h */
diff --git a/docshell/shistory/nsSHEntryShared.cpp b/docshell/shistory/nsSHEntryShared.cpp
new file mode 100644
index 000000000..645294408
--- /dev/null
+++ b/docshell/shistory/nsSHEntryShared.cpp
@@ -0,0 +1,385 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSHEntryShared.h"
+
+#include "nsIDOMDocument.h"
+#include "nsISHistory.h"
+#include "nsISHistoryInternal.h"
+#include "nsIDocument.h"
+#include "nsIWebNavigation.h"
+#include "nsIContentViewer.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsDocShellEditorData.h"
+#include "nsThreadUtils.h"
+#include "nsILayoutHistoryState.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Preferences.h"
+#include "nsArray.h"
+
+namespace dom = mozilla::dom;
+
+namespace {
+
+uint64_t gSHEntrySharedID = 0;
+
+} // namespace
+
+#define CONTENT_VIEWER_TIMEOUT_SECONDS "browser.sessionhistory.contentViewerTimeout"
+// Default this to time out unused content viewers after 30 minutes
+#define CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT (30 * 60)
+
+typedef nsExpirationTracker<nsSHEntryShared, 3> HistoryTrackerBase;
+class HistoryTracker final : public HistoryTrackerBase
+{
+public:
+ explicit HistoryTracker(uint32_t aTimeout)
+ : HistoryTrackerBase(1000 * aTimeout / 2, "HistoryTracker")
+ {
+ }
+
+protected:
+ virtual void NotifyExpired(nsSHEntryShared* aObj)
+ {
+ RemoveObject(aObj);
+ aObj->Expire();
+ }
+};
+
+static HistoryTracker* gHistoryTracker = nullptr;
+
+void
+nsSHEntryShared::EnsureHistoryTracker()
+{
+ if (!gHistoryTracker) {
+ // nsExpirationTracker doesn't allow one to change the timer period,
+ // so just set it once when the history tracker is used for the first time.
+ gHistoryTracker = new HistoryTracker(
+ mozilla::Preferences::GetUint(CONTENT_VIEWER_TIMEOUT_SECONDS,
+ CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT));
+ }
+}
+
+void
+nsSHEntryShared::Shutdown()
+{
+ delete gHistoryTracker;
+ gHistoryTracker = nullptr;
+}
+
+nsSHEntryShared::nsSHEntryShared()
+ : mDocShellID(0)
+ , mIsFrameNavigation(false)
+ , mSaveLayoutState(true)
+ , mSticky(true)
+ , mDynamicallyCreated(false)
+ , mLastTouched(0)
+ , mID(gSHEntrySharedID++)
+ , mExpired(false)
+ , mViewerBounds(0, 0, 0, 0)
+{
+}
+
+nsSHEntryShared::~nsSHEntryShared()
+{
+ RemoveFromExpirationTracker();
+
+#ifdef DEBUG
+ if (gHistoryTracker) {
+ // 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()) != nullptr) {
+ NS_ASSERTION(elem != this, "Found dead entry still in the tracker!");
+ }
+ }
+#endif
+
+ if (mContentViewer) {
+ RemoveFromBFCacheSync();
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsSHEntryShared, nsIBFCacheEntry, nsIMutationObserver)
+
+already_AddRefed<nsSHEntryShared>
+nsSHEntryShared::Duplicate(nsSHEntryShared* aEntry)
+{
+ RefPtr<nsSHEntryShared> newEntry = new nsSHEntryShared();
+
+ newEntry->mDocShellID = aEntry->mDocShellID;
+ newEntry->mChildShells.AppendObjects(aEntry->mChildShells);
+ newEntry->mTriggeringPrincipal = aEntry->mTriggeringPrincipal;
+ newEntry->mPrincipalToInherit = aEntry->mPrincipalToInherit;
+ 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 (gHistoryTracker && 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()
+{
+ RefPtr<nsSHEntryShared> kungFuDeathGrip = this;
+
+ if (mDocument) {
+ mDocument->SetBFCacheEntry(nullptr);
+ mDocument->RemoveMutationObserver(this);
+ mDocument = nullptr;
+ }
+ if (mContentViewer) {
+ mContentViewer->ClearHistoryEntry();
+ }
+
+ RemoveFromExpirationTracker();
+ mContentViewer = nullptr;
+ mSticky = true;
+ mWindowState = nullptr;
+ mViewerBounds.SetRect(0, 0, 0, 0);
+ mChildShells.Clear();
+ mRefreshURIList = nullptr;
+ mEditorData = nullptr;
+}
+
+void
+nsSHEntryShared::Expire()
+{
+ // This entry has timed out. If we still have a content viewer, we need to
+ // evict it.
+ if (!mContentViewer) {
+ return;
+ }
+ nsCOMPtr<nsIDocShell> 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) {
+ EnsureHistoryTracker();
+ 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 mozilla::Runnable
+{
+public:
+ DestroyViewerEvent(nsIContentViewer* aViewer, nsIDocument* aDocument)
+ : mViewer(aViewer)
+ , mDocument(aDocument)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ 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(uint64_t* aID)
+{
+ *aID = mID;
+ return NS_OK;
+}
+
+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,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aNewValue)
+{
+}
+
+void
+nsSHEntryShared::NativeAnonymousChildListChange(nsIDocument* aDocument,
+ nsIContent* aContent,
+ bool aIsRemove)
+{
+}
+
+void
+nsSHEntryShared::AttributeChanged(nsIDocument* aDocument,
+ dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue)
+{
+ RemoveFromBFCacheAsync();
+}
+
+void
+nsSHEntryShared::ContentAppended(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t /* unused */)
+{
+ RemoveFromBFCacheAsync();
+}
+
+void
+nsSHEntryShared::ContentInserted(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t /* unused */)
+{
+ RemoveFromBFCacheAsync();
+}
+
+void
+nsSHEntryShared::ContentRemoved(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer,
+ nsIContent* aPreviousSibling)
+{
+ RemoveFromBFCacheAsync();
+}
+
+void
+nsSHEntryShared::ParentChainChanged(nsIContent* aContent)
+{
+}
diff --git a/docshell/shistory/nsSHEntryShared.h b/docshell/shistory/nsSHEntryShared.h
new file mode 100644
index 000000000..0a40691ff
--- /dev/null
+++ b/docshell/shistory/nsSHEntryShared.h
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#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"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+
+class nsSHEntry;
+class nsISHEntry;
+class nsIDocument;
+class nsIContentViewer;
+class nsIDocShellTreeItem;
+class nsILayoutHistoryState;
+class nsDocShellEditorData;
+class nsIMutableArray;
+
+// 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 final
+ : public nsIBFCacheEntry
+ , public nsIMutationObserver
+{
+public:
+ static void EnsureHistoryTracker();
+ static void Shutdown();
+
+ nsSHEntryShared();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMUTATIONOBSERVER
+ NS_DECL_NSIBFCACHEENTRY
+
+ nsExpirationState *GetExpirationState() { return &mExpirationState; }
+
+private:
+ ~nsSHEntryShared();
+
+ friend class nsSHEntry;
+
+ friend class HistoryTracker;
+
+ 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.
+ uint64_t mDocShellID;
+ nsCOMArray<nsIDocShellTreeItem> mChildShells;
+ nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
+ nsCOMPtr<nsIPrincipal> mPrincipalToInherit;
+ nsCString mContentType;
+ bool mIsFrameNavigation;
+ bool mSaveLayoutState;
+ bool mSticky;
+ bool mDynamicallyCreated;
+ nsCOMPtr<nsISupports> mCacheKey;
+ uint32_t mLastTouched;
+
+ // These members aren't copied by nsSHEntryShared::Duplicate() because
+ // they're specific to a particular content viewer.
+ uint64_t mID;
+ nsCOMPtr<nsIContentViewer> mContentViewer;
+ nsCOMPtr<nsIDocument> mDocument;
+ nsCOMPtr<nsILayoutHistoryState> mLayoutHistoryState;
+ bool mExpired;
+ nsCOMPtr<nsISupports> mWindowState;
+ nsIntRect mViewerBounds;
+ nsCOMPtr<nsIMutableArray> mRefreshURIList;
+ nsExpirationState mExpirationState;
+ nsAutoPtr<nsDocShellEditorData> mEditorData;
+};
+
+#endif
diff --git a/docshell/shistory/nsSHTransaction.cpp b/docshell/shistory/nsSHTransaction.cpp
new file mode 100644
index 000000000..061454d35
--- /dev/null
+++ b/docshell/shistory/nsSHTransaction.cpp
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSHTransaction.h"
+#include "nsISHEntry.h"
+
+nsSHTransaction::nsSHTransaction()
+ : mPersist(true)
+ , mPrev(nullptr)
+{
+}
+
+nsSHTransaction::~nsSHTransaction()
+{
+}
+
+NS_IMPL_ADDREF(nsSHTransaction)
+NS_IMPL_RELEASE(nsSHTransaction)
+
+NS_INTERFACE_MAP_BEGIN(nsSHTransaction)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISHTransaction)
+ NS_INTERFACE_MAP_ENTRY(nsISHTransaction)
+NS_INTERFACE_MAP_END
+
+NS_IMETHODIMP
+nsSHTransaction::Create(nsISHEntry* aSHEntry, nsISHTransaction* aPrev)
+{
+ SetSHEntry(aSHEntry);
+ if (aPrev) {
+ aPrev->SetNext(this);
+ }
+
+ SetPrev(aPrev);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHTransaction::GetSHEntry(nsISHEntry** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mSHEntry;
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHTransaction::SetSHEntry(nsISHEntry* aSHEntry)
+{
+ mSHEntry = aSHEntry;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHTransaction::GetNext(nsISHTransaction** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mNext;
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHTransaction::SetNext(nsISHTransaction* aNext)
+{
+ if (aNext) {
+ NS_ENSURE_SUCCESS(aNext->SetPrev(this), NS_ERROR_FAILURE);
+ }
+
+ mNext = aNext;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHTransaction::SetPrev(nsISHTransaction* aPrev)
+{
+ /* This is weak reference to parent. Do not Addref it */
+ mPrev = aPrev;
+ return NS_OK;
+}
+
+nsresult
+nsSHTransaction::GetPrev(nsISHTransaction** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mPrev;
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHTransaction::SetPersist(bool aPersist)
+{
+ mPersist = aPersist;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHTransaction::GetPersist(bool* aPersist)
+{
+ NS_ENSURE_ARG_POINTER(aPersist);
+
+ *aPersist = mPersist;
+ return NS_OK;
+}
diff --git a/docshell/shistory/nsSHTransaction.h b/docshell/shistory/nsSHTransaction.h
new file mode 100644
index 000000000..334aa3074
--- /dev/null
+++ b/docshell/shistory/nsSHTransaction.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSHTransaction_h
+#define nsSHTransaction_h
+
+// Helper Classes
+#include "nsCOMPtr.h"
+
+// Needed interfaces
+#include "nsISHTransaction.h"
+
+class nsISHEntry;
+
+class nsSHTransaction : public nsISHTransaction
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISHTRANSACTION
+
+ nsSHTransaction();
+
+protected:
+ virtual ~nsSHTransaction();
+
+protected:
+ bool mPersist;
+
+ nsISHTransaction* mPrev; // Weak Reference
+ nsCOMPtr<nsISHTransaction> mNext;
+ nsCOMPtr<nsISHEntry> mSHEntry;
+};
+
+#endif /* nsSHTransaction_h */
diff --git a/docshell/shistory/nsSHistory.cpp b/docshell/shistory/nsSHistory.cpp
new file mode 100644
index 000000000..7c148ffcc
--- /dev/null
+++ b/docshell/shistory/nsSHistory.cpp
@@ -0,0 +1,1932 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSHistory.h"
+#include <algorithm>
+
+// Helper Classes
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPtr.h"
+
+// Interfaces Needed
+#include "nsILayoutHistoryState.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellLoadInfo.h"
+#include "nsISHContainer.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIURI.h"
+#include "nsIContentViewer.h"
+#include "nsIObserverService.h"
+#include "prclist.h"
+#include "mozilla/Services.h"
+#include "nsTArray.h"
+#include "nsCOMArray.h"
+#include "nsDocShell.h"
+#include "mozilla/Attributes.h"
+#include "nsISHEntry.h"
+#include "nsISHTransaction.h"
+#include "nsISHistoryListener.h"
+#include "nsComponentManagerUtils.h"
+
+// For calculating max history entries and max cachable contentviewers
+#include "prsystem.h"
+#include "mozilla/MathAlgorithms.h"
+
+using namespace mozilla;
+
+#define PREF_SHISTORY_SIZE "browser.sessionhistory.max_entries"
+#define PREF_SHISTORY_MAX_TOTAL_VIEWERS "browser.sessionhistory.max_total_viewers"
+
+static const char* kObservedPrefs[] = {
+ PREF_SHISTORY_SIZE,
+ PREF_SHISTORY_MAX_TOTAL_VIEWERS,
+ nullptr
+};
+
+static int32_t gHistoryMaxSize = 50;
+// Max viewers allowed per SHistory objects
+static const int32_t 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
+int32_t nsSHistory::sHistoryMaxTotalViewers = -1;
+
+// 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 uint32_t gTouchCounter = 0;
+
+static LazyLogModule gSHistoryLog("nsSHistory");
+
+#define LOG(format) MOZ_LOG(gSHistoryLog, mozilla::LogLevel::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 (MOZ_LOG_TEST(gSHistoryLog, LogLevel::Debug)) { \
+ nsAutoCString _specStr(NS_LITERAL_CSTRING("(null)"));\
+ if (uri) { \
+ _specStr = uri->GetSpecOrDefault(); \
+ } \
+ 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 (MOZ_LOG_TEST(gSHistoryLog, LogLevel::Debug)) { \
+ nsCOMPtr<nsIURI> uri; \
+ shentry->GetURI(getter_AddRefs(uri)); \
+ LOG_SPEC(format, uri); \
+ } \
+ PR_END_MACRO
+
+// Iterates over all registered session history listeners.
+#define ITERATE_LISTENERS(body) \
+ PR_BEGIN_MACRO \
+ { \
+ nsAutoTObserverArray<nsWeakPtr, 2>::EndLimitedIterator \
+ iter(mListeners); \
+ while (iter.HasMore()) { \
+ nsCOMPtr<nsISHistoryListener> listener = \
+ do_QueryReferent(iter.GetNext()); \
+ if (listener) { \
+ body \
+ } \
+ } \
+ } \
+ PR_END_MACRO
+
+// Calls a given method on all registered session history listeners.
+#define NOTIFY_LISTENERS(method, args) \
+ ITERATE_LISTENERS( \
+ listener->method args; \
+ );
+
+// Calls a given method on all registered session history listeners.
+// Listeners may return 'false' to cancel an action so make sure that we
+// set the return value to 'false' if one of the listeners wants to cancel.
+#define NOTIFY_LISTENERS_CANCELABLE(method, retval, args) \
+ PR_BEGIN_MACRO \
+ { \
+ bool canceled = false; \
+ retval = true; \
+ ITERATE_LISTENERS( \
+ listener->method args; \
+ if (!retval) { \
+ canceled = true; \
+ } \
+ ); \
+ if (canceled) { \
+ retval = false; \
+ } \
+ } \
+ PR_END_MACRO
+
+enum HistCmd
+{
+ HIST_CMD_BACK,
+ HIST_CMD_FORWARD,
+ HIST_CMD_GOTOINDEX,
+ HIST_CMD_RELOAD
+};
+
+class nsSHistoryObserver final : public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ nsSHistoryObserver() {}
+
+protected:
+ ~nsSHistoryObserver() {}
+};
+
+StaticRefPtr<nsSHistoryObserver> gObserver;
+
+NS_IMPL_ISUPPORTS(nsSHistoryObserver, nsIObserver)
+
+NS_IMETHODIMP
+nsSHistoryObserver::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+{
+ if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ nsSHistory::UpdatePrefs();
+ nsSHistory::GloballyEvictContentViewers();
+ } else if (!strcmp(aTopic, "cacheservice:empty-cache") ||
+ !strcmp(aTopic, "memory-pressure")) {
+ nsSHistory::GloballyEvictAllContentViewers();
+ }
+
+ return NS_OK;
+}
+
+namespace {
+
+already_AddRefed<nsIContentViewer>
+GetContentViewerForTransaction(nsISHTransaction* aTrans)
+{
+ nsCOMPtr<nsISHEntry> entry;
+ aTrans->GetSHEntry(getter_AddRefs(entry));
+ if (!entry) {
+ return nullptr;
+ }
+
+ 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(nullptr);
+ ownerEntry->SyncPresentationState();
+ viewer->Destroy();
+ }
+}
+
+} // namespace
+
+nsSHistory::nsSHistory()
+ : mIndex(-1)
+ , mLength(0)
+ , mRequestedIndex(-1)
+ , mIsPartial(false)
+ , mGlobalIndexOffset(0)
+ , mEntriesInFollowingPartialHistories(0)
+ , mRootDocShell(nullptr)
+{
+ // Add this new SHistory object to the list
+ PR_APPEND_LINK(this, &gSHistoryList);
+}
+
+nsSHistory::~nsSHistory()
+{
+ // Remove this SHistory object from the list
+ PR_REMOVE_LINK(this);
+}
+
+NS_IMPL_ADDREF(nsSHistory)
+NS_IMPL_RELEASE(nsSHistory)
+
+NS_INTERFACE_MAP_BEGIN(nsSHistory)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISHistory)
+ NS_INTERFACE_MAP_ENTRY(nsISHistory)
+ NS_INTERFACE_MAP_ENTRY(nsIWebNavigation)
+ NS_INTERFACE_MAP_ENTRY(nsISHistoryInternal)
+NS_INTERFACE_MAP_END
+
+// static
+uint32_t
+nsSHistory::CalcMaxTotalViewers()
+{
+ // Calculate an estimate of how many ContentViewers we should cache based
+ // on RAM. This assumes that the average ContentViewer is 4MB (conservative)
+ // and caps the max at 8 ContentViewers
+ //
+ // TODO: Should we split the cache memory betw. ContentViewer caching and
+ // nsCacheService?
+ //
+ // RAM ContentViewers
+ // -----------------------
+ // 32 Mb 0
+ // 64 Mb 1
+ // 128 Mb 2
+ // 256 Mb 3
+ // 512 Mb 5
+ // 1024 Mb 8
+ // 2048 Mb 8
+ // 4096 Mb 8
+ uint64_t bytes = PR_GetPhysicalMemorySize();
+
+ if (bytes == 0) {
+ return 0;
+ }
+
+ // Conversion from unsigned int64_t to double doesn't work on all platforms.
+ // We need to truncate the value at INT64_MAX to make sure we don't
+ // overflow.
+ if (bytes > INT64_MAX) {
+ bytes = INT64_MAX;
+ }
+
+ double kBytesD = (double)(bytes >> 10);
+
+ // This is essentially the same calculation as for nsCacheService,
+ // except that we divide the final memory calculation by 4, since
+ // we assume each ContentViewer takes on average 4MB
+ uint32_t viewers = 0;
+ double x = std::log(kBytesD) / std::log(2.0) - 14;
+ if (x > 0) {
+ viewers = (uint32_t)(x * x - x + 2.001); // add .001 for rounding
+ viewers /= 4;
+ }
+
+ // Cap it off at 8 max
+ if (viewers > 8) {
+ viewers = 8;
+ }
+ return viewers;
+}
+
+// static
+void
+nsSHistory::UpdatePrefs()
+{
+ Preferences::GetInt(PREF_SHISTORY_SIZE, &gHistoryMaxSize);
+ Preferences::GetInt(PREF_SHISTORY_MAX_TOTAL_VIEWERS,
+ &sHistoryMaxTotalViewers);
+ // 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
+nsresult
+nsSHistory::Startup()
+{
+ UpdatePrefs();
+
+ // The goal of this is to unbreak users who have inadvertently set their
+ // session history size to less than the default value.
+ int32_t defaultHistoryMaxSize =
+ Preferences::GetDefaultInt(PREF_SHISTORY_SIZE, 50);
+ if (gHistoryMaxSize < defaultHistoryMaxSize) {
+ gHistoryMaxSize = defaultHistoryMaxSize;
+ }
+
+ // Allow the user to override the max total number of cached viewers,
+ // but keep the per SHistory cached viewer limit constant
+ if (!gObserver) {
+ gObserver = new nsSHistoryObserver();
+ Preferences::AddStrongObservers(gObserver, kObservedPrefs);
+
+ nsCOMPtr<nsIObserverService> obsSvc =
+ mozilla::services::GetObserverService();
+ if (obsSvc) {
+ // Observe empty-cache notifications so tahat clearing the disk/memory
+ // cache will also evict all content viewers.
+ obsSvc->AddObserver(gObserver, "cacheservice:empty-cache", false);
+
+ // Same for memory-pressure notifications
+ obsSvc->AddObserver(gObserver, "memory-pressure", false);
+ }
+ }
+
+ // Initialize the global list of all SHistory objects
+ PR_INIT_CLIST(&gSHistoryList);
+ return NS_OK;
+}
+
+// static
+void
+nsSHistory::Shutdown()
+{
+ if (gObserver) {
+ Preferences::RemoveObservers(gObserver, kObservedPrefs);
+ nsCOMPtr<nsIObserverService> obsSvc =
+ mozilla::services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->RemoveObserver(gObserver, "cacheservice:empty-cache");
+ obsSvc->RemoveObserver(gObserver, "memory-pressure");
+ }
+ gObserver = nullptr;
+ }
+}
+
+/* Add an entry to the History list at mIndex and
+ * increment the index to point to the new entry
+ */
+NS_IMETHODIMP
+nsSHistory::AddEntry(nsISHEntry* aSHEntry, bool aPersist)
+{
+ NS_ENSURE_ARG(aSHEntry);
+
+ nsCOMPtr<nsISHTransaction> currentTxn;
+
+ if (mListRoot) {
+ GetTransactionAtIndex(mIndex, getter_AddRefs(currentTxn));
+ }
+
+ bool currentPersist = true;
+ if (currentTxn) {
+ currentTxn->GetPersist(&currentPersist);
+ }
+
+ int32_t currentIndex = mIndex;
+
+ if (!currentPersist) {
+ NOTIFY_LISTENERS(OnHistoryReplaceEntry, (currentIndex));
+ NS_ENSURE_SUCCESS(currentTxn->SetSHEntry(aSHEntry), NS_ERROR_FAILURE);
+ currentTxn->SetPersist(aPersist);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISHTransaction> txn(
+ do_CreateInstance(NS_SHTRANSACTION_CONTRACTID));
+ NS_ENSURE_TRUE(txn, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIURI> uri;
+ aSHEntry->GetURI(getter_AddRefs(uri));
+ NOTIFY_LISTENERS(OnHistoryNewEntry, (uri, currentIndex));
+
+ // If a listener has changed mIndex, we need to get currentTxn again,
+ // otherwise we'll be left at an inconsistent state (see bug 320742)
+ if (currentIndex != mIndex) {
+ GetTransactionAtIndex(mIndex, getter_AddRefs(currentTxn));
+ }
+
+ // Set the ShEntry and parent for the transaction. setting the
+ // parent will properly set the parent child relationship
+ txn->SetPersist(aPersist);
+ NS_ENSURE_SUCCESS(txn->Create(aSHEntry, currentTxn), NS_ERROR_FAILURE);
+
+ // A little tricky math here... Basically when adding an object regardless of
+ // what the length was before, it should always be set back to the current and
+ // lop off the forward.
+ mLength = (++mIndex + 1);
+ NOTIFY_LISTENERS(OnLengthChange, (mLength));
+
+ // Much like how mLength works above, when changing our entries, all following
+ // partial histories should be purged, so we just reset the number to zero.
+ mEntriesInFollowingPartialHistories = 0;
+
+ // If this is the very first transaction, initialize the list
+ if (!mListRoot) {
+ mListRoot = txn;
+ }
+
+ // Purge History list if it is too long
+ if (gHistoryMaxSize >= 0 && mLength > gHistoryMaxSize) {
+ PurgeHistory(mLength - gHistoryMaxSize);
+ }
+
+ RemoveDynEntries(mIndex - 1, mIndex);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::GetIsPartial(bool* aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mIsPartial;
+ return NS_OK;
+}
+
+/* Get size of the history list */
+NS_IMETHODIMP
+nsSHistory::GetCount(int32_t* aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::GetGlobalCount(int32_t* aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mGlobalIndexOffset + mLength + mEntriesInFollowingPartialHistories;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::GetGlobalIndexOffset(int32_t* aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mGlobalIndexOffset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::OnPartialSessionHistoryActive(int32_t aGlobalLength, int32_t aTargetIndex)
+{
+ NS_ENSURE_TRUE(mIsPartial, NS_ERROR_UNEXPECTED);
+
+ int32_t extraLength = aGlobalLength - mLength - mGlobalIndexOffset;
+ NS_ENSURE_TRUE(extraLength >= 0, NS_ERROR_UNEXPECTED);
+
+ if (extraLength != mEntriesInFollowingPartialHistories) {
+ mEntriesInFollowingPartialHistories = extraLength;
+ }
+
+ if (mIndex == aTargetIndex) {
+ // TODO When we finish OnPartialSessionHistoryDeactive, we'll need to active
+ // the suspended document here.
+
+ // Fire location change to update canGoBack / canGoForward.
+ NS_DispatchToCurrentThread(NewRunnableMethod(static_cast<nsDocShell*>(mRootDocShell),
+ &nsDocShell::FireDummyOnLocationChange));
+ return NS_OK;
+ }
+
+ return LoadEntry(aTargetIndex, nsIDocShellLoadInfo::loadHistory,
+ HIST_CMD_GOTOINDEX);
+}
+
+NS_IMETHODIMP
+nsSHistory::OnPartialSessionHistoryDeactive()
+{
+ NS_ENSURE_TRUE(mIsPartial, NS_ERROR_UNEXPECTED);
+
+ // TODO We need to suspend current document first. Much like what happens when
+ // loading a new page. Move the ownership of the document to nsISHEntry or so.
+ return NS_OK;
+}
+
+/* Get index of the history list */
+NS_IMETHODIMP
+nsSHistory::GetIndex(int32_t* aResult)
+{
+ NS_PRECONDITION(aResult, "null out param?");
+ *aResult = mIndex;
+ return NS_OK;
+}
+
+/* Get the requestedIndex */
+NS_IMETHODIMP
+nsSHistory::GetRequestedIndex(int32_t* aResult)
+{
+ NS_PRECONDITION(aResult, "null out param?");
+ *aResult = mRequestedIndex;
+ return NS_OK;
+}
+
+/* Get the entry at a given index */
+NS_IMETHODIMP
+nsSHistory::GetEntryAtIndex(int32_t aIndex, bool aModifyIndex,
+ nsISHEntry** aResult)
+{
+ nsresult rv;
+ nsCOMPtr<nsISHTransaction> txn;
+
+ /* GetTransactionAtIndex ensures aResult is valid and validates aIndex */
+ rv = GetTransactionAtIndex(aIndex, getter_AddRefs(txn));
+ if (NS_SUCCEEDED(rv) && txn) {
+ // Get the Entry from the transaction
+ rv = txn->GetSHEntry(aResult);
+ if (NS_SUCCEEDED(rv) && (*aResult)) {
+ // Set mIndex to the requested index, if asked to do so..
+ if (aModifyIndex) {
+ mIndex = aIndex;
+ }
+ }
+ }
+ return rv;
+}
+
+/* Get the transaction at a given index */
+NS_IMETHODIMP
+nsSHistory::GetTransactionAtIndex(int32_t aIndex, nsISHTransaction** aResult)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ if (mLength <= 0 || aIndex < 0 || aIndex >= mLength) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mListRoot) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aIndex == 0) {
+ *aResult = mListRoot;
+ NS_ADDREF(*aResult);
+ return NS_OK;
+ }
+
+ int32_t cnt = 0;
+ nsCOMPtr<nsISHTransaction> tempPtr;
+ rv = GetRootTransaction(getter_AddRefs(tempPtr));
+ if (NS_FAILED(rv) || !tempPtr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ while (true) {
+ nsCOMPtr<nsISHTransaction> ptr;
+ rv = tempPtr->GetNext(getter_AddRefs(ptr));
+ if (NS_SUCCEEDED(rv) && ptr) {
+ cnt++;
+ if (cnt == aIndex) {
+ ptr.forget(aResult);
+ break;
+ } else {
+ tempPtr = ptr;
+ continue;
+ }
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return NS_OK;
+}
+
+/* Get the index of a given entry */
+NS_IMETHODIMP
+nsSHistory::GetIndexOfEntry(nsISHEntry* aSHEntry, int32_t* aResult)
+{
+ NS_ENSURE_ARG(aSHEntry);
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = -1;
+
+ if (mLength <= 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsISHTransaction> currentTxn;
+ int32_t cnt = 0;
+
+ nsresult rv = GetRootTransaction(getter_AddRefs(currentTxn));
+ if (NS_FAILED(rv) || !currentTxn) {
+ return NS_ERROR_FAILURE;
+ }
+
+ while (true) {
+ nsCOMPtr<nsISHEntry> entry;
+ rv = currentTxn->GetSHEntry(getter_AddRefs(entry));
+ if (NS_FAILED(rv) || !entry) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aSHEntry == entry) {
+ *aResult = cnt;
+ break;
+ }
+
+ rv = currentTxn->GetNext(getter_AddRefs(currentTxn));
+ if (NS_FAILED(rv) || !currentTxn) {
+ return NS_ERROR_FAILURE;
+ }
+
+ cnt++;
+ }
+
+ return NS_OK;
+}
+
+#ifdef DEBUG
+nsresult
+nsSHistory::PrintHistory()
+{
+ nsCOMPtr<nsISHTransaction> txn;
+ int32_t index = 0;
+ nsresult rv;
+
+ if (!mListRoot) {
+ return NS_ERROR_FAILURE;
+ }
+
+ txn = mListRoot;
+
+ while (1) {
+ if (!txn) {
+ break;
+ }
+ nsCOMPtr<nsISHEntry> entry;
+ rv = txn->GetSHEntry(getter_AddRefs(entry));
+ if (NS_FAILED(rv) && !entry) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsILayoutHistoryState> layoutHistoryState;
+ nsCOMPtr<nsIURI> uri;
+ nsXPIDLString title;
+
+ entry->GetLayoutHistoryState(getter_AddRefs(layoutHistoryState));
+ entry->GetURI(getter_AddRefs(uri));
+ entry->GetTitle(getter_Copies(title));
+
+#if 0
+ nsAutoCString url;
+ if (uri) {
+ uri->GetSpec(url);
+ }
+
+ printf("**** SH Transaction #%d, Entry = %x\n", index, entry.get());
+ printf("\t\t URL = %s\n", url.get());
+
+ printf("\t\t Title = %s\n", NS_LossyConvertUTF16toASCII(title).get());
+ printf("\t\t layout History Data = %x\n", layoutHistoryState.get());
+#endif
+
+ nsCOMPtr<nsISHTransaction> next;
+ rv = txn->GetNext(getter_AddRefs(next));
+ if (NS_SUCCEEDED(rv) && next) {
+ txn = next;
+ index++;
+ continue;
+ } else {
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+#endif
+
+NS_IMETHODIMP
+nsSHistory::GetRootTransaction(nsISHTransaction** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mListRoot;
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+/* Get the max size of the history list */
+NS_IMETHODIMP
+nsSHistory::GetMaxLength(int32_t* aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = gHistoryMaxSize;
+ return NS_OK;
+}
+
+/* Set the max size of the history list */
+NS_IMETHODIMP
+nsSHistory::SetMaxLength(int32_t aMaxSize)
+{
+ if (aMaxSize < 0) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ gHistoryMaxSize = aMaxSize;
+ if (mLength > aMaxSize) {
+ PurgeHistory(mLength - aMaxSize);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::PurgeHistory(int32_t aEntries)
+{
+ if (mLength <= 0 || aEntries <= 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aEntries = std::min(aEntries, mLength);
+
+ bool purgeHistory = true;
+ NOTIFY_LISTENERS_CANCELABLE(OnHistoryPurge, purgeHistory,
+ (aEntries, &purgeHistory));
+
+ if (!purgeHistory) {
+ // Listener asked us not to purge
+ return NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA;
+ }
+
+ int32_t cnt = 0;
+ while (cnt < aEntries) {
+ nsCOMPtr<nsISHTransaction> nextTxn;
+ if (mListRoot) {
+ mListRoot->GetNext(getter_AddRefs(nextTxn));
+ mListRoot->SetNext(nullptr);
+ }
+ mListRoot = nextTxn;
+ if (mListRoot) {
+ mListRoot->SetPrev(nullptr);
+ }
+ cnt++;
+ }
+ mLength -= cnt;
+ mIndex -= cnt;
+ NOTIFY_LISTENERS(OnLengthChange, (mLength));
+
+ // All following partial histories will be deleted in this case.
+ mEntriesInFollowingPartialHistories = 0;
+
+ // Now if we were not at the end of the history, mIndex could have
+ // become far too negative. If so, just set it to -1.
+ if (mIndex < -1) {
+ mIndex = -1;
+ }
+
+ if (mRootDocShell) {
+ mRootDocShell->HistoryPurged(cnt);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::AddSHistoryListener(nsISHistoryListener* aListener)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ // Check if the listener supports Weak Reference. This is a must.
+ // This listener functionality is used by embedders and we want to
+ // have the right ownership with who ever listens to SHistory
+ nsWeakPtr listener = do_GetWeakReference(aListener);
+ if (!listener) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mListeners.AppendElementUnlessExists(listener) ?
+ NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP
+nsSHistory::RemoveSHistoryListener(nsISHistoryListener* aListener)
+{
+ // Make sure the listener that wants to be removed is the
+ // one we have in store.
+ nsWeakPtr listener = do_GetWeakReference(aListener);
+ mListeners.RemoveElement(listener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::SetPartialSHistoryListener(nsIPartialSHistoryListener* aListener)
+{
+ mPartialHistoryListener = do_GetWeakReference(aListener);
+ return NS_OK;
+}
+
+/* Replace an entry in the History list at a particular index.
+ * Do not update index or count.
+ */
+NS_IMETHODIMP
+nsSHistory::ReplaceEntry(int32_t aIndex, nsISHEntry* aReplaceEntry)
+{
+ NS_ENSURE_ARG(aReplaceEntry);
+ nsresult rv;
+ nsCOMPtr<nsISHTransaction> currentTxn;
+
+ if (!mListRoot) {
+ // Session History is not initialised.
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = GetTransactionAtIndex(aIndex, getter_AddRefs(currentTxn));
+
+ if (currentTxn) {
+ NOTIFY_LISTENERS(OnHistoryReplaceEntry, (aIndex));
+
+ // Set the replacement entry in the transaction
+ rv = currentTxn->SetSHEntry(aReplaceEntry);
+ rv = currentTxn->SetPersist(true);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSHistory::NotifyOnHistoryReload(nsIURI* aReloadURI, uint32_t aReloadFlags,
+ bool* aCanReload)
+{
+ NOTIFY_LISTENERS_CANCELABLE(OnHistoryReload, *aCanReload,
+ (aReloadURI, aReloadFlags, aCanReload));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::EvictOutOfRangeContentViewers(int32_t aIndex)
+{
+ // Check our per SHistory object limit in the currently navigated SHistory
+ EvictOutOfRangeWindowContentViewers(aIndex);
+ // Check our total limit across all SHistory objects
+ 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.
+ nsCOMPtr<nsISHTransaction> trans = mListRoot;
+ while (trans) {
+ EvictContentViewerForTransaction(trans);
+
+ nsCOMPtr<nsISHTransaction> temp = trans;
+ temp->GetNext(getter_AddRefs(trans));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::GetCanGoBack(bool* aCanGoBack)
+{
+ NS_ENSURE_ARG_POINTER(aCanGoBack);
+
+ if (mGlobalIndexOffset) {
+ *aCanGoBack = true;
+ return NS_OK;
+ }
+
+ int32_t index = -1;
+ NS_ENSURE_SUCCESS(GetIndex(&index), NS_ERROR_FAILURE);
+ if (index > 0) {
+ *aCanGoBack = true;
+ return NS_OK;
+ }
+
+ *aCanGoBack = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::GetCanGoForward(bool* aCanGoForward)
+{
+ NS_ENSURE_ARG_POINTER(aCanGoForward);
+
+ if (mEntriesInFollowingPartialHistories) {
+ *aCanGoForward = true;
+ return NS_OK;
+ }
+
+ int32_t index = -1;
+ int32_t count = -1;
+ NS_ENSURE_SUCCESS(GetIndex(&index), NS_ERROR_FAILURE);
+ NS_ENSURE_SUCCESS(GetCount(&count), NS_ERROR_FAILURE);
+ if (index >= 0 && index < (count - 1)) {
+ *aCanGoForward = true;
+ return NS_OK;
+ }
+
+ *aCanGoForward = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::GoBack()
+{
+ bool canGoBack = false;
+
+ GetCanGoBack(&canGoBack);
+ if (!canGoBack) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return LoadEntry(mIndex - 1, nsIDocShellLoadInfo::loadHistory, HIST_CMD_BACK);
+}
+
+NS_IMETHODIMP
+nsSHistory::GoForward()
+{
+ bool canGoForward = false;
+
+ GetCanGoForward(&canGoForward);
+ if (!canGoForward) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return LoadEntry(mIndex + 1, nsIDocShellLoadInfo::loadHistory,
+ HIST_CMD_FORWARD);
+}
+
+NS_IMETHODIMP
+nsSHistory::Reload(uint32_t aReloadFlags)
+{
+ nsDocShellInfoLoadType loadType;
+ if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY &&
+ aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) {
+ loadType = nsIDocShellLoadInfo::loadReloadBypassProxyAndCache;
+ } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY) {
+ loadType = nsIDocShellLoadInfo::loadReloadBypassProxy;
+ } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) {
+ loadType = nsIDocShellLoadInfo::loadReloadBypassCache;
+ } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_CHARSET_CHANGE) {
+ loadType = nsIDocShellLoadInfo::loadReloadCharsetChange;
+ } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_ALLOW_MIXED_CONTENT) {
+ loadType = nsIDocShellLoadInfo::loadReloadMixedContent;
+ } else {
+ loadType = nsIDocShellLoadInfo::loadReloadNormal;
+ }
+
+ // We are reloading. Send Reload notifications.
+ // nsDocShellLoadFlagType is not public, where as nsIWebNavigation
+ // is public. So send the reload notifications with the
+ // nsIWebNavigation flags.
+ bool canNavigate = true;
+ nsCOMPtr<nsIURI> currentURI;
+ GetCurrentURI(getter_AddRefs(currentURI));
+ NOTIFY_LISTENERS_CANCELABLE(OnHistoryReload, canNavigate,
+ (currentURI, aReloadFlags, &canNavigate));
+ if (!canNavigate) {
+ return NS_OK;
+ }
+
+ return LoadEntry(mIndex, loadType, HIST_CMD_RELOAD);
+}
+
+NS_IMETHODIMP
+nsSHistory::ReloadCurrentEntry()
+{
+ // Notify listeners
+ bool canNavigate = true;
+ nsCOMPtr<nsIURI> currentURI;
+ GetCurrentURI(getter_AddRefs(currentURI));
+ NOTIFY_LISTENERS_CANCELABLE(OnHistoryGotoIndex, canNavigate,
+ (mIndex, currentURI, &canNavigate));
+ if (!canNavigate) {
+ return NS_OK;
+ }
+
+ return LoadEntry(mIndex, nsIDocShellLoadInfo::loadHistory, HIST_CMD_RELOAD);
+}
+
+void
+nsSHistory::EvictOutOfRangeWindowContentViewers(int32_t aIndex)
+{
+ // 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
+ // + *
+ //
+ // 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.)
+
+ if (aIndex < 0) {
+ return;
+ }
+ NS_ENSURE_TRUE_VOID(aIndex < mLength);
+
+ // Calculate the range that's safe from eviction.
+ int32_t startSafeIndex = std::max(0, aIndex - gHistoryMaxViewers);
+ int32_t endSafeIndex = std::min(mLength, aIndex + gHistoryMaxViewers);
+
+ LOG(("EvictOutOfRangeWindowContentViewers(index=%d), "
+ "mLength=%d. Safe range [%d, %d]",
+ aIndex, mLength, startSafeIndex, endSafeIndex));
+
+ // The content viewers in range aIndex -/+ gHistoryMaxViewers will not be
+ // evicted. Collect a set of them so we don't accidentally evict one of them
+ // if it appears outside this range.
+ nsCOMArray<nsIContentViewer> safeViewers;
+ nsCOMPtr<nsISHTransaction> trans;
+ GetTransactionAtIndex(startSafeIndex, getter_AddRefs(trans));
+ for (int32_t i = startSafeIndex; trans && i <= endSafeIndex; i++) {
+ nsCOMPtr<nsIContentViewer> viewer = GetContentViewerForTransaction(trans);
+ safeViewers.AppendObject(viewer);
+ nsCOMPtr<nsISHTransaction> temp = trans;
+ temp->GetNext(getter_AddRefs(trans));
+ }
+
+ // Walk the SHistory list and evict any content viewers that aren't safe.
+ GetTransactionAtIndex(0, getter_AddRefs(trans));
+ while (trans) {
+ nsCOMPtr<nsIContentViewer> viewer = GetContentViewerForTransaction(trans);
+ if (safeViewers.IndexOf(viewer) == -1) {
+ EvictContentViewerForTransaction(trans);
+ }
+
+ nsCOMPtr<nsISHTransaction> temp = trans;
+ temp->GetNext(getter_AddRefs(trans));
+ }
+}
+
+namespace {
+
+class TransactionAndDistance
+{
+public:
+ TransactionAndDistance(nsISHTransaction* aTrans, uint32_t aDist)
+ : mTransaction(aTrans)
+ , mLastTouched(0)
+ , 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?");
+ }
+ }
+
+ 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;
+ uint32_t mLastTouched;
+ int32_t mDistance;
+};
+
+} // namespace
+
+// 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;
+
+ PRCList* listEntry = PR_LIST_HEAD(&gSHistoryList);
+ while (listEntry != &gSHistoryList) {
+ nsSHistory* shist = static_cast<nsSHistory*>(listEntry);
+
+ // 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;
+
+ // 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.
+ //
+ int32_t startIndex = std::max(0, shist->mIndex - gHistoryMaxViewers);
+ int32_t endIndex = std::min(shist->mLength - 1,
+ shist->mIndex + gHistoryMaxViewers);
+ nsCOMPtr<nsISHTransaction> trans;
+ shist->GetTransactionAtIndex(startIndex, getter_AddRefs(trans));
+ for (int32_t 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 (uint32_t j = 0; j < shTransactions.Length(); j++) {
+ TransactionAndDistance& container = shTransactions[j];
+ if (container.mViewer == contentViewer) {
+ container.mDistance = std::min(container.mDistance,
+ DeprecatedAbs(i - shist->mIndex));
+ found = true;
+ break;
+ }
+ }
+
+ // If we didn't find a TransactionAndDistance for this content viewer,
+ // make a new one.
+ if (!found) {
+ TransactionAndDistance container(trans,
+ DeprecatedAbs(i - shist->mIndex));
+ shTransactions.AppendElement(container);
+ }
+ }
+
+ nsCOMPtr<nsISHTransaction> temp = trans;
+ temp->GetNext(getter_AddRefs(trans));
+ }
+
+ // We've found all the transactions belonging to shist which have viewers.
+ // Add those transactions to our global list and move on.
+ transactions.AppendElements(shTransactions);
+ listEntry = PR_NEXT_LINK(shist);
+ }
+
+ // We now have collected all cached content viewers. First check that we
+ // have enough that we actually need to evict some.
+ if ((int32_t)transactions.Length() <= sHistoryMaxTotalViewers) {
+ return;
+ }
+
+ // 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();
+
+ for (int32_t i = transactions.Length() - 1; i >= sHistoryMaxTotalViewers;
+ --i) {
+ EvictContentViewerForTransaction(transactions[i].mTransaction);
+ }
+}
+
+nsresult
+nsSHistory::EvictExpiredContentViewerForEntry(nsIBFCacheEntry* aEntry)
+{
+ int32_t startIndex = std::max(0, mIndex - gHistoryMaxViewers);
+ int32_t endIndex = std::min(mLength - 1, mIndex + gHistoryMaxViewers);
+ nsCOMPtr<nsISHTransaction> trans;
+ GetTransactionAtIndex(startIndex, getter_AddRefs(trans));
+
+ int32_t i;
+ for (i = startIndex; trans && i <= endIndex; ++i) {
+ nsCOMPtr<nsISHEntry> entry;
+ trans->GetSHEntry(getter_AddRefs(entry));
+
+ // Does entry have the same BFCacheEntry as the argument to this method?
+ if (entry->HasBFCacheEntry(aEntry)) {
+ break;
+ }
+
+ nsCOMPtr<nsISHTransaction> temp = trans;
+ temp->GetNext(getter_AddRefs(trans));
+ }
+ if (i > endIndex) {
+ return NS_OK;
+ }
+
+ if (i == mIndex) {
+ NS_WARNING("How did the current SHEntry expire?");
+ return NS_OK;
+ }
+
+ EvictContentViewerForTransaction(trans);
+
+ return NS_OK;
+}
+
+// Evicts all content viewers in all history objects. This is very
+// inefficient, because it requires a linear search through all SHistory
+// objects for each viewer to be evicted. However, this method is called
+// infrequently -- only when the disk or memory cache is cleared.
+
+// static
+void
+nsSHistory::GloballyEvictAllContentViewers()
+{
+ int32_t maxViewers = sHistoryMaxTotalViewers;
+ sHistoryMaxTotalViewers = 0;
+ GloballyEvictContentViewers();
+ sHistoryMaxTotalViewers = maxViewers;
+}
+
+void
+GetDynamicChildren(nsISHContainer* aContainer,
+ nsTArray<uint64_t>& aDocshellIDs,
+ bool aOnlyTopLevelDynamic)
+{
+ int32_t count = 0;
+ aContainer->GetChildCount(&count);
+ for (int32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsISHEntry> child;
+ aContainer->GetChildAt(i, getter_AddRefs(child));
+ if (child) {
+ bool dynAdded = false;
+ child->IsDynamicallyAdded(&dynAdded);
+ if (dynAdded) {
+ uint64_t docshellID = 0;
+ child->GetDocshellID(&docshellID);
+ aDocshellIDs.AppendElement(docshellID);
+ }
+ if (!dynAdded || !aOnlyTopLevelDynamic) {
+ nsCOMPtr<nsISHContainer> childAsContainer = do_QueryInterface(child);
+ if (childAsContainer) {
+ GetDynamicChildren(childAsContainer, aDocshellIDs,
+ aOnlyTopLevelDynamic);
+ }
+ }
+ }
+ }
+}
+
+bool
+RemoveFromSessionHistoryContainer(nsISHContainer* aContainer,
+ nsTArray<uint64_t>& aDocshellIDs)
+{
+ nsCOMPtr<nsISHEntry> root = do_QueryInterface(aContainer);
+ NS_ENSURE_TRUE(root, false);
+
+ bool didRemove = false;
+ int32_t childCount = 0;
+ aContainer->GetChildCount(&childCount);
+ for (int32_t i = childCount - 1; i >= 0; --i) {
+ nsCOMPtr<nsISHEntry> child;
+ aContainer->GetChildAt(i, getter_AddRefs(child));
+ if (child) {
+ uint64_t docshelldID = 0;
+ child->GetDocshellID(&docshelldID);
+ if (aDocshellIDs.Contains(docshelldID)) {
+ didRemove = true;
+ aContainer->RemoveChild(child);
+ } else {
+ nsCOMPtr<nsISHContainer> container = do_QueryInterface(child);
+ if (container) {
+ bool childRemoved =
+ RemoveFromSessionHistoryContainer(container, aDocshellIDs);
+ if (childRemoved) {
+ didRemove = true;
+ }
+ }
+ }
+ }
+ }
+ return didRemove;
+}
+
+bool
+RemoveChildEntries(nsISHistory* aHistory, int32_t aIndex,
+ nsTArray<uint64_t>& aEntryIDs)
+{
+ nsCOMPtr<nsISHEntry> rootHE;
+ aHistory->GetEntryAtIndex(aIndex, false, getter_AddRefs(rootHE));
+ nsCOMPtr<nsISHContainer> root = do_QueryInterface(rootHE);
+ return root ? RemoveFromSessionHistoryContainer(root, aEntryIDs) : false;
+}
+
+bool
+IsSameTree(nsISHEntry* aEntry1, nsISHEntry* aEntry2)
+{
+ if (!aEntry1 && !aEntry2) {
+ return true;
+ }
+ if ((!aEntry1 && aEntry2) || (aEntry1 && !aEntry2)) {
+ return false;
+ }
+ uint32_t id1, id2;
+ aEntry1->GetID(&id1);
+ aEntry2->GetID(&id2);
+ if (id1 != id2) {
+ return false;
+ }
+
+ nsCOMPtr<nsISHContainer> container1 = do_QueryInterface(aEntry1);
+ nsCOMPtr<nsISHContainer> container2 = do_QueryInterface(aEntry2);
+ int32_t count1, count2;
+ container1->GetChildCount(&count1);
+ container2->GetChildCount(&count2);
+ // We allow null entries in the end of the child list.
+ int32_t count = std::max(count1, count2);
+ for (int32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsISHEntry> child1, child2;
+ container1->GetChildAt(i, getter_AddRefs(child1));
+ container2->GetChildAt(i, getter_AddRefs(child2));
+ if (!IsSameTree(child1, child2)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool
+nsSHistory::RemoveDuplicate(int32_t aIndex, bool aKeepNext)
+{
+ NS_ASSERTION(aIndex >= 0, "aIndex must be >= 0!");
+ NS_ASSERTION(aIndex != 0 || aKeepNext,
+ "If we're removing index 0 we must be keeping the next");
+ NS_ASSERTION(aIndex != mIndex, "Shouldn't remove mIndex!");
+ int32_t compareIndex = aKeepNext ? aIndex + 1 : aIndex - 1;
+ nsCOMPtr<nsISHEntry> root1, root2;
+ GetEntryAtIndex(aIndex, false, getter_AddRefs(root1));
+ GetEntryAtIndex(compareIndex, false, getter_AddRefs(root2));
+ if (IsSameTree(root1, root2)) {
+ nsCOMPtr<nsISHTransaction> txToRemove, txToKeep, txNext, txPrev;
+ GetTransactionAtIndex(aIndex, getter_AddRefs(txToRemove));
+ GetTransactionAtIndex(compareIndex, getter_AddRefs(txToKeep));
+ if (!txToRemove) {
+ return false;
+ }
+ NS_ENSURE_TRUE(txToKeep, false);
+ txToRemove->GetNext(getter_AddRefs(txNext));
+ txToRemove->GetPrev(getter_AddRefs(txPrev));
+ txToRemove->SetNext(nullptr);
+ txToRemove->SetPrev(nullptr);
+ if (aKeepNext) {
+ if (txPrev) {
+ txPrev->SetNext(txToKeep);
+ } else {
+ txToKeep->SetPrev(nullptr);
+ }
+ } else {
+ txToKeep->SetNext(txNext);
+ }
+
+ if (aIndex == 0 && aKeepNext) {
+ NS_ASSERTION(txToRemove == mListRoot,
+ "Transaction at index 0 should be mListRoot!");
+ // We're removing the very first session history transaction!
+ mListRoot = txToKeep;
+ }
+ if (mRootDocShell) {
+ static_cast<nsDocShell*>(mRootDocShell)->HistoryTransactionRemoved(aIndex);
+ }
+
+ // Adjust our indices to reflect the removed transaction
+ if (mIndex > aIndex) {
+ mIndex = mIndex - 1;
+ }
+
+ // NB: If the transaction we are removing is the transaction currently
+ // being navigated to (mRequestedIndex) then we adjust the index
+ // only if we're not keeping the next entry (because if we are keeping
+ // the next entry (because the current is a duplicate of the next), then
+ // that entry slides into the spot that we're currently pointing to.
+ // We don't do this adjustment for mIndex because mIndex cannot equal
+ // aIndex.
+
+ // NB: We don't need to guard on mRequestedIndex being nonzero here,
+ // because either they're strictly greater than aIndex which is at least
+ // zero, or they are equal to aIndex in which case aKeepNext must be true
+ // if aIndex is zero.
+ if (mRequestedIndex > aIndex || (mRequestedIndex == aIndex && !aKeepNext)) {
+ mRequestedIndex = mRequestedIndex - 1;
+ }
+ --mLength;
+ mEntriesInFollowingPartialHistories = 0;
+ NOTIFY_LISTENERS(OnLengthChange, (mLength));
+ return true;
+ }
+ return false;
+}
+
+NS_IMETHODIMP_(void)
+nsSHistory::RemoveEntries(nsTArray<uint64_t>& aIDs, int32_t aStartIndex)
+{
+ int32_t index = aStartIndex;
+ while (index >= 0 && RemoveChildEntries(this, --index, aIDs)) {
+ }
+ int32_t minIndex = index;
+ index = aStartIndex;
+ while (index >= 0 && RemoveChildEntries(this, index++, aIDs)) {
+ }
+
+ // We need to remove duplicate nsSHEntry trees.
+ bool didRemove = false;
+ while (index > minIndex) {
+ if (index != mIndex) {
+ didRemove = RemoveDuplicate(index, index < mIndex) || didRemove;
+ }
+ --index;
+ }
+ if (didRemove && mRootDocShell) {
+ NS_DispatchToCurrentThread(NewRunnableMethod(static_cast<nsDocShell*>(mRootDocShell),
+ &nsDocShell::FireDummyOnLocationChange));
+ }
+}
+
+void
+nsSHistory::RemoveDynEntries(int32_t aOldIndex, int32_t aNewIndex)
+{
+ // Search for the entries which are in the current index,
+ // but not in the new one.
+ nsCOMPtr<nsISHEntry> originalSH;
+ GetEntryAtIndex(aOldIndex, false, getter_AddRefs(originalSH));
+ nsCOMPtr<nsISHContainer> originalContainer = do_QueryInterface(originalSH);
+ AutoTArray<uint64_t, 16> toBeRemovedEntries;
+ if (originalContainer) {
+ nsTArray<uint64_t> originalDynDocShellIDs;
+ GetDynamicChildren(originalContainer, originalDynDocShellIDs, true);
+ if (originalDynDocShellIDs.Length()) {
+ nsCOMPtr<nsISHEntry> currentSH;
+ GetEntryAtIndex(aNewIndex, false, getter_AddRefs(currentSH));
+ nsCOMPtr<nsISHContainer> newContainer = do_QueryInterface(currentSH);
+ if (newContainer) {
+ nsTArray<uint64_t> newDynDocShellIDs;
+ GetDynamicChildren(newContainer, newDynDocShellIDs, false);
+ for (uint32_t i = 0; i < originalDynDocShellIDs.Length(); ++i) {
+ if (!newDynDocShellIDs.Contains(originalDynDocShellIDs[i])) {
+ toBeRemovedEntries.AppendElement(originalDynDocShellIDs[i]);
+ }
+ }
+ }
+ }
+ }
+ if (toBeRemovedEntries.Length()) {
+ RemoveEntries(toBeRemovedEntries, aOldIndex);
+ }
+}
+
+NS_IMETHODIMP
+nsSHistory::UpdateIndex()
+{
+ // Update the actual index with the right value.
+ if (mIndex != mRequestedIndex && mRequestedIndex != -1) {
+ RemoveDynEntries(mIndex, mRequestedIndex);
+ mIndex = mRequestedIndex;
+ }
+
+ mRequestedIndex = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::Stop(uint32_t aStopFlags)
+{
+ // Not implemented
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::GetDocument(nsIDOMDocument** aDocument)
+{
+ // Not implemented
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::GetCurrentURI(nsIURI** aResultURI)
+{
+ NS_ENSURE_ARG_POINTER(aResultURI);
+ nsresult rv;
+
+ nsCOMPtr<nsISHEntry> currentEntry;
+ rv = GetEntryAtIndex(mIndex, false, getter_AddRefs(currentEntry));
+ if (NS_FAILED(rv) && !currentEntry) {
+ return rv;
+ }
+ rv = currentEntry->GetURI(aResultURI);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSHistory::GetReferringURI(nsIURI** aURI)
+{
+ *aURI = nullptr;
+ // Not implemented
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::SetSessionHistory(nsISHistory* aSessionHistory)
+{
+ // Not implemented
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::GetSessionHistory(nsISHistory** aSessionHistory)
+{
+ // Not implemented
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::LoadURIWithOptions(const char16_t* aURI,
+ uint32_t aLoadFlags,
+ nsIURI* aReferringURI,
+ uint32_t aReferrerPolicy,
+ nsIInputStream* aPostStream,
+ nsIInputStream* aExtraHeaderStream,
+ nsIURI* aBaseURI)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::SetOriginAttributesBeforeLoading(JS::HandleValue aOriginAttributes)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::LoadURI(const char16_t* aURI,
+ uint32_t aLoadFlags,
+ nsIURI* aReferringURI,
+ nsIInputStream* aPostStream,
+ nsIInputStream* aExtraHeaderStream)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::GotoIndex(int32_t aGlobalIndex)
+{
+ // We provide abstraction of grouped session history for nsIWebNavigation
+ // functions, so the index passed in here is global index.
+ return LoadEntry(aGlobalIndex - mGlobalIndexOffset, nsIDocShellLoadInfo::loadHistory,
+ HIST_CMD_GOTOINDEX);
+}
+
+nsresult
+nsSHistory::LoadNextPossibleEntry(int32_t aNewIndex, long aLoadType,
+ uint32_t aHistCmd)
+{
+ mRequestedIndex = -1;
+ if (aNewIndex < mIndex) {
+ return LoadEntry(aNewIndex - 1, aLoadType, aHistCmd);
+ }
+ if (aNewIndex > mIndex) {
+ return LoadEntry(aNewIndex + 1, aLoadType, aHistCmd);
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSHistory::LoadEntry(int32_t aIndex, long aLoadType, uint32_t aHistCmd)
+{
+ if (!mRootDocShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aIndex < 0 || aIndex >= mLength) {
+ if (aIndex + mGlobalIndexOffset < 0) {
+ // The global index is negative.
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aIndex - mLength >= mEntriesInFollowingPartialHistories) {
+ // The global index exceeds max possible value.
+ return NS_ERROR_FAILURE;
+ }
+
+ // The global index is valid. trigger cross browser navigation.
+ nsCOMPtr<nsIPartialSHistoryListener> listener =
+ do_QueryReferent(mPartialHistoryListener);
+ if (!listener) {
+ return NS_ERROR_FAILURE;
+ }
+ return listener->OnRequestCrossBrowserNavigation(aIndex + mGlobalIndexOffset);
+ }
+
+ // Keep note of requested history index in mRequestedIndex.
+ mRequestedIndex = aIndex;
+
+ nsCOMPtr<nsISHEntry> prevEntry;
+ GetEntryAtIndex(mIndex, false, getter_AddRefs(prevEntry));
+
+ nsCOMPtr<nsISHEntry> nextEntry;
+ GetEntryAtIndex(mRequestedIndex, false, getter_AddRefs(nextEntry));
+ if (!nextEntry || !prevEntry) {
+ mRequestedIndex = -1;
+ return NS_ERROR_FAILURE;
+ }
+
+ // Remember that this entry is getting loaded at this point in the sequence
+ nsCOMPtr<nsISHEntryInternal> entryInternal = do_QueryInterface(nextEntry);
+
+ if (entryInternal) {
+ entryInternal->SetLastTouched(++gTouchCounter);
+ }
+
+ // Send appropriate listener notifications
+ bool canNavigate = true;
+ // Get the uri for the entry we are about to visit
+ nsCOMPtr<nsIURI> nextURI;
+ nextEntry->GetURI(getter_AddRefs(nextURI));
+
+ if (aHistCmd == HIST_CMD_BACK) {
+ // We are going back one entry. Send GoBack notifications
+ NOTIFY_LISTENERS_CANCELABLE(OnHistoryGoBack, canNavigate,
+ (nextURI, &canNavigate));
+ } else if (aHistCmd == HIST_CMD_FORWARD) {
+ // We are going forward. Send GoForward notification
+ NOTIFY_LISTENERS_CANCELABLE(OnHistoryGoForward, canNavigate,
+ (nextURI, &canNavigate));
+ } else if (aHistCmd == HIST_CMD_GOTOINDEX) {
+ // We are going somewhere else. This is not reload either
+ NOTIFY_LISTENERS_CANCELABLE(OnHistoryGotoIndex, canNavigate,
+ (aIndex, nextURI, &canNavigate));
+ }
+
+ if (!canNavigate) {
+ // If the listener asked us not to proceed with
+ // the operation, simply return.
+ mRequestedIndex = -1;
+ return NS_OK; // XXX Maybe I can return some other error code?
+ }
+
+ if (mRequestedIndex == mIndex) {
+ // Possibly a reload case
+ return InitiateLoad(nextEntry, mRootDocShell, aLoadType);
+ }
+
+ // Going back or forward.
+ bool differenceFound = false;
+ nsresult rv = LoadDifferingEntries(prevEntry, nextEntry, mRootDocShell,
+ aLoadType, differenceFound);
+ if (!differenceFound) {
+ // We did not find any differences. Go further in the history.
+ return LoadNextPossibleEntry(aIndex, aLoadType, aHistCmd);
+ }
+
+ return rv;
+}
+
+nsresult
+nsSHistory::LoadDifferingEntries(nsISHEntry* aPrevEntry, nsISHEntry* aNextEntry,
+ nsIDocShell* aParent, long aLoadType,
+ bool& aDifferenceFound)
+{
+ if (!aPrevEntry || !aNextEntry || !aParent) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult result = NS_OK;
+ uint32_t prevID, nextID;
+
+ aPrevEntry->GetID(&prevID);
+ aNextEntry->GetID(&nextID);
+
+ // Check the IDs to verify if the pages are different.
+ if (prevID != nextID) {
+ aDifferenceFound = true;
+
+ // Set the Subframe flag if not navigating the root docshell.
+ aNextEntry->SetIsSubFrame(aParent != mRootDocShell);
+ return InitiateLoad(aNextEntry, aParent, aLoadType);
+ }
+
+ // The entries are the same, so compare any child frames
+ int32_t pcnt = 0;
+ int32_t ncnt = 0;
+ int32_t dsCount = 0;
+ nsCOMPtr<nsISHContainer> prevContainer(do_QueryInterface(aPrevEntry));
+ nsCOMPtr<nsISHContainer> nextContainer(do_QueryInterface(aNextEntry));
+
+ if (!prevContainer || !nextContainer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ prevContainer->GetChildCount(&pcnt);
+ nextContainer->GetChildCount(&ncnt);
+ aParent->GetChildCount(&dsCount);
+
+ // Create an array for child docshells.
+ nsCOMArray<nsIDocShell> docshells;
+ for (int32_t i = 0; i < dsCount; ++i) {
+ nsCOMPtr<nsIDocShellTreeItem> treeItem;
+ aParent->GetChildAt(i, getter_AddRefs(treeItem));
+ nsCOMPtr<nsIDocShell> shell = do_QueryInterface(treeItem);
+ if (shell) {
+ docshells.AppendElement(shell.forget());
+ }
+ }
+
+ // Search for something to load next.
+ for (int32_t i = 0; i < ncnt; ++i) {
+ // First get an entry which may cause a new page to be loaded.
+ nsCOMPtr<nsISHEntry> nChild;
+ nextContainer->GetChildAt(i, getter_AddRefs(nChild));
+ if (!nChild) {
+ continue;
+ }
+ uint64_t docshellID = 0;
+ nChild->GetDocshellID(&docshellID);
+
+ // Then find the associated docshell.
+ nsIDocShell* dsChild = nullptr;
+ int32_t count = docshells.Count();
+ for (int32_t j = 0; j < count; ++j) {
+ uint64_t shellID = 0;
+ nsIDocShell* shell = docshells[j];
+ shell->GetHistoryID(&shellID);
+ if (shellID == docshellID) {
+ dsChild = shell;
+ break;
+ }
+ }
+ if (!dsChild) {
+ continue;
+ }
+
+ // Then look at the previous entries to see if there was
+ // an entry for the docshell.
+ nsCOMPtr<nsISHEntry> pChild;
+ for (int32_t k = 0; k < pcnt; ++k) {
+ nsCOMPtr<nsISHEntry> child;
+ prevContainer->GetChildAt(k, getter_AddRefs(child));
+ if (child) {
+ uint64_t dID = 0;
+ child->GetDocshellID(&dID);
+ if (dID == docshellID) {
+ pChild = child;
+ break;
+ }
+ }
+ }
+
+ // Finally recursively call this method.
+ // This will either load a new page to shell or some subshell or
+ // do nothing.
+ LoadDifferingEntries(pChild, nChild, dsChild, aLoadType, aDifferenceFound);
+ }
+ return result;
+}
+
+nsresult
+nsSHistory::InitiateLoad(nsISHEntry* aFrameEntry, nsIDocShell* aFrameDS,
+ long aLoadType)
+{
+ NS_ENSURE_STATE(aFrameDS && aFrameEntry);
+
+ nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
+
+ /* Set the loadType in the SHEntry too to what was passed on.
+ * This will be passed on to child subframes later in nsDocShell,
+ * so that proper loadType is maintained through out a frameset
+ */
+ aFrameEntry->SetLoadType(aLoadType);
+ aFrameDS->CreateLoadInfo(getter_AddRefs(loadInfo));
+
+ loadInfo->SetLoadType(aLoadType);
+ loadInfo->SetSHEntry(aFrameEntry);
+
+ nsCOMPtr<nsIURI> originalURI;
+ aFrameEntry->GetOriginalURI(getter_AddRefs(originalURI));
+ loadInfo->SetOriginalURI(originalURI);
+
+ bool loadReplace;
+ aFrameEntry->GetLoadReplace(&loadReplace);
+ loadInfo->SetLoadReplace(loadReplace);
+
+ nsCOMPtr<nsIURI> nextURI;
+ aFrameEntry->GetURI(getter_AddRefs(nextURI));
+ // Time to initiate a document load
+ return aFrameDS->LoadURI(nextURI, loadInfo,
+ nsIWebNavigation::LOAD_FLAGS_NONE, false);
+
+}
+
+NS_IMETHODIMP
+nsSHistory::SetRootDocShell(nsIDocShell* aDocShell)
+{
+ mRootDocShell = aDocShell;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::GetSHistoryEnumerator(nsISimpleEnumerator** aEnumerator)
+{
+ NS_ENSURE_ARG_POINTER(aEnumerator);
+ RefPtr<nsSHEnumerator> iterator = new nsSHEnumerator(this);
+ iterator.forget(aEnumerator);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::OnAttachGroupedSessionHistory(int32_t aOffset)
+{
+ NS_ENSURE_TRUE(!mIsPartial, NS_ERROR_UNEXPECTED);
+ NS_ENSURE_TRUE(aOffset >= 0, NS_ERROR_ILLEGAL_VALUE);
+
+ mIsPartial = true;
+ mGlobalIndexOffset = aOffset;
+
+ // The last attached history is always at the end of the group.
+ mEntriesInFollowingPartialHistories = 0;
+
+ // Setting grouped history info may change canGoBack / canGoForward.
+ // Send a location change to update these values.
+ NS_DispatchToCurrentThread(NewRunnableMethod(static_cast<nsDocShell*>(mRootDocShell),
+ &nsDocShell::FireDummyOnLocationChange));
+ return NS_OK;
+
+}
+
+nsSHEnumerator::nsSHEnumerator(nsSHistory* aSHistory) : mIndex(-1)
+{
+ mSHistory = aSHistory;
+}
+
+nsSHEnumerator::~nsSHEnumerator()
+{
+ mSHistory = nullptr;
+}
+
+NS_IMPL_ISUPPORTS(nsSHEnumerator, nsISimpleEnumerator)
+
+NS_IMETHODIMP
+nsSHEnumerator::HasMoreElements(bool* aReturn)
+{
+ int32_t cnt;
+ *aReturn = false;
+ mSHistory->GetCount(&cnt);
+ if (mIndex >= -1 && mIndex < (cnt - 1)) {
+ *aReturn = true;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEnumerator::GetNext(nsISupports** aItem)
+{
+ NS_ENSURE_ARG_POINTER(aItem);
+ int32_t cnt = 0;
+
+ nsresult result = NS_ERROR_FAILURE;
+ mSHistory->GetCount(&cnt);
+ if (mIndex < (cnt - 1)) {
+ mIndex++;
+ nsCOMPtr<nsISHEntry> hEntry;
+ result = mSHistory->GetEntryAtIndex(mIndex, false, getter_AddRefs(hEntry));
+ if (hEntry) {
+ result = CallQueryInterface(hEntry, aItem);
+ }
+ }
+ return result;
+}
diff --git a/docshell/shistory/nsSHistory.h b/docshell/shistory/nsSHistory.h
new file mode 100644
index 000000000..cddc27269
--- /dev/null
+++ b/docshell/shistory/nsSHistory.h
@@ -0,0 +1,133 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSHistory_h
+#define nsSHistory_h
+
+#include "nsCOMPtr.h"
+#include "nsISHistory.h"
+#include "nsISHistoryInternal.h"
+#include "nsIWebNavigation.h"
+#include "nsISimpleEnumerator.h"
+#include "nsTObserverArray.h"
+#include "nsWeakPtr.h"
+#include "nsIPartialSHistoryListener.h"
+
+#include "prclist.h"
+
+class nsIDocShell;
+class nsSHEnumerator;
+class nsSHistoryObserver;
+class nsISHEntry;
+class nsISHTransaction;
+
+class nsSHistory final : public PRCList,
+ public nsISHistory,
+ public nsISHistoryInternal,
+ public nsIWebNavigation
+{
+public:
+ nsSHistory();
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISHISTORY
+ NS_DECL_NSISHISTORYINTERNAL
+ NS_DECL_NSIWEBNAVIGATION
+
+ // One time initialization method called upon docshell module construction
+ static nsresult Startup();
+ static void Shutdown();
+ static void UpdatePrefs();
+
+ // Max number of total cached content viewers. If the pref
+ // browser.sessionhistory.max_total_viewers is negative, then
+ // this value is calculated based on the total amount of memory.
+ // Otherwise, it comes straight from the pref.
+ static uint32_t GetMaxTotalViewers() { return sHistoryMaxTotalViewers; }
+
+private:
+ virtual ~nsSHistory();
+ friend class nsSHEnumerator;
+ friend class nsSHistoryObserver;
+
+ // Could become part of nsIWebNavigation
+ NS_IMETHOD GetTransactionAtIndex(int32_t aIndex, nsISHTransaction** aResult);
+ nsresult LoadDifferingEntries(nsISHEntry* aPrevEntry, nsISHEntry* aNextEntry,
+ nsIDocShell* aRootDocShell, long aLoadType,
+ bool& aDifferenceFound);
+ nsresult InitiateLoad(nsISHEntry* aFrameEntry, nsIDocShell* aFrameDS,
+ long aLoadType);
+
+ NS_IMETHOD LoadEntry(int32_t aIndex, long aLoadType, uint32_t aHistCmd);
+
+#ifdef DEBUG
+ nsresult PrintHistory();
+#endif
+
+ // Evict content viewers in this window which don't lie in the "safe" range
+ // around aIndex.
+ void EvictOutOfRangeWindowContentViewers(int32_t aIndex);
+ static void GloballyEvictContentViewers();
+ static void GloballyEvictAllContentViewers();
+
+ // Calculates a max number of total
+ // content viewers to cache, based on amount of total memory
+ static uint32_t CalcMaxTotalViewers();
+
+ void RemoveDynEntries(int32_t aOldIndex, int32_t aNewIndex);
+
+ nsresult LoadNextPossibleEntry(int32_t aNewIndex, long aLoadType,
+ uint32_t aHistCmd);
+
+ // aIndex is the index of the transaction which may be removed.
+ // If aKeepNext is true, aIndex is compared to aIndex + 1,
+ // otherwise comparison is done to aIndex - 1.
+ bool RemoveDuplicate(int32_t aIndex, bool aKeepNext);
+
+ nsCOMPtr<nsISHTransaction> mListRoot;
+ int32_t mIndex;
+ int32_t mLength;
+ int32_t mRequestedIndex;
+
+ // Set to true if attached to a grouped session history.
+ bool mIsPartial;
+
+ // The number of entries before this session history object.
+ int32_t mGlobalIndexOffset;
+
+ // The number of entries after this session history object.
+ int32_t mEntriesInFollowingPartialHistories;
+
+ // Session History listeners
+ nsAutoTObserverArray<nsWeakPtr, 2> mListeners;
+
+ // Partial session history listener
+ nsWeakPtr mPartialHistoryListener;
+
+ // Weak reference. Do not refcount this.
+ nsIDocShell* mRootDocShell;
+
+ // Max viewers allowed total, across all SHistory objects
+ static int32_t sHistoryMaxTotalViewers;
+};
+
+class nsSHEnumerator : public nsISimpleEnumerator
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ explicit nsSHEnumerator(nsSHistory* aHistory);
+
+protected:
+ friend class nsSHistory;
+ virtual ~nsSHEnumerator();
+
+private:
+ int32_t mIndex;
+ nsSHistory* mSHistory;
+};
+
+#endif /* nsSHistory */