blob: 3b4cc08d8b13e855625b7840adfb3f707bdbc71f [file] [log] [blame]
// 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