// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/translate/translate_frame_binder.h"

#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/chrome_content_browser_client.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/translate/content/common/translate.mojom.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_client.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/public/test/prerender_test_util.h"
#include "mojo/public/cpp/bindings/binder_map.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace translate {

namespace {

class TestTranslateDriverBindingContentBrowserClient
    : public ChromeContentBrowserClient {
 public:
  TestTranslateDriverBindingContentBrowserClient() = default;
  ~TestTranslateDriverBindingContentBrowserClient() override = default;

  void RegisterBrowserInterfaceBindersForFrame(
      content::RenderFrameHost* render_frame_host,
      mojo::BinderMapWithContext<content::RenderFrameHost*>* map) override {
    ChromeContentBrowserClient::RegisterBrowserInterfaceBindersForFrame(
        render_frame_host, map);
    // Override binding for translate::mojom::ContentTranslateDriver.
    map->Add<translate::mojom::ContentTranslateDriver>(base::BindRepeating(
        &TestTranslateDriverBindingContentBrowserClient::BindTest,
        weak_factory_.GetWeakPtr()));
  }

  void BindTest(content::RenderFrameHost* render_frame_host,
                mojo::PendingReceiver<translate::mojom::ContentTranslateDriver>
                    receiver) {
    // It should be called on the active page.
    ASSERT_EQ(render_frame_host->GetLifecycleState(),
              content::RenderFrameHost::LifecycleState::kActive);

    translate::BindContentTranslateDriver(render_frame_host,
                                          std::move(receiver));

    // Check if |render_frame_host| is a primary main frame again here even
    // though BindContentTranslateDriver method already checks it, since it's
    // difficult to know if the method succeeds to bind the given receiver.
    if (render_frame_host->IsInPrimaryMainFrame())
      render_frame_binding_map_[render_frame_host] = true;

    if (quit_on_binding_)
      std::move(quit_on_binding_).Run();
  }

  bool WaitForBinding(content::RenderFrameHost* render_frame_host,
                      base::OnceClosure callback) {
    if (IsBound(render_frame_host))
      return false;
    quit_on_binding_ = std::move(callback);
    return true;
  }

  bool IsBound(content::RenderFrameHost* render_frame_host) {
    return render_frame_binding_map_[render_frame_host];
  }

 private:
  base::OnceClosure quit_on_binding_;
  std::map<content::RenderFrameHost*, bool> render_frame_binding_map_;
  base::WeakPtrFactory<TestTranslateDriverBindingContentBrowserClient>
      weak_factory_{this};
};

}  // namespace

class TranslateFrameBinderBrowserTest : public InProcessBrowserTest {
 public:
  TranslateFrameBinderBrowserTest() = default;
  ~TranslateFrameBinderBrowserTest() override = default;

  void SetUpOnMainThread() override {
    ASSERT_TRUE(test_server_handle_ =
                    embedded_test_server()->StartAndReturnHandle());
  }

  content::WebContents* web_contents() const {
    return browser()->tab_strip_model()->GetActiveWebContents();
  }

 private:
  net::test_server::EmbeddedTestServerHandle test_server_handle_;
};

class TranslateFrameBinderPrerenderBrowserTest
    : public TranslateFrameBinderBrowserTest {
 public:
  TranslateFrameBinderPrerenderBrowserTest()
      : prerender_helper_(base::BindRepeating(
            &TranslateFrameBinderPrerenderBrowserTest::web_contents,
            base::Unretained(this))) {}
  ~TranslateFrameBinderPrerenderBrowserTest() override = default;

  void SetUp() override {
    prerender_helper_.RegisterServerRequestMonitor(embedded_test_server());
    TranslateFrameBinderBrowserTest::SetUp();
  }

  content::test::PrerenderTestHelper* prerender_helper() {
    return &prerender_helper_;
  }

 private:
  content::test::PrerenderTestHelper prerender_helper_;
};

// Tests that mojom::ContentTranslateDriver binding is deferred in prerendering
// and it's safe to access WebContents in the binding function since it's
// executed after prerendering activation.
IN_PROC_BROWSER_TEST_F(TranslateFrameBinderPrerenderBrowserTest,
                       NotBindingInPrerendering) {
  TestTranslateDriverBindingContentBrowserClient test_browser_client;
  auto* old_browser_client = SetBrowserClientForTesting(&test_browser_client);

  const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
  const GURL kPrerenderingUrl = embedded_test_server()->GetURL("/title1.html");

  // Navigate to an initial page.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), kInitialUrl));

  content::PrerenderHostId host_id =
      prerender_helper()->AddPrerender(kPrerenderingUrl);
  content::RenderFrameHost* prerendered_frame_host =
      prerender_helper()->GetPrerenderedMainFrameHost(host_id);
  content::test::PrerenderHostObserver host_observer(*web_contents(), host_id);
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(test_browser_client.IsBound(prerendered_frame_host));

  // Activate the prerendered page.
  prerender_helper()->NavigatePrimaryPage(kPrerenderingUrl);
  EXPECT_TRUE(host_observer.was_activated());

  base::RunLoop run_loop;
  if (test_browser_client.WaitForBinding(prerendered_frame_host,
                                         run_loop.QuitClosure())) {
    run_loop.Run();
  }
  EXPECT_TRUE(test_browser_client.IsBound(prerendered_frame_host));

  content::SetBrowserClientForTesting(old_browser_client);
}

class TranslateFrameBinderFencedFrameBrowserTest
    : public TranslateFrameBinderBrowserTest {
 public:
  TranslateFrameBinderFencedFrameBrowserTest() = default;
  ~TranslateFrameBinderFencedFrameBrowserTest() override = default;
  TranslateFrameBinderFencedFrameBrowserTest(
      const TranslateFrameBinderFencedFrameBrowserTest&) = delete;

  TranslateFrameBinderFencedFrameBrowserTest& operator=(
      const TranslateFrameBinderFencedFrameBrowserTest&) = delete;

  content::test::FencedFrameTestHelper& fenced_frame_test_helper() {
    return fenced_frame_helper_;
  }

 private:
  content::test::FencedFrameTestHelper fenced_frame_helper_;
};

// TODO(crbug.com/40911156): Flaky on multiple platforms.
IN_PROC_BROWSER_TEST_F(TranslateFrameBinderFencedFrameBrowserTest,
                       DISABLED_NotBindingInFencedFrame) {
  TestTranslateDriverBindingContentBrowserClient test_browser_client;
  auto* old_browser_client = SetBrowserClientForTesting(&test_browser_client);

  // Navigate to an initial page.
  const GURL initial_url = embedded_test_server()->GetURL("/empty.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));

  // Create a fenced frame.
  const GURL fenced_frame_url =
      embedded_test_server()->GetURL("/fenced_frames/title1.html");
  content::RenderFrameHost* fenced_frame_host =
      fenced_frame_test_helper().CreateFencedFrame(
          web_contents()->GetPrimaryMainFrame(), fenced_frame_url);
  base::RunLoop run_loop;
  if (test_browser_client.WaitForBinding(fenced_frame_host,
                                         run_loop.QuitClosure())) {
    run_loop.Run();
  }
  // Fenced frame should not be bound.
  EXPECT_FALSE(test_browser_client.IsBound(fenced_frame_host));

  content::SetBrowserClientForTesting(old_browser_client);
}

}  // namespace translate
