#include "content/browser/site_per_process_browsertest.h"
#include <list>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/functional/callback_helpers.h"
#include "base/json/json_reader.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/ranges/algorithm.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/scoped_run_loop_timeout.h"
#include "base/test/test_timeouts.h"
#include "base/time/time.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/navigation_controller_impl.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/navigator.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_child_frame.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/content_navigation_policy.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/back_forward_cache_util.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/content_mock_cert_verifier.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "content/test/render_document_feature.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::ElementsAre;
using testing::WhenSorted;
namespace content {
namespace {
void UnloadPrint(const ToRenderFrameHost& target, const char* message) {
EXPECT_TRUE(
ExecJs(target, JsReplace("window.onunload = function() { "
" window.domAutomationController.send($1);"
"}",
message)));
}
}
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, DetachInUnloadHandler) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b(b))"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetPrimaryFrameTree()
.root();
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
" +--Site B -- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://b.com/",
DepictFrameTree(root));
EXPECT_EQ(1, EvalJs(root->child_at(0), "frames.length;"));
RenderFrameDeletedObserver deleted_observer(
root->child_at(0)->child_at(0)->current_frame_host());
EXPECT_TRUE(
ExecJs(root->child_at(0)->child_at(0),
"window.onunload=function(e){\n"
" window.parent.document.getElementById('child-0').remove();\n"
"};\n"));
auto script = JsReplace("window.document.getElementById('child-0').src = $1",
embedded_test_server()->GetURL(
"c.com", "/cross_site_iframe_factory.html?c"));
EXPECT_TRUE(ExecJs(root->child_at(0), script));
deleted_observer.WaitUntilDeleted();
EXPECT_EQ(0, EvalJs(root->child_at(0), "frames.length;"));
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://b.com/",
DepictFrameTree(root));
}
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, NavigateInUnloadHandler) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b(b))"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetPrimaryFrameTree()
.root();
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
" +--Site B -- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://b.com/",
DepictFrameTree(root));
EXPECT_EQ(1,
EvalJs(root->child_at(0)->current_frame_host(), "frames.length;"));
EXPECT_TRUE(ExecJs(root->child_at(0)->child_at(0)->current_frame_host(),
"window.onunload=function(e){\n"
" window.location = '#navigate';\n"
"};\n"));
RenderFrameDeletedObserver deleted_observer(
root->child_at(0)->child_at(0)->current_frame_host());
auto script = JsReplace("window.document.getElementById('child-0').src = $1",
embedded_test_server()->GetURL(
"c.com", "/cross_site_iframe_factory.html"));
EXPECT_TRUE(ExecJs(root->child_at(0)->current_frame_host(), script));
deleted_observer.WaitUntilDeleted();
EXPECT_EQ(0, EvalJs(root->child_at(0)->child_at(0)->current_frame_host(),
"frames.length;"));
EXPECT_EQ(
" Site A ------------ proxies for B C\n"
" +--Site B ------- proxies for A C\n"
" +--Site C -- proxies for A B\n"
"Where A = http://a.com/\n"
" B = http://b.com/\n"
" C = http://c.com/",
DepictFrameTree(root));
}
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
CanceledBeforeUnloadShouldNotClearRemoteFrameView) {
GURL a_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), a_url));
FrameTreeNode* child_node =
web_contents()->GetPrimaryFrameTree().root()->child_at(0);
GURL b_url(embedded_test_server()->GetURL(
"b.com", "/render_frame_host/beforeunload.html"));
EXPECT_TRUE(NavigateToURLFromRenderer(child_node, b_url));
CrossProcessFrameConnector* frame_connector_delegate =
static_cast<RenderWidgetHostViewChildFrame*>(
child_node->current_frame_host()->GetView())
->FrameConnectorForTesting();
PrepContentsForBeforeUnloadTest(web_contents());
SetShouldProceedOnBeforeUnload(shell(), true ,
false );
ASSERT_TRUE(
ExecJs(web_contents(),
"document.querySelector('iframe').style.visibility = 'hidden';"));
while (!frame_connector_delegate->IsHidden()) {
base::RunLoop run_loop;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
run_loop.Run();
}
AppModalDialogWaiter dialog_waiter(shell());
ASSERT_TRUE(ExecJs(web_contents(),
"document.querySelector('iframe').src = 'about:blank';"));
dialog_waiter.Wait();
ASSERT_TRUE(static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetView())
->IsRenderWidgetHostViewChildFrame());
ASSERT_TRUE(
ExecJs(web_contents(),
"document.querySelector('iframe').style.visibility = 'visible';"));
while (frame_connector_delegate->IsHidden()) {
base::RunLoop run_loop;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
run_loop.Run();
}
}
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
ParentOriginDoesNotChangeInUnloadHandler) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetPrimaryFrameTree()
.root();
GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
auto* popup_shell = OpenPopup(shell()->web_contents(), b_url, "popup");
WebContents* popup_web_contents = popup_shell->web_contents();
EXPECT_TRUE(ExecJs(root->child_at(0),
"window.onunload = function(e) {"
" window.open('','popup').domAutomationController.send("
" 'top-origin ' + location.ancestorOrigins[0]);"
"};"));
GURL c_url(embedded_test_server()->GetURL("c.com", "/title1.html"));
DOMMessageQueue msg_queue(popup_web_contents);
EXPECT_TRUE(NavigateToURL(shell(), c_url));
std::string message, top_origin;
while (msg_queue.WaitForMessage(&message)) {
base::TrimString(message, "\"", &message);
auto message_parts = base::SplitString(message, " ", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
if (message_parts[0] == "top-origin") {
top_origin = message_parts[1];
break;
}
}
EXPECT_EQ(top_origin + "/", main_url.DeprecatedGetOriginAsURL().spec());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
UnloadACKArrivesPriorToProcessShutdownRequest) {
GURL start_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), start_url));
RenderFrameHostImpl* rfh = web_contents()->GetPrimaryMainFrame();
rfh->DisableUnloadTimerForTesting();
RenderProcessHostWatcher watcher(
rfh->GetProcess(), RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
bool received_unload = false;
auto unload_ack_filter = base::BindLambdaForTesting([&]() {
received_unload = true;
return false;
});
rfh->SetUnloadACKCallbackForTesting(unload_ack_filter);
DisableBackForwardCacheForTesting(web_contents(),
BackForwardCache::TEST_REQUIRES_NO_CACHING);
GURL cross_site_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
EXPECT_TRUE(NavigateToURLFromRenderer(shell(), cross_site_url));
watcher.Wait();
EXPECT_TRUE(received_unload);
EXPECT_TRUE(watcher.did_exit_normally());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
NoCommitTimeoutWithBeforeUnloadDialog) {
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), a_url));
RenderProcessHost* a_process =
web_contents->GetPrimaryMainFrame()->GetProcess();
GURL b_url(embedded_test_server()->GetURL("b.com", "/title2.html"));
Shell* new_shell = OpenPopup(web_contents, b_url, "newtab");
WebContents* new_contents = new_shell->web_contents();
EXPECT_TRUE(WaitForLoadStop(new_contents));
RenderProcessHost* b_process =
new_contents->GetPrimaryMainFrame()->GetProcess();
EXPECT_NE(a_process, b_process);
web_contents->GetPrimaryMainFrame()
->DisableBeforeUnloadHangMonitorForTesting();
web_contents->GetPrimaryMainFrame()->ExecuteJavaScriptWithUserGestureForTests(
std::u16string(), base::NullCallback());
BeforeUnloadBlockingDelegate test_delegate(web_contents);
EXPECT_TRUE(
ExecJs(web_contents, "window.onbeforeunload=function(e){ return 'x' }"));
EXPECT_TRUE(ExecJs(web_contents,
"setTimeout(function() { window.location.reload() }, 0)"));
test_delegate.Wait();
base::TimeDelta kTimeout = base::Milliseconds(100);
NavigationRequest::SetCommitTimeoutForTesting(kTimeout);
GURL hung_url(embedded_test_server()->GetURL("a.com", "/title3.html"));
UnresponsiveRendererObserver unresponsive_renderer_observer(new_contents);
EXPECT_TRUE(
ExecJs(new_contents, JsReplace("window.location = $1", hung_url)));
RenderProcessHost* hung_process =
unresponsive_renderer_observer.Wait(kTimeout * 10);
EXPECT_FALSE(hung_process);
NavigationRequest::SetCommitTimeoutForTesting(base::TimeDelta());
}
// B1 D --- Navigate ---> E D
// C1 C2
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_MAC)
#define MAYBE_UnloadHandlerSubframes DISABLED_UnloadHandlerSubframes
#else
#define MAYBE_UnloadHandlerSubframes UnloadHandlerSubframes
#endif
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
MAYBE_UnloadHandlerSubframes) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b(c(b),c(a(c))),d)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
UnloadPrint(root, "A1");
UnloadPrint(root->child_at(0), "B1");
UnloadPrint(root->child_at(0)->child_at(0), "C1");
UnloadPrint(root->child_at(0)->child_at(1), "C2");
UnloadPrint(root->child_at(0)->child_at(0)->child_at(0), "B2");
UnloadPrint(root->child_at(0)->child_at(1)->child_at(0), "A2");
UnloadPrint(root->child_at(0)->child_at(1)->child_at(0)->child_at(0), "C3");
DOMMessageQueue dom_message_queue(
WebContents::FromRenderFrameHost(web_contents()->GetPrimaryMainFrame()));
root->child_at(0)->current_frame_host()->DisableUnloadTimerForTesting();
RenderProcessHostWatcher shutdown_B(
root->child_at(0)->current_frame_host()->GetProcess(),
RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
RenderProcessHostWatcher shutdown_C(
root->child_at(0)->child_at(0)->current_frame_host()->GetProcess(),
RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
GURL e_url(embedded_test_server()->GetURL("e.com", "/title1.html"));
NavigateFrameToURL(root->child_at(0), e_url);
std::string message;
std::vector<std::string> messages;
for (int i = 0; i < 6; ++i) {
EXPECT_TRUE(dom_message_queue.WaitForMessage(&message));
base::TrimString(message, "\"", &message);
messages.push_back(message);
}
EXPECT_FALSE(dom_message_queue.PopMessage(&message));
EXPECT_THAT(messages,
WhenSorted(ElementsAre("A2", "B1", "B2", "C1", "C2", "C3")));
auto B1 = base::ranges::find(messages, "B1");
auto B2 = base::ranges::find(messages, "B2");
EXPECT_LT(B1, B2);
auto C2 = base::ranges::find(messages, "C2");
auto C3 = base::ranges::find(messages, "C3");
EXPECT_LT(C2, C3);
shutdown_B.Wait();
shutdown_C.Wait();
}
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, SlowUnloadHandlerInIframe) {
GURL initial_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
GURL next_url(embedded_test_server()->GetURL("c.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), initial_url));
RenderFrameHostImpl* rfh_b = web_contents()
->GetPrimaryFrameTree()
.root()
->child_at(0)
->current_frame_host();
rfh_b->DoNotDeleteForTesting();
DisableBackForwardCacheForTesting(web_contents(),
BackForwardCache::TEST_USES_UNLOAD_EVENT);
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
RenderFrameDeletedObserver deleted_observer(root->current_frame_host());
EXPECT_TRUE(NavigateToURL(shell(), next_url));
deleted_observer.WaitUntilDeleted();
}
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, Unload_ABAB) {
web_contents()->GetController().GetBackForwardCache().DisableForTesting(
content::BackForwardCache::TEST_USES_UNLOAD_EVENT);
GURL initial_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b(a(b)))"));
GURL next_url(embedded_test_server()->GetURL("c.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), initial_url));
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
UnloadPrint(root, "A1");
UnloadPrint(root->child_at(0), "B1");
UnloadPrint(root->child_at(0)->child_at(0), "A2");
UnloadPrint(root->child_at(0)->child_at(0)->child_at(0), "B2");
root->current_frame_host()->DisableUnloadTimerForTesting();
DOMMessageQueue dom_message_queue(
WebContents::FromRenderFrameHost(web_contents()->GetPrimaryMainFrame()));
RenderProcessHostWatcher shutdown_A(
root->current_frame_host()->GetProcess(),
RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
RenderProcessHostWatcher shutdown_B(
root->child_at(0)->current_frame_host()->GetProcess(),
RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
EXPECT_TRUE(NavigateToURL(shell(), next_url));
std::vector<std::string> messages;
std::string message;
for (int i = 0; i < 4; ++i) {
EXPECT_TRUE(dom_message_queue.WaitForMessage(&message));
base::TrimString(message, "\"", &message);
messages.push_back(message);
}
EXPECT_FALSE(dom_message_queue.PopMessage(&message));
EXPECT_THAT(messages, WhenSorted(ElementsAre("A1", "A2", "B1", "B2")));
auto A1 = base::ranges::find(messages, "A1");
auto A2 = base::ranges::find(messages, "A2");
auto B1 = base::ranges::find(messages, "B1");
auto B2 = base::ranges::find(messages, "B2");
EXPECT_LT(A1, A2);
EXPECT_LT(B1, B2);
shutdown_A.Wait();
shutdown_B.Wait();
}
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, UnloadNestedPendingDeletion) {
std::string onunload_script = "window.onunload = function(){}";
GURL url_abc(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b(c))"));
GURL url_d(embedded_test_server()->GetURL("d.com", "/title1.html"));
GURL url_e(embedded_test_server()->GetURL("e.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_abc));
RenderFrameHostImpl* rfh_a = web_contents()->GetPrimaryMainFrame();
RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host();
RenderFrameHostImpl* rfh_c = rfh_b->child_at(0)->current_frame_host();
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive,
rfh_a->lifecycle_state());
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive,
rfh_b->lifecycle_state());
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive,
rfh_c->lifecycle_state());
auto unload_ack_filter = base::BindRepeating([] { return true; });
rfh_b->SetUnloadACKCallbackForTesting(unload_ack_filter);
rfh_c->SetUnloadACKCallbackForTesting(unload_ack_filter);
EXPECT_TRUE(ExecJs(rfh_b->frame_tree_node(), onunload_script));
EXPECT_TRUE(ExecJs(rfh_c->frame_tree_node(), onunload_script));
rfh_b->DisableUnloadTimerForTesting();
rfh_c->DisableUnloadTimerForTesting();
RenderFrameDeletedObserver delete_b(rfh_b), delete_c(rfh_c);
EXPECT_TRUE(NavigateToURLFromRenderer(rfh_c->frame_tree_node(), url_d));
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive,
rfh_a->lifecycle_state());
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive,
rfh_b->lifecycle_state());
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kRunningUnloadHandlers,
rfh_c->lifecycle_state());
RenderFrameHostImpl* rfh_d = rfh_b->child_at(0)->current_frame_host();
rfh_d->SetSubframeUnloadTimeoutForTesting(base::Seconds(30));
RenderFrameDeletedObserver delete_d(rfh_d);
rfh_d->DoNotDeleteForTesting();
EXPECT_TRUE(ExecJs(rfh_d->frame_tree_node(), onunload_script));
EXPECT_TRUE(NavigateToURLFromRenderer(rfh_b->frame_tree_node(), url_e));
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive,
rfh_a->lifecycle_state());
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kRunningUnloadHandlers,
rfh_b->lifecycle_state());
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kRunningUnloadHandlers,
rfh_c->lifecycle_state());
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kRunningUnloadHandlers,
rfh_d->lifecycle_state());
EXPECT_FALSE(delete_c.deleted());
EXPECT_FALSE(delete_d.deleted());
rfh_d->DetachForTesting();
EXPECT_TRUE(delete_c.deleted());
EXPECT_TRUE(delete_d.deleted());
EXPECT_FALSE(delete_b.deleted());
rfh_b->SetUnloadACKCallbackForTesting(base::NullCallback());
rfh_b->OnUnloadACK();
EXPECT_TRUE(delete_b.deleted());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, PartialUnloadHandler) {
web_contents()->GetController().GetBackForwardCache().DisableForTesting(
content::BackForwardCache::TEST_USES_UNLOAD_EVENT);
GURL url_aba(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b(a))"));
GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_aba));
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
RenderFrameHostImpl* a1 = root->current_frame_host();
RenderFrameHostImpl* b1 = a1->child_at(0)->current_frame_host();
RenderFrameHostImpl* a2 = b1->child_at(0)->current_frame_host();
RenderFrameDeletedObserver delete_a1(a1);
RenderFrameDeletedObserver delete_a2(a2);
RenderFrameDeletedObserver delete_b1(b1);
auto unload_ack_filter = base::BindRepeating([] { return true; });
a1->SetUnloadACKCallbackForTesting(unload_ack_filter);
a1->DoNotDeleteForTesting();
a2->DoNotDeleteForTesting();
a1->DisableUnloadTimerForTesting();
b1->SetSubframeUnloadTimeoutForTesting(base::Seconds(30));
UnloadPrint(a2->frame_tree_node(), "A2");
DOMMessageQueue dom_message_queue(
WebContents::FromRenderFrameHost(web_contents()->GetPrimaryMainFrame()));
EXPECT_TRUE(NavigateToURL(shell(), url_c));
std::string message, message_unused;
EXPECT_TRUE(dom_message_queue.WaitForMessage(&message));
EXPECT_FALSE(dom_message_queue.PopMessage(&message_unused));
EXPECT_EQ("\"A2\"", message);
EXPECT_FALSE(delete_a1.deleted());
EXPECT_FALSE(delete_b1.deleted());
EXPECT_FALSE(delete_a2.deleted());
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kRunningUnloadHandlers,
a1->lifecycle_state());
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kReadyToBeDeleted,
b1->lifecycle_state());
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kRunningUnloadHandlers,
a2->lifecycle_state());
b1->DetachForTesting();
EXPECT_FALSE(delete_a1.deleted());
EXPECT_FALSE(delete_b1.deleted());
EXPECT_FALSE(delete_a2.deleted());
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kRunningUnloadHandlers,
a1->lifecycle_state());
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kReadyToBeDeleted,
b1->lifecycle_state());
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kRunningUnloadHandlers,
a2->lifecycle_state());
a2->DetachForTesting();
EXPECT_FALSE(delete_a1.deleted());
EXPECT_TRUE(delete_b1.deleted());
EXPECT_TRUE(delete_a2.deleted());
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kRunningUnloadHandlers,
a1->lifecycle_state());
a1->ResumeDeletionForTesting();
a1->SetUnloadACKCallbackForTesting(base::NullCallback());
a1->OnUnloadACK();
EXPECT_TRUE(delete_a1.deleted());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
PendingDeletionCheckCompletedOnSubtree) {
web_contents()->GetController().GetBackForwardCache().DisableForTesting(
content::BackForwardCache::TEST_USES_UNLOAD_EVENT);
GURL url_1(embedded_test_server()->GetURL(
"a.com",
"/cross_site_iframe_factory.html?a(a,a,a(a),a(a),a(a),a(a,a),a(a,a))"));
GURL url_2(embedded_test_server()->GetURL("b.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_1));
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
RenderFrameHostImpl* rfh_0 = root->current_frame_host();
RenderFrameHostImpl* rfh_1 = rfh_0->child_at(0)->current_frame_host();
RenderFrameHostImpl* rfh_2 = rfh_0->child_at(1)->current_frame_host();
RenderFrameHostImpl* rfh_3 = rfh_0->child_at(2)->current_frame_host();
RenderFrameHostImpl* rfh_4 = rfh_3->child_at(0)->current_frame_host();
RenderFrameHostImpl* rfh_5 = rfh_0->child_at(3)->current_frame_host();
RenderFrameHostImpl* rfh_6 = rfh_5->child_at(0)->current_frame_host();
RenderFrameHostImpl* rfh_7 = rfh_0->child_at(4)->current_frame_host();
RenderFrameHostImpl* rfh_8 = rfh_7->child_at(0)->current_frame_host();
RenderFrameHostImpl* rfh_9 = rfh_0->child_at(5)->current_frame_host();
RenderFrameHostImpl* rfh_10 = rfh_9->child_at(0)->current_frame_host();
RenderFrameHostImpl* rfh_11 = rfh_9->child_at(1)->current_frame_host();
RenderFrameHostImpl* rfh_12 = rfh_0->child_at(6)->current_frame_host();
RenderFrameHostImpl* rfh_13 = rfh_12->child_at(0)->current_frame_host();
RenderFrameHostImpl* rfh_14 = rfh_12->child_at(1)->current_frame_host();
RenderFrameDeletedObserver delete_a0(rfh_0), delete_a1(rfh_1),
delete_a2(rfh_2), delete_a3(rfh_3), delete_a4(rfh_4), delete_a5(rfh_5),
delete_a6(rfh_6), delete_a7(rfh_7), delete_a8(rfh_8), delete_a9(rfh_9),
delete_a10(rfh_10), delete_a11(rfh_11), delete_a12(rfh_12),
delete_a13(rfh_13), delete_a14(rfh_14);
UnloadPrint(rfh_1->frame_tree_node(), "");
UnloadPrint(rfh_3->frame_tree_node(), "");
UnloadPrint(rfh_6->frame_tree_node(), "");
UnloadPrint(rfh_14->frame_tree_node(), "");
auto unload_ack_filter = base::BindRepeating([] { return true; });
rfh_0->SetUnloadACKCallbackForTesting(unload_ack_filter);
rfh_0->DoNotDeleteForTesting();
rfh_1->DoNotDeleteForTesting();
rfh_3->DoNotDeleteForTesting();
rfh_5->DoNotDeleteForTesting();
rfh_6->DoNotDeleteForTesting();
rfh_12->DoNotDeleteForTesting();
rfh_14->DoNotDeleteForTesting();
rfh_0->DisableUnloadTimerForTesting();
EXPECT_TRUE(NavigateToURL(shell(), url_2));
EXPECT_FALSE(delete_a0.deleted());
EXPECT_FALSE(delete_a1.deleted());
EXPECT_TRUE(delete_a2.deleted());
EXPECT_FALSE(delete_a3.deleted());
EXPECT_TRUE(delete_a4.deleted());
EXPECT_FALSE(delete_a5.deleted());
EXPECT_FALSE(delete_a6.deleted());
EXPECT_TRUE(delete_a7.deleted());
EXPECT_TRUE(delete_a8.deleted());
EXPECT_TRUE(delete_a9.deleted());
EXPECT_TRUE(delete_a10.deleted());
EXPECT_TRUE(delete_a11.deleted());
EXPECT_FALSE(delete_a12.deleted());
EXPECT_TRUE(delete_a13.deleted());
EXPECT_FALSE(delete_a14.deleted());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
DetachedIframeUnloadHandlerABC) {
GURL initial_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b(c))"));
EXPECT_TRUE(NavigateToURL(shell(), initial_url));
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
RenderFrameHostImpl* rfh_a = root->current_frame_host();
RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host();
RenderFrameHostImpl* rfh_c = rfh_b->child_at(0)->current_frame_host();
UnloadPrint(rfh_b->frame_tree_node(), "B");
UnloadPrint(rfh_c->frame_tree_node(), "C");
DOMMessageQueue dom_message_queue(web_contents());
RenderProcessHostWatcher shutdown_B(
rfh_b->GetProcess(), RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
RenderProcessHostWatcher shutdown_C(
rfh_c->GetProcess(), RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
ExecuteScriptAsync(root, "document.querySelector('iframe').remove();");
std::vector<std::string> messages(2);
EXPECT_TRUE(dom_message_queue.WaitForMessage(&messages[0]));
EXPECT_TRUE(dom_message_queue.WaitForMessage(&messages[1]));
std::string unused;
EXPECT_FALSE(dom_message_queue.PopMessage(&unused));
std::sort(messages.begin(), messages.end());
EXPECT_EQ("\"B\"", messages[0]);
EXPECT_EQ("\"C\"", messages[1]);
shutdown_B.Wait();
shutdown_C.Wait();
}
#if defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER)
#define MAYBE_DetachedIframeUnloadHandlerABCB \
DISABLED_DetachedIframeUnloadHandlerABCB
#else
#define MAYBE_DetachedIframeUnloadHandlerABCB DetachedIframeUnloadHandlerABCB
#endif
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
MAYBE_DetachedIframeUnloadHandlerABCB) {
base::test::ScopedRunLoopTimeout increase_timeout(
FROM_HERE, TestTimeouts::action_max_timeout());
GURL initial_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b(c(b)))"));
EXPECT_TRUE(NavigateToURL(shell(), initial_url));
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
RenderFrameHostImpl* rfh_a = root->current_frame_host();
RenderFrameHostImpl* rfh_b1 = rfh_a->child_at(0)->current_frame_host();
RenderFrameHostImpl* rfh_c = rfh_b1->child_at(0)->current_frame_host();
RenderFrameHostImpl* rfh_b2 = rfh_c->child_at(0)->current_frame_host();
UnloadPrint(rfh_b1->frame_tree_node(), "B1");
UnloadPrint(rfh_b2->frame_tree_node(), "B2");
UnloadPrint(rfh_c->frame_tree_node(), "C");
DOMMessageQueue dom_message_queue(web_contents());
RenderProcessHostWatcher shutdown_B(
rfh_b1->GetProcess(), RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
RenderProcessHostWatcher shutdown_C(
rfh_c->GetProcess(), RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
ExecuteScriptAsync(root, "document.querySelector('iframe').remove();");
std::vector<std::string> messages(3);
EXPECT_TRUE(dom_message_queue.WaitForMessage(&messages[0]));
EXPECT_TRUE(dom_message_queue.WaitForMessage(&messages[1]));
EXPECT_TRUE(dom_message_queue.WaitForMessage(&messages[2]));
std::string unused;
EXPECT_FALSE(dom_message_queue.PopMessage(&unused));
std::sort(messages.begin(), messages.end());
EXPECT_EQ("\"B1\"", messages[0]);
EXPECT_EQ("\"B2\"", messages[1]);
EXPECT_EQ("\"C\"", messages[2]);
shutdown_B.Wait();
shutdown_C.Wait();
}
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
DetachedIframeUnloadHandlerAAB) {
GURL initial_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(a(b))"));
EXPECT_TRUE(NavigateToURL(shell(), initial_url));
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
RenderFrameHostImpl* rfh_a1 = root->current_frame_host();
RenderFrameHostImpl* rfh_a2 = rfh_a1->child_at(0)->current_frame_host();
RenderFrameHostImpl* rfh_b = rfh_a2->child_at(0)->current_frame_host();
UnloadPrint(rfh_a2->frame_tree_node(), "A2");
UnloadPrint(rfh_b->frame_tree_node(), "B");
DOMMessageQueue dom_message_queue(web_contents());
RenderProcessHostWatcher shutdown_B(
rfh_b->GetProcess(), RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
ExecuteScriptAsync(rfh_a2->frame_tree_node(),
"parent.document.querySelector('iframe').remove();");
std::vector<std::string> messages(2);
EXPECT_TRUE(dom_message_queue.WaitForMessage(&messages[0]));
EXPECT_TRUE(dom_message_queue.WaitForMessage(&messages[1]));
std::string unused;
EXPECT_FALSE(dom_message_queue.PopMessage(&unused));
std::sort(messages.begin(), messages.end());
EXPECT_EQ("\"A2\"", messages[0]);
EXPECT_EQ("\"B\"", messages[1]);
shutdown_B.Wait();
}
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
RendererInitiatedWindowCloseWithUnload) {
GURL main_url(embedded_test_server()->GetURL("a.com", "/empty.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
GURL open_url = embedded_test_server()->GetURL(
"a.com", "/unload_handler_force_layout.html");
EXPECT_TRUE(ExecJs(root,
"window.addEventListener('message', function(event) {\n"
" document.title = event.data;\n"
"});"));
{
std::u16string title_when_loaded = u"loaded";
TitleWatcher title_watcher(shell()->web_contents(), title_when_loaded);
EXPECT_TRUE(ExecJs(root, JsReplace("var w = window.open($1)", open_url)));
EXPECT_EQ(title_watcher.WaitAndGetTitle(), title_when_loaded);
}
{
std::u16string title_when_done = u"unloaded";
TitleWatcher title_watcher(shell()->web_contents(), title_when_done);
EXPECT_TRUE(ExecJs(root, "w.close()"));
EXPECT_EQ(title_watcher.WaitAndGetTitle(), title_when_done);
}
}
IN_PROC_BROWSER_TEST_P(
SitePerProcessBrowserTest,
IsDetachedSubframeObservableDuringUnloadHandlerSameProcess) {
GURL page_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(a(b),c)"));
EXPECT_TRUE(NavigateToURL(shell(), page_url));
RenderFrameHostImpl* node1 =
static_cast<WebContentsImpl*>(shell()->web_contents())
->GetPrimaryFrameTree()
.root()
->current_frame_host();
RenderFrameHostImpl* node2 = node1->child_at(0)->current_frame_host();
RenderFrameHostImpl* node3 = node2->child_at(0)->current_frame_host();
RenderFrameHostImpl* node4 = node1->child_at(1)->current_frame_host();
ASSERT_TRUE(ExecJs(node1, "window.name = 'node1'"));
ASSERT_TRUE(ExecJs(node2, "window.name = 'node2'"));
ASSERT_TRUE(ExecJs(node3, "window.name = 'node3'"));
ASSERT_TRUE(ExecJs(node4, "window.name = 'node4'"));
ASSERT_TRUE(ExecJs(node1, "window.node2 = window[0]"));
ASSERT_TRUE(ExecJs(node1, "window.node3 = window[0][0]"));
ASSERT_TRUE(ExecJs(node1, "window.node4 = window[1]"));
EXPECT_EQ(true, EvalJs(node1, "!!window.node2"));
EXPECT_EQ(true, EvalJs(node1, "!!window.node3"));
EXPECT_EQ(true, EvalJs(node1, "!!window.node4"));
node3->DoNotDeleteForTesting();
node2->DisableUnloadTimerForTesting();
ASSERT_TRUE(ExecJs(node3, "window.onunload = ()=>{}"));
const char* kPostMessageHandlerScript = R"(
window.postMessageGotData == false;
window.postMessageCallback = function() {};
function receiveMessage(event) {
console.log('node4 - receiveMessage...');
var can_node3_be_found = false;
try {
can_node3_be_found = !!top[0][0]; // top.node2.node3
} catch(e) {
can_node3_be_found = false;
}
window.postMessageGotData = true;
window.postMessageData = can_node3_be_found;
window.postMessageCallback(window.postMessageData);
}
window.addEventListener("message", receiveMessage, false);
)";
ASSERT_TRUE(ExecJs(node4, kPostMessageHandlerScript));
const char* kNavigationScript = R"(
var node2_frame = document.getElementsByTagName('iframe')[0];
node2_frame.onload = function() {
console.log('node2_frame.onload ...');
window.node4.postMessage('try to find node3', '*');
};
node2_frame.src = $1;
)";
GURL url = embedded_test_server()->GetURL("a.com", "/title1.html");
ASSERT_TRUE(ExecJs(node1, JsReplace(kNavigationScript, url)));
const char* kPostMessageResultsScript = R"(
new Promise(function (resolve, reject) {
if (window.postMessageGotData)
resolve(window.postMessageData);
else
window.postMessageCallback = resolve;
});
)";
EXPECT_EQ(false, EvalJs(node4, kPostMessageResultsScript));
}
IN_PROC_BROWSER_TEST_P(
SitePerProcessBrowserTest,
IsDetachedSubframeObservableDuringUnloadHandlerCrossProcess) {
GURL page_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(a(b),c)"));
EXPECT_TRUE(NavigateToURL(shell(), page_url));
RenderFrameHostImpl* node1 =
static_cast<WebContentsImpl*>(shell()->web_contents())
->GetPrimaryFrameTree()
.root()
->current_frame_host();
RenderFrameHostImpl* node2 = node1->child_at(0)->current_frame_host();
RenderFrameHostImpl* node3 = node2->child_at(0)->current_frame_host();
RenderFrameHostImpl* node4 = node1->child_at(1)->current_frame_host();
ASSERT_TRUE(ExecJs(node1, "window.name = 'node1'"));
ASSERT_TRUE(ExecJs(node2, "window.name = 'node2'"));
ASSERT_TRUE(ExecJs(node3, "window.name = 'node3'"));
ASSERT_TRUE(ExecJs(node4, "window.name = 'node4'"));
ASSERT_TRUE(ExecJs(node1, "window.node2 = window[0]"));
ASSERT_TRUE(ExecJs(node1, "window.node3 = window[0][0]"));
ASSERT_TRUE(ExecJs(node1, "window.node4 = window[1]"));
EXPECT_EQ(true, EvalJs(node1, "!!window.node2"));
EXPECT_EQ(true, EvalJs(node1, "!!window.node3"));
EXPECT_EQ(true, EvalJs(node1, "!!window.node4"));
node3->DoNotDeleteForTesting();
node2->DisableUnloadTimerForTesting();
ASSERT_TRUE(ExecJs(node3, "window.onunload = ()=>{}"));
const char* kPostMessageHandlerScript = R"(
window.postMessageGotData == false;
window.postMessageCallback = function() {};
function receiveMessage(event) {
console.log('node4 - receiveMessage...');
var can_node3_be_found = false;
try {
can_node3_be_found = !!top[0][0]; // top.node2.node3
} catch(e) {
can_node3_be_found = false;
}
window.postMessageGotData = true;
window.postMessageData = can_node3_be_found;
window.postMessageCallback(window.postMessageData);
}
window.addEventListener("message", receiveMessage, false);
)";
ASSERT_TRUE(ExecJs(node4, kPostMessageHandlerScript));
const char* kNavigationScript = R"(
var node2_frame = document.getElementsByTagName('iframe')[0];
node2_frame.onload = function() {
console.log('node2_frame.onload ...');
window.node4.postMessage('try to find node3', '*');
};
node2_frame.src = $1;
)";
GURL url = embedded_test_server()->GetURL("d.com", "/title1.html");
ASSERT_TRUE(ExecJs(node1, JsReplace(kNavigationScript, url)));
const char* kPostMessageResultsScript = R"(
new Promise(function (resolve, reject) {
if (window.postMessageGotData)
resolve(window.postMessageData);
else
window.postMessageCallback = resolve;
});
)";
EXPECT_EQ(false, EvalJs(node4, kPostMessageResultsScript));
}
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, FocusedFrameUnload) {
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b,c)")));
RenderFrameHostImpl* A1 = web_contents()->GetPrimaryMainFrame();
RenderFrameHostImpl* B2 = A1->child_at(0)->current_frame_host();
RenderFrameHostImpl* C3 = A1->child_at(1)->current_frame_host();
FrameTree* frame_tree = A1->frame_tree();
EXPECT_EQ(A1->frame_tree_node(), frame_tree->GetFocusedFrame());
EXPECT_TRUE(ExecJs(A1, "document.querySelector('iframe').focus()"));
EXPECT_EQ(B2->frame_tree_node(), frame_tree->GetFocusedFrame());
B2->SetSubframeUnloadTimeoutForTesting(base::Seconds(30));
EXPECT_FALSE(B2->GetSuddenTerminationDisablerState(
blink::mojom::SuddenTerminationDisablerType::kUnloadHandler));
B2->DoNotDeleteForTesting();
EXPECT_TRUE(ExecJs(B2, "window.onunload = ()=>{};"));
EXPECT_TRUE(B2->GetSuddenTerminationDisablerState(
blink::mojom::SuddenTerminationDisablerType::kUnloadHandler));
EXPECT_TRUE(B2->IsActive());
EXPECT_TRUE(ExecJs(A1, "document.querySelector('iframe').remove()"));
EXPECT_EQ(nullptr, frame_tree->GetFocusedFrame());
EXPECT_EQ(2u, A1->child_count());
EXPECT_TRUE(B2->IsPendingDeletion());
EXPECT_TRUE(NavigateToURLFromRenderer(
C3->frame_tree_node(),
embedded_test_server()->GetURL("d.com", "/title1.html")));
EXPECT_TRUE(WaitForLoadStop(web_contents()));
EXPECT_EQ(2u, A1->child_count());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, UnloadTimeout) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
RenderFrameHostImpl* A1 = web_contents()->GetPrimaryMainFrame();
RenderFrameHostImpl* B2 = A1->child_at(0)->current_frame_host();
EXPECT_TRUE(ExecJs(B2, "window.onunload = ()=>{};"));
B2->DoNotDeleteForTesting();
RenderFrameDeletedObserver delete_B2(B2);
EXPECT_TRUE(ExecJs(A1, "document.querySelector('iframe').remove()"));
delete_B2.WaitUntilDeleted();
}
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
UnloadPostMessageToParentCrossProcess) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
RenderFrameHostImpl* A1 = web_contents()->GetPrimaryMainFrame();
RenderFrameHostImpl* B2 = A1->child_at(0)->current_frame_host();
RenderFrameDeletedObserver delete_B2(B2);
EXPECT_TRUE(ExecJs(B2, R"(
window.addEventListener("unload", function() {
window.parent.postMessage("B2 message", "*");
});
)"));
EXPECT_TRUE(ExecJs(A1, R"(
window.received_message = "nothing received";
var received = false;
window.addEventListener('message', function(event) {
received_message = event.data;
});
document.querySelector('iframe').remove();
)"));
delete_B2.WaitUntilDeleted();
EXPECT_EQ("nothing received", EvalJs(A1, "received_message"));
}
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
UnloadPostMessageToParentSameProcess) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(a)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
RenderFrameHostImpl* A1 = web_contents()->GetPrimaryMainFrame();
RenderFrameHostImpl* A2 = A1->child_at(0)->current_frame_host();
RenderFrameDeletedObserver delete_A1(A2);
EXPECT_TRUE(ExecJs(A2, R"(
window.addEventListener("unload", function() {
window.parent.postMessage("A2 message", "*");
});
)"));
EXPECT_TRUE(ExecJs(A1, R"(
window.received_message = "nothing received";
var received = false;
window.addEventListener('message', function(event) {
received_message = event.data;
});
document.querySelector('iframe').remove();
)"));
delete_A1.WaitUntilDeleted();
EXPECT_EQ("A2 message", EvalJs(A1, "received_message"));
}
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
SameProcessNavigationResilientToDetachDropped) {
DisableBackForwardCacheForTesting(shell()->web_contents(),
BackForwardCache::TEST_REQUIRES_NO_CACHING);
GURL A1_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
GURL A3_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), A1_url));
RenderFrameHostImpl* A1 = web_contents()->GetPrimaryMainFrame();
RenderFrameHostImpl* B1 = A1->child_at(0)->current_frame_host();
B1->DoNotDeleteForTesting();
RenderFrameDeletedObserver delete_B1(B1);
shell()->LoadURL(A3_url);
delete_B1.WaitUntilDeleted();
}
#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
#define MAYBE_NestedSubframeWithUnloadHandler \
DISABLED_NestedSubframeWithUnloadHandler
#else
#define MAYBE_NestedSubframeWithUnloadHandler NestedSubframeWithUnloadHandler
#endif
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
MAYBE_NestedSubframeWithUnloadHandler) {
GURL main_url = embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b(b,c))");
GURL iframe_new_url = embedded_test_server()->GetURL("b.com", "/title1.html");
EXPECT_TRUE(NavigateToURL(shell(), main_url));
RenderFrameHostImpl* A1 = web_contents()->GetPrimaryMainFrame();
RenderFrameHostImpl* B2 = A1->child_at(0)->current_frame_host();
RenderFrameHostImpl* B3 = B2->child_at(0)->current_frame_host();
RenderFrameHostImpl* C4 = B2->child_at(1)->current_frame_host();
RenderFrameDeletedObserver delete_B2(B2);
RenderFrameDeletedObserver delete_B3(B3);
RenderFrameDeletedObserver delete_C4(C4);
UnloadPrint(B2, "B2");
UnloadPrint(B3, "B3");
UnloadPrint(C4, "C4");
ExecuteScriptAsync(B2, JsReplace("location.href = $1", iframe_new_url));
DOMMessageQueue dom_message_queue(
WebContents::FromRenderFrameHost(web_contents()->GetPrimaryMainFrame()));
if (ShouldCreateNewHostForSameSiteSubframe())
delete_B2.WaitUntilDeleted();
delete_B3.WaitUntilDeleted();
delete_C4.WaitUntilDeleted();
std::string message;
std::vector<std::string> messages;
for (int i = 0; i < 3; ++i) {
EXPECT_TRUE(dom_message_queue.WaitForMessage(&message));
base::TrimString(message, "\"", &message);
messages.push_back(message);
}
EXPECT_FALSE(dom_message_queue.PopMessage(&message));
EXPECT_THAT(messages, WhenSorted(ElementsAre("B2", "B3", "C4")));
}
class SitePerProcessSSLBrowserTest : public SitePerProcessBrowserTest {
protected:
SitePerProcessSSLBrowserTest() = default;
net::EmbeddedTestServer* https_server() { return &https_server_; }
private:
void SetUpOnMainThread() override {
SitePerProcessBrowserTest::SetUpOnMainThread();
mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
https_server()->AddDefaultHandlers(GetTestDataFilePath());
ASSERT_TRUE(https_server()->Start());
}
void SetUpCommandLine(base::CommandLine* command_line) override {
SitePerProcessBrowserTest::SetUpCommandLine(command_line);
mock_cert_verifier_.SetUpCommandLine(command_line);
}
void SetUpInProcessBrowserTestFixture() override {
SitePerProcessBrowserTest::SetUpInProcessBrowserTestFixture();
mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
}
void TearDownInProcessBrowserTestFixture() override {
SitePerProcessBrowserTest::TearDownInProcessBrowserTestFixture();
mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
}
content::ContentMockCertVerifier mock_cert_verifier_;
net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS};
};
IN_PROC_BROWSER_TEST_P(SitePerProcessSSLBrowserTest,
UnloadHandlersArePowerful) {
DisableBackForwardCacheForTesting(
web_contents(), content::BackForwardCache::TEST_USES_UNLOAD_EVENT);
GURL url =
https_server()->GetURL("a.com", "/cross_site_iframe_factory.html?a(b)");
EXPECT_TRUE(NavigateToURL(shell(), url));
RenderFrameHostImpl* A1 = web_contents()->GetPrimaryMainFrame();
RenderFrameHostImpl* B2 = A1->child_at(0)->current_frame_host();
A1->DisableUnloadTimerForTesting();
B2->SetSubframeUnloadTimeoutForTesting(base::Seconds(30));
ASSERT_TRUE(ExecJs(B2, R"(
window.addEventListener("unload", function() {
// Waiting for 100ms, to give more time for browser-side things to go bad
// and delete RenderFrameHostImpl prematurely.
var start = (new Date()).getTime();
do {
curr = (new Date()).getTime();
} while (start + 100 > curr);
// Test that various RFHI-dependent things work fine in an unload handler.
stateObj = { "history_test_key": "history_test_value" }
history.replaceState(stateObj, 'title', window.location.href);
console.log('console.log() sent');
// As a sanity check, test that RFHI-independent things also work fine.
localStorage.localstorage_test_key = 'localstorage_test_value';
document.cookie = 'cookie_test_key=' +
'cookie_test_value; SameSite=none; Secure';
});
)"));
{
WebContentsConsoleObserver console_observer(web_contents());
console_observer.SetPattern("console.log() sent");
RenderFrameDeletedObserver B2_deleted(B2);
GURL away_url(https_server()->GetURL("a.com", "/title1.html"));
ASSERT_TRUE(ExecJs(A1, JsReplace("location = $1", away_url)));
B2_deleted.WaitUntilDeleted();
ASSERT_TRUE(console_observer.Wait());
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(away_url, web_contents()->GetLastCommittedURL());
}
web_contents()->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(url, web_contents()->GetLastCommittedURL());
EXPECT_EQ(
2u, CollectAllRenderFrameHosts(web_contents()->GetPrimaryPage()).size());
RenderFrameHostImpl* A4 = web_contents()->GetPrimaryMainFrame();
RenderFrameHostImpl* B5 = A4->child_at(0)->current_frame_host();
EXPECT_EQ("localstorage_test_value",
EvalJs(B5, "localStorage.localstorage_test_key"));
EXPECT_EQ("cookie_test_key=cookie_test_value", EvalJs(B5, "document.cookie"));
if (!AreAllSitesIsolatedForTesting()) {
EXPECT_EQ("history_test_value",
EvalJs(B5, "history.state.history_test_key"));
}
}
IN_PROC_BROWSER_TEST_P(SitePerProcessSSLBrowserTest,
UnloadHandlersArePowerfulGrandChild) {
DisableBackForwardCacheForTesting(
web_contents(), content::BackForwardCache::TEST_USES_UNLOAD_EVENT);
GURL url = https_server()->GetURL("a.com",
"/cross_site_iframe_factory.html?a(b(c))");
EXPECT_TRUE(NavigateToURL(shell(), url));
RenderFrameHostImpl* A1 = web_contents()->GetPrimaryMainFrame();
RenderFrameHostImpl* B2 = A1->child_at(0)->current_frame_host();
RenderFrameHostImpl* C3 = B2->child_at(0)->current_frame_host();
A1->DisableUnloadTimerForTesting();
B2->SetSubframeUnloadTimeoutForTesting(base::Seconds(30));
C3->SetSubframeUnloadTimeoutForTesting(base::Seconds(30));
ASSERT_TRUE(ExecJs(C3, R"(
window.addEventListener("unload", function() {
// Waiting for 100ms, to give more time for browser-side things to go bad
// and delete RenderFrameHostImpl prematurely.
var start = (new Date()).getTime();
do {
curr = (new Date()).getTime();
} while (start + 100 > curr);
// Test that various RFHI-dependent things work fine in an unload handler.
stateObj = { "history_test_key": "history_test_value" }
history.replaceState(stateObj, 'title', window.location.href);
console.log('console.log() sent');
// As a sanity check, test that RFHI-independent things also work fine.
localStorage.localstorage_test_key = 'localstorage_test_value';
document.cookie = 'cookie_test_key=' +
'cookie_test_value; SameSite=none; Secure';
});
)"));
{
WebContentsConsoleObserver console_observer(web_contents());
console_observer.SetPattern("console.log() sent");
RenderFrameDeletedObserver B2_deleted(B2);
RenderFrameDeletedObserver C3_deleted(C3);
GURL away_url(https_server()->GetURL("a.com", "/title1.html"));
ASSERT_TRUE(ExecJs(A1, JsReplace("location = $1", away_url)));
B2_deleted.WaitUntilDeleted();
C3_deleted.WaitUntilDeleted();
ASSERT_TRUE(console_observer.Wait());
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(away_url, web_contents()->GetLastCommittedURL());
}
web_contents()->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(url, web_contents()->GetLastCommittedURL());
EXPECT_EQ(
3u, CollectAllRenderFrameHosts(web_contents()->GetPrimaryPage()).size());
RenderFrameHostImpl* A5 = web_contents()->GetPrimaryMainFrame();
RenderFrameHostImpl* B6 = A5->child_at(0)->current_frame_host();
RenderFrameHostImpl* C7 = B6->child_at(0)->current_frame_host();
EXPECT_EQ("localstorage_test_value",
EvalJs(C7, "localStorage.localstorage_test_key"));
EXPECT_EQ("cookie_test_key=cookie_test_value", EvalJs(C7, "document.cookie"));
if (!AreAllSitesIsolatedForTesting()) {
EXPECT_EQ("history_test_value",
EvalJs(C7, "history.state.history_test_key"));
}
}
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
UnloadInInitialEmptyDocument) {
GURL url = embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)");
EXPECT_TRUE(NavigateToURL(shell(), url));
RenderFrameHostImpl* a1 = web_contents()->GetPrimaryMainFrame();
RenderFrameHostImpl* b2 = a1->child_at(0)->current_frame_host();
ASSERT_EQ(0u, b2->child_count());
EXPECT_TRUE(ExecJs(b2, R"(
let iframe = document.createElement("iframe");
document.body.appendChild(iframe);
iframe.contentWindow.onunload = () => {
window.domAutomationController.send("B3 unloaded");
}
)"));
ASSERT_EQ(1u, b2->child_count());
RenderFrameHostImpl* b3 = b2->child_at(0)->current_frame_host();
auto has_unload_handler = [](RenderFrameHostImpl* rfh) {
return rfh->GetSuddenTerminationDisablerState(
blink::mojom::SuddenTerminationDisablerType::kUnloadHandler);
};
EXPECT_FALSE(has_unload_handler(a1));
EXPECT_FALSE(has_unload_handler(b2));
EXPECT_TRUE(has_unload_handler(b3));
DOMMessageQueue dom_message_queue(
WebContents::FromRenderFrameHost(web_contents()->GetPrimaryMainFrame()));
ExecuteScriptAsync(a1, "document.querySelector('iframe').remove();");
std::string message;
EXPECT_TRUE(dom_message_queue.WaitForMessage(&message));
EXPECT_EQ("\"B3 unloaded\"", message);
}
INSTANTIATE_TEST_SUITE_P(All,
SitePerProcessSSLBrowserTest,
testing::ValuesIn(RenderDocumentFeatureLevelValues()));
}