blob: bf2b7534ea18a3c3a23ba2e970370f3932fb2220 [file]
// Copyright 2026 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/android/additional_navigation_params.h"
#include "base/android/jni_android.h"
#include "base/functional/callback_helpers.h"
#include "base/run_loop.h"
#include "base/test/run_until.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/storage_partition_impl.h"
#include "content/public/browser/android/additional_navigation_params_android.h"
#include "content/public/browser/initiator_navigation_state.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/child_process_id.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/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "net/dns/mock_host_resolver.h"
#include "third_party/blink/public/common/tokens/tokens.h"
#include "url/gurl.h"
namespace content {
class AdditionalNavigationParamsBrowserTest : public ContentBrowserTest {
public:
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(embedded_test_server()->Start());
ContentBrowserTest::SetUpOnMainThread();
}
WebContents* web_contents() { return shell()->web_contents(); }
};
// AdditionalNavigationParams carries the initiator frame token across an
// arbitrary delay before it is consumed by NavigationController::LoadUrl. The
// initiator frame may be detached during that delay, so creating the params
// must keep the frame's PolicyContainerHost reachable for the lifetime of the
// returned keep-alive handle.
IN_PROC_BROWSER_TEST_F(AdditionalNavigationParamsBrowserTest,
InitiatorPolicyContainerHostKeptAliveAfterFrameDetach) {
ASSERT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a.com(b.com)")));
RenderFrameHostImpl* child_rfh =
static_cast<RenderFrameHostImpl*>(ChildFrameAt(web_contents(), 0));
ASSERT_TRUE(child_rfh);
const blink::LocalFrameToken child_token = child_rfh->GetFrameToken();
const ChildProcessId child_process_id = child_rfh->GetProcess()->GetID();
base::android::ScopedJavaLocalRef<jobject> java_params =
CreateJavaAdditionalNavigationParams(
base::android::AttachCurrentThread(), *child_rfh);
ASSERT_TRUE(java_params);
// Detach the initiator frame while still holding the params.
RenderFrameDeletedObserver deleted_observer(child_rfh);
ASSERT_TRUE(
ExecJs(web_contents(), "document.querySelector('iframe').remove();"));
ASSERT_TRUE(deleted_observer.WaitUntilDeleted());
ASSERT_FALSE(
RenderFrameHostImpl::FromFrameToken(child_process_id, child_token));
// Verify round-trip extraction of frame token and process ID even after frame
// detachment.
std::optional<blink::LocalFrameToken> extracted_frame_token =
GetInitiatorFrameTokenFromJavaAdditionalNavigationParams(
base::android::AttachCurrentThread(), java_params);
EXPECT_TRUE(extracted_frame_token.has_value());
EXPECT_EQ(extracted_frame_token.value(), child_token);
ChildProcessId extracted_process_id =
GetInitiatorProcessIdFromJavaAdditionalNavigationParams(
base::android::AttachCurrentThread(), java_params);
EXPECT_FALSE(extracted_process_id.is_null());
EXPECT_EQ(extracted_process_id, child_process_id);
// We can get the state from the Java object.
scoped_refptr<InitiatorNavigationState> taken_state =
TakeNativeStateFromJavaAdditionalNavigationParams(
base::android::AttachCurrentThread(), java_params);
EXPECT_TRUE(taken_state);
// Clean up the Java object to satisfy LifetimeAssert and release native
// state.
DestroyJavaAdditionalNavigationParams(base::android::AttachCurrentThread(),
java_params);
}
} // namespace content