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 var localStrings; 6 7 // Contents of lines that act as delimiters for multi-line values. 8 var DELIM_START = '---------- START ----------'; 9 var DELIM_END = '---------- END ----------'; 10 11 // Limit file size to 10 MiB to prevent hanging on accidental upload. 12 var MAX_FILE_SIZE = 10485760; 13 14 function getValueDivForButton(button) { 15 return $(button.id.substr(0, button.id.length - 4)); 16 } 17 18 function getButtonForValueDiv(valueDiv) { 19 return $(valueDiv.id + '-btn'); 20 } 21 22 function handleDragOver(e) { 23 e.dataTransfer.dropEffect = 'copy'; 24 e.preventDefault(); 25 } 26 27 function handleDrop(e) { 28 var file = e.dataTransfer.files[0]; 29 if (file) { 30 e.preventDefault(); 31 importLog(file); 32 } 33 } 34 35 function showError(fileName) { 36 $('status').textContent = localStrings.getStringF('parseError', fileName); 37 } 38 39 /** 40 * Toggles whether an item is collapsed or expanded. 41 */ 42 function changeCollapsedStatus() { 43 var valueDiv = getValueDivForButton(this); 44 if (valueDiv.parentNode.className == 'number-collapsed') { 45 valueDiv.parentNode.className = 'number-expanded'; 46 this.textContent = localStrings.getString('collapseBtn'); 47 } else { 48 valueDiv.parentNode.className = 'number-collapsed'; 49 this.textContent = localStrings.getString('expandBtn'); 50 } 51 } 52 53 /** 54 * Collapses all log items. 55 */ 56 function collapseAll() { 57 var valueDivs = document.getElementsByClassName('stat-value'); 58 for (var i = 0; i < valueDivs.length; i++) { 59 var button = getButtonForValueDiv(valueDivs[i]); 60 if (button && button.className != 'button-hidden') { 61 button.textContent = localStrings.getString('expandBtn'); 62 valueDivs[i].parentNode.className = 'number-collapsed'; 63 } 64 } 65 } 66 67 /** 68 * Expands all log items. 69 */ 70 function expandAll() { 71 var valueDivs = document.getElementsByClassName('stat-value'); 72 for (var i = 0; i < valueDivs.length; i++) { 73 var button = getButtonForValueDiv(valueDivs[i]); 74 if (button && button.className != 'button-hidden') { 75 button.textContent = localStrings.getString('collapseBtn'); 76 valueDivs[i].parentNode.className = 'number-expanded'; 77 } 78 } 79 } 80 81 /** 82 * Collapse only those log items with multi-line values. 83 */ 84 function collapseMultiLineStrings() { 85 var valueDivs = document.getElementsByClassName('stat-value'); 86 var nameDivs = document.getElementsByClassName('stat-name'); 87 for (var i = 0; i < valueDivs.length; i++) { 88 var button = getButtonForValueDiv(valueDivs[i]); 89 button.onclick = changeCollapsedStatus; 90 if (valueDivs[i].scrollHeight > (nameDivs[i].scrollHeight * 2)) { 91 button.className = ''; 92 button.textContent = localStrings.getString('expandBtn'); 93 valueDivs[i].parentNode.className = 'number-collapsed'; 94 } else { 95 button.className = 'button-hidden'; 96 valueDivs[i].parentNode.className = 'number'; 97 } 98 } 99 } 100 101 /** 102 * Read in a log asynchronously, calling parseSystemLog if successful. 103 * @param {File} file The file to read. 104 */ 105 function importLog(file) { 106 if (file && file.size <= MAX_FILE_SIZE) { 107 var reader = new FileReader(); 108 reader.onload = function() { 109 if (parseSystemLog(this.result)) { 110 // Reset table title and status 111 $('tableTitle').textContent = 112 localStrings.getStringF('logFileTableTitle', file.name); 113 $('status').textContent = ''; 114 } else { 115 showError(file.name); 116 } 117 }; 118 reader.readAsText(file); 119 } else if (file) { 120 showError(file.name); 121 } 122 } 123 124 /** 125 * Convert text-based log into list of name-value pairs. 126 * @param {string} text The raw text of a log. 127 * @return {boolean} True if the log was parsed successfully. 128 */ 129 function parseSystemLog(text) { 130 var details = []; 131 var lines = text.split('\n'); 132 for (var i = 0, len = lines.length; i < len; i++) { 133 // Skip empty lines. 134 if (!lines[i]) 135 continue; 136 137 var delimiter = lines[i].indexOf('='); 138 if (delimiter <= 0) { 139 if (i == lines.length - 1) 140 break; 141 // If '=' is missing here, format is wrong. 142 return false; 143 } 144 145 var name = lines[i].substring(0, delimiter); 146 var value = ''; 147 // Set value if non-empty 148 if (lines[i].length > delimiter + 1) 149 value = lines[i].substring(delimiter + 1); 150 151 // Delimiters are based on kMultilineIndicatorString, kMultilineStartString, 152 // and kMultilineEndString in components/feedback/feedback_data.cc. 153 // If these change, we should check for both the old and new versions. 154 if (value == '<multiline>') { 155 // Skip start delimiter. 156 if (i == len - 1 || 157 lines[++i].indexOf(DELIM_START) == -1) 158 return false; 159 160 ++i; 161 value = ''; 162 // Append lines between start and end delimiters. 163 while (i < len && lines[i] != DELIM_END) 164 value += lines[i++] + '\n'; 165 166 // Remove trailing newline. 167 if (value) 168 value = value.substr(0, value.length - 1); 169 } 170 details.push({'statName': name, 'statValue': value}); 171 } 172 173 templateData['details'] = details; 174 i18nTemplate.process(document, templateData); 175 jstProcess(new JsEvalContext(templateData), $('t')); 176 177 collapseMultiLineStrings(); 178 return true; 179 } 180 181 document.addEventListener('DOMContentLoaded', function() { 182 localStrings = new LocalStrings(); 183 184 $('collapseAll').onclick = collapseAll; 185 $('expandAll').onclick = expandAll; 186 187 var tp = $('t'); 188 tp.addEventListener('dragover', handleDragOver, false); 189 tp.addEventListener('drop', handleDrop, false); 190 191 collapseMultiLineStrings(); 192 }); 193