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