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