When navigating a subframe, make sure that all docshells have navigated to the correct cloned session history entry. Bug 301397, r=darin, a=bsmedberg

This commit is contained in:
bryner%brianryner.com 2005-08-03 22:32:10 +00:00
parent c6acac68a8
commit 3064c2307e
3 changed files with 279 additions and 63 deletions

View File

@ -2606,7 +2606,7 @@ nsDocShell::AddChildSHEntry(nsISHEntry * aCloneRef, nsISHEntry * aNewEntry,
PRUint32 cloneID = 0;
nsCOMPtr<nsISHEntry> nextEntry;
aCloneRef->GetID(&cloneID);
rv = CloneAndReplace(currentEntry, cloneID, aNewEntry,
rv = CloneAndReplace(currentEntry, this, cloneID, aNewEntry,
getter_AddRefs(nextEntry));
if (NS_SUCCEEDED(rv)) {
@ -4463,7 +4463,7 @@ nsDocShell::Embed(nsIContentViewer * aContentViewer,
}
// XXX What if SetupNewViewer fails?
if (mLSHE)
mOSHE = mLSHE;
SetHistoryEntry(&mOSHE, mLSHE);
PRBool updateHistory = PR_TRUE;
@ -4541,7 +4541,7 @@ nsDocShell::OnStateChange(nsIWebProgress * aProgress, nsIRequest * aRequest,
// Save history state of the previous page
rv = PersistLayoutHistoryState();
if (mOSHE)
mOSHE = mLSHE;
SetHistoryEntry(&mOSHE, mLSHE);
}
}
@ -4684,7 +4684,7 @@ nsDocShell::EndPageLoad(nsIWebProgress * aProgress,
// Clear the mLSHE reference to indicate document loading is done one
// way or another.
mLSHE = nsnull;
SetHistoryEntry(&mLSHE, nsnull);
}
// if there's a refresh header in the channel, this method
// will set it up for us.
@ -5099,7 +5099,7 @@ nsDocShell::RestorePresentation(nsISHEntry *aSHEntry, PRBool *aRestoring)
printf("restoring presentation from session history: %s\n", spec.get());
#endif
mLSHE = aSHEntry;
SetHistoryEntry(&mLSHE, aSHEntry);
// Add the request to our load group. We do this before swapping out
// the content viewers so that consumers of STATE_START can access
@ -5245,7 +5245,7 @@ nsDocShell::RestoreFromHistory()
NS_ENSURE_SUCCESS(rv, rv);
// mLSHE is now our currently-loaded document.
mOSHE = mLSHE;
SetHistoryEntry(&mOSHE, mLSHE);
// XXX special wyciwyg handling in Embed()?
// XXX SetLayoutHistoryState(null)?
@ -6234,7 +6234,7 @@ nsDocShell::InternalLoad(nsIURI * aURI,
* onLocationChange() notifications to the browser to update
* back/forward buttons.
*/
mLSHE = aSHEntry;
SetHistoryEntry(&mLSHE, aSHEntry);
/* This is a anchor traversal with in the same page.
* call OnNewURI() so that, this traversal will be
@ -6263,7 +6263,7 @@ nsDocShell::InternalLoad(nsIURI * aURI,
* by OnNewURI() for normal loads or aSHEntry for history loads.
*/
if (mLSHE) {
mOSHE = mLSHE;
SetHistoryEntry(&mOSHE, mLSHE);
// Save the postData obtained from the previous page
// in to the session history entry created for the
// anchor page, so that any history load of the anchor
@ -6290,7 +6290,7 @@ nsDocShell::InternalLoad(nsIURI * aURI,
/* Clear out mLSHE so that further anchor visits get
* recorded in SH and SH won't misbehave.
*/
mLSHE = nsnull;
SetHistoryEntry(&mLSHE, nsnull);
/* Set the title for the SH entry for this target url. so that
* SH menus in go/back/forward buttons won't be empty for this.
*/
@ -6365,7 +6365,7 @@ nsDocShell::InternalLoad(nsIURI * aURI,
// been called. But when loading an error page, do not clear the
// mLSHE for the real page.
if (mLoadType != LOAD_ERROR_PAGE) {
mLSHE = aSHEntry;
SetHistoryEntry(&mLSHE, aSHEntry);
mSavingOldViewer = savePresentation;
}
@ -7083,7 +7083,7 @@ nsDocShell::OnNewURI(nsIURI * aURI, nsIChannel * aChannel,
// If this is a refresh to the currently loaded url, we don't
// have to update session or global history.
if (mLoadType == LOAD_REFRESH && !inputStream && equalUri) {
mLSHE = mOSHE;
SetHistoryEntry(&mLSHE, mOSHE);
}
@ -7486,72 +7486,235 @@ NS_IMETHODIMP nsDocShell::PersistLayoutHistoryState()
return rv;
}
NS_IMETHODIMP
nsDocShell::CloneAndReplace(nsISHEntry * src, PRUint32 aCloneID,
nsISHEntry * replaceEntry,
nsISHEntry ** resultEntry)
/* static */ nsresult
nsDocShell::WalkHistoryEntries(nsISHEntry *aRootEntry,
nsDocShell *aRootShell,
WalkHistoryEntriesFunc aCallback,
void *aData)
{
nsresult result = NS_OK;
NS_ENSURE_ARG_POINTER(resultEntry);
nsISHEntry *dest = (nsISHEntry *) nsnull;
PRUint32 srcID;
src->GetID(&srcID);
NS_ENSURE_TRUE(aRootEntry, NS_ERROR_FAILURE);
if (!src || !replaceEntry)
nsCOMPtr<nsISHContainer> container(do_QueryInterface(aRootEntry));
if (!container)
return NS_ERROR_FAILURE;
if (srcID == aCloneID) {
PRInt32 childCount;
container->GetChildCount(&childCount);
for (PRInt32 i = 0; i < childCount; i++) {
nsCOMPtr<nsISHEntry> childEntry;
container->GetChildAt(i, getter_AddRefs(childEntry));
if (!childEntry) {
// childEntry can be null for valid reasons, for example if the
// docshell at index i never loaded anything useful.
continue;
}
nsDocShell *childShell = nsnull;
if (aRootShell) {
// Walk the children of aRootShell and see if one of them
// has srcChild as a SHEntry.
PRInt32 childCount = aRootShell->mChildList.Count();
for (PRInt32 j = 0; j < childCount; ++j) {
nsDocShell *child =
NS_STATIC_CAST(nsDocShell*, aRootShell->ChildAt(j));
if (child->HasHistoryEntry(childEntry)) {
childShell = child;
break;
}
}
}
nsresult rv = aCallback(childEntry, childShell, i, aData);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
// callback data for WalkHistoryEntries
struct CloneAndReplaceData
{
PRUint32 cloneID;
nsISHEntry *replaceEntry;
nsISHEntry *destTreeParent;
nsCOMPtr<nsISHEntry> resultEntry;
};
/* static */ nsresult
nsDocShell::CloneAndReplaceChild(nsISHEntry *aEntry, nsDocShell *aShell,
PRInt32 aEntryIndex, void *aData)
{
nsresult result = NS_OK;
nsCOMPtr<nsISHEntry> dest;
CloneAndReplaceData *data = NS_STATIC_CAST(CloneAndReplaceData*, aData);
PRUint32 cloneID = data->cloneID;
nsISHEntry *replaceEntry = data->replaceEntry;
PRUint32 srcID;
aEntry->GetID(&srcID);
if (srcID == cloneID) {
// Just replace the entry, and don't walk the children.
dest = replaceEntry;
dest->SetIsSubFrame(PR_TRUE);
*resultEntry = dest;
NS_IF_ADDREF(*resultEntry);
}
else {
} else {
// Clone the SHEntry...
result = src->Clone(&dest);
result = aEntry->Clone(getter_AddRefs(dest));
if (NS_FAILED(result))
return result;
// This entry is for a frame...
// This entry is for a subframe navigation
dest->SetIsSubFrame(PR_TRUE);
// Transfer the owning reference to 'resultEntry'. From this point on
// 'dest' is *not* an owning reference...
*resultEntry = dest;
// Walk the children
CloneAndReplaceData childData = { cloneID, replaceEntry, dest, nsnull };
result = WalkHistoryEntries(aEntry, aShell,
CloneAndReplaceChild, &childData);
if (NS_FAILED(result))
return result;
PRInt32 childCount = 0;
if (aShell)
aShell->SwapHistoryEntries(aEntry, dest);
}
nsCOMPtr<nsISHContainer> srcContainer(do_QueryInterface(src));
if (!srcContainer)
return NS_ERROR_FAILURE;
nsCOMPtr<nsISHContainer> destContainer(do_QueryInterface(dest));
if (!destContainer)
return NS_ERROR_FAILURE;
srcContainer->GetChildCount(&childCount);
for (PRInt32 i = 0; i < childCount; i++) {
nsCOMPtr<nsISHEntry> srcChild;
srcContainer->GetChildAt(i, getter_AddRefs(srcChild));
if (!srcChild) {
// srcChild can be null for valid reasons, for example if the
// docshell at index i never loaded anything useful. So if we
// hit a null here, just go on; it'll be null in the cloned
// tree as well.
continue;
}
nsCOMPtr<nsISHEntry> destChild;
result =
CloneAndReplace(srcChild, aCloneID, replaceEntry,
getter_AddRefs(destChild));
if (NS_FAILED(result))
return result;
result = destContainer->AddChild(destChild, i);
if (NS_FAILED(result))
return result;
} // for
nsCOMPtr<nsISHContainer> container =
do_QueryInterface(data->destTreeParent);
if (container)
container->AddChild(dest, aEntryIndex);
data->resultEntry = dest;
return result;
}
/* static */ nsresult
nsDocShell::CloneAndReplace(nsISHEntry *aSrcEntry,
nsDocShell *aSrcShell,
PRUint32 aCloneID,
nsISHEntry *aReplaceEntry,
nsISHEntry **aResultEntry)
{
NS_ENSURE_ARG_POINTER(aResultEntry);
NS_ENSURE_TRUE(aReplaceEntry, NS_ERROR_FAILURE);
CloneAndReplaceData data = { aCloneID, aReplaceEntry, nsnull, nsnull };
nsresult rv = CloneAndReplaceChild(aSrcEntry, aSrcShell, 0, &data);
data.resultEntry.swap(*aResultEntry);
return rv;
}
void
nsDocShell::SwapHistoryEntries(nsISHEntry *aOldEntry, nsISHEntry *aNewEntry)
{
if (aOldEntry == mOSHE)
mOSHE = aNewEntry;
if (aOldEntry == mLSHE)
mLSHE = aNewEntry;
}
struct SwapEntriesData
{
nsDocShell *ignoreShell; // constant; the shell to ignore
nsISHEntry *destTreeRoot; // constant; the root of the dest tree
nsISHEntry *destTreeParent; // constant; the node under destTreeRoot
// whose children will correspond to aEntry
};
nsresult
nsDocShell::SetChildHistoryEntry(nsISHEntry *aEntry, nsDocShell *aShell,
PRInt32 aEntryIndex, void *aData)
{
SwapEntriesData *data = NS_STATIC_CAST(SwapEntriesData*, aData);
nsDocShell *ignoreShell = data->ignoreShell;
if (!aShell || aShell == ignoreShell)
return NS_OK;
nsISHEntry *destTreeRoot = data->destTreeRoot;
// aEntry is a clone of the child of destTreeParent with the same index.
nsCOMPtr<nsISHEntry> destEntry;
nsCOMPtr<nsISHContainer> container =
do_QueryInterface(data->destTreeParent);
if (container) {
container->GetChildAt(aEntryIndex, getter_AddRefs(destEntry));
NS_ASSERTION(destEntry, "oops, history trees are out of sync");
#ifdef DEBUG
PRUint32 id1, id2;
aEntry->GetID(&id1);
destEntry->GetID(&id2);
NS_ASSERTION(id1 == id2, "oops, history trees are out of sync");
#endif
} else {
destEntry = destTreeRoot;
}
aShell->SwapHistoryEntries(aEntry, destEntry);
// Now handle the children of aEntry.
SwapEntriesData childData = { ignoreShell, destTreeRoot, destEntry };
return WalkHistoryEntries(aEntry, aShell,
SetChildHistoryEntry, &childData);
}
static nsISHEntry*
GetRootSHEntry(nsISHEntry *aEntry)
{
nsCOMPtr<nsISHEntry> rootEntry = aEntry;
nsISHEntry *result = nsnull;
while (rootEntry) {
result = rootEntry;
result->GetParent(getter_AddRefs(rootEntry));
}
return result;
}
void
nsDocShell::SetHistoryEntry(nsCOMPtr<nsISHEntry> *aPtr, nsISHEntry *aEntry)
{
// We need to sync up the docshell and session history trees for
// subframe navigation. If the load was in a subframe, we forward up to
// the root docshell, which will then recursively sync up all docshells
// to their corresponding entries in the new session history tree.
// If we don't do this, then we can cache a content viewer on the wrong
// cloned entry, and subsequently restore it at the wrong time.
nsISHEntry *newRootEntry = GetRootSHEntry(aEntry);
if (newRootEntry) {
// newRootEntry is now the new root entry.
// Find the old root entry as well.
nsISHEntry *oldRootEntry = GetRootSHEntry(*aPtr);
if (oldRootEntry) {
nsCOMPtr<nsIDocShellTreeItem> parentAsItem;
GetSameTypeParent(getter_AddRefs(parentAsItem));
nsCOMPtr<nsIDocShell> rootShell = do_QueryInterface(parentAsItem);
if (rootShell) { // if we're the root just set it, nothing to swap
SwapEntriesData data = { this, newRootEntry };
nsIDocShell *rootIDocShell =
NS_STATIC_CAST(nsIDocShell*, rootShell);
nsDocShell *rootDocShell = NS_STATIC_CAST(nsDocShell*,
rootIDocShell);
nsresult rv = SetChildHistoryEntry(oldRootEntry, rootDocShell,
0, &data);
NS_ASSERTION(NS_SUCCEEDED(rv), "SetChildHistoryEntry failed");
}
}
}
*aPtr = aEntry;
}

View File

@ -332,12 +332,65 @@ protected:
NS_IMETHOD LoadHistoryEntry(nsISHEntry * aEntry, PRUint32 aLoadType);
NS_IMETHOD PersistLayoutHistoryState();
NS_IMETHOD CloneAndReplace(nsISHEntry * srcEntry, PRUint32 aCloneID,
nsISHEntry * areplaceEntry, nsISHEntry ** destEntry);
// Clone a session history tree for subframe navigation.
// The tree rooted at |aSrcEntry| will be cloned into |aDestEntry|, except
// for the entry with id |aCloneID|, which will be replaced with
// |aReplaceEntry|. |aSrcShell| is a (possibly null) docshell which
// corresponds to |aSrcEntry| via its mLSHE or mOHE pointers, and will
// have that pointer updated to point to the cloned history entry.
static nsresult CloneAndReplace(nsISHEntry *aSrcEntry,
nsDocShell *aSrcShell,
PRUint32 aCloneID,
nsISHEntry *aReplaceEntry,
nsISHEntry **aDestEntry);
// Child-walking callback for CloneAndReplace
static nsresult CloneAndReplaceChild(nsISHEntry *aEntry,
nsDocShell *aShell,
PRInt32 aChildIndex, void *aData);
nsresult GetRootSessionHistory(nsISHistory ** aReturn);
nsresult GetHttpChannel(nsIChannel * aChannel, nsIHttpChannel ** aReturn);
PRBool ShouldDiscardLayoutState(nsIHttpChannel * aChannel);
// Determine whether this docshell corresponds to the given history entry,
// via having a pointer to it in mOSHE or mLSHE.
PRBool HasHistoryEntry(nsISHEntry *aEntry) const
{
return aEntry && (aEntry == mOSHE || aEntry == mLSHE);
}
// Update any pointers (mOSHE or mLSHE) to aOldEntry to point to aNewEntry
void SwapHistoryEntries(nsISHEntry *aOldEntry, nsISHEntry *aNewEntry);
// Call this method to swap in a new history entry to m[OL]SHE, rather than
// setting it directly. This completes the navigation in all docshells
// in the case of a subframe navigation.
void SetHistoryEntry(nsCOMPtr<nsISHEntry> *aPtr, nsISHEntry *aEntry);
// Child-walking callback for SetHistoryEntry
static nsresult SetChildHistoryEntry(nsISHEntry *aEntry,
nsDocShell *aShell,
PRInt32 aEntryIndex, void *aData);
// Callback prototype for WalkHistoryEntries.
// aEntry is the child history entry, aShell is its corresponding docshell,
// aChildIndex is the child's index in its parent entry, and aData is
// the opaque pointer passed to WalkHistoryEntries.
typedef nsresult (*WalkHistoryEntriesFunc)(nsISHEntry *aEntry,
nsDocShell *aShell,
PRInt32 aChildIndex,
void *aData);
// For each child of aRootEntry, find the corresponding docshell which is
// a child of aRootShell, and call aCallback. The opaque pointer aData
// is passed to the callback.
static nsresult WalkHistoryEntries(nsISHEntry *aRootEntry,
nsDocShell *aRootShell,
WalkHistoryEntriesFunc aCallback,
void *aData);
// Global History
nsresult AddToGlobalHistory(nsIURI * aURI, PRBool aRedirect, nsIURI * aReferrer);

View File

@ -908,7 +908,7 @@ nsresult nsWebShell::EndPageLoad(nsIWebProgress *aProgress,
// is a bit of a hack, and if the force-load fails I think we'll
// end up being confused about what page we're on... but we would
// anyway, since we've updated the session history index above.
mOSHE = loadingSHE;
SetHistoryEntry(&mOSHE, loadingSHE);
/* The user does want to repost the data to the server.
* Initiate a new load again.