blob: c1ce9d71b40bca603fb661161bdcd24b3e0419ec [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/site_per_process_browsertest.h"
#include "build/build_config.h"
#include "content/browser/renderer_host/cross_process_frame_connector.h"
#include "content/browser/renderer_host/frame_tree.h"
#include "content/browser/renderer_host/render_frame_proxy_host.h"
#include "content/common/features.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/shell/browser/shell.h"
#include "content/test/render_document_feature.h"
#include "content/test/render_widget_host_visibility_observer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
namespace {
// Helper class to wait for the next sad frame to be shown in a specific
// FrameTreeNode. This can be used to wait for sad frame visibility metrics to
// be logged.
class SadFrameShownObserver {
public:
explicit SadFrameShownObserver(FrameTreeNode* ftn) {
RenderFrameProxyHost* proxy_to_parent =
ftn->render_manager()->GetProxyToParent();
proxy_to_parent->cross_process_frame_connector()
->set_child_frame_crash_shown_closure_for_testing(
run_loop_.QuitClosure());
}
explicit SadFrameShownObserver(RenderFrameHostImpl* rfhi) {
RenderFrameProxyHost* proxy_to_parent = rfhi->GetProxyToOuterDelegate();
proxy_to_parent->cross_process_frame_connector()
->set_child_frame_crash_shown_closure_for_testing(
run_loop_.QuitClosure());
}
SadFrameShownObserver(const SadFrameShownObserver&) = delete;
SadFrameShownObserver& operator=(const SadFrameShownObserver&) = delete;
void Wait() { run_loop_.Run(); }
private:
base::RunLoop run_loop_;
};
} // namespace
// Disable the feature to mark hidden tabs with sad frames for reload, for use
// in tests where this feature interferes with the behavior being tested.
class SitePerProcessBrowserTestWithoutSadFrameTabReload
: public SitePerProcessBrowserTest {
public:
SitePerProcessBrowserTestWithoutSadFrameTabReload() {
feature_list_.InitAndDisableFeature(
features::kReloadHiddenTabsWithCrashedSubframes);
}
private:
base::test::ScopedFeatureList feature_list_;
};
// This test is flaky on all platforms.
// TODO(crbug.com/40749527): Deflake it and enable this test back.
IN_PROC_BROWSER_TEST_P(
SitePerProcessBrowserTestWithoutSadFrameTabReload,
DISABLED_ChildFrameCrashMetrics_KilledWhileHiddenThenShown) {
// Set-up a frame tree that helps verify what the metrics tracks:
// 1) frames (12 frames are affected if B process gets killed) or
// 2) widgets (10 b widgets and 1 c widget are affected if B is killed) or
// 3) crashes (1 crash if B process gets killed)?
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b(b,c),b,b,b,b,b,b,b,b,b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
// Hide the web contents (UpdateWebContentsVisibility is called twice to avoid
// hitting the |!did_first_set_visible_| case). Make sure all subframes are
// considered hidden at this point.
web_contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
web_contents()->UpdateWebContentsVisibility(Visibility::HIDDEN);
for (size_t i = 0U; i < root->child_count(); i++) {
RenderFrameProxyHost* proxy_to_parent =
root->child_at(i)->render_manager()->GetProxyToParent();
CrossProcessFrameConnector* connector =
proxy_to_parent->cross_process_frame_connector();
EXPECT_FALSE(connector->IsVisible())
<< " subframe " << i << " with URL " << root->child_at(i)->current_url()
<< " is visible";
}
// Kill the subframe.
base::HistogramTester histograms;
RenderProcessHost* child_process =
root->child_at(0)->current_frame_host()->GetProcess();
RenderProcessHostWatcher crash_observer(
child_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
child_process->Shutdown(0);
crash_observer.Wait();
// Verify that no child frame metrics got logged (yet - while WebContents are
// hidden).
histograms.ExpectTotalCount("Stability.ChildFrameCrash.Visibility", 0);
histograms.ExpectTotalCount(
"Stability.ChildFrameCrash.ShownAfterCrashingReason", 0);
// Show the web contents, wait for each of the subframe FrameTreeNodes to
// show a sad frame, and verify that the expected metrics got logged.
std::vector<std::unique_ptr<SadFrameShownObserver>> observers;
for (size_t i = 0U; i < root->child_count(); i++) {
observers.push_back(
std::make_unique<SadFrameShownObserver>(root->child_at(i)));
}
web_contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
for (size_t i = 0U; i < root->child_count(); i++) {
SCOPED_TRACE(testing::Message()
<< " Waiting for sad frame from subframe " << i
<< " with URL:" << root->child_at(i)->current_url());
observers[i]->Wait();
}
histograms.ExpectUniqueSample(
"Stability.ChildFrameCrash.Visibility",
CrossProcessFrameConnector::CrashVisibility::kShownAfterCrashing, 10);
histograms.ExpectUniqueSample(
"Stability.ChildFrameCrash.ShownAfterCrashingReason",
CrossProcessFrameConnector::ShownAfterCrashingReason::kTabWasShown, 10);
// Hide and show the web contents again and verify that no more metrics got
// logged.
web_contents()->UpdateWebContentsVisibility(Visibility::HIDDEN);
web_contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
histograms.ExpectUniqueSample(
"Stability.ChildFrameCrash.Visibility",
CrossProcessFrameConnector::CrashVisibility::kShownAfterCrashing, 10);
histograms.ExpectUniqueSample(
"Stability.ChildFrameCrash.ShownAfterCrashingReason",
CrossProcessFrameConnector::ShownAfterCrashingReason::kTabWasShown, 10);
}
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTestWithoutSadFrameTabReload,
ChildFrameCrashMetrics_ScrolledIntoViewAfterTabIsShown) {
// Start on a page that has a single iframe, which is positioned out of
// view, and navigate that iframe cross-site.
GURL main_url(
embedded_test_server()->GetURL("a.com", "/iframe_out_of_view.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
EXPECT_TRUE(NavigateToURLFromRenderer(
root->child_at(0),
embedded_test_server()->GetURL("b.com", "/title1.html")));
// Hide the web contents (UpdateWebContentsVisibility is called twice to avoid
// hitting the |!did_first_set_visible_| case).
web_contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
web_contents()->UpdateWebContentsVisibility(Visibility::HIDDEN);
// Kill the child frame.
base::HistogramTester histograms;
RenderProcessHost* child_process =
root->child_at(0)->current_frame_host()->GetProcess();
RenderProcessHostWatcher crash_observer(
child_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
child_process->Shutdown(0);
crash_observer.Wait();
// Verify that no child frame crash metrics got logged yet.
histograms.ExpectTotalCount("Stability.ChildFrameCrash.Visibility", 0);
histograms.ExpectTotalCount(
"Stability.ChildFrameCrash.ShownAfterCrashingReason", 0);
// Show the web contents. The crash metrics still shouldn't be logged, since
// the crashed frame is out of view.
web_contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
histograms.ExpectTotalCount("Stability.ChildFrameCrash.Visibility", 0);
histograms.ExpectTotalCount(
"Stability.ChildFrameCrash.ShownAfterCrashingReason", 0);
// Scroll the subframe into view and wait until the scrolled frame draws
// itself.
std::string scrolling_script = R"(
var frame = document.body.querySelector("iframe");
frame.scrollIntoView();
)";
EXPECT_TRUE(ExecJs(root, scrolling_script));
// This will ensure that browser has received the
// FrameHostMsg_UpdateViewportIntersection IPC message from the renderer main
// thread.
EXPECT_EQ(true,
EvalJsAfterLifecycleUpdate(root->current_frame_host(), "", "true"));
// Verify that the expected metrics got logged.
histograms.ExpectUniqueSample(
"Stability.ChildFrameCrash.Visibility",
CrossProcessFrameConnector::CrashVisibility::kShownAfterCrashing, 1);
histograms.ExpectUniqueSample(
"Stability.ChildFrameCrash.ShownAfterCrashingReason",
CrossProcessFrameConnector::ShownAfterCrashingReason::
kViewportIntersectionAfterTabWasShown,
1);
// Hide and show the web contents again and verify that no more metrics got
// logged.
web_contents()->UpdateWebContentsVisibility(Visibility::HIDDEN);
web_contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
histograms.ExpectUniqueSample(
"Stability.ChildFrameCrash.Visibility",
CrossProcessFrameConnector::CrashVisibility::kShownAfterCrashing, 1);
histograms.ExpectUniqueSample(
"Stability.ChildFrameCrash.ShownAfterCrashingReason",
CrossProcessFrameConnector::ShownAfterCrashingReason::
kViewportIntersectionAfterTabWasShown,
1);
}
class SitePerProcessBrowserTestWithSadFrameTabReload
: public SitePerProcessBrowserTest {
public:
SitePerProcessBrowserTestWithSadFrameTabReload() {
// Disable the feature to mark hidden tabs with sad frames for reload, since
// it makes the scenario for which this test collects metrics impossible.
feature_list_.InitAndEnableFeature(
features::kReloadHiddenTabsWithCrashedSubframes);
}
void CrashProcess(FrameTreeNode* ftn) {
RenderProcessHost* process = ftn->current_frame_host()->GetProcess();
RenderProcessHostWatcher crash_observer(
process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
process->Shutdown(0);
crash_observer.Wait();
EXPECT_FALSE(ftn->current_frame_host()->IsRenderFrameLive());
}
void CrashRendererProcess(RenderFrameHostImpl* rfhi) {
RenderProcessHost* process = rfhi->GetProcess();
RenderProcessHostWatcher crash_observer(
process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
process->Shutdown(0);
crash_observer.Wait();
EXPECT_FALSE(rfhi->IsRenderFrameLive());
}
WebContentsImpl* web_contents() {
return static_cast<WebContentsImpl*>(shell()->web_contents());
}
RenderFrameHostImpl* primary_main_frame_host() {
return web_contents()->GetPrimaryMainFrame();
}
content::test::FencedFrameTestHelper& fenced_frame_test_helper() {
return fenced_frame_helper_;
}
private:
content::test::FencedFrameTestHelper fenced_frame_helper_;
base::test::ScopedFeatureList feature_list_;
};
// Verify the feature where hidden tabs with a visible crashed subframe are
// marked for reload. This avoids showing crashed subframes if a hidden tab is
// eventually shown. See https://crbug.com/841572.
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTestWithSadFrameTabReload,
ReloadHiddenTabWithCrashedSubframeInViewport) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
// Hide the WebContents (UpdateWebContentsVisibility is called twice to avoid
// hitting the |!did_first_set_visible_| case).
web_contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
web_contents()->UpdateWebContentsVisibility(Visibility::HIDDEN);
EXPECT_EQ(Visibility::HIDDEN, web_contents()->GetVisibility());
// Kill the b.com subframe's process. This should mark the hidden
// WebContents for reload.
{
base::HistogramTester histograms;
CrashProcess(root->child_at(0));
histograms.ExpectUniqueSample(
"Stability.ChildFrameCrash.TabMarkedForReload", true, 1);
histograms.ExpectUniqueSample(
"Stability.ChildFrameCrash.TabMarkedForReload.Visibility",
blink::mojom::FrameVisibility::kRenderedInViewport, 1);
}
// Show the WebContents. This should trigger a reload of the main frame.
{
base::HistogramTester histograms;
web_contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
EXPECT_TRUE(WaitForLoadStop(web_contents()));
histograms.ExpectUniqueSample(
"Navigation.LoadIfNecessaryType",
NavigationControllerImpl::NeedsReloadType::kCrashedSubframe, 1);
}
// Both frames should now have live renderer processes.
EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
EXPECT_TRUE(root->child_at(0)->current_frame_host()->IsRenderFrameLive());
}
// Verify that when a tab is reloaded because it was previously marked for
// reload due to having a sad frame, we log the sad frame as shown during a tab
// reload, rather than being shown to the user directly, since the sad frame is
// expected to go away shortly. See https://crbug.com/1132938.
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTestWithSadFrameTabReload,
CrashedSubframeVisibilityMetricsDuringTabReload) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b,c)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
// Hide the WebContents (UpdateWebContentsVisibility is called twice to avoid
// hitting the |!did_first_set_visible_| case).
RenderWidgetHostVisibilityObserver hide_observer(
root->child_at(0)->current_frame_host()->GetRenderWidgetHost(),
false /* became_visible */);
web_contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
web_contents()->UpdateWebContentsVisibility(Visibility::HIDDEN);
EXPECT_EQ(Visibility::HIDDEN, web_contents()->GetVisibility());
hide_observer.WaitUntilSatisfied();
// Kill the b.com subframe's process. This should mark the hidden
// WebContents for reload.
CrashProcess(root->child_at(0));
auto& controller = static_cast<NavigationControllerImpl&>(
shell()->web_contents()->GetController());
EXPECT_TRUE(controller.NeedsReload());
EXPECT_EQ(1, controller.GetEntryCount());
// Show the WebContents. This should trigger a reload of the main frame. Sad
// frame visibility metrics should indicate that the sad frame is shown while
// the tab is being reloaded. Because the tab reload will wipe out the sad
// frame, this isn't as bad as kShownAfterCrashing.
{
base::HistogramTester histograms;
SadFrameShownObserver sad_frame_observer(root->child_at(0));
TestNavigationManager manager(web_contents(), main_url);
web_contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
EXPECT_TRUE(manager.WaitForRequestStart());
sad_frame_observer.Wait();
histograms.ExpectUniqueSample("Stability.ChildFrameCrash.Visibility",
CrashVisibility::kShownWhileAncestorIsLoading,
1);
// Ensure no new metrics are logged after the reload completes.
ASSERT_TRUE(manager.WaitForNavigationFinished());
EXPECT_TRUE(manager.was_successful());
EXPECT_FALSE(controller.NeedsReload());
EXPECT_EQ(1, controller.GetEntryCount());
histograms.ExpectUniqueSample("Stability.ChildFrameCrash.Visibility",
CrashVisibility::kShownWhileAncestorIsLoading,
1);
}
}
// Verify that a sad frame shown when its parent frame is loading is logged
// with appropriate metrics, namely as kShownWhileAncestorIsLoading rather than
// kShownAfterCrashing. See https://crbug.com/1132938.
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTestWithSadFrameTabReload,
CrashedSubframeVisibilityMetricsDuringParentLoad) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b(c))"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
FrameTreeNode* child = root->child_at(0);
FrameTreeNode* grandchild = child->child_at(0);
// Hide the grandchild frame.
RenderWidgetHostVisibilityObserver hide_observer(
grandchild->current_frame_host()->GetRenderWidgetHost(),
false /* became_visible */);
EXPECT_TRUE(
ExecJs(child, "document.querySelector('iframe').style.display = 'none'"));
hide_observer.WaitUntilSatisfied();
// Kill the c.com grandchild process.
CrashProcess(grandchild);
// Start a navigation in the b.com frame, but don't commit.
GURL url_d(embedded_test_server()->GetURL("d.com", "/title1.html"));
TestNavigationManager manager(web_contents(), url_d);
EXPECT_TRUE(ExecJs(child, JsReplace("location.href = $1", url_d)));
EXPECT_TRUE(manager.WaitForRequestStart());
// Make the grandchild iframe with the sad frame visible again. This should
// get logged as kShownWhileAncestorIsLoading, because its parent is
// currently loading.
{
base::HistogramTester histograms;
SadFrameShownObserver sad_frame_observer(grandchild);
EXPECT_TRUE(ExecJs(
child, "document.querySelector('iframe').style.display = 'block'"));
sad_frame_observer.Wait();
histograms.ExpectUniqueSample("Stability.ChildFrameCrash.Visibility",
CrashVisibility::kShownWhileAncestorIsLoading,
1);
// Ensure no new metrics are logged after the navigation completes.
ASSERT_TRUE(manager.WaitForNavigationFinished());
EXPECT_TRUE(manager.was_successful());
histograms.ExpectUniqueSample("Stability.ChildFrameCrash.Visibility",
CrashVisibility::kShownWhileAncestorIsLoading,
1);
}
}
// Verify that a sad frame shown when its parent frame is loading is logged
// with appropriate metrics, namely as kShownWhileAncestorIsLoading rather than
// kShownAfterCrashing. See https://crbug.com/1132938.
IN_PROC_BROWSER_TEST_P(
SitePerProcessBrowserTestWithSadFrameTabReload,
// TODO(crbug.com/40839850): Re-enable this test
DISABLED_CrashedFencedframeVisibilityMetricsDuringParentLoad) {
GURL primary_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL child_url(
embedded_test_server()->GetURL("b.com", "/fenced_frames/title1.html"));
GURL grandchild_url(
embedded_test_server()->GetURL("c.com", "/fenced_frames/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), primary_url));
RenderFrameHostImplWrapper primary_rfh(primary_main_frame_host());
RenderFrameHostImplWrapper child_rfh(
fenced_frame_test_helper().CreateFencedFrame(primary_rfh.get(),
child_url));
// Note that height and width follows the layout function in
// content/test/data/cross_site_iframe_factory.html.
EXPECT_TRUE(ExecJs(primary_rfh.get(), R"(
var ff = document.querySelector('fencedframe');
// layoutX = gridSizeX * largestChildX + extraXPerLevel
ff.width = 1 * (110 + 30) + 50;
// layoutY = gridSizeY * largestChildY + extraYPerLevel
ff.height = 1 * (110 + 30) + 50
)"));
RenderFrameHostImplWrapper grandchild_rfh(
fenced_frame_test_helper().CreateFencedFrame(child_rfh.get(),
grandchild_url));
// Note that height and width follows the layout function in
// content/test/data/cross_site_iframe_factory.html.
EXPECT_TRUE(ExecJs(child_rfh.get(), R"(
var ff = document.querySelector('fencedframe');
ff.width = 110;
ff.height = 110;
)"));
// Hide the grandchild frame.
RenderWidgetHostVisibilityObserver hide_observer(
grandchild_rfh->GetRenderWidgetHost(), false /* became_visible */);
EXPECT_TRUE(
ExecJs(child_rfh.get(),
"document.querySelector('fencedframe').style.display = 'none'"));
hide_observer.WaitUntilSatisfied();
// Kill the grandchild process.
CrashRendererProcess(grandchild_rfh.get());
// Start a navigation in the child frame, but don't commit.
GURL url_d(
embedded_test_server()->GetURL("d.com", "/fenced_frames/title1.html"));
TestNavigationManager manager(web_contents(), url_d);
EXPECT_TRUE(ExecJs(child_rfh.get(), JsReplace("location.href = $1", url_d)));
EXPECT_TRUE(manager.WaitForRequestStart());
// Make the grandchild fencedframe with the sad frame visible again.
// This should get logged as kShownWhileAncestorIsLoading, because its parent
// is currently loading.
{
base::HistogramTester histograms;
SadFrameShownObserver sad_frame_observer(grandchild_rfh.get());
EXPECT_TRUE(ExecJs(
child_rfh.get(),
"document.querySelector('fencedframe').style.display = 'block'"));
sad_frame_observer.Wait();
histograms.ExpectUniqueSample("Stability.ChildFrameCrash.Visibility",
CrashVisibility::kShownWhileAncestorIsLoading,
1);
// Ensure no new metrics are logged after the navigation completes.
ASSERT_TRUE(manager.WaitForNavigationFinished());
EXPECT_TRUE(manager.was_successful());
histograms.ExpectUniqueSample("Stability.ChildFrameCrash.Visibility",
CrashVisibility::kShownWhileAncestorIsLoading,
1);
}
}
// Verify the feature where hidden tabs with crashed subframes are marked for
// reload. This avoids showing crashed subframes if a hidden tab is eventually
// shown. Similar to the test above, except that the crashed subframe is
// scrolled out of view.
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTestWithSadFrameTabReload,
// TODO(crbug.com/40870019): Re-enable this test
DISABLED_ReloadHiddenTabWithCrashedSubframeOutOfView) {
// Set WebContents to VISIBLE to avoid hitting the |!did_first_set_visible_|
// case when we hide it later.
web_contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
// Navigate to a page with an OOPIF that's scrolled out of view.
GURL out_of_view_url(
embedded_test_server()->GetURL("a.com", "/iframe_out_of_view.html"));
EXPECT_TRUE(NavigateToURL(shell(), out_of_view_url));
EXPECT_EQ("LOADED", EvalJs(shell(), "notifyWhenLoaded();"));
NavigateIframeToURL(web_contents(), "test_iframe",
embedded_test_server()->GetURL("b.com", "/title1.html"));
// This will ensure that the layout has completed and visibility of the OOPIF
// has been updated in the browser process.
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
EXPECT_EQ(true,
EvalJsAfterLifecycleUpdate(root->current_frame_host(), "", "true"));
// Verify the OOPIF isn't visible at the moment.
RenderFrameProxyHost* proxy_to_parent =
root->child_at(0)->render_manager()->GetProxyToParent();
CrossProcessFrameConnector* connector =
proxy_to_parent->cross_process_frame_connector();
EXPECT_FALSE(connector->IsVisible());
EXPECT_EQ(blink::mojom::FrameVisibility::kRenderedOutOfViewport,
connector->visibility());
// Hide the WebContents and crash the OOPIF.
{
base::HistogramTester histograms;
web_contents()->UpdateWebContentsVisibility(Visibility::HIDDEN);
CrashProcess(root->child_at(0));
histograms.ExpectUniqueSample(
"Stability.ChildFrameCrash.TabMarkedForReload", true, 1);
histograms.ExpectUniqueSample(
"Stability.ChildFrameCrash.TabMarkedForReload.Visibility",
blink::mojom::FrameVisibility::kRenderedOutOfViewport, 1);
}
EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
EXPECT_FALSE(root->child_at(0)->current_frame_host()->IsRenderFrameLive());
// Show the tab and ensure that it reloads.
{
base::HistogramTester histograms;
web_contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
EXPECT_TRUE(WaitForLoadStop(web_contents()));
histograms.ExpectUniqueSample(
"Navigation.LoadIfNecessaryType",
NavigationControllerImpl::NeedsReloadType::kCrashedSubframe, 1);
}
EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
EXPECT_TRUE(root->child_at(0)->current_frame_host()->IsRenderFrameLive());
}
// Verify that hidden tabs with a crashed subframe are not marked for reload
// when the crashed subframe is hidden with "display:none".
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTestWithSadFrameTabReload,
DoNotReloadHiddenTabWithHiddenCrashedSubframe) {
// Set WebContents to VISIBLE to avoid hitting the |!did_first_set_visible_|
// case when we hide it later.
web_contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
GURL hidden_iframe_url(
embedded_test_server()->GetURL("a.com", "/page_with_hidden_iframe.html"));
EXPECT_TRUE(NavigateToURL(shell(), hidden_iframe_url));
NavigateIframeToURL(web_contents(), "test_iframe",
embedded_test_server()->GetURL("b.com", "/title1.html"));
// Ensure that the parent frame has propagated the OOPIF's hidden visibility
// to the browser process by forcing requestAnimationFrame and
// waiting for layout to finish.
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
EXPECT_EQ(true,
EvalJsAfterLifecycleUpdate(root->current_frame_host(), "", "true"));
// The OOPIF should be hidden at this point.
RenderFrameProxyHost* proxy_to_parent =
root->child_at(0)->render_manager()->GetProxyToParent();
EXPECT_TRUE(proxy_to_parent->cross_process_frame_connector()->IsHidden());
// Crashing a hidden OOPIF shouldn't mark the tab for reload.
web_contents()->UpdateWebContentsVisibility(Visibility::HIDDEN);
base::HistogramTester histograms;
CrashProcess(root->child_at(0));
histograms.ExpectUniqueSample("Stability.ChildFrameCrash.TabMarkedForReload",
false, 1);
// Making the WebContents visible again should keep the sad frame and should
// not load anything new.
web_contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
EXPECT_TRUE(WaitForLoadStop(web_contents()));
EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
EXPECT_FALSE(root->child_at(0)->current_frame_host()->IsRenderFrameLive());
}
// Ensure that the sad frame reload policy doesn't trigger for a visible tab,
// even if it becomes hidden and then visible again.
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTestWithSadFrameTabReload,
DoNotReloadVisibleTabWithCrashedSubframe) {
// Set WebContents to VISIBLE to avoid hitting the |!did_first_set_visible_|
// case when we hide it later.
web_contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
EXPECT_EQ(Visibility::VISIBLE, web_contents()->GetVisibility());
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
base::HistogramTester histograms;
CrashProcess(root->child_at(0));
histograms.ExpectUniqueSample("Stability.ChildFrameCrash.TabMarkedForReload",
false, 1);
EXPECT_EQ(Visibility::VISIBLE, web_contents()->GetVisibility());
web_contents()->UpdateWebContentsVisibility(Visibility::HIDDEN);
web_contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
EXPECT_TRUE(WaitForLoadStop(web_contents()));
EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
EXPECT_FALSE(root->child_at(0)->current_frame_host()->IsRenderFrameLive());
histograms.ExpectUniqueSample("Stability.ChildFrameCrash.TabMarkedForReload",
false, 1);
}
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
// TODO(crbug.com/40870019): Re-enable this test
DISABLED_ChildFrameCrashMetrics_KilledWhileVisible) {
// Set-up a frame tree that helps verify what the metrics tracks:
// 1) frames (12 frames are affected if B process gets killed) or
// 2) crashes (simply 1 crash if B process gets killed)?
// 3) widgets (10 b widgets and 1 c widget are affected if B is killed,
// but a sad frame will appear only in 9 widgets - this excludes
// widgets for the b,c(b) part of the frame tree) or
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b(b,c(b)),b,b,b,b,b,b,b,b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
std::vector<std::unique_ptr<SadFrameShownObserver>> observers;
for (size_t i = 0U; i < root->child_count(); i++) {
// At this point, all b.com subframes should be considered visible.
RenderFrameProxyHost* proxy_to_parent =
root->child_at(i)->render_manager()->GetProxyToParent();
CrossProcessFrameConnector* connector =
proxy_to_parent->cross_process_frame_connector();
EXPECT_TRUE(connector->IsVisible())
<< " subframe " << i << " with URL " << root->child_at(i)->current_url()
<< " is hidden";
observers.push_back(
std::make_unique<SadFrameShownObserver>(root->child_at(i)));
}
// Kill the child frame and wait for each of the subframe FrameTreeNodes to
// show a sad frame.
base::HistogramTester histograms;
RenderProcessHost* child_process =
root->child_at(0)->current_frame_host()->GetProcess();
RenderProcessHostWatcher crash_observer(
child_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
child_process->Shutdown(0);
crash_observer.Wait();
for (size_t i = 0U; i < root->child_count(); i++) {
SCOPED_TRACE(testing::Message()
<< " Waiting for sad frame from subframe " << i
<< " with URL:" << root->child_at(i)->current_url());
observers[i]->Wait();
}
// Verify that the expected metrics got logged.
histograms.ExpectUniqueSample(
"Stability.ChildFrameCrash.Visibility",
CrossProcessFrameConnector::CrashVisibility::kCrashedWhileVisible, 9);
// Hide and show the web contents and verify that no more metrics got logged.
web_contents()->UpdateWebContentsVisibility(Visibility::HIDDEN);
web_contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
histograms.ExpectUniqueSample(
"Stability.ChildFrameCrash.Visibility",
CrossProcessFrameConnector::CrashVisibility::kCrashedWhileVisible, 9);
}
INSTANTIATE_TEST_SUITE_P(All,
SitePerProcessBrowserTestWithoutSadFrameTabReload,
testing::ValuesIn(RenderDocumentFeatureLevelValues()));
INSTANTIATE_TEST_SUITE_P(All,
SitePerProcessBrowserTestWithSadFrameTabReload,
testing::ValuesIn(RenderDocumentFeatureLevelValues()));
} // namespace content