blob: 74680da2b442d7d538dd11a330fca22758edb733 [file] [log] [blame]
// Copyright 2021 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/live_caption_speech_recognition_host.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/accessibility/live_caption_controller.h"
#include "chrome/browser/accessibility/live_caption_controller_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.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/soda_installer.h"
#include "components/sync_preferences/pref_service_syncable.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/test/browser_test.h"
#include "media/base/media_switches.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/constants/ash_features.h"
#endif
namespace {
// Chrome OS requires an additional feature flag to enable Live Caption.
std::vector<base::Feature> RequiredFeatureFlags() {
std::vector<base::Feature> features = {media::kLiveCaption,
media::kUseSodaForLiveCaption};
#if BUILDFLAG(IS_CHROMEOS_ASH)
features.push_back(ash::features::kOnDeviceSpeechRecognition);
#endif
return features;
}
// A WebContentsObserver that allows waiting for some media to start or stop
// playing fullscreen.
class FullscreenEventsWaiter : public content::WebContentsObserver {
public:
explicit FullscreenEventsWaiter(content::WebContents* web_contents)
: WebContentsObserver(web_contents) {}
FullscreenEventsWaiter(const FullscreenEventsWaiter& rhs) = delete;
FullscreenEventsWaiter& operator=(const FullscreenEventsWaiter& rhs) = delete;
~FullscreenEventsWaiter() override = default;
void MediaEffectivelyFullscreenChanged(bool value) override {
if (run_loop_)
run_loop_->Quit();
}
// Wait for the current media playing fullscreen mode to be equal to
// |expected_media_fullscreen_mode|.
void Wait() {
run_loop_ = std::make_unique<base::RunLoop>();
run_loop_->Run();
}
private:
std::unique_ptr<base::RunLoop> run_loop_;
};
} // namespace
namespace captions {
class LiveCaptionSpeechRecognitionHostTest : public InProcessBrowserTest {
public:
LiveCaptionSpeechRecognitionHostTest() = default;
~LiveCaptionSpeechRecognitionHostTest() override = default;
LiveCaptionSpeechRecognitionHostTest(
const LiveCaptionSpeechRecognitionHostTest&) = delete;
LiveCaptionSpeechRecognitionHostTest& operator=(
const LiveCaptionSpeechRecognitionHostTest&) = delete;
// InProcessBrowserTest overrides:
void SetUp() override {
scoped_feature_list_.InitWithFeatures(RequiredFeatureFlags(), {});
// This is required for the fullscreen video tests.
embedded_test_server()->ServeFilesFromSourceDirectory(
base::FilePath(FILE_PATH_LITERAL("content/test/data")));
InProcessBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
ASSERT_TRUE(embedded_test_server()->Start());
}
void CreateLiveCaptionSpeechRecognitionHost(
content::RenderFrameHost* frame_host) {
mojo::Remote<media::mojom::SpeechRecognitionRecognizerClient> remote;
mojo::PendingReceiver<media::mojom::SpeechRecognitionRecognizerClient>
receiver;
remote.Bind(receiver.InitWithNewPipeAndPassRemote());
LiveCaptionSpeechRecognitionHost::Create(frame_host, std::move(receiver));
remotes_.emplace(frame_host, std::move(remote));
}
void OnSpeechRecognitionRecognitionEvent(content::RenderFrameHost* frame_host,
std::string text,
bool expected_success) {
remotes_[frame_host]->OnSpeechRecognitionRecognitionEvent(
media::SpeechRecognitionResult(text, /*is_final=*/false),
base::BindOnce(&LiveCaptionSpeechRecognitionHostTest::
DispatchTranscriptionCallback,
base::Unretained(this), expected_success));
}
void OnLanguageIdentificationEvent(
content::RenderFrameHost* frame_host,
const std::string& language,
const media::mojom::ConfidenceLevel confidence_level) {
remotes_[frame_host]->OnLanguageIdentificationEvent(
media::mojom::LanguageIdentificationEvent::New(language,
confidence_level));
}
void OnSpeechRecognitionError(content::RenderFrameHost* frame_host) {
remotes_[frame_host]->OnSpeechRecognitionError();
}
void SetLiveCaptionEnabled(bool enabled) {
browser()->profile()->GetPrefs()->SetBoolean(prefs::kLiveCaptionEnabled,
enabled);
if (enabled)
speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
}
bool HasBubbleController() {
return LiveCaptionControllerFactory::GetForProfile(browser()->profile())
->caption_bubble_controller_.get() != nullptr;
}
void ExpectIsWidgetVisible(bool visible) {
#if defined(TOOLKIT_VIEWS)
CaptionBubbleController* bubble_controller =
LiveCaptionControllerFactory::GetForProfile(browser()->profile())
->caption_bubble_controller_.get();
EXPECT_EQ(visible, bubble_controller->IsWidgetVisibleForTesting());
#endif
}
private:
void DispatchTranscriptionCallback(bool expected_success, bool success) {
EXPECT_EQ(expected_success, success);
}
base::test::ScopedFeatureList scoped_feature_list_;
std::map<content::RenderFrameHost*,
mojo::Remote<media::mojom::SpeechRecognitionRecognizerClient>>
remotes_;
};
// Disabled due to flaky crashes; https://crbug.com/1216304.
IN_PROC_BROWSER_TEST_F(LiveCaptionSpeechRecognitionHostTest,
DISABLED_DestroysWithoutCrashing) {
content::RenderFrameHost* frame_host =
browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
CreateLiveCaptionSpeechRecognitionHost(frame_host);
SetLiveCaptionEnabled(true);
OnSpeechRecognitionRecognitionEvent(
frame_host,
"Pandas' coloring helps them camouflage in snowy environments.",
/* expected_success= */ true);
base::RunLoop().RunUntilIdle();
ExpectIsWidgetVisible(true);
ui_test_utils::NavigateToURL(browser(), GURL("http://www.google.com"));
content::WaitForLoadStop(
browser()->tab_strip_model()->GetActiveWebContents());
content::RenderFrameHost* new_frame_host =
browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
// After navigating to a new URL, the main frame should be different from the
// former frame host.
CreateLiveCaptionSpeechRecognitionHost(new_frame_host);
ExpectIsWidgetVisible(false);
// Test passes if the following line runs without crashing.
OnSpeechRecognitionRecognitionEvent(new_frame_host,
"Pandas have vertical slits for pupils.",
/* expected_success= */ true);
base::RunLoop().RunUntilIdle();
ExpectIsWidgetVisible(true);
}
IN_PROC_BROWSER_TEST_F(LiveCaptionSpeechRecognitionHostTest,
OnSpeechRecognitionRecognitionEvent) {
content::RenderFrameHost* frame_host =
browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
CreateLiveCaptionSpeechRecognitionHost(frame_host);
SetLiveCaptionEnabled(true);
OnSpeechRecognitionRecognitionEvent(frame_host,
"Pandas learn to climb at 5 months old.",
/* expected_success= */ true);
base::RunLoop().RunUntilIdle();
ExpectIsWidgetVisible(true);
SetLiveCaptionEnabled(false);
OnSpeechRecognitionRecognitionEvent(
frame_host,
"Pandas have an extended wrist bone which they use like a thumb.",
/* expected_success= */ false);
base::RunLoop().RunUntilIdle();
}
IN_PROC_BROWSER_TEST_F(LiveCaptionSpeechRecognitionHostTest,
OnLanguageIdentificationEvent) {
content::RenderFrameHost* frame_host =
browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
CreateLiveCaptionSpeechRecognitionHost(frame_host);
SetLiveCaptionEnabled(true);
OnLanguageIdentificationEvent(
frame_host, "en-US", media::mojom::ConfidenceLevel::kHighlyConfident);
}
IN_PROC_BROWSER_TEST_F(LiveCaptionSpeechRecognitionHostTest,
OnSpeechRecognitionError) {
content::RenderFrameHost* frame_host =
browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
CreateLiveCaptionSpeechRecognitionHost(frame_host);
SetLiveCaptionEnabled(true);
OnSpeechRecognitionError(frame_host);
}
#if defined(OS_MAC) || defined(OS_CHROMEOS)
IN_PROC_BROWSER_TEST_F(LiveCaptionSpeechRecognitionHostTest,
MediaEffectivelyFullscreenChanged) {
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
content::RenderFrameHost* frame_host = web_contents->GetMainFrame();
CreateLiveCaptionSpeechRecognitionHost(frame_host);
EXPECT_TRUE(content::NavigateToURL(
web_contents, embedded_test_server()->GetURL("/media/fullscreen.html")));
SetLiveCaptionEnabled(true);
EXPECT_TRUE(HasBubbleController());
FullscreenEventsWaiter waiter(web_contents);
EXPECT_TRUE(content::ExecJs(web_contents, "makeFullscreen('small_video')"));
waiter.Wait();
EXPECT_TRUE(HasBubbleController());
EXPECT_TRUE(content::ExecJs(web_contents, "exitFullscreen()"));
waiter.Wait();
EXPECT_TRUE(HasBubbleController());
}
#endif
} // namespace captions