1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "components/translate/core/browser/translate_script.h"
6
7#include "base/bind.h"
8#include "base/command_line.h"
9#include "base/logging.h"
10#include "base/message_loop/message_loop.h"
11#include "base/strings/string_piece.h"
12#include "base/strings/string_util.h"
13#include "base/strings/stringprintf.h"
14#include "components/translate/core/browser/translate_url_fetcher.h"
15#include "components/translate/core/browser/translate_url_util.h"
16#include "components/translate/core/common/translate_switches.h"
17#include "components/translate/core/common/translate_util.h"
18#include "google_apis/google_api_keys.h"
19#include "grit/components_resources.h"
20#include "net/base/escape.h"
21#include "net/base/url_util.h"
22#include "ui/base/resource/resource_bundle.h"
23
24namespace translate {
25
26namespace {
27
28const int kExpirationDelayDays = 1;
29
30}  // namespace
31
32const char TranslateScript::kScriptURL[] =
33    "https://translate.google.com/translate_a/element.js";
34const char TranslateScript::kRequestHeader[] =
35    "Google-Translate-Element-Mode: library";
36const char TranslateScript::kAlwaysUseSslQueryName[] = "aus";
37const char TranslateScript::kAlwaysUseSslQueryValue[] = "true";
38const char TranslateScript::kCallbackQueryName[] = "cb";
39const char TranslateScript::kCallbackQueryValue[] =
40    "cr.googleTranslate.onTranslateElementLoad";
41const char TranslateScript::kCssLoaderCallbackQueryName[] = "clc";
42const char TranslateScript::kCssLoaderCallbackQueryValue[] =
43    "cr.googleTranslate.onLoadCSS";
44const char TranslateScript::kJavascriptLoaderCallbackQueryName[] = "jlc";
45const char TranslateScript::kJavascriptLoaderCallbackQueryValue[] =
46    "cr.googleTranslate.onLoadJavascript";
47
48TranslateScript::TranslateScript()
49    : expiration_delay_(base::TimeDelta::FromDays(kExpirationDelayDays)),
50      weak_method_factory_(this) {
51}
52
53TranslateScript::~TranslateScript() {
54}
55
56void TranslateScript::Request(const RequestCallback& callback) {
57  DCHECK(data_.empty()) << "Do not fetch the script if it is already fetched";
58  callback_list_.push_back(callback);
59
60  if (fetcher_.get() != NULL) {
61    // If there is already a request in progress, do nothing. |callback| will be
62    // run on completion.
63    return;
64  }
65
66  GURL translate_script_url;
67  // Check if command-line contains an alternative URL for translate service.
68  const CommandLine& command_line = *CommandLine::ForCurrentProcess();
69  if (command_line.HasSwitch(translate::switches::kTranslateScriptURL)) {
70    translate_script_url = GURL(command_line.GetSwitchValueASCII(
71        translate::switches::kTranslateScriptURL));
72    if (!translate_script_url.is_valid() ||
73        !translate_script_url.query().empty()) {
74      LOG(WARNING) << "The following translate URL specified at the "
75                   << "command-line is invalid: "
76                   << translate_script_url.spec();
77      translate_script_url = GURL();
78    }
79  }
80
81  // Use default URL when command-line argument is not specified, or specified
82  // URL is invalid.
83  if (translate_script_url.is_empty())
84    translate_script_url = GURL(kScriptURL);
85
86  translate_script_url = net::AppendQueryParameter(
87      translate_script_url,
88      kCallbackQueryName,
89      kCallbackQueryValue);
90  translate_script_url = net::AppendQueryParameter(
91      translate_script_url,
92      kAlwaysUseSslQueryName,
93      kAlwaysUseSslQueryValue);
94#if !defined(OS_IOS)
95  // iOS doesn't need to use specific loaders for the isolated world.
96  translate_script_url = net::AppendQueryParameter(
97      translate_script_url,
98      kCssLoaderCallbackQueryName,
99      kCssLoaderCallbackQueryValue);
100  translate_script_url = net::AppendQueryParameter(
101      translate_script_url,
102      kJavascriptLoaderCallbackQueryName,
103      kJavascriptLoaderCallbackQueryValue);
104#endif  // !defined(OS_IOS)
105
106  translate_script_url = AddHostLocaleToUrl(translate_script_url);
107  translate_script_url = AddApiKeyToUrl(translate_script_url);
108
109  fetcher_.reset(new TranslateURLFetcher(kFetcherId));
110  fetcher_->set_extra_request_header(kRequestHeader);
111  fetcher_->Request(
112      translate_script_url,
113      base::Bind(&TranslateScript::OnScriptFetchComplete,
114                 base::Unretained(this)));
115}
116
117
118void TranslateScript::OnScriptFetchComplete(
119    int id, bool success, const std::string& data) {
120  DCHECK_EQ(kFetcherId, id);
121
122  scoped_ptr<const TranslateURLFetcher> delete_ptr(fetcher_.release());
123
124  if (success) {
125    DCHECK(data_.empty());
126    // Insert variable definitions on API Key and security origin.
127    data_ = base::StringPrintf("var translateApiKey = '%s';\n",
128                               google_apis::GetAPIKey().c_str());
129
130    GURL security_origin = translate::GetTranslateSecurityOrigin();
131    base::StringAppendF(
132        &data_, "var securityOrigin = '%s';", security_origin.spec().c_str());
133
134    // Append embedded translate.js and a remote element library.
135    base::StringPiece str = ResourceBundle::GetSharedInstance().
136        GetRawDataResource(IDR_TRANSLATE_JS);
137    str.AppendToString(&data_);
138    data_ += data;
139
140    // We'll expire the cached script after some time, to make sure long
141    // running browsers still get fixes that might get pushed with newer
142    // scripts.
143    base::MessageLoop::current()->PostDelayedTask(
144        FROM_HERE,
145        base::Bind(&TranslateScript::Clear,
146                   weak_method_factory_.GetWeakPtr()),
147        expiration_delay_);
148  }
149
150  for (RequestCallbackList::iterator it = callback_list_.begin();
151       it != callback_list_.end();
152       ++it) {
153    it->Run(success, data);
154  }
155  callback_list_.clear();
156}
157
158}  // namespace translate
159