1// Copyright 2013 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 "chrome/browser/translate/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 "chrome/browser/translate/translate_url_fetcher.h"
15#include "chrome/browser/translate/translate_url_util.h"
16#include "chrome/common/chrome_switches.h"
17#include "components/translate/common/translate_util.h"
18#include "google_apis/google_api_keys.h"
19#include "grit/browser_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 {
25
26const int kExpirationDelayDays = 1;
27
28}  // namespace
29
30const char TranslateScript::kScriptURL[] =
31    "https://translate.google.com/translate_a/element.js";
32const char TranslateScript::kRequestHeader[] =
33    "Google-Translate-Element-Mode: library";
34const char TranslateScript::kAlwaysUseSslQueryName[] = "aus";
35const char TranslateScript::kAlwaysUseSslQueryValue[] = "true";
36const char TranslateScript::kCallbackQueryName[] = "cb";
37const char TranslateScript::kCallbackQueryValue[] =
38    "cr.googleTranslate.onTranslateElementLoad";
39const char TranslateScript::kCssLoaderCallbackQueryName[] = "clc";
40const char TranslateScript::kCssLoaderCallbackQueryValue[] =
41    "cr.googleTranslate.onLoadCSS";
42const char TranslateScript::kJavascriptLoaderCallbackQueryName[] = "jlc";
43const char TranslateScript::kJavascriptLoaderCallbackQueryValue[] =
44    "cr.googleTranslate.onLoadJavascript";
45
46TranslateScript::TranslateScript()
47    : expiration_delay_(base::TimeDelta::FromDays(kExpirationDelayDays)),
48      weak_method_factory_(this) {
49}
50
51TranslateScript::~TranslateScript() {
52}
53
54void TranslateScript::Request(const Callback& callback) {
55  if (fetcher_.get() != NULL) {
56    NOTREACHED();
57    return;
58  }
59
60  callback_ = callback;
61
62  GURL translate_script_url;
63  // Check if command-line contains an alternative URL for translate service.
64  const CommandLine& command_line = *CommandLine::ForCurrentProcess();
65  if (command_line.HasSwitch(switches::kTranslateScriptURL)) {
66    translate_script_url = GURL(
67        command_line.GetSwitchValueASCII(switches::kTranslateScriptURL));
68    if (!translate_script_url.is_valid() ||
69        !translate_script_url.query().empty()) {
70      LOG(WARNING) << "The following translate URL specified at the "
71                   << "command-line is invalid: "
72                   << translate_script_url.spec();
73      translate_script_url = GURL();
74    }
75  }
76
77  // Use default URL when command-line argument is not specified, or specified
78  // URL is invalid.
79  if (translate_script_url.is_empty())
80    translate_script_url = GURL(kScriptURL);
81
82  translate_script_url = net::AppendQueryParameter(
83      translate_script_url,
84      kCallbackQueryName,
85      kCallbackQueryValue);
86  translate_script_url = net::AppendQueryParameter(
87      translate_script_url,
88      kAlwaysUseSslQueryName,
89      kAlwaysUseSslQueryValue);
90#if !defined(OS_IOS)
91  // iOS doesn't need to use specific loaders for the isolated world.
92  translate_script_url = net::AppendQueryParameter(
93      translate_script_url,
94      kCssLoaderCallbackQueryName,
95      kCssLoaderCallbackQueryValue);
96  translate_script_url = net::AppendQueryParameter(
97      translate_script_url,
98      kJavascriptLoaderCallbackQueryName,
99      kJavascriptLoaderCallbackQueryValue);
100#endif  // !defined(OS_IOS)
101
102  translate_script_url =
103      TranslateURLUtil::AddHostLocaleToUrl(translate_script_url);
104  translate_script_url =
105      TranslateURLUtil::AddApiKeyToUrl(translate_script_url);
106
107  fetcher_.reset(new TranslateURLFetcher(kFetcherId));
108  fetcher_->set_extra_request_header(kRequestHeader);
109  fetcher_->Request(
110      translate_script_url,
111      base::Bind(&TranslateScript::OnScriptFetchComplete,
112                 base::Unretained(this)));
113}
114
115
116void TranslateScript::OnScriptFetchComplete(
117    int id, bool success, const std::string& data) {
118  DCHECK_EQ(kFetcherId, id);
119
120  scoped_ptr<const TranslateURLFetcher> delete_ptr(fetcher_.release());
121
122  if (success) {
123    DCHECK(data_.empty());
124    // Insert variable definitions on API Key and security origin.
125    data_ = base::StringPrintf("var translateApiKey = '%s';\n",
126                               google_apis::GetAPIKey().c_str());
127
128    GURL security_origin = translate::GetTranslateSecurityOrigin();
129    base::StringAppendF(
130        &data_, "var securityOrigin = '%s';", security_origin.spec().c_str());
131
132    // Append embedded translate.js and a remote element library.
133    base::StringPiece str = ResourceBundle::GetSharedInstance().
134        GetRawDataResource(IDR_TRANSLATE_JS);
135    str.AppendToString(&data_);
136    data_ += data;
137
138    // We'll expire the cached script after some time, to make sure long
139    // running browsers still get fixes that might get pushed with newer
140    // scripts.
141    base::MessageLoop::current()->PostDelayedTask(
142        FROM_HERE,
143        base::Bind(&TranslateScript::Clear,
144                   weak_method_factory_.GetWeakPtr()),
145        expiration_delay_);
146  }
147
148  callback_.Run(success, data);
149}
150