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 (function() { 6 'use strict'; 7 8 cr.define('cr.translateInternals', function() { 9 10 var detectionLogs_ = null; 11 12 function detectionLogs() { 13 if (detectionLogs_ === null) 14 detectionLogs_ = []; 15 return detectionLogs_; 16 } 17 18 /** 19 * Initializes UI and sends a message to the browser for 20 * initialization. 21 */ 22 function initialize() { 23 cr.ui.decorate('tabbox', cr.ui.TabBox); 24 chrome.send('requestInfo'); 25 26 var button = $('detection-logs-dump'); 27 button.addEventListener('click', onDetectionLogsDump); 28 29 var enableTranslateSettings = templateData['enable-translate-settings']; 30 if (!enableTranslateSettings) { 31 $('prefs-blocked-languages').hidden = true; 32 $('prefs-language-blacklist').querySelector('h2 span').hidden = true; 33 } 34 35 var tabpanelNodeList = document.getElementsByTagName('tabpanel'); 36 var tabpanels = Array.prototype.slice.call(tabpanelNodeList, 0); 37 var tabpanelIds = tabpanels.map(function(tab) { 38 return tab.id; 39 }); 40 41 var tabNodeList = document.getElementsByTagName('tab'); 42 var tabs = Array.prototype.slice.call(tabNodeList, 0); 43 tabs.forEach(function(tab) { 44 tab.onclick = function(e) { 45 var tabbox = document.querySelector('tabbox'); 46 var tabpanel = tabpanels[tabbox.selectedIndex]; 47 var hash = tabpanel.id.match(/(?:^tabpanel-)(.+)/)[1]; 48 window.location.hash = hash; 49 }; 50 }); 51 52 window.onhashchange = function(e) { 53 var hash = window.location.hash; 54 55 // Remove the first character '#'. 56 hash = hash.substring(1); 57 58 var id = 'tabpanel-' + hash; 59 if (tabpanelIds.indexOf(id) == -1) 60 return; 61 62 $(id).selected = true; 63 }; 64 } 65 66 /** 67 * Creates a new LI element with a button to dismiss the item. 68 * 69 * @param {string} text The lable of the LI element. 70 * @param {Function} func Callback called when the button is clicked. 71 */ 72 function createLIWithDismissingButton(text, func) { 73 var span = document.createElement('span'); 74 span.textContent = text; 75 76 var li = document.createElement('li'); 77 li.appendChild(span); 78 79 var button = document.createElement('button'); 80 button.textContent = 'X'; 81 button.addEventListener('click', function(e) { 82 e.preventDefault(); 83 func(); 84 }, false); 85 86 li.appendChild(button); 87 return li; 88 } 89 90 /** 91 * Formats the language name to a human-readable text. For example, if 92 * |langCode| is 'en', this may return 'en (English)'. 93 * 94 * @param {string} langCode ISO 639 language code. 95 * @return {string} The formatted string. 96 */ 97 function formatLanguageCode(langCode) { 98 var key = 'language-' + langCode; 99 if (key in templateData) { 100 var langName = templateData[key]; 101 return langCode + ' (' + langName + ')'; 102 } 103 104 return langCode; 105 } 106 107 /** 108 * Formats the error type to a human-readable text. 109 * 110 * @param {string} error Translation error type from the browser. 111 * @return {string} The formatted string. 112 */ 113 function formatTranslateErrorsType(error) { 114 // This list is from chrome/common/translate/translate_errors.h. 115 // If this header file is updated, the below list also should be updated. 116 var errorStrs = { 117 0: 'None', 118 1: 'Network', 119 2: 'Initialization Error', 120 3: 'Unknown Language', 121 4: 'Unsupported Language', 122 5: 'Identical Languages', 123 6: 'Translation Error', 124 }; 125 126 if (error < 0 || errorStrs.length <= error) { 127 console.error('Invalid error code:', error); 128 return 'Invalid Error Code'; 129 } 130 return errorStrs[error]; 131 } 132 133 /** 134 * Handles the message of 'prefsUpdated' from the browser. 135 * 136 * @param {Object} detail the object which represents pref values. 137 */ 138 function onPrefsUpdated(detail) { 139 var ul; 140 141 ul = document.querySelector('#prefs-blocked-languages ul'); 142 ul.innerHTML = ''; 143 144 if ('translate_blocked_languages' in detail) { 145 var langs = detail['translate_blocked_languages']; 146 147 langs.forEach(function(langCode) { 148 var text = formatLanguageCode(langCode); 149 150 var li = createLIWithDismissingButton(text, function() { 151 chrome.send('removePrefItem', 152 ['blocked_languages', langCode]); 153 }); 154 ul.appendChild(li); 155 }); 156 } 157 158 ul = document.querySelector('#prefs-language-blacklist ul'); 159 ul.innerHTML = ''; 160 161 if ('translate_language_blacklist' in detail) { 162 var langs = detail['translate_language_blacklist']; 163 164 langs.forEach(function(langCode) { 165 var text = formatLanguageCode(langCode); 166 167 var li = createLIWithDismissingButton(text, function() { 168 chrome.send('removePrefItem', 169 ['language_blacklist', langCode]); 170 }); 171 ul.appendChild(li); 172 }); 173 } 174 175 ul = document.querySelector('#prefs-site-blacklist ul'); 176 ul.innerHTML = ''; 177 178 if ('translate_site_blacklist' in detail) { 179 var sites = detail['translate_site_blacklist']; 180 181 sites.forEach(function(site) { 182 var li = createLIWithDismissingButton(site, function() { 183 chrome.send('removePrefItem', ['site_blacklist', site]); 184 }); 185 ul.appendChild(li); 186 }); 187 } 188 189 ul = document.querySelector('#prefs-whitelists ul'); 190 ul.innerHTML = ''; 191 192 if ('translate_whitelists' in detail) { 193 var pairs = detail['translate_whitelists']; 194 195 Object.keys(pairs).forEach(function(fromLangCode) { 196 var toLangCode = pairs[fromLangCode]; 197 var text = formatLanguageCode(fromLangCode) + ' \u2192 ' + 198 formatLanguageCode(toLangCode); 199 200 var li = createLIWithDismissingButton(text, function() { 201 chrome.send('removePrefItem', 202 ['whitelists', fromLangCode, toLangCode]); 203 }); 204 ul.appendChild(li); 205 }); 206 } 207 208 var p = document.querySelector('#prefs-dump p'); 209 var content = JSON.stringify(detail, null, 2); 210 p.textContent = content; 211 } 212 213 /** 214 * Handles the message of 'supportedLanguagesUpdated' from the browser. 215 * 216 * @param {Object} details the object which represents the supported 217 * languages by the Translate server. 218 */ 219 function onSupportedLanguagesUpdated(details) { 220 var span = 221 $('prefs-supported-languages-last-updated').querySelector('span'); 222 span.textContent = formatDate(new Date(details['last_updated'])); 223 224 var ul = $('prefs-supported-languages-languages'); 225 ul.innerHTML = ''; 226 var languages = details['languages']; 227 for (var i = 0; i < languages.length; i++) { 228 var language = languages[i]; 229 var li = document.createElement('li'); 230 231 var text = formatLanguageCode(language); 232 if (details['alpha_languages'].indexOf(language) != -1) 233 text += ' - alpha'; 234 li.innerText = text; 235 236 ul.appendChild(li); 237 } 238 } 239 240 /** 241 * Addes '0's to |number| as a string. |width| is length of the string 242 * including '0's. 243 * 244 * @param {string} number The number to be converted into a string. 245 * @param {number} width The width of the returned string. 246 * @return {string} The formatted string. 247 */ 248 function padWithZeros(number, width) { 249 var numberStr = number.toString(); 250 var restWidth = width - numberStr.length; 251 if (restWidth <= 0) 252 return numberStr; 253 254 return Array(restWidth + 1).join('0') + numberStr; 255 } 256 257 /** 258 * Formats |date| as a Date object into a string. The format is like 259 * '2006-01-02 15:04:05'. 260 * 261 * @param {Date} date Date to be formatted. 262 * @return {string} The formatted string. 263 */ 264 function formatDate(date) { 265 var year = date.getFullYear(); 266 var month = date.getMonth() + 1; 267 var day = date.getDay(); 268 var hour = date.getHours(); 269 var minute = date.getMinutes(); 270 var second = date.getSeconds(); 271 272 var yearStr = padWithZeros(year, 4); 273 var monthStr = padWithZeros(month, 2); 274 var dayStr = padWithZeros(day, 2); 275 var hourStr = padWithZeros(hour, 2); 276 var minuteStr = padWithZeros(minute, 2); 277 var secondStr = padWithZeros(second, 2); 278 279 var str = yearStr + '-' + monthStr + '-' + dayStr + ' ' + 280 hourStr + ':' + minuteStr + ':' + secondStr; 281 282 return str; 283 } 284 285 /** 286 * Appends a new TD element to the specified element. 287 * 288 * @param {string} parent The element to which a new TD element is appended. 289 * @param {string} content The text content of the element. 290 * @param {string} className The class name of the element. 291 */ 292 function appendTD(parent, content, className) { 293 var td = document.createElement('td'); 294 td.textContent = content; 295 td.className = className; 296 parent.appendChild(td); 297 } 298 299 /** 300 * Handles the message of 'languageDetectionInfoAdded' from the 301 * browser. 302 * 303 * @param {Object} detail The object which represents the logs. 304 */ 305 function onLanguageDetectionInfoAdded(detail) { 306 cr.translateInternals.detectionLogs().push(detail); 307 308 var tr = document.createElement('tr'); 309 310 appendTD(tr, formatDate(new Date(detail['time'])), 'detection-logs-time'); 311 appendTD(tr, detail['url'], 'detection-logs-url'); 312 appendTD(tr, formatLanguageCode(detail['content_language']), 313 'detection-logs-content-language'); 314 appendTD(tr, formatLanguageCode(detail['cld_language']), 315 'detection-logs-cld-language'); 316 appendTD(tr, detail['is_cld_reliable'], 'detection-logs-is-cld-reliable'); 317 appendTD(tr, formatLanguageCode(detail['html_root_language']), 318 'detection-logs-html-root-language'); 319 appendTD(tr, formatLanguageCode(detail['adopted_language']), 320 'detection-logs-adopted-language'); 321 appendTD(tr, formatLanguageCode(detail['content']), 322 'detection-logs-content'); 323 324 // TD (and TR) can't use the CSS property 'max-height', so DIV 325 // in the content is needed. 326 var contentTD = tr.querySelector('.detection-logs-content'); 327 var div = document.createElement('div'); 328 div.textContent = contentTD.textContent; 329 contentTD.textContent = ''; 330 contentTD.appendChild(div); 331 332 var tabpanel = $('tabpanel-detection-logs'); 333 var tbody = tabpanel.getElementsByTagName('tbody')[0]; 334 tbody.appendChild(tr); 335 } 336 337 /** 338 * Handles the message of 'translateErrorDetailsAdded' from the 339 * browser. 340 * 341 * @param {Object} details The object which represents the logs. 342 */ 343 function onTranslateErrorDetailsAdded(details) { 344 var tr = document.createElement('tr'); 345 346 appendTD(tr, formatDate(new Date(details['time'])), 'error-logs-time'); 347 appendTD(tr, details['url'], 'error-logs-url'); 348 appendTD( 349 tr, 350 details['error'] + ': ' + formatTranslateErrorsType(details['error']), 351 'error-logs-error'); 352 353 var tabpanel = $('tabpanel-error-logs'); 354 var tbody = tabpanel.getElementsByTagName('tbody')[0]; 355 tbody.appendChild(tr); 356 } 357 358 /** 359 * Handles the message of 'translateEventDetailsAdded' from the browser. 360 * 361 * @param {Object} details The object which contains event information. 362 */ 363 function onTranslateEventDetailsAdded(details) { 364 var tr = document.createElement('tr'); 365 appendTD(tr, formatDate(new Date(details['time'])), 'event-logs-time'); 366 appendTD(tr, details['filename'] + ': ' + details['line'], 367 'event-logs-place'); 368 appendTD(tr, details['message'], 'event-logs-message'); 369 370 var tbody = $('event-logs').getElementsByTagName('tbody')[0]; 371 tbody.appendChild(tr); 372 } 373 374 /** 375 * The callback entry point from the browser. This function will be 376 * called by the browser. 377 * 378 * @param {string} message The name of the sent message. 379 * @param {Object} details The argument of the sent message. 380 */ 381 function messageHandler(message, details) { 382 switch (message) { 383 case 'languageDetectionInfoAdded': 384 onLanguageDetectionInfoAdded(details); 385 break; 386 case 'prefsUpdated': 387 onPrefsUpdated(details); 388 break; 389 case 'supportedLanguagesUpdated': 390 onSupportedLanguagesUpdated(details); 391 break; 392 case 'translateErrorDetailsAdded': 393 onTranslateErrorDetailsAdded(details); 394 break; 395 case 'translateEventDetailsAdded': 396 onTranslateEventDetailsAdded(details); 397 break; 398 default: 399 console.error('Unknown message:', message); 400 break; 401 } 402 } 403 404 /** 405 * The callback of button#detetion-logs-dump. 406 */ 407 function onDetectionLogsDump() { 408 var data = JSON.stringify(cr.translateInternals.detectionLogs()); 409 var blob = new Blob([data], {'type': 'text/json'}); 410 var url = webkitURL.createObjectURL(blob); 411 var filename = 'translate_internals_detect_logs_dump.json'; 412 413 var a = document.createElement('a'); 414 a.setAttribute('href', url); 415 a.setAttribute('download', filename); 416 417 var event = document.createEvent('MouseEvent'); 418 event.initMouseEvent('click', true, true, window, 0, 419 0, 0, 0, 0, 0, 0, 0, 0, 0, null); 420 a.dispatchEvent(event); 421 } 422 423 return { 424 detectionLogs: detectionLogs, 425 initialize: initialize, 426 messageHandler: messageHandler, 427 }; 428 }); 429 430 /** 431 * The entry point of the UI. 432 */ 433 function main() { 434 cr.doc.addEventListener('DOMContentLoaded', 435 cr.translateInternals.initialize); 436 } 437 438 main(); 439 })(); 440