blob: b7b4ca83d67880e3addc54cb3f7def18f8cedd81 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/translate/core/browser/translate_script.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/strings/escape.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "components/grit/components_resources.h"
#include "components/translate/core/browser/translate_url_fetcher.h"
#include "components/translate/core/browser/translate_url_util.h"
#include "components/translate/core/common/translate_switches.h"
#include "components/translate/core/common/translate_util.h"
#include "components/variations/variations_associated_data.h"
#include "google_apis/google_api_keys.h"
#include "net/base/url_util.h"
#include "net/http/http_request_headers.h"
#include "ui/base/resource/resource_bundle.h"
namespace translate {
namespace {
const int kExpirationDelayDays = 1;
} // namespace
const char TranslateScript::kScriptURL[] =
"https://translate.googleapis.com/translate_a/element.js";
const char TranslateScript::kRequestHeaderName[] =
"Google-Translate-Element-Mode";
const char TranslateScript::kRequestHeaderValue[] = "library";
const char TranslateScript::kAlwaysUseSslQueryName[] = "aus";
const char TranslateScript::kAlwaysUseSslQueryValue[] = "true";
const char TranslateScript::kCallbackQueryName[] = "cb";
const char TranslateScript::kCallbackQueryValue[] =
"cr.googleTranslate.onTranslateElementLoad";
const char TranslateScript::kCssLoaderCallbackQueryName[] = "clc";
const char TranslateScript::kCssLoaderCallbackQueryValue[] =
"cr.googleTranslate.onLoadCSS";
const char TranslateScript::kJavascriptLoaderCallbackQueryName[] = "jlc";
const char TranslateScript::kJavascriptLoaderCallbackQueryValue[] =
"cr.googleTranslate.onLoadJavascript";
TranslateScript::TranslateScript()
: expiration_delay_(base::Days(kExpirationDelayDays)) {}
TranslateScript::~TranslateScript() = default;
void TranslateScript::Request(RequestCallback callback, bool is_incognito) {
script_fetch_start_time_ = base::Time::Now().InMillisecondsFSinceUnixEpoch();
DCHECK(data_.empty()) << "Do not fetch the script if it is already fetched";
callback_list_.AddUnsafe(std::move(callback));
if (fetcher_) {
// If there is already a request in progress, do nothing. |callback| will be
// run on completion.
return;
}
GURL translate_script_url = GetTranslateScriptURL();
fetcher_ = std::make_unique<TranslateURLFetcher>();
net::HttpRequestHeaders headers;
headers.SetHeader(TranslateScript::kRequestHeaderName,
TranslateScript::kRequestHeaderValue);
fetcher_->set_extra_request_header(headers);
fetcher_->Request(translate_script_url,
base::BindOnce(&TranslateScript::OnScriptFetchComplete,
base::Unretained(this)),
is_incognito);
}
// static
GURL TranslateScript::GetTranslateScriptURL() {
GURL translate_script_url;
// Check if command-line contains an alternative URL for translate service.
const base::CommandLine& command_line =
*base::CommandLine::ForCurrentProcess();
if (command_line.HasSwitch(translate::switches::kTranslateScriptURL)) {
translate_script_url = GURL(command_line.GetSwitchValueASCII(
translate::switches::kTranslateScriptURL));
if (!translate_script_url.is_valid()) {
LOG(WARNING) << "The following translate URL specified at the "
<< "command-line is invalid: "
<< translate_script_url.spec();
translate_script_url = GURL();
} else {
LOG(WARNING) << "Using custom translate URL: "
<< translate_script_url.spec();
}
}
// Use default URL when command-line argument is not specified, or specified
// URL is invalid.
if (translate_script_url.is_empty())
translate_script_url = GURL(kScriptURL);
translate_script_url = net::AppendQueryParameter(
translate_script_url, kCallbackQueryName, kCallbackQueryValue);
translate_script_url = net::AppendQueryParameter(
translate_script_url, kAlwaysUseSslQueryName, kAlwaysUseSslQueryValue);
translate_script_url = net::AppendQueryParameter(
translate_script_url, kCssLoaderCallbackQueryName,
kCssLoaderCallbackQueryValue);
translate_script_url = net::AppendQueryParameter(
translate_script_url, kJavascriptLoaderCallbackQueryName,
kJavascriptLoaderCallbackQueryValue);
translate_script_url = AddHostLocaleToUrl(translate_script_url);
translate_script_url = AddApiKeyToUrl(translate_script_url);
return translate_script_url;
}
void TranslateScript::OnScriptFetchComplete(bool success,
const std::string& data) {
std::unique_ptr<const TranslateURLFetcher> delete_ptr(std::move(fetcher_));
if (success) {
DCHECK(data_.empty());
// Insert variable definitions on API Key and security origin.
data_ = base::StringPrintf("var translateApiKey = '%s';\n",
google_apis::GetAPIKey().c_str());
// Insert server params to pass experimental params to google translate
// server.
std::string server_params;
std::map<std::string, std::string> params;
base::StringAppendF(
&data_, "var gtTimeInfo = {'fetchStart': %0.f, 'fetchEnd': %0.f};\n",
script_fetch_start_time_,
base::Time::Now().InMillisecondsFSinceUnixEpoch());
base::StringAppendF(&data_, "var serverParams = '%s';\n",
server_params.c_str());
GURL security_origin = translate::GetTranslateSecurityOrigin();
base::StringAppendF(&data_, "var securityOrigin = '%s';\n",
security_origin.spec().c_str());
// Load embedded translate.js.
data_.append(ui::ResourceBundle::GetSharedInstance().LoadDataResourceString(
IDR_TRANSLATE_JS));
#if BUILDFLAG(IS_IOS)
// Append snippet to install callbacks on translate.js if available.
const char* install_callbacks =
"try {"
" "
"__gCrWeb.getRegisteredApi('translate').getFunction('installCallbacks')"
"();"
"} catch (error) {};";
data_.append(install_callbacks);
#endif // BUILDFLAG(IS_IOS)
// Wrap |data| in try/catch block to handle unexpected script errors.
static constexpr char kFormat[] =
"try {"
" %s;"
"} catch (error) {"
" cr.googleTranslate.onTranslateElementError(error);"
"};";
base::StringAppendF(&data_, kFormat, data.c_str());
// We'll expire the cached script after some time, to make sure long
// running browsers still get fixes that might get pushed with newer
// scripts.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&TranslateScript::Clear,
weak_method_factory_.GetWeakPtr()),
expiration_delay_);
}
callback_list_.Notify(success);
}
} // namespace translate