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// This code is used in conjunction with the Google Translate Element script.
6// It is executed in an isolated world of a page to translate it from one
7// language to another.
8// It should be included in the page before the Translate Element script.
9
10var cr = cr || {};
11
12/**
13 * An object to provide functions to interact with the Translate library.
14 * @type {object}
15 */
16cr.googleTranslate = (function() {
17  /**
18   * The Translate Element library's instance.
19   * @type {object}
20   */
21  var lib;
22
23  /**
24   * A flag representing if the Translate Element library is initialized.
25   * @type {boolean}
26   */
27  var libReady = false;
28
29  /**
30   * Error definitions for |errorCode|. See chrome/common/translate_errors.h
31   * to modify the definition.
32   * @const
33   */
34  var ERROR = {
35    'NONE': 0,
36    'INITIALIZATION_ERROR': 2,
37    'UNSUPPORTED_LANGUAGE': 4,
38    'TRANSLATION_ERROR': 6,
39    'TRANSLATION_TIMEOUT': 7,
40    'UNEXPECTED_SCRIPT_ERROR': 8,
41    'BAD_ORIGIN': 9,
42    'SCRIPT_LOAD_ERROR': 10
43  };
44
45  /**
46   * Error code map from te.dom.DomTranslator.Error to |errorCode|.
47   * See also go/dom_translator.js in google3.
48   * @const
49   */
50  var TRANSLATE_ERROR_TO_ERROR_CODE_MAP = {
51    0: ERROR['NONE'],
52    1: ERROR['TRANSLATION_ERROR'],
53    2: ERROR['UNSUPPORTED_LANGUAGE']
54  };
55
56  /**
57   * An error code happened in translate.js and the Translate Element library.
58   */
59  var errorCode = ERROR['NONE'];
60
61  /**
62   * A flag representing if the Translate Element has finished a translation.
63   * @type {boolean}
64   */
65  var finished = false;
66
67  /**
68   * Counts how many times the checkLibReady function is called. The function
69   * is called in every 100 msec and counted up to 6.
70   * @type {number}
71   */
72  var checkReadyCount = 0;
73
74  /**
75   * Time in msec when this script is injected.
76   * @type {number}
77   */
78  var injectedTime = performance.now();
79
80  /**
81   * Time in msec when the Translate Element library is loaded completely.
82   * @type {number}
83   */
84  var loadedTime = 0.0;
85
86  /**
87   * Time in msec when the Translate Element library is initialized and ready
88   * for performing translation.
89   * @type {number}
90   */
91  var readyTime = 0.0;
92
93  /**
94   * Time in msec when the Translate Element library starts a translation.
95   * @type {number}
96   */
97  var startTime = 0.0;
98
99  /**
100   * Time in msec when the Translate Element library ends a translation.
101   * @type {number}
102   */
103  var endTime = 0.0;
104
105  function checkLibReady() {
106    if (lib.isAvailable()) {
107      readyTime = performance.now();
108      libReady = true;
109      return;
110    }
111    if (checkReadyCount++ > 5) {
112      errorCode = ERROR['TRANSLATION_TIMEOUT'];
113      return;
114    }
115    setTimeout(checkLibReady, 100);
116  }
117
118  function onTranslateProgress(progress, opt_finished, opt_error) {
119    finished = opt_finished;
120    // opt_error can be 'undefined'.
121    if (typeof opt_error == 'boolean' && opt_error) {
122      // TODO(toyoshim): Remove boolean case once a server is updated.
123      errorCode = ERROR['TRANSLATION_ERROR'];
124      // We failed to translate, restore so the page is in a consistent state.
125      lib.restore();
126    } else if (typeof opt_error == 'number' && opt_error != 0) {
127      errorCode = TRANSLATE_ERROR_TO_ERROR_CODE_MAP[opt_error];
128      lib.restore();
129    }
130    if (finished)
131      endTime = performance.now();
132  }
133
134  // Public API.
135  return {
136    /**
137     * Whether the library is ready.
138     * The translate function should only be called when |libReady| is true.
139     * @type {boolean}
140     */
141    get libReady() {
142      return libReady;
143    },
144
145    /**
146     * Whether the current translate has finished successfully.
147     * @type {boolean}
148     */
149    get finished() {
150      return finished;
151    },
152
153    /**
154     * Whether an error occured initializing the library of translating the
155     * page.
156     * @type {boolean}
157     */
158    get error() {
159      return errorCode != ERROR['NONE'];
160    },
161
162    /**
163     * Returns a number to represent error type.
164     * @type {number}
165     */
166    get errorCode() {
167      return errorCode;
168    },
169
170    /**
171     * The language the page translated was in. Is valid only after the page
172     * has been successfully translated and the original language specified to
173     * the translate function was 'auto'. Is empty otherwise.
174     * Some versions of Element library don't provide |getDetectedLanguage|
175     * function. In that case, this function returns 'und'.
176     * @type {boolean}
177     */
178    get sourceLang() {
179      if (!libReady || !finished || errorCode != ERROR['NONE'])
180        return '';
181      if (!lib.getDetectedLanguage)
182        return 'und'; // Defined as translate::kUnknownLanguageCode in C++.
183      return lib.getDetectedLanguage();
184    },
185
186    /**
187     * Time in msec from this script being injected to all server side scripts
188     * being loaded.
189     * @type {number}
190     */
191    get loadTime() {
192      if (loadedTime == 0)
193        return 0;
194      return loadedTime - injectedTime;
195    },
196
197    /**
198     * Time in msec from this script being injected to the Translate Element
199     * library being ready.
200     * @type {number}
201     */
202    get readyTime() {
203      if (!libReady)
204        return 0;
205      return readyTime - injectedTime;
206    },
207
208    /**
209     * Time in msec to perform translation.
210     * @type {number}
211     */
212    get translationTime() {
213      if (!finished)
214        return 0;
215      return endTime - startTime;
216    },
217
218    /**
219     * Translate the page contents.  Note that the translation is asynchronous.
220     * You need to regularly check the state of |finished| and |errorCode| to
221     * know if the translation finished or if there was an error.
222     * @param {string} originalLang The language the page is in.
223     * @param {string} targetLang The language the page should be translated to.
224     * @return {boolean} False if the translate library was not ready, in which
225     *                   case the translation is not started.  True otherwise.
226     */
227    translate: function(originalLang, targetLang) {
228      finished = false;
229      errorCode = ERROR['NONE'];
230      if (!libReady)
231        return false;
232      startTime = performance.now();
233      try {
234        lib.translatePage(originalLang, targetLang, onTranslateProgress);
235      } catch (err) {
236        console.error('Translate: ' + err);
237        errorCode = ERROR['UNEXPECTED_SCRIPT_ERROR'];
238        return false;
239      }
240      return true;
241    },
242
243    /**
244     * Reverts the page contents to its original value, effectively reverting
245     * any performed translation.  Does nothing if the page was not translated.
246     */
247    revert: function() {
248      lib.restore();
249    },
250
251    /**
252     * Entry point called by the Translate Element once it has been injected in
253     * the page.
254     */
255    onTranslateElementLoad: function() {
256      loadedTime = performance.now();
257      try {
258        lib = google.translate.TranslateService({
259          // translateApiKey is predefined by translate_script.cc.
260          'key': translateApiKey,
261          'useSecureConnection': true
262        });
263        translateApiKey = undefined;
264      } catch (err) {
265        errorCode = ERROR['INITIALIZATION_ERROR'];
266        translateApiKey = undefined;
267        return;
268      }
269      // The TranslateService is not available immediately as it needs to start
270      // Flash.  Let's wait until it is ready.
271      checkLibReady();
272    },
273
274    /**
275     * Entry point called by the Translate Element when it want to load an
276     * external CSS resource into the page.
277     * @param {string} url URL of an external CSS resource to load.
278     */
279    onLoadCSS: function(url) {
280      var element = document.createElement('link');
281      element.type = 'text/css';
282      element.rel = 'stylesheet';
283      element.charset = 'UTF-8';
284      element.href = url;
285      document.head.appendChild(element);
286    },
287
288    /**
289     * Entry point called by the Translate Element when it want to load and run
290     * an external JavaScript on the page.
291     * @param {string} url URL of an external JavaScript to load.
292     */
293    onLoadJavascript: function(url) {
294      // securityOrigin is predefined by translate_script.cc.
295      if (url.indexOf(securityOrigin) != 0) {
296        console.error('Translate: ' + url + ' is not allowed to load.');
297        errorCode = ERROR['BAD_ORIGIN'];
298        return;
299      }
300      var xhr = new XMLHttpRequest();
301      xhr.open('GET', url, true);
302      xhr.onreadystatechange = function() {
303        if (this.readyState != this.DONE)
304          return;
305        if (this.status != 200) {
306          errorCode = ERROR['SCRIPT_LOAD_ERROR'];
307          return;
308        }
309        eval(this.responseText);
310      }
311      xhr.send();
312    }
313  };
314})();
315