// Copyright 2020 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/public/browser/document_user_data.h"

#include "base/command_line.h"
#include "base/memory/weak_ptr.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/render_frame_host_impl.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/browser/render_frame_host.h"
#include "content/public/browser/site_isolation_policy.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.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/public/test/navigation_handle_observer.h"
#include "content/public/test/render_frame_host_test_support.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_navigation_throttle.h"
#include "content/public/test/test_navigation_throttle_inserter.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/url_loader_interceptor.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/url_request/url_request_failed_job.h"
#include "testing/gmock/include/gmock/gmock.h"

namespace content {

namespace {

int next_id = 0;

// Example class which inherits the DocumentUserData, all the data is
// associated to the lifetime of the document.
class Data : public DocumentUserData<Data> {
 public:
  ~Data() override = default;

  base::WeakPtr<Data> GetWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); }

  int unique_id() { return unique_id_; }

 private:
  explicit Data(RenderFrameHost* render_frame_host)
      : DocumentUserData<Data>(render_frame_host) {
    unique_id_ = ++next_id;
  }

  friend class content::DocumentUserData<Data>;

  int unique_id_;

  base::WeakPtrFactory<Data> weak_ptr_factory_{this};

  DOCUMENT_USER_DATA_KEY_DECL();
};

DOCUMENT_USER_DATA_KEY_IMPL(Data);

// Observer class to track creation of new popups. It is used
// in subsequent tests.
class PopupCreatedObserver : public WebContentsDelegate {
 public:
  using WebContentsCreatedCallback =
      base::RepeatingCallback<void(WebContents* web_contents)>;

  explicit PopupCreatedObserver(WebContentsCreatedCallback callback)
      : callback_(std::move(callback)) {}

  void AddNewContents(WebContents* source_contents,
                      std::unique_ptr<WebContents> new_contents,
                      const GURL& target_url,
                      WindowOpenDisposition disposition,
                      const blink::mojom::WindowFeatures& window_features,
                      bool user_gesture,
                      bool* was_blocked) override {
    callback_.Run(new_contents.get());
    web_contents_.push_back(std::move(new_contents));
  }

 private:
  WebContentsCreatedCallback callback_;
  std::vector<std::unique_ptr<WebContents>> web_contents_;
};

}  // namespace

class DocumentUserDataTest : public ContentBrowserTest {
 public:
  ~DocumentUserDataTest() override = default;

 protected:
  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");
    ContentBrowserTest::SetUpOnMainThread();
  }

  WebContentsImpl* web_contents() const {
    return static_cast<WebContentsImpl*>(shell()->web_contents());
  }

  RenderFrameHostImpl* top_frame_host() {
    return web_contents()->GetPrimaryFrameTree().root()->current_frame_host();
  }
};

// Test basic functionality of DocumentUserData.
IN_PROC_BROWSER_TEST_F(DocumentUserDataTest,
                       GetCreateAndDeleteForCurrentDocument) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));

  // 1) Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = top_frame_host();

  // 2) Get the Data associated with this RenderFrameHost. It should be null
  // before creation.
  Data* data = Data::GetForCurrentDocument(rfh_a);
  EXPECT_FALSE(data);

  // 3) Create Data and check that GetForCurrentDocument shouldn't return null
  // now.
  Data::CreateForCurrentDocument(rfh_a);
  base::WeakPtr<Data> created_data =
      Data::GetForCurrentDocument(rfh_a)->GetWeakPtr();
  EXPECT_TRUE(created_data);

  // 4) Delete Data and check that GetForCurrentDocument should return null.
  Data::DeleteForCurrentDocument(rfh_a);
  EXPECT_FALSE(created_data);
  EXPECT_FALSE(Data::GetForCurrentDocument(rfh_a));
}

// Test GetOrCreateForCurrentDocument API of DocumentUserData.
IN_PROC_BROWSER_TEST_F(DocumentUserDataTest, GetOrCreateForCurrentDocument) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));

  // 1) Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = top_frame_host();

  // 2) Get the Data associated with this RenderFrameHost. It should be null
  // before creation.
  Data* data = Data::GetForCurrentDocument(rfh_a);
  EXPECT_FALSE(data);

  // 3) |GetOrCreateForCurrentDocument| should create Data.
  base::WeakPtr<Data> created_data =
      Data::GetOrCreateForCurrentDocument(rfh_a)->GetWeakPtr();
  EXPECT_TRUE(created_data);

  // 4) Another call to |GetOrCreateForCurrentDocument| should not create the
  // new data and the previous data created in 3) should be preserved.
  Data::GetOrCreateForCurrentDocument(rfh_a);
  EXPECT_TRUE(created_data);
}

// Tests that DocumentUserData objects are different for each
// RenderFrameHost in FrameTree when there are multiple frames.
IN_PROC_BROWSER_TEST_F(DocumentUserDataTest, CheckForMultipleRFHsInFrameTree) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b)"));

  // 1) Navigate to a(b).
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = top_frame_host();
  RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host();

  // 2) Create DocumentUserData associated with both RenderFrameHosts
  // a and b.
  Data::CreateForCurrentDocument(rfh_a);
  Data* data_a = Data::GetForCurrentDocument(rfh_a);
  Data::CreateForCurrentDocument(rfh_b);
  Data* data_b = Data::GetForCurrentDocument(rfh_b);
  EXPECT_TRUE(data_a);
  EXPECT_TRUE(data_b);

  // 3) Check that DUD objects for both RenderFrameHost a and b have different
  // unique_id's.
  EXPECT_NE(data_a->unique_id(), data_b->unique_id());
}

// Tests that DocumentUserData object is preserved when the renderer
// process crashes even when RenderFrameHost still exists, but the DUD object
// is cleared if the RenderFrameHost is reused for the new RenderFrame creation
// when the previous renderer process crashes.
IN_PROC_BROWSER_TEST_F(DocumentUserDataTest,
                       CrashedFrameUserDataIsPreservedAndDeletedOnReset) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));

  // 1) Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = top_frame_host();

  // 2) Create DocumentUserData associated with A.
  Data::CreateForCurrentDocument(rfh_a);
  base::WeakPtr<Data> data = Data::GetForCurrentDocument(rfh_a)->GetWeakPtr();
  EXPECT_TRUE(data);

  // 3) Make the renderer crash.
  RenderProcessHost* renderer_process = rfh_a->GetProcess();
  RenderProcessHostWatcher crash_observer(
      renderer_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
  renderer_process->Shutdown(0);
  crash_observer.Wait();

  // 4) DUD shouldn't be cleared after the renderer crash.
  EXPECT_FALSE(rfh_a->IsRenderFrameLive());
  EXPECT_TRUE(data);

  // 5) Register an observer which observes created RenderFrameHost and checks
  // if the data is cleared when callback was run.
  bool did_clear_user_data = false;
  RenderFrameHostCreatedObserver observer(
      web_contents(), base::BindRepeating(
                          [](bool* did_clear_user_data, RenderFrameHost* rfh) {
                            if (!Data::GetForCurrentDocument(rfh))
                              *did_clear_user_data = true;
                          },
                          &did_clear_user_data));

  // 6) Re-initialize RenderFrame, now DUD should be cleared on new
  // RenderFrame creation after crash when
  // RenderFrameHostImpl::RenderFrameDeleted was called.
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  root->render_manager()->InitializeMainRenderFrameForImmediateUse();
  EXPECT_TRUE(did_clear_user_data);

  // With RenderDocument, on a reload renderer crashes would give a new
  // RenderFrameHost different from rfh_a.
  RenderFrameHostImpl* new_rfh_a = top_frame_host();
  EXPECT_TRUE(new_rfh_a->IsRenderFrameLive());

  // 7) DUD should be cleared after initialization.
  EXPECT_FALSE(data);

  // 8) Check DUD object creation after new RenderFrame creation.
  Data::CreateForCurrentDocument(new_rfh_a);
  base::WeakPtr<Data> new_data =
      Data::GetForCurrentDocument(new_rfh_a)->GetWeakPtr();
  EXPECT_TRUE(new_data);
}

// Tests that DocumentUserData object is not cleared when speculative
// RFH commits after the renderer hosting the current RFH (of old URL) crashes
// i.e., while navigating to a new URL (using speculative RFH) and having the
// current RFH (of old URL) not alive.
IN_PROC_BROWSER_TEST_F(DocumentUserDataTest,
                       CheckWithFrameCrashDuringNavigation) {
  // TODO(sreejakshetty): Investigate why the data is being deleted after crash
  // when BackForwardCache is enabled.
  DisableBackForwardCacheForTesting(shell()->web_contents(),
                                    BackForwardCache::TEST_REQUIRES_NO_CACHING);

  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", "/title2.html"));

  // Isolate "b.com" so we are guaranteed to get a different process
  // for navigations to this origin on Android. Doing this ensures that a
  // speculative RenderFrameHost is used.
  IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
                           {"b.com"});

  // 1) Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = top_frame_host();

  // 2) Start navigation to B, but don't commit yet.
  TestNavigationManager manager(shell()->web_contents(), url_b);
  // We should disable proactive BrowsingInstance swap for the navigation below
  // and also use PAGE_TRANSITION_LINK for the navigation to ensure that the
  // speculative RFH is going to use the same BrowsingInstance as the original
  // RFH. Otherwise, we'd create a new speculative RFH at ReadyToCommit time,
  // deleting the DUD we created for the first speculative RFH.
  DisableProactiveBrowsingInstanceSwapFor(rfh_a);
  shell()->LoadURLForFrame(url_b, std::string(),
                           ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK));
  EXPECT_TRUE(manager.WaitForRequestStart());

  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  RenderFrameHostImpl* pending_rfh =
      root->render_manager()->speculative_frame_host();
  NavigationRequest* navigation_request = root->navigation_request();
  EXPECT_EQ(navigation_request->GetAssociatedRFHType(),
            NavigationRequest::AssociatedRenderFrameHostType::SPECULATIVE);
  EXPECT_TRUE(pending_rfh);

  // 3) Get the DocumentUserData associated with the speculative
  // RenderFrameHost.
  Data::CreateForCurrentDocument(pending_rfh);
  base::WeakPtr<Data> data =
      Data::GetForCurrentDocument(pending_rfh)->GetWeakPtr();
  EXPECT_TRUE(data);

  // 4) Crash the renderer hosting current RFH.
  RenderProcessHost* renderer_process = rfh_a->GetProcess();
  RenderProcessHostWatcher crash_observer(
      renderer_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
  renderer_process->Shutdown(0);
  crash_observer.Wait();

  // 5) Check that the DUD object is not cleared after renderer process
  // crashes.
  EXPECT_EQ(top_frame_host(), rfh_a);
  EXPECT_FALSE(pending_rfh->IsActive());
  EXPECT_FALSE(rfh_a->IsRenderFrameLive());
  EXPECT_TRUE(pending_rfh->IsRenderFrameLive());
  EXPECT_TRUE(data);

  // 6) Let the navigation finish and make sure it has succeeded.
  ASSERT_TRUE(manager.WaitForNavigationFinished());
  EXPECT_EQ(url_b,
            web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());

  // 7) Data shouldn't be cleared in this case, as state
  // |committed_speculative_rfh_before_navigation_commit_| is true during the
  // check in DidCommitInternalNavigation as the speculative RFH swaps with the
  // crashed RFH and performs commit before navigation commit happens.
  EXPECT_TRUE(data);
}

// Tests that DocumentUserData object is not cleared when speculative
// RFH commits after renderer hosting the current RFH (of old URL) which happens
// before navigating to a new URL (using Speculative RFH) and having the current
// RenderFrameHost (of old URL) not alive.
IN_PROC_BROWSER_TEST_F(DocumentUserDataTest,
                       CheckWithFrameCrashBeforeNavigation) {
  if (ShouldSkipEarlyCommitPendingForCrashedFrame())
    return;

  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", "/title2.html"));

  // Isolate "b.com" so we are guaranteed to get a different process
  // for navigations to this origin on Android. Doing this ensures that a
  // speculative RenderFrameHost is used.
  IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
                           {"b.com"});

  // 1) Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = top_frame_host();

  // 2) Crash the renderer hosting current RFH.
  RenderProcessHost* renderer_process = rfh_a->GetProcess();
  RenderProcessHostWatcher crash_observer(
      renderer_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
  renderer_process->Shutdown(0);
  crash_observer.Wait();

  // 3) Start navigation to B, but don't commit yet.
  TestNavigationManager manager(shell()->web_contents(), url_b);
  shell()->LoadURL(url_b);
  EXPECT_TRUE(manager.WaitForRequestStart());

  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  // Speculative RenderFrameHost for B will commit early because current rfh_a
  // is not alive after the crash in step (2).
  RenderFrameHostImpl* current_rfh =
      root->render_manager()->current_frame_host();
  NavigationRequest* navigation_request = root->navigation_request();
  EXPECT_EQ(navigation_request->GetAssociatedRFHType(),
            NavigationRequest::AssociatedRenderFrameHostType::CURRENT);
  EXPECT_TRUE(current_rfh);
  EXPECT_TRUE(current_rfh->IsActive());

  // 4) Get the DocumentUserData associated with speculative
  // RenderFrameHost.
  Data::CreateForCurrentDocument(current_rfh);
  base::WeakPtr<Data> data =
      Data::GetForCurrentDocument(current_rfh)->GetWeakPtr();
  EXPECT_TRUE(data);

  // 5) Let the navigation finish and make sure it has succeeded.
  ASSERT_TRUE(manager.WaitForNavigationFinished());
  EXPECT_EQ(url_b,
            web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());

  // 6) Data shouldn't be cleared in this case, as state
  // |committed_speculative_rfh_before_navigation_commit_| is true during the
  // check in DidCommitInternalNavigation as the speculative RFH swaps with the
  // crashed RFH and performs commit before navigation commit happens.
  EXPECT_TRUE(data);
}

// Test that the DocumentUserData object is cleared when the RenderFrameHost is
// reused for the new RenderFrame creation after a RendererDebug URL crash
// without navigating to a new document. This initializes the RenderFrame using
// RenderFrameHostManager::InitializeMainRenderFrameForImmediateUse.
IN_PROC_BROWSER_TEST_F(DocumentUserDataTest,
                       CheckWithRenderFrameCreationAfterRendererDebugURLCrash) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL renderer_debug_url("javascript:'hello'");
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));

  // 1) Load a javascript URL, which is a renderer debug URL.  This navigation
  // won't commit, but the renderer process will synchronously process the
  // javascript URL and install an HTML document that contains "hello".
  shell()->LoadURL(renderer_debug_url);
  ASSERT_EQ("hello", EvalJs(shell(), "document.body.innerText"));

  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  RenderFrameHostImpl* rfh = root->current_frame_host();

  // 2) Get the DocumentUserData associated with rfh.
  Data::CreateForCurrentDocument(rfh);
  base::WeakPtr<Data> data = Data::GetForCurrentDocument(rfh)->GetWeakPtr();
  EXPECT_TRUE(data);

  // 3) Crash the renderer hosting current RFH.
  RenderProcessHost* renderer_process = rfh->GetProcess();
  RenderProcessHostWatcher crash_observer(
      renderer_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
  renderer_process->Shutdown(0);
  crash_observer.Wait();

  // 4) DUD shouldn't be cleared after the renderer crash.
  EXPECT_FALSE(rfh->IsRenderFrameLive());
  EXPECT_TRUE(data);

  // 5) Load the renderer_debug_url again. This should result in calling
  // RenderFrameHostManager::InitializeMainRenderFrameForImmediateUse where we
  // initialize new RenderFrame.
  shell()->LoadURL(renderer_debug_url);
  ASSERT_EQ("hello", EvalJs(shell(), "document.body.innerText"));

  // 6) DUD should be cleared after initialization.
  EXPECT_FALSE(data);
}

// Tests that DocumentUserData object is created for speculative
// RenderFrameHost and check if they point to same object before and after
// commit.
IN_PROC_BROWSER_TEST_F(DocumentUserDataTest,
                       CheckIDsForSpeculativeRFHBeforeAndAfterCommit) {
  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", "/title2.html"));

  // Isolate "b.com" so we are guaranteed to get a different process
  // for navigations to this origin on Android. Doing this ensures that a
  // speculative RenderFrameHost is used.
  IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
                           {"b.com"});

  // 1) Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));

  // 2) Start navigation to B, but don't commit yet.
  TestNavigationManager manager(shell()->web_contents(), url_b);
  shell()->LoadURL(url_b);
  EXPECT_TRUE(manager.WaitForRequestStart());

  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  RenderFrameHostImpl* pending_rfh =
      root->render_manager()->speculative_frame_host();
  NavigationRequest* navigation_request = root->navigation_request();
  EXPECT_EQ(navigation_request->GetAssociatedRFHType(),
            NavigationRequest::AssociatedRenderFrameHostType::SPECULATIVE);
  EXPECT_TRUE(pending_rfh);

  // 3) While there is a speculative RenderFrameHost in the root FrameTreeNode,
  // get the Data associated with this RenderFrameHost.
  Data::CreateForCurrentDocument(pending_rfh);
  Data* data_before_commit = Data::GetForCurrentDocument(pending_rfh);
  EXPECT_TRUE(data_before_commit);

  // 4) Let the navigation finish and make sure it is succeeded.
  ASSERT_TRUE(manager.WaitForNavigationFinished());
  EXPECT_EQ(url_b,
            web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());

  RenderFrameHostImpl* rfh_b = top_frame_host();
  EXPECT_EQ(pending_rfh, rfh_b);
  Data* data_after_commit = Data::GetForCurrentDocument(rfh_b);
  EXPECT_TRUE(data_after_commit);

  // 5) Check |data_before_commit| and |data_after_commit| have same ID.
  EXPECT_EQ(data_before_commit->unique_id(), data_after_commit->unique_id());
}

// Tests that DocumentUserData object is deleted when the speculative
// RenderFrameHost gets deleted before being able to commit.
IN_PROC_BROWSER_TEST_F(DocumentUserDataTest, SpeculativeRFHDeleted) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b)"));
  GURL url_c(embedded_test_server()->GetURL("c.com", "/hung"));
  IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());

  // 1) Initial state: A(B).
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = web_contents()->GetPrimaryMainFrame();
  RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host();

  // Leave rfh_b in pending deletion state.
  LeaveInPendingDeletionState(rfh_b);

  // 2) Navigation from B to C. The server is slow to respond.
  TestNavigationManager navigation_observer(web_contents(), url_c);
  EXPECT_TRUE(ExecJs(rfh_b, JsReplace("location.href=$1;", url_c)));
  EXPECT_TRUE(navigation_observer.WaitForRequestStart());
  RenderFrameHostImpl* pending_rfh_c =
      rfh_b->frame_tree_node()->render_manager()->speculative_frame_host();

  // 3) While there is a speculative RenderFrameHost get the Data associated
  // with it.
  Data::CreateForCurrentDocument(pending_rfh_c);
  base::WeakPtr<Data> data =
      Data::GetForCurrentDocument(pending_rfh_c)->GetWeakPtr();
  EXPECT_TRUE(data);

  // 4) Delete the speculative RenderFrameHost by navigating from a
  // RenderFrameHost in pending deletion.
  RenderFrameDeletedObserver delete_speculative_c(pending_rfh_c);
  EXPECT_TRUE(
      ExecJs(rfh_a, JsReplace("document.querySelector('iframe').remove();")));
  delete_speculative_c.WaitUntilDeleted();
  EXPECT_TRUE(delete_speculative_c.deleted());

  // 5) Once the speculative RenderFrameHost is deleted, the associated
  // DocumentUserData should be deleted.
  EXPECT_FALSE(data);
}

// Tests that DocumentUserData is cleared when the RenderFrameHost is
// deleted.
IN_PROC_BROWSER_TEST_F(DocumentUserDataTest, RenderFrameHostDeleted) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b)"));

  // 1) Navigate to a(b).
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  RenderFrameHostImpl* rfh_a = top_frame_host();
  RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host();
  RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);

  // 2) Get the Data associated with the rfh_b and check the data gets cleared
  // on RenderFrameHost deletion.
  Data::CreateForCurrentDocument(rfh_b);
  base::WeakPtr<Data> data = Data::GetForCurrentDocument(rfh_b)->GetWeakPtr();
  EXPECT_TRUE(data);

  // 3) Detach the child frame.
  EXPECT_TRUE(ExecJs(root, "document.querySelector('iframe').remove()"));

  // 4) Once the RenderFrameHost is deleted, the associated
  // DocumentUserData should be deleted.
  delete_observer_rfh_b.WaitUntilDeleted();
  EXPECT_FALSE(data);
}

// Tests that DocumentUserData object is not cleared when the
// RenderFrameHost is in pending deletion state for both main frame and sub
// frame.
IN_PROC_BROWSER_TEST_F(DocumentUserDataTest, CheckInPendingDeletionState) {
  ASSERT_TRUE(embedded_test_server()->Start());
  IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
  GURL url_ab(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b)"));
  GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html"));

  // 1) Navigate to A(B).
  EXPECT_TRUE(NavigateToURL(shell(), url_ab));
  RenderFrameHostImpl* rfh_a = top_frame_host();
  RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host();

  // Test needs these RenderFrameHosts to be pending deletion after navigating
  // but it doesn't happen with BackForwardCache as it is stored in cache.
  // BFCache case is covered explicitly by
  // "DocumentUserDataWithBackForwardCacheTest.
  //      BackForwardCacheNavigation" test.
  DisableBackForwardCacheForTesting(shell()->web_contents(),
                                    BackForwardCache::TEST_REQUIRES_NO_CACHING);

  // 2) Leave both rfh_a and rfh_b in pending deletion state.
  LeaveInPendingDeletionState(rfh_a);
  LeaveInPendingDeletionState(rfh_b);

  // 3) Create DUD object for both rfh_a and rfh_b before running unload
  // handlers.
  Data::CreateForCurrentDocument(rfh_a);
  Data::CreateForCurrentDocument(rfh_b);

  base::WeakPtr<Data> data_a = Data::GetForCurrentDocument(rfh_a)->GetWeakPtr();
  base::WeakPtr<Data> data_b = Data::GetForCurrentDocument(rfh_b)->GetWeakPtr();
  EXPECT_TRUE(data_a);
  EXPECT_TRUE(data_b);

  // 4) Navigate from A(B) to C.
  EXPECT_TRUE(NavigateToURL(shell(), url_c));

  // 5) Check DUD objects |data_a| and |data_b| are not cleared when rfh_a and
  // rfh_b are in pending deletion state.
  EXPECT_EQ(rfh_a->lifecycle_state(),
            RenderFrameHostImpl::LifecycleStateImpl::kRunningUnloadHandlers);
  EXPECT_EQ(rfh_b->lifecycle_state(),
            RenderFrameHostImpl::LifecycleStateImpl::kRunningUnloadHandlers);
  EXPECT_TRUE(data_a);
  EXPECT_TRUE(data_b);

  EXPECT_FALSE(rfh_a->IsActive());
  EXPECT_FALSE(rfh_b->IsActive());
}

// Tests that DocumentUserData associated with RenderFrameHost is not
// cleared on same document navigation.
IN_PROC_BROWSER_TEST_F(DocumentUserDataTest, CommitSameDocumentNavigation) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_a2(embedded_test_server()->GetURL("a.com", "/title1.html#2"));

  // 1) Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = top_frame_host();

  // 2) Get the Data associated with this RenderFrameHost.
  Data::CreateForCurrentDocument(rfh_a);
  Data* data = Data::GetForCurrentDocument(rfh_a);
  EXPECT_TRUE(data);

  // 3) Navigate to A#2 (same document navigation).
  EXPECT_TRUE(ExecJs(shell(), JsReplace("location = $1", url_a2.spec())));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  EXPECT_EQ(url_a2,
            web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());

  // 4) Check if the DUD objects are pointing to the same instance after
  // navigation.
  Data* data2 = Data::GetForCurrentDocument(rfh_a);
  EXPECT_TRUE(data2);
  EXPECT_EQ(data->unique_id(), data2->unique_id());
}

// Tests that DocumentUserData object is not cleared when navigation
// is cancelled.
IN_PROC_BROWSER_TEST_F(DocumentUserDataTest, CancelledNavigation) {
  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.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = top_frame_host();

  // 2) Get the Data associated with this RenderFrameHost.
  Data::CreateForCurrentDocument(rfh_a);
  Data* data = Data::GetForCurrentDocument(rfh_a);
  EXPECT_TRUE(data);

  // 3) Cancel all navigation attempts.
  TestNavigationThrottleInserter throttle_inserter(
      shell()->web_contents(),
      base::BindLambdaForTesting(
          [&](NavigationHandle* handle) -> std::unique_ptr<NavigationThrottle> {
            auto throttle = std::make_unique<TestNavigationThrottle>(handle);
            throttle->SetResponse(TestNavigationThrottle::WILL_START_REQUEST,
                                  TestNavigationThrottle::SYNCHRONOUS,
                                  NavigationThrottle::CANCEL_AND_IGNORE);
            return throttle;
          }));

  // 4) Try navigating to B.
  EXPECT_FALSE(NavigateToURL(shell(), url_b));

  // 5) We should still be showing page A.
  EXPECT_EQ(rfh_a, top_frame_host());

  // 6) The data shouldn't be cleared in the case of cancelled navigations and
  // should be pointing to the same instances.
  Data* data2 = Data::GetForCurrentDocument(rfh_a);
  EXPECT_TRUE(data2);
  EXPECT_EQ(data->unique_id(), data2->unique_id());
}

// Tests that DocumentUserData object is cleared when a failed
// navigation results in an error page.
IN_PROC_BROWSER_TEST_F(DocumentUserDataTest, FailedNavigation) {
  // This test is only valid if error page isolation is enabled.
  if (!SiteIsolationPolicy::IsErrorPageIsolationEnabled(true))
    return;

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL error_url(embedded_test_server()->GetURL("/close-socket"));
  std::unique_ptr<URLLoaderInterceptor> url_interceptor =
      URLLoaderInterceptor::SetupRequestFailForURL(error_url,
                                                   net::ERR_DNS_TIMED_OUT);

  // 1) Start with a successful navigation to a document.
  EXPECT_TRUE(NavigateToURL(shell(), url));
  RenderFrameHostImpl* rfh_a = top_frame_host();
  RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);

  // 2) Get the Data associated with RenderFrameHost associated with url.
  Data::CreateForCurrentDocument(rfh_a);
  base::WeakPtr<Data> data = Data::GetForCurrentDocument(rfh_a)->GetWeakPtr();
  EXPECT_TRUE(data);

  // This test expects old RenderFrameHost to be deleted after navigating to an
  // error page, disable back-forward cache to ensure that RenderFrameHost gets
  // deleted.
  DisableBackForwardCacheForTesting(shell()->web_contents(),
                                    BackForwardCache::TEST_REQUIRES_NO_CACHING);

  // 3) Browser-initiated navigation to an error page.
  NavigationHandleObserver observer(shell()->web_contents(), error_url);
  EXPECT_FALSE(NavigateToURL(shell(), error_url));
  EXPECT_TRUE(observer.is_error());
  EXPECT_EQ(net::ERR_DNS_TIMED_OUT, observer.net_error_code());

  // 4) The associated DocumentUserData object should be deleted.
  delete_observer_rfh_a.WaitUntilDeleted();
  EXPECT_FALSE(data);
}

// Tests that DocumentUserData object is cleared when it is neither a
// same document navigation nor when it is stored in back-forward cache after
// navigating away.
IN_PROC_BROWSER_TEST_F(DocumentUserDataTest, CrossSiteNavigation) {
  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.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = top_frame_host();
  RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);

  // 2) Get the Data associated with this RenderFrameHost.
  Data::CreateForCurrentDocument(rfh_a);
  base::WeakPtr<Data> data = Data::GetForCurrentDocument(rfh_a)->GetWeakPtr();
  EXPECT_TRUE(data);

  DisableBackForwardCacheForTesting(shell()->web_contents(),
                                    BackForwardCache::TEST_REQUIRES_NO_CACHING);

  // 3) Navigate to B.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_NE(rfh_a, top_frame_host());

  // 4) Both rfh_a and DUD should be deleted.
  delete_observer_rfh_a.WaitUntilDeleted();
  EXPECT_FALSE(data);
}

IN_PROC_BROWSER_TEST_F(DocumentUserDataTest, SameSiteNavigation) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_a2(embedded_test_server()->GetURL("a.com", "/title2.html"));
  // The test assumes the previous page gets deleted after navigation. Disable
  // back-forward cache to ensure that it doesn't get preserved in the cache.
  DisableBackForwardCacheForTesting(shell()->web_contents(),
                                    BackForwardCache::TEST_REQUIRES_NO_CACHING);

  // 1) Navigate to A1.
  EXPECT_TRUE(NavigateToURL(shell(), url_a1));
  RenderFrameHostImpl* rfh_a1 = top_frame_host();

  // 2) Get the Data associated with this RenderFrameHost.
  Data::CreateForCurrentDocument(rfh_a1);
  base::WeakPtr<Data> data = Data::GetForCurrentDocument(rfh_a1)->GetWeakPtr();

  EXPECT_TRUE(data);

  // 3) Navigate to A2.
  EXPECT_TRUE(NavigateToURL(shell(), url_a2));

  // 4) The associated DocumentUserData should be deleted.
  EXPECT_FALSE(data);
}

// This test ensures that the data created during the new WebContents
// initialisation is not lost during the initial "navigation" triggered by the
// window.open.
IN_PROC_BROWSER_TEST_F(DocumentUserDataTest, WindowOpen) {
  ASSERT_TRUE(embedded_test_server()->Start());

  int popup_data_id = -1;
  WebContents* new_tab = nullptr;

  // 1) Navigate to A.
  GURL url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url));

  // 2) Register WebContentsDelegate to get notified about new popups.
  PopupCreatedObserver observer(base::BindRepeating(
      [](int* popup_data_id, WebContents** new_tab, WebContents* web_contents) {
        EXPECT_EQ(*popup_data_id, -1);
        EXPECT_FALSE(*new_tab);
        *new_tab = web_contents;

        *popup_data_id = Data::GetOrCreateForCurrentDocument(
                             web_contents->GetPrimaryMainFrame())
                             ->unique_id();
      },
      &popup_data_id, &new_tab));
  web_contents()->SetDelegate(&observer);

  // 3) Invoke a window.open() without parameters. The delegate should capture
  // the ownership of the new WebContents and attach Data to its main frame.
  EXPECT_TRUE(ExecJs(top_frame_host(), "window.open()"));
  EXPECT_TRUE(new_tab);
  // Do not check for the success status because we haven't committed a
  // navigation yet, which causes checks to fail.
  WaitForLoadStopWithoutSuccessCheck(new_tab);

  // 4) Expect the data to the preserved (it might have been deleted due to us
  // having a fake initial navigation for blank window.open).
  Data* new_tab_data =
      Data::GetForCurrentDocument(new_tab->GetPrimaryMainFrame());
  EXPECT_TRUE(new_tab_data);
  EXPECT_EQ(new_tab_data->unique_id(), popup_data_id);

  web_contents()->SetDelegate(nullptr);
}

IN_PROC_BROWSER_TEST_F(DocumentUserDataTest, BlankIframe) {
  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", "/page_with_blank_iframe.html"));

  // 0) Navigate to A to ensure that we have a live frame (see a comment in
  // AttachOnCreatingInitialFrame).
  EXPECT_TRUE(NavigateToURL(shell(), url_a));

  int starting_id = next_id + 1;

  std::vector<RenderFrameHost*> created_rfhs;

  // 1) Register an observer which observes all created RenderFrameHosts
  // and attaches user data to them.
  RenderFrameHostCreatedObserver observer(
      web_contents(), base::BindRepeating(
                          [](std::vector<RenderFrameHost*>* created_rfhs,
                             RenderFrameHost* rfh) {
                            created_rfhs->push_back(rfh);
                            Data::GetOrCreateForCurrentDocument(rfh);
                          },
                          &created_rfhs));

  // 2) Navigate to a page with a blank iframe.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));

  // 3) Expect that we have two frames with valid user data.
  // The danger here lies in the perculiar properties of the initial navigation
  // for the blank iframes, which does not create a new document, but goes via
  // DidCommitProvisionalLoad.
  std::vector<int> current_ids;
  for (RenderFrameHost* rfh : created_rfhs) {
    if (auto* data = Data::GetForCurrentDocument(rfh)) {
      current_ids.push_back(data->unique_id());
    }
  }

  EXPECT_EQ(2u, created_rfhs.size());
  EXPECT_THAT(current_ids, testing::ElementsAre(starting_id, starting_id + 1));
}

IN_PROC_BROWSER_TEST_F(DocumentUserDataTest, SrcDocIframe) {
  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", "/frame_tree/page_with_srcdoc_frame.html"));

  // 0) Navigate to A to ensure that we have a live frame (see a comment in
  // AttachOnCreatingInitialFrame).
  EXPECT_TRUE(NavigateToURL(shell(), url_a));

  int starting_id = next_id + 1;

  std::vector<RenderFrameHost*> created_rfhs;

  // 1) Register an observer which observes all created RenderFrameHosts
  // and attaches user data to them.
  RenderFrameHostCreatedObserver observer(
      web_contents(), base::BindRepeating(
                          [](std::vector<RenderFrameHost*>* created_rfhs,
                             RenderFrameHost* rfh) {
                            created_rfhs->push_back(rfh);
                            Data::GetOrCreateForCurrentDocument(rfh);
                          },
                          &created_rfhs));

  // 2) Navigate to a page with a srcdoc iframe.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));

  // 3) Expect that we have one frame with valid user data.
  // For the subframe, the user data was attached to the initial blank document
  // and should have been deleted when the document was navigated to
  // about:srcdoc.
  std::vector<int> current_ids;
  for (RenderFrameHost* rfh : created_rfhs) {
    if (auto* data = Data::GetForCurrentDocument(rfh)) {
      current_ids.push_back(data->unique_id());
    }
  }

  EXPECT_EQ(2u, created_rfhs.size());
  EXPECT_THAT(current_ids, testing::ElementsAre(starting_id));
}

// This test doesn't actually work at the moment and documents the current
// behaviour rather than intended one.
// TODO(sreejakshetty): Fix it.
IN_PROC_BROWSER_TEST_F(DocumentUserDataTest, AttachOnCreatingInitialFrame) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url(embedded_test_server()->GetURL("a.com", "/title1.html"));

  bool observed_frame_creation = false;

  // 1) Register an observer which observes all created RenderFrameHosts
  // and attaches user data to them.
  RenderFrameHostCreatedObserver observer(
      web_contents(),
      base::BindRepeating(
          [](bool* observed_frame_creation, RenderFrameHost* rfh) {
            Data::GetOrCreateForCurrentDocument(rfh);
            *observed_frame_creation = true;
          },
          &observed_frame_creation));

  // 2) Navigate to a new page.
  EXPECT_FALSE(
      shell()->web_contents()->GetPrimaryMainFrame()->IsRenderFrameLive());
  EXPECT_TRUE(NavigateToURL(shell(), url));

  // 3) Unfortunately, user data will not be there – the reason is that we
  // actually reuse the initial RenderFrameHost, which wasn't fully initialised
  // when we started a navigation (IsRenderFrameLive == false). It will complete
  // its initialisation, dispatching RenderFrameCreated, but during the
  // navigation commit we will consider the document in it to be reset later
  // when we commit the navigation. In the short term, we should not reset DUD
  // in that case. In the long term, we probably should create a new
  // RenderFrameHost here.
  EXPECT_TRUE(observed_frame_creation);
  EXPECT_FALSE(Data::GetForCurrentDocument(
      shell()->web_contents()->GetPrimaryMainFrame()));
}

// Test DocumentUserData with BackForwardCache feature enabled.
class DocumentUserDataWithBackForwardCacheTest : public DocumentUserDataTest {
 public:
  DocumentUserDataWithBackForwardCacheTest() {
    scoped_feature_list_.InitWithFeaturesAndParameters(
        GetDefaultEnabledBackForwardCacheFeaturesForTesting(
            /*ignore_outstanding_network_request=*/false),
        GetDefaultDisabledBackForwardCacheFeaturesForTesting());
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

// Tests that DocumentUserData object is not cleared on storing and
// restoring a page from back-forward cache.
IN_PROC_BROWSER_TEST_F(DocumentUserDataWithBackForwardCacheTest,
                       BackForwardCacheNavigation) {
  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.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = top_frame_host();

  // 2) Get the Data associated with this RenderFrameHost.
  Data::CreateForCurrentDocument(rfh_a);
  Data* data = Data::GetForCurrentDocument(rfh_a);
  EXPECT_TRUE(data);

  //  3) Navigate to B. A should be stored in back-forward cache.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 4) Data associated with document shouldn't have been cleared on navigating
  // away with BackForwardCache.
  data = Data::GetForCurrentDocument(rfh_a);
  EXPECT_TRUE(data);

  // 5) Go back to A.
  web_contents()->GetController().GoBack();
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));

  Data* data_after_restore =
      Data::GetForCurrentDocument(web_contents()->GetPrimaryMainFrame());
  EXPECT_TRUE(data);

  // 6) Both the instances of Data before and after restore should point to the
  // same object and make sure they aren't null.
  EXPECT_EQ(data_after_restore->unique_id(), data->unique_id());
}

}  // namespace content