|  | // Copyright 2022 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/content/android/translate_message.h" | 
|  |  | 
|  | #include <algorithm> | 
|  |  | 
|  | #include "base/android/jni_android.h" | 
|  | #include "base/android/jni_array.h" | 
|  | #include "base/android/jni_string.h" | 
|  | #include "base/strings/utf_string_conversions.h" | 
|  | #include "base/test/metrics/histogram_tester.h" | 
|  | #include "components/language/core/browser/language_model.h" | 
|  | #include "components/language/core/browser/language_prefs.h" | 
|  | #include "components/language/core/browser/pref_names.h" | 
|  | #include "components/language/core/common/language_experiments.h" | 
|  | #include "components/language_detection/core/constants.h" | 
|  | #include "components/messages/android/message_enums.h" | 
|  | #include "components/pref_registry/pref_registry_syncable.h" | 
|  | #include "components/sync_preferences/testing_pref_service_syncable.h" | 
|  | #include "components/translate/core/browser/language_state.h" | 
|  | #include "components/translate/core/browser/mock_translate_client.h" | 
|  | #include "components/translate/core/browser/mock_translate_driver.h" | 
|  | #include "components/translate/core/browser/mock_translate_ranker.h" | 
|  | #include "components/translate/core/browser/translate_client.h" | 
|  | #include "components/translate/core/browser/translate_download_manager.h" | 
|  | #include "components/translate/core/browser/translate_manager.h" | 
|  | #include "components/translate/core/browser/translate_pref_names.h" | 
|  | #include "components/translate/core/browser/translate_prefs.h" | 
|  | #include "components/translate/core/browser/translate_ui_delegate.h" | 
|  | #include "components/translate/core/browser/translate_ui_languages_manager.h" | 
|  | #include "components/translate/core/common/translate_metrics.h" | 
|  | #include "components/translate/core/common/translate_util.h" | 
|  | #include "testing/gmock/include/gmock/gmock.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  | #include "url/gurl.h" | 
|  |  | 
|  | namespace translate { | 
|  | namespace { | 
|  |  | 
|  | using ::testing::_; | 
|  | using ::testing::DoAll; | 
|  | using ::testing::InvokeWithoutArgs; | 
|  | using ::testing::Mock; | 
|  | using testing::MockTranslateClient; | 
|  | using testing::MockTranslateRanker; | 
|  | using ::testing::Return; | 
|  | using ::testing::Test; | 
|  | using ::testing::Truly; | 
|  |  | 
|  | class TestBridge : public TranslateMessage::Bridge { | 
|  | public: | 
|  | ~TestBridge() override; | 
|  |  | 
|  | base::WeakPtr<TestBridge> GetWeakPtr() { | 
|  | return weak_ptr_factory_.GetWeakPtr(); | 
|  | } | 
|  |  | 
|  | MOCK_METHOD(bool, | 
|  | CreateTranslateMessage, | 
|  | (JNIEnv*, content::WebContents*, TranslateMessage*, jint), | 
|  | (override)); | 
|  |  | 
|  | MOCK_METHOD(void, | 
|  | ShowTranslateError, | 
|  | (JNIEnv*, content::WebContents*), | 
|  | (override)); | 
|  |  | 
|  | MOCK_METHOD(void, | 
|  | ShowMessage, | 
|  | (JNIEnv*, | 
|  | base::android::ScopedJavaLocalRef<jstring>, | 
|  | base::android::ScopedJavaLocalRef<jstring>, | 
|  | base::android::ScopedJavaLocalRef<jstring>, | 
|  | jboolean), | 
|  | (override)); | 
|  |  | 
|  | MOCK_METHOD(base::android::ScopedJavaLocalRef<jobjectArray>, | 
|  | ConstructMenuItemArray, | 
|  | (JNIEnv*, | 
|  | base::android::ScopedJavaLocalRef<jobjectArray>, | 
|  | base::android::ScopedJavaLocalRef<jobjectArray>, | 
|  | base::android::ScopedJavaLocalRef<jbooleanArray>, | 
|  | base::android::ScopedJavaLocalRef<jintArray>, | 
|  | base::android::ScopedJavaLocalRef<jobjectArray>), | 
|  | (override)); | 
|  |  | 
|  | MOCK_METHOD(void, ClearNativePointer, (JNIEnv*), (override)); | 
|  | MOCK_METHOD(void, Dismiss, (JNIEnv*), (override)); | 
|  |  | 
|  | private: | 
|  | base::WeakPtrFactory<TestBridge> weak_ptr_factory_{this}; | 
|  | }; | 
|  |  | 
|  | TestBridge::~TestBridge() = default; | 
|  |  | 
|  | class TestLanguageModel : public language::LanguageModel { | 
|  | std::vector<LanguageDetails> GetLanguages() override { | 
|  | return {LanguageDetails("en", 1.0)}; | 
|  | } | 
|  | }; | 
|  |  | 
|  | class TestTranslateDriver : public testing::MockTranslateDriver { | 
|  | public: | 
|  | ~TestTranslateDriver() override; | 
|  |  | 
|  | MOCK_METHOD(void, RevertTranslation, (int), (override)); | 
|  | MOCK_METHOD(bool, IsIncognito, (), (const override)); | 
|  | MOCK_METHOD(bool, HasCurrentPage, (), (const override)); | 
|  |  | 
|  | const GURL& GetLastCommittedURL() const override { return url_; } | 
|  | void SetLastCommittedURL(GURL url) { url_ = std::move(url); } | 
|  |  | 
|  | private: | 
|  | GURL url_; | 
|  | }; | 
|  |  | 
|  | TestTranslateDriver::~TestTranslateDriver() = default; | 
|  |  | 
|  | class JavaStringMatcher { | 
|  | public: | 
|  | JavaStringMatcher(JNIEnv* env, std::u16string expected) | 
|  | : env_(env), expected_(std::move(expected)) {} | 
|  |  | 
|  | bool operator()( | 
|  | const base::android::ScopedJavaLocalRef<jstring>& actual) const { | 
|  | return base::android::ConvertJavaStringToUTF16(env_, actual) == expected_; | 
|  | } | 
|  |  | 
|  | private: | 
|  | raw_ptr<JNIEnv> env_; | 
|  | std::u16string expected_; | 
|  | }; | 
|  |  | 
|  | bool IsJavaStringNonNull( | 
|  | const base::android::ScopedJavaLocalRef<jstring>& java_string) { | 
|  | return static_cast<bool>(java_string); | 
|  | } | 
|  | bool IsJavaStringNull( | 
|  | const base::android::ScopedJavaLocalRef<jstring>& java_string) { | 
|  | return !java_string; | 
|  | } | 
|  |  | 
|  | base::android::ScopedJavaLocalRef<jobjectArray> CreateTestJobjectArray( | 
|  | JNIEnv* env) { | 
|  | std::string test_strings[] = {"test", "opaque", "jobjectArray"}; | 
|  | return base::android::ToJavaArrayOfStrings(env, test_strings); | 
|  | } | 
|  |  | 
|  | constexpr int kDefaultDismissalDurationSeconds = 10; | 
|  |  | 
|  | struct SecondaryMenuItem { | 
|  | TranslateMessage::OverflowMenuItemId id; | 
|  | bool has_checkmark; | 
|  | std::string language_code; | 
|  | }; | 
|  |  | 
|  | constexpr const char kInfobarEventHistogram[] = | 
|  | "Translate.CompactInfobar.Event"; | 
|  |  | 
|  | class TranslateMessageTest : public ::testing::Test { | 
|  | public: | 
|  | TranslateMessageTest() { | 
|  | ON_CALL(driver_, IsIncognito()).WillByDefault(Return(false)); | 
|  | ON_CALL(driver_, HasCurrentPage()).WillByDefault(Return(true)); | 
|  | driver_.SetLastCommittedURL(GURL("http://www.example.com/")); | 
|  | } | 
|  |  | 
|  | void ShowBeforeTranslationMessage(JNIEnv* env, | 
|  | const std::string& source_language_code, | 
|  | const std::string& target_language_code) { | 
|  | EXPECT_CALL(*bridge_, | 
|  | ShowMessage(env, | 
|  | /*title=*/Truly(IsJavaStringNonNull), | 
|  | /*description=*/Truly(IsJavaStringNonNull), | 
|  | /*primary_button_text=*/Truly(IsJavaStringNonNull), | 
|  | /*has_overflow_menu=*/true)); | 
|  |  | 
|  | translate_message_->ShowTranslateStep(TRANSLATE_STEP_BEFORE_TRANSLATE, | 
|  | source_language_code, | 
|  | target_language_code); | 
|  | } | 
|  |  | 
|  | void ExpectTranslationInProgress(JNIEnv* env, | 
|  | const std::string& source_language_code, | 
|  | const std::string& target_language_code) { | 
|  | EXPECT_CALL(*bridge_, | 
|  | ShowMessage(env, | 
|  | /*title=*/Truly(IsJavaStringNonNull), | 
|  | /*description=*/Truly(IsJavaStringNonNull), | 
|  | /*primary_button_text=*/Truly(IsJavaStringNull), | 
|  | /*has_overflow_menu=*/true)); | 
|  |  | 
|  | EXPECT_CALL( | 
|  | *client_, | 
|  | ShowTranslateUI(TRANSLATE_STEP_TRANSLATING, source_language_code, | 
|  | target_language_code, TranslateErrors::NONE, false)) | 
|  | .WillOnce(DoAll(InvokeWithoutArgs([this, source_language_code, | 
|  | target_language_code]() { | 
|  | translate_message_->ShowTranslateStep( | 
|  | TRANSLATE_STEP_TRANSLATING, source_language_code, | 
|  | target_language_code); | 
|  | }), | 
|  | Return(true))); | 
|  | } | 
|  |  | 
|  | void FinishTranslation(JNIEnv* env, | 
|  | const std::string& source_language_code, | 
|  | const std::string& target_language_code) { | 
|  | EXPECT_CALL(*bridge_, | 
|  | ShowMessage(env, | 
|  | /*title=*/Truly(IsJavaStringNonNull), | 
|  | /*description=*/Truly(IsJavaStringNonNull), | 
|  | /*primary_button_text=*/Truly(IsJavaStringNonNull), | 
|  | /*has_overflow_menu=*/true)); | 
|  |  | 
|  | EXPECT_CALL( | 
|  | *client_, | 
|  | ShowTranslateUI(TRANSLATE_STEP_AFTER_TRANSLATE, source_language_code, | 
|  | target_language_code, TranslateErrors::NONE, false)) | 
|  | .WillOnce(DoAll(InvokeWithoutArgs([this, source_language_code, | 
|  | target_language_code]() { | 
|  | translate_message_->ShowTranslateStep( | 
|  | TRANSLATE_STEP_AFTER_TRANSLATE, | 
|  | source_language_code, target_language_code); | 
|  | }), | 
|  | Return(true))); | 
|  |  | 
|  | manager_->PageTranslated(source_language_code, target_language_code, | 
|  | TranslateErrors::NONE); | 
|  | } | 
|  |  | 
|  | void ExpectTranslationReverts(JNIEnv* env, | 
|  | const std::string& source_language_code, | 
|  | const std::string& target_language_code) { | 
|  | EXPECT_CALL(driver_, RevertTranslation(_)); | 
|  |  | 
|  | EXPECT_CALL(*bridge_, | 
|  | ShowMessage(env, | 
|  | /*title=*/Truly(IsJavaStringNonNull), | 
|  | /*description=*/Truly(IsJavaStringNonNull), | 
|  | /*primary_button_text=*/Truly(IsJavaStringNonNull), | 
|  | /*has_overflow_menu=*/true)); | 
|  | } | 
|  |  | 
|  | void TranslateThenRevertThenDismiss(JNIEnv* env, | 
|  | const std::string& source_language_code, | 
|  | const std::string& target_language_code) { | 
|  | ShowBeforeTranslationMessage(env, source_language_code, | 
|  | target_language_code); | 
|  |  | 
|  | ExpectTranslationInProgress(env, source_language_code, | 
|  | target_language_code); | 
|  | translate_message_->HandlePrimaryAction(env); | 
|  |  | 
|  | FinishTranslation(env, source_language_code, target_language_code); | 
|  |  | 
|  | ExpectTranslationReverts(env, source_language_code, target_language_code); | 
|  | translate_message_->HandlePrimaryAction(env); | 
|  |  | 
|  | // Simulate a dismissal triggered from the Java side. | 
|  | EXPECT_CALL(*bridge_, ClearNativePointer(env)); | 
|  | int prev_on_dismiss_callback_called_count = | 
|  | on_dismiss_callback_called_count_; | 
|  | translate_message_->HandleDismiss( | 
|  | env, static_cast<jint>(messages::DismissReason::TIMER)); | 
|  |  | 
|  | // The on-dismiss callback should have been called. | 
|  | EXPECT_EQ(prev_on_dismiss_callback_called_count + 1, | 
|  | on_dismiss_callback_called_count_); | 
|  | } | 
|  |  | 
|  | void ExpectConstructMenuItemArray( | 
|  | JNIEnv* env, | 
|  | std::vector<SecondaryMenuItem> expected_items, | 
|  | base::android::ScopedJavaLocalRef<jobjectArray> return_value = nullptr) { | 
|  | EXPECT_CALL( | 
|  | *bridge_, | 
|  | ConstructMenuItemArray( | 
|  | env, | 
|  | /*titles=*/_, | 
|  | /*subtitles=*/_, | 
|  | /*has_checkmarks=*/ | 
|  | Truly([env, expected_items]( | 
|  | const base::android::ScopedJavaLocalRef<jbooleanArray>& | 
|  | actual) { | 
|  | std::vector<bool> actual_vector; | 
|  | base::android::JavaBooleanArrayToBoolVector(env, actual, | 
|  | &actual_vector); | 
|  | return std::ranges::equal(expected_items, actual_vector, | 
|  | std::equal_to<>(), | 
|  | &SecondaryMenuItem::has_checkmark); | 
|  | }), | 
|  | /*overflow_menu_item_ids=*/ | 
|  | Truly([env, expected_items]( | 
|  | const base::android::ScopedJavaLocalRef<jintArray>& | 
|  | actual) { | 
|  | std::vector<int> actual_vector; | 
|  | base::android::JavaIntArrayToIntVector(env, actual, | 
|  | &actual_vector); | 
|  | return std::ranges::equal(expected_items, actual_vector, | 
|  | std::equal_to<>(), | 
|  | [](const SecondaryMenuItem& item) { | 
|  | return static_cast<int>(item.id); | 
|  | }); | 
|  | }), | 
|  | /*language_codes=*/ | 
|  | Truly([env, expected_items]( | 
|  | const base::android::ScopedJavaLocalRef<jobjectArray>& | 
|  | actual) { | 
|  | std::vector<std::string> actual_vector; | 
|  | base::android::AppendJavaStringArrayToStringVector( | 
|  | env, actual, &actual_vector); | 
|  | return std::ranges::equal(expected_items, actual_vector, | 
|  | std::equal_to<>(), | 
|  | &SecondaryMenuItem::language_code); | 
|  | }))) | 
|  | .WillOnce(Return(return_value)); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | void SetUp() override { | 
|  | ::testing::Test::SetUp(); | 
|  |  | 
|  | pref_service_ = | 
|  | std::make_unique<sync_preferences::TestingPrefServiceSyncable>(); | 
|  | language::LanguagePrefs::RegisterProfilePrefs(pref_service_->registry()); | 
|  | pref_service_->SetString(testing::accept_languages_prefs, std::string()); | 
|  | pref_service_->SetString(language::prefs::kAcceptLanguages, std::string()); | 
|  | pref_service_->registry()->RegisterBooleanPref( | 
|  | prefs::kOfferTranslateEnabled, true); | 
|  | TranslatePrefs::RegisterProfilePrefs(pref_service_->registry()); | 
|  | ranker_ = std::make_unique<MockTranslateRanker>(); | 
|  | client_ = | 
|  | std::make_unique<MockTranslateClient>(&driver_, pref_service_.get()); | 
|  | manager_ = std::make_unique<TranslateManager>(client_.get(), ranker_.get(), | 
|  | language_model_.get()); | 
|  | manager_->GetLanguageState()->set_translation_declined(false); | 
|  | TranslateDownloadManager::GetInstance()->set_application_locale("en"); | 
|  | translate_prefs_ = client_->GetTranslatePrefs(); | 
|  |  | 
|  | auto owned_bridge = std::make_unique<TestBridge>(); | 
|  | bridge_ = owned_bridge->GetWeakPtr(); | 
|  |  | 
|  | translate_message_ = std::make_unique<TranslateMessage>( | 
|  | /*web_contents=*/nullptr, manager_->GetWeakPtr(), | 
|  | base::BindRepeating( | 
|  | [](int* on_dismiss_callback_called_count) { | 
|  | ++(*on_dismiss_callback_called_count); | 
|  | }, | 
|  | &on_dismiss_callback_called_count_), | 
|  | std::move(owned_bridge)); | 
|  | } | 
|  |  | 
|  | TestTranslateDriver driver_; | 
|  | std::unique_ptr<sync_preferences::TestingPrefServiceSyncable> pref_service_; | 
|  | std::unique_ptr<MockTranslateClient> client_; | 
|  | std::unique_ptr<TestLanguageModel> language_model_; | 
|  | std::unique_ptr<TranslateManager> manager_; | 
|  | std::unique_ptr<MockTranslateRanker> ranker_; | 
|  | std::unique_ptr<TranslatePrefs> translate_prefs_; | 
|  |  | 
|  | base::WeakPtr<TestBridge> bridge_; | 
|  | std::unique_ptr<TranslateMessage> translate_message_; | 
|  | int on_dismiss_callback_called_count_ = 0; | 
|  | }; | 
|  |  | 
|  | TEST_F(TranslateMessageTest, TranslateAndRevert) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ON_CALL(*client_, IsTranslatableURL(_)).WillByDefault(Return(true)); | 
|  |  | 
|  | { | 
|  | base::HistogramTester histogram_tester; | 
|  | ShowBeforeTranslationMessage(env, "fr", "en"); | 
|  | histogram_tester.ExpectUniqueSample(kInfobarEventHistogram, | 
|  | InfobarEvent::INFOBAR_IMPRESSION, 1); | 
|  | } | 
|  |  | 
|  | { | 
|  | base::HistogramTester histogram_tester; | 
|  | ExpectTranslationInProgress(env, "fr", "en"); | 
|  | translate_message_->HandlePrimaryAction(env); | 
|  | histogram_tester.ExpectUniqueSample( | 
|  | kInfobarEventHistogram, InfobarEvent::INFOBAR_TARGET_TAB_TRANSLATE, 1); | 
|  | } | 
|  |  | 
|  | { | 
|  | base::HistogramTester histogram_tester; | 
|  | FinishTranslation(env, "fr", "en"); | 
|  | histogram_tester.ExpectTotalCount(kInfobarEventHistogram, 0); | 
|  | } | 
|  |  | 
|  | { | 
|  | base::HistogramTester histogram_tester; | 
|  | ExpectTranslationReverts(env, "fr", "en"); | 
|  | translate_message_->HandlePrimaryAction(env); | 
|  | histogram_tester.ExpectUniqueSample(kInfobarEventHistogram, | 
|  | InfobarEvent::INFOBAR_REVERT, 1); | 
|  | } | 
|  |  | 
|  | // Simulate a dismissal triggered by the Java side. | 
|  | EXPECT_CALL(*bridge_, ClearNativePointer(env)); | 
|  | translate_message_->HandleDismiss( | 
|  | env, static_cast<jint>(messages::DismissReason::TIMER)); | 
|  | EXPECT_EQ(1, on_dismiss_callback_called_count_); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, TranslateAndRevertMultipleTimes) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ON_CALL(*client_, IsTranslatableURL(_)).WillByDefault(Return(true)); | 
|  | TranslateThenRevertThenDismiss(env, "fr", "en"); | 
|  |  | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | TranslateThenRevertThenDismiss(env, "de", "es"); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, ShowErrorBeforeTranslation) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | ON_CALL(*client_, ShowTranslateUI(_, _, _, _, _)).WillByDefault(Return(true)); | 
|  | manager_->GetLanguageState()->SetSourceLanguage("fr"); | 
|  | manager_->GetLanguageState()->SetCurrentLanguage("fr"); | 
|  |  | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | EXPECT_CALL(*bridge_, ShowTranslateError(env, _)); | 
|  | EXPECT_CALL(*bridge_, | 
|  | ShowMessage(env, | 
|  | /*title=*/Truly(IsJavaStringNonNull), | 
|  | /*description=*/Truly(IsJavaStringNonNull), | 
|  | /*primary_button_text=*/Truly(IsJavaStringNonNull), | 
|  | /*has_overflow_menu=*/true)); | 
|  |  | 
|  | translate_message_->ShowTranslateStep(TRANSLATE_STEP_TRANSLATE_ERROR, "fr", | 
|  | "en"); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, ShowErrorAfterTranslation) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | ON_CALL(*client_, ShowTranslateUI(_, _, _, _, _)).WillByDefault(Return(true)); | 
|  | manager_->GetLanguageState()->SetSourceLanguage("fr"); | 
|  | manager_->GetLanguageState()->SetCurrentLanguage("en"); | 
|  |  | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | EXPECT_CALL(*bridge_, ShowTranslateError(env, _)); | 
|  | EXPECT_CALL(*bridge_, | 
|  | ShowMessage(env, | 
|  | /*title=*/Truly(IsJavaStringNonNull), | 
|  | /*description=*/Truly(IsJavaStringNonNull), | 
|  | /*primary_button_text=*/Truly(IsJavaStringNonNull), | 
|  | /*has_overflow_menu=*/true)); | 
|  |  | 
|  | translate_message_->ShowTranslateStep(TRANSLATE_STEP_TRANSLATE_ERROR, "fr", | 
|  | "en"); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, DismissMessageOnDestruction) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  |  | 
|  | ShowBeforeTranslationMessage(env, "fr", "en"); | 
|  |  | 
|  | EXPECT_CALL(*bridge_, Dismiss(env)) | 
|  | .WillOnce(InvokeWithoutArgs([env, message = translate_message_.get()]() { | 
|  | message->HandleDismiss( | 
|  | env, | 
|  | static_cast<jint>(messages::DismissReason::DISMISSED_BY_FEATURE)); | 
|  | })); | 
|  | EXPECT_CALL(*bridge_, ClearNativePointer(env)); | 
|  |  | 
|  | translate_message_.reset(); | 
|  |  | 
|  | // By design, the on_dismiss_callback_ will not be called, since it's cleared | 
|  | // in the TranslateMessage destructor before dismissing the message, in order | 
|  | // to prevent a use-after-free bug. | 
|  | EXPECT_EQ(0, on_dismiss_callback_called_count_); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, ShowOverflowMenu) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  |  | 
|  | ShowBeforeTranslationMessage(env, "fr", "en"); | 
|  |  | 
|  | ExpectConstructMenuItemArray( | 
|  | env, | 
|  | std::vector<SecondaryMenuItem>( | 
|  | {{TranslateMessage::OverflowMenuItemId::kChangeTargetLanguage, false, | 
|  | std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kInvalid, false, | 
|  | std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId:: | 
|  | kToggleAlwaysTranslateLanguage, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kToggleNeverTranslateLanguage, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kToggleNeverTranslateSite, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kChangeSourceLanguage, false, | 
|  | std::string()}})); | 
|  |  | 
|  | base::HistogramTester histogram_tester; | 
|  | translate_message_->BuildOverflowMenu(env); | 
|  | histogram_tester.ExpectUniqueSample(kInfobarEventHistogram, | 
|  | InfobarEvent::INFOBAR_OPTIONS, 1); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, OverflowMenuToggleAlwaysTranslateLanguage) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ON_CALL(*client_, IsTranslatableURL(_)).WillByDefault(Return(true)); | 
|  |  | 
|  | ShowBeforeTranslationMessage(env, "fr", "en"); | 
|  |  | 
|  | ExpectConstructMenuItemArray( | 
|  | env, | 
|  | std::vector<SecondaryMenuItem>( | 
|  | {{TranslateMessage::OverflowMenuItemId::kChangeTargetLanguage, false, | 
|  | std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kInvalid, false, | 
|  | std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId:: | 
|  | kToggleAlwaysTranslateLanguage, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kToggleNeverTranslateLanguage, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kToggleNeverTranslateSite, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kChangeSourceLanguage, false, | 
|  | std::string()}})); | 
|  |  | 
|  | translate_message_->BuildOverflowMenu(env); | 
|  | EXPECT_FALSE( | 
|  | translate_prefs_->IsLanguagePairOnAlwaysTranslateList("fr", "en")); | 
|  |  | 
|  | { | 
|  | base::HistogramTester histogram_tester; | 
|  | // Toggle "Always translate pages in <language>" to on. | 
|  | ExpectTranslationInProgress(env, "fr", "en"); | 
|  | EXPECT_FALSE(translate_message_->HandleSecondaryMenuItemClicked( | 
|  | env, | 
|  | static_cast<int>(TranslateMessage::OverflowMenuItemId:: | 
|  | kToggleAlwaysTranslateLanguage), | 
|  | base::android::ConvertUTF8ToJavaString(env, std::string()), | 
|  | static_cast<jboolean>(false))); | 
|  | histogram_tester.ExpectUniqueSample( | 
|  | kInfobarEventHistogram, InfobarEvent::INFOBAR_ALWAYS_TRANSLATE, 1); | 
|  | } | 
|  |  | 
|  | EXPECT_TRUE( | 
|  | translate_prefs_->IsLanguagePairOnAlwaysTranslateList("fr", "en")); | 
|  |  | 
|  | FinishTranslation(env, "fr", "en"); | 
|  |  | 
|  | ExpectConstructMenuItemArray( | 
|  | env, | 
|  | std::vector<SecondaryMenuItem>( | 
|  | {{TranslateMessage::OverflowMenuItemId::kChangeTargetLanguage, false, | 
|  | std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kInvalid, false, | 
|  | std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId:: | 
|  | kToggleAlwaysTranslateLanguage, | 
|  | true, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kToggleNeverTranslateLanguage, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kToggleNeverTranslateSite, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kChangeSourceLanguage, false, | 
|  | std::string()}})); | 
|  |  | 
|  | translate_message_->BuildOverflowMenu(env); | 
|  |  | 
|  | { | 
|  | base::HistogramTester histogram_tester; | 
|  | // Toggle "Always translate pages in <language>" to off. | 
|  | EXPECT_FALSE(translate_message_->HandleSecondaryMenuItemClicked( | 
|  | env, | 
|  | static_cast<int>(TranslateMessage::OverflowMenuItemId:: | 
|  | kToggleAlwaysTranslateLanguage), | 
|  | base::android::ConvertUTF8ToJavaString(env, std::string()), | 
|  | static_cast<jboolean>(true))); | 
|  | histogram_tester.ExpectUniqueSample( | 
|  | kInfobarEventHistogram, InfobarEvent::INFOBAR_ALWAYS_TRANSLATE_UNDO, 1); | 
|  | } | 
|  |  | 
|  | EXPECT_FALSE( | 
|  | translate_prefs_->IsLanguagePairOnAlwaysTranslateList("fr", "en")); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, OverflowMenuToggleNeverTranslateLanguage) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | // Begin from a translated page. | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ON_CALL(*client_, IsTranslatableURL(_)).WillByDefault(Return(true)); | 
|  | ShowBeforeTranslationMessage(env, "fr", "en"); | 
|  | ExpectTranslationInProgress(env, "fr", "en"); | 
|  | translate_message_->HandlePrimaryAction(env); | 
|  | FinishTranslation(env, "fr", "en"); | 
|  |  | 
|  | ExpectConstructMenuItemArray( | 
|  | env, | 
|  | std::vector<SecondaryMenuItem>( | 
|  | {{TranslateMessage::OverflowMenuItemId::kChangeTargetLanguage, false, | 
|  | std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kInvalid, false, | 
|  | std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId:: | 
|  | kToggleAlwaysTranslateLanguage, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kToggleNeverTranslateLanguage, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kToggleNeverTranslateSite, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kChangeSourceLanguage, false, | 
|  | std::string()}})); | 
|  |  | 
|  | translate_message_->BuildOverflowMenu(env); | 
|  | EXPECT_FALSE(translate_prefs_->IsBlockedLanguage("fr")); | 
|  |  | 
|  | { | 
|  | base::HistogramTester histogram_tester; | 
|  | // Toggle "Never translate pages in <language>" to on. | 
|  | ExpectTranslationReverts(env, "fr", "en"); | 
|  | EXPECT_FALSE(translate_message_->HandleSecondaryMenuItemClicked( | 
|  | env, | 
|  | static_cast<int>(TranslateMessage::OverflowMenuItemId:: | 
|  | kToggleNeverTranslateLanguage), | 
|  | base::android::ConvertUTF8ToJavaString(env, std::string()), | 
|  | static_cast<jboolean>(false))); | 
|  | histogram_tester.ExpectUniqueSample( | 
|  | kInfobarEventHistogram, InfobarEvent::INFOBAR_NEVER_TRANSLATE, 1); | 
|  | } | 
|  |  | 
|  | EXPECT_TRUE(translate_prefs_->IsBlockedLanguage("fr")); | 
|  |  | 
|  | ExpectConstructMenuItemArray( | 
|  | env, | 
|  | std::vector<SecondaryMenuItem>( | 
|  | {{TranslateMessage::OverflowMenuItemId::kChangeTargetLanguage, false, | 
|  | std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kInvalid, false, | 
|  | std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId:: | 
|  | kToggleAlwaysTranslateLanguage, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kToggleNeverTranslateLanguage, | 
|  | true, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kToggleNeverTranslateSite, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kChangeSourceLanguage, false, | 
|  | std::string()}})); | 
|  |  | 
|  | translate_message_->BuildOverflowMenu(env); | 
|  |  | 
|  | { | 
|  | base::HistogramTester histogram_tester; | 
|  | // Toggle "Never translate pages in <language>" to off. | 
|  | EXPECT_FALSE(translate_message_->HandleSecondaryMenuItemClicked( | 
|  | env, | 
|  | static_cast<int>(TranslateMessage::OverflowMenuItemId:: | 
|  | kToggleNeverTranslateLanguage), | 
|  | base::android::ConvertUTF8ToJavaString(env, std::string()), | 
|  | static_cast<jboolean>(true))); | 
|  | histogram_tester.ExpectUniqueSample( | 
|  | kInfobarEventHistogram, InfobarEvent::INFOBAR_NEVER_TRANSLATE_UNDO, 1); | 
|  | } | 
|  |  | 
|  | EXPECT_FALSE(translate_prefs_->IsBlockedLanguage("fr")); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, OverflowMenuToggleNeverTranslateSite) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | // Begin from a translated page. | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ON_CALL(*client_, IsTranslatableURL(_)).WillByDefault(Return(true)); | 
|  | ShowBeforeTranslationMessage(env, "fr", "en"); | 
|  | ExpectTranslationInProgress(env, "fr", "en"); | 
|  | translate_message_->HandlePrimaryAction(env); | 
|  | FinishTranslation(env, "fr", "en"); | 
|  |  | 
|  | ExpectConstructMenuItemArray( | 
|  | env, | 
|  | std::vector<SecondaryMenuItem>( | 
|  | {{TranslateMessage::OverflowMenuItemId::kChangeTargetLanguage, false, | 
|  | std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kInvalid, false, | 
|  | std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId:: | 
|  | kToggleAlwaysTranslateLanguage, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kToggleNeverTranslateLanguage, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kToggleNeverTranslateSite, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kChangeSourceLanguage, false, | 
|  | std::string()}})); | 
|  |  | 
|  | translate_message_->BuildOverflowMenu(env); | 
|  | EXPECT_FALSE(translate_prefs_->IsSiteOnNeverPromptList( | 
|  | driver_.GetLastCommittedURL().HostNoBracketsPiece())); | 
|  |  | 
|  | { | 
|  | base::HistogramTester histogram_tester; | 
|  | // Toggle "Never translate this site" to on. | 
|  | ExpectTranslationReverts(env, "fr", "en"); | 
|  | EXPECT_FALSE(translate_message_->HandleSecondaryMenuItemClicked( | 
|  | env, | 
|  | static_cast<int>( | 
|  | TranslateMessage::OverflowMenuItemId::kToggleNeverTranslateSite), | 
|  | base::android::ConvertUTF8ToJavaString(env, std::string()), | 
|  | static_cast<jboolean>(false))); | 
|  | histogram_tester.ExpectUniqueSample( | 
|  | kInfobarEventHistogram, InfobarEvent::INFOBAR_NEVER_TRANSLATE_SITE, 1); | 
|  | } | 
|  |  | 
|  | EXPECT_TRUE(translate_prefs_->IsSiteOnNeverPromptList( | 
|  | driver_.GetLastCommittedURL().HostNoBracketsPiece())); | 
|  |  | 
|  | ExpectConstructMenuItemArray( | 
|  | env, | 
|  | std::vector<SecondaryMenuItem>( | 
|  | {{TranslateMessage::OverflowMenuItemId::kChangeTargetLanguage, false, | 
|  | std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kInvalid, false, | 
|  | std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId:: | 
|  | kToggleAlwaysTranslateLanguage, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kToggleNeverTranslateLanguage, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kToggleNeverTranslateSite, | 
|  | true, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kChangeSourceLanguage, false, | 
|  | std::string()}})); | 
|  |  | 
|  | translate_message_->BuildOverflowMenu(env); | 
|  |  | 
|  | { | 
|  | base::HistogramTester histogram_tester; | 
|  | // Toggle "Never translate this site" to off. | 
|  | EXPECT_FALSE(translate_message_->HandleSecondaryMenuItemClicked( | 
|  | env, | 
|  | static_cast<int>( | 
|  | TranslateMessage::OverflowMenuItemId::kToggleNeverTranslateSite), | 
|  | base::android::ConvertUTF8ToJavaString(env, std::string()), | 
|  | static_cast<jboolean>(true))); | 
|  | histogram_tester.ExpectUniqueSample( | 
|  | kInfobarEventHistogram, InfobarEvent::INFOBAR_NEVER_TRANSLATE_SITE_UNDO, | 
|  | 1); | 
|  | } | 
|  |  | 
|  | EXPECT_FALSE(translate_prefs_->IsSiteOnNeverPromptList( | 
|  | driver_.GetLastCommittedURL().HostNoBracketsPiece())); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, OverflowMenuChangeSourceLanguage) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | translate_prefs_->AddToLanguageList("en", true); | 
|  | translate_prefs_->AddToLanguageList("es", true); | 
|  | translate_prefs_->AddToLanguageList("de", true); | 
|  |  | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ON_CALL(*client_, IsTranslatableURL(_)).WillByDefault(Return(true)); | 
|  | ShowBeforeTranslationMessage(env, "fr", "en"); | 
|  |  | 
|  | ExpectConstructMenuItemArray( | 
|  | env, | 
|  | std::vector<SecondaryMenuItem>( | 
|  | {{TranslateMessage::OverflowMenuItemId::kChangeTargetLanguage, false, | 
|  | std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kInvalid, false, | 
|  | std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId:: | 
|  | kToggleAlwaysTranslateLanguage, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kToggleNeverTranslateLanguage, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kToggleNeverTranslateSite, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kChangeSourceLanguage, false, | 
|  | std::string()}}), | 
|  | CreateTestJobjectArray(env)); | 
|  |  | 
|  | EXPECT_TRUE(translate_message_->BuildOverflowMenu(env)); | 
|  |  | 
|  | std::vector<SecondaryMenuItem> menu_items; | 
|  | TranslateUIDelegate ui_delegate(manager_->GetWeakPtr(), "fr", "en"); | 
|  | for (size_t i = 0U; | 
|  | i < ui_delegate.translate_ui_languages_manager()->GetNumberOfLanguages(); | 
|  | ++i) { | 
|  | std::string language_code = | 
|  | ui_delegate.translate_ui_languages_manager()->GetLanguageCodeAt(i); | 
|  | if (language_code == "fr") | 
|  | continue; | 
|  | menu_items.emplace_back(SecondaryMenuItem{ | 
|  | TranslateMessage::OverflowMenuItemId::kChangeSourceLanguage, false, | 
|  | std::move(language_code)}); | 
|  | } | 
|  |  | 
|  | { | 
|  | base::HistogramTester histogram_tester; | 
|  | // Click the kChangeSourceLanguage option in the overflow menu, which should | 
|  | // return a list of language picker menu items. | 
|  | ExpectConstructMenuItemArray(env, menu_items, CreateTestJobjectArray(env)); | 
|  | EXPECT_TRUE(translate_message_->HandleSecondaryMenuItemClicked( | 
|  | env, | 
|  | static_cast<int>( | 
|  | TranslateMessage::OverflowMenuItemId::kChangeSourceLanguage), | 
|  | base::android::ConvertUTF8ToJavaString(env, std::string()), | 
|  | static_cast<jboolean>(false))); | 
|  | histogram_tester.ExpectUniqueSample(kInfobarEventHistogram, | 
|  | InfobarEvent::INFOBAR_PAGE_NOT_IN, 1); | 
|  | } | 
|  |  | 
|  | // Clicking a language should kick off a translation. | 
|  | ExpectTranslationInProgress(env, "de", "en"); | 
|  | EXPECT_FALSE(translate_message_->HandleSecondaryMenuItemClicked( | 
|  | env, | 
|  | static_cast<int>( | 
|  | TranslateMessage::OverflowMenuItemId::kChangeSourceLanguage), | 
|  | base::android::ConvertUTF8ToJavaString(env, "de"), | 
|  | static_cast<jboolean>(false))); | 
|  |  | 
|  | FinishTranslation(env, "de", "en"); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, | 
|  | OverflowMenuChangeTargetLanguageWithContentLanguages) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | translate_prefs_->AddToLanguageList("en", true); | 
|  | translate_prefs_->AddToLanguageList("es", true); | 
|  | translate_prefs_->AddToLanguageList("de", true); | 
|  |  | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ON_CALL(*client_, IsTranslatableURL(_)).WillByDefault(Return(true)); | 
|  | ShowBeforeTranslationMessage(env, "fr", "en"); | 
|  |  | 
|  | ExpectConstructMenuItemArray( | 
|  | env, | 
|  | std::vector<SecondaryMenuItem>( | 
|  | {{TranslateMessage::OverflowMenuItemId::kChangeTargetLanguage, false, | 
|  | std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kInvalid, false, | 
|  | std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId:: | 
|  | kToggleAlwaysTranslateLanguage, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kToggleNeverTranslateLanguage, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kToggleNeverTranslateSite, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kChangeSourceLanguage, false, | 
|  | std::string()}}), | 
|  | CreateTestJobjectArray(env)); | 
|  |  | 
|  | EXPECT_TRUE(translate_message_->BuildOverflowMenu(env)); | 
|  |  | 
|  | std::vector<SecondaryMenuItem> menu_items = { | 
|  | {TranslateMessage::OverflowMenuItemId::kChangeTargetLanguage, false, | 
|  | "es"}, | 
|  | {TranslateMessage::OverflowMenuItemId::kChangeTargetLanguage, false, | 
|  | "de"}, | 
|  | // Divider after content languages. | 
|  | {TranslateMessage::OverflowMenuItemId::kInvalid, false, std::string()}, | 
|  | }; | 
|  |  | 
|  | TranslateUIDelegate ui_delegate(manager_->GetWeakPtr(), "fr", "en"); | 
|  | for (size_t i = 0U; | 
|  | i < ui_delegate.translate_ui_languages_manager()->GetNumberOfLanguages(); | 
|  | ++i) { | 
|  | std::string language_code = | 
|  | ui_delegate.translate_ui_languages_manager()->GetLanguageCodeAt(i); | 
|  | if (language_code == "en" || | 
|  | language_code == language_detection::kUnknownLanguageCode) { | 
|  | continue; | 
|  | } | 
|  | menu_items.emplace_back(SecondaryMenuItem{ | 
|  | TranslateMessage::OverflowMenuItemId::kChangeTargetLanguage, false, | 
|  | std::move(language_code)}); | 
|  | } | 
|  |  | 
|  | // Click the kChangeTargetLanguage option in the overflow menu, which should | 
|  | // return a list of language picker menu items. | 
|  | ExpectConstructMenuItemArray(env, menu_items, CreateTestJobjectArray(env)); | 
|  | EXPECT_TRUE(translate_message_->HandleSecondaryMenuItemClicked( | 
|  | env, | 
|  | static_cast<int>( | 
|  | TranslateMessage::OverflowMenuItemId::kChangeTargetLanguage), | 
|  | base::android::ConvertUTF8ToJavaString(env, std::string()), | 
|  | static_cast<jboolean>(false))); | 
|  |  | 
|  | // Clicking a language should kick off a translation. | 
|  | ExpectTranslationInProgress(env, "fr", "de"); | 
|  | EXPECT_FALSE(translate_message_->HandleSecondaryMenuItemClicked( | 
|  | env, | 
|  | static_cast<int>( | 
|  | TranslateMessage::OverflowMenuItemId::kChangeTargetLanguage), | 
|  | base::android::ConvertUTF8ToJavaString(env, "de"), | 
|  | static_cast<jboolean>(false))); | 
|  |  | 
|  | FinishTranslation(env, "fr", "de"); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, OverflowMenuIncognito) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  | ON_CALL(driver_, IsIncognito()).WillByDefault(Return(true)); | 
|  |  | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ShowBeforeTranslationMessage(env, "fr", "en"); | 
|  |  | 
|  | // Doesn't include the kToggleAlwaysTranslateLanguage option. | 
|  | ExpectConstructMenuItemArray( | 
|  | env, | 
|  | std::vector<SecondaryMenuItem>( | 
|  | {{TranslateMessage::OverflowMenuItemId::kChangeTargetLanguage, false, | 
|  | std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kInvalid, false, | 
|  | std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kToggleNeverTranslateLanguage, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kToggleNeverTranslateSite, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kChangeSourceLanguage, false, | 
|  | std::string()}})); | 
|  |  | 
|  | translate_message_->BuildOverflowMenu(env); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, OverflowMenuEmptyUrl) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  | driver_.SetLastCommittedURL(GURL()); | 
|  |  | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ShowBeforeTranslationMessage(env, "fr", "en"); | 
|  |  | 
|  | // Doesn't include the kToggleNeverTranslateSite option. | 
|  | ExpectConstructMenuItemArray( | 
|  | env, | 
|  | std::vector<SecondaryMenuItem>( | 
|  | {{TranslateMessage::OverflowMenuItemId::kChangeTargetLanguage, false, | 
|  | std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kInvalid, false, | 
|  | std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId:: | 
|  | kToggleAlwaysTranslateLanguage, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kToggleNeverTranslateLanguage, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kChangeSourceLanguage, false, | 
|  | std::string()}})); | 
|  |  | 
|  | translate_message_->BuildOverflowMenu(env); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, OverflowMenuUnknownSourceLanguage) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ShowBeforeTranslationMessage(env, language_detection::kUnknownLanguageCode, | 
|  | "en"); | 
|  |  | 
|  | // Doesn't include the kToggleAlwaysTranslateLanguage option or the | 
|  | // kToggleNeverTranslateLanguage option. | 
|  | ExpectConstructMenuItemArray( | 
|  | env, | 
|  | std::vector<SecondaryMenuItem>( | 
|  | {{TranslateMessage::OverflowMenuItemId::kChangeTargetLanguage, false, | 
|  | std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kInvalid, false, | 
|  | std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kToggleNeverTranslateSite, | 
|  | false, std::string()}, | 
|  | {TranslateMessage::OverflowMenuItemId::kChangeSourceLanguage, false, | 
|  | std::string()}})); | 
|  |  | 
|  | translate_message_->BuildOverflowMenu(env); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, CreateTranslateMessageFails) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(false)); | 
|  |  | 
|  | // ShowMessage should not be called after CreateTranslateMessage fails. | 
|  | EXPECT_CALL(*bridge_, ShowMessage(_, _, _, _, _)).Times(0); | 
|  |  | 
|  | translate_message_->ShowTranslateStep(TRANSLATE_STEP_BEFORE_TRANSLATE, "fr", | 
|  | "en"); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, CreateTranslateMessageFailsThenSucceeds) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | // The first call to CreateTranslateMessage will fail. | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(false)); | 
|  |  | 
|  | // ShowMessage should not be called after CreateTranslateMessage fails. | 
|  | EXPECT_CALL(*bridge_, ShowMessage(_, _, _, _, _)).Times(0); | 
|  |  | 
|  | translate_message_->ShowTranslateStep(TRANSLATE_STEP_BEFORE_TRANSLATE, "fr", | 
|  | "en"); | 
|  |  | 
|  | // The second call to CreateTranslateMessage will succeed, and test that the | 
|  | // whole process of translating, reverting, and dismissing works properly | 
|  | // afterwards. | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ON_CALL(*client_, IsTranslatableURL(_)).WillByDefault(Return(true)); | 
|  | TranslateThenRevertThenDismiss(env, "fr", "en"); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, CreateTranslateMessageSucceedsThenFails) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | // The first call to CreateTranslateMessage will succeed. | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ON_CALL(*client_, IsTranslatableURL(_)).WillByDefault(Return(true)); | 
|  | TranslateThenRevertThenDismiss(env, "fr", "en"); | 
|  |  | 
|  | // The second call to CreateTranslateMessage will fail. | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(false)); | 
|  |  | 
|  | // ShowMessage should not be called after CreateTranslateMessage fails. | 
|  | EXPECT_CALL(*bridge_, ShowMessage(_, _, _, _, _)).Times(0); | 
|  | translate_message_->ShowTranslateStep(TRANSLATE_STEP_BEFORE_TRANSLATE, "fr", | 
|  | "en"); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, TranslationDismissedInProgressByTimer) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | ScopedDictPrefUpdate(pref_service_.get(), | 
|  | TranslatePrefs::kPrefTranslateDeniedCount) | 
|  | ->Set("fr", 100); | 
|  | ScopedDictPrefUpdate(pref_service_.get(), | 
|  | TranslatePrefs::kPrefTranslateIgnoredCount) | 
|  | ->Set("fr", 100); | 
|  |  | 
|  | // Show the translate message and click "Translate". | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ON_CALL(*client_, IsTranslatableURL(_)).WillByDefault(Return(true)); | 
|  | ShowBeforeTranslationMessage(env, "fr", "en"); | 
|  | ExpectTranslationInProgress(env, "fr", "en"); | 
|  | translate_message_->HandlePrimaryAction(env); | 
|  |  | 
|  | EXPECT_EQ(1, translate_prefs_->GetTranslationAcceptedCount("fr")); | 
|  | EXPECT_EQ(0, translate_prefs_->GetTranslationDeniedCount("fr")); | 
|  | EXPECT_EQ(0, translate_prefs_->GetTranslationIgnoredCount("fr")); | 
|  |  | 
|  | // Dismiss the translate message while translation is still in-progress. | 
|  | EXPECT_CALL(*bridge_, ClearNativePointer(env)); | 
|  | translate_message_->HandleDismiss( | 
|  | env, static_cast<jint>(messages::DismissReason::TIMER)); | 
|  | EXPECT_EQ(1, on_dismiss_callback_called_count_); | 
|  |  | 
|  | EXPECT_EQ(1, translate_prefs_->GetTranslationAcceptedCount("fr")); | 
|  | EXPECT_EQ(0, translate_prefs_->GetTranslationDeniedCount("fr")); | 
|  | EXPECT_EQ(0, translate_prefs_->GetTranslationIgnoredCount("fr")); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, TranslationDismissedInProgressByGesture) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | ScopedDictPrefUpdate(pref_service_.get(), | 
|  | TranslatePrefs::kPrefTranslateDeniedCount) | 
|  | ->Set("fr", 100); | 
|  | ScopedDictPrefUpdate(pref_service_.get(), | 
|  | TranslatePrefs::kPrefTranslateIgnoredCount) | 
|  | ->Set("fr", 100); | 
|  |  | 
|  | // Show the translate message and click "Translate". | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ON_CALL(*client_, IsTranslatableURL(_)).WillByDefault(Return(true)); | 
|  | ShowBeforeTranslationMessage(env, "fr", "en"); | 
|  | ExpectTranslationInProgress(env, "fr", "en"); | 
|  | translate_message_->HandlePrimaryAction(env); | 
|  |  | 
|  | EXPECT_EQ(1, translate_prefs_->GetTranslationAcceptedCount("fr")); | 
|  | EXPECT_EQ(0, translate_prefs_->GetTranslationDeniedCount("fr")); | 
|  | EXPECT_EQ(0, translate_prefs_->GetTranslationIgnoredCount("fr")); | 
|  |  | 
|  | // Dismiss the translate message while translation is still in-progress. | 
|  | EXPECT_CALL(*bridge_, ClearNativePointer(env)); | 
|  | translate_message_->HandleDismiss( | 
|  | env, static_cast<jint>(messages::DismissReason::GESTURE)); | 
|  | EXPECT_EQ(1, on_dismiss_callback_called_count_); | 
|  |  | 
|  | EXPECT_EQ(1, translate_prefs_->GetTranslationAcceptedCount("fr")); | 
|  | EXPECT_EQ(0, translate_prefs_->GetTranslationDeniedCount("fr")); | 
|  | EXPECT_EQ(0, translate_prefs_->GetTranslationIgnoredCount("fr")); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, TranslationIgnored) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | ScopedDictPrefUpdate(pref_service_.get(), | 
|  | TranslatePrefs::kPrefTranslateAcceptedCount) | 
|  | ->Set("fr", 100); | 
|  | ScopedDictPrefUpdate(pref_service_.get(), | 
|  | TranslatePrefs::kPrefTranslateDeniedCount) | 
|  | ->Set("fr", 100); | 
|  |  | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ShowBeforeTranslationMessage(env, "fr", "en"); | 
|  |  | 
|  | base::HistogramTester histogram_tester; | 
|  |  | 
|  | EXPECT_CALL(*bridge_, ClearNativePointer(env)); | 
|  | translate_message_->HandleDismiss( | 
|  | env, static_cast<jint>(messages::DismissReason::TIMER)); | 
|  |  | 
|  | histogram_tester.ExpectUniqueSample(kInfobarEventHistogram, | 
|  | InfobarEvent::INFOBAR_DECLINE, 1); | 
|  |  | 
|  | EXPECT_EQ(1, on_dismiss_callback_called_count_); | 
|  |  | 
|  | EXPECT_EQ(100, translate_prefs_->GetTranslationAcceptedCount("fr")); | 
|  | EXPECT_EQ(100, translate_prefs_->GetTranslationDeniedCount("fr")); | 
|  |  | 
|  | EXPECT_EQ(1, translate_prefs_->GetTranslationIgnoredCount("fr")); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, TranslationNotIgnoredBecauseOverflowMenuOpened) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | // Show the translate message and simulate the overflow menu being opened. | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ShowBeforeTranslationMessage(env, "fr", "en"); | 
|  |  | 
|  | EXPECT_CALL(*bridge_, ConstructMenuItemArray(env, _, _, _, _, _)) | 
|  | .WillOnce(Return(nullptr)); | 
|  | translate_message_->BuildOverflowMenu(env); | 
|  |  | 
|  | // Dismiss the translate message. | 
|  | EXPECT_CALL(*bridge_, ClearNativePointer(env)); | 
|  | translate_message_->HandleDismiss( | 
|  | env, static_cast<jint>(messages::DismissReason::TIMER)); | 
|  | EXPECT_EQ(1, on_dismiss_callback_called_count_); | 
|  |  | 
|  | // The dismissal isn't counted as an ignore because opening the overflow menu | 
|  | // counts as interacting with the UI. | 
|  | EXPECT_EQ(0, translate_prefs_->GetTranslationIgnoredCount("fr")); | 
|  |  | 
|  | // Show the translate message again, this time without interacting with it. | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ShowBeforeTranslationMessage(env, "fr", "en"); | 
|  |  | 
|  | // Dismiss the translate message. | 
|  | EXPECT_CALL(*bridge_, ClearNativePointer(env)); | 
|  | translate_message_->HandleDismiss( | 
|  | env, static_cast<jint>(messages::DismissReason::TIMER)); | 
|  | EXPECT_EQ(2, on_dismiss_callback_called_count_); | 
|  |  | 
|  | // The dismissal still isn't counted as an ignore because the translate | 
|  | // message was previously interacted with on this page. | 
|  | EXPECT_EQ(0, translate_prefs_->GetTranslationIgnoredCount("fr")); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, TranslationNotIgnoredBecauseErrorOccurred) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | ON_CALL(*client_, ShowTranslateUI(_, _, _, _, _)).WillByDefault(Return(true)); | 
|  | manager_->GetLanguageState()->SetSourceLanguage("fr"); | 
|  | manager_->GetLanguageState()->SetCurrentLanguage("fr"); | 
|  |  | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | EXPECT_CALL(*bridge_, ShowTranslateError(env, _)); | 
|  | EXPECT_CALL(*bridge_, | 
|  | ShowMessage(env, | 
|  | /*title=*/Truly(IsJavaStringNonNull), | 
|  | /*description=*/Truly(IsJavaStringNonNull), | 
|  | /*primary_button_text=*/Truly(IsJavaStringNonNull), | 
|  | /*has_overflow_menu=*/true)); | 
|  |  | 
|  | translate_message_->ShowTranslateStep(TRANSLATE_STEP_TRANSLATE_ERROR, "fr", | 
|  | "en"); | 
|  |  | 
|  | // Dismiss the message. | 
|  | EXPECT_CALL(*bridge_, ClearNativePointer(env)); | 
|  | translate_message_->HandleDismiss( | 
|  | env, static_cast<jint>(messages::DismissReason::TIMER)); | 
|  | EXPECT_EQ(1, on_dismiss_callback_called_count_); | 
|  |  | 
|  | EXPECT_EQ(0, translate_prefs_->GetTranslationIgnoredCount("fr")); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, TranslationDenied) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | ScopedDictPrefUpdate(pref_service_.get(), | 
|  | TranslatePrefs::kPrefTranslateAcceptedCount) | 
|  | ->Set("fr", 100); | 
|  | ScopedDictPrefUpdate(pref_service_.get(), | 
|  | TranslatePrefs::kPrefTranslateIgnoredCount) | 
|  | ->Set("fr", 100); | 
|  |  | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ShowBeforeTranslationMessage(env, "fr", "en"); | 
|  |  | 
|  | base::HistogramTester histogram_tester; | 
|  |  | 
|  | EXPECT_CALL(*bridge_, ClearNativePointer(env)); | 
|  | translate_message_->HandleDismiss( | 
|  | env, static_cast<jint>(messages::DismissReason::GESTURE)); | 
|  |  | 
|  | histogram_tester.ExpectUniqueSample(kInfobarEventHistogram, | 
|  | InfobarEvent::INFOBAR_DECLINE, 1); | 
|  | EXPECT_EQ(1, on_dismiss_callback_called_count_); | 
|  |  | 
|  | EXPECT_EQ(0, translate_prefs_->GetTranslationAcceptedCount("fr")); | 
|  | EXPECT_EQ(1, translate_prefs_->GetTranslationDeniedCount("fr")); | 
|  | EXPECT_EQ(100, translate_prefs_->GetTranslationIgnoredCount("fr")); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, TranslationNotDeniedBecauseOverflowMenuOpened) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | // Show the translate message and simulate the overflow menu being opened. | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ShowBeforeTranslationMessage(env, "fr", "en"); | 
|  |  | 
|  | EXPECT_CALL(*bridge_, ConstructMenuItemArray(env, _, _, _, _, _)) | 
|  | .WillOnce(Return(nullptr)); | 
|  | translate_message_->BuildOverflowMenu(env); | 
|  |  | 
|  | // Dismiss the translate message. | 
|  | EXPECT_CALL(*bridge_, ClearNativePointer(env)); | 
|  | translate_message_->HandleDismiss( | 
|  | env, static_cast<jint>(messages::DismissReason::GESTURE)); | 
|  | EXPECT_EQ(1, on_dismiss_callback_called_count_); | 
|  |  | 
|  | // The dismissal isn't counted as an denial because opening the overflow menu | 
|  | // counts as interacting with the UI. | 
|  | EXPECT_EQ(0, translate_prefs_->GetTranslationDeniedCount("fr")); | 
|  |  | 
|  | // Show the translate message again, this time without interacting with it. | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ShowBeforeTranslationMessage(env, "fr", "en"); | 
|  |  | 
|  | // Dismiss the translate message. | 
|  | EXPECT_CALL(*bridge_, ClearNativePointer(env)); | 
|  | translate_message_->HandleDismiss( | 
|  | env, static_cast<jint>(messages::DismissReason::GESTURE)); | 
|  | EXPECT_EQ(2, on_dismiss_callback_called_count_); | 
|  |  | 
|  | // The dismissal still isn't counted as an denial because the translate | 
|  | // message was previously interacted with on this page. | 
|  | EXPECT_EQ(0, translate_prefs_->GetTranslationDeniedCount("fr")); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, TranslationNotDeniedBecauseErrorOccurred) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | ON_CALL(*client_, ShowTranslateUI(_, _, _, _, _)).WillByDefault(Return(true)); | 
|  | manager_->GetLanguageState()->SetSourceLanguage("fr"); | 
|  | manager_->GetLanguageState()->SetCurrentLanguage("fr"); | 
|  |  | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | EXPECT_CALL(*bridge_, ShowTranslateError(env, _)); | 
|  | EXPECT_CALL(*bridge_, | 
|  | ShowMessage(env, | 
|  | /*title=*/Truly(IsJavaStringNonNull), | 
|  | /*description=*/Truly(IsJavaStringNonNull), | 
|  | /*primary_button_text=*/Truly(IsJavaStringNonNull), | 
|  | /*has_overflow_menu=*/true)); | 
|  |  | 
|  | translate_message_->ShowTranslateStep(TRANSLATE_STEP_TRANSLATE_ERROR, "fr", | 
|  | "en"); | 
|  |  | 
|  | // Dismiss the message. | 
|  | EXPECT_CALL(*bridge_, ClearNativePointer(env)); | 
|  | translate_message_->HandleDismiss( | 
|  | env, static_cast<jint>(messages::DismissReason::GESTURE)); | 
|  | EXPECT_EQ(1, on_dismiss_callback_called_count_); | 
|  |  | 
|  | EXPECT_EQ(0, translate_prefs_->GetTranslationDeniedCount("fr")); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, AutoAlwaysTranslate) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | ScopedDictPrefUpdate(pref_service_.get(), | 
|  | TranslatePrefs::kPrefTranslateAcceptedCount) | 
|  | ->Set("fr", GetAutoAlwaysThreshold() - 1); | 
|  | ScopedDictPrefUpdate(pref_service_.get(), | 
|  | TranslatePrefs::kPrefTranslateAutoAlwaysCount) | 
|  | ->Set("fr", GetMaximumNumberOfAutoAlways() - 1); | 
|  |  | 
|  | EXPECT_FALSE( | 
|  | translate_prefs_->IsLanguagePairOnAlwaysTranslateList("fr", "en")); | 
|  |  | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ON_CALL(*client_, IsTranslatableURL(_)).WillByDefault(Return(true)); | 
|  | ShowBeforeTranslationMessage(env, "fr", "en"); | 
|  | ExpectTranslationInProgress(env, "fr", "en"); | 
|  | translate_message_->HandlePrimaryAction(env); | 
|  |  | 
|  | { | 
|  | base::HistogramTester histogram_tester; | 
|  | FinishTranslation(env, "fr", "en"); | 
|  | histogram_tester.ExpectUniqueSample( | 
|  | kInfobarEventHistogram, | 
|  | InfobarEvent::INFOBAR_SNACKBAR_AUTO_ALWAYS_IMPRESSION, 1); | 
|  | } | 
|  |  | 
|  | EXPECT_TRUE( | 
|  | translate_prefs_->IsLanguagePairOnAlwaysTranslateList("fr", "en")); | 
|  | EXPECT_EQ(GetMaximumNumberOfAutoAlways(), | 
|  | translate_prefs_->GetTranslationAutoAlwaysCount("fr")); | 
|  | EXPECT_EQ(0, translate_prefs_->GetTranslationAcceptedCount("fr")); | 
|  |  | 
|  | { | 
|  | base::HistogramTester histogram_tester; | 
|  | // Simulate clicking "Undo", which should disable "Always translate | 
|  | // language." | 
|  | ExpectTranslationReverts(env, "fr", "en"); | 
|  | translate_message_->HandlePrimaryAction(env); | 
|  |  | 
|  | histogram_tester.ExpectBucketCount( | 
|  | kInfobarEventHistogram, | 
|  | InfobarEvent::INFOBAR_SNACKBAR_CANCEL_AUTO_ALWAYS, 1); | 
|  | histogram_tester.ExpectBucketCount(kInfobarEventHistogram, | 
|  | InfobarEvent::INFOBAR_REVERT, 1); | 
|  | histogram_tester.ExpectTotalCount(kInfobarEventHistogram, 2); | 
|  | } | 
|  |  | 
|  | EXPECT_FALSE( | 
|  | translate_prefs_->IsLanguagePairOnAlwaysTranslateList("fr", "en")); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, AutoAlwaysTranslatePastAcceptedThreshold) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | // Once the translation happens, this count will be 1 larger than the | 
|  | // threshold. This test is to make sure that the comparison is | 
|  | // greater-than-or-equal, not just equality. | 
|  | ScopedDictPrefUpdate(pref_service_.get(), | 
|  | TranslatePrefs::kPrefTranslateAcceptedCount) | 
|  | ->Set("fr", GetAutoAlwaysThreshold()); | 
|  | ScopedDictPrefUpdate(pref_service_.get(), | 
|  | TranslatePrefs::kPrefTranslateAutoAlwaysCount) | 
|  | ->Set("fr", GetMaximumNumberOfAutoAlways() - 1); | 
|  |  | 
|  | EXPECT_FALSE( | 
|  | translate_prefs_->IsLanguagePairOnAlwaysTranslateList("fr", "en")); | 
|  |  | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ON_CALL(*client_, IsTranslatableURL(_)).WillByDefault(Return(true)); | 
|  | ShowBeforeTranslationMessage(env, "fr", "en"); | 
|  | ExpectTranslationInProgress(env, "fr", "en"); | 
|  | translate_message_->HandlePrimaryAction(env); | 
|  | FinishTranslation(env, "fr", "en"); | 
|  |  | 
|  | EXPECT_TRUE( | 
|  | translate_prefs_->IsLanguagePairOnAlwaysTranslateList("fr", "en")); | 
|  | EXPECT_EQ(GetMaximumNumberOfAutoAlways(), | 
|  | translate_prefs_->GetTranslationAutoAlwaysCount("fr")); | 
|  | EXPECT_EQ(0, translate_prefs_->GetTranslationAcceptedCount("fr")); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, AutoAlwaysTranslateDismissedInProgress) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | ScopedDictPrefUpdate(pref_service_.get(), | 
|  | TranslatePrefs::kPrefTranslateAcceptedCount) | 
|  | ->Set("fr", GetAutoAlwaysThreshold() - 1); | 
|  | ScopedDictPrefUpdate(pref_service_.get(), | 
|  | TranslatePrefs::kPrefTranslateAutoAlwaysCount) | 
|  | ->Set("fr", GetMaximumNumberOfAutoAlways() - 1); | 
|  |  | 
|  | EXPECT_FALSE( | 
|  | translate_prefs_->IsLanguagePairOnAlwaysTranslateList("fr", "en")); | 
|  |  | 
|  | // Start the translation. | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ON_CALL(*client_, IsTranslatableURL(_)).WillByDefault(Return(true)); | 
|  | ShowBeforeTranslationMessage(env, "fr", "en"); | 
|  | ExpectTranslationInProgress(env, "fr", "en"); | 
|  | translate_message_->HandlePrimaryAction(env); | 
|  |  | 
|  | // Simulate the message being dismissed from Java. | 
|  | EXPECT_CALL(*bridge_, ClearNativePointer(env)); | 
|  | translate_message_->HandleDismiss( | 
|  | env, static_cast<jint>(messages::DismissReason::GESTURE)); | 
|  | EXPECT_EQ(1, on_dismiss_callback_called_count_); | 
|  | EXPECT_FALSE( | 
|  | translate_prefs_->IsLanguagePairOnAlwaysTranslateList("fr", "en")); | 
|  |  | 
|  | // Finish the translation, causing the Message to pop up again. | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | FinishTranslation(env, "fr", "en"); | 
|  |  | 
|  | EXPECT_TRUE( | 
|  | translate_prefs_->IsLanguagePairOnAlwaysTranslateList("fr", "en")); | 
|  | EXPECT_EQ(GetMaximumNumberOfAutoAlways(), | 
|  | translate_prefs_->GetTranslationAutoAlwaysCount("fr")); | 
|  | EXPECT_EQ(0, translate_prefs_->GetTranslationAcceptedCount("fr")); | 
|  |  | 
|  | // Simulate clicking "Undo", which should disable "Always translate language." | 
|  | ExpectTranslationReverts(env, "fr", "en"); | 
|  | translate_message_->HandlePrimaryAction(env); | 
|  |  | 
|  | EXPECT_FALSE( | 
|  | translate_prefs_->IsLanguagePairOnAlwaysTranslateList("fr", "en")); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, AutoAlwaysTranslateThresholdNotReached) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | ScopedDictPrefUpdate(pref_service_.get(), | 
|  | TranslatePrefs::kPrefTranslateAcceptedCount) | 
|  | ->Set("fr", GetAutoAlwaysThreshold() - 2); | 
|  | ScopedDictPrefUpdate(pref_service_.get(), | 
|  | TranslatePrefs::kPrefTranslateAutoAlwaysCount) | 
|  | ->Set("fr", GetMaximumNumberOfAutoAlways() - 1); | 
|  |  | 
|  | EXPECT_FALSE( | 
|  | translate_prefs_->IsLanguagePairOnAlwaysTranslateList("fr", "en")); | 
|  |  | 
|  | // Start the translation. | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ON_CALL(*client_, IsTranslatableURL(_)).WillByDefault(Return(true)); | 
|  | ShowBeforeTranslationMessage(env, "fr", "en"); | 
|  | ExpectTranslationInProgress(env, "fr", "en"); | 
|  | translate_message_->HandlePrimaryAction(env); | 
|  | FinishTranslation(env, "fr", "en"); | 
|  |  | 
|  | // Auto-always-translate should not have triggered, since the threshold of | 
|  | // accepted translations has not been reached. | 
|  | EXPECT_FALSE( | 
|  | translate_prefs_->IsLanguagePairOnAlwaysTranslateList("fr", "en")); | 
|  | EXPECT_EQ(GetAutoAlwaysThreshold() - 1, | 
|  | translate_prefs_->GetTranslationAcceptedCount("fr")); | 
|  | EXPECT_EQ(GetMaximumNumberOfAutoAlways() - 1, | 
|  | translate_prefs_->GetTranslationAutoAlwaysCount("fr")); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, AutoAlwaysTranslatePastMaximumTimes) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | ScopedDictPrefUpdate(pref_service_.get(), | 
|  | TranslatePrefs::kPrefTranslateAcceptedCount) | 
|  | ->Set("fr", GetAutoAlwaysThreshold() - 1); | 
|  | ScopedDictPrefUpdate(pref_service_.get(), | 
|  | TranslatePrefs::kPrefTranslateAutoAlwaysCount) | 
|  | ->Set("fr", GetMaximumNumberOfAutoAlways()); | 
|  |  | 
|  | EXPECT_FALSE( | 
|  | translate_prefs_->IsLanguagePairOnAlwaysTranslateList("fr", "en")); | 
|  |  | 
|  | // Start the translation. | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ON_CALL(*client_, IsTranslatableURL(_)).WillByDefault(Return(true)); | 
|  | ShowBeforeTranslationMessage(env, "fr", "en"); | 
|  | ExpectTranslationInProgress(env, "fr", "en"); | 
|  | translate_message_->HandlePrimaryAction(env); | 
|  | FinishTranslation(env, "fr", "en"); | 
|  |  | 
|  | // Auto-always-translate should not have triggered, since | 
|  | // auto-always-translate has already triggered the maximum number of allowed | 
|  | // times. | 
|  | EXPECT_FALSE( | 
|  | translate_prefs_->IsLanguagePairOnAlwaysTranslateList("fr", "en")); | 
|  | EXPECT_EQ(GetAutoAlwaysThreshold(), | 
|  | translate_prefs_->GetTranslationAcceptedCount("fr")); | 
|  | EXPECT_EQ(GetMaximumNumberOfAutoAlways(), | 
|  | translate_prefs_->GetTranslationAutoAlwaysCount("fr")); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, AutoAlwaysTranslateInterruptedByOverflowMenu) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | ScopedDictPrefUpdate(pref_service_.get(), | 
|  | TranslatePrefs::kPrefTranslateAcceptedCount) | 
|  | ->Set("fr", GetAutoAlwaysThreshold() - 1); | 
|  | ScopedDictPrefUpdate(pref_service_.get(), | 
|  | TranslatePrefs::kPrefTranslateAutoAlwaysCount) | 
|  | ->Set("fr", GetMaximumNumberOfAutoAlways() - 1); | 
|  |  | 
|  | EXPECT_FALSE( | 
|  | translate_prefs_->IsLanguagePairOnAlwaysTranslateList("fr", "en")); | 
|  |  | 
|  | // Start the translation. | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ON_CALL(*client_, IsTranslatableURL(_)).WillByDefault(Return(true)); | 
|  | ShowBeforeTranslationMessage(env, "fr", "en"); | 
|  | ExpectTranslationInProgress(env, "fr", "en"); | 
|  | translate_message_->HandlePrimaryAction(env); | 
|  |  | 
|  | // While the translation is in progress, simulate the overflow menu being | 
|  | // opened. | 
|  | EXPECT_CALL(*bridge_, ConstructMenuItemArray(env, _, _, _, _, _)) | 
|  | .WillOnce(Return(nullptr)); | 
|  | translate_message_->BuildOverflowMenu(env); | 
|  |  | 
|  | FinishTranslation(env, "fr", "en"); | 
|  |  | 
|  | // Auto-always-translate should not have triggered, since opening the overflow | 
|  | // menu mid-translation should have prevented it from triggering. | 
|  | EXPECT_FALSE( | 
|  | translate_prefs_->IsLanguagePairOnAlwaysTranslateList("fr", "en")); | 
|  | EXPECT_EQ(GetAutoAlwaysThreshold(), | 
|  | translate_prefs_->GetTranslationAcceptedCount("fr")); | 
|  | EXPECT_EQ(GetMaximumNumberOfAutoAlways() - 1, | 
|  | translate_prefs_->GetTranslationAutoAlwaysCount("fr")); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, AutoNeverTranslate) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | ScopedDictPrefUpdate(pref_service_.get(), | 
|  | TranslatePrefs::kPrefTranslateDeniedCount) | 
|  | ->Set("fr", GetAutoNeverThreshold() - 1); | 
|  |  | 
|  | EXPECT_FALSE(translate_prefs_->IsBlockedLanguage("fr")); | 
|  |  | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ShowBeforeTranslationMessage(env, "fr", "en"); | 
|  |  | 
|  | { | 
|  | base::HistogramTester histogram_tester; | 
|  |  | 
|  | // Dismissing the message should cause the auto-never-translate confirmation | 
|  | // message to be shown. | 
|  | EXPECT_CALL(*bridge_, | 
|  | ShowMessage(env, | 
|  | /*title=*/Truly(IsJavaStringNonNull), | 
|  | /*description=*/Truly(IsJavaStringNull), | 
|  | /*primary_button_text=*/Truly(IsJavaStringNonNull), | 
|  | /*has_overflow_menu=*/false)); | 
|  | translate_message_->HandleDismiss( | 
|  | env, static_cast<jint>(messages::DismissReason::GESTURE)); | 
|  |  | 
|  | histogram_tester.ExpectBucketCount( | 
|  | kInfobarEventHistogram, | 
|  | InfobarEvent::INFOBAR_SNACKBAR_AUTO_NEVER_IMPRESSION, 1); | 
|  | histogram_tester.ExpectBucketCount(kInfobarEventHistogram, | 
|  | InfobarEvent::INFOBAR_DECLINE, 1); | 
|  | histogram_tester.ExpectTotalCount(kInfobarEventHistogram, 2); | 
|  | } | 
|  |  | 
|  | // The dismissal callback should not have been run. | 
|  | EXPECT_EQ(0, on_dismiss_callback_called_count_); | 
|  |  | 
|  | EXPECT_TRUE(translate_prefs_->IsBlockedLanguage("fr")); | 
|  |  | 
|  | { | 
|  | base::HistogramTester histogram_tester; | 
|  | // Click "Undo" on the confirmation. | 
|  | EXPECT_CALL(*bridge_, Dismiss(env)) | 
|  | .WillOnce(InvokeWithoutArgs([env, | 
|  | message = translate_message_.get()]() { | 
|  | message->HandleDismiss( | 
|  | env, | 
|  | static_cast<jint>(messages::DismissReason::DISMISSED_BY_FEATURE)); | 
|  | })); | 
|  | EXPECT_CALL(*bridge_, ClearNativePointer(env)); | 
|  | translate_message_->HandlePrimaryAction(env); | 
|  |  | 
|  | histogram_tester.ExpectUniqueSample( | 
|  | kInfobarEventHistogram, | 
|  | InfobarEvent::INFOBAR_SNACKBAR_CANCEL_AUTO_NEVER, 1); | 
|  | } | 
|  |  | 
|  | EXPECT_EQ(1, on_dismiss_callback_called_count_); | 
|  | EXPECT_FALSE(translate_prefs_->IsBlockedLanguage("fr")); | 
|  | } | 
|  |  | 
|  | TEST_F(TranslateMessageTest, AutoNeverTranslatePastMaximumTimes) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | ScopedDictPrefUpdate(pref_service_.get(), | 
|  | TranslatePrefs::kPrefTranslateDeniedCount) | 
|  | ->Set("fr", GetAutoNeverThreshold()); | 
|  | ScopedDictPrefUpdate(pref_service_.get(), | 
|  | TranslatePrefs::kPrefTranslateAutoNeverCount) | 
|  | ->Set("fr", GetMaximumNumberOfAutoNever()); | 
|  |  | 
|  | EXPECT_FALSE(translate_prefs_->IsBlockedLanguage("fr")); | 
|  |  | 
|  | EXPECT_CALL(*bridge_, CreateTranslateMessage( | 
|  | env, _, _, kDefaultDismissalDurationSeconds)) | 
|  | .WillOnce(Return(true)); | 
|  | ShowBeforeTranslationMessage(env, "fr", "en"); | 
|  |  | 
|  | // Dismiss the message. | 
|  | EXPECT_CALL(*bridge_, ClearNativePointer(env)); | 
|  | translate_message_->HandleDismiss( | 
|  | env, static_cast<jint>(messages::DismissReason::GESTURE)); | 
|  | EXPECT_EQ(1, on_dismiss_callback_called_count_); | 
|  | EXPECT_FALSE(translate_prefs_->IsBlockedLanguage("fr")); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace translate |