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

#include "components/translate/core/browser/translate_manager.h"

#include <memory>

#include "base/functional/bind.h"
#include "base/i18n/language_tag.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/run_until.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/scoped_logging_settings.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/language/accept_languages_service_factory.h"
#include "chrome/browser/prefs/session_startup_pref.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/translate/chrome_translate_client.h"
#include "chrome/browser/translate/translate_test_utils.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/read_anything/read_anything_controller.h"
#include "chrome/browser/ui/side_panel/side_panel_entry_id.h"
#include "chrome/browser/ui/side_panel/side_panel_ui.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/toasts/api/toast_id.h"
#include "chrome/browser/ui/toasts/toast_controller.h"
#include "chrome/browser/ui/toasts/toast_features.h"
#include "chrome/browser/ui/toasts/toast_view.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/translate/translate_bubble_controller.h"
#include "chrome/common/chrome_isolated_world_ids.h"
#include "chrome/test/base/chrome_test_path_utils.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/search_test_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/translate/core/browser/translate_browser_metrics.h"
#include "components/translate/core/browser/translate_error_details.h"
#include "components/translate/core/browser/translate_prefs.h"
#include "components/translate/core/common/language_detection_details.h"
#include "components/translate/core/common/translate_features.h"
#include "components/translate/core/common/translate_switches.h"
#include "components/translate/core/common/translate_util.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.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/prerender_test_util.h"
#include "content/public/test/test_navigation_observer.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/events/base_event_utils.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/test/button_test_api.h"
#include "url/gurl.h"

namespace translate {
namespace {

static const char kTestValidScript[] =
    "var google = {};"
    "window.isTranslationRestored = false;"
    "google.translate = (function() {"
    "  return {"
    "    TranslateService: function() {"
    "      return {"
    "        isAvailable : function() {"
    "          return true;"
    "        },"
    "        restore : function() {"
    "          isTranslationRestored = true;"
    "          return;"
    "        },"
    "        getDetectedLanguage : function() {"
    "          return \"fr\";"
    "        },"
    "        translatePage : function(sourceLang, targetLang,"
    "                                 onTranslateProgress) {"
    "          var error = (sourceLang == 'auto') ? true : false;"
    "          onTranslateProgress(100, true, error);"
    "        }"
    "      };"
    "    }"
    "  };"
    "})();"
    "cr.googleTranslate.onTranslateElementLoad();";

static const char kTestScriptInitializationError[] =
    "var google = {};"
    "google.translate = (function() {"
    "  return {"
    "    TranslateService: function() {"
    "      return error;"
    "    }"
    "  };"
    "})();"
    "cr.googleTranslate.onTranslateElementLoad();";

static const char kTestScriptIdenticalLanguages[] =
    "var google = {};"
    "google.translate = (function() {"
    "  return {"
    "    TranslateService: function() {"
    "      return {"
    "        isAvailable : function() {"
    "          return true;"
    "        },"
    "        restore : function() {"
    "          return;"
    "        },"
    "        getDetectedLanguage : function() {"
    "          return \"en\";"
    "        },"
    "        translatePage : function(sourceLang, targetLang,"
    "                                 onTranslateProgress) {"
    "          onTranslateProgress(100, true, 0);"
    "        }"
    "      };"
    "    }"
    "  };"
    "})();"
    "cr.googleTranslate.onTranslateElementLoad();";

static const char kTestScriptAvailableTimeout[] =
    "var google = {};"
    "google.translate = (function() {"
    "  return {"
    "    TranslateService: function() {"
    "      return {"
    "        isAvailable : function() {"
    "          return false;"
    "        },"
    "      };"
    "    }"
    "  };"
    "})();"
    "cr.googleTranslate.onTranslateElementLoad();";

static const char kTestScriptUnexpectedScriptError[] =
    "var google = {};"
    "google.translate = (function() {"
    "  return {"
    "    TranslateService: function() {"
    "      return {"
    "        isAvailable : function() {"
    "          return true;"
    "        },"
    "        restore : function() {"
    "          return;"
    "        },"
    "        getDetectedLanguage : function() {"
    "          return \"fr\";"
    "        },"
    "        translatePage : function(sourceLang, targetLang,"
    "                                 onTranslateProgress) {"
    "          return error;"
    "        }"
    "      };"
    "    }"
    "  };"
    "})();"
    "cr.googleTranslate.onTranslateElementLoad();";

static const char kTestScriptBadOrigin[] =
    "var google = {};"
    "google.translate = (function() {"
    "  return {"
    "    TranslateService: function() {"
    "      return {"
    "        isAvailable : function() {"
    "          return true;"
    "        },"
    "        restore : function() {"
    "          return;"
    "        },"
    "        getDetectedLanguage : function() {"
    "          return \"fr\";"
    "        },"
    "        translatePage : function(sourceLang, targetLang,"
    "                                 onTranslateProgress) {"
    "          var url = \"\";"
    "          cr.googleTranslate.onLoadJavascript(url);"
    "        }"
    "      };"
    "    }"
    "  };"
    "})();"
    "cr.googleTranslate.onTranslateElementLoad();";

static const char kTestScriptLoadError[] =
    "var google = {};"
    "google.translate = (function() {"
    "  return {"
    "    TranslateService: function() {"
    "      return {"
    "        isAvailable : function() {"
    "          return true;"
    "        },"
    "        restore : function() {"
    "          return;"
    "        },"
    "        getDetectedLanguage : function() {"
    "          return \"fr\";"
    "        },"
    "        translatePage : function(sourceLang, targetLang,"
    "                                 onTranslateProgress) {"
    "          var url = \"https://translate.googleapis.com/INVALID\";"
    "          cr.googleTranslate.onLoadJavascript(url);"
    "        }"
    "      };"
    "    }"
    "  };"
    "})();"
    "cr.googleTranslate.onTranslateElementLoad();";

static const char kTranslateHrefHintStatusHistogram[] =
    "Translate.HrefHint.Status";

class TranslateManagerBrowserTest : public InProcessBrowserTest {
 public:
  TranslateManagerBrowserTest() {
    error_subscription_ = TranslateManager::RegisterTranslateErrorCallback(
        base::BindRepeating(&TranslateManagerBrowserTest::OnTranslateError,
                            base::Unretained(this)));
  }

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

  ~TranslateManagerBrowserTest() override = default;

  void WaitUntilLanguageDetermined(
      ChromeTranslateClient* chrome_translate_client) {
    if (chrome_translate_client->GetLanguageState().source_language().empty()) {
      language_determined_waiter_->Wait();
    }
  }

  void WaitUntilPageTranslated() {
    translate::CreateTranslateWaiter(
        browser()->tab_strip_model()->GetActiveWebContents(),
        TranslateWaiter::WaitEvent::kPageTranslated)
        ->Wait();
  }

  void ResetObserver() {
    language_determined_waiter_ = translate::CreateTranslateWaiter(
        browser()->tab_strip_model()->GetActiveWebContents(),
        TranslateWaiter::WaitEvent::kLanguageDetermined);
  }

  std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
      const net::test_server::HttpRequest& request) {
    if (request.GetURL().GetPath() != "/mock_translate_script.js") {
      return nullptr;
    }

    std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
        new net::test_server::BasicHttpResponse);
    http_response->set_code(net::HTTP_OK);
    http_response->set_content(script_);
    http_response->set_content_type("text/javascript");
    return std::move(http_response);
  }

  void OnTranslateError(const TranslateErrorDetails& details) {
    error_type_ = details.error;
  }

  TranslateErrors GetPageTranslatedResult() { return error_type_; }

  ChromeTranslateClient* GetChromeTranslateClient() {
    return ChromeTranslateClient::FromWebContents(
        browser()->tab_strip_model()->GetActiveWebContents());
  }

  ToastController* GetToastController() {
    return browser()->GetFeatures().toast_controller();
  }

  bool IsToastShown(ToastId id) {
    ToastController* controller = GetToastController();
    return controller && controller->IsShowingToast() &&
           controller->GetCurrentToastId() == id;
  }

  bool IsTranslateBubbleShown() {
    return TranslateBubbleController::From(browser())->GetTranslateBubble() !=
           nullptr;
  }

  void ClickToastActionButton() {
    ToastController* controller = GetToastController();
    ASSERT_TRUE(controller);
    toasts::ToastView* view = controller->GetToastViewForTesting();
    ASSERT_TRUE(view);
    views::MdTextButton* button = view->action_button_for_testing();
    ASSERT_TRUE(button);

    // Simulate click
    ui::MouseEvent event(ui::EventType::kMousePressed, gfx::Point(),
                         gfx::Point(), ui::EventTimeForNow(),
                         ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON);
    views::test::ButtonTestApi(button).NotifyClick(event);
  }

  void ClickFrenchHrefTranslateLinkOnGooglePage() {
    SetTranslateScript(kTestValidScript);

    ChromeTranslateClient* chrome_translate_client = GetChromeTranslateClient();

    // Load a page with hrefTranslate tags.
    ASSERT_TRUE(
        AddTabAtIndex(0,
                      GURL(embedded_test_server()->GetURL(
                          "www.google.com", "/href_translate_test.html")),
                      ui::PAGE_TRANSITION_TYPED));
    ResetObserver();
    chrome_translate_client = GetChromeTranslateClient();
    WaitUntilLanguageDetermined(chrome_translate_client);

    // TODO(crbug.com/40200965): Migrate to better mechanism for testing around
    // language detection. All pages are detected as "fr".
    //
    // In the case of href translate, we don't actually care if the current
    // page is french, only that it loaded and whether href translate
    // updates the current language state or not.
    EXPECT_EQ("fr",
              chrome_translate_client->GetLanguageState().source_language());

    // Navigate to the French page by way of a link on the original page
    ResetObserver();
    chrome_translate_client->GetTranslateManager()
        ->GetLanguageState()
        ->SetSourceLanguage("");
    content::WebContents* web_contents =
        browser()->tab_strip_model()->GetWebContentsAt(0);

    const std::string click_link_js =
        "(function() { document.getElementById('test').click(); })();";
    ASSERT_TRUE(content::ExecJs(web_contents, click_link_js));

    // Detect language on the new page
    // TODO(crbug.com/40200965): Migrate to better mechanism for testing around
    // language detection. All pages are currently detected as "fr" due to the
    // override.
    WaitUntilLanguageDetermined(chrome_translate_client);
    EXPECT_EQ("fr",
              chrome_translate_client->GetLanguageState().source_language());
  }

 protected:
  // InProcessBrowserTest members.
  void SetUp() override { InProcessBrowserTest::SetUp(); }
  void SetUpOnMainThread() override {
    ResetObserver();
    error_type_ = TranslateErrors::NONE;

    host_resolver()->AddRule("www.google.com", "127.0.0.1");
    embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
        &TranslateManagerBrowserTest::HandleRequest, base::Unretained(this)));
    embedded_test_server()->StartAcceptingConnections();

    GetChromeTranslateClient()->GetTranslatePrefs()->ResetToDefaults();
  }
  void SetUpCommandLine(base::CommandLine* command_line) override {
    ASSERT_TRUE(embedded_test_server()->InitializeAndListen());

    InitFeatures();

    // Enable Experimental web platform features for HrefTranslate tests
    command_line->AppendSwitch(
        ::switches::kEnableExperimentalWebPlatformFeatures);

    command_line->AppendSwitchASCII(
        switches::kTranslateScriptURL,
        embedded_test_server()->GetURL("/mock_translate_script.js").spec());
    // TODO(crbug.com/40200965): Migrate to better mechanism for testing around
    // language detection.
    // All pages will have language detected as "fr". These tests are around
    // the manager logic so the language detection behavior should be
    // deterministic rather than relying on the page content.
    command_line->AppendSwitch(::switches::kOverrideLanguageDetection);
  }
  void TearDownOnMainThread() override {
    language_determined_waiter_.reset();

    EXPECT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());

    InProcessBrowserTest::TearDownOnMainThread();
  }

  void SetTranslateScript(const std::string& script) { script_ = script; }

  virtual void InitFeatures() {
    scoped_feature_list_.InitWithFeatures(
        {toast_features::kTranslateToast, features::kReadAnythingImprovedUi},
        {});
  }

 protected:
  base::test::ScopedFeatureList scoped_feature_list_;

 private:
  TranslateErrors error_type_;

  base::CallbackListSubscription error_subscription_;

  std::unique_ptr<TranslateWaiter> language_determined_waiter_;

  std::string script_;
};

// Tests that language detection returns a response.
// TODO(crbug.com/40200965): Migrate to better mechanism for testing around
// language detection. Seeding the TFLite model can racy/flaky on browsertests
// so we override the response.
IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest, PageLanguageDetection) {
  ChromeTranslateClient* chrome_translate_client = GetChromeTranslateClient();

  // Open a new tab with a page in French.
  ASSERT_TRUE(AddTabAtIndex(
      0, GURL(embedded_test_server()->GetURL("/french_page.html")),
      ui::PAGE_TRANSITION_TYPED));
  ResetObserver();
  chrome_translate_client = GetChromeTranslateClient();
  WaitUntilLanguageDetermined(chrome_translate_client);

  EXPECT_EQ("fr",
            chrome_translate_client->GetLanguageState().source_language());
}

// Tests that the language detection / HTML attribute override works correctly.
// For languages in the always-translate list, the detected language should
// override the HTML attribute. For all other languages, the HTML attribute
// should be used.
//
// TODO(crbug.com/40200965): Migrate to better mechanism for testing around
// language detection. All pages will return "fr" as the detected language.
//
// Disabled due to language detection always returning French. (See TODO)
IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest,
                       DISABLED_PageLanguageDetectionConflict) {
  // Open a new tab with a page in Korean with incorrect HTML language
  // attribute specified. The language attribute should not be overridden by the
  // language detection.
  ASSERT_TRUE(AddTabAtIndex(
      0,
      GURL(embedded_test_server()->GetURL("/korean_page_lang_conflict.html")),
      ui::PAGE_TRANSITION_TYPED));
  ResetObserver();
  ChromeTranslateClient* chrome_translate_client = GetChromeTranslateClient();
  WaitUntilLanguageDetermined(chrome_translate_client);

  EXPECT_EQ("en",
            chrome_translate_client->GetLanguageState().source_language());
}

// Tests that the language detection / HTML attribute override works correctly.
// For languages in the always-translate list, the detected language should
// override the HTML attribute. For all other languages, the HTML attribute
// should be used.
IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest,
                       PageLanguageDetectionConflictOverride) {
  // Open a new tab with a page in French with incorrect HTML language
  // attribute specified. The language attribute should be overridden by the
  // language detection.
  ASSERT_TRUE(AddTabAtIndex(
      0,
      GURL(embedded_test_server()->GetURL("/french_page_lang_conflict.html")),
      ui::PAGE_TRANSITION_TYPED));
  ResetObserver();
  ChromeTranslateClient* chrome_translate_client = GetChromeTranslateClient();
  WaitUntilLanguageDetermined(chrome_translate_client);

  EXPECT_EQ("fr",
            chrome_translate_client->GetLanguageState().source_language());
}

// Test that the translation was successful.
// TODO(crbug.com/518688037): Flaky on Windows.
#if BUILDFLAG(IS_WIN)
#define MAYBE_PageTranslationSuccess DISABLED_PageTranslationSuccess
#else
#define MAYBE_PageTranslationSuccess PageTranslationSuccess
#endif
IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest,
                       MAYBE_PageTranslationSuccess) {
  SetTranslateScript(kTestValidScript);

  ChromeTranslateClient* chrome_translate_client = GetChromeTranslateClient();
  base::HistogramTester histograms;

  // Open a new tab with a page in French.
  ASSERT_TRUE(AddTabAtIndex(
      0, GURL(embedded_test_server()->GetURL("/french_page.html")),
      ui::PAGE_TRANSITION_TYPED));
  ResetObserver();
  chrome_translate_client = GetChromeTranslateClient();
  WaitUntilLanguageDetermined(chrome_translate_client);

  EXPECT_EQ("fr",
            chrome_translate_client->GetLanguageState().source_language());

  // Translate the page through TranslateManager.
  TranslateManager* manager = chrome_translate_client->GetTranslateManager();

  LanguageState* language_state = manager->GetLanguageState();
  EXPECT_EQ(TranslationType::kUninitialized,
            language_state->translation_type());

  manager->TranslatePage(
      chrome_translate_client->GetLanguageState().source_language(), "en", true,
      TranslationType::kAutomaticTranslationByPref);

  EXPECT_EQ(TranslationType::kAutomaticTranslationByPref,
            language_state->translation_type());
  WaitUntilPageTranslated();

  EXPECT_FALSE(chrome_translate_client->GetLanguageState().translation_error());
  EXPECT_EQ(TranslateErrors::NONE, GetPageTranslatedResult());
  EXPECT_EQ(TranslationType::kUninitialized,
            language_state->translation_type());

  histograms.ExpectTotalCount("Translate.LanguageDetection.ContentLength", 1);
  histograms.ExpectBucketCount("Translate.LanguageDetection.ContentLength", 149,
                               1);
  histograms.ExpectTotalCount("Translate.LanguageDeterminedDuration", 1);
}

// Test that the translation was successful in an about:blank page.
// This is a regression test for https://crbug.com/41447825.
// Disabled due to flakiness: https://crbug.com/40762691.
IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest,
                       DISABLED_PageTranslationAboutBlank) {
  ASSERT_TRUE(AddTabAtIndex(
      0, GURL(embedded_test_server()->GetURL("/french_page.html")),
      ui::PAGE_TRANSITION_TYPED));
  ResetObserver();

  // Open a pop-up window and leave it at the initial about:blank URL.
  content::WebContentsAddedObserver popup_observer;
  ASSERT_TRUE(
      content::ExecJs(browser()->tab_strip_model()->GetActiveWebContents(),
                      "window.open('about:blank', 'popup')"));
  content::WebContents* popup = popup_observer.GetWebContents();

  // A round-trip to the renderer process helps avoid a race where the
  // browser-side translate structures are not yet ready for the translate call.
  EXPECT_EQ("ping", content::EvalJs(popup, "'ping'"));

  // Translate the about:blank page.
  ChromeTranslateClient* chrome_translate_client =
      ChromeTranslateClient::FromWebContents(popup);
  TranslateManager* manager = chrome_translate_client->GetTranslateManager();
  manager->TranslatePage("fr", "en", true);

  // Verify that the crash from https://crbug.com/41447825 didn't happen.
  EXPECT_EQ("still alive", content::EvalJs(popup, "'still alive'"));

  // Wait for translation to finish and verify it was successful.
  WaitUntilPageTranslated();
  EXPECT_FALSE(chrome_translate_client->GetLanguageState().translation_error());
  EXPECT_EQ(TranslateErrors::NONE, GetPageTranslatedResult());
}

// Test that hrefTranslate is propagating properly.
IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest, HrefTranslateSuccess) {
  base::HistogramTester histograms;
  GetChromeTranslateClient()
      ->GetTranslateManager()
      ->SetIgnoreMissingKeyForTesting(true);

  ClickFrenchHrefTranslateLinkOnGooglePage();

  // See that the page was translated automatically.
  WaitUntilPageTranslated();
  EXPECT_EQ("ja",
            GetChromeTranslateClient()->GetLanguageState().current_language());

  // The target shouldn't be added to accept languages.
  EXPECT_FALSE(AcceptLanguagesServiceFactory::GetForBrowserContext(
                   browser()->GetProfile())
                   ->IsAcceptLanguage("ja"));

  histograms.ExpectUniqueSample(
      kTranslateHrefHintStatusHistogram,
      static_cast<int>(
          TranslateBrowserMetrics::HrefTranslateStatus::kAutoTranslated),
      1);
}

// Test that hrefTranslate doesn't auto-translate if the originator of the
// navigation isn't a Google origin.
IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest,
                       HrefTranslateNotFromGoogle) {
  base::HistogramTester histograms;
  ChromeTranslateClient* chrome_translate_client = GetChromeTranslateClient();
  chrome_translate_client->GetTranslateManager()->SetIgnoreMissingKeyForTesting(
      true);
  SetTranslateScript(kTestValidScript);

  // Load a page with hrefTranslate tags.
  ASSERT_TRUE(AddTabAtIndex(
      0, GURL(embedded_test_server()->GetURL("/href_translate_test.html")),
      ui::PAGE_TRANSITION_TYPED));
  ResetObserver();
  chrome_translate_client = GetChromeTranslateClient();
  WaitUntilLanguageDetermined(chrome_translate_client);
  // TODO(crbug.com/40200965): Migrate to better mechanism for testing around
  // language detection. All pages will return "fr" as the detected language.
  EXPECT_EQ("fr",
            chrome_translate_client->GetLanguageState().source_language());

  // Navigate to the French page by way of a link on the original page.
  ResetObserver();
  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetWebContentsAt(0);

  const std::string click_link_js =
      "(function() { document.getElementById('test').click(); })();";
  ASSERT_TRUE(content::ExecJs(web_contents, click_link_js));

  // Detect language on the new page.
  // TODO(crbug.com/40200965): Migrate to better mechanism for testing around
  // language detection. Note: this only tests that the source language was
  // whatever the page was before. The real test is that the href translate
  // update did not occur, tested by AutoTranslateTo() below and the histograms.
  WaitUntilLanguageDetermined(chrome_translate_client);
  EXPECT_EQ("fr",
            chrome_translate_client->GetLanguageState().source_language());

  EXPECT_EQ("", chrome_translate_client->GetLanguageState().AutoTranslateTo());

  histograms.ExpectTotalCount(kTranslateHrefHintStatusHistogram, 0);
}

// Test that hrefTranslate with an unsupported language doesn't trigger.
IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest, HrefTranslateUnsupported) {
  base::HistogramTester histograms;
  ChromeTranslateClient* chrome_translate_client = GetChromeTranslateClient();
  chrome_translate_client->GetTranslateManager()->SetIgnoreMissingKeyForTesting(
      true);
  SetTranslateScript(kTestValidScript);

  // Load a page with hrefTranslate tags.
  ASSERT_TRUE(AddTabAtIndex(0,
                            GURL(embedded_test_server()->GetURL(
                                "www.google.com", "/href_translate_test.html")),
                            ui::PAGE_TRANSITION_TYPED));
  ResetObserver();
  chrome_translate_client = GetChromeTranslateClient();
  WaitUntilLanguageDetermined(chrome_translate_client);
  EXPECT_EQ("fr",
            chrome_translate_client->GetLanguageState().source_language());

  // Navigate to the French page by way of a link on the original page. This
  // link has the hrefTranslate attribute set to "unsupported", so it shouldn't
  // trigger translate.
  ResetObserver();
  chrome_translate_client->GetTranslateManager()
      ->GetLanguageState()
      ->SetSourceLanguage("");
  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetWebContentsAt(0);

  const std::string click_link_js =
      "(function() { "
      "document.getElementById('test-unsupported-language').click(); })();";
  ASSERT_TRUE(content::ExecJs(web_contents, click_link_js));

  // Detect language on the new page.
  // TODO(crbug.com/40200965): Migrate to better mechanism for testing around
  // language detection. Note: this only tests that the source language was
  // whatever the page was before. The real test is that the href translate
  // update did not occur, tested by AutoTranslateTo() below and the histograms.
  WaitUntilLanguageDetermined(chrome_translate_client);
  EXPECT_EQ("fr",
            chrome_translate_client->GetLanguageState().source_language());

  EXPECT_EQ("", chrome_translate_client->GetLanguageState().AutoTranslateTo());

  histograms.ExpectUniqueSample(
      kTranslateHrefHintStatusHistogram,
      static_cast<int>(TranslateBrowserMetrics::HrefTranslateStatus::
                           kNoUiShownNotAutoTranslated),
      1);
}

// Test an href translate link to a conflicted page.
IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest, HrefTranslateConflict) {
  base::HistogramTester histograms;
  ChromeTranslateClient* chrome_translate_client = GetChromeTranslateClient();
  chrome_translate_client->GetTranslateManager()->SetIgnoreMissingKeyForTesting(
      true);

  SetTranslateScript(kTestValidScript);

  // Load a page with hrefTranslate tags.
  ASSERT_TRUE(AddTabAtIndex(0,
                            GURL(embedded_test_server()->GetURL(
                                "www.google.com", "/href_translate_test.html")),
                            ui::PAGE_TRANSITION_TYPED));
  ResetObserver();
  chrome_translate_client = GetChromeTranslateClient();
  // TODO(crbug.com/40200965): Migrate to better mechanism for testing around
  // language detection. All pages will return "fr" as the detected language.
  WaitUntilLanguageDetermined(chrome_translate_client);
  EXPECT_EQ("fr",
            chrome_translate_client->GetLanguageState().source_language());

  // Navigate to the French page that thinks its in English by way of a link on
  // the original page.
  ResetObserver();
  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetWebContentsAt(0);

  const std::string click_link_js =
      "(function() { document.getElementById('test-conflict').click(); })();";
  ASSERT_TRUE(content::ExecJs(web_contents, click_link_js));

  // Detect language on the new page.
  WaitUntilLanguageDetermined(chrome_translate_client);
  EXPECT_EQ("fr",
            chrome_translate_client->GetLanguageState().source_language());

  // See that the page was translated automatically.
  WaitUntilPageTranslated();
  EXPECT_EQ("en",
            chrome_translate_client->GetLanguageState().current_language());

  histograms.ExpectUniqueSample(
      kTranslateHrefHintStatusHistogram,
      static_cast<int>(
          TranslateBrowserMetrics::HrefTranslateStatus::kAutoTranslated),
      1);
}

// Test an href translate link without an href lang for the landing page.
IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest, HrefTranslateNoHrefLang) {
  base::HistogramTester histograms;
  ChromeTranslateClient* chrome_translate_client = GetChromeTranslateClient();
  chrome_translate_client->GetTranslateManager()->SetIgnoreMissingKeyForTesting(
      true);
  SetTranslateScript(kTestValidScript);

  // Load a page with hrefTranslate tags.
  ASSERT_TRUE(AddTabAtIndex(0,
                            GURL(embedded_test_server()->GetURL(
                                "www.google.com", "/href_translate_test.html")),
                            ui::PAGE_TRANSITION_TYPED));
  ResetObserver();
  chrome_translate_client = GetChromeTranslateClient();
  // TODO(crbug.com/40200965): Migrate to better mechanism for testing around
  // language detection. All pages will return "fr" as the detected language.
  WaitUntilLanguageDetermined(chrome_translate_client);

  EXPECT_EQ("fr",
            chrome_translate_client->GetLanguageState().source_language());

  // Use a link with no hrefLang to navigate to a French page.
  ResetObserver();
  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetWebContentsAt(0);

  const std::string click_link_js =
      "(function() { document.getElementById('test-no-hrefLang').click(); "
      "})();";
  ASSERT_TRUE(content::ExecJs(web_contents, click_link_js));

  // Detect language on the new page
  WaitUntilLanguageDetermined(chrome_translate_client);
  EXPECT_EQ("fr",
            chrome_translate_client->GetLanguageState().source_language());

  // See that the page was translated automatically
  WaitUntilPageTranslated();
  EXPECT_EQ("en",
            chrome_translate_client->GetLanguageState().current_language());

  histograms.ExpectUniqueSample(
      kTranslateHrefHintStatusHistogram,
      static_cast<int>(
          TranslateBrowserMetrics::HrefTranslateStatus::kAutoTranslated),
      1);
}

// Test an href translate link that's overridden by the auto translate settings.
IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest,
                       HrefTranslateOverridenByAutoTranslate) {
  base::HistogramTester histograms;
  GetChromeTranslateClient()
      ->GetTranslateManager()
      ->SetIgnoreMissingKeyForTesting(true);

  // Before browsing: set auto translate from French to Chinese.
  GetChromeTranslateClient()
      ->GetTranslatePrefs()
      ->AddLanguagePairToAlwaysTranslateList("fr", "zh-CN");

  ClickFrenchHrefTranslateLinkOnGooglePage();

  // See that the page was translated automatically.
  WaitUntilPageTranslated();
  EXPECT_EQ("zh-CN",
            GetChromeTranslateClient()->GetLanguageState().current_language());

  histograms.ExpectUniqueSample(
      kTranslateHrefHintStatusHistogram,
      static_cast<int>(TranslateBrowserMetrics::HrefTranslateStatus::
                           kAutoTranslatedDifferentTargetLanguage),
      1);
}

// Test that hrefTranslate will auto translate if the target language is on the
// user's language blocklist, since that blocklist will be overridden.
IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest,
                       HrefTranslateLanguageBlocked) {
  base::HistogramTester histograms;
  GetChromeTranslateClient()
      ->GetTranslateManager()
      ->SetIgnoreMissingKeyForTesting(true);
  GetChromeTranslateClient()->GetTranslatePrefs()->AddToLanguageList(
      base::i18n::GetKnownLanguageTag("fr"), true);

  ClickFrenchHrefTranslateLinkOnGooglePage();

  // See that the page was translated automatically.
  WaitUntilPageTranslated();
  EXPECT_EQ("ja",
            GetChromeTranslateClient()->GetLanguageState().current_language());

  // The target shouldn't be added to accept languages.
  EXPECT_FALSE(AcceptLanguagesServiceFactory::GetForBrowserContext(
                   browser()->GetProfile())
                   ->IsAcceptLanguage("ja"));

  histograms.ExpectUniqueSample(
      kTranslateHrefHintStatusHistogram,
      static_cast<int>(
          TranslateBrowserMetrics::HrefTranslateStatus::kAutoTranslated),
      1);
}

// Test that hrefTranslate doesn't translate if the website is in the user's
// site blocklist.
IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest, HrefTranslateSiteBlocked) {
  base::HistogramTester histograms;
  GetChromeTranslateClient()
      ->GetTranslateManager()
      ->SetIgnoreMissingKeyForTesting(true);
  GetChromeTranslateClient()->GetTranslatePrefs()->AddSiteToNeverPromptList(
      "www.google.com");

  ClickFrenchHrefTranslateLinkOnGooglePage();

  // The page should not have been automatically translated.
  histograms.ExpectUniqueSample(
      kTranslateHrefHintStatusHistogram,
      static_cast<int>(TranslateBrowserMetrics::HrefTranslateStatus::
                           kNoUiShownNotAutoTranslated),
      1);
}

// Test that hrefTranslate doesn't translate if the language is in the user's
// language blocklist and the website is in the user's site blocklist.
IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest,
                       HrefTranslateLanguageAndSiteBlocked) {
  base::HistogramTester histograms;
  GetChromeTranslateClient()
      ->GetTranslateManager()
      ->SetIgnoreMissingKeyForTesting(true);
  GetChromeTranslateClient()->GetTranslatePrefs()->AddToLanguageList(
      base::i18n::GetKnownLanguageTag("fr"), true);
  GetChromeTranslateClient()->GetTranslatePrefs()->AddSiteToNeverPromptList(
      "www.google.com");

  ClickFrenchHrefTranslateLinkOnGooglePage();

  // The page should not have been automatically translated.
  histograms.ExpectUniqueSample(
      kTranslateHrefHintStatusHistogram,
      static_cast<int>(TranslateBrowserMetrics::HrefTranslateStatus::
                           kNoUiShownNotAutoTranslated),
      1);
}

// Test if there was an error during translation.
IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest, PageTranslationError) {
  SetTranslateScript(kTestValidScript);

  ChromeTranslateClient* chrome_translate_client = GetChromeTranslateClient();

  // Open a new tab with a page in French and translate to French to force an
  // error.
  // TODO(crbug.com/40200965): Migrate to better mechanism for testing around
  // language detection. All pages will return "fr" as the detected language.
  ASSERT_TRUE(AddTabAtIndex(
      0, GURL(embedded_test_server()->GetURL("/french_page.html")),
      ui::PAGE_TRANSITION_TYPED));
  ResetObserver();
  chrome_translate_client = GetChromeTranslateClient();
  WaitUntilLanguageDetermined(chrome_translate_client);

  // Translate the page through TranslateManager.
  TranslateManager* manager = chrome_translate_client->GetTranslateManager();
  manager->TranslatePage(
      chrome_translate_client->GetLanguageState().source_language(), "fr",
      true);

  WaitUntilPageTranslated();

  EXPECT_TRUE(chrome_translate_client->GetLanguageState().translation_error());
  EXPECT_EQ(TranslateErrors::TRANSLATION_ERROR, GetPageTranslatedResult());
}

// Test if there was an error during translate library initialization.
IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest,
                       PageTranslationInitializationError) {
  SetTranslateScript(kTestScriptInitializationError);

  ChromeTranslateClient* chrome_translate_client = GetChromeTranslateClient();

  // Open a new tab with a page in French.
  ASSERT_TRUE(AddTabAtIndex(
      0, GURL(embedded_test_server()->GetURL("/french_page.html")),
      ui::PAGE_TRANSITION_TYPED));
  ResetObserver();
  chrome_translate_client = GetChromeTranslateClient();
  WaitUntilLanguageDetermined(chrome_translate_client);

  EXPECT_EQ("fr",
            chrome_translate_client->GetLanguageState().source_language());

  // Translate the page through TranslateManager.
  TranslateManager* manager = chrome_translate_client->GetTranslateManager();
  manager->TranslatePage(
      chrome_translate_client->GetLanguageState().source_language(), "en",
      true);

  WaitUntilPageTranslated();

  EXPECT_TRUE(chrome_translate_client->GetLanguageState().translation_error());
  EXPECT_EQ(TranslateErrors::INITIALIZATION_ERROR, GetPageTranslatedResult());
}

// Test the checks translate lib never gets ready and throws timeout.
IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest,
                       PageTranslationTimeoutError) {
  SetTranslateScript(kTestScriptAvailableTimeout);

  ChromeTranslateClient* chrome_translate_client = GetChromeTranslateClient();

  // Open a new tab with a page in French.
  ASSERT_TRUE(AddTabAtIndex(
      0, GURL(embedded_test_server()->GetURL("/french_page.html")),
      ui::PAGE_TRANSITION_TYPED));
  ResetObserver();
  chrome_translate_client = GetChromeTranslateClient();
  WaitUntilLanguageDetermined(chrome_translate_client);

  EXPECT_EQ("fr",
            chrome_translate_client->GetLanguageState().source_language());

  // Translate the page through TranslateManager.
  TranslateManager* manager = chrome_translate_client->GetTranslateManager();
  manager->TranslatePage(
      chrome_translate_client->GetLanguageState().source_language(), "en",
      true);

  WaitUntilPageTranslated();

  EXPECT_TRUE(chrome_translate_client->GetLanguageState().translation_error());
  EXPECT_EQ(TranslateErrors::TRANSLATION_TIMEOUT, GetPageTranslatedResult());
}

// Test the checks if both source and target languages mentioned are identical.
IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest,
                       PageTranslationIdenticalLanguagesError) {
  SetTranslateScript(kTestScriptIdenticalLanguages);

  ChromeTranslateClient* chrome_translate_client = GetChromeTranslateClient();

  // Open a new tab with a page in French.
  ASSERT_TRUE(AddTabAtIndex(
      0, GURL(embedded_test_server()->GetURL("/french_page.html")),
      ui::PAGE_TRANSITION_TYPED));
  ResetObserver();
  chrome_translate_client = GetChromeTranslateClient();
  WaitUntilLanguageDetermined(chrome_translate_client);

  EXPECT_EQ("fr",
            chrome_translate_client->GetLanguageState().source_language());

  // Translate the page through TranslateManager.
  TranslateManager* manager = chrome_translate_client->GetTranslateManager();
  manager->TranslatePage("aa", "en", true);

  WaitUntilPageTranslated();

  EXPECT_TRUE(chrome_translate_client->GetLanguageState().translation_error());
  EXPECT_EQ(TranslateErrors::IDENTICAL_LANGUAGES, GetPageTranslatedResult());
}

// Test if there was an error during translatePage script execution.
IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest,
                       PageTranslationUnexpectedScriptError) {
  SetTranslateScript(kTestScriptUnexpectedScriptError);

  ChromeTranslateClient* chrome_translate_client = GetChromeTranslateClient();

  // Open a new tab with a page in French.
  ASSERT_TRUE(AddTabAtIndex(
      0, GURL(embedded_test_server()->GetURL("/french_page.html")),
      ui::PAGE_TRANSITION_TYPED));
  ResetObserver();
  chrome_translate_client = GetChromeTranslateClient();
  WaitUntilLanguageDetermined(chrome_translate_client);

  EXPECT_EQ("fr",
            chrome_translate_client->GetLanguageState().source_language());

  // Translate the page through TranslateManager.
  TranslateManager* manager = chrome_translate_client->GetTranslateManager();
  manager->TranslatePage(
      chrome_translate_client->GetLanguageState().source_language(), "en",
      true);

  WaitUntilPageTranslated();

  EXPECT_TRUE(chrome_translate_client->GetLanguageState().translation_error());
  EXPECT_EQ(TranslateErrors::UNEXPECTED_SCRIPT_ERROR,
            GetPageTranslatedResult());
}

// Test if securityOrigin mentioned in url is valid.
IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest,
                       PageTranslationBadOriginError) {
  SetTranslateScript(kTestScriptBadOrigin);

  ChromeTranslateClient* chrome_translate_client = GetChromeTranslateClient();

  ASSERT_TRUE(AddTabAtIndex(
      0, GURL(embedded_test_server()->GetURL("/french_page.html")),
      ui::PAGE_TRANSITION_TYPED));
  ResetObserver();
  chrome_translate_client = GetChromeTranslateClient();
  WaitUntilLanguageDetermined(chrome_translate_client);

  EXPECT_EQ("fr",
            chrome_translate_client->GetLanguageState().source_language());

  // Translate the page through TranslateManager.
  TranslateManager* manager = chrome_translate_client->GetTranslateManager();
  manager->TranslatePage(
      chrome_translate_client->GetLanguageState().source_language(), "en",
      true);

  WaitUntilPageTranslated();

  EXPECT_TRUE(chrome_translate_client->GetLanguageState().translation_error());
  EXPECT_EQ(TranslateErrors::BAD_ORIGIN, GetPageTranslatedResult());
}

// Test if there was an error during script load.
IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest,
                       PageTranslationScriptLoadError) {
  SetTranslateScript(kTestScriptLoadError);

  ChromeTranslateClient* chrome_translate_client = GetChromeTranslateClient();

  // Open a new tab with a page in French.
  ASSERT_TRUE(AddTabAtIndex(
      0, GURL(embedded_test_server()->GetURL("/french_page.html")),
      ui::PAGE_TRANSITION_TYPED));
  ResetObserver();
  chrome_translate_client = GetChromeTranslateClient();
  WaitUntilLanguageDetermined(chrome_translate_client);

  EXPECT_EQ("fr",
            chrome_translate_client->GetLanguageState().source_language());

  // Translate the page through TranslateManager.
  TranslateManager* manager = chrome_translate_client->GetTranslateManager();
  manager->TranslatePage(
      chrome_translate_client->GetLanguageState().source_language(), "en",
      true);

  WaitUntilPageTranslated();

  EXPECT_TRUE(chrome_translate_client->GetLanguageState().translation_error());
  EXPECT_EQ(TranslateErrors::SCRIPT_LOAD_ERROR, GetPageTranslatedResult());
}

// Test that session restore restores the translate infobar and other translate
// settings.
IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest,
                       PRE_TranslateSessionRestore) {
  SessionStartupPref pref(SessionStartupPref::LAST);
  SessionStartupPref::SetStartupPref(browser()->GetProfile(), pref);

  ChromeTranslateClient* chrome_translate_client = GetChromeTranslateClient();

  ResetObserver();

  GURL french_url = chrome_test_utils::GetTestUrl(
      base::FilePath(), base::FilePath(FILE_PATH_LITERAL("french_page.html")));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), french_url));

  WaitUntilLanguageDetermined(chrome_translate_client);
  EXPECT_EQ("fr",
            chrome_translate_client->GetLanguageState().source_language());
}

// TODO(crbug.com/518213587): Flaky on Windows
#if BUILDFLAG(IS_WIN)
#define MAYBE_TranslateSessionRestore DISABLED_TranslateSessionRestore
#else
#define MAYBE_TranslateSessionRestore TranslateSessionRestore
#endif
IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest,
                       MAYBE_TranslateSessionRestore) {
  // Make restored tab active to (on some platforms) initiate language
  // detection.
  browser()->tab_strip_model()->ActivateTabAt(
      0, TabStripUserGestureDetails(
             TabStripUserGestureDetails::GestureType::kOther));

  content::WebContents* restored_web_contents =
      browser()->tab_strip_model()->GetWebContentsAt(0);
  ChromeTranslateClient* restored_translate_client =
      ChromeTranslateClient::FromWebContents(restored_web_contents);
  ResetObserver();
  WaitUntilLanguageDetermined(restored_translate_client);
  EXPECT_EQ("fr",
            restored_translate_client->GetLanguageState().current_language());
}

// Test that hrefTranslate overrides manual translate
IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest,
                       HrefTranslateOverridesManualTranslate) {
  ChromeTranslateClient* chrome_translate_client = GetChromeTranslateClient();
  TranslateManager* manager = chrome_translate_client->GetTranslateManager();
  manager->SetIgnoreMissingKeyForTesting(true);

  // Set target language manually
  manager->SetPredefinedTargetLanguage(base::i18n::GetKnownLanguageTag("ru"));
  EXPECT_EQ("ru", chrome_translate_client->GetLanguageState()
                      .GetPredefinedTargetLanguage());

  SetTranslateScript(kTestValidScript);

  // Load a German page and detect it's language
  ASSERT_TRUE(AddTabAtIndex(0,
                            GURL(embedded_test_server()->GetURL(
                                "www.google.com", "/href_translate_test.html")),
                            ui::PAGE_TRANSITION_TYPED));
  ResetObserver();
  chrome_translate_client = GetChromeTranslateClient();
  WaitUntilLanguageDetermined(chrome_translate_client);

  EXPECT_EQ("fr",
            chrome_translate_client->GetLanguageState().source_language());

  // Navigate to the French page by way of a link on the original page
  ResetObserver();
  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetWebContentsAt(0);

  const std::string click_link_js =
      "(function() { document.getElementById('test').click(); })();";
  ASSERT_TRUE(content::ExecJs(web_contents, click_link_js));

  // Detect language on the new page
  WaitUntilLanguageDetermined(chrome_translate_client);
  EXPECT_EQ("fr",
            chrome_translate_client->GetLanguageState().source_language());

  // Href-translate to ja should override manual translate to ru.
  WaitUntilPageTranslated();
  EXPECT_EQ("ja",
            chrome_translate_client->GetLanguageState().current_language());
}

class TranslateManagerPrerenderBrowserTest
    : public TranslateManagerBrowserTest {
 public:
  TranslateManagerPrerenderBrowserTest()
      : prerender_helper_(base::BindRepeating(
            &TranslateManagerPrerenderBrowserTest::web_contents,
            base::Unretained(this))) {}

  void InitFeatures() override {
    scoped_feature_list_.InitAndEnableFeature(toast_features::kTranslateToast);
  }

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

 protected:
  content::test::PrerenderTestHelper prerender_helper_;
  base::test::ScopedFeatureList scoped_feature_list_;
};

// TODO(crbug.com/520628534): Flaky.
#if BUILDFLAG(IS_WIN)
#define MAYBE_SkipPrerenderPage DISABLED_SkipPrerenderPage
#else
#define MAYBE_SkipPrerenderPage SkipPrerenderPage
#endif
IN_PROC_BROWSER_TEST_F(TranslateManagerPrerenderBrowserTest,
                       MAYBE_SkipPrerenderPage) {
  SetTranslateScript(kTestValidScript);

  ChromeTranslateClient* chrome_translate_client = GetChromeTranslateClient();
  base::HistogramTester histograms;

  // Load a French page.
  prerender_helper_.NavigatePrimaryPage(
      embedded_test_server()->GetURL("/french_page.html"));
  ResetObserver();

  // Prerender a German page.
  prerender_helper_.AddPrerender(
      embedded_test_server()->GetURL("/german_page.html"));

  // The prerendering page should not affect the primary page.
  chrome_translate_client = GetChromeTranslateClient();
  WaitUntilLanguageDetermined(chrome_translate_client);
  EXPECT_EQ("fr",
            chrome_translate_client->GetLanguageState().source_language());
  TranslateManager* manager = chrome_translate_client->GetTranslateManager();
  manager->TranslatePage(
      chrome_translate_client->GetLanguageState().source_language(), "en",
      true);
  WaitUntilPageTranslated();
  EXPECT_FALSE(chrome_translate_client->GetLanguageState().translation_error());
  EXPECT_EQ(TranslateErrors::NONE, GetPageTranslatedResult());
  histograms.ExpectTotalCount("Translate.LanguageDetection.ContentLength", 1);
  histograms.ExpectTotalCount("Translate.LanguageDeterminedDuration", 1);

  // Activate the prerendered page.
  prerender_helper_.NavigatePrimaryPage(
      embedded_test_server()->GetURL("/german_page.html"));

  // Check that the translation service still works well.
  ResetObserver();
  chrome_translate_client = GetChromeTranslateClient();
  // TODO(crbug.com/40200965): Migrate to better mechanism for testing around
  // language detection.
  WaitUntilLanguageDetermined(chrome_translate_client);
  EXPECT_EQ("fr",
            chrome_translate_client->GetLanguageState().source_language());
  manager->TranslatePage(
      chrome_translate_client->GetLanguageState().source_language(), "en",
      true);
  WaitUntilPageTranslated();
  EXPECT_FALSE(chrome_translate_client->GetLanguageState().translation_error());
  EXPECT_EQ(TranslateErrors::NONE, GetPageTranslatedResult());
  histograms.ExpectTotalCount("Translate.LanguageDetection.ContentLength", 2);

  // Check noisy data was filtered out.
  histograms.ExpectTotalCount("Translate.LanguageDeterminedDuration", 1);
}

class TranslateManagerBackForwardCacheBrowserTest
    : public TranslateManagerBrowserTest {
 public:
  TranslateManagerBackForwardCacheBrowserTest() = default;
  ~TranslateManagerBackForwardCacheBrowserTest() override = default;

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

  void InitFeatures() override { SetupFeaturesAndParameters(); }

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

  content::RenderFrameHost* current_frame_host() {
    return web_contents()->GetPrimaryMainFrame();
  }

  GURL GetURL(const std::string& host) {
    return embedded_test_server()->GetURL(host, "/french_page.html");
  }

  void SetupFeaturesAndParameters() {
    scoped_feature_list_.InitWithFeaturesAndParameters(
        content::GetDefaultEnabledBackForwardCacheFeaturesForTesting(
            {{toast_features::kTranslateToast, {}}}),
        content::GetDefaultDisabledBackForwardCacheFeaturesForTesting(
            {// Entry to the cache can be slow during testing and cause
             // flakiness.
             ::features::kBackForwardCacheEntryTimeout}));

    vmodule_switches_.InitWithSwitches("back_forward_cache_impl=1");
  }

 private:
  logging::ScopedVmoduleSwitches vmodule_switches_;
};

// TODO(crbug.com/520500034): Re-enable once consistent failures are fixed.
#if BUILDFLAG(IS_WIN)
#define MAYBE_RestorePageTranslatorAfterBackForwardCache \
  DISABLED_RestorePageTranslatorAfterBackForwardCache
#else
#define MAYBE_RestorePageTranslatorAfterBackForwardCache \
  RestorePageTranslatorAfterBackForwardCache
#endif
IN_PROC_BROWSER_TEST_F(TranslateManagerBackForwardCacheBrowserTest,
                       MAYBE_RestorePageTranslatorAfterBackForwardCache) {
  SetTranslateScript(kTestValidScript);

  EXPECT_TRUE(content::NavigateToURL(web_contents(), GetURL("a.com")));
  content::RenderFrameHostWrapper rfh_a(current_frame_host());

  WaitUntilLanguageDetermined(GetChromeTranslateClient());

  EXPECT_EQ("fr",
            GetChromeTranslateClient()->GetLanguageState().source_language());
  ResetObserver();
  EXPECT_TRUE(content::NavigateToURL(web_contents(), GetURL("b.com")));
  content::RenderFrameHostWrapper rfh_b(current_frame_host());
  WaitUntilLanguageDetermined(GetChromeTranslateClient());

  // A is frozen in the BackForwardCache.
  EXPECT_EQ(rfh_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);
  ResetObserver();

  // 3) Navigate back.
  ASSERT_TRUE(content::HistoryGoBack(web_contents()));
  WaitUntilLanguageDetermined(GetChromeTranslateClient());
  ResetObserver();

  // A is restored, B is stored.
  EXPECT_EQ(rfh_b->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);

  // Translate the page through TranslateManager.
  TranslateManager* manager = GetChromeTranslateClient()->GetTranslateManager();

  LanguageState* language_state = manager->GetLanguageState();
  EXPECT_EQ(TranslationType::kUninitialized,
            language_state->translation_type());

  manager->TranslatePage(
      GetChromeTranslateClient()->GetLanguageState().source_language(), "en",
      true, TranslationType::kAutomaticTranslationByPref);

  EXPECT_EQ(TranslationType::kAutomaticTranslationByPref,
            language_state->translation_type());
  WaitUntilPageTranslated();
}

IN_PROC_BROWSER_TEST_F(TranslateManagerBackForwardCacheBrowserTest,
                       RestoreOriginStateAfterCache) {
  SetTranslateScript(kTestValidScript);

  ChromeTranslateClient* chrome_translate_client = GetChromeTranslateClient();

  EXPECT_TRUE(content::NavigateToURL(web_contents(), GetURL("a.com")));
  content::RenderFrameHostWrapper rfh_a(current_frame_host());

  ResetObserver();
  chrome_translate_client = GetChromeTranslateClient();
  WaitUntilLanguageDetermined(chrome_translate_client);

  TranslateManager* manager = chrome_translate_client->GetTranslateManager();

  manager->TranslatePage(
      chrome_translate_client->GetLanguageState().source_language(), "en", true,
      TranslationType::kAutomaticTranslationByPref);

  WaitUntilPageTranslated();

  {
    // Intentionally check translated state directly in blink. Without any
    // proxies.
    ASSERT_EQ(true, content::EvalJs(rfh_a.get(), "cr.googleTranslate.finished",
                                    content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
                                    ISOLATED_WORLD_ID_TRANSLATE));
    ASSERT_EQ(0, content::EvalJs(rfh_a.get(), "cr.googleTranslate.errorCode",
                                 content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
                                 ISOLATED_WORLD_ID_TRANSLATE));
  }

  ResetObserver();
  EXPECT_TRUE(content::NavigateToURL(web_contents(), GetURL("b.com")));

  // A is frozen in the BackForwardCache.
  EXPECT_EQ(rfh_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);
  WaitUntilLanguageDetermined(GetChromeTranslateClient());

  // Navigate back.
  ResetObserver();
  ASSERT_TRUE(content::HistoryGoBack(web_contents()));
  WaitUntilLanguageDetermined(GetChromeTranslateClient());

  EXPECT_EQ(rfh_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kActive);

  {
    // Intentionally check restored state directly in blink. Without any
    // proxies.
    ASSERT_EQ(true, content::EvalJs(rfh_a.get(), "window.isTranslationRestored",
                                    content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
                                    ISOLATED_WORLD_ID_TRANSLATE));
    ASSERT_EQ(0, content::EvalJs(rfh_a.get(), "cr.googleTranslate.errorCode",
                                 content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
                                 ISOLATED_WORLD_ID_TRANSLATE));
  }
}

IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest, AutoTranslateToastFlow) {
  TranslateManager::SetIgnoreMissingKeyForTesting(true);
  ChromeTranslateClient* chrome_translate_client = GetChromeTranslateClient();

  // Enable auto-translate for fr/en.
  chrome_translate_client->GetTranslatePrefs()
      ->AddLanguagePairToAlwaysTranslateList("fr", "en");

  SetTranslateScript(kTestValidScript);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/french_page.html")));

  WaitUntilPageTranslated();

  // Verify the Toast is shown when auto-translate is enabled and the Bubble is
  // NOT shown
  EXPECT_TRUE(IsToastShown(ToastId::kTranslate));
  EXPECT_FALSE(IsTranslateBubbleShown());

  // Click Undo button on Toast which should revert the translation and show the
  // Bubble.
  ClickToastActionButton();

  EXPECT_TRUE(IsTranslateBubbleShown());
  EXPECT_EQ(true, content::EvalJs(
                      browser()->tab_strip_model()->GetActiveWebContents(),
                      "window.isTranslationRestored",
                      content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
                      ISOLATED_WORLD_ID_TRANSLATE));

  // Navigate to another page, Toast should show again since we clicked Undo.
  content::TestNavigationObserver nav_observer(
      browser()->tab_strip_model()->GetActiveWebContents());
  ASSERT_TRUE(
      content::ExecJs(browser()->tab_strip_model()->GetActiveWebContents(),
                      "window.location.href = 'french_page.html?2';"));
  nav_observer.Wait();

  WaitUntilPageTranslated();

  EXPECT_TRUE(IsToastShown(ToastId::kTranslate));
  EXPECT_FALSE(IsTranslateBubbleShown());

  // Attempt to dismiss the Toast.
  if (GetToastController()->GetToastCloseTimerForTesting()->IsRunning()) {
    GetToastController()->GetToastCloseTimerForTesting()->FireNow();
  }

  // Navigate to yet another page, Toast should NOT show this time because we
  // didn't undo the previous one.
  content::TestNavigationObserver nav_observer2(
      browser()->tab_strip_model()->GetActiveWebContents());
  ASSERT_TRUE(
      content::ExecJs(browser()->tab_strip_model()->GetActiveWebContents(),
                      "window.location.href = 'french_page.html?3';"));
  nav_observer2.Wait();

  WaitUntilPageTranslated();

  EXPECT_FALSE(IsToastShown(ToastId::kTranslate));
  EXPECT_FALSE(IsTranslateBubbleShown());
}

IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest, NoAutoTranslateNoToast) {
  TranslateManager::SetIgnoreMissingKeyForTesting(true);
  SetTranslateScript(kTestValidScript);

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/french_page.html")));

  ChromeTranslateClient* chrome_translate_client = GetChromeTranslateClient();
  WaitUntilLanguageDetermined(chrome_translate_client);

  EXPECT_FALSE(IsToastShown(ToastId::kTranslate));
  EXPECT_TRUE(IsTranslateBubbleShown());
}

#if !BUILDFLAG(IS_ANDROID)
IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest, IsReadingModeOpen) {
  ChromeTranslateClient* chrome_translate_client = GetChromeTranslateClient();
  EXPECT_FALSE(chrome_translate_client->IsReadingModeOpen());

  // 1. Test Side Panel Mode
  // Show reading mode side panel
  SidePanelUI* side_panel_ui = browser()->GetFeatures().side_panel_ui();
  ASSERT_TRUE(side_panel_ui);
  side_panel_ui->Show(SidePanelEntryId::kReadAnything);
  EXPECT_TRUE(base::test::RunUntil(
      [&]() { return chrome_translate_client->IsReadingModeOpen(); }));

  // Hide it
  side_panel_ui->Close();
  EXPECT_TRUE(base::test::RunUntil(
      [&]() { return !chrome_translate_client->IsReadingModeOpen(); }));

  // 2. Test Immersive Mode
  tabs::TabInterface* tab = browser()->tab_strip_model()->GetActiveTab();
  ASSERT_TRUE(tab);
  auto* controller = ReadAnythingController::From(tab);
  ASSERT_TRUE(controller);

  // Show immersive Reading Mode UI
  controller->ShowImmersiveUI(
      ReadAnythingController::ReadAnythingOpenTrigger::kOmniboxChip);
  EXPECT_TRUE(base::test::RunUntil(
      [&]() { return chrome_translate_client->IsReadingModeOpen(); }));

  // Close immersive Reading Mode UI
  controller->CloseImmersiveUI(ReadAnythingCloseReason::kClosedByUser);
  EXPECT_TRUE(base::test::RunUntil(
      [&]() { return !chrome_translate_client->IsReadingModeOpen(); }));
}

IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest,
                       TriggerPdfTranslationOpensSidePanel) {
  ChromeTranslateClient* chrome_translate_client = GetChromeTranslateClient();
  EXPECT_FALSE(chrome_translate_client->IsReadingModeOpen());

  chrome_translate_client->TriggerPdfTranslation();
  EXPECT_TRUE(base::test::RunUntil(
      [&]() { return chrome_translate_client->IsReadingModeOpen(); }));
}
#endif

}  // namespace
}  // namespace translate
