- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / net_internals / cros_log_marker.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  * This class stores the filter queries as history and highlight the log text
6  * to increase the readability of the log
7  *
8  *   - Enable / Disable highlights
9  *   - Highlights text with multiple colors
10  *   - Resolve hghlight conficts (A text highlighted by multiple colors) so that
11  *     the latest added highlight always has highest priority to display.
12  *
13  */
14 var CrosLogMarker = (function() {
15   'use strict';
16
17   // Special classes (defined in log_visualizer_view.css)
18   var LOG_MARKER_HIGHLIGHT_CLASS = 'cros-log-visualizer-marker-highlight';
19   var LOG_MARKER_CONTAINER_ID = 'cros-log-visualizer-marker-container';
20   var LOG_MARKER_HISTORY_ENTRY_CLASS =
21       'cros-log-visualizer-marker-history-entry';
22   var LOG_MARKER_HISTORY_COLOR_TAG_CLASS =
23           'cros-log-visualizer-marker-history-color-tag';
24
25   /**
26    * Colors used for highlighting. (Current we support 6 colors)
27    * TODO(shinfan): Add more supoorted colors.
28    */
29   var COLOR_USAGE_SET = {
30     'Crimson': false,
31     'DeepSkyBlue': false,
32     'DarkSeaGreen': false,
33     'GoldenRod': false,
34     'IndianRed': false,
35     'Orange': false
36   };
37   var COLOR_NUMBER = Object.keys(COLOR_USAGE_SET).length;
38
39
40   /**
41    * CrosHighlightTag represents a single highlight tag in text.
42    */
43   var CrosHighlightTag = (function() {
44     /**
45      * @constructor
46      */
47     function CrosHighlightTag(color, field, range, priority) {
48       this.color = color;
49       this.field = field;
50       this.range = range;
51       this.priority = priority;
52       this.enabled = true;
53     }
54
55     return CrosHighlightTag;
56   })();
57
58   /**
59    * @constructor
60    * @param {CrosLogVisualizerView} logVisualizerView A reference to
61    *     CrosLogVisualizerView.
62    */
63   function CrosLogMarker(logVisualizerView) {
64     this.container = $(LOG_MARKER_CONTAINER_ID);
65     // Stores highlight objects for each entry.
66     this.entryHighlights = [];
67     // Stores all the filter queries.
68     this.markHistory = {};
69     // Object references from CrosLogVisualizerView.
70     this.logEntries = logVisualizerView.logEntries;
71     this.logVisualizerView = logVisualizerView;
72     // Counts how many highlights are created.
73     this.markCount = 0;
74     for (var i = 0; i < this.logEntries.length; i++) {
75       this.entryHighlights.push([]);
76     }
77   }
78
79   CrosLogMarker.prototype = {
80     /**
81      * Saves the query to the mark history and highlights the text
82      * based on the query.
83      */
84     addMarkHistory: function(query) {
85       // Increases the counter
86       this.markCount += 1;
87
88       // Find an avaiable color.
89       var color = this.pickColor();
90       if (!color) {
91         // If all colors are occupied.
92         alert('You can only add at most ' + COLOR_NUMBER + 'markers.');
93         return;
94       }
95
96       // Updates HTML elements.
97       var historyEntry = addNode(this.container, 'div');
98       historyEntry.className = LOG_MARKER_HISTORY_ENTRY_CLASS;
99
100       // A color tag that indicats the color used.
101       var colorTag = addNode(historyEntry, 'div');
102       colorTag.className = LOG_MARKER_HISTORY_COLOR_TAG_CLASS;
103       colorTag.style.background = color;
104
105       // Displays the query text.
106       var queryText = addNodeWithText(historyEntry, 'p', query);
107       queryText.style.color = color;
108
109       // Adds a button to remove the marker.
110       var removeBtn = addNodeWithText(historyEntry, 'a', 'Remove');
111       removeBtn.addEventListener(
112           'click', this.onRemoveBtnClicked_.bind(this, historyEntry, color));
113
114       // A checkbox that lets user enable and disable the marker.
115       var enableCheckbox = addNode(historyEntry, 'input');
116       enableCheckbox.type = 'checkbox';
117       enableCheckbox.checked = true;
118       enableCheckbox.color = color;
119       enableCheckbox.addEventListener('change',
120           this.onEnableCheckboxChange_.bind(this, enableCheckbox), false);
121
122       // Searches log text for matched patterns and highlights them.
123       this.patternMatch(query, color);
124     },
125
126     /**
127      * Search the text for matched strings
128      */
129     patternMatch: function(query, color) {
130       var pattern = new RegExp(query, 'i');
131       for (var i = 0; i < this.logEntries.length; i++) {
132         var entry = this.logEntries[i];
133         // Search description of each log entry
134         // TODO(shinfan): Add more search fields
135         var positions = this.findPositions(
136             pattern, entry.description);
137         for (var j = 0; j < positions.length; j++) {
138             var pos = positions[j];
139             this.mark(entry, pos, 'description', color);
140         }
141         this.sortHighlightsByStartPosition_(this.entryHighlights[i]);
142       }
143     },
144
145     /**
146      * Highlights the text.
147      * @param {CrosLogEntry} entry The log entry to be highlighted
148      * @param {int|Array} position [start, end]
149      * @param {string} field The field of entry to be highlighted
150      * @param {string} color color used for highlighting
151      */
152     mark: function(entry, position, field, color) {
153       // Creates the highlight object
154       var tag = new CrosHighlightTag(color, field, position, this.markCount);
155       // Add the highlight into entryHighlights
156       this.entryHighlights[entry.rowNum].push(tag);
157     },
158
159     /**
160      * Find the highlight objects that covers the given position
161      * @param {CrosHighlightTag|Array} highlights highlights of a log entry
162      * @param {int} position The target index
163      * @param {string} field The target field
164      * @return {CrosHighlightTag|Array} Highlights that cover the position
165      */
166     getHighlight: function(highlights, index, field) {
167       var res = [];
168       for (var j = 0; j < highlights.length; j++) {
169         var highlight = highlights[j];
170         if (highlight.range[0] <= index &&
171             highlight.range[1] > index &&
172             highlight.field == field &&
173             highlight.enabled) {
174           res.push(highlight);
175         }
176       }
177       /**
178        * Sorts the result by priority so that the highlight with
179        * highest priority comes first.
180        */
181       this.sortHighlightsByPriority_(res);
182       return res;
183     },
184
185     /**
186      * This function highlights the entry by going through the text from left
187      * to right and searching for "key" positions.
188      * A "key" position is a position that one (or more) highlight
189      * starts or ends. We only care about "key" positions because this is where
190      * the text highlight status changes.
191      * At each key position, the function decides if the text between this
192      * position and previous position need to be highlighted and resolves
193      * highlight conflicts.
194      *
195      * @param {CrosLogEntry} entry The entry going to be highlighted.
196      * @param {string} field The specified field of the entry.
197      * @param {DOMElement} parent Parent node.
198      */
199     getHighlightedEntry: function(entry, field, parent) {
200       var rowNum = entry.rowNum;
201       // Get the original text content of the entry (without any highlights).
202       var content = this.logEntries[rowNum][field];
203       var index = 0;
204       while (index < content.length) {
205         var nextIndex = this.getNextIndex(
206             this.entryHighlights[rowNum], index, field, content);
207         // Searches for highlights that have the position in range.
208         var highlights = this.getHighlight(
209             this.entryHighlights[rowNum], index, field);
210         var text = content.substr(index, nextIndex - index);
211         if (highlights.length > 0) {
212           // Always picks the highlight with highest priority.
213           this.addSpan(text, highlights[0].color, parent);
214         } else {
215           addNodeWithText(parent, 'span', text);
216         }
217         index = nextIndex;
218       }
219     },
220
221     /**
222      * A helper function that is used by this.getHightlightedEntry
223      * It returns the first index where a highlight begins or ends from
224      * the given index.
225      * @param {CrosHighlightTag|Array} highlights An array of highlights
226      *     of a log entry.
227      * @param {int} index The start position.
228      * @param {string} field The specified field of entry.
229      *     Other fields are ignored.
230      * @param {string} content The text content of the log entry.
231      * @return {int} The first index where a highlight begins or ends.
232      */
233     getNextIndex: function(highlights, index, field, content) {
234       var minGap = Infinity;
235       var res = -1;
236       for (var i = 0; i < highlights.length; i++) {
237         if (highlights[i].field != field || !highlights[i].enabled)
238           continue;
239         // Distance between current index and the start index of highlight.
240         var gap1 = highlights[i].range[0] - index;
241         // Distance between current index and the end index of highlight.
242         var gap2 = highlights[i].range[1] - index;
243         if (gap1 > 0 && gap1 < minGap) {
244           minGap = gap1;
245           res = highlights[i].range[0];
246         }
247         if (gap2 > 0 && gap2 < minGap) {
248           minGap = gap2;
249           res = highlights[i].range[1];
250         }
251       }
252       // Returns |res| if found. Otherwise returns the end position of the text.
253       return res > 0 ? res : content.length;
254     },
255
256     /**
257      * A helper function that is used by this.getHightlightedEntry.
258      * It adds the HTML label to the text.
259      */
260     addSpan: function(text, color, parent) {
261       var span = addNodeWithText(parent, 'span', text);
262       span.style.color = color;
263       span.className = LOG_MARKER_HIGHLIGHT_CLASS;
264     },
265
266     /**
267      * A helper function that is used by this.getHightlightedEntry.
268      * It adds the HTML label to the text.
269      */
270     pickColor: function() {
271       for (var color in COLOR_USAGE_SET) {
272         if (!COLOR_USAGE_SET[color]) {
273           COLOR_USAGE_SET[color] = true;
274           return color;
275         }
276       }
277       return false;
278     },
279
280     /**
281      * A event handler that enables and disables the corresponding marker.
282      * @private
283      */
284     onEnableCheckboxChange_: function(checkbox) {
285       for (var i = 0; i < this.entryHighlights.length; i++) {
286         for (var j = 0; j < this.entryHighlights[i].length; j++) {
287           if (this.entryHighlights[i][j].color == checkbox.color) {
288             this.entryHighlights[i][j].enabled = checkbox.checked;
289            }
290         }
291       }
292       this.refreshLogTable();
293     },
294
295     /**
296      * A event handlier that removes the marker from history.
297      * @private
298      */
299     onRemoveBtnClicked_: function(entry, color) {
300       entry.parentNode.removeChild(entry);
301       COLOR_USAGE_SET[color] = false;
302       for (var i = 0; i < this.entryHighlights.length; i++) {
303         var highlights = this.entryHighlights[i];
304         while (true) {
305           var index = this.findHighlightByColor_(highlights, color);
306           if (index == -1)
307             break;
308           highlights.splice(index, 1);
309         }
310       }
311       this.refreshLogTable();
312     },
313
314     /**
315      * A helper function that returns the index of first highlight that
316      * has the target color. Otherwise returns -1.
317      * @private
318      */
319     findHighlightByColor_: function(highlights, color) {
320       for (var i = 0; i < highlights.length; i++) {
321         if (highlights[i].color == color)
322           return i;
323       }
324       return -1;
325     },
326
327     /**
328      * Refresh the log table in the CrosLogVisualizerView.
329      */
330     refreshLogTable: function() {
331       this.logVisualizerView.populateTable();
332       this.logVisualizerView.filterLog();
333     },
334
335     /**
336      * A pattern can appear multiple times in a string.
337      * Returns positions of all the appearance.
338      */
339     findPositions: function(pattern, str) {
340       var res = [];
341       str = str.toLowerCase();
342       var match = str.match(pattern);
343       if (!match)
344         return res;
345       for (var i = 0; i < match.length; i++) {
346         var index = 0;
347         while (true) {
348           var start = str.indexOf(match[i].toLowerCase(), index);
349           if (start == -1)
350             break;
351           var end = start + match[i].length;
352           res.push([start, end]);
353           index = end + 1;
354         }
355       }
356       return res;
357     },
358
359     /**
360      * A helper function used in sorting highlights by start position.
361      * @param {HighlightTag} h1, h2 Two highlight tags in the array.
362      * @private
363      */
364     compareStartPosition_: function(h1, h2) {
365       return h1.range[0] - h2.range[0];
366     },
367
368     /**
369      * A helper function used in sorting highlights by priority.
370      * @param {HighlightTag} h1, h2 Two highlight tags in the array.
371      * @private
372      */
373     comparePriority_: function(h1, h2) {
374       return h2.priority - h1.priority;
375     },
376
377     /**
378      * A helper function that sorts the highlights array by start position.
379      * @private
380      */
381     sortHighlightsByStartPosition_: function(highlights) {
382       highlights.sort(this.compareStartPosition_);
383     },
384
385     /**
386      * A helper function that sorts the highlights array by priority.
387      * @private
388      */
389     sortHighlightsByPriority_: function(highlights) {
390       highlights.sort(this.comparePriority_);
391     }
392   };
393
394   return CrosLogMarker;
395 })();