blob: beb0055eef1cafa452e70ace44c2b8437f06fafa [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string_view>
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/weak_document_ptr.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/shell/common/render_frame_test_helper.mojom.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/dns/mock_host_resolver.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/chrome_debug_urls.h"
#include "third_party/blink/public/common/tokens/tokens.h"
#include "url/gurl.h"
namespace content {
namespace {
// The general structure of all tests is to navigate A -> A -> B. A -> A will
// reuse the same `RenderFrameHost` (without RenderDocument) while A -> B will
// swap to a new `RenderFrameHost` (with --site-per-process).
class DocumentTokenBrowserTest : public ContentBrowserTest {
protected:
void SetUpOnMainThread() override {
ContentBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(embedded_test_server()->Start());
}
WebContentsImpl* web_contents() {
return static_cast<WebContentsImpl*>(shell()->web_contents());
}
blink::DocumentToken GetBrowserSideToken(ToRenderFrameHost adapter) {
return static_cast<RenderFrameHostImpl*>(adapter.render_frame_host())
->GetDocumentToken();
}
// Verifies that the browser-side `DocumentToken` and the renderer-side
// `DocumentToken` have matching values.
[[nodiscard]] ::testing::AssertionResult VerifyMatchingTokens(
ToRenderFrameHost adapter) {
blink::DocumentToken token_from_browser = GetBrowserSideToken(adapter);
mojo::Remote<mojom::RenderFrameTestHelper> remote;
adapter.render_frame_host()->GetRemoteInterfaces()->GetInterface(
remote.BindNewPipeAndPassReceiver());
blink::DocumentToken token_from_renderer;
base::RunLoop run_loop;
remote->GetDocumentToken(
base::BindLambdaForTesting([&](const blink::DocumentToken& token) {
token_from_renderer = token;
run_loop.Quit();
}));
run_loop.Run();
if (token_from_browser == token_from_renderer) {
return ::testing::AssertionSuccess();
}
return ::testing::AssertionFailure()
<< "browser token was " << token_from_browser
<< " but renderer token was " << token_from_renderer;
}
// Whether or not `NavigateAndGetNewToken()` should wait for the response and
// validate document token state immediately afterwards. Most tests should
// expect and wait for a response; however, tests that are exercising
// `CommitFailedNavigation()` will probably want to specify `kNo`.
enum class ExpectedResponse {
kYes,
kNo,
};
// Navigate `adapter.render_frame_host()` to `target_url`. Verifies that the
// browser and renderer state are in sync, and that the document token is not
// updated until the navigation actually commits.
//
// Note: this helper makes IPCs to the `RenderFrame`; for the first navigation
// in a WebContents, it is typically more appropriate to use `NavigateToURL()`
// or another similar helper instead.
blink::DocumentToken NavigateAndGetNewToken(
ToRenderFrameHost adapter,
const GURL& target_url,
ExpectedResponse expect_response = ExpectedResponse::kYes) {
SCOPED_TRACE(target_url.spec());
// Capture the FrameTreeNode now; when a navigation commits, the current
// RenderFrameHost may change.
RenderFrameHostImpl* const old_render_frame_host =
static_cast<RenderFrameHostImpl*>(adapter.render_frame_host());
FrameTreeNode* const frame_tree_node =
old_render_frame_host->frame_tree_node();
const int old_process_id =
old_render_frame_host->GetProcess()->GetDeprecatedID();
const blink::LocalFrameToken old_frame_token =
old_render_frame_host->GetFrameToken();
const blink::DocumentToken old_document_token =
GetBrowserSideToken(old_render_frame_host);
const WeakDocumentPtr old_weak_document_ptr =
old_render_frame_host->GetWeakDocumentPtr();
EXPECT_EQ(old_render_frame_host, RenderFrameHostImpl::FromDocumentToken(
old_process_id, old_document_token));
// Start a new navigation in the main frame. The navigation is still
// ongoing, so `DocumentToken` should not be updated yet.
TestNavigationManager nav_manager(
WebContents::FromRenderFrameHost(old_render_frame_host), target_url);
EXPECT_TRUE(BeginNavigateToURLFromRenderer(adapter, target_url));
EXPECT_TRUE(VerifyMatchingTokens(old_render_frame_host));
EXPECT_EQ(old_document_token, GetBrowserSideToken(old_render_frame_host));
EXPECT_EQ(old_render_frame_host, RenderFrameHostImpl::FromDocumentToken(
old_process_id, old_document_token));
// Just before the request is actually issued, the navigation is still
// ongoing, so `DocumentToken` should not be updated yet.
EXPECT_TRUE(nav_manager.WaitForRequestStart());
EXPECT_TRUE(VerifyMatchingTokens(old_render_frame_host));
EXPECT_EQ(old_document_token, GetBrowserSideToken(old_render_frame_host));
EXPECT_EQ(old_render_frame_host, RenderFrameHostImpl::FromDocumentToken(
old_process_id, old_document_token));
if (ExpectedResponse::kYes == expect_response) {
// Just before reading the response, the navigation is still ongoing, so
// `DocumentToken` should not be updated yet.
EXPECT_TRUE(nav_manager.WaitForResponse());
EXPECT_TRUE(VerifyMatchingTokens(old_render_frame_host));
EXPECT_EQ(old_document_token, GetBrowserSideToken(old_render_frame_host));
EXPECT_EQ(old_render_frame_host, RenderFrameHostImpl::FromDocumentToken(
old_process_id, old_document_token));
}
// Once a cross-document navigation completes, the document token should be
// updated though.
EXPECT_TRUE(nav_manager.WaitForNavigationFinished());
// The RenderFrameHost may have changed; use the FrameTreeNode captured
// above instead.
RenderFrameHostImpl* const new_render_frame_host =
frame_tree_node->current_frame_host();
EXPECT_EQ(target_url, new_render_frame_host->GetLastCommittedURL());
EXPECT_TRUE(VerifyMatchingTokens(new_render_frame_host));
const blink::LocalFrameToken new_frame_token =
new_render_frame_host->GetFrameToken();
const blink::DocumentToken new_document_token =
GetBrowserSideToken(new_render_frame_host);
EXPECT_NE(new_document_token, old_document_token);
if (new_frame_token == old_frame_token) {
// If the RenderFrameHost is reused, it should no longer be possible to
// use the old token to look up the RenderFrameHost.
EXPECT_EQ(nullptr, RenderFrameHostImpl::FromDocumentToken(
old_process_id, old_document_token));
} else if (old_weak_document_ptr.AsRenderFrameHostIfValid()) {
// Otherwise, if the old RenderFrameHost is still around, it should still
// map to the same RenderFrameHost.
EXPECT_EQ(old_render_frame_host, RenderFrameHostImpl::FromDocumentToken(
old_process_id, old_document_token));
}
EXPECT_EQ(new_render_frame_host,
RenderFrameHostImpl::FromDocumentToken(
new_render_frame_host->GetProcess()->GetDeprecatedID(),
new_document_token));
return new_document_token;
}
};
IN_PROC_BROWSER_TEST_F(DocumentTokenBrowserTest, MainFrameBasic) {
std::vector<blink::DocumentToken> seen_tokens;
ASSERT_TRUE(NavigateToURL(
web_contents(), embedded_test_server()->GetURL("a.com", "/title1.html")));
EXPECT_TRUE(VerifyMatchingTokens(web_contents()));
seen_tokens.push_back(GetBrowserSideToken(web_contents()));
seen_tokens.push_back(NavigateAndGetNewToken(
web_contents(), embedded_test_server()->GetURL("a.com", "/title1.html")));
seen_tokens.push_back(NavigateAndGetNewToken(
web_contents(), embedded_test_server()->GetURL("b.com", "/title1.html")));
std::set unique_tokens(seen_tokens.begin(), seen_tokens.end());
EXPECT_EQ(unique_tokens.size(), seen_tokens.size());
}
IN_PROC_BROWSER_TEST_F(DocumentTokenBrowserTest, SubFrameBasic) {
std::vector<blink::DocumentToken> seen_tokens;
ASSERT_TRUE(NavigateToURL(
web_contents(), embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(a)")));
EXPECT_TRUE(VerifyMatchingTokens(web_contents()));
EXPECT_TRUE(VerifyMatchingTokens(ChildFrameAt(web_contents(), 0)));
seen_tokens.push_back(GetBrowserSideToken(web_contents()));
seen_tokens.push_back(GetBrowserSideToken(ChildFrameAt(web_contents(), 0)));
seen_tokens.push_back(NavigateAndGetNewToken(
ChildFrameAt(web_contents(), 0),
embedded_test_server()->GetURL("a.com", "/title1.html")));
// Main document did not navigate so the token should be the same.
EXPECT_EQ(seen_tokens[0], GetBrowserSideToken(web_contents()));
seen_tokens.push_back(NavigateAndGetNewToken(
ChildFrameAt(web_contents(), 0),
embedded_test_server()->GetURL("b.com", "/title1.html")));
// Main document did not navigate so the token should be the same.
EXPECT_EQ(seen_tokens[0], GetBrowserSideToken(web_contents()));
std::set unique_tokens(seen_tokens.begin(), seen_tokens.end());
EXPECT_EQ(unique_tokens.size(), seen_tokens.size());
}
IN_PROC_BROWSER_TEST_F(DocumentTokenBrowserTest, NewWindowBasic) {
std::vector<blink::DocumentToken> seen_tokens;
ASSERT_TRUE(NavigateToURL(
web_contents(), embedded_test_server()->GetURL("a.com", "/title1.html")));
EXPECT_EQ(1u, Shell::windows().size());
seen_tokens.push_back(GetBrowserSideToken(web_contents()));
WebContents* new_contents = nullptr;
{
// This block is largely derived from `NavigateAndGetNewToken()`. This test
// cannot easily reuse that helper because:
//
// - it is important to specify an actual target URL other than about:blank
// for `window.open()`. Specifying no target URL and then later navigating
// the window has subtly different behavior (e.g. the
// `NewWindowSyncCommit` test below).
// - the helper expects the `WebContents` to already exist in order to
// install
// `TestNavigationManager`. However, in this test, a new `WebContents` is
// created in the process of running the test.
ExecuteScriptAsync(web_contents(), JsReplace("window.open($1)",
embedded_test_server()->GetURL(
"a.com", "/title1.html")));
ShellAddedObserver wait_for_new_shell;
new_contents = wait_for_new_shell.GetShell()->web_contents();
DCHECK_EQ(2u, Shell::windows().size());
DCHECK_EQ(new_contents, Shell::windows()[1]->web_contents());
DCHECK_NE(new_contents, web_contents());
seen_tokens.push_back(GetBrowserSideToken(new_contents));
TestNavigationManager nav_manager(
new_contents, embedded_test_server()->GetURL("a.com", "/title1.html"));
// Capture the FrameTreeNode now; when a navigation commits, the current
// RenderFrameHost may change.
RenderFrameHostImpl* const old_render_frame_host =
static_cast<RenderFrameHostImpl*>(new_contents->GetPrimaryMainFrame());
FrameTreeNode* const frame_tree_node =
old_render_frame_host->frame_tree_node();
const int old_process_id =
old_render_frame_host->GetProcess()->GetDeprecatedID();
const blink::LocalFrameToken old_frame_token =
old_render_frame_host->GetFrameToken();
const blink::DocumentToken old_document_token =
GetBrowserSideToken(new_contents);
const WeakDocumentPtr old_weak_document_ptr =
old_render_frame_host->GetWeakDocumentPtr();
EXPECT_TRUE(VerifyMatchingTokens(new_contents));
EXPECT_EQ(old_document_token, GetBrowserSideToken(new_contents));
EXPECT_EQ(old_render_frame_host, RenderFrameHostImpl::FromDocumentToken(
old_process_id, old_document_token));
// Even after creating a new window, the original `WebContents` should still
// have the same `DocumentToken`.
EXPECT_EQ(seen_tokens[0], GetBrowserSideToken(web_contents()));
// Just before the request is actually issued, the navigation is still
// ongoing, so `DocumentToken` should not be updated yet.
EXPECT_TRUE(nav_manager.WaitForRequestStart());
EXPECT_TRUE(VerifyMatchingTokens(new_contents));
EXPECT_EQ(old_document_token, GetBrowserSideToken(new_contents));
EXPECT_EQ(old_render_frame_host, RenderFrameHostImpl::FromDocumentToken(
old_process_id, old_document_token));
// The original `WebContents` should still have the same `DocumentToken`.
EXPECT_EQ(seen_tokens[0], GetBrowserSideToken(web_contents()));
// Just before reading the response, the navigation is still ongoing, so
// `DocumentToken` should not be updated yet.
EXPECT_TRUE(nav_manager.WaitForResponse());
EXPECT_TRUE(VerifyMatchingTokens(new_contents));
EXPECT_EQ(old_document_token, GetBrowserSideToken(new_contents));
EXPECT_EQ(old_render_frame_host, RenderFrameHostImpl::FromDocumentToken(
old_process_id, old_document_token));
// The original `WebContents` should still have the same `DocumentToken`.
EXPECT_EQ(seen_tokens[0], GetBrowserSideToken(web_contents()));
// Once a cross-document navigation completes, the document token should be
// updated though.
ASSERT_TRUE(nav_manager.WaitForNavigationFinished());
// The RenderFrameHost may have changed; use the FrameTreeNode captured
// above instead.
RenderFrameHostImpl* const new_render_frame_host =
frame_tree_node->current_frame_host();
EXPECT_EQ(embedded_test_server()->GetURL("a.com", "/title1.html"),
new_render_frame_host->GetLastCommittedURL());
EXPECT_TRUE(VerifyMatchingTokens(new_render_frame_host));
const blink::LocalFrameToken new_frame_token =
new_render_frame_host->GetFrameToken();
const blink::DocumentToken new_document_token =
GetBrowserSideToken(new_render_frame_host);
EXPECT_NE(new_document_token, old_document_token);
if (new_frame_token == old_frame_token) {
// If the RenderFrameHost is reused, it should no longer be possible to
// use the old token to look up the RenderFrameHost.
EXPECT_EQ(nullptr, RenderFrameHostImpl::FromDocumentToken(
old_process_id, old_document_token));
} else if (old_weak_document_ptr.AsRenderFrameHostIfValid()) {
// Otherwise, if the old RenderFrameHost is still around, it should still
// map to the same RenderFrameHost.
EXPECT_EQ(old_render_frame_host, RenderFrameHostImpl::FromDocumentToken(
old_process_id, old_document_token));
}
EXPECT_EQ(new_render_frame_host,
RenderFrameHostImpl::FromDocumentToken(
new_render_frame_host->GetProcess()->GetDeprecatedID(),
new_document_token));
seen_tokens.push_back(new_document_token);
// The original `WebContents` should still have the same `DocumentToken`.
EXPECT_EQ(seen_tokens[0], GetBrowserSideToken(web_contents()));
}
seen_tokens.push_back(NavigateAndGetNewToken(
new_contents, embedded_test_server()->GetURL("a.com", "/title1.html")));
// The original `WebContents` should still have the same `DocumentToken`.
EXPECT_EQ(seen_tokens[0], GetBrowserSideToken(web_contents()));
seen_tokens.push_back(NavigateAndGetNewToken(
new_contents, embedded_test_server()->GetURL("b.com", "/title1.html")));
// The original `WebContents` should still have the same `DocumentToken`.
EXPECT_EQ(seen_tokens[0], GetBrowserSideToken(web_contents()));
std::set unique_tokens(seen_tokens.begin(), seen_tokens.end());
EXPECT_EQ(unique_tokens.size(), seen_tokens.size());
}
IN_PROC_BROWSER_TEST_F(DocumentTokenBrowserTest, SubFrameSyncCommit) {
std::vector<blink::DocumentToken> seen_tokens;
// This is a basic test that the synchronous commit of about:blank reuses the
// same DocumentToken. See https://crbug.com/778318 for more details.
ASSERT_TRUE(NavigateToURL(
web_contents(),
embedded_test_server()->GetURL("a.com", "/page_with_blank_iframe.html")));
EXPECT_TRUE(VerifyMatchingTokens(web_contents()));
EXPECT_TRUE(VerifyMatchingTokens(ChildFrameAt(web_contents(), 0)));
seen_tokens.push_back(GetBrowserSideToken(web_contents()));
seen_tokens.push_back(GetBrowserSideToken(ChildFrameAt(web_contents(), 0)));
seen_tokens.push_back(NavigateAndGetNewToken(
ChildFrameAt(web_contents(), 0),
embedded_test_server()->GetURL("a.com", "/title1.html")));
// Main document did not navigate so the token should be the same.
EXPECT_EQ(seen_tokens[0], GetBrowserSideToken(web_contents()));
seen_tokens.push_back(NavigateAndGetNewToken(
ChildFrameAt(web_contents(), 0),
embedded_test_server()->GetURL("b.com", "/title1.html")));
// Main document did not navigate so the token should be the same.
EXPECT_EQ(seen_tokens[0], GetBrowserSideToken(web_contents()));
std::set unique_tokens(seen_tokens.begin(), seen_tokens.end());
EXPECT_EQ(unique_tokens.size(), seen_tokens.size());
}
IN_PROC_BROWSER_TEST_F(DocumentTokenBrowserTest, NewWindowSyncCommit) {
std::vector<blink::DocumentToken> seen_tokens;
ASSERT_TRUE(NavigateToURL(web_contents(), GURL("about:blank")));
EXPECT_EQ(1u, Shell::windows().size());
EXPECT_TRUE(VerifyMatchingTokens(web_contents()));
seen_tokens.push_back(GetBrowserSideToken(web_contents()));
// This is a basic test that the synchronous commit of about:blank reuses the
// same DocumentToken. See https://crbug.com/778318 for more details.
ASSERT_TRUE(ExecJs(web_contents(), "window.open()"));
ASSERT_EQ(2u, Shell::windows().size());
WebContents* new_contents = Shell::windows()[1]->web_contents();
DCHECK_NE(new_contents, web_contents());
EXPECT_TRUE(VerifyMatchingTokens(new_contents));
// The original `WebContents` should still have the same `DocumentToken`.
EXPECT_EQ(seen_tokens[0], GetBrowserSideToken(web_contents()));
seen_tokens.push_back(NavigateAndGetNewToken(
new_contents, embedded_test_server()->GetURL("a.com", "/title1.html")));
// The original `WebContents` should still have the same `DocumentToken`.
EXPECT_EQ(seen_tokens[0], GetBrowserSideToken(web_contents()));
seen_tokens.push_back(NavigateAndGetNewToken(
new_contents, embedded_test_server()->GetURL("a.com", "/title1.html")));
// The original `WebContents` should still have the same `DocumentToken`.
EXPECT_EQ(seen_tokens[0], GetBrowserSideToken(web_contents()));
seen_tokens.push_back(NavigateAndGetNewToken(
new_contents, embedded_test_server()->GetURL("b.com", "/title1.html")));
// The original `WebContents` should still have the same `DocumentToken`.
EXPECT_EQ(seen_tokens[0], GetBrowserSideToken(web_contents()));
std::set unique_tokens(seen_tokens.begin(), seen_tokens.end());
EXPECT_EQ(unique_tokens.size(), seen_tokens.size());
}
IN_PROC_BROWSER_TEST_F(DocumentTokenBrowserTest, JavascriptURL) {
ASSERT_TRUE(NavigateToURL(
web_contents(), embedded_test_server()->GetURL("a.com", "/title1.html")));
EXPECT_TRUE(VerifyMatchingTokens(web_contents()));
const blink::DocumentToken token = GetBrowserSideToken(web_contents());
// A javascript: navigation that replaces the document should not change the
// DocumentToken. This does not use the normal Navigate*() helpers since it
// does not commit a normal cross-document navigation.
ASSERT_TRUE(ExecJs(web_contents(),
JsReplace("location = $1", "javascript:'Hello world!'")));
EXPECT_EQ("Hello world!", EvalJs(web_contents(), "document.body.innerText"));
EXPECT_TRUE(VerifyMatchingTokens(web_contents()));
EXPECT_EQ(token, GetBrowserSideToken(web_contents()));
}
IN_PROC_BROWSER_TEST_F(DocumentTokenBrowserTest, FailedNavigation) {
std::vector<blink::DocumentToken> seen_tokens;
ASSERT_TRUE(NavigateToURL(
web_contents(), embedded_test_server()->GetURL("a.com", "/title1.html")));
EXPECT_TRUE(VerifyMatchingTokens(web_contents()));
seen_tokens.push_back(GetBrowserSideToken(web_contents()));
seen_tokens.push_back(NavigateAndGetNewToken(
web_contents(), embedded_test_server()->GetURL("a.com", "/close-socket"),
ExpectedResponse::kNo));
seen_tokens.push_back(NavigateAndGetNewToken(
web_contents(), embedded_test_server()->GetURL("a.com", "/close-socket"),
ExpectedResponse::kNo));
seen_tokens.push_back(NavigateAndGetNewToken(
web_contents(), embedded_test_server()->GetURL("b.com", "/close-socket"),
ExpectedResponse::kNo));
// Test that a regular successful navigation still updates the document token.
seen_tokens.push_back(NavigateAndGetNewToken(
web_contents(), embedded_test_server()->GetURL("a.com", "/title1.html")));
std::set unique_tokens(seen_tokens.begin(), seen_tokens.end());
EXPECT_EQ(unique_tokens.size(), seen_tokens.size());
}
IN_PROC_BROWSER_TEST_F(DocumentTokenBrowserTest, CrashThenReload) {
ASSERT_TRUE(NavigateToURL(
web_contents(), embedded_test_server()->GetURL("a.com", "/title1.html")));
EXPECT_TRUE(VerifyMatchingTokens(web_contents()));
const int old_process_id =
web_contents()->GetPrimaryMainFrame()->GetProcess()->GetDeprecatedID();
const blink::DocumentToken old_document_token =
GetBrowserSideToken(web_contents());
// Cause the renderer to crash.
RenderProcessHostWatcher crash_observer(
web_contents(), RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
EXPECT_FALSE(NavigateToURL(shell(), GURL(blink::kChromeUICrashURL)));
// Wait for browser to notice the renderer crash.
crash_observer.Wait();
// After a crash, the DocumentToken should still be the same even though the
// renderer process is gone..
EXPECT_EQ(old_document_token, GetBrowserSideToken(web_contents()));
// But when a live RenderFrame is needed again, RenderDocument should force a
// new RenderFrameHost, and thus, a new DocumentToken. The remainder of this
// test does not use `NavigateAndGetNewToken()`, which tries to use a
// renderer-initiated navigation (which is not possible when the renderer is
// not live).
TestNavigationManager nav_manager(
web_contents(), embedded_test_server()->GetURL("a.com", "/title1.html"));
shell()->LoadURL(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(VerifyMatchingTokens(web_contents()));
const int new_process_id =
web_contents()->GetPrimaryMainFrame()->GetProcess()->GetDeprecatedID();
const blink::DocumentToken token_after_navigation_started =
GetBrowserSideToken(web_contents());
EXPECT_NE(token_after_navigation_started, old_document_token);
const WeakDocumentPtr document_weak_ptr =
web_contents()->GetPrimaryMainFrame()->GetWeakDocumentPtr();
EXPECT_EQ(web_contents()->GetPrimaryMainFrame(),
RenderFrameHostImpl::FromDocumentToken(
new_process_id, token_after_navigation_started));
// The old RenderFrameHost should be gone at this point, so a document token
// lookup should fail.
EXPECT_EQ(nullptr, RenderFrameHostImpl::FromDocumentToken(
old_process_id, old_document_token));
// After the navigation finishes, the RenderFrameHost will still use the same
// DocumentToken, since no new DocumentAssociatedData was created. The latter
// is indirectly tested by checking if the WeakDocumentPtr is still valid
// after the navigation commits.
ASSERT_TRUE(nav_manager.WaitForNavigationFinished());
EXPECT_TRUE(VerifyMatchingTokens(web_contents()));
const blink::DocumentToken token_after_navigation_finished =
GetBrowserSideToken(web_contents());
EXPECT_NE(token_after_navigation_finished, old_document_token);
EXPECT_EQ(token_after_navigation_finished, token_after_navigation_started);
EXPECT_NE(document_weak_ptr.AsRenderFrameHostIfValid(), nullptr);
EXPECT_EQ(web_contents()->GetPrimaryMainFrame(),
RenderFrameHostImpl::FromDocumentToken(
new_process_id, token_after_navigation_started));
}
IN_PROC_BROWSER_TEST_F(DocumentTokenBrowserTest,
CrashThenImmediateReinitialize) {
ASSERT_TRUE(NavigateToURL(
web_contents(), embedded_test_server()->GetURL("a.com", "/title1.html")));
EXPECT_TRUE(VerifyMatchingTokens(web_contents()));
RenderFrameHostImpl* main_frame = web_contents()->GetPrimaryMainFrame();
const blink::LocalFrameToken frame_token = main_frame->GetFrameToken();
const blink::DocumentToken old_document_token =
GetBrowserSideToken(main_frame);
const WeakDocumentPtr document_weak_ptr = main_frame->GetWeakDocumentPtr();
// Cause the renderer to crash.
RenderProcessHostWatcher crash_observer(
web_contents(), RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
EXPECT_FALSE(NavigateToURL(shell(), GURL(blink::kChromeUICrashURL)));
// Wait for browser to notice the renderer crash.
crash_observer.Wait();
// If the main render frame is re-initialized, it also gets a new
// DocumentAssociatedData. Validate that the new DocumentAssociatedData is
// created before the renderer is re-created; a typical failure in this path
// will manifest as a mismatch between the browser and renderer-side document
// tokens.
main_frame->frame_tree_node()
->render_manager()
->InitializeMainRenderFrameForImmediateUse();
// The RenderFrameHost should be reused.
ASSERT_EQ(frame_token,
web_contents()->GetPrimaryMainFrame()->GetFrameToken());
EXPECT_TRUE(VerifyMatchingTokens(web_contents()));
// The re-created RenderFrame should have a distinct document token.
const blink::DocumentToken new_document_token =
GetBrowserSideToken(web_contents());
EXPECT_NE(new_document_token, old_document_token);
// The previous DocumentWeakPtr should be invalidated since the
// DocumentAssociatedData was re-created.
EXPECT_FALSE(document_weak_ptr.AsRenderFrameHostIfValid());
// Even though the RenderFrameHost did not change, only a lookup using the new
// DocumentToken should succeed.
EXPECT_EQ(web_contents()->GetPrimaryMainFrame(),
RenderFrameHostImpl::FromDocumentToken(web_contents()
->GetPrimaryMainFrame()
->GetProcess()
->GetDeprecatedID(),
new_document_token));
EXPECT_EQ(nullptr,
RenderFrameHostImpl::FromDocumentToken(web_contents()
->GetPrimaryMainFrame()
->GetProcess()
->GetDeprecatedID(),
old_document_token));
}
// TODO(crbug.com/40238502): Add tests for bfcache navigations and
// prerender activations.
IN_PROC_BROWSER_TEST_F(DocumentTokenBrowserTest, MismatchedProcessID) {
RenderFrameHostImpl* main_frame = web_contents()->GetPrimaryMainFrame();
bool called = false;
mojo::ReportBadMessageCallback callback =
base::BindLambdaForTesting([&called](std::string_view reason) {
called = true;
EXPECT_EQ("process ID does not match requested DocumentToken", reason);
});
EXPECT_EQ(nullptr, RenderFrameHostImpl::FromDocumentToken(
main_frame->GetProcess()->GetDeprecatedID() + 1,
main_frame->GetDocumentToken(), &callback));
EXPECT_TRUE(called);
}
} // namespace
} // namespace content