From b0e77fe7c56c1cf76f1c0360d7eb3b4010471eed Mon Sep 17 00:00:00 2001 From: "bryner%brianryner.com" Date: Tue, 2 Aug 2005 18:35:07 +0000 Subject: [PATCH] When restoring a presentation, move everything except adding the request to the loadgroup into the async plevent callback. This fixes the problem with restoring window properties when history.back() is used (with untrusted JS on the stack). Bug 301516, r=darin, a=asa git-svn-id: svn://10.0.0.236/trunk@176999 18797224-902f-48f8-a5cc-f745e15eee43 --- mozilla/docshell/base/nsDocShell.cpp | 331 +++++++++++++------------- mozilla/docshell/base/nsDocShell.h | 16 +- mozilla/docshell/base/nsIDocShell.idl | 14 +- 3 files changed, 189 insertions(+), 172 deletions(-) diff --git a/mozilla/docshell/base/nsDocShell.cpp b/mozilla/docshell/base/nsDocShell.cpp index d69c087101e..8ff7e46447c 100644 --- a/mozilla/docshell/base/nsDocShell.cpp +++ b/mozilla/docshell/base/nsDocShell.cpp @@ -4644,7 +4644,7 @@ nsDocShell::EndPageLoad(nsIWebProgress * aProgress, // This will cause any OnLoad(...) handlers to fire, if it is a HTML // document... // - if ((mIsRestoringDocument || !mEODForCurrentDocument) && mContentViewer) { + if (!mEODForCurrentDocument && mContentViewer) { mIsExecutingOnLoadHandler = PR_TRUE; mContentViewer->LoadComplete(aStatus); mIsExecutingOnLoadHandler = PR_FALSE; @@ -4932,7 +4932,11 @@ public: PR_STATIC_CALLBACK(void*) HandleRestorePresentationEvent(PLEvent *aEvent) { - NS_STATIC_CAST(RestorePresentationEvent*, aEvent)->mDocShell->FinishRestore(); + RestorePresentationEvent *event = + NS_STATIC_CAST(RestorePresentationEvent*, aEvent); + + nsresult rv = event->mDocShell->RestoreFromHistory(); + NS_ASSERTION(NS_SUCCEEDED(rv), "RestoreFromHistory failed"); return nsnull; } @@ -4955,7 +4959,7 @@ nsDocShell::BeginRestore(nsIContentViewer *aContentViewer, PRBool aTop) nsresult rv; if (!aContentViewer) { rv = EnsureContentViewer(); - NS_ENSURE_TRUE(rv, rv); + NS_ENSURE_SUCCESS(rv, rv); aContentViewer = mContentViewer; } @@ -4977,23 +4981,7 @@ nsDocShell::BeginRestore(nsIContentViewer *aContentViewer, PRBool aTop) } } - if (aTop) { - // Post a PLEvent that will remove the request after we've returned - // to the event loop. This mimics the way it is called by nsIChannel - // implementations. - - nsCOMPtr uiThreadQueue; - NS_GetMainEventQ(getter_AddRefs(uiThreadQueue)); - NS_ENSURE_TRUE(uiThreadQueue, NS_ERROR_UNEXPECTED); - - PLEvent *evt = new RestorePresentationEvent(this); - NS_ENSURE_TRUE(evt, NS_ERROR_OUT_OF_MEMORY); - - rv = uiThreadQueue->PostEvent(evt); - if (NS_FAILED(rv)) { - PL_DestroyEvent(evt); - } - } else { + if (!aTop) { // For non-top frames, there is no notion of making sure that the // previous document is in the domwindow when STATE_START notifications // happen. We can just call BeginRestore for all of the child shells @@ -5062,55 +5050,25 @@ nsDocShell::GetRestoringDocument(PRBool *aRestoring) return NS_OK; } -class ContentViewerDestroyEvent : public PLEvent -{ -public: - ContentViewerDestroyEvent(nsIContentViewer *aViewer, nsDocShell *aShell); - - nsCOMPtr mViewer; -}; - -PR_STATIC_CALLBACK(void*) -HandleContentViewerDestroyEvent(PLEvent *aEvent) -{ - NS_STATIC_CAST(ContentViewerDestroyEvent*, aEvent)->mViewer->Destroy(); - return nsnull; -} - -PR_STATIC_CALLBACK(void) -DestroyContentViewerDestroyEvent(PLEvent *aEvent) -{ - delete NS_STATIC_CAST(ContentViewerDestroyEvent*, aEvent); -} - -ContentViewerDestroyEvent::ContentViewerDestroyEvent(nsIContentViewer *aViewer, - nsDocShell *aShell) - : mViewer(aViewer) -{ - PL_InitEvent(this, aShell, ::HandleContentViewerDestroyEvent, - ::DestroyContentViewerDestroyEvent); -} - nsresult -nsDocShell::RestorePresentation(nsISHEntry *aSHEntry, PRBool aSavePresentation, - PRBool *aRestored) +nsDocShell::RestorePresentation(nsISHEntry *aSHEntry, PRBool *aRestoring) { NS_ASSERTION(mLoadType & LOAD_CMD_HISTORY, "RestorePresentation should only be called for history loads"); - nsCOMPtr uri; - aSHEntry->GetURI(getter_AddRefs(uri)); - nsCOMPtr viewer; aSHEntry->GetContentViewer(getter_AddRefs(viewer)); #ifdef DEBUG_PAGE_CACHE + nsCOMPtr uri; + aSHEntry->GetURI(getter_AddRefs(uri)); + nsCAutoString spec; if (uri) uri->GetSpec(spec); #endif - *aRestored = PR_FALSE; + *aRestoring = PR_FALSE; if (!viewer) { #ifdef DEBUG_PAGE_CACHE @@ -5141,9 +5099,7 @@ nsDocShell::RestorePresentation(nsISHEntry *aSHEntry, PRBool aSavePresentation, printf("restoring presentation from session history: %s\n", spec.get()); #endif - // Notify the old content viewer that it's being hidden. - FirePageHideNotification(!aSavePresentation); - mFiredUnloadEvent = PR_FALSE; + 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 @@ -5153,6 +5109,83 @@ nsDocShell::RestorePresentation(nsISHEntry *aSHEntry, PRBool aSavePresentation, BeginRestore(viewer, PR_TRUE); + // Post a PLEvent that will remove the request after we've returned + // to the event loop. This mimics the way it is called by nsIChannel + // implementations. + + nsCOMPtr uiThreadQueue; + NS_GetMainEventQ(getter_AddRefs(uiThreadQueue)); + NS_ENSURE_TRUE(uiThreadQueue, NS_ERROR_UNEXPECTED); + + PLEvent *evt = new RestorePresentationEvent(this); + NS_ENSURE_TRUE(evt, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv = uiThreadQueue->PostEvent(evt); + if (NS_SUCCEEDED(rv)) { + // The rest of the restore processing will happen on our PLEvent + // callback. + *aRestoring = PR_TRUE; + } else { + PL_DestroyEvent(evt); + } + + return NS_OK; +} + +nsresult +nsDocShell::RestoreFromHistory() +{ + // This section of code follows the same ordering as CreateContentViewer. + if (!mLSHE) + return NS_ERROR_FAILURE; + + nsCOMPtr viewer; + mLSHE->GetContentViewer(getter_AddRefs(viewer)); + if (!viewer) + return NS_ERROR_FAILURE; + + if (mSavingOldViewer) { + // We determined that it was safe to cache the document presentation + // at the time we initiated the new load. We need to check whether + // it's still safe to do so, since there may have been DOM mutations + // or new requests initiated. + nsCOMPtr domDoc; + viewer->GetDOMDocument(getter_AddRefs(domDoc)); + nsCOMPtr doc = do_QueryInterface(domDoc); + nsIRequest *request = nsnull; + if (doc) + request = doc->GetChannel(); + mSavingOldViewer = CanSavePresentation(mLoadType, request); + } + + // Notify the old content viewer that it's being hidden. + FirePageHideNotification(!mSavingOldViewer); + // Set mFiredUnloadEvent = PR_FALSE so that the unload handler for the + // *new* document will fire. + mFiredUnloadEvent = PR_FALSE; + + mURIResultedInDocument = PR_TRUE; + nsCOMPtr rootSH; + GetRootSessionHistory(getter_AddRefs(rootSH)); + if (rootSH) { + nsCOMPtr hist = do_QueryInterface(rootSH); + hist->UpdateIndex(); + } + + // Rather than call Embed(), we will retrieve the viewer from the session + // history entry and swap it in. + // XXX can we refactor this so that we can just call Embed()? + // XXX PersistLayoutHistoryState() ? + nsresult rv; + if (mContentViewer) { + if (mSavingOldViewer && NS_FAILED(CaptureState())) { + if (mOSHE) { + mOSHE->SyncPresentationState(); + } + mSavingOldViewer = PR_FALSE; + } + } + // Save off the root view's parent and sibling so that we can insert the // new content viewer's root view at the same position. Also save the // bounds of the root view's widget. @@ -5185,36 +5218,9 @@ nsDocShell::RestorePresentation(nsISHEntry *aSHEntry, PRBool aSavePresentation, // content viewer, we prevent the viewer from being torn down after // Destroy() is called. - nsresult rv; - if (mContentViewer) { - if (aSavePresentation) { - rv = CaptureState(); - if (NS_FAILED(rv) && mOSHE) { - mOSHE->SyncPresentationState(); - aSavePresentation = PR_FALSE; - } - } - - mContentViewer->Close(aSavePresentation ? mOSHE.get() : nsnull); - mContentViewer->Hide(); - - // It's unsafe to actually destroy the content viewer here. - // In particular, if we're called from within a plugin's event loop, - // then attempting to tear down the plugin can cause a hang. - // So, post an event to destroy and release the viewer. - - nsCOMPtr uiThreadQueue; - NS_GetMainEventQ(getter_AddRefs(uiThreadQueue)); - NS_ENSURE_TRUE(uiThreadQueue, NS_ERROR_UNEXPECTED); - - PLEvent *evt = new ContentViewerDestroyEvent(mContentViewer, this); - NS_ENSURE_TRUE(evt, NS_ERROR_OUT_OF_MEMORY); - - rv = uiThreadQueue->PostEvent(evt); - if (NS_FAILED(rv)) { - PL_DestroyEvent(evt); - } + mContentViewer->Close(mSavingOldViewer ? mOSHE.get() : nsnull); + mContentViewer->Destroy(); } mContentViewer.swap(viewer); @@ -5224,37 +5230,84 @@ nsDocShell::RestorePresentation(nsISHEntry *aSHEntry, PRBool aSavePresentation, rv = mContentViewer->Open(); // Now remove it from the cached presentation. - aSHEntry->SetContentViewer(nsnull); - - // Restore our child docshell hierarchy. - DestroyChildren(); - NS_ENSURE_SUCCESS(rv, rv); - - PRInt32 i = 0; - nsCOMPtr childShell; - while (NS_SUCCEEDED(aSHEntry->ChildShellAt(i++, - getter_AddRefs(childShell))) && - childShell) { - AddChild(childShell); - } - - // And release the references in the history entry. - aSHEntry->ClearChildShells(); - - // Dispatch STATE_START notifications for the new child shells. - rv = BeginRestoreChildren(); - NS_ENSURE_SUCCESS(rv, rv); + mLSHE->SetContentViewer(nsnull); + mEODForCurrentDocument = PR_FALSE; // Restore the sticky state of the viewer. The viewer has set this state // on the history entry in Destroy() just before marking itself non-sticky, // to avoid teardown of the presentation. PRBool sticky; - aSHEntry->GetSticky(&sticky); + mLSHE->GetSticky(&sticky); mContentViewer->SetSticky(sticky); + // Now that we have switched documents, forget all of our children. + DestroyChildren(); + NS_ENSURE_SUCCESS(rv, rv); + + // mLSHE is now our currently-loaded document. + mOSHE = mLSHE; + + // XXX special wyciwyg handling in Embed()? + // XXX SetLayoutHistoryState(null)? + // This is the end of our Embed() replacement + + mSavingOldViewer = PR_FALSE; + mEODForCurrentDocument = PR_FALSE; + + // Tell the event loop to favor plevents over user events, see comments + // in CreateContentViewer. + if (++gNumberOfDocumentsLoading == 1) + PL_FavorPerformanceHint(PR_TRUE, NS_EVENT_STARVATION_DELAY_HINT); + + nsCOMPtr domDoc; + mContentViewer->GetDOMDocument(getter_AddRefs(domDoc)); + nsCOMPtr document = do_QueryInterface(domDoc); + if (document) { + SetCurrentURI(document->GetDocumentURI(), + document->GetChannel(), PR_TRUE); + } + + // This is the end of our CreateContentViewer() replacement. + // Now we simulate a load. First, we restore the state of the javascript + // window object. + nsCOMPtr privWin = + do_GetInterface(NS_STATIC_CAST(nsIInterfaceRequestor*, this)); + NS_ASSERTION(privWin, "could not get nsPIDOMWindow interface"); + + nsCOMPtr windowState; + mLSHE->GetWindowState(getter_AddRefs(windowState)); + + rv = privWin->RestoreWindowState(windowState); + NS_ENSURE_SUCCESS(rv, rv); + + mLSHE->SetWindowState(nsnull); + + // Now, dispatch a title change event which would happed as the + // is parsed. + nsCOMPtr nsDoc = do_QueryInterface(document); + if (nsDoc) { + const nsAFlatString &title = document->GetDocumentTitle(); + nsDoc->SetTitle(title); + } + + // Now we simulate appending child docshells for subframes. + PRInt32 i = 0; + nsCOMPtr child; + while (NS_SUCCEEDED(mLSHE->ChildShellAt(i++, getter_AddRefs(child))) && + child) { + AddChild(child); + + nsCOMPtr childShell = do_QueryInterface(child); + rv = childShell->BeginRestore(nsnull, PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + } + + // And release the references in the history entry. + mLSHE->ClearChildShells(); + // get the previous content viewer size nsRect oldBounds(0, 0, 0, 0); - aSHEntry->GetViewerBounds(oldBounds); + mLSHE->GetViewerBounds(oldBounds); nsCOMPtr shell; nsDocShell::GetPresShell(getter_AddRefs(shell)); @@ -5285,26 +5338,10 @@ nsDocShell::RestorePresentation(nsISHEntry *aSHEntry, PRBool aSavePresentation, } } - // restore the state of the window object - nsCOMPtr privWin = - do_GetInterface(NS_STATIC_CAST(nsIInterfaceRequestor*, this)); - NS_ASSERTION(privWin, "could not get nsPIDOMWindow interface"); - - nsCOMPtr windowState; - aSHEntry->GetWindowState(getter_AddRefs(windowState)); - - rv = privWin->RestoreWindowState(windowState); - NS_ENSURE_SUCCESS(rv, rv); - - aSHEntry->SetWindowState(nsnull); - // Restore the refresh URI list. The refresh timers will be restarted // when EndPageLoad() is called. - aSHEntry->GetRefreshURIList(getter_AddRefs(mRefreshURIList)); - aSHEntry->SetRefreshURIList(nsnull); - - // aSHEntry is now our currently-loaded document. - mOSHE = aSHEntry; + mLSHE->GetRefreshURIList(getter_AddRefs(mRefreshURIList)); + mLSHE->SetRefreshURIList(nsnull); // Meta-refresh timers have been restarted for this shell, but not // for our children. Walk the child shells and restart their timers. @@ -5315,13 +5352,6 @@ nsDocShell::RestorePresentation(nsISHEntry *aSHEntry, PRBool aSavePresentation, child->ResumeRefreshURIs(); } - nsCOMPtr rootSH; - GetRootSessionHistory(getter_AddRefs(rootSH)); - if (rootSH) { - nsCOMPtr hist = do_QueryInterface(rootSH); - hist->UpdateIndex(); - } - // Make sure this presentation is the same size as the previous // presentation. If this is not the same size we showed it at last time, // then we need to resize the widget. @@ -5348,29 +5378,13 @@ nsDocShell::RestorePresentation(nsISHEntry *aSHEntry, PRBool aSavePresentation, } } - // Tell the event loop to favor plevents over user events, see comments - // in CreateContentViewer. - if (++gNumberOfDocumentsLoading == 1) - PL_FavorPerformanceHint(PR_TRUE, NS_EVENT_STARVATION_DELAY_HINT); + // Simulate the completion of the load. + nsDocShell::FinishRestore(); - SetCurrentURI(uri); - - // Restore the page title. Re-setting the current title on the document - // will update the docshell title and dispatch DOMTitleChanged. - // (We must do this after setting mOSHE so that the correct history entry - // title is set). - nsIDocument *doc = shell->GetDocument(); - nsCOMPtr nsDoc = do_QueryInterface(doc); - if (doc && nsDoc) { - const nsAFlatString &title = doc->GetDocumentTitle(); - nsDoc->SetTitle(title); - } - // Restart plugins, and paint the content. if (shell) shell->Thaw(); - *aRestored = PR_TRUE; return NS_OK; } @@ -5582,7 +5596,6 @@ nsDocShell::SetupNewViewer(nsIContentViewer * aNewViewer) PRBool styleDisabled; // |newMUDV| also serves as a flag to set the data from the above vars nsCOMPtr newMUDV; - PRBool savePresentation = mSavingOldViewer; if (mContentViewer || parent) { nsCOMPtr oldMUDV; @@ -5595,11 +5608,11 @@ nsDocShell::SetupNewViewer(nsIContentViewer * aNewViewer) // Tell the old content viewer to hibernate in session history when // it is destroyed. - if (savePresentation && NS_FAILED(CaptureState())) { + if (mSavingOldViewer && NS_FAILED(CaptureState())) { if (mOSHE) { mOSHE->SyncPresentationState(); } - savePresentation = PR_FALSE; + mSavingOldViewer = PR_FALSE; } } else { @@ -5724,7 +5737,7 @@ nsDocShell::SetupNewViewer(nsIContentViewer * aNewViewer) } } - mContentViewer->Close(savePresentation ? mOSHE.get() : nsnull); + mContentViewer->Close(mSavingOldViewer ? mOSHE.get() : nsnull); aNewViewer->SetPreviousViewer(mContentViewer); mContentViewer = nsnull; @@ -6359,9 +6372,9 @@ nsDocShell::InternalLoad(nsIURI * aURI, // If we have a saved content viewer in history, restore and show it now. if (aSHEntry && (mLoadType & LOAD_CMD_HISTORY)) { nsCOMPtr oldEntry = mOSHE; - PRBool restored; - rv = RestorePresentation(aSHEntry, savePresentation, &restored); - if (restored) + PRBool restoring; + rv = RestorePresentation(aSHEntry, &restoring); + if (restoring) return rv; // We failed to restore the presentation, so clean up. diff --git a/mozilla/docshell/base/nsDocShell.h b/mozilla/docshell/base/nsDocShell.h index ba3d3511d8b..23b85f0f30d 100644 --- a/mozilla/docshell/base/nsDocShell.h +++ b/mozilla/docshell/base/nsDocShell.h @@ -261,6 +261,12 @@ public: // nsIScriptGlobalObjectOwner methods virtual nsIScriptGlobalObject* GetScriptGlobalObject(); + + // Restores a cached presentation from history (mLSHE). + // This method swaps out the content viewer and simulates loads for + // subframes. It then simulates the completion of the toplevel load. + nsresult RestoreFromHistory(); + protected: // Object Management virtual ~nsDocShell(); @@ -440,12 +446,10 @@ protected: // state) and stores them on |mOSHE|. nsresult CaptureState(); - // Restores the presentation stored in |aSHEntry|. The old presentation - // is saved in session history if |aSavePresentation| is true. - // If a presentation could be restored, |aRestored| is set to true. - nsresult RestorePresentation(nsISHEntry *aSHEntry, - PRBool aSavePresentation, - PRBool *aRestored); + // Begin the toplevel restore process for |aSHEntry|. + // This simulates a channel open, and defers the real work until + // RestoreFromHistory is called from a PLEvent. + nsresult RestorePresentation(nsISHEntry *aSHEntry, PRBool *aRestoring); // Call BeginRestore(nsnull, PR_FALSE) for each child of this shell. nsresult BeginRestoreChildren(); diff --git a/mozilla/docshell/base/nsIDocShell.idl b/mozilla/docshell/base/nsIDocShell.idl index 9db7ca7b954..f172f89f481 100644 --- a/mozilla/docshell/base/nsIDocShell.idl +++ b/mozilla/docshell/base/nsIDocShell.idl @@ -365,13 +365,13 @@ interface nsIDocShell : nsISupports void resumeRefreshURIs(); /** - * Begin firing WebProgressListener notifications and DOM events for - * restoring a page presentation. |viewer| is the content viewer whose - * document we are starting to load. If null, it defaults to the docshell's - * current content viewer, creating one if necessary. |top| should be true - * for the toplevel docshell that is being restored; it will be set to false - * when this method is called for child docshells. This method will post an - * event to call finishRestore() after returning to the event loop. + * Begin firing WebProgressListener notifications for restoring a page + * presentation. |viewer| is the content viewer whose document we are + * starting to load. If null, it defaults to the docshell's current content + * viewer, creating one if necessary. |top| should be true for the toplevel + * docshell that is being restored; it will be set to false when this method + * is called for child docshells. This method will post an event to + * complete the simulated load after returning to the event loop. */ void beginRestore(in nsIContentViewer viewer, in boolean top);