Home | History | Annotate | Download | only in translate_internals
      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