diff options
Diffstat (limited to 'docshell/shistory')
-rw-r--r-- | docshell/shistory/moz.build | 40 | ||||
-rw-r--r-- | docshell/shistory/nsIBFCacheEntry.idl | 18 | ||||
-rw-r--r-- | docshell/shistory/nsIGroupedSHistory.idl | 47 | ||||
-rw-r--r-- | docshell/shistory/nsIPartialSHistory.idl | 62 | ||||
-rw-r--r-- | docshell/shistory/nsIPartialSHistoryListener.idl | 24 | ||||
-rw-r--r-- | docshell/shistory/nsISHContainer.idl | 49 | ||||
-rw-r--r-- | docshell/shistory/nsISHEntry.idl | 352 | ||||
-rw-r--r-- | docshell/shistory/nsISHTransaction.idl | 56 | ||||
-rw-r--r-- | docshell/shistory/nsISHistory.idl | 221 | ||||
-rw-r--r-- | docshell/shistory/nsISHistoryInternal.idl | 104 | ||||
-rw-r--r-- | docshell/shistory/nsISHistoryListener.idl | 108 | ||||
-rw-r--r-- | docshell/shistory/nsSHEntry.cpp | 944 | ||||
-rw-r--r-- | docshell/shistory/nsSHEntry.h | 71 | ||||
-rw-r--r-- | docshell/shistory/nsSHEntryShared.cpp | 385 | ||||
-rw-r--r-- | docshell/shistory/nsSHEntryShared.h | 97 | ||||
-rw-r--r-- | docshell/shistory/nsSHTransaction.cpp | 107 | ||||
-rw-r--r-- | docshell/shistory/nsSHTransaction.h | 37 | ||||
-rw-r--r-- | docshell/shistory/nsSHistory.cpp | 1932 | ||||
-rw-r--r-- | docshell/shistory/nsSHistory.h | 133 |
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(¤tPersist); + } + + 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 */ |