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