| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/accessibility/caption_controller.h" |
| |
| #include "base/files/file_path.h" |
| #include "base/ranges/ranges.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "chrome/browser/accessibility/caption_controller_factory.h" |
| #include "chrome/browser/accessibility/caption_host_impl.h" |
| #include "chrome/browser/browser_features.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/lifetime/application_lifetime.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_keep_alive_types.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/profiles/profile_window.h" |
| #include "chrome/browser/profiles/scoped_profile_keep_alive.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/browser/ui/caption_bubble_controller.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/live_caption/pref_names.h" |
| #include "components/soda/pref_names.h" |
| #include "components/sync_preferences/pref_service_syncable.h" |
| #include "content/public/test/browser_test.h" |
| #include "media/base/media_switches.h" |
| |
| namespace captions { |
| |
| // Blocks until a new profile is created. |
| void UnblockOnProfileCreation(base::RunLoop* run_loop, |
| Profile* profile, |
| Profile::CreateStatus status) { |
| if (status == Profile::CREATE_STATUS_INITIALIZED) |
| run_loop->Quit(); |
| } |
| |
| Profile* CreateProfile() { |
| ProfileManager* profile_manager = g_browser_process->profile_manager(); |
| base::FilePath profile_path = |
| profile_manager->GenerateNextProfileDirectoryPath(); |
| base::RunLoop run_loop; |
| profile_manager->CreateProfileAsync( |
| profile_path, base::BindRepeating(&UnblockOnProfileCreation, &run_loop)); |
| run_loop.Run(); |
| return profile_manager->GetProfileByPath(profile_path); |
| } |
| |
| class CaptionControllerTest : public InProcessBrowserTest { |
| public: |
| CaptionControllerTest() = default; |
| ~CaptionControllerTest() override = default; |
| CaptionControllerTest(const CaptionControllerTest&) = delete; |
| CaptionControllerTest& operator=(const CaptionControllerTest&) = delete; |
| |
| // InProcessBrowserTest overrides: |
| void SetUp() override { |
| scoped_feature_list_.InitWithFeatures( |
| {media::kLiveCaption, media::kUseSodaForLiveCaption}, {}); |
| InProcessBrowserTest::SetUp(); |
| } |
| |
| void SetLiveCaptionEnabled(bool enabled) { |
| browser()->profile()->GetPrefs()->SetBoolean(prefs::kLiveCaptionEnabled, |
| enabled); |
| if (enabled) |
| speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting(); |
| } |
| |
| void SetLiveCaptionEnabledOnProfile(bool enabled, Profile* profile) { |
| profile->GetPrefs()->SetBoolean(prefs::kLiveCaptionEnabled, enabled); |
| if (enabled) |
| speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting(); |
| } |
| |
| CaptionController* GetController() { |
| return GetControllerForProfile(browser()->profile()); |
| } |
| |
| CaptionController* GetControllerForProfile(Profile* profile) { |
| return CaptionControllerFactory::GetForProfile(profile); |
| } |
| |
| CaptionBubbleController* GetBubbleController() { |
| return GetBubbleControllerForProfile(browser()->profile()); |
| } |
| |
| CaptionBubbleController* GetBubbleControllerForProfile(Profile* profile) { |
| return GetControllerForProfile(profile)->caption_bubble_controller_.get(); |
| } |
| |
| CaptionHostImpl* GetCaptionHostImpl() { |
| if (!caption_host_impl_) { |
| caption_host_impl_ = std::make_unique<CaptionHostImpl>( |
| browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame()); |
| } |
| return caption_host_impl_.get(); |
| } |
| |
| bool DispatchTranscription(std::string text) { |
| return DispatchTranscriptionToProfile(text, browser()->profile()); |
| } |
| |
| bool DispatchTranscriptionToProfile(std::string text, Profile* profile) { |
| return GetControllerForProfile(profile)->DispatchTranscription( |
| GetCaptionHostImpl(), |
| chrome::mojom::TranscriptionResult::New(text, false /* is_final */)); |
| } |
| |
| void OnError() { OnErrorOnProfile(browser()->profile()); } |
| |
| void OnErrorOnProfile(Profile* profile) { |
| GetControllerForProfile(profile)->OnError(GetCaptionHostImpl()); |
| } |
| |
| void OnAudioStreamEnd() { OnAudioStreamEndOnProfile(browser()->profile()); } |
| |
| void OnAudioStreamEndOnProfile(Profile* profile) { |
| GetControllerForProfile(profile)->OnAudioStreamEnd(GetCaptionHostImpl()); |
| } |
| |
| bool HasBubbleController() { |
| return HasBubbleControllerOnProfile(browser()->profile()); |
| } |
| |
| bool HasBubbleControllerOnProfile(Profile* profile) { |
| return GetControllerForProfile(profile)->caption_bubble_controller_ != |
| nullptr; |
| } |
| |
| void ExpectIsWidgetVisible(bool visible) { |
| ExpectIsWidgetVisibleOnProfile(visible, browser()->profile()); |
| } |
| |
| void ExpectIsWidgetVisibleOnProfile(bool visible, Profile* profile) { |
| #if defined(TOOLKIT_VIEWS) |
| EXPECT_EQ( |
| visible, |
| GetBubbleControllerForProfile(profile)->IsWidgetVisibleForTesting()); |
| #endif |
| } |
| |
| void ExpectBubbleLabelTextEquals(std::string text) { |
| ExpectBubbleLabelTextOnProfileEquals(text, browser()->profile()); |
| } |
| |
| void ExpectBubbleLabelTextOnProfileEquals(std::string text, |
| Profile* profile) { |
| #if defined(TOOLKIT_VIEWS) |
| EXPECT_EQ( |
| text, |
| GetBubbleControllerForProfile(profile)->GetBubbleLabelTextForTesting()); |
| #endif |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| |
| std::unique_ptr<CaptionHostImpl> caption_host_impl_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(CaptionControllerTest, ProfilePrefsAreRegistered) { |
| EXPECT_FALSE( |
| browser()->profile()->GetPrefs()->GetBoolean(prefs::kLiveCaptionEnabled)); |
| EXPECT_EQ(base::FilePath(), g_browser_process->local_state()->GetFilePath( |
| prefs::kSodaBinaryPath)); |
| EXPECT_EQ(base::FilePath(), g_browser_process->local_state()->GetFilePath( |
| prefs::kSodaEnUsConfigPath)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CaptionControllerTest, |
| ProfilePrefsAreRegistered_Incognito) { |
| // Set live caption enabled on the regular profile. |
| SetLiveCaptionEnabled(true); |
| EXPECT_TRUE( |
| browser()->profile()->GetPrefs()->GetBoolean(prefs::kLiveCaptionEnabled)); |
| EXPECT_EQ(base::FilePath(), g_browser_process->local_state()->GetFilePath( |
| prefs::kSodaBinaryPath)); |
| EXPECT_EQ(base::FilePath(), g_browser_process->local_state()->GetFilePath( |
| prefs::kSodaEnUsConfigPath)); |
| |
| // Ensure that live caption is also enabled in the incognito profile. |
| Profile* incognito_profile = |
| browser()->profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true); |
| EXPECT_TRUE( |
| incognito_profile->GetPrefs()->GetBoolean(prefs::kLiveCaptionEnabled)); |
| EXPECT_EQ(base::FilePath(), g_browser_process->local_state()->GetFilePath( |
| prefs::kSodaBinaryPath)); |
| EXPECT_EQ(base::FilePath(), g_browser_process->local_state()->GetFilePath( |
| prefs::kSodaEnUsConfigPath)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CaptionControllerTest, LiveCaptionEnabledChanged) { |
| EXPECT_EQ(nullptr, GetBubbleController()); |
| EXPECT_FALSE(HasBubbleController()); |
| |
| SetLiveCaptionEnabled(true); |
| EXPECT_NE(nullptr, GetBubbleController()); |
| EXPECT_TRUE(HasBubbleController()); |
| |
| SetLiveCaptionEnabled(false); |
| EXPECT_EQ(nullptr, GetBubbleController()); |
| EXPECT_FALSE(HasBubbleController()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CaptionControllerTest, |
| LiveCaptionEnabledChanged_BubbleVisible) { |
| SetLiveCaptionEnabled(true); |
| // Make the bubble visible by dispatching a transcription. |
| DispatchTranscription( |
| "In Switzerland it is illegal to own just one guinea pig."); |
| ExpectIsWidgetVisible(true); |
| |
| SetLiveCaptionEnabled(false); |
| EXPECT_EQ(nullptr, GetBubbleController()); |
| EXPECT_FALSE(HasBubbleController()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CaptionControllerTest, OnSodaInstalled) { |
| EXPECT_FALSE(HasBubbleController()); |
| browser()->profile()->GetPrefs()->SetBoolean(prefs::kLiveCaptionEnabled, |
| true); |
| EXPECT_FALSE(HasBubbleController()); |
| |
| // The UI is only created after SODA is installed. |
| speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting(); |
| EXPECT_TRUE(HasBubbleController()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CaptionControllerTest, DispatchTranscription) { |
| bool success = DispatchTranscription("A baby spider is called a spiderling."); |
| EXPECT_FALSE(success); |
| EXPECT_FALSE(HasBubbleController()); |
| |
| SetLiveCaptionEnabled(true); |
| success = DispatchTranscription( |
| "A baby octopus is about the size of a flea when it is born."); |
| EXPECT_TRUE(success); |
| ExpectIsWidgetVisible(true); |
| ExpectBubbleLabelTextEquals( |
| "A baby octopus is about the size of a flea when it is born."); |
| |
| SetLiveCaptionEnabled(false); |
| success = DispatchTranscription( |
| "Approximately 10-20% of power outages in the US are caused by " |
| "squirrels."); |
| EXPECT_FALSE(success); |
| EXPECT_FALSE(HasBubbleController()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CaptionControllerTest, OnError) { |
| OnError(); |
| EXPECT_FALSE(HasBubbleController()); |
| |
| SetLiveCaptionEnabled(true); |
| OnError(); |
| ExpectIsWidgetVisible(true); |
| |
| SetLiveCaptionEnabled(false); |
| OnError(); |
| EXPECT_FALSE(HasBubbleController()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CaptionControllerTest, OnAudioStreamEnd) { |
| OnAudioStreamEnd(); |
| EXPECT_FALSE(HasBubbleController()); |
| |
| SetLiveCaptionEnabled(true); |
| DispatchTranscription("Some cicadas appear only once every 17 years."); |
| ExpectIsWidgetVisible(true); |
| |
| OnAudioStreamEnd(); |
| ExpectIsWidgetVisible(false); |
| |
| SetLiveCaptionEnabled(false); |
| OnAudioStreamEnd(); |
| EXPECT_FALSE(HasBubbleController()); |
| } |
| |
| #if !BUILDFLAG(IS_CHROMEOS_ASH) // No multi-profile on ChromeOS. |
| |
| IN_PROC_BROWSER_TEST_F(CaptionControllerTest, |
| LiveCaptionEnabledChanged_MultipleProfiles) { |
| Profile* profile1 = browser()->profile(); |
| Profile* profile2 = CreateProfile(); |
| |
| // The profiles start with no caption bubble controllers. |
| EXPECT_FALSE(HasBubbleControllerOnProfile(profile1)); |
| EXPECT_FALSE(HasBubbleControllerOnProfile(profile2)); |
| |
| // Enable live caption on profile1. |
| SetLiveCaptionEnabled(true); |
| EXPECT_TRUE(HasBubbleControllerOnProfile(profile1)); |
| EXPECT_FALSE(HasBubbleControllerOnProfile(profile2)); |
| |
| // Enable live caption on profile2. |
| SetLiveCaptionEnabledOnProfile(true, profile2); |
| EXPECT_TRUE(HasBubbleControllerOnProfile(profile1)); |
| EXPECT_TRUE(HasBubbleControllerOnProfile(profile2)); |
| |
| // Disable live caption on profile1. |
| SetLiveCaptionEnabled(false); |
| EXPECT_FALSE(HasBubbleControllerOnProfile(profile1)); |
| EXPECT_TRUE(HasBubbleControllerOnProfile(profile2)); |
| |
| // Disable live caption on profile2. |
| SetLiveCaptionEnabledOnProfile(false, profile2); |
| EXPECT_FALSE(HasBubbleControllerOnProfile(profile1)); |
| EXPECT_FALSE(HasBubbleControllerOnProfile(profile2)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CaptionControllerTest, |
| DispatchTranscription_MultipleProfiles) { |
| Profile* profile1 = browser()->profile(); |
| Profile* profile2 = CreateProfile(); |
| |
| // Enable live caption on both profiles. |
| SetLiveCaptionEnabled(true); |
| SetLiveCaptionEnabledOnProfile(true, profile2); |
| |
| // Dispatch transcription routes the transcription to the right profile. |
| bool success = |
| DispatchTranscriptionToProfile("Only female mosquitos bite.", profile1); |
| EXPECT_TRUE(success); |
| ExpectIsWidgetVisibleOnProfile(true, profile1); |
| ExpectIsWidgetVisibleOnProfile(false, profile2); |
| ExpectBubbleLabelTextOnProfileEquals("Only female mosquitos bite.", profile1); |
| ExpectBubbleLabelTextOnProfileEquals("", profile2); |
| |
| success = DispatchTranscriptionToProfile( |
| "Mosquitos were around at the time of the dinosaurs.", profile2); |
| EXPECT_TRUE(success); |
| ExpectIsWidgetVisibleOnProfile(true, profile1); |
| ExpectIsWidgetVisibleOnProfile(true, profile2); |
| ExpectBubbleLabelTextOnProfileEquals("Only female mosquitos bite.", profile1); |
| ExpectBubbleLabelTextOnProfileEquals( |
| "Mosquitos were around at the time of the dinosaurs.", profile2); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CaptionControllerTest, OnError_MultipleProfiles) { |
| Profile* profile1 = browser()->profile(); |
| Profile* profile2 = CreateProfile(); |
| |
| // Enable live caption on both profiles. |
| SetLiveCaptionEnabled(true); |
| SetLiveCaptionEnabledOnProfile(true, profile2); |
| |
| // OnError routes to the right profile. |
| OnErrorOnProfile(profile1); |
| ExpectIsWidgetVisibleOnProfile(true, profile1); |
| ExpectIsWidgetVisibleOnProfile(false, profile2); |
| |
| OnErrorOnProfile(profile2); |
| ExpectIsWidgetVisibleOnProfile(true, profile1); |
| ExpectIsWidgetVisibleOnProfile(true, profile2); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CaptionControllerTest, |
| OnAudioStreamEnd_MultipleProfiles) { |
| Profile* profile1 = browser()->profile(); |
| Profile* profile2 = CreateProfile(); |
| |
| // Enable live caption on both profiles. |
| SetLiveCaptionEnabled(true); |
| SetLiveCaptionEnabledOnProfile(true, profile2); |
| |
| DispatchTranscriptionToProfile( |
| "Capybaras are the largest rodents in the world.", profile1); |
| DispatchTranscriptionToProfile("Capybaras' teeth grow continuously.", |
| profile2); |
| ExpectIsWidgetVisibleOnProfile(true, profile1); |
| ExpectIsWidgetVisibleOnProfile(true, profile2); |
| |
| // OnAudioStreamEnd routes to the right profile. |
| OnAudioStreamEndOnProfile(profile1); |
| ExpectIsWidgetVisibleOnProfile(false, profile1); |
| ExpectIsWidgetVisibleOnProfile(true, profile2); |
| |
| OnAudioStreamEndOnProfile(profile2); |
| ExpectIsWidgetVisibleOnProfile(false, profile1); |
| ExpectIsWidgetVisibleOnProfile(false, profile2); |
| } |
| |
| #endif // !BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| } // namespace captions |