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