// 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
