- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / net_internals / cros_log_visualizer_view.js
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 /**
6  * This view displays the log messages from various resources in an
7  * interactive log visualizer
8  *
9  *   - Filter checkboxes
10  *   - Filter text inputs
11  *   - Display the log by different sections: time|level|process|description
12  *
13  */
14 var CrosLogVisualizerView = (function() {
15   'use strict';
16
17   // Inherits from DivView.
18   var superClass = DivView;
19
20   // Special classes (defined in log_visualizer_view.css)
21   var LOG_CONTAINER_CLASSNAME = 'cros-log-visualizer-container';
22   var LOG_FILTER_PNAME_BLOCK_CLASSNAME =
23       'cros-log-visualizer-filter-pname-block';
24   var LOG_CELL_HEADER_CLASSNAME = 'cros-log-visualizer-td-head';
25   var LOG_CELL_TIME_CLASSNAME = 'cros-log-visualizer-td-time';
26   var LOG_CELL_PNAME_CLASSNAME = 'cros-log-visualizer-td-pname';
27   var LOG_CELL_PID_CLASSNAME = 'cros-log-visualizer-td-pid';
28   var LOG_CELL_DESCRIPTION_CLASSNAME = 'cros-log-visualizer-td-description';
29   var LOG_CELL_LEVEL_CLASSNAME = 'cros-log-visualizer-td-level';
30   var LOG_CELL_LEVEL_CLASSNAME_LIST = {
31     'Error': 'cros-log-visualizer-td-level-error',
32     'Warning': 'cros-log-visualizer-td-level-warning',
33     'Info': 'cros-log-visualizer-td-level-info',
34     'Unknown': 'cros-log-visualizer-td-level-unknown'
35   };
36
37   /**
38    * @constructor
39    */
40   function CrosLogVisualizerView() {
41     assertFirstConstructorCall(CrosLogVisualizerView);
42
43     // Call superclass's constructor.
44     superClass.call(this, CrosLogVisualizerView.MAIN_BOX_ID);
45
46     // Stores log entry objects
47     this.logEntries = [];
48     // Stores current search query
49     this.currentQuery = '';
50     // Stores raw text data of log
51     this.logData = '';
52     // Stores all the unique process names
53     this.pNames = [];
54     // References to special HTML elements in log_visualizer_view.html
55     this.pNameCheckboxes = {};
56     this.levelCheckboxes = {};
57     this.tableEntries = [];
58
59     this.initialize();
60   }
61
62   CrosLogVisualizerView.TAB_ID = 'tab-handle-cros-log-visualizer';
63   CrosLogVisualizerView.TAB_NAME = 'Log Visualizer';
64   CrosLogVisualizerView.TAB_HASH = '#visualizer';
65
66   // IDs for special HTML elements in log_visualizer_view.html
67   CrosLogVisualizerView.MAIN_BOX_ID = 'cros-log-visualizer-tab-content';
68   CrosLogVisualizerView.LOG_TABLE_ID = 'cros-log-visualizer-log-table';
69   CrosLogVisualizerView.LOG_FILTER_PNAME_ID =
70       'cros-log-visualizer-filter-pname';
71   CrosLogVisualizerView.LOG_SEARCH_INPUT_ID =
72       'cros-log-visualizer-search-input';
73   CrosLogVisualizerView.LOG_SEARCH_SAVE_BTN_ID = 'cros-log-visualizer-save-btn';
74   CrosLogVisualizerView.LOG_VISUALIZER_CONTAINER_ID =
75       'cros-log-visualizer-visualizer-container';
76
77   cr.addSingletonGetter(CrosLogVisualizerView);
78
79   /**
80    * Contains types of logs we are interested in
81    */
82   var LOGS_LIST = {
83     'NETWORK_LOG': 1,
84     'SYSTEM_LOG': 2
85   };
86
87   /**
88    * Contains headers of the log table
89    */
90   var TABLE_HEADERS_LIST = ['Level', 'Time', 'Process', 'PID', 'Description'];
91
92   CrosLogVisualizerView.prototype = {
93     // Inherit the superclass's methods.
94     __proto__: superClass.prototype,
95
96     /**
97      * Called during the initialization of the View. Adds the system log
98      * listener into Browser_Bridge so that the system log can be retrieved.
99      */
100     initialize: function() {
101       g_browser.addSystemLogObserver(this);
102       $(CrosLogVisualizerView.LOG_SEARCH_INPUT_ID).addEventListener('keyup',
103           this.onSearchQueryChange_.bind(this));
104       $(CrosLogVisualizerView.LOG_SEARCH_SAVE_BTN_ID).addEventListener(
105           'click', this.onSaveBtnClicked_.bind(this));
106     },
107
108     /**
109      * Called when the save button is clicked. Saves the current filter query
110      * to the mark history. And highlights the matched text with colors.
111      */
112     onSaveBtnClicked_: function() {
113       this.marker.addMarkHistory(this.currentQuery);
114       // Clears the filter query
115       $(CrosLogVisualizerView.LOG_SEARCH_INPUT_ID).value = '';
116       this.currentQuery = '';
117       // Refresh the table
118       this.populateTable();
119       this.filterLog();
120     },
121
122     onSearchQueryChange_: function() {
123       var inputField = $(CrosLogVisualizerView.LOG_SEARCH_INPUT_ID);
124       this.currentQuery = inputField.value;
125       this.filterLog();
126     },
127
128     /**
129      * Creates the log table where each row represents a entry of log.
130      * This function is called if and only if the log is received from system
131      * level.
132      */
133     populateTable: function() {
134       var logTable = $(CrosLogVisualizerView.LOG_TABLE_ID);
135       logTable.innerHTML = '';
136       this.tableEntries.length = 0;
137       // Create entries
138       for (var i = 0; i < this.logEntries.length; i++) {
139         this.logEntries[i].rowNum = i;
140         var row = this.createTableRow(this.logEntries[i]);
141         logTable.appendChild(row);
142       }
143     },
144
145     /**
146      * Creates the single row of the table where each row is a representation
147      * of the logEntry object.
148      */
149     createTableRow: function(entry) {
150       var row = document.createElement('tr');
151       for (var i = 0; i < 5; i++) {
152         // Creates rows
153         addNode(row, 'td');
154       }
155       var cells = row.childNodes;
156       // Level cell
157       cells[0].className = LOG_CELL_LEVEL_CLASSNAME;
158       var levelTag = addNodeWithText(cells[0], 'p', entry.level);
159       levelTag.className = LOG_CELL_LEVEL_CLASSNAME_LIST[entry.level];
160
161       // Time cell
162       cells[1].className = LOG_CELL_TIME_CLASSNAME;
163       cells[1].textContent = entry.getTime();
164
165       // Process name cell
166       cells[2].className = LOG_CELL_PNAME_CLASSNAME;
167       this.marker.getHighlightedEntry(entry, 'processName', cells[2]);
168
169       // Process ID cell
170       cells[3].className = LOG_CELL_PID_CLASSNAME;
171       this.marker.getHighlightedEntry(entry, 'processID', cells[3]);
172
173       // Description cell
174       cells[4].className = LOG_CELL_DESCRIPTION_CLASSNAME;
175       this.marker.getHighlightedEntry(entry, 'description', cells[4]);
176
177       // Add the row into this.tableEntries for future reference
178       this.tableEntries.push(row);
179       return row;
180     },
181
182     /**
183      * Regenerates the table and filter.
184      */
185     refresh: function() {
186       this.createFilter();
187       this.createLogMaker();
188       this.populateTable();
189       this.createVisualizer();
190     },
191
192     /**
193      * Uses the search query to match the pattern in different fields of entry.
194      */
195     patternMatch: function(entry, pattern) {
196       return entry.processID.match(pattern) ||
197              entry.processName.match(pattern) ||
198              entry.level.match(pattern) ||
199              entry.description.match(pattern);
200     },
201
202     /**
203      * Filters the log to show/hide the rows in the table.
204      * Each logEntry instance has a visibility property. This function
205      * shows or hides the row only based on this property.
206      */
207     filterLog: function() {
208       // Supports regular expression
209       var pattern = new RegExp(this.currentQuery, 'i');
210       for (var i = 0; i < this.logEntries.length; i++) {
211         var entry = this.logEntries[i];
212         // Filters the result by pname and level
213         var pNameCheckbox = this.pNameCheckboxes[entry.processName];
214         var levelCheckbox = this.levelCheckboxes[entry.level];
215         entry.visibility = pNameCheckbox.checked && levelCheckbox.checked &&
216             !this.visualizer.isOutOfBound(entry);
217         if (this.currentQuery) {
218           // If the search query is not empty, filter the result by query
219           entry.visibility = entry.visibility &&
220               this.patternMatch(entry, pattern);
221         }
222         // Changes style of HTML row based on the visibility of logEntry
223         if (entry.visibility) {
224           this.tableEntries[i].style.display = 'table-row';
225         } else {
226           this.tableEntries[i].style.display = 'none';
227         }
228       }
229       this.filterVisualizer();
230     },
231
232     /**
233      * Initializes filter tags and checkboxes. There are two types of filters:
234      * Level and Process. Level filters are static that we have only 4 levels
235      * in total but process filters are dynamically changing based on the log.
236      * The filter layout looks like:
237      *  |-----------------------------------------------------------------|
238      *  |                                                                 |
239      *  |                     Section of process filter                   |
240      *  |                                                                 |
241      *  |-----------------------------------------------------------------|
242      *  |                                                                 |
243      *  |                      Section of level filter                    |
244      *  |                                                                 |
245      *  |-----------------------------------------------------------------|
246      */
247     createFilter: function() {
248       this.createFilterByPName();
249       this.levelCheckboxes = {
250         'Error': $('checkbox-error'),
251         'Warning': $('checkbox-warning'),
252         'Info': $('checkbox-info'),
253         'Unknown': $('checkbox-unknown')
254       };
255
256       for (var level in this.levelCheckboxes) {
257         this.levelCheckboxes[level].addEventListener(
258             'change', this.onFilterChange_.bind(this));
259       }
260     },
261
262     /**
263      * Helper function of createFilter(). Create filter section of
264      * process filters.
265      */
266     createFilterByPName: function() {
267       var filterContainerDiv = $(CrosLogVisualizerView.LOG_FILTER_PNAME_ID);
268       filterContainerDiv.innerHTML = 'Process: ';
269       for (var i = 0; i < this.pNames.length; i++) {
270         var pNameBlock = this.createPNameBlock(this.pNames[i]);
271         filterContainerDiv.appendChild(pNameBlock);
272       }
273     },
274
275     /**
276      * Helper function of createFilterByPName(). Create a single filter block in
277      * the section of process filters.
278      */
279     createPNameBlock: function(pName) {
280       var block = document.createElement('span');
281       block.className = LOG_FILTER_PNAME_BLOCK_CLASSNAME;
282
283       var tag = document.createElement('label');
284       var span = document.createElement('span');
285       span.textContent = pName;
286
287       var checkbox = document.createElement('input');
288       checkbox.type = 'checkbox';
289       checkbox.name = pName;
290       checkbox.value = pName;
291       checkbox.checked = true;
292       checkbox.addEventListener('change', this.onFilterChange_.bind(this));
293       this.pNameCheckboxes[pName] = checkbox;
294
295       tag.appendChild(checkbox);
296       tag.appendChild(span);
297       block.appendChild(tag);
298
299       return block;
300     },
301
302     /**
303      * Click handler for filter checkboxes. Everytime a checkbox is clicked,
304      * the visibility of related logEntries are changed.
305      */
306     onFilterChange_: function() {
307       this.filterLog();
308     },
309
310     /**
311      * Creates a visualizer that visualizes the logs as a timeline graph
312      * during the initialization of the View.
313      */
314     createVisualizer: function() {
315       this.visualizer = new CrosLogVisualizer(this,
316           CrosLogVisualizerView.LOG_VISUALIZER_CONTAINER_ID);
317       this.visualizer.updateEvents(this.logEntries);
318     },
319
320     /**
321      * Sync the visibility of log entries with the visualizer.
322      */
323     filterVisualizer: function() {
324       this.visualizer.updateEvents(this.logEntries);
325     },
326
327     /**
328      * Called during the initialization. It creates the log marker that
329      * highlights log text.
330      */
331     createLogMaker: function() {
332       this.marker = new CrosLogMarker(this);
333     },
334
335     /**
336      * Given a row text line of log, a logEntry instance is initialized and used
337      * for parsing. After the text is parsed, we put the instance into
338      * logEntries which is an array for storing. This function is called when
339      * the data is received from Browser Bridge.
340      */
341     addLogEntry: function(logType, textEntry) {
342       var newEntry = new CrosLogEntry();
343       if (logType == LOGS_LIST.NETWORK_LOG) {
344         newEntry.tokenizeNetworkLog(textEntry);
345       } else {
346         //TODO(shinfan): Add more if cases here
347       }
348       this.logEntries.push(newEntry);
349
350       // Record pname
351       var pName = newEntry.processName;
352       if (this.pNames.indexOf(pName) == -1) {
353         this.pNames.push(pName);
354       }
355     },
356
357     /*
358      * Asynchronous call back function from Browser Bridge.
359      */
360     onSystemLogChanged: function(callback) {
361       if (callback.log == this.logData) return;
362       this.logData = callback.log;
363       // Clear the old array by setting length to zero
364       this.logEntries.length = 0;
365       var entries = callback.log.split('\n');
366       for (var i = 1; i < entries.length; i++) {
367         this.addLogEntry(LOGS_LIST.NETWORK_LOG, entries[i]);
368       }
369       this.refresh();
370     }
371   };
372
373   return CrosLogVisualizerView;
374 })();