Late swapout ACK can cause an active RenderViewHost to be incorrectly marked as inactive |
||
Issue descriptionThe following test, originally from lfg@'s https://codereview.chromium.org/2496233003/patch/380001/390004, shows how this can happen: IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, RenderViewHostStaysActiveWithLateSwapoutACK) { EXPECT_TRUE(NavigateToURL( shell(), embedded_test_server()->GetURL("a.com", "/title1.html"))); // Open a popup and navigate it to b.com. Shell* popup = OpenPopup( shell(), embedded_test_server()->GetURL("a.com", "/title2.html"), "foo"); RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(popup->web_contents()->GetMainFrame()); RenderViewHostImpl* rvh = rfh->render_view_host(); // Disable the swapout ACK and the swapout timer. scoped_refptr<SwapoutACKMessageFilter> filter = new SwapoutACKMessageFilter(); rfh->GetProcess()->AddFilter(filter.get()); rfh->DisableSwapOutTimerForTesting(); // Navigate popup to b.com. Because there's an opener, the RVH for a.com // stays around in swapped-out state. EXPECT_TRUE(NavigateToURLInSameBrowsingInstance( popup, embedded_test_server()->GetURL("b.com", "/title3.html"))); EXPECT_FALSE(rvh->is_active()); // Kill the b.com process. RenderProcessHost* b_process = popup->web_contents()->GetMainFrame()->GetProcess(); RenderProcessHostWatcher crash_observer( b_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); b_process->Shutdown(0); crash_observer.Wait(); ASSERT_TRUE(rfh->IsRenderFrameLive()); ASSERT_FALSE(rfh->is_active()); // Go back in the popup from b.com to a.com/title2.html. Because the current // b.com RFH is dead, the new RFH is committed right away (without waiting // for renderer to commit), so that users don't need to look at the sad tab. TestNavigationObserver back_observer(popup->web_contents()); popup->web_contents()->GetController().GoBack(); // Pretend that the original RFH in a.com now finishes running its unload // handler and sends the swapout ACK. rfh->OnSwappedOut(); // Wait for the new a.com navigation to finish. back_observer.Wait(); // The RVH for a.com should've been reused, and it should be active. EXPECT_TRUE(rvh->is_active()); } The test currently fails on the last line on ToT (@ r542543). This might happen in an A-(1)->B-(2)->A navigation sequence where (1) has a long unload handler. When the navigation in (1) commits, it will make the RVH for A inactive. Then the navigation in (2) will reuse the same RVH and make it active once it commits. I think normally, the commit can't happen before the unload handler for (1) has finished running (as it's in the same process), but in this case the browser-side commit happens early, when we start the navigation, because we've also killed process B after (1). As a result, CommitPending() might happen before the unload handler from (1) finishes, and hence the unload handler, when it's actually done, will trigger RenderFrameHostImpl::OnSwappedOut() which will incorrectly set the RVH to inactive. Now we have an active RVH with a main frame, for which is_active() returns false. The timing/steps for this are very hard to get right, but I've verified it can also happen in Chrome outside of tests (had to increase unload timeout to 100s). Running with an RVH in this state can lead to other bad things - one example is that killing and reloading the current tab will recreate the RenderView in a bad state, causing input events to no longer work.
,
Mar 21 2018
The following revision refers to this bug: https://chromium.googlesource.com/chromium/src.git/+/4562582bc84c9696f251064c299debd9d4fbbf5a commit 4562582bc84c9696f251064c299debd9d4fbbf5a Author: Alex Moshchuk <alexmos@chromium.org> Date: Wed Mar 21 19:28:27 2018 Prevent a late swapout ACK from marking a reused active RVH as inactive. This CL fixes a bug where IsSwappedOut() always marked the RVH as inactive and swapped-out. This is incorrect in the case where the RVH has been reused by another navigation that has already made it active. The timing for that is hard to nail: usually, reusing a renderer process for another navigation implies that the new navigation needs to wait for a previous unload handler to finish running before a new navigation can be started/committed. However, if the navigation that reuses the RVH is coming off of a cross-site sad tab page, it will commit the pending RFH in the browser immediately, before the navigation actually takes places in the renderer, which makes it possible for a late swapout ACK from an unload handler to arrive after CommitPending() has already marked the reused RVH as active. Bug: 823567 Change-Id: I934ad70d3bd92d03f795291d603fb584f24b6c54 Reviewed-on: https://chromium-review.googlesource.com/971304 Commit-Queue: Alex Moshchuk <alexmos@chromium.org> Reviewed-by: Lucas Gadani <lfg@chromium.org> Cr-Commit-Position: refs/heads/master@{#544801} [modify] https://crrev.com/4562582bc84c9696f251064c299debd9d4fbbf5a/content/browser/frame_host/render_frame_host_impl.cc [modify] https://crrev.com/4562582bc84c9696f251064c299debd9d4fbbf5a/content/browser/frame_host/render_frame_host_impl.h [modify] https://crrev.com/4562582bc84c9696f251064c299debd9d4fbbf5a/content/browser/site_per_process_browsertest.cc
,
Mar 21 2018
|
||
►
Sign in to add a comment |
||
Comment 1 by alex...@chromium.org
, Mar 20 2018Owner: alex...@chromium.org
Status: Assigned (was: Available)