blob: 3fe9ffc07cd0d6e7830c25af9047d9304a96d108 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/speech/on_device_speech_recognition_impl.h"
#include <memory>
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/test/run_until.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/browsing_data/chrome_browsing_data_remover_constants.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/soda/soda_installer.h"
#include "content/public/browser/document_user_data.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browsing_data_remover_test_util.h"
#include "media/base/media_switches.h"
#include "media/mojo/mojom/speech_recognizer.mojom.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "url/gurl.h"
namespace {
constexpr char kEnglishLanguageCode[] = "en-US";
constexpr char kEnglishAlternateLocaleCode[] = "en-AU";
constexpr char kFrenchLanguageCode[] = "fr-FR";
constexpr char kInvalidLanguageCode[] = "xx-XX";
} // namespace
namespace speech {
class OnDeviceSpeechRecognitionImplBrowserTest : public InProcessBrowserTest {
public:
OnDeviceSpeechRecognitionImplBrowserTest() {
scoped_feature_list_.InitAndEnableFeature(media::kOnDeviceWebSpeech);
}
OnDeviceSpeechRecognitionImplBrowserTest(
const OnDeviceSpeechRecognitionImplBrowserTest&) = delete;
OnDeviceSpeechRecognitionImplBrowserTest& operator=(
const OnDeviceSpeechRecognitionImplBrowserTest&) = delete;
~OnDeviceSpeechRecognitionImplBrowserTest() override = default;
// InProcessBrowserTest
void SetUpOnMainThread() override;
void OnDeviceWebSpeechAvailableCallback(
media::mojom::AvailabilityStatus actual_status);
void OnDeviceWebSpeechAvailableCallbackAndAssertStatus(
media::mojom::AvailabilityStatus expected_status,
media::mojom::AvailabilityStatus actual_status);
void Install();
void InstallCallback(bool expected_success, bool actual_success);
void WaitUntilAvailable(const std::string& language);
void NavigateToUrl(const std::string& url_string);
void ClearSiteContentSettings();
OnDeviceSpeechRecognitionImpl* on_device_speech_recognition();
protected:
base::test::ScopedFeatureList scoped_feature_list_;
media::mojom::AvailabilityStatus availability_status_;
};
void OnDeviceSpeechRecognitionImplBrowserTest::SetUpOnMainThread() {
host_resolver()->AddRule("*", "127.0.0.1");
embedded_https_test_server().ServeFilesFromSourceDirectory(
GetChromeTestDataDir());
ASSERT_TRUE(embedded_https_test_server().Start());
speech::SodaInstaller::GetInstance()->NeverDownloadSodaForTesting();
}
void OnDeviceSpeechRecognitionImplBrowserTest::
OnDeviceWebSpeechAvailableCallback(
media::mojom::AvailabilityStatus actual_status) {
OnDeviceWebSpeechAvailableCallbackAndAssertStatus(actual_status,
actual_status);
}
void OnDeviceSpeechRecognitionImplBrowserTest::
OnDeviceWebSpeechAvailableCallbackAndAssertStatus(
media::mojom::AvailabilityStatus expected_status,
media::mojom::AvailabilityStatus actual_status) {
ASSERT_EQ(expected_status, actual_status);
availability_status_ = actual_status;
}
void OnDeviceSpeechRecognitionImplBrowserTest::Install() {
// Install on-device speech recognition and simulate the installation of the
// SODA library and language pack.
on_device_speech_recognition()->Install(
{kEnglishLanguageCode},
base::BindOnce(&OnDeviceSpeechRecognitionImplBrowserTest::InstallCallback,
base::Unretained(this), true));
speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting(
speech::LanguageCode::kEnUs);
}
void OnDeviceSpeechRecognitionImplBrowserTest::InstallCallback(
bool expected_success,
bool actual_success) {
ASSERT_EQ(expected_success, actual_success);
}
void OnDeviceSpeechRecognitionImplBrowserTest::WaitUntilAvailable(
const std::string& language) {
ASSERT_TRUE(base::test::RunUntil([&]() {
on_device_speech_recognition()->Available(
{language}, base::BindOnce(&OnDeviceSpeechRecognitionImplBrowserTest::
OnDeviceWebSpeechAvailableCallback,
base::Unretained(this)));
return availability_status_ == media::mojom::AvailabilityStatus::kAvailable;
}));
}
void OnDeviceSpeechRecognitionImplBrowserTest::NavigateToUrl(
const std::string& url_string) {
const GURL kUrl(
embedded_https_test_server().GetURL(url_string, "/empty.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), kUrl));
}
void OnDeviceSpeechRecognitionImplBrowserTest::ClearSiteContentSettings() {
content::BrowsingDataRemover* remover =
browser()->profile()->GetBrowsingDataRemover();
content::BrowsingDataRemoverCompletionObserver observer(remover);
remover->RemoveAndReply(
base::Time(), base::Time::Max(),
chrome_browsing_data_remover::DATA_TYPE_CONTENT_SETTINGS,
chrome_browsing_data_remover::ALL_ORIGIN_TYPES, &observer);
observer.BlockUntilCompletion();
}
OnDeviceSpeechRecognitionImpl*
OnDeviceSpeechRecognitionImplBrowserTest::on_device_speech_recognition() {
return OnDeviceSpeechRecognitionImpl::GetOrCreateForCurrentDocument(
chrome_test_utils::GetActiveWebContents(this)->GetPrimaryMainFrame());
}
IN_PROC_BROWSER_TEST_F(OnDeviceSpeechRecognitionImplBrowserTest, Available) {
on_device_speech_recognition()->Available(
{kInvalidLanguageCode},
base::BindOnce(&OnDeviceSpeechRecognitionImplBrowserTest::
OnDeviceWebSpeechAvailableCallbackAndAssertStatus,
base::Unretained(this),
media::mojom::AvailabilityStatus::kUnavailable));
on_device_speech_recognition()->Available(
{kEnglishLanguageCode},
base::BindOnce(&OnDeviceSpeechRecognitionImplBrowserTest::
OnDeviceWebSpeechAvailableCallbackAndAssertStatus,
base::Unretained(this),
media::mojom::AvailabilityStatus::kDownloadable));
}
IN_PROC_BROWSER_TEST_F(OnDeviceSpeechRecognitionImplBrowserTest, Install) {
NavigateToUrl("foo.com");
// Verify that installing an invalid language code returns false.
on_device_speech_recognition()->Install(
{kInvalidLanguageCode},
base::BindOnce(&OnDeviceSpeechRecognitionImplBrowserTest::InstallCallback,
base::Unretained(this), false));
// Verify that on-device speech recognition is downloadable before it is
// installed.
on_device_speech_recognition()->Available(
{kEnglishLanguageCode},
base::BindOnce(&OnDeviceSpeechRecognitionImplBrowserTest::
OnDeviceWebSpeechAvailableCallbackAndAssertStatus,
base::Unretained(this),
media::mojom::AvailabilityStatus::kDownloadable));
Install();
// Verify that on-device speech recognition is available after it is
// installed.
WaitUntilAvailable(kEnglishLanguageCode);
// On-device speech recognition availability is masked by origin, so the
// previously installed language pack should not be available to a different
// origin even if it's already installed.
NavigateToUrl("bar.com");
on_device_speech_recognition()->Available(
{kEnglishLanguageCode},
base::BindOnce(&OnDeviceSpeechRecognitionImplBrowserTest::
OnDeviceWebSpeechAvailableCallbackAndAssertStatus,
base::Unretained(this),
media::mojom::AvailabilityStatus::kDownloadable));
// Verify that on-device speech recognition can be installed on the second
// origin.
Install();
WaitUntilAvailable(kEnglishLanguageCode);
// Verify that clearing site content settings resets the on-device speech
// recognition mask for both origins.
ClearSiteContentSettings();
on_device_speech_recognition()->Available(
{kEnglishLanguageCode},
base::BindOnce(&OnDeviceSpeechRecognitionImplBrowserTest::
OnDeviceWebSpeechAvailableCallbackAndAssertStatus,
base::Unretained(this),
media::mojom::AvailabilityStatus::kDownloadable));
NavigateToUrl("foo.com");
on_device_speech_recognition()->Available(
{kEnglishLanguageCode},
base::BindOnce(&OnDeviceSpeechRecognitionImplBrowserTest::
OnDeviceWebSpeechAvailableCallbackAndAssertStatus,
base::Unretained(this),
media::mojom::AvailabilityStatus::kDownloadable));
}
// Verify that the `Available()` and `Install()` methods can handle multiple
// languages.
IN_PROC_BROWSER_TEST_F(OnDeviceSpeechRecognitionImplBrowserTest,
MultipleLanguages) {
NavigateToUrl("foo.com");
on_device_speech_recognition()->Available(
{kEnglishLanguageCode, kInvalidLanguageCode},
base::BindOnce(&OnDeviceSpeechRecognitionImplBrowserTest::
OnDeviceWebSpeechAvailableCallbackAndAssertStatus,
base::Unretained(this),
media::mojom::AvailabilityStatus::kUnavailable));
on_device_speech_recognition()->Available(
{kEnglishLanguageCode, kFrenchLanguageCode},
base::BindOnce(&OnDeviceSpeechRecognitionImplBrowserTest::
OnDeviceWebSpeechAvailableCallbackAndAssertStatus,
base::Unretained(this),
media::mojom::AvailabilityStatus::kDownloadable));
on_device_speech_recognition()->Install(
{kEnglishLanguageCode},
base::BindOnce(&OnDeviceSpeechRecognitionImplBrowserTest::InstallCallback,
base::Unretained(this), true));
speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting(
speech::LanguageCode::kEnUs);
WaitUntilAvailable(kEnglishLanguageCode);
on_device_speech_recognition()->Available(
{kEnglishLanguageCode, kFrenchLanguageCode},
base::BindOnce(&OnDeviceSpeechRecognitionImplBrowserTest::
OnDeviceWebSpeechAvailableCallbackAndAssertStatus,
base::Unretained(this),
media::mojom::AvailabilityStatus::kDownloadable));
on_device_speech_recognition()->Install(
{kFrenchLanguageCode},
base::BindOnce(&OnDeviceSpeechRecognitionImplBrowserTest::InstallCallback,
base::Unretained(this), true));
speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting(
speech::LanguageCode::kFrFr);
WaitUntilAvailable(kFrenchLanguageCode);
on_device_speech_recognition()->Available(
{kEnglishLanguageCode, kFrenchLanguageCode},
base::BindOnce(&OnDeviceSpeechRecognitionImplBrowserTest::
OnDeviceWebSpeechAvailableCallbackAndAssertStatus,
base::Unretained(this),
media::mojom::AvailabilityStatus::kAvailable));
}
// Verify that installing different locales of the same language works.
IN_PROC_BROWSER_TEST_F(OnDeviceSpeechRecognitionImplBrowserTest,
AlternateLocales) {
NavigateToUrl("foo.com");
on_device_speech_recognition()->Available(
{kEnglishAlternateLocaleCode},
base::BindOnce(&OnDeviceSpeechRecognitionImplBrowserTest::
OnDeviceWebSpeechAvailableCallbackAndAssertStatus,
base::Unretained(this),
media::mojom::AvailabilityStatus::kDownloadable));
on_device_speech_recognition()->Install(
{kEnglishLanguageCode},
base::BindOnce(&OnDeviceSpeechRecognitionImplBrowserTest::InstallCallback,
base::Unretained(this), true));
speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting(
speech::LanguageCode::kEnUs);
WaitUntilAvailable(kEnglishLanguageCode);
on_device_speech_recognition()->Available(
{kEnglishAlternateLocaleCode},
base::BindOnce(&OnDeviceSpeechRecognitionImplBrowserTest::
OnDeviceWebSpeechAvailableCallbackAndAssertStatus,
base::Unretained(this),
media::mojom::AvailabilityStatus::kAvailable));
}
// Verify that passing in empty parameters work as expected.
IN_PROC_BROWSER_TEST_F(OnDeviceSpeechRecognitionImplBrowserTest,
EmptyParameters) {
NavigateToUrl("foo.com");
on_device_speech_recognition()->Available(
{}, base::BindOnce(&OnDeviceSpeechRecognitionImplBrowserTest::
OnDeviceWebSpeechAvailableCallbackAndAssertStatus,
base::Unretained(this),
media::mojom::AvailabilityStatus::kUnavailable));
on_device_speech_recognition()->Install(
{},
base::BindOnce(&OnDeviceSpeechRecognitionImplBrowserTest::InstallCallback,
base::Unretained(this), false));
}
IN_PROC_BROWSER_TEST_F(OnDeviceSpeechRecognitionImplBrowserTest,
FileSchemeUrl) {
base::ScopedAllowBlockingForTesting allow_blocking;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL("file:///empty.html")));
on_device_speech_recognition()->Available(
{kEnglishLanguageCode},
base::BindOnce(&OnDeviceSpeechRecognitionImplBrowserTest::
OnDeviceWebSpeechAvailableCallbackAndAssertStatus,
base::Unretained(this),
media::mojom::AvailabilityStatus::kDownloadable));
on_device_speech_recognition()->Install(
{kEnglishLanguageCode},
base::BindOnce(&OnDeviceSpeechRecognitionImplBrowserTest::InstallCallback,
base::Unretained(this), true));
speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting(
speech::LanguageCode::kEnUs);
WaitUntilAvailable(kEnglishLanguageCode);
on_device_speech_recognition()->Available(
{kEnglishAlternateLocaleCode},
base::BindOnce(&OnDeviceSpeechRecognitionImplBrowserTest::
OnDeviceWebSpeechAvailableCallbackAndAssertStatus,
base::Unretained(this),
media::mojom::AvailabilityStatus::kAvailable));
}
} // namespace speech