#include "extensions/renderer/api/i18n_hooks_delegate.h"
#include <vector>
#include "base/check.h"
#include "base/i18n/rtl.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_thread.h"
#include "extensions/common/extension.h"
#include "extensions/common/message_bundle.h"
#include "extensions/renderer/bindings/api_binding_types.h"
#include "extensions/renderer/bindings/js_runner.h"
#include "extensions/renderer/get_script_context.h"
#include "extensions/renderer/script_context.h"
#include "extensions/renderer/shared_l10n_map.h"
#include "extensions/renderer/worker_thread_dispatcher.h"
#include "gin/converter.h"
#include "gin/data_object_builder.h"
#include "third_party/cld_3/src/src/nnet_language_identifier.h"
#include "v8/include/v8-container.h"
#include "v8/include/v8-context.h"
#include "v8/include/v8-exception.h"
#include "v8/include/v8-isolate.h"
#include "v8/include/v8-object.h"
#include "v8/include/v8-primitive.h"
namespace extensions {
namespace {
constexpr char kGetMessage[] = "i18n.getMessage";
constexpr char kGetUILanguage[] = "i18n.getUILanguage";
constexpr char kDetectLanguage[] = "i18n.detectLanguage";
const int kCldNumLangs = 3;
const int kCld3MinimumByteThreshold = 50;
struct DetectedLanguage {
DetectedLanguage(const std::string& language, int percentage)
: language(language), percentage(percentage) {}
v8::Local<v8::Value> ToV8(v8::Isolate* isolate) const;
std::string language;
int percentage;
};
struct LanguageDetectionResult {
LanguageDetectionResult() {}
LanguageDetectionResult(const LanguageDetectionResult&) = delete;
LanguageDetectionResult& operator=(const LanguageDetectionResult&) = delete;
~LanguageDetectionResult() {}
v8::Local<v8::Value> ToV8(v8::Local<v8::Context> context) const;
bool is_reliable = false;
std::vector<DetectedLanguage> languages;
};
v8::Local<v8::Value> DetectedLanguage::ToV8(v8::Isolate* isolate) const {
return gin::DataObjectBuilder(isolate)
.Set("language", language)
.Set("percentage", percentage)
.Build();
}
v8::Local<v8::Value> LanguageDetectionResult::ToV8(
v8::Local<v8::Context> context) const {
v8::Isolate* isolate = context->GetIsolate();
DCHECK(isolate->GetCurrentContext() == context);
v8::Local<v8::Array> v8_languages = v8::Array::New(isolate, languages.size());
for (uint32_t i = 0; i < languages.size(); ++i) {
bool success =
v8_languages->CreateDataProperty(context, i, languages[i].ToV8(isolate))
.ToChecked();
DCHECK(success) << "CreateDataProperty() should never fail.";
}
return gin::DataObjectBuilder(isolate)
.Set("isReliable", is_reliable)
.Set("languages", v8_languages.As<v8::Value>())
.Build();
}
void InitDetectedLanguages(
const std::vector<chrome_lang_id::NNetLanguageIdentifier::Result>&
lang_results,
LanguageDetectionResult* result) {
std::vector<DetectedLanguage>* detected_languages = &result->languages;
DCHECK(detected_languages->empty());
bool* is_reliable = &result->is_reliable;
*is_reliable = true;
for (const auto& lang_result : lang_results) {
const std::string& language_code = lang_result.language;
if (language_code == chrome_lang_id::NNetLanguageIdentifier::kUnknown) {
break;
}
if (base::EndsWith(language_code, "-Latn",
base::CompareCase::INSENSITIVE_ASCII)) {
continue;
}
*is_reliable = *is_reliable && lang_result.is_reliable;
const int percent = static_cast<int>(100 * lang_result.proportion);
detected_languages->emplace_back(language_code, percent);
}
if (detected_languages->empty())
*is_reliable = false;
}
v8::Local<v8::Value> GetI18nMessage(const std::string& message_name,
const std::string& extension_id,
v8::Local<v8::Value> v8_substitutions,
v8::Local<v8::Value> v8_options,
IPC::Sender* message_sender,
v8::Local<v8::Context> context) {
v8::Isolate* isolate = context->GetIsolate();
std::string message = SharedL10nMap::GetInstance().GetMessage(
extension_id, message_name, message_sender);
std::vector<std::string> substitutions;
v8::TryCatch try_catch(isolate);
if (v8_substitutions->IsArray()) {
v8::Local<v8::Array> placeholders = v8_substitutions.As<v8::Array>();
uint32_t count = placeholders->Length();
if (count > 9)
return v8::Undefined(isolate);
for (uint32_t i = 0; i < count; ++i) {
v8::Local<v8::Value> placeholder;
if (!placeholders->Get(context, i).ToLocal(&placeholder))
return v8::Undefined(isolate);
v8::String::Utf8Value string_value(isolate, placeholder);
if (*string_value)
substitutions.push_back(*string_value);
}
} else if (v8_substitutions->IsString()) {
substitutions.push_back(gin::V8ToString(isolate, v8_substitutions));
}
if (v8_options->IsObject()) {
v8::Local<v8::Object> options = v8_options.As<v8::Object>();
v8::Local<v8::Value> key =
v8::String::NewFromUtf8(isolate, "escapeLt").ToLocalChecked();
v8::Local<v8::Value> html;
if (options->Get(context, key).ToLocal(&html) && html->IsBoolean() &&
html.As<v8::Boolean>()->Value()) {
base::ReplaceChars(message, "<", "<", &message);
}
}
message = base::ReplaceStringPlaceholders(message, substitutions, nullptr);
return gin::StringToV8(isolate, message);
}
v8::Local<v8::Value> DetectTextLanguage(v8::Local<v8::Context> context,
const std::string& text) {
chrome_lang_id::NNetLanguageIdentifier nnet_lang_id(0,
512);
std::vector<chrome_lang_id::NNetLanguageIdentifier::Result> lang_results =
nnet_lang_id.FindTopNMostFreqLangs(text, kCldNumLangs);
if (text.size() < kCld3MinimumByteThreshold) {
for (auto& result : lang_results)
result.is_reliable = false;
}
LanguageDetectionResult result;
InitDetectedLanguages(lang_results, &result);
return result.ToV8(context);
}
}
using RequestResult = APIBindingHooks::RequestResult;
I18nHooksDelegate::I18nHooksDelegate() {}
I18nHooksDelegate::~I18nHooksDelegate() = default;
RequestResult I18nHooksDelegate::HandleRequest(
const std::string& method_name,
const APISignature* signature,
v8::Local<v8::Context> context,
std::vector<v8::Local<v8::Value>>* arguments,
const APITypeReferenceMap& refs) {
using Handler = RequestResult (I18nHooksDelegate::*)(
ScriptContext*, const APISignature::V8ParseResult&);
static constexpr struct {
Handler handler;
base::StringPiece method;
} kHandlers[] = {
{&I18nHooksDelegate::HandleGetMessage, kGetMessage},
{&I18nHooksDelegate::HandleGetUILanguage, kGetUILanguage},
{&I18nHooksDelegate::HandleDetectLanguage, kDetectLanguage},
};
ScriptContext* script_context = GetScriptContextFromV8ContextChecked(context);
Handler handler = nullptr;
for (const auto& handler_entry : kHandlers) {
if (handler_entry.method == method_name) {
handler = handler_entry.handler;
break;
}
}
if (!handler)
return RequestResult(RequestResult::NOT_HANDLED);
APISignature::V8ParseResult parse_result =
signature->ParseArgumentsToV8(context, *arguments, refs);
if (!parse_result.succeeded()) {
RequestResult result(RequestResult::INVALID_INVOCATION);
result.error = std::move(*parse_result.error);
return result;
}
return (this->*handler)(script_context, parse_result);
}
RequestResult I18nHooksDelegate::HandleGetMessage(
ScriptContext* script_context,
const APISignature::V8ParseResult& parse_result) {
const std::vector<v8::Local<v8::Value>>& arguments = *parse_result.arguments;
DCHECK_EQ(binding::AsyncResponseType::kNone, parse_result.async_type);
DCHECK(script_context->extension());
DCHECK(arguments[0]->IsString());
IPC::Sender* message_sender = nullptr;
if (script_context->IsForServiceWorker()) {
message_sender = WorkerThreadDispatcher::Get();
} else {
message_sender = script_context->GetRenderFrame();
}
v8::Local<v8::Value> message = GetI18nMessage(
gin::V8ToString(script_context->isolate(), arguments[0]),
script_context->extension()->id(), arguments[1], arguments[2],
message_sender, script_context->v8_context());
RequestResult result(RequestResult::HANDLED);
result.return_value = message;
return result;
}
RequestResult I18nHooksDelegate::HandleGetUILanguage(
ScriptContext* script_context,
const APISignature::V8ParseResult& parse_result) {
DCHECK_EQ(binding::AsyncResponseType::kNone, parse_result.async_type);
RequestResult result(RequestResult::HANDLED);
const std::string lang = base::i18n::GetConfiguredLocale();
DCHECK(!lang.empty());
result.return_value = gin::StringToSymbol(script_context->isolate(), lang);
return result;
}
RequestResult I18nHooksDelegate::HandleDetectLanguage(
ScriptContext* script_context,
const APISignature::V8ParseResult& parse_result) {
const std::vector<v8::Local<v8::Value>>& arguments = *parse_result.arguments;
DCHECK(arguments[0]->IsString());
v8::Local<v8::Context> v8_context = script_context->v8_context();
v8::Local<v8::Value> detected_languages = DetectTextLanguage(
v8_context, gin::V8ToString(script_context->isolate(), arguments[0]));
v8::Local<v8::Value> response_args[] = {detected_languages};
RequestResult result(RequestResult::HANDLED);
if (parse_result.async_type == binding::AsyncResponseType::kCallback) {
DCHECK(arguments[1]->IsFunction());
JSRunner::Get(v8_context)
->RunJSFunction(arguments[1].As<v8::Function>(), v8_context,
std::size(response_args), response_args);
} else {
DCHECK_EQ(binding::AsyncResponseType::kPromise, parse_result.async_type);
auto promise_resolver =
v8::Promise::Resolver::New(v8_context).ToLocalChecked();
promise_resolver->Resolve(v8_context, response_args[0]).FromJust();
result.return_value = promise_resolver->GetPromise();
}
return result;
}
}