blob: 67e9de98b40e7cbe95bbc4e941f5ad31ae553e2c [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <iterator>
#include <memory>
#include "base/command_line.h"
#include "base/containers/fixed_flat_set.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/native_library.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/ai/ai_test_utils.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/component_updater/translate_kit_component_installer.h"
#include "chrome/browser/on_device_translation/component_manager.h"
#include "chrome/browser/on_device_translation/constants.h"
#include "chrome/browser/on_device_translation/language_pack_util.h"
#include "chrome/browser/on_device_translation/pref_names.h"
#include "chrome/browser/on_device_translation/service_controller.h"
#include "chrome/browser/on_device_translation/service_controller_manager.h"
#include "chrome/browser/on_device_translation/test/test_util.h"
#include "chrome/browser/on_device_translation/translation_manager_impl.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profile_test_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/crx_file/id_util.h"
#include "components/language/core/browser/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/services/on_device_translation/public/cpp/features.h"
#include "components/services/on_device_translation/test/test_util.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/url_loader_interceptor.h"
#include "net/dns/mock_host_resolver.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/blink/public/common/features_generated.h"
#include "third_party/blink/public/mojom/on_device_translation/translation_manager.mojom.h"
using ::blink::mojom::CanCreateTranslatorResult;
using ::blink::mojom::TranslatorLanguageCode;
using ::content::JsReplace;
using ::testing::_;
using ::testing::Invoke;
namespace on_device_translation {
namespace {
// Generated by:
// tools/origin_trials/generate_token.py --version 3 --expire-days 3650 \
// https://translation-api.test TranslationAPI
// Token details:
// Version: 3
// Origin: https://translation-api.test:443
// Is Subdomain: None
// Is Third Party: None
// Usage Restriction: None
// Feature: TranslationAPI
// Expiry: 2045451101 (2034-10-26 04:51:41 UTC)
constexpr std::string_view kOriginTrialToken =
"Aydt1qwq7OUQa+NVUSW2n7PSlK9ukCz19p0IAah6P827eCHoiHUUks9mxWwyZd7tADQcEm3/eW"
"4K5+79dQUiOwIAAABheyJvcmlnaW4iOiAiaHR0cHM6Ly90cmFuc2xhdGlvbi1hcGkudGVzdDo0"
"NDMiLCAiZmVhdHVyZSI6ICJUcmFuc2xhdGlvbkFQSSIsICJleHBpcnkiOiAyMDQ1NDUxMTAxfQ"
"==";
constexpr auto kLanguagePackKeys = base::MakeFixedFlatSet<LanguagePackKey>({
LanguagePackKey::kEn_Es, LanguagePackKey::kEn_Ja,
LanguagePackKey::kAr_En, LanguagePackKey::kBn_En,
LanguagePackKey::kDe_En, LanguagePackKey::kEn_Fr,
LanguagePackKey::kEn_Hi, LanguagePackKey::kEn_It,
LanguagePackKey::kEn_Ko, LanguagePackKey::kEn_Nl,
LanguagePackKey::kEn_Pl, LanguagePackKey::kEn_Pt,
LanguagePackKey::kEn_Ru, LanguagePackKey::kEn_Th,
LanguagePackKey::kEn_Tr, LanguagePackKey::kEn_Vi,
LanguagePackKey::kEn_Zh, LanguagePackKey::kEn_ZhHant,
LanguagePackKey::kBg_En, LanguagePackKey::kCs_En,
LanguagePackKey::kDa_En, LanguagePackKey::kEl_En,
LanguagePackKey::kEn_Fi, LanguagePackKey::kEn_Hr,
LanguagePackKey::kEn_Hu, LanguagePackKey::kEn_Id,
LanguagePackKey::kEn_Iw, LanguagePackKey::kEn_Lt,
LanguagePackKey::kEn_No, LanguagePackKey::kEn_Ro,
LanguagePackKey::kEn_Sk, LanguagePackKey::kEn_Sl,
LanguagePackKey::kEn_Sv, LanguagePackKey::kEn_Uk,
LanguagePackKey::kEn_Kn, LanguagePackKey::kEn_Ta,
LanguagePackKey::kEn_Te, LanguagePackKey::kEn_Mr,
});
static_assert(std::size(kLanguagePackKeys) ==
static_cast<size_t>(LanguagePackKey::kMaxValue) + 1);
std::string GetPreferredLanguageString(
const base::span<const LanguagePackKey>& language_pack_keys) {
// Get unique set of language codes from the keys.
std::set<std::string_view> language_codes;
for (const auto& language_pack_key : language_pack_keys) {
language_codes.insert(GetSourceLanguageCode(language_pack_key));
language_codes.insert(GetTargetLanguageCode(language_pack_key));
}
// Create a preferred string
std::string selected_languages = "";
for (auto language_code : language_codes) {
selected_languages += language_code;
selected_languages += ",";
}
// Remove the extra comma at the end.
selected_languages.pop_back();
return selected_languages;
}
// Handles HTTP requests to `path` with `content` as the response body.
// `content` is expected to be JavaScript; the response mime type is always
// set to "text/javascript". Invokes `done_callback` after serving the HTTP
// request.
std::unique_ptr<net::test_server::HttpResponse> RespondWithJS(
const std::string& path,
const std::string& content,
base::OnceClosure done_callback,
const net::test_server::HttpRequest& request) {
GURL request_url = request.GetURL();
if (request_url.path() != path) {
return nullptr;
}
auto response = std::make_unique<net::test_server::BasicHttpResponse>();
response->set_content_type("text/javascript");
response->set_content(content);
std::move(done_callback).Run();
return response;
}
// Sets the path of the mock library to the command line.
void SetMockLibraryPathToCommandLine(base::CommandLine* command_line) {
command_line->AppendSwitchPath(kTranslateKitBinaryPath, GetMockLibraryPath());
}
// Writes fake dictionary data to a file and sets the path of the file to the
// command line.
void WriteFakeDictionaryDataAndSetCommandLine(LanguagePackKey key,
const base::FilePath& temp_dir,
base::CommandLine* command_line) {
const auto dict_dir_path =
temp_dir.AppendASCII(GetPackageInstallDirName(key));
const auto dict_path = dict_dir_path.AppendASCII("dict.dat");
CHECK(base::CreateDirectory(dict_dir_path));
CHECK(base::File(dict_path, base::File::FLAG_CREATE | base::File::FLAG_WRITE)
.WriteAndCheck(0, base::as_byte_span(CreateFakeDictionaryData(
GetSourceLanguageCode(key),
GetTargetLanguageCode(key)))));
command_line->AppendSwitchASCII(
"translate-kit-packages",
base::StrCat({GetSourceLanguageCode(key), ",", GetTargetLanguageCode(key),
",", dict_dir_path.AsUTF8Unsafe()}));
}
// Returns a string representation of the result of CanCreateTranslator().
std::string_view GetCanCreateTranslatorResultString(
CanCreateTranslatorResult result) {
switch (result) {
case CanCreateTranslatorResult::kReadily:
return "available";
case CanCreateTranslatorResult::kAfterDownloadLibraryNotReady:
case CanCreateTranslatorResult::kAfterDownloadLanguagePackNotReady:
case CanCreateTranslatorResult::
kAfterDownloadLibraryAndLanguagePackNotReady:
case CanCreateTranslatorResult::kAfterDownloadTranslatorCreationRequired:
return "downloadable";
case CanCreateTranslatorResult::kNoNotSupportedLanguage:
case CanCreateTranslatorResult::kNoAcceptLanguagesCheckFailed:
case CanCreateTranslatorResult::kNoExceedsLanguagePackCountLimitation:
case CanCreateTranslatorResult::kNoServiceCrashed:
case CanCreateTranslatorResult::kNoDisallowedByPolicy:
case CanCreateTranslatorResult::kNoExceedsServiceCountLimitation:
return "unavailable";
}
}
void Sleep(base::TimeDelta delay) {
base::RunLoop loop;
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, loop.QuitClosure(), delay);
loop.Run();
}
// An implementation of SupportsUserData to be used in tests.
class TestSupportsUserData : public base::SupportsUserData {
public:
TestSupportsUserData() = default;
~TestSupportsUserData() override = default;
};
} // namespace
class OnDeviceTranslationBrowserTest : public InProcessBrowserTest {
public:
OnDeviceTranslationBrowserTest() {
scoped_feature_list_.InitAndEnableFeature(blink::features::kTranslationAPI);
CHECK(tmp_dir_.CreateUniqueTempDir());
}
~OnDeviceTranslationBrowserTest() override = default;
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
base::FilePath test_data_dir;
base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir);
embedded_https_test_server().ServeFilesFromDirectory(test_data_dir);
ASSERT_TRUE(embedded_https_test_server().Start());
}
protected:
const base::FilePath& GetTempDir() { return tmp_dir_.GetPath(); }
content::BrowserContext* GetBrowserContext() {
return browser()
->tab_strip_model()
->GetActiveWebContents()
->GetBrowserContext();
}
const url::Origin GetLastCommittedOrigin() {
return browser()
->tab_strip_model()
->GetActiveWebContents()
->GetPrimaryMainFrame()
->GetLastCommittedOrigin();
}
// Navigates to an empty page.
void NavigateToEmptyPage() {
CHECK(ui_test_utils::NavigateToURL(
browser(), embedded_https_test_server().GetURL("/empty.html")));
}
// Sets the SelectedLanguages prefs to the given value. This will change the
// AcceptLanguages pref.
void SetSelectedLanguages(const std::string_view value) {
browser()->profile()->GetPrefs()->SetString(
language::prefs::kSelectedLanguages, value);
}
// Sets the SelectedLanguages prefs to support all the languages in the
// `language_pack_keys`. This will change the AcceptLanguages pref.
void SetSelectedLanguages(
const base::span<const LanguagePackKey>& language_pack_keys) {
SetSelectedLanguages(GetPreferredLanguageString(language_pack_keys));
}
// Tests the behavior of availability().
void TestCanTranslateResult(const std::string_view sourceLang,
const std::string_view targetLang,
CanCreateTranslatorResult expected_result) {
NavigateToEmptyPage();
// Call CanCreateTranslator() via mojo interface to verify the detailed
// result.
mojo::Remote<blink::mojom::TranslationManager> remote;
TestSupportsUserData fake_user_data;
TranslationManagerImpl::Bind(GetBrowserContext(), &fake_user_data,
GetLastCommittedOrigin(),
remote.BindNewPipeAndPassReceiver());
base::RunLoop run_loop;
remote->TranslationAvailable(
TranslatorLanguageCode::New(std::string(sourceLang)),
TranslatorLanguageCode::New(std::string(targetLang)),
base::BindLambdaForTesting([&](CanCreateTranslatorResult result) {
EXPECT_EQ(result, expected_result);
run_loop.Quit();
}));
run_loop.Run();
// Need to navigate to an empty page to reset the state of the
// TranslationManagerImpl.
NavigateToEmptyPage();
// Calls TranslationAvailable() via JS API (Translator.availability()) to
// verify the result string.
TestTranslationAvailable(
browser(), sourceLang, targetLang,
GetCanCreateTranslatorResultString(expected_result));
}
content::EvalJsResult EvalJs(std::string_view script,
Browser* target_browser = nullptr) {
return content::EvalJs((target_browser ? target_browser : browser())
->tab_strip_model()
->GetActiveWebContents(),
script);
}
// Evaluates the given script and returns the result string. If the script
// throws an error, returns the error message.
// When `target_browser` is not nullptr, the script is evaluated in the
// context of the given browser, otherwise the script is evaluated in the
// context of the default browser.
std::string EvalJsCatchingError(std::string_view script,
Browser* target_browser = nullptr) {
return EvalJs(base::StringPrintf(R"(
(async () => {
try {
%s
} catch (e) {
return e.toString();
}
})();
)",
script),
target_browser)
.ExtractString();
}
// Creates a console observer for the given pattern. When `target_browser`
// is not nullptr, the observer is created in the context of the given
// browser, otherwise the observer is created in the context of the default
// browser.
std::unique_ptr<content::WebContentsConsoleObserver> CreateConsoleObserver(
const std::string_view pattern,
Browser* target_browser = nullptr) {
auto observer = std::make_unique<content::WebContentsConsoleObserver>(
(target_browser ? target_browser : browser())
->tab_strip_model()
->GetActiveWebContents());
observer->SetPattern(std::string(pattern));
return observer;
}
// Waits for the console observer to receive a message.
void WaitForConsoleObserver(
content::WebContentsConsoleObserver& console_observer) {
ASSERT_TRUE(console_observer.Wait());
EXPECT_FALSE(console_observer.messages().empty());
}
private:
base::ScopedTempDir tmp_dir_;
base::test::ScopedFeatureList scoped_feature_list_;
};
// Tests the behavior of create() when the library is installed before
// the language pack.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest,
CreateTranslatorInstallLibraryAndThenLanguagePack) {
MockComponentManager mock_component_manager(GetTempDir());
NavigateToEmptyPage();
base::RunLoop run_loop_for_register_translate_kit;
EXPECT_CALL(mock_component_manager, RegisterTranslateKitComponentImpl())
.WillOnce(Invoke([&]() { run_loop_for_register_translate_kit.Quit(); }));
base::RunLoop run_loop_for_register_language_pack;
EXPECT_CALL(mock_component_manager,
RegisterTranslateKitLanguagePackComponent(_))
.WillOnce(Invoke([&](LanguagePackKey key) {
EXPECT_EQ(key, LanguagePackKey::kEn_Ja);
run_loop_for_register_language_pack.Quit();
}));
// Create a translator.
EXPECT_EQ(EvalJsCatchingError(R"(
window._testPromise = Translator.create({
sourceLanguage: 'en',
targetLanguage: 'ja',
});
window._testPromiseResolved = false;
window._testPromise.then(() => {
window._testPromiseResolved = true;
});
return 'OK';
)"),
"OK");
// Wait until RegisterTranslateKitComponentImpl() is called.
run_loop_for_register_translate_kit.Run();
// Wait until RegisterTranslateKitLanguagePackComponent() is called.
run_loop_for_register_language_pack.Run();
// Install the mock TranslateKit component.
mock_component_manager.InstallMockTranslateKitComponent();
// The promise of create() should not be resolved yet.
EXPECT_FALSE(EvalJs("window._testPromiseResolved").ExtractBool());
// Install the mock language pack.
mock_component_manager.InstallMockLanguagePack(LanguagePackKey::kEn_Ja);
// Translate "hello" to Japanese.
// Note: the mock TranslateKit component returns the concatenation of the
// content of "dict.dat" in the language pack and the input text.
// See comments in mock_translate_kit_lib.cc for more details.
EXPECT_EQ(EvalJsCatchingError(
"return await (await window._testPromise).translate('hello');"),
"en to ja: hello");
}
// Tests the behavior of create() when the language pack is installed
// before the library.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest,
CreateTranslatorInstallLanguagePackAndThenLibrary) {
MockComponentManager mock_component_manager(GetTempDir());
NavigateToEmptyPage();
base::RunLoop run_loop_for_register_translate_kit;
EXPECT_CALL(mock_component_manager, RegisterTranslateKitComponentImpl())
.WillOnce(Invoke([&]() { run_loop_for_register_translate_kit.Quit(); }));
base::RunLoop run_loop_for_register_language_pack;
EXPECT_CALL(mock_component_manager,
RegisterTranslateKitLanguagePackComponent(_))
.WillOnce(Invoke([&](LanguagePackKey key) {
EXPECT_EQ(key, LanguagePackKey::kEn_Ja);
run_loop_for_register_language_pack.Quit();
}));
// Create a translator.
EXPECT_EQ(EvalJsCatchingError(R"(
window._testPromise = Translator.create({
sourceLanguage: 'en',
targetLanguage: 'ja',
});
window._testPromiseResolved = false;
window._testPromise.then(() => {
window._testPromiseResolved = true;
});
return 'OK';
)"),
"OK");
// Wait until RegisterTranslateKitComponentImpl() is called.
run_loop_for_register_translate_kit.Run();
// Wait until RegisterTranslateKitLanguagePackComponent() is called.
run_loop_for_register_language_pack.Run();
// Install the mock language pack.
mock_component_manager.InstallMockLanguagePack(LanguagePackKey::kEn_Ja);
// The promise of create() should not be resolved yet.
EXPECT_FALSE(EvalJs("window._testPromiseResolved").ExtractBool());
// Install the mock TranslateKit component.
mock_component_manager.InstallMockTranslateKitComponent();
// Translate "hello" to Japanese.
EXPECT_EQ(EvalJsCatchingError(
"return await (await window._testPromise).translate('hello');"),
"en to ja: hello");
}
// Tests the behavior of multiple create() calls with different
// source/target languages.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest,
CreateTranslatorInstallMultipleLanguagePacks) {
MockComponentManager mock_component_manager(GetTempDir());
NavigateToEmptyPage();
base::RunLoop run_loop_for_register_translate_kit;
EXPECT_CALL(mock_component_manager, RegisterTranslateKitComponentImpl())
.WillOnce(Invoke([&]() { run_loop_for_register_translate_kit.Quit(); }));
base::RunLoop run_loop_for_register_en_ja_language_pack;
base::RunLoop run_loop_for_register_en_es_language_pack;
EXPECT_CALL(mock_component_manager,
RegisterTranslateKitLanguagePackComponent(_))
.WillOnce(Invoke([&](LanguagePackKey key) {
EXPECT_EQ(key, LanguagePackKey::kEn_Ja);
// Call RegisterLanguagePack() to avoid redundant calls of
// RegisterTranslateKitLanguagePackComponent().
mock_component_manager.RegisterLanguagePack(key);
run_loop_for_register_en_ja_language_pack.Quit();
}))
.WillOnce(Invoke([&](LanguagePackKey key) {
EXPECT_EQ(key, LanguagePackKey::kEn_Es);
mock_component_manager.RegisterLanguagePack(key);
run_loop_for_register_en_es_language_pack.Quit();
}));
// Create create() multiple times.
// 1. En => Ja.
// 2. En => Es.
// 3. En => Ja.
EXPECT_EQ(EvalJsCatchingError(R"(
window._testEnJaPromise1 = Translator.create({
sourceLanguage: 'en',
targetLanguage: 'ja',
});
window._testEnJaPromise1Resolved = false;
window._testEnJaPromise1.then(() => {
window._testEnJaPromise1Resolved = true;
});
window._testEnEsPromise = Translator.create({
sourceLanguage: 'en',
targetLanguage: 'es',
});
window._testEnEsPromiseResolved = false;
window._testEnEsPromise.then(() => {
window._testEnEsPromiseResolved = true;
});
window._testEnJaPromise2 = Translator.create({
sourceLanguage: 'en',
targetLanguage: 'ja',
});
window._testEnJaPromise2Resolved = false;
window._testEnJaPromise2.then(() => {
window._testEnJaPromise2Resolved = true;
});
return 'OK';
)"),
"OK");
// Wait until RegisterTranslateKitComponentImpl() is called.
run_loop_for_register_translate_kit.Run();
// Wait until RegisterTranslateKitLanguagePackComponent() is called for
// `en_ja` and `en_es`.
run_loop_for_register_en_ja_language_pack.Run();
run_loop_for_register_en_es_language_pack.Run();
// Install the mock TranslateKit component.
mock_component_manager.InstallMockTranslateKitComponent();
// All promises should not be resolved yet.
EXPECT_FALSE(EvalJs("window._testEnJaPromise1Resolved").ExtractBool());
EXPECT_FALSE(EvalJs("window._testEnJaPromise2Resolved").ExtractBool());
EXPECT_FALSE(EvalJs("window._testEnEsPromiseResolved").ExtractBool());
// Install the mock `en_ja` language pack.
mock_component_manager.InstallMockLanguagePack(LanguagePackKey::kEn_Ja);
// Translate to Japanese. Both `en_ja` promises should be resolved now.
EXPECT_EQ(
EvalJsCatchingError(
"return await (await window._testEnJaPromise1).translate('hello');"),
"en to ja: hello");
EXPECT_EQ(
EvalJsCatchingError(
"return await (await window._testEnJaPromise2).translate('hi');"),
"en to ja: hi");
// The promise of `en_es` should not be resolved yet.
EXPECT_FALSE(EvalJs("window._testEnEsPromiseResolved").ExtractBool());
// Install the mock `en_es` language pack.
mock_component_manager.InstallMockLanguagePack(LanguagePackKey::kEn_Es);
// Translate to Spanish. The `en_es` promise should be resolved now.
EXPECT_EQ(
EvalJsCatchingError(
"return await (await window._testEnEsPromise).translate('hello');"),
"en to es: hello");
}
// Tests the behavior of create() when the number of pending tasks
// exceeds the limit.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest,
ExceedMaxPendingTaskCount) {
MockComponentManager mock_component_manager(GetTempDir());
NavigateToEmptyPage();
base::RunLoop run_loop_for_register_translate_kit;
EXPECT_CALL(mock_component_manager, RegisterTranslateKitComponentImpl())
.WillOnce(Invoke([&]() { run_loop_for_register_translate_kit.Quit(); }));
base::RunLoop run_loop_for_register_language_pack;
EXPECT_CALL(mock_component_manager,
RegisterTranslateKitLanguagePackComponent(_))
.WillOnce(Invoke([&](LanguagePackKey key) {
EXPECT_EQ(key, LanguagePackKey::kEn_Ja);
// Call RegisterLanguagePack() to avoid redundant calls of
// RegisterTranslateKitLanguagePackComponent().
mock_component_manager.RegisterLanguagePack(key);
run_loop_for_register_language_pack.Quit();
}));
// Call create() kMaxPendingTaskCount times.
EXPECT_EQ(EvalJsCatchingError(base::StringPrintf(R"(
window._testPromises = [];
window._testPromisesResolved = false;
const kMaxPendingTaskCount = %zd;
for (let i = 0; i < kMaxPendingTaskCount; ++i) {
const promise = Translator.create({
sourceLanguage: 'en',
targetLanguage: 'ja',
});
promise.then(() => {
window._testPromisesResolved = true;
});
window._testPromises.push(promise);
}
return 'OK';
)",
kMaxPendingTaskCount)),
"OK");
// Wait until RegisterTranslateKitComponentImpl() is called.
run_loop_for_register_translate_kit.Run();
// Wait until RegisterTranslateKitLanguagePackComponent() is called.
run_loop_for_register_language_pack.Run();
// Install the mock TranslateKit component.
mock_component_manager.InstallMockTranslateKitComponent();
// Any promise should not be resolved yet.
EXPECT_FALSE(EvalJs("window._testPromisesResolved").ExtractBool());
auto console_observer =
CreateConsoleObserver("Too many Translator API requests are queued.");
// Calling create() one more time fails.
EXPECT_EQ(EvalJsCatchingError(R"(
await Translator.create({
sourceLanguage: 'en',
targetLanguage: 'ja',
});
return 'OK';
)"),
"NotSupportedError: Unable to create translator for the given "
"source and target language.");
// The console message should be logged.
WaitForConsoleObserver(*console_observer);
// The all 1024 promises should not be resolved yet.
EXPECT_FALSE(EvalJs("window._testPromisesResolved").ExtractBool());
// Install the mock language pack.
mock_component_manager.InstallMockLanguagePack(LanguagePackKey::kEn_Ja);
// All promises should be resolved now. The all 1024 translators should
// be able to translate.
EXPECT_EQ(EvalJsCatchingError(R"(
const translators = await Promise.all(window._testPromises);
const results = await Promise.all(translators.map((translator) => {
return translator.translate('hello');
}));
for (const result of results) {
if (result != 'en to ja: hello') {
return `Unexpected result ${result}`;
}
}
return 'OK';
)"),
"OK");
}
// Tests the behavior of TranslationAPILimitLanguagePackCount
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest,
ExceedLanguagePackCount) {
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.ExpectCallRegisterTranslateKitComponentAndInstall();
const base::span<const LanguagePackKey> language_packs =
base::span(kLanguagePackKeys);
NavigateToEmptyPage();
// Get the amount of packages we can install and assert that we have enough
// language packs for this test.
size_t installable_package_count =
on_device_translation::GetInstallablePackageCount(0);
ASSERT_GE(language_packs.size(), installable_package_count + 1);
// Add all the languages we're going to test to the selected languages so we
// don't fail PassAcceptLanguagesCheck.
SetSelectedLanguages(language_packs.first(installable_package_count + 1));
// Test that we can install all the language packs up to the language pack
// limitation.
mock_component_manager.ExpectCallRegisterLanguagePackComponentAndInstall(
language_packs.first(installable_package_count));
for (const auto& language_pack_key :
language_packs.first(installable_package_count)) {
TestSimpleTranslationWorks(browser(), language_pack_key);
}
// The language pack count is equal to the limitation. So no more language
// pack can be downloaded.
auto console_observer = CreateConsoleObserver(
"The Translator API language pack count exceeded the limitation. See "
"https://developer.chrome.com/docs/ai/"
"translator-api?#supported-languages for more details.");
TestCreateTranslator(browser(), language_packs.at(installable_package_count),
"NotSupportedError: Unable to create translator for the "
"given source and target language.");
// The console message should be logged.
WaitForConsoleObserver(*console_observer);
}
// Tests the behavior of the failure of translation.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest, TranslationFailure) {
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.ExpectCallRegisterTranslateKitComponentAndInstall();
mock_component_manager.ExpectCallRegisterLanguagePackComponentAndInstall(
{LanguagePackKey::kEn_Ja});
NavigateToEmptyPage();
// Tries to translate "SIMULATE_ERROR" to Japanese. This "SIMULATE_ERROR"
// causes failure in the mock TranslateKit component. See comments in
// mock_translate_kit_lib.cc.
EXPECT_EQ(EvalJsCatchingError(R"(
const translator = await Translator.create({
sourceLanguage: 'en',
targetLanguage: 'ja',
});
return await translator.translate('SIMULATE_ERROR');
)"),
"UnknownError: Other generic failures occurred.");
}
// Tests the behavior of the crash of calling translate().
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest, CrashWhileTranslating) {
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.ExpectCallRegisterTranslateKitComponentAndInstall();
mock_component_manager.ExpectCallRegisterLanguagePackComponentAndInstall(
{LanguagePackKey::kEn_Ja});
NavigateToEmptyPage();
// Tries to translate "CAUSE_CRASH" to Japanese. This "CAUSE_CRASH" causes
// a crash in the mock TranslateKit component. See comments in
// mock_translate_kit_lib.cc.
EXPECT_EQ(EvalJsCatchingError(R"(
window._translator = await Translator.create({
sourceLanguage: 'en',
targetLanguage: 'ja',
});
return await window._translator.translate('CAUSE_CRASH');
)"),
"UnknownError: Other generic failures occurred.");
// After the crash, the translator is not usable.
EXPECT_EQ(EvalJsCatchingError(
"return await window._translator.translate('hello');"),
"UnknownError: Other generic failures occurred.");
// But a new translator can be created and used.
TestSimpleTranslationWorks(browser(), "en", "ja");
}
// Tests the behavior of failing to load the library
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest,
CreateTranslatorErrorLibraryLoadFailed) {
MockComponentManager mock_component_manager(GetTempDir());
EXPECT_CALL(mock_component_manager, RegisterTranslateKitComponentImpl())
.Times(1);
mock_component_manager.InstallEmptyMockComponent();
mock_component_manager.ExpectCallRegisterLanguagePackComponentAndInstall(
{LanguagePackKey::kEn_Ja});
NavigateToEmptyPage();
auto console_observer =
#if BUILDFLAG(IS_WIN)
// On Windows, the service crashes when failed to preload the library
// in PreLockdownSandboxHook().
CreateConsoleObserver("The translation service crashed.");
#else
CreateConsoleObserver("Failed to load the translation library.");
#endif // BUILDFLAG(IS_WIN)
EXPECT_EQ(EvalJsCatchingError(R"(
const translator = await Translator.create({
sourceLanguage: 'en',
targetLanguage: 'ja',
});
)"),
"NotSupportedError: Unable to create translator for the given "
"source and target language.");
// The console message should be logged.
WaitForConsoleObserver(*console_observer);
}
// Tests the behavior of failing to load the library as a result of the
// incompatibility of the library.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest,
CreateTranslatorErrorLibraryIncompatible) {
MockComponentManager mock_component_manager(GetTempDir());
EXPECT_CALL(mock_component_manager, RegisterTranslateKitComponentImpl())
.Times(1);
mock_component_manager.InstallMockInvalidFunctionPointerLibraryComponent();
mock_component_manager.ExpectCallRegisterLanguagePackComponentAndInstall(
{LanguagePackKey::kEn_Ja});
NavigateToEmptyPage();
auto console_observer =
CreateConsoleObserver("The translation library is not compatible.");
EXPECT_EQ(EvalJsCatchingError(R"(
const translator = await Translator.create({
sourceLanguage: 'en',
targetLanguage: 'ja',
});
)"),
"NotSupportedError: Unable to create translator for the given "
"source and target language.");
// The console message should be logged.
WaitForConsoleObserver(*console_observer);
}
// Tests the behavior of failing to load the library as a result of the
// initialization failure of the library.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest,
CreateTranslatorErrorLibraryFailedToInitialize) {
MockComponentManager mock_component_manager(GetTempDir());
EXPECT_CALL(mock_component_manager, RegisterTranslateKitComponentImpl())
.Times(1);
mock_component_manager.InstallMockFailingLibraryComponent();
mock_component_manager.ExpectCallRegisterLanguagePackComponentAndInstall(
{LanguagePackKey::kEn_Ja});
NavigateToEmptyPage();
auto console_observer =
CreateConsoleObserver("Failed to initialize the translation library.");
EXPECT_EQ(EvalJsCatchingError(R"(
const translator = await Translator.create({
sourceLanguage: 'en',
targetLanguage: 'ja',
});
)"),
"NotSupportedError: Unable to create translator for the given "
"source and target language.");
// The console message should be logged.
WaitForConsoleObserver(*console_observer);
}
// Tests the behavior of failure of translator creation in the library.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest,
CreateTranslatorErrorLibraryTranslatorCreationFailed) {
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.ExpectCallRegisterTranslateKitComponentAndInstall();
mock_component_manager.ExpectCallRegisterLanguagePackComponentAndInstall(
{LanguagePackKey::kEn_Ja});
NavigateToEmptyPage();
auto console_observer = CreateConsoleObserver(
"The translation library failed to create a translator.");
EXPECT_EQ(EvalJsCatchingError(R"(
const translator = await Translator.create({
sourceLanguage: 'ja',
targetLanguage: 'en',
});
)"),
"NotSupportedError: Unable to create translator for the given "
"source and target language.");
// The console message should be logged.
WaitForConsoleObserver(*console_observer);
}
// Tests progress monitor behavior.
class OnDeviceTranslationProgressMonitorBrowserTest
: public OnDeviceTranslationBrowserTest {
public:
OnDeviceTranslationProgressMonitorBrowserTest() = default;
~OnDeviceTranslationProgressMonitorBrowserTest() override = default;
void SetUpOnMainThread() override {
OnDeviceTranslationBrowserTest::SetUpOnMainThread();
NavigateToEmptyPage();
translation_manager_ = std::make_unique<MockTranslationManagerImpl>(
GetBrowserContext(), GetLastCommittedOrigin());
// Setup a ComponentUpdateService to be used by the TranslationManager.
EXPECT_CALL(*translation_manager_, GetComponentUpdateService())
.WillOnce(Invoke([&]() { return &component_update_service_; }));
// `GetComponentIDs` should be called by the
// `AIModelDownloadProgressManager` to filter out existing downloads.
EXPECT_CALL(component_update_service_, GetComponentIDs()).Times(1);
}
void TearDownOnMainThread() override {
translation_manager_.reset();
OnDeviceTranslationBrowserTest::TearDownOnMainThread();
}
void TranslateAndMonitorProgress(std::string& source_language,
std::string& target_language) {
std::set<LanguagePackKey> language_pack_keys =
CalculateRequiredLanguagePacks(source_language, target_language);
base::RunLoop run_loop_translate_kit;
EXPECT_CALL(component_manager_, RegisterTranslateKitComponentImpl())
.WillOnce(Invoke([&]() { run_loop_translate_kit.Quit(); }));
base::RunLoop run_loop_language_pack;
EXPECT_CALL(component_manager_,
RegisterTranslateKitLanguagePackComponent(_))
.WillRepeatedly(Invoke([&](LanguagePackKey key) {
EXPECT_EQ(language_pack_keys.erase(key), 1u);
if (language_pack_keys.empty()) {
run_loop_language_pack.Quit();
}
}));
EXPECT_EQ(EvalJsCatchingError(base::StringPrintf(R"(
self.progressEvents = [];
self.createTranslatorPromise = Translator.create({
sourceLanguage: '%s',
targetLanguage: '%s',
monitor(m) {
m.addEventListener(
'downloadprogress', ({loaded, total}) => {
self.progressEvents.push({loaded, total});
});
},
});
return "OK";
)",
source_language,
target_language)),
"OK");
run_loop_translate_kit.Run();
run_loop_language_pack.Run();
}
AITestUtils::FakeComponent GetComponentForTranslateKit(uint64_t total_bytes) {
return {component_updater::TranslateKitComponentInstallerPolicy::
GetExtensionId(),
total_bytes};
}
AITestUtils::FakeComponent GetComponentForLanguagePack(
LanguagePackKey language_pack_key,
uint64_t total_bytes) {
const LanguagePackComponentConfig& config =
GetLanguagePackComponentConfig(language_pack_key);
std::string id =
crx_file::id_util::GenerateIdFromHash(config.public_key_sha);
return {id, total_bytes};
}
void SendUpdate(AITestUtils::FakeComponent component,
uint64_t downloaded_bytes) {
component_update_service_.SendUpdate(component.CreateUpdateItem(
update_client::ComponentState::kDownloading, downloaded_bytes));
}
double NormalizedProgress(uint64_t downloaded_bytes, uint64_t total_bytes) {
// `AIUtils::NormalizeModelDownloadProgress` normalizes to 0 - 0x10000
// range. We divide it by 0x10000 (65536) again to get it in the 0.0 - 1.0
// range.
return AIUtils::NormalizeModelDownloadProgress(downloaded_bytes,
total_bytes) /
65536.0;
}
void FinishInstalling(std::string& source_language,
std::string& target_language) {
component_manager_.InstallMockTranslateKitComponentLater();
std::set<LanguagePackKey> language_pack_keys =
CalculateRequiredLanguagePacks(source_language, target_language);
for (LanguagePackKey language_pack_key : language_pack_keys) {
component_manager_.InstallMockLanguagePackLater(language_pack_key);
}
}
void ExpectUpdatesAre(const std::vector<double>& expected_updates) {
base::Value::List actual_updates = EvalJs(R"((async () => {
await self.createTranslatorPromise;
return self.progressEvents;
})())")
.ExtractList();
ASSERT_EQ(actual_updates.size(), expected_updates.size());
for (size_t i = 0; i < actual_updates.size(); i++) {
auto& actual_update = actual_updates[i].GetDict();
std::optional<double> actual_loaded = actual_update.FindDouble("loaded");
std::optional<double> actual_total = actual_update.FindDouble("total");
ASSERT_TRUE(actual_loaded);
ASSERT_TRUE(actual_total);
double expected_loaded = expected_updates[i];
EXPECT_EQ(*actual_loaded, expected_loaded);
EXPECT_EQ(*actual_total, 1);
}
}
private:
MockComponentManager component_manager_{GetTempDir()};
AITestUtils::MockComponentUpdateService component_update_service_;
std::unique_ptr<MockTranslationManagerImpl> translation_manager_;
base::test::ScopedFeatureList scoped_feature_list_;
};
// Tests that progress events are received properly when translation requires
// one language pack.
//
// TODO(crbug.com/403592445): Add another test for when translation requires two
// language packs. It's not possible currently since the browser tests can't
// translate between two non-english languages.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationProgressMonitorBrowserTest,
ReceivesProgressEventsForOneLanguagePack) {
std::string source_language = "en";
std::string target_language = "ja";
TranslateAndMonitorProgress(source_language, target_language);
// Components we expect to receive updates for.
AITestUtils::FakeComponent translation_kit =
GetComponentForTranslateKit(4321);
AITestUtils::FakeComponent en_ja_language_pack =
GetComponentForLanguagePack(LanguagePackKey::kEn_Ja, 1234);
// The downloaded bytes and total bytes for all components.
uint64_t downloaded_bytes = 0;
uint64_t total_bytes =
translation_kit.total_bytes() + en_ja_language_pack.total_bytes();
std::vector<double> expected_updates = {};
// Receives the zero update.
{
expected_updates.emplace_back(0);
SendUpdate(translation_kit, 0);
SendUpdate(en_ja_language_pack, 0);
}
// Receives an update for translation kit normalized to the total_bytes.
{
Sleep(base::Milliseconds(100));
uint64_t update_bytes = 999;
downloaded_bytes += update_bytes;
SendUpdate(translation_kit, update_bytes);
expected_updates.emplace_back(
NormalizedProgress(downloaded_bytes, total_bytes));
}
// Receives an update for the en ja language pack normalized to the
// total_bytes.
{
Sleep(base::Milliseconds(100));
uint64_t update_bytes = 300;
downloaded_bytes += update_bytes;
SendUpdate(en_ja_language_pack, update_bytes);
expected_updates.emplace_back(
NormalizedProgress(downloaded_bytes, total_bytes));
}
// Receives the final one update when all bytes are loaded.
{
SendUpdate(translation_kit, translation_kit.total_bytes());
SendUpdate(en_ja_language_pack, en_ja_language_pack.total_bytes());
expected_updates.emplace_back(1);
}
FinishInstalling(source_language, target_language);
ExpectUpdatesAre(expected_updates);
}
// Tests V1 behavior.
class OnDeviceTranslationV1BrowserTest : public OnDeviceTranslationBrowserTest {
public:
OnDeviceTranslationV1BrowserTest() {
scoped_feature_list_.InitAndEnableFeature(
blink::features::kTranslationAPIV1);
}
~OnDeviceTranslationV1BrowserTest() override = default;
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// The language model limit is not triggered when the V1 flag is enabled.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationV1BrowserTest,
NoLanguageModelLimitation) {
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.ExpectCallRegisterTranslateKitComponentAndInstall();
const base::span<const LanguagePackKey> language_packs =
base::span(kLanguagePackKeys);
NavigateToEmptyPage();
// Expect that the number of available language packs is less than the
// installable language pack size, given there is no limitation in place.
size_t installable_package_count =
on_device_translation::GetInstallablePackageCount(0);
ASSERT_LE(language_packs.size() + 1, installable_package_count);
// Add all the languages we're going to test to the selected languages so we
// don't fail `PassAcceptLanguagesCheck`.
SetSelectedLanguages(language_packs);
// Test that we can install all of the possible language packs for
// translation.
mock_component_manager.ExpectCallRegisterLanguagePackComponentAndInstall(
language_packs);
for (const auto& language_pack_key : language_packs) {
TestSimpleTranslationWorks(browser(), language_pack_key);
}
// Get the last language pack key.
LanguagePackKey last_language_pack = *(language_packs.end() - 1);
// Confirm that the last language pack install succeeded.
TestTranslationAvailable(browser(), GetSourceLanguageCode(last_language_pack),
GetTargetLanguageCode(last_language_pack),
"available");
}
// Tests that `Translator.availability()` for a translation
// containing a language outside of English + the user's preferred languages.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationV1BrowserTest,
TranslatorAvailabilityMaskedForNonPreferredLanguages) {
SetSelectedLanguages("fr");
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.InstallMockTranslateKitComponent();
mock_component_manager.InstallMockLanguagePack(LanguagePackKey::kEn_Fr);
NavigateToEmptyPage();
// Translation is not available for unsupported languages.
TestTranslationAvailable(browser(), "fr", "abcxyz", "unavailable");
// The Japanese language pack needs to be downloaded in order for
// translation between Japanese and French to be available.
TestTranslationAvailable(browser(), "ja", "fr", "downloadable");
// Even if all required language packs are installed, the
// Japanese <-> French translation availability status remains
// "downloadable" because Japanese is not included in English +
// preferred languages.
mock_component_manager.InstallMockLanguagePack(LanguagePackKey::kEn_Ja);
TestTranslationAvailable(browser(), "ja", "fr", "downloadable");
}
// A delay is triggered for a "downloadable" translation containing a language
// outside of English + preferred languages.
IN_PROC_BROWSER_TEST_F(
OnDeviceTranslationV1BrowserTest,
CreateTranslator_Delay_ForMaskedDownloadableTranslation) {
// Setup Translate Kit Component and select Spanish as the preferred language.
SetSelectedLanguages("en,es");
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.InstallMockTranslateKitComponent();
NavigateToEmptyPage();
auto manager =
MockTranslationManagerImpl(GetBrowserContext(), GetLastCommittedOrigin());
// Simulate the download of an additional language pack (Japanese) by another
// site.
mock_component_manager.InstallMockLanguagePack(LanguagePackKey::kEn_Ja);
// The delay is triggered upon the initial translator creation for Japanese,
// given that it is not a preferred language.
EXPECT_CALL(manager, GetTranslatorDownloadDelay()).Times(1);
TestSimpleTranslationWorks(browser(), "en", "ja");
}
// No delay is triggered for a "downloadable" translation between English +
// preferred languages.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationV1BrowserTest,
CreateTranslator_NoDelay_DownloadableTranslation) {
SetSelectedLanguages("en,es");
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.InstallMockTranslateKitComponent();
NavigateToEmptyPage();
auto manager =
MockTranslationManagerImpl(GetBrowserContext(), GetLastCommittedOrigin());
mock_component_manager.ExpectCallRegisterLanguagePackComponentAndInstall(
{LanguagePackKey::kEn_Es});
EXPECT_CALL(manager, GetTranslatorDownloadDelay()).Times(0);
TestSimpleTranslationWorks(browser(), "en", "es");
// No delay is triggered now that the translation is "available".
EXPECT_CALL(manager, GetTranslatorDownloadDelay()).Times(0);
TestSimpleTranslationWorks(browser(), "en", "es");
}
// No delay is triggered in attempt to create a translator for an unsupported
// language.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationV1BrowserTest,
CreateTranslator_NoDelay_UnsupportedLanguage) {
SetSelectedLanguages("en,xx");
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.InstallMockTranslateKitComponent();
NavigateToEmptyPage();
auto manager =
MockTranslationManagerImpl(GetBrowserContext(), GetLastCommittedOrigin());
EXPECT_CALL(manager, GetTranslatorDownloadDelay()).Times(0);
EXPECT_NE(EvalJsCatchingError(R"(
const translator = await Translator.create({
sourceLanguage: 'en',
targetLanguage: 'xx',
});
return await translator.translate('hello');
)"),
"en to xx: hello");
}
// Tests the behavior of the crash of calling create() and availability().
class OnDeviceTranslationCrashingLangBrowserTest
: public OnDeviceTranslationBrowserTest {
public:
OnDeviceTranslationCrashingLangBrowserTest() {
// Need to set TranslationAPIAcceptLanguagesCheck to false to use a fake
// language code `cause_crash` to trigger a crash.
scoped_feature_list_.InitWithFeaturesAndParameters(
{{blink::features::kTranslationAPI,
{{"TranslationAPIAcceptLanguagesCheck", "false"}}}},
{});
}
~OnDeviceTranslationCrashingLangBrowserTest() override = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
OnDeviceTranslationBrowserTest::SetUpCommandLine(command_line);
// Need to set the language pack path to the command line to accept the
// fake language code `cause_crash`.
command_line->AppendSwitchASCII(
"translate-kit-packages",
base::StrCat({"cause_crash,ja,", GetTempDir().AsUTF8Unsafe()}));
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Tests the behavior of the crash of calling create().
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationCrashingLangBrowserTest,
CrashWhileCallingCreateTranslator) {
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.ExpectCallRegisterTranslateKitComponentAndInstall();
NavigateToEmptyPage();
auto console_observer =
CreateConsoleObserver("The translation service crashed.");
// Tries to create a translator for the fake language code `cause_crash`. This
// causes a crash in the mock TranslateKit component. See comments in
// mock_translate_kit_lib.cc.
EXPECT_EQ(EvalJsCatchingError(R"(
const translator = await Translator.create({
sourceLanguage: 'cause_crash',
targetLanguage: 'ja',
});
)"),
"NotSupportedError: Unable to create translator for the given "
"source and target language.");
// The console message should be logged.
WaitForConsoleObserver(*console_observer);
}
// Tests the behavior of the crash of calling availability().
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationCrashingLangBrowserTest,
CrashWhileCallingCanTranslate) {
MockComponentManager mock_component_manager(GetTempDir());
EXPECT_CALL(mock_component_manager, RegisterTranslateKitComponentImpl())
.Times(0);
mock_component_manager.InstallMockTranslateKitComponent();
NavigateToEmptyPage();
// Tries to call availability() for the fake language code `cause_crash`. This
// causes a crash in the mock TranslateKit component. See comments in
// mock_translate_kit_lib.cc.
TestCanTranslateResult("cause_crash", "ja",
CanCreateTranslatorResult::kNoServiceCrashed);
}
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest, NoExistFileHandling) {
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.ExpectCallRegisterTranslateKitComponentAndInstall();
mock_component_manager.ExpectCallRegisterLanguagePackComponentAndInstall(
{LanguagePackKey::kEn_Ja});
NavigateToEmptyPage();
EXPECT_EQ(EvalJsCatchingError(R"(
const translator = await Translator.create({
sourceLanguage: 'en',
targetLanguage: 'ja',
});
return await translator.translate('CHECK_NOT_EXIST_FILE');
)"),
"Result of Open(): Failed, result of FileExists(): false, "
"is_directory: false");
}
// Tests the behavior of create() when the accept language check
// fails.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest,
CreateTranslatorAcceptLanguagesCheckFailed) {
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.DoNotExpectCallRegisterTranslateKitComponent();
mock_component_manager.DoNotExpectCallRegisterLanguagePackComponent();
NavigateToEmptyPage();
auto console_observer = CreateConsoleObserver(
"The preferred languages check for Translator API failed. See "
"https://developer.chrome.com/docs/ai/"
"translator-api?#supported-languages for more details.");
// Create a translator for unsupported language.
TestCreateTranslator(browser(), "en", "xx",
"NotSupportedError: Unable to create translator for the "
"given source and target language.");
// The console message should be logged.
WaitForConsoleObserver(*console_observer);
}
// Tests that the browser process can handle the case that the frame is deleted
// while creating a translator.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest,
FrameDeletedWhileCreatingATranslator) {
MockComponentManager mock_component_manager(GetTempDir());
base::RunLoop run_loop_for_register_translate_kit;
EXPECT_CALL(mock_component_manager, RegisterTranslateKitComponentImpl())
.WillOnce(Invoke([&]() { run_loop_for_register_translate_kit.Quit(); }));
base::RunLoop run_loop_for_register_language_pack;
EXPECT_CALL(mock_component_manager,
RegisterTranslateKitLanguagePackComponent(_))
.WillOnce(Invoke([&](LanguagePackKey key) {
EXPECT_EQ(key, LanguagePackKey::kEn_Ja);
run_loop_for_register_language_pack.Quit();
}));
NavigateToEmptyPage();
// Create a translator in an iframe.
EXPECT_EQ(EvalJsCatchingError(R"(
window._testIframe = document.createElement('iframe');
document.body.appendChild(window._testIframe);
window._testIframe.contentWindow.Translator.create({
sourceLanguage: 'en',
targetLanguage: 'ja',
});
return 'OK';
)"),
"OK");
// Wait until RegisterTranslateKitComponentImpl() is called.
run_loop_for_register_translate_kit.Run();
// Wait until RegisterTranslateKitLanguagePackComponent() is called.
run_loop_for_register_language_pack.Run();
// Deletes the iframe after the browser process receives the request.
EXPECT_TRUE(ExecJs(browser()->tab_strip_model()->GetActiveWebContents(),
"document.body.removeChild(window._testIframe);"));
// Install the mock TranslateKit component.
mock_component_manager.InstallMockTranslateKitComponent();
// Install the mock language pack.
mock_component_manager.InstallMockLanguagePack(LanguagePackKey::kEn_Ja);
// Test that Translator API works even after the frame requesting a translator
// is deleted.
TestSimpleTranslationWorks(browser(), "en", "ja");
}
// Tests that the service is terminated when the idle timeout is reached.
IN_PROC_BROWSER_TEST_F(
OnDeviceTranslationBrowserTest,
TerminateServiceWhenIdleTimeoutReachedAfterTranslatorDestroyed) {
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.ExpectCallRegisterTranslateKitComponentAndInstall();
mock_component_manager.ExpectCallRegisterLanguagePackComponentAndInstall(
{LanguagePackKey::kEn_Ja});
NavigateToEmptyPage();
auto service_controller =
ServiceControllerManager::GetForBrowserContext(browser()->profile())
->GetServiceControllerForOrigin(
embedded_https_test_server().GetOrigin());
// Set the idle timeout to be 100 microseconds.
service_controller->SetServiceIdleTimeoutForTesting(base::Microseconds(100));
// Test that Translator API works.
EXPECT_EQ(EvalJsCatchingError(R"(
window._translator = await Translator.create({
sourceLanguage: 'en',
targetLanguage: 'ja',
});
return await window._translator.translate('hello');
)"),
"en to ja: hello");
// Check that the service is still running.
EXPECT_TRUE(service_controller->IsServiceRunning());
// Wait for 200 microseconds.
EXPECT_EQ(EvalJsCatchingError(R"(
await new Promise(resolve => { setTimeout(resolve, 200); });
return 'OK';
)"),
"OK");
// Check that the service is still running, because the translator is still
// available.
EXPECT_TRUE(service_controller->IsServiceRunning());
// Destroy the translator. And wait for 200 microseconds. (Note: wait more
// than the idle timeout 100 microseconds to avoid flakiness.)
EXPECT_EQ(EvalJsCatchingError(R"(
window._translator.destroy();
await new Promise(resolve => { setTimeout(resolve, 200); });
return 'OK';
)"),
"OK");
// Check that the service is not running, because the translator was
// destroyed, and the idle timeout was reached.
EXPECT_FALSE(service_controller->IsServiceRunning());
}
// Tests that the service is terminated when the idle timeout is reached after
// the frame is removed.
IN_PROC_BROWSER_TEST_F(
OnDeviceTranslationBrowserTest,
TerminateServiceWhenIdleTimeoutReachedAfterFrameRemoved) {
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.ExpectCallRegisterTranslateKitComponentAndInstall();
mock_component_manager.ExpectCallRegisterLanguagePackComponentAndInstall(
{LanguagePackKey::kEn_Ja});
NavigateToEmptyPage();
auto service_controller =
ServiceControllerManager::GetForBrowserContext(browser()->profile())
->GetServiceControllerForOrigin(
embedded_https_test_server().GetOrigin());
// Set the idle timeout to be 100 microseconds.
service_controller->SetServiceIdleTimeoutForTesting(base::Microseconds(100));
// Test that Translator API on an iframe works.
EXPECT_EQ(EvalJsCatchingError(R"(
window._iframe = document.createElement('iframe');
document.body.appendChild(window._iframe);
const translator =
await window._iframe.contentWindow.Translator.create({
sourceLanguage: 'en',
targetLanguage: 'ja',
});
return await translator.translate('hello');
)"),
"en to ja: hello");
// Check that the service is still running.
EXPECT_TRUE(service_controller->IsServiceRunning());
// Wait for 200 microseconds.
EXPECT_EQ(EvalJsCatchingError(R"(
await new Promise(resolve => { setTimeout(resolve, 200); });
return 'OK';
)"),
"OK");
// Check that the service is still running, because the ifame is still
// available.
EXPECT_TRUE(service_controller->IsServiceRunning());
// Remove the iframe and wait for 200 microseconds. (Note: wait more than the
// idle timeout 100 microseconds to avoid flakiness.)
EXPECT_EQ(EvalJsCatchingError(R"(
document.body.removeChild(window._iframe);
await new Promise(resolve => { setTimeout(resolve, 200); });
return 'OK';
)"),
"OK");
// Check that the service is not running, because the iframe was removed, and
// the idle timeout was reached.
EXPECT_FALSE(service_controller->IsServiceRunning());
}
// Test the behavior of availability() when the language pack is ready.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest, CanTranslateReadily) {
MockComponentManager mock_component_manager(GetTempDir());
EXPECT_CALL(mock_component_manager, RegisterTranslateKitComponentImpl())
.Times(0);
mock_component_manager.InstallMockTranslateKitComponent();
mock_component_manager.InstallMockLanguagePack(LanguagePackKey::kEn_Ja);
TestCanTranslateResult("en", "ja", CanCreateTranslatorResult::kReadily);
}
// Test the behavior of availability() when the language pack is not ready.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest,
CanTranslateAfterDownloadLanguagePackNotReady) {
MockComponentManager mock_component_manager(GetTempDir());
EXPECT_CALL(mock_component_manager, RegisterTranslateKitComponentImpl())
.Times(0);
mock_component_manager.InstallMockTranslateKitComponent();
TestCanTranslateResult(
"en", "ja",
CanCreateTranslatorResult::kAfterDownloadLanguagePackNotReady);
}
// Test the behavior of availability() when both the library and the language
// pack are not ready.
IN_PROC_BROWSER_TEST_F(
OnDeviceTranslationBrowserTest,
CanTranslateAfterDownloadLibraryAndLanguagePackNotReady) {
MockComponentManager mock_component_manager(GetTempDir());
EXPECT_CALL(mock_component_manager, RegisterTranslateKitComponentImpl())
.Times(0);
TestCanTranslateResult(
"en", "ja",
CanCreateTranslatorResult::kAfterDownloadLibraryAndLanguagePackNotReady);
}
// Test the behavior of availability() when the language pack is ready, but the
// library is not ready.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest,
CanTranslateAfterDownloadLibraryNotReady) {
MockComponentManager mock_component_manager(GetTempDir());
EXPECT_CALL(mock_component_manager, RegisterTranslateKitComponentImpl())
.Times(0);
mock_component_manager.InstallMockLanguagePack(LanguagePackKey::kEn_Ja);
TestCanTranslateResult(
"en", "ja", CanCreateTranslatorResult::kAfterDownloadLibraryNotReady);
}
// Test the behavior of availability() when the language is not supported.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest,
CanTranslateNoNotSupportedLanguage) {
// This test case uses English as the source language and an unsupported
// language code as the target language. To avoid the failure of
// PassAcceptLanguagesCheck(), we set the SelectedLanguages to be English and
// the unsupported language code.
SetSelectedLanguages("en,xx");
MockComponentManager mock_component_manager(GetTempDir());
EXPECT_CALL(mock_component_manager, RegisterTranslateKitComponentImpl())
.Times(0);
TestCanTranslateResult("en", "xx",
CanCreateTranslatorResult::kNoNotSupportedLanguage);
}
// Test the behavior of availability() when the language pack is not ready, and
// the language pack count will exceed the limitation.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest,
CanTranslateNoExceedsLanguagePackCountLimitation) {
// This test case uses English as the source language and French as the target
// language. To avoid the failure of PassAcceptLanguagesCheck(), we set the
// SelectedLanguages to be English and French.
SetSelectedLanguages("en,fr");
MockComponentManager mock_component_manager(GetTempDir());
EXPECT_CALL(mock_component_manager, RegisterTranslateKitComponentImpl())
.Times(0);
mock_component_manager.InstallMockTranslateKitComponent();
// No language packs are installed yet.
size_t installed_package_count = 0;
// Get the amount of packages we can install.
size_t installable_package_count =
on_device_translation::GetInstallablePackageCount(
installed_package_count);
ASSERT_NE(installable_package_count, std::numeric_limits<size_t>::max());
for (const auto& language_pack_key : kLanguagePackKeys) {
mock_component_manager.InstallMockLanguagePack(language_pack_key);
installed_package_count++;
if (installed_package_count < installable_package_count) {
// The language pack count is less than the limitation.
TestCanTranslateResult(
"en", "fr",
CanCreateTranslatorResult::kAfterDownloadLanguagePackNotReady);
} else {
// The language pack count is equal to the limitation. So no more language
// pack can be downloaded.
TestCanTranslateResult(
"en", "fr",
CanCreateTranslatorResult::kNoExceedsLanguagePackCountLimitation);
break;
}
}
ASSERT_EQ(installed_package_count, installable_package_count);
}
// Test the behavior of availability() when the language pack is not ready, and
// the language pack count exceed the limitation after downloading two language
// packs.
IN_PROC_BROWSER_TEST_F(
OnDeviceTranslationBrowserTest,
CanTranslateNoExceedsLanguagePackCountLimitationTwoPackagesRequired) {
// This test case use Hindi and French as the source and target languages.
// To translate from Hindi to French, two language packs are required one for
// hi->en and one for en->fr.
SetSelectedLanguages("hi,fr");
MockComponentManager mock_component_manager(GetTempDir());
EXPECT_CALL(mock_component_manager, RegisterTranslateKitComponentImpl())
.Times(0);
mock_component_manager.InstallMockTranslateKitComponent();
// No language packs are installed yet.
size_t installed_package_count = 0;
// Get the amount of packages we can install.
size_t installable_package_count =
on_device_translation::GetInstallablePackageCount(
installed_package_count);
ASSERT_NE(installable_package_count, std::numeric_limits<size_t>::max());
for (const auto& language_pack_key : kLanguagePackKeys) {
mock_component_manager.InstallMockLanguagePack(language_pack_key);
installed_package_count++;
if (installed_package_count < installable_package_count - 1) {
// The language pack count is less than the limitation.
TestCanTranslateResult(
"hi", "fr",
CanCreateTranslatorResult::kAfterDownloadLanguagePackNotReady);
} else if (installed_package_count < installable_package_count) {
// The language pack count is less than the limitation. But if
// we download the required language packs, the language pack count will
// exceed the limitation. So availability() returns `no`.
TestCanTranslateResult(
"hi", "fr",
CanCreateTranslatorResult::kNoExceedsLanguagePackCountLimitation);
} else {
// The language pack count is 3, which is equal to the limitation. So no
// more language pack can be downloaded.
TestCanTranslateResult(
"hi", "fr",
CanCreateTranslatorResult::kNoExceedsLanguagePackCountLimitation);
break;
}
}
ASSERT_EQ(installed_package_count, installable_package_count);
}
// Test the behavior of availability() when PassAcceptLanguagesCheck() checks
// fails.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest,
CanTranslateNoAcceptLanguagesCheckFailed) {
MockComponentManager mock_component_manager(GetTempDir());
EXPECT_CALL(mock_component_manager, RegisterTranslateKitComponentImpl())
.Times(0);
mock_component_manager.InstallMockTranslateKitComponent();
mock_component_manager.InstallMockLanguagePack(LanguagePackKey::kEn_Ko);
// Korean is not treated as a popular language. So if `ko` is not in the
// accept languages, PassAcceptLanguagesCheck() will return false.
TestCanTranslateResult(
"en", "ko", CanCreateTranslatorResult::kNoAcceptLanguagesCheckFailed);
}
// Test the behavior of `availability()` when the execution context is not
// valid.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest,
Availability_InvalidStateError) {
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.InstallMockTranslateKitComponent();
NavigateToEmptyPage();
EXPECT_EQ(EvalJsCatchingError(R"(
const iframe = document.createElement('iframe');
const loadPromise = new Promise(resolve => {
iframe.addEventListener('load', resolve);
});
iframe.src = location.href;
document.body.appendChild(iframe);
await loadPromise;
const iframeTranslator = iframe.contentWindow.Translator;
document.body.removeChild(iframe);
await iframeTranslator.availability({
sourceLanguage: "en",
targetLanguage: "es"
});
)"),
"InvalidStateError: Failed to execute 'availability' on "
"'Translator': The execution context is not valid.");
}
// Test the behavior of `availability()` for an unsupported language.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest,
Availability_Unavailable_NotSupportedLanguage) {
// Set the unsupported language as a preferred language in order to avoid
// `PassAcceptLanguagesCheck()` failure, for testing purposes.
SetSelectedLanguages("en,xx");
MockComponentManager mock_component_manager(GetTempDir());
NavigateToEmptyPage();
TestTranslationAvailable(browser(), "en", "xx", "unavailable");
}
// Test the behavior of `availability()` when the `PassAcceptLanguagesCheck()`
// check fails.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest,
Availability_Unavailable_AcceptLanguagesCheckFailed) {
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.InstallMockTranslateKitComponent();
mock_component_manager.InstallMockLanguagePack(LanguagePackKey::kEn_Ko);
NavigateToEmptyPage();
// Korean is not treated as a popular language. So if `ko` is not in the
// accept languages, `PassAcceptLanguagesCheck()` will return false.
TestTranslationAvailable(browser(), "en", "ko", "unavailable");
}
// Test the behavior of `availability()` where the source language and the
// target language are the same language.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest,
Availability_Unavailable_SameSourceAndTargetLanguage) {
SetSelectedLanguages("en,ja");
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.InstallMockLanguagePack(LanguagePackKey::kEn_Ja);
NavigateToEmptyPage();
TestTranslationAvailable(browser(), "ja", "ja", "unavailable");
TestTranslationAvailable(browser(), "en", "en", "unavailable");
}
// Test the behavior of `availability()` when the language pack is not ready,
// and the language pack count will exceed the imitation.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest,
Availability_No_ExceedsLangPackCountLimitation) {
// This test case uses English as the source language and French as the target
// language. To avoid the failure of `PassAcceptLanguagesCheck()`, we set the
// preferred languages to English and French.
SetSelectedLanguages("en,fr");
MockComponentManager mock_component_manager(GetTempDir());
NavigateToEmptyPage();
mock_component_manager.InstallMockTranslateKitComponent();
// No language packs are installed yet.
size_t installed_package_count = 0;
// Get the amount of packages we can install.
size_t installable_package_count =
on_device_translation::GetInstallablePackageCount(
installed_package_count);
ASSERT_NE(installable_package_count, std::numeric_limits<size_t>::max());
for (const auto& language_pack_key : kLanguagePackKeys) {
mock_component_manager.InstallMockLanguagePack(language_pack_key);
installed_package_count++;
if (installed_package_count < installable_package_count) {
// The language pack count is less than the limitation.
TestTranslationAvailable(browser(), "en", "fr", "downloadable");
} else {
// The language pack count is equal to the limitation. So no more language
// pack can be downloaded.
TestTranslationAvailable(browser(), "en", "fr", "unavailable");
break;
}
}
ASSERT_EQ(installed_package_count, installable_package_count);
}
// Test the behavior of `availability()` when the language pack is not ready,
// and the language pack count exceeds the limitation after downloading two
// more language packs.
IN_PROC_BROWSER_TEST_F(
OnDeviceTranslationBrowserTest,
Availability_Unavailable_ExceedsLangPackCountLimitationTwoPackagesRequired) {
// This test case uses Hindi and French as the source and target languages.
// To translate from Hindi to French, two language packs are required: one for
// the hi->en translation and one for the en->fr translation.
SetSelectedLanguages("hi,fr");
MockComponentManager mock_component_manager(GetTempDir());
NavigateToEmptyPage();
mock_component_manager.InstallMockTranslateKitComponent();
// No language packs are installed yet.
size_t installed_package_count = 0;
// Get the amount of packages we can install.
size_t installable_package_count =
on_device_translation::GetInstallablePackageCount(
installed_package_count);
ASSERT_NE(installable_package_count, std::numeric_limits<size_t>::max());
for (const auto& language_pack_key : kLanguagePackKeys) {
mock_component_manager.InstallMockLanguagePack(language_pack_key);
installed_package_count++;
if (installed_package_count < installable_package_count - 1) {
// The language pack count is less than the limitation.
TestTranslationAvailable(browser(), "hi", "fr", "downloadable");
} else if (installed_package_count < installable_package_count) {
// The language pack count is less than the limitation.
// If we download the required language packs, the language pack count
// will exceed the limitation. As a result, `availability()` is
// 'unavailable'.
TestTranslationAvailable(browser(), "hi", "fr", "unavailable");
} else {
// The language pack count is 3, which is equal to the limitation. As a
// result, no more language packs can be downloaded.
TestTranslationAvailable(browser(), "hi", "fr", "unavailable");
break;
}
}
ASSERT_EQ(installed_package_count, installable_package_count);
}
// Test the behavior of `availability()` when both the library and the language
// packs are not ready.
IN_PROC_BROWSER_TEST_F(
OnDeviceTranslationBrowserTest,
Availability_Downloadable_LibraryAndLanguagePackNotReady) {
SetSelectedLanguages("en,ja");
MockComponentManager mock_component_manager(GetTempDir());
NavigateToEmptyPage();
TestTranslationAvailable(browser(), "en", "ja", "downloadable");
}
// Test the behavior of `availability()` when the library is not ready.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest,
Availability_Downloadable_LibraryNotReady) {
SetSelectedLanguages("en,es");
MockComponentManager mock_component_manager(GetTempDir());
EXPECT_CALL(mock_component_manager, RegisterTranslateKitComponentImpl())
.Times(0);
NavigateToEmptyPage();
TestTranslationAvailable(browser(), "en", "es", "downloadable");
}
// Test the behavior of `availability()` when the library is ready, and
// language packs are not ready.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest,
Availability_Downloadable_LanguagePacksNotReady) {
SetSelectedLanguages("en,es");
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.InstallMockTranslateKitComponent();
NavigateToEmptyPage();
TestTranslationAvailable(browser(), "en", "es", "downloadable");
}
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest,
Availability_Available_ForSelectedPopularAndNonPopular) {
SetSelectedLanguages("ja,fr");
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.InstallMockTranslateKitComponent();
mock_component_manager.InstallMockLanguagePack(LanguagePackKey::kEn_Ja);
mock_component_manager.InstallMockLanguagePack(LanguagePackKey::kEn_Fr);
NavigateToEmptyPage();
TestTranslationAvailable(browser(), "ja", "fr", "available");
}
// Test the behavior of `availability()` when both the library and language
// packs are ready.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest,
Availability_Available_LibraryAndLanguagePackReady) {
SetSelectedLanguages("en,ja");
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.InstallMockTranslateKitComponent();
mock_component_manager.InstallMockLanguagePack(LanguagePackKey::kEn_Ja);
NavigateToEmptyPage();
TestTranslationAvailable(browser(), "en", "ja", "available");
}
// Test that calling both the legacy and new API works.
// This is a regression test for https://crbug.com/381344025.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest, UseBothLegacyAndNewAPI) {
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.ExpectCallRegisterTranslateKitComponentAndInstall();
mock_component_manager.ExpectCallRegisterLanguagePackComponentAndInstall(
{LanguagePackKey::kEn_Ja});
NavigateToEmptyPage();
// Test that Translator legacy API works.
EXPECT_EQ(EvalJsCatchingError(R"(
const translator = await Translator.create({
sourceLanguage: 'en',
targetLanguage: 'ja',
});
return await translator.translate('hello');
)"),
"en to ja: hello");
// Test that Translator new API works.
EXPECT_EQ(EvalJsCatchingError(R"(
const translator = await Translator.create({
sourceLanguage: 'en',
targetLanguage: 'ja',
});
return await translator.translate('hello');
)"),
"en to ja: hello");
}
// TODO(crbug.com/410842873): Add test to check behavior for extension
// service workers once supported.
//
// Test the behavior of the Translator API accessed from a service worker
// outside of an extension.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBrowserTest,
APIAvailability_NonExtensionWorkers) {
const std::string kWorkerScript =
"try {"
" Translator;"
" self.postMessage('test');"
"} catch (e) {"
" self.postMessage(e.name);"
"}";
base::RunLoop loop;
embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
&RespondWithJS, "/js-response", kWorkerScript, loop.QuitClosure()));
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"/workers/create_dedicated_worker.html?worker_url=/js-response")));
loop.Run();
EXPECT_EQ(
"ReferenceError",
content::EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
"waitForMessage();"));
}
// Test the behavior of availability() when PassAcceptLanguagesCheck() checks
// is skipped.
class OnDeviceTranslationSkipAcceptLanguagesCheckBrowserTest
: public OnDeviceTranslationBrowserTest {
public:
OnDeviceTranslationSkipAcceptLanguagesCheckBrowserTest() {
scoped_feature_list_.InitWithFeaturesAndParameters(
{{blink::features::kTranslationAPI,
{{"TranslationAPIAcceptLanguagesCheck", "false"}}}},
{});
}
~OnDeviceTranslationSkipAcceptLanguagesCheckBrowserTest() override = default;
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Test the behavior of availability() when PassAcceptLanguagesCheck() checks
// is skipped.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationSkipAcceptLanguagesCheckBrowserTest,
CanTranslateAcceptLanguagesCheckSkipped) {
MockComponentManager mock_component_manager(GetTempDir());
EXPECT_CALL(mock_component_manager, RegisterTranslateKitComponentImpl())
.Times(0);
mock_component_manager.InstallMockTranslateKitComponent();
mock_component_manager.InstallMockLanguagePack(LanguagePackKey::kEn_Ko);
TestCanTranslateResult("en", "ko", CanCreateTranslatorResult::kReadily);
}
// Test the behavior of Translator API in a cross origin iframe.
class OnDeviceTranslationCrossOriginBrowserTest
: public OnDeviceTranslationBrowserTest {
public:
OnDeviceTranslationCrossOriginBrowserTest() {
// Use a reduced value for TranslationAPIMaxServiceCount to speed
// up the test.
scoped_feature_list_.InitWithFeaturesAndParameters(
{{blink::features::kTranslationAPI,
{{"TranslationAPIMaxServiceCount", "2"}}}},
{});
CHECK_EQ(kTranslationAPIMaxServiceCount.Get(), 2u);
}
~OnDeviceTranslationCrossOriginBrowserTest() override = default;
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
// Need to use URLLoaderInterceptor to handle requests to multiple origins.
url_loader_interceptor_.emplace(base::BindRepeating(
&OnDeviceTranslationCrossOriginBrowserTest::InterceptRequest));
}
void TearDownOnMainThread() override { url_loader_interceptor_.reset(); }
protected:
// Creates a URL for an iframe with a unique origin.
static GURL CreateCrossOriginIframeUrl(size_t index) {
return GURL(
base::StringPrintf("https://test-%zd.example/frame.html", index));
}
// Navigates to the test page.
void NavigateToTestPage(Browser* target_browser) {
CHECK(ui_test_utils::NavigateToURL(
target_browser ? target_browser : browser(),
GURL("https://translation-api.test/index.html")));
}
// Adds an iframe to the test page and optionally sets its permission policy.
void AddIframe(size_t index,
Browser* target_browser,
bool permission_policy_enabled) {
EXPECT_EQ(EvalJsCatchingError(JsReplace("return addIframe($1, $2);",
CreateCrossOriginIframeUrl(index),
permission_policy_enabled),
target_browser),
"loaded");
}
// Removes the iframe and waits for the service deletion.
void RemoveIframeAndWaitForServiceDeletion(size_t index,
Browser* target_browser) {
base::RunLoop run_loop;
ServiceControllerManager::GetForBrowserContext(target_browser->profile())
->set_service_controller_deleted_observer_for_testing(
run_loop.QuitClosure());
EXPECT_EQ(EvalJsCatchingError(JsReplace("return removeIframe($1);",
CreateCrossOriginIframeUrl(index)),
target_browser),
"removed");
run_loop.Run();
}
// Creates a translator and translates in the iframe. Returns the result of
// the translation or the error message.
std::string TryCreateTranslatorAndTranslateInIframe(size_t index,
Browser* target_browser) {
const std::string_view translateTestScript = R"(
(async () => {
try {
window._translator = await Translator.create({
sourceLanguage: 'en',
targetLanguage: 'ja',
});
if (window._translator) {
return await window._translator.translate('hello');
} else {
return 'NotSupportedError';
}
} catch (e) {
return e.name.toString();
}
})()
)";
return EvalJsCatchingError(
JsReplace("return evalInIframe($1, $2);",
CreateCrossOriginIframeUrl(index), translateTestScript),
target_browser);
}
// Checks that the translator is created and the translation is successful or
// not.
void CheckTranslateInIframe(size_t index,
bool expect_success,
Browser* target_browser) {
EXPECT_EQ(TryCreateTranslatorAndTranslateInIframe(index, target_browser),
expect_success ? "en to ja: hello" : "NotSupportedError");
}
// Checks the result of availability() in the iframe.
std::string TryCanTranslateInIframe(size_t index, Browser* target_browser) {
const std::string_view translateTestScript = R"(
(async () => {
try {
return await Translator.availability({
sourceLanguage: 'en',
targetLanguage: 'ja',
});
} catch (e) {
return e.name.toString();
}
})()
)";
return EvalJsCatchingError(
JsReplace("return evalInIframe($1, $2);",
CreateCrossOriginIframeUrl(index), translateTestScript),
target_browser);
}
private:
// URLLoaderInterceptor callback
static bool InterceptRequest(
content::URLLoaderInterceptor::RequestParams* params) {
if (params->url_request.url.path() == "/index.html") {
content::URLLoaderInterceptor::WriteResponse(
"HTTP/1.1 200 OK\nContent-Type: text/html; charset=utf-8\n\n",
R"(
<head><script>
const frames = {};
async function addIframe(url, permissionPolicyEnabled) {
const frame = document.createElement('iframe');
frames[url] = frame;
if (permissionPolicyEnabled) {
frame.setAttribute('allow', 'translator');
}
const loadedPromise = new Promise(resolve => {
frame.addEventListener('load', () => {
resolve('loaded');
});
});
frame.src = url;
document.body.appendChild(frame);
return await loadedPromise;
}
async function evalInIframe(url, script) {
const frame = frames[url];
const channel = new MessageChannel();
const onMessagePromise = new Promise(resolve => {
channel.port1.addEventListener('message', e => {
resolve(e.data);
});
});
channel.port1.start();
frame.contentWindow.postMessage(script, '*', [channel.port2]);
return await onMessagePromise;
}
function removeIframe(url) {
document.body.removeChild(frames[url]);
return 'removed';
}
</script></head>)",
params->client.get(),
/*ssl_info=*/std::nullopt, params->url_request.url);
return true;
} else if (params->url_request.url.path() == "/frame.html") {
content::URLLoaderInterceptor::WriteResponse(
"HTTP/1.1 200 OK\nContent-Type: text/html; charset=utf-8\n\n",
R"(
<head><script>
window.addEventListener('message', async (e) => {
e.ports[0].postMessage(await eval(e.data));
});
</script></head>)",
params->client.get(),
/*ssl_info=*/std::nullopt, params->url_request.url);
return true;
}
return false;
}
base::test::ScopedFeatureList scoped_feature_list_;
std::optional<content::URLLoaderInterceptor> url_loader_interceptor_;
};
// Tests the behavior of the Translation API in a cross origin iframe.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationCrossOriginBrowserTest,
TranslateInCrossOriginIframe) {
MockComponentManager mock_component_manager(GetTempDir());
EXPECT_CALL(mock_component_manager, RegisterTranslateKitComponentImpl())
.Times(0);
NavigateToTestPage(browser());
AddIframe(0, browser(), /*enable_permission_policy=*/false);
// Translation is not available in cross-origin iframes without permission
// policy.
CheckTranslateInIframe(0, /*expect_success=*/false, browser());
}
// Tests the behavior of the Translation API in a cross origin iframe when the
// service count exceeds the limit.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationCrossOriginBrowserTest,
ExceedServiceCountLimit) {
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.ExpectCallRegisterTranslateKitComponentAndInstall();
mock_component_manager.ExpectCallRegisterLanguagePackComponentAndInstall(
{LanguagePackKey::kEn_Ja});
NavigateToTestPage(browser());
size_t i = 0;
// Until the service count exceeds the limit, the translator can be created,
// and the translation is successful.
for (; i < kTranslationAPIMaxServiceCount.Get(); i++) {
AddIframe(i, browser(), /*enable_permission_policy=*/true);
CheckTranslateInIframe(i, /*expect_success=*/true, browser());
EXPECT_EQ(TryCanTranslateInIframe(i, browser()), "available");
}
// When the service count exceeds the limit, the translator cannot be created,
// even when the permission policy is still enabled.
AddIframe(i, browser(), /*enable_permission_policy=*/true);
auto console_observer = CreateConsoleObserver(
"The translation service count exceeded the limitation.");
CheckTranslateInIframe(i, /*expect_success=*/false, browser());
WaitForConsoleObserver(*console_observer);
EXPECT_EQ(TryCanTranslateInIframe(i, browser()), "unavailable");
// When the service count is back to under the limit, the translator can be
// created again.
RemoveIframeAndWaitForServiceDeletion(0, browser());
CheckTranslateInIframe(i, /*expect_success=*/true, browser());
EXPECT_EQ(TryCanTranslateInIframe(i, browser()), "available");
}
// Tests the behavior of the Translation API in a cross origin iframe using the
// incognito profile.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationCrossOriginBrowserTest,
TranslateInIframeIncognitoBrowser) {
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.ExpectCallRegisterTranslateKitComponentAndInstall();
mock_component_manager.ExpectCallRegisterLanguagePackComponentAndInstall(
{LanguagePackKey::kEn_Ja});
Browser* incognito_browser = CreateIncognitoBrowser();
NavigateToTestPage(incognito_browser);
AddIframe(0, incognito_browser, /*enable_permission_policy=*/true);
CheckTranslateInIframe(0, /*expect_success=*/true, incognito_browser);
AddIframe(1, incognito_browser, /*enable_permission_policy=*/false);
CheckTranslateInIframe(1, /*expect_success=*/false, incognito_browser);
}
// Tests the behavior of the Translation API in a cross origin iframe using the
// guest profile.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationCrossOriginBrowserTest,
TranslateInIframeGuestBrowser) {
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.ExpectCallRegisterTranslateKitComponentAndInstall();
mock_component_manager.ExpectCallRegisterLanguagePackComponentAndInstall(
{LanguagePackKey::kEn_Ja});
Browser* guest_browser = CreateGuestBrowser();
NavigateToTestPage(guest_browser);
AddIframe(0, guest_browser, /*enable_permission_policy=*/true);
CheckTranslateInIframe(0, /*expect_success=*/true, guest_browser);
}
// Tests the behavior of the Translation API in a cross origin iframe using
// multiple profiles.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationCrossOriginBrowserTest,
ServiceCountLimitIsolatedPerProfile) {
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.ExpectCallRegisterTranslateKitComponentAndInstall();
mock_component_manager.ExpectCallRegisterLanguagePackComponentAndInstall(
{LanguagePackKey::kEn_Ja});
ProfileManager* profile_manager = g_browser_process->profile_manager();
base::FilePath other_path =
profile_manager->GenerateNextProfileDirectoryPath();
// Create an additional profile.
Profile& additional_profile =
profiles::testing::CreateProfileSync(profile_manager, other_path);
std::vector<Browser*> browsers = {
browser(),
CreateBrowser(&additional_profile),
CreateIncognitoBrowser(),
CreateGuestBrowser(),
};
for (auto* target_browser : browsers) {
NavigateToTestPage(target_browser);
}
// Create translators and translate in each profile. Until the service count
// per profile exceeds the limit, the translator can be created, and the
// translation is successful.
for (size_t i = 0; i < kTranslationAPIMaxServiceCount.Get(); i++) {
for (auto* target_browser : browsers) {
AddIframe(i, target_browser, /*enable_permission_policy=*/true);
CheckTranslateInIframe(i, /*expect_success=*/true, target_browser);
}
}
const size_t limit_count = kTranslationAPIMaxServiceCount.Get();
// When the service count per profile exceeds the limit, the translator
// cannot be created.
for (auto* target_browser : browsers) {
AddIframe(limit_count, target_browser, /*enable_permission_policy=*/true);
auto console_observer = CreateConsoleObserver(
"The translation service count exceeded the limitation.",
target_browser);
CheckTranslateInIframe(limit_count, /*expect_success=*/false,
target_browser);
// The console message should be logged.
WaitForConsoleObserver(*console_observer);
}
// When the service count per profile is back to under the limit, the
// translator can be created again.
for (auto* target_browser : browsers) {
RemoveIframeAndWaitForServiceDeletion(0, target_browser);
CheckTranslateInIframe(limit_count, /*expect_success=*/true,
target_browser);
}
}
// Tests the behavior of the Translation API in a cross origin iframe using
// the command line. We need this test because the implementation of
// availability() is different when the command line is used.
class OnDeviceTranslationCrossOriginWithCommandLineBrowserTest
: public OnDeviceTranslationCrossOriginBrowserTest {
public:
OnDeviceTranslationCrossOriginWithCommandLineBrowserTest() = default;
~OnDeviceTranslationCrossOriginWithCommandLineBrowserTest() override =
default;
void SetUpCommandLine(base::CommandLine* command_line) override {
OnDeviceTranslationCrossOriginBrowserTest::SetUpCommandLine(command_line);
SetMockLibraryPathToCommandLine(command_line);
WriteFakeDictionaryDataAndSetCommandLine(LanguagePackKey::kEn_Ja,
GetTempDir(), command_line);
}
};
// Tests the behavior of the Translation API in a cross origin iframe when the
// service count exceeds the limit.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationCrossOriginWithCommandLineBrowserTest,
ExceedServiceCountLimit) {
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.DoNotExpectCallRegisterTranslateKitComponent();
mock_component_manager.DoNotExpectCallRegisterLanguagePackComponent();
NavigateToTestPage(browser());
size_t i = 0;
// Until the service count exceeds the limit, the translator can be created,
// and the translation is successful.
for (; i < kTranslationAPIMaxServiceCount.Get(); i++) {
AddIframe(i, browser(), /*enable_permission_policy=*/true);
CheckTranslateInIframe(i, /*expect_success=*/true, browser());
EXPECT_EQ(TryCanTranslateInIframe(i, browser()), "available");
}
// When the service count exceeds the limit, the translator cannot be created.
AddIframe(i, browser(), /*enable_permission_policy=*/true);
CheckTranslateInIframe(i, /*expect_success=*/false, browser());
EXPECT_EQ(TryCanTranslateInIframe(i, browser()), "unavailable");
// When the service count is back to under the limit, the translator can be
// created again.
RemoveIframeAndWaitForServiceDeletion(0, browser());
CheckTranslateInIframe(i, /*expect_success=*/true, browser());
EXPECT_EQ(TryCanTranslateInIframe(i, browser()), "available");
}
// Tests the behavior of the Origin Trial token for the Translation API.
class OnDeviceTranslationOriginTrialBrowserTest : public InProcessBrowserTest {
public:
OnDeviceTranslationOriginTrialBrowserTest() {
CHECK(tmp_dir_.CreateUniqueTempDir());
}
~OnDeviceTranslationOriginTrialBrowserTest() override = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
InProcessBrowserTest::SetUpCommandLine(command_line);
// Add the public key following:
// https://chromium.googlesource.com/chromium/src/+/HEAD/docs/origin_trials_integration.md#manual-testing.
command_line->AppendSwitchASCII(
"origin-trial-public-key",
"dRCs+TocuKkocNKa0AtZ4awrt9XKH2SQCI6o4FY6BNA=");
}
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
// Need to use URLLoaderInterceptor to test the behavior of Origin Trial.
url_loader_interceptor_.emplace(base::BindRepeating(
&OnDeviceTranslationOriginTrialBrowserTest::InterceptRequest));
}
void TearDownOnMainThread() override { url_loader_interceptor_.reset(); }
protected:
const base::FilePath& GetTempDir() { return tmp_dir_.GetPath(); }
// Injects the Origin Trial token into the page.
void InjectOriginTrialMetaTag() {
EXPECT_TRUE(
ExecJs(browser()->tab_strip_model()->GetActiveWebContents(),
base::StringPrintf(R"(
(() => {
const meta = document.createElement('meta');
meta.httpEquiv = 'origin-trial';
meta.content = '%s';
document.head.appendChild(meta);
})()
)",
std::string(kOriginTrialToken).c_str())));
}
bool IsDefinedJs(std::string js_expression) {
return EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
base::StringPrintf("(%s) !== undefined", js_expression))
.ExtractBool();
}
// Tests that `window.Translator.availability` and
// `window.Translator.availability` don't exist.
void ExpectAPIDisabled() {
if (!IsDefinedJs("window.Translator")) {
// `window.Translator` is not there, we're done.
return;
}
// We expect to find the detection API but no translate API.
EXPECT_FALSE(IsDefinedJs("window.Translator.availability"));
EXPECT_FALSE(IsDefinedJs("window.Translator.create"));
}
// Tests that `window.Translator.availability` and
// `window.Translator.availability` both exist.
void ExpectAPIEnabled() {
EXPECT_TRUE(IsDefinedJs("window.Translator"));
EXPECT_TRUE(IsDefinedJs("window.Translator.availability"));
EXPECT_TRUE(IsDefinedJs("window.Translator.create"));
}
private:
// URLLoaderInterceptor callback
static bool InterceptRequest(
content::URLLoaderInterceptor::RequestParams* params) {
if (params->url_request.url.path() == "/blank.html") {
content::URLLoaderInterceptor::WriteResponse(
"HTTP/1.1 200 OK\nContent-Type: text/html; charset=utf-8\n\n",
"<body></body>", params->client.get(),
/*ssl_info=*/std::nullopt, params->url_request.url);
return true;
} else if (params->url_request.url.path() == "/ot_token_header.html") {
content::URLLoaderInterceptor::WriteResponse(
base::StrCat(
{"HTTP/1.1 200 OK\nContent-Type: text/html; charset=utf-8\n",
"Origin-Trial: ", kOriginTrialToken, "\n\n"}),
"<body></body>", params->client.get(),
/*ssl_info=*/std::nullopt, params->url_request.url);
return true;
}
return false;
}
base::ScopedTempDir tmp_dir_;
std::optional<content::URLLoaderInterceptor> url_loader_interceptor_;
};
// Tests the behavior of the Origin Trial token meta tag for the Translation
// API.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationOriginTrialBrowserTest,
WithOriginTrialTokenMetaTag) {
MockComponentManager mock_component_manager(GetTempDir());
CHECK(ui_test_utils::NavigateToURL(
browser(), GURL("https://translation-api.test/blank.html")));
// The API is not available without the Origin Trial token.
ExpectAPIDisabled();
// Inject the Origin Trial token into the page.
InjectOriginTrialMetaTag();
// The API is available with the Origin Trial token.
ExpectAPIEnabled();
// Test the behavior of Translation API.
mock_component_manager.ExpectCallRegisterTranslateKitComponentAndInstall();
mock_component_manager.ExpectCallRegisterLanguagePackComponentAndInstall(
{LanguagePackKey::kEn_Ja});
TestSimpleTranslationWorks(browser(), "en", "ja");
}
// Tests the behavior of the Origin Trial token header for the Translation API.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationOriginTrialBrowserTest,
WithOriginTrialTokenHeader) {
MockComponentManager mock_component_manager(GetTempDir());
// Navigate to the page with the Origin Trial token header.
CHECK(ui_test_utils::NavigateToURL(
browser(), GURL("https://translation-api.test/ot_token_header.html")));
// The API is available when the Origin Trial token is in the header.
ExpectAPIEnabled();
// Test the behavior of Translation API.
mock_component_manager.ExpectCallRegisterTranslateKitComponentAndInstall();
mock_component_manager.ExpectCallRegisterLanguagePackComponentAndInstall(
{LanguagePackKey::kEn_Ja});
TestSimpleTranslationWorks(browser(), "en", "ja");
}
// Tests the behavior of the Origin Trial token when the kill switch is enabled.
class OnDeviceTranslationOriginTrialKillSwitchBrowserTest
: public OnDeviceTranslationOriginTrialBrowserTest {
public:
OnDeviceTranslationOriginTrialKillSwitchBrowserTest() {
// Disable the feature to enable the kill switch.
scoped_feature_list_.InitAndDisableFeature(
blink::features::kTranslationAPI);
}
~OnDeviceTranslationOriginTrialKillSwitchBrowserTest() override = default;
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Tests the behavior of the Origin Trial token meta tag when the kill switch is
// enabled.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationOriginTrialKillSwitchBrowserTest,
WithOriginTrialTokenMetaTag) {
CHECK(ui_test_utils::NavigateToURL(
browser(), GURL("https://translation-api.test/blank.html")));
// The API is not available when the kill switch is enabled.
ExpectAPIDisabled();
// Inject the Origin Trial token into the page.
InjectOriginTrialMetaTag();
// The API is not available when the kill switch is enabled even if the Origin
// Trial token is injected.
ExpectAPIDisabled();
}
// Tests the behavior of the Origin Trial token header when the kill switch is
// enabled.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationOriginTrialKillSwitchBrowserTest,
WithOriginTrialTokenHeader) {
// Navigate to the page with the Origin Trial token header.
CHECK(ui_test_utils::NavigateToURL(
browser(), GURL("https://translation-api.test/ot_token_header.html")));
// The API is not available when the kill switch is enabled even if the Origin
// Trial token is in the header.
ExpectAPIDisabled();
}
// Tests the behavior of when the command line flag "translate-kit-binary-path"
// is provided.
class OnDeviceTranslationBinaryPathCommandLineBrowserTest
: public OnDeviceTranslationBrowserTest {
public:
OnDeviceTranslationBinaryPathCommandLineBrowserTest() = default;
~OnDeviceTranslationBinaryPathCommandLineBrowserTest() override = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
OnDeviceTranslationBrowserTest::SetUpCommandLine(command_line);
SetMockLibraryPathToCommandLine(command_line);
}
};
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationBinaryPathCommandLineBrowserTest,
SimpleTranslation) {
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.DoNotExpectCallRegisterTranslateKitComponent();
mock_component_manager.ExpectCallRegisterLanguagePackComponentAndInstall(
{LanguagePackKey::kEn_Ja});
NavigateToEmptyPage();
TestSimpleTranslationWorks(browser(), "en", "ja");
}
// Tests the behavior of when the command line flag "translate-kit-packages"
// is provided.
class OnDeviceTranslationPackagesCommandLineBrowserTest
: public OnDeviceTranslationBrowserTest {
public:
OnDeviceTranslationPackagesCommandLineBrowserTest() = default;
~OnDeviceTranslationPackagesCommandLineBrowserTest() override = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
OnDeviceTranslationBrowserTest::SetUpCommandLine(command_line);
WriteFakeDictionaryDataAndSetCommandLine(LanguagePackKey::kEn_Ja,
GetTempDir(), command_line);
}
};
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationPackagesCommandLineBrowserTest,
SimpleTranslation) {
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.ExpectCallRegisterTranslateKitComponentAndInstall();
mock_component_manager.DoNotExpectCallRegisterLanguagePackComponent();
NavigateToEmptyPage();
TestSimpleTranslationWorks(browser(), "en", "ja");
}
// Tests the behavior of availability() when the required language package
// is provided by the command line flag.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationPackagesCommandLineBrowserTest,
CanTranslateReadily) {
MockComponentManager mock_component_manager(GetTempDir());
EXPECT_CALL(mock_component_manager, RegisterTranslateKitComponentImpl())
.Times(0);
mock_component_manager.InstallMockTranslateKitComponent();
mock_component_manager.DoNotExpectCallRegisterLanguagePackComponent();
NavigateToEmptyPage();
TestCanTranslateResult("en", "ja", CanCreateTranslatorResult::kReadily);
}
// Tests the behavior of availability() when the required language package
// is not provided by the command line flag.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationPackagesCommandLineBrowserTest,
CanTranslateNoNotSupportedLanguage) {
// This test case uses English as the source language and French as the target
// language. To avoid the failure of PassAcceptLanguagesCheck(), we set the
// SelectedLanguages to be English and French.
SetSelectedLanguages("en,fr");
MockComponentManager mock_component_manager(GetTempDir());
EXPECT_CALL(mock_component_manager, RegisterTranslateKitComponentImpl())
.Times(0);
mock_component_manager.InstallMockTranslateKitComponent();
mock_component_manager.DoNotExpectCallRegisterLanguagePackComponent();
NavigateToEmptyPage();
TestCanTranslateResult("en", "fr",
CanCreateTranslatorResult::kNoNotSupportedLanguage);
}
// Tests the behavior of availability() when the required language package
// is provided by the command line flag, but the library is not ready.
IN_PROC_BROWSER_TEST_F(OnDeviceTranslationPackagesCommandLineBrowserTest,
CanTranslateAfterDownloadLibraryNotReady) {
MockComponentManager mock_component_manager(GetTempDir());
EXPECT_CALL(mock_component_manager, RegisterTranslateKitComponentImpl())
.Times(0);
mock_component_manager.DoNotExpectCallRegisterLanguagePackComponent();
NavigateToEmptyPage();
TestCanTranslateResult(
"en", "ja", CanCreateTranslatorResult::kAfterDownloadLibraryNotReady);
}
// Tests the behavior of when the command line flags "translate-kit-binary-path"
// and "translate-kit-packages" are provided.
class OnDeviceTranslationBinaryPathAndPackagesCommandLineBrowserTest
: public OnDeviceTranslationBrowserTest {
public:
OnDeviceTranslationBinaryPathAndPackagesCommandLineBrowserTest() = default;
~OnDeviceTranslationBinaryPathAndPackagesCommandLineBrowserTest() override =
default;
void SetUpCommandLine(base::CommandLine* command_line) override {
OnDeviceTranslationBrowserTest::SetUpCommandLine(command_line);
SetMockLibraryPathToCommandLine(command_line);
WriteFakeDictionaryDataAndSetCommandLine(LanguagePackKey::kEn_Ja,
GetTempDir(), command_line);
}
};
IN_PROC_BROWSER_TEST_F(
OnDeviceTranslationBinaryPathAndPackagesCommandLineBrowserTest,
SimpleTranslation) {
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.DoNotExpectCallRegisterTranslateKitComponent();
mock_component_manager.DoNotExpectCallRegisterLanguagePackComponent();
NavigateToEmptyPage();
TestSimpleTranslationWorks(browser(), "en", "ja");
}
// Tests the behavior of when the number of values passed to the
// "translate-kit-packages" command-line flag is not a multiple of three.
class OnDeviceTranslationInvalidCommandLineNonTriadPackageFlagBrowserTest
: public OnDeviceTranslationBrowserTest {
public:
OnDeviceTranslationInvalidCommandLineNonTriadPackageFlagBrowserTest() =
default;
~OnDeviceTranslationInvalidCommandLineNonTriadPackageFlagBrowserTest()
override = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
OnDeviceTranslationBrowserTest::SetUpCommandLine(command_line);
// This is an invalid flag as "translate-kit-packages" requires
// arguments in groups of three: "first language code, second language code,
// model path".
command_line->AppendSwitchASCII("translate-kit-packages", "en,ja");
}
};
IN_PROC_BROWSER_TEST_F(
OnDeviceTranslationInvalidCommandLineNonTriadPackageFlagBrowserTest,
SimpleTranslation) {
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.ExpectCallRegisterTranslateKitComponentAndInstall();
mock_component_manager.ExpectCallRegisterLanguagePackComponentAndInstall(
{LanguagePackKey::kEn_Ja});
NavigateToEmptyPage();
TestSimpleTranslationWorks(browser(), "en", "ja");
}
// Tests the behavior of when non-ASCII language code is passed to the
// "translate-kit-packages" command-line flag.
class OnDeviceTranslationInvalidCommandLineNonAsciiLanguageFlagBrowserTest
: public OnDeviceTranslationBrowserTest {
public:
OnDeviceTranslationInvalidCommandLineNonAsciiLanguageFlagBrowserTest() =
default;
~OnDeviceTranslationInvalidCommandLineNonAsciiLanguageFlagBrowserTest()
override = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
OnDeviceTranslationBrowserTest::SetUpCommandLine(command_line);
// This is an invalid flag as "translate-kit-packages" requires
// arguments in groups of three: "first language code, second language code,
// model path", and the language codes must be ASCII.
command_line->AppendSwitchASCII(
"translate-kit-packages",
base::StrCat({"en,日本語,", GetTempDir().AsUTF8Unsafe()}));
}
};
IN_PROC_BROWSER_TEST_F(
OnDeviceTranslationInvalidCommandLineNonAsciiLanguageFlagBrowserTest,
SimpleTranslation) {
MockComponentManager mock_component_manager(GetTempDir());
mock_component_manager.ExpectCallRegisterTranslateKitComponentAndInstall();
mock_component_manager.ExpectCallRegisterLanguagePackComponentAndInstall(
{LanguagePackKey::kEn_Ja});
NavigateToEmptyPage();
TestSimpleTranslationWorks(browser(), "en", "ja");
}
} // namespace on_device_translation