blob: 04e3b990df117eeebe0e513c4f6a1fff96ed9502 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/test/scoped_feature_list.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/site_isolation_policy.h"
#include "content/public/common/content_switches.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.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "net/dns/mock_host_resolver.h"
namespace content {
class MessagePortCloseEventBrowserTest : public ContentBrowserTest {
public:
MessagePortCloseEventBrowserTest() {
InitBackForwardCacheFeature(&feature_list_for_back_forward_cache_,
/*enable_back_forward_cache=*/true);
}
~MessagePortCloseEventBrowserTest() override = default;
void SetUpOnMainThread() override {
// Setup the server to allow serving separate sites, so we can perform
// cross-process navigation.
host_resolver()->AddRule("*", "127.0.0.1");
}
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures,
"MessagePortCloseEvent");
}
RenderFrameHost* GetPrimaryMainFrame() {
return shell()->web_contents()->GetPrimaryMainFrame();
}
base::test::ScopedFeatureList feature_list_for_back_forward_cache_;
};
// Confirm the close event is fired when the page crashes.
IN_PROC_BROWSER_TEST_F(MessagePortCloseEventBrowserTest,
CloseEventHappensIfProcessCrashes) {
// If site isolation is turned off, A and B shares the same SiteInstance.
// So, when A crashes, B also crashes.
if (!SiteIsolationPolicy::UseDedicatedProcessesForAllSites()) {
GTEST_SKIP() << "test requires site isolation";
}
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
// 1) Navigate to A and open a popup.
ASSERT_TRUE(NavigateToURL(shell(), url_a));
WebContents* contents_a = shell()->web_contents();
RenderFrameHostImplWrapper rfh_a(GetPrimaryMainFrame());
Shell* popup = OpenPopup(rfh_a.get(), url_b, "");
WebContents* contents_b = popup->web_contents();
ASSERT_EQ(2u, rfh_a->GetSiteInstance()->GetRelatedActiveContentsCount());
// 2) Set up a MessageChannel between page A and page B. The message channel
// is created in page B and one of the ports is passed to page A.
ASSERT_TRUE(ExecJs(contents_a, R"(
window.onmessage = (e) => {
const port = e.ports[0];
port.start();
}
)"));
ASSERT_TRUE(ExecJs(contents_b, R"(
const {port1, port2} = new MessageChannel();
port1.start();
var closeEventPromise = new Promise(resolve => port1.onclose = resolve);
window.opener.postMessage('', '*', [port2]);
)"));
// 3) Crash the renderer A.
CrashTab(contents_a);
// 4) Confirm the close event is fired on the port in page B.
EXPECT_TRUE(ExecJs(contents_b, "closeEventPromise"));
}
// Confirm the close event is fired when the document stored in the BFCache
// is destroyed.
IN_PROC_BROWSER_TEST_F(MessagePortCloseEventBrowserTest,
CloseEventHappensIfPageEvictedFromBackForwardCache) {
ASSERT_TRUE(embedded_test_server()->Start());
// 1) Load a page that registers a service worker.
ASSERT_TRUE(NavigateToURL(shell(),
embedded_test_server()->GetURL(
"/service_worker/create_service_worker.html")));
ASSERT_EQ("DONE",
EvalJs(shell(), "register('message_port_close_event.js');"));
// 2) Load the page again so we are controlled.
ASSERT_TRUE(NavigateToURL(shell(),
embedded_test_server()->GetURL(
"/service_worker/create_service_worker.html")));
ASSERT_EQ(true, EvalJs(shell(), "!!navigator.serviceWorker.controller"));
// 3) Establish message port connection with the service worker.
ASSERT_TRUE(ExecJs(shell(), R"(
const {port1, port2} = new MessageChannel();
port1.start();
const ctrl = navigator.serviceWorker.controller;
ctrl.postMessage("init", [port2]);
)"));
RenderFrameHostImplWrapper rfh_1(GetPrimaryMainFrame());
WebContents* web_contents = shell()->web_contents();
// 4) Navigate to the empty page in the service worker's scope and confirm the
// previous page is in BackForwardCache.
ASSERT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("/service_worker/empty.html")));
ASSERT_TRUE(rfh_1->IsInBackForwardCache());
RenderFrameHostImplWrapper rfh_2(GetPrimaryMainFrame());
// 5) Ask the service worker to create the promise that resolves when
// it receives a close event from the BFCached page when that page gets
// evicted.
ASSERT_TRUE(ExecJs(rfh_2.get(), R"(
navigator.serviceWorker.controller.postMessage("wait for close event");
var closeEventPromise = new Promise(resolve => {
navigator.serviceWorker.addEventListener('message', (event) => {
resolve(event.data);
})});
)"));
// 6) Flush the cache and evict the previously BFCached page.
web_contents->GetController().GetBackForwardCache().Flush();
// 7) Confirm the previous page is evicted.
ASSERT_TRUE(rfh_1.WaitUntilRenderFrameDeleted());
// 8) Confirm the close event is fired on the port in the service worker.
EXPECT_EQ("close event is fired", EvalJs(rfh_2.get(), "closeEventPromise"));
}
} // namespace content