summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorshinfan@chromium.org <shinfan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-06-27 09:08:48 +0000
committershinfan@chromium.org <shinfan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-06-27 09:08:48 +0000
commitd60c81a0455c34c6280f9603cba357e840419293 (patch)
treebeb66eacf394754db8db9cca2e5a1ff78d777bac
parent393c77583781f32afbe6ef5357e756219d6771f0 (diff)
downloadchromium_src-d60c81a0455c34c6280f9603cba357e840419293.zip
chromium_src-d60c81a0455c34c6280f9603cba357e840419293.tar.gz
chromium_src-d60c81a0455c34c6280f9603cba357e840419293.tar.bz2
Add a text marker feature to CrosLogAnalyzer
Add a text marker feature to log Analyzer in chrome://net-internals/ to help developers view and highlight log text. - Support multiple colors - Auto resolve mark conflicts - Dynamically enable/disable highlights TEST=manual BUG=246661 Review URL: https://chromiumcodereview.appspot.com/17176007 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@208868 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/resources/net_internals/cros_log_analyzer_view.css51
-rw-r--r--chrome/browser/resources/net_internals/cros_log_analyzer_view.html12
-rw-r--r--chrome/browser/resources/net_internals/cros_log_analyzer_view.js33
-rw-r--r--chrome/browser/resources/net_internals/cros_log_marker.js394
-rw-r--r--chrome/browser/resources/net_internals/index.js1
5 files changed, 484 insertions, 7 deletions
diff --git a/chrome/browser/resources/net_internals/cros_log_analyzer_view.css b/chrome/browser/resources/net_internals/cros_log_analyzer_view.css
index 23e4a73..098190c 100644
--- a/chrome/browser/resources/net_internals/cros_log_analyzer_view.css
+++ b/chrome/browser/resources/net_internals/cros_log_analyzer_view.css
@@ -112,9 +112,10 @@
border-radius: 5px;
font-size: 12px;
margin-bottom: 5px;
+ margin-left: 720px;
margin-top: -40px;
padding: 5px;
- width: 240px;
+ width: 280px;
}
#cros-log-analyzer-search-container input {
@@ -169,6 +170,54 @@
-webkit-animation: fade 1s linear 1;
}
+#cros-log-analyzer-marker-container {
+ background-color: rgb(242, 242, 242);
+ border: 1px solid rgb(211, 211, 211);
+ left: 1030px;
+ min-height: 320px;
+ padding: 10px;
+ position: absolute;
+ top: 63px;
+ width: 200px;
+}
+
+.cros-log-analyzer-marker-history-entry {
+ display: -webkit-flex;
+ height: 20px;
+}
+
+
+.cros-log-analyzer-marker-history-entry * {
+ display: block;
+}
+
+.cros-log-analyzer-marker-history-entry p {
+ font-size: 13px;
+ height: 15px;
+ margin: 0;
+ width: 98px;
+}
+
+.cros-log-analyzer-marker-history-entry a {
+ color: grey;
+ font-size: 11px;
+}
+
+.cros-log-analyzer-marker-history-color-tag {
+ border-radius: 10px;
+ height: 10px;
+ margin: 4px;
+ width: 10px;
+}
+
+.cros-log-analyzer-marker-highlight {
+ font-weight: bold;
+}
+
+#cros-log-analyzer-save-btn {
+ background-color: rgb(211, 211, 211);
+}
+
@-webkit-keyframes fade {
0%
{
diff --git a/chrome/browser/resources/net_internals/cros_log_analyzer_view.html b/chrome/browser/resources/net_internals/cros_log_analyzer_view.html
index 8c9080a..a23543c5 100644
--- a/chrome/browser/resources/net_internals/cros_log_analyzer_view.html
+++ b/chrome/browser/resources/net_internals/cros_log_analyzer_view.html
@@ -3,11 +3,14 @@
<div id="cros-log-analyzer-header">
Network Log
</div>
- <div id='cros-log-analyzer-search-container' class='cros-log-analyzer-container'>
+ <div id='cros-log-analyzer-search-container'
+ class='cros-log-analyzer-container'>
Search:
<input type='text' id='cros-log-analyzer-search-input'>
+ <span id='cros-log-analyzer-save-btn'>Save</span>
</div>
- <table id="cros-log-analyzer-log-header-table" class="cros-log-analyzer-container">
+ <table id="cros-log-analyzer-log-header-table"
+ class="cros-log-analyzer-container">
<tr>
<td class="cros-log-analyzer-td-level">Level</td>
<td class="cros-log-analyzer-td-time">Time</td>
@@ -20,7 +23,10 @@
<table id="cros-log-analyzer-log-table">
</table>
</div>
- <div id='cros-log-analyzer-filter-container' class='cros-log-analyzer-container'>
+ <div id='cros-log-analyzer-marker-container'>
+ </div>
+ <div id='cros-log-analyzer-filter-container'
+ class='cros-log-analyzer-container'>
<div id='cros-log-analyzer-filter-pname'>
</div>
<div id='cros-log-analyzer-filter-level'>
diff --git a/chrome/browser/resources/net_internals/cros_log_analyzer_view.js b/chrome/browser/resources/net_internals/cros_log_analyzer_view.js
index 4efadce..fa6927a 100644
--- a/chrome/browser/resources/net_internals/cros_log_analyzer_view.js
+++ b/chrome/browser/resources/net_internals/cros_log_analyzer_view.js
@@ -67,6 +67,7 @@ var CrosLogAnalyzerView = (function() {
CrosLogAnalyzerView.LOG_TABLE_ID = 'cros-log-analyzer-log-table';
CrosLogAnalyzerView.LOG_FILTER_PNAME_ID = 'cros-log-analyzer-filter-pname';
CrosLogAnalyzerView.LOG_SEARCH_INPUT_ID = 'cros-log-analyzer-search-input';
+ CrosLogAnalyzerView.LOG_SEARCH_SAVE_BTN_ID = 'cros-log-analyzer-save-btn';
CrosLogAnalyzerView.LOG_VISUALIZER_CONTAINER_ID =
'cros-log-analyzer-visualizer-container';
@@ -97,7 +98,22 @@ var CrosLogAnalyzerView = (function() {
g_browser.addSystemLogObserver(this);
$(CrosLogAnalyzerView.LOG_SEARCH_INPUT_ID).addEventListener('keyup',
this.onSearchQueryChange_.bind(this));
+ $(CrosLogAnalyzerView.LOG_SEARCH_SAVE_BTN_ID).addEventListener(
+ 'click', this.onSaveBtnClicked_.bind(this));
+ },
+ /**
+ * Called when the save button is clicked. Saves the current filter query
+ * to the mark history. And highlights the matched text with colors.
+ */
+ onSaveBtnClicked_: function() {
+ this.marker.addMarkHistory(this.currentQuery);
+ // Clears the filter query
+ $(CrosLogAnalyzerView.LOG_SEARCH_INPUT_ID).value = '';
+ this.currentQuery = '';
+ // Refresh the table
+ this.populateTable();
+ this.filterLog();
},
onSearchQueryChange_: function() {
@@ -114,8 +130,10 @@ var CrosLogAnalyzerView = (function() {
populateTable: function() {
var logTable = $(CrosLogAnalyzerView.LOG_TABLE_ID);
logTable.innerHTML = '';
+ this.tableEntries.length = 0;
// Create entries
for (var i = 0; i < this.logEntries.length; i++) {
+ this.logEntries[i].rowNum = i;
var row = this.createTableRow(this.logEntries[i]);
logTable.appendChild(row);
}
@@ -143,15 +161,15 @@ var CrosLogAnalyzerView = (function() {
// Process name cell
cells[2].className = LOG_CELL_PNAME_CLASSNAME;
- cells[2].textContent = entry.processName;
+ this.marker.getHighlightedEntry(entry, 'processName', cells[2]);
// Process ID cell
cells[3].className = LOG_CELL_PID_CLASSNAME;
- cells[3].textContent = entry.processID;
+ this.marker.getHighlightedEntry(entry, 'processID', cells[3]);
// Description cell
cells[4].className = LOG_CELL_DESCRIPTION_CLASSNAME;
- cells[4].textContent = entry.description;
+ this.marker.getHighlightedEntry(entry, 'description', cells[4]);
// Add the row into this.tableEntries for future reference
this.tableEntries.push(row);
@@ -163,6 +181,7 @@ var CrosLogAnalyzerView = (function() {
*/
refresh: function() {
this.createFilter();
+ this.createLogMaker();
this.populateTable();
this.createVisualizer();
},
@@ -303,6 +322,14 @@ var CrosLogAnalyzerView = (function() {
},
/**
+ * Called during the initialization. It creates the log marker that
+ * highlights log text.
+ */
+ createLogMaker: function() {
+ this.marker = new CrosLogMarker(this);
+ },
+
+ /**
* Given a row text line of log, a logEntry instance is initialized and used
* for parsing. After the text is parsed, we put the instance into
* logEntries which is an array for storing. This function is called when
diff --git a/chrome/browser/resources/net_internals/cros_log_marker.js b/chrome/browser/resources/net_internals/cros_log_marker.js
new file mode 100644
index 0000000..71201e3
--- /dev/null
+++ b/chrome/browser/resources/net_internals/cros_log_marker.js
@@ -0,0 +1,394 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+/**
+ * This class stores the filter queries as history and highlight the log text
+ * to increase the readability of the log
+ *
+ * - Enable / Disable highlights
+ * - Highlights text with multiple colors
+ * - Resolve hghlight conficts (A text highlighted by multiple colors) so that
+ * the latest added highlight always has highest priority to display.
+ *
+ */
+var CrosLogMarker = (function() {
+ 'use strict';
+
+ // Special classes (defined in log_analyzer_view.css)
+ var LOG_MARKER_HIGHLIGHT_CLASS = 'cros-log-analyzer-marker-highlight';
+ var LOG_MARKER_CONTAINER_ID = 'cros-log-analyzer-marker-container';
+ var LOG_MARKER_HISTORY_ENTRY_CLASS = 'cros-log-analyzer-marker-history-entry';
+ var LOG_MARKER_HISTORY_COLOR_TAG_CLASS =
+ 'cros-log-analyzer-marker-history-color-tag';
+
+ /**
+ * Colors used for highlighting. (Current we support 6 colors)
+ * TODO(shinfan): Add more supoorted colors.
+ */
+ var COLOR_USAGE_SET = {
+ 'Crimson': false,
+ 'DeepSkyBlue': false,
+ 'DarkSeaGreen': false,
+ 'GoldenRod': false,
+ 'IndianRed': false,
+ 'Orange': false
+ };
+ var COLOR_NUMBER = Object.keys(COLOR_USAGE_SET).length;
+
+
+ /**
+ * CrosHighlightTag represents a single highlight tag in text.
+ */
+ var CrosHighlightTag = (function() {
+ /**
+ * @constructor
+ */
+ function CrosHighlightTag(color, field, range, priority) {
+ this.color = color;
+ this.field = field;
+ this.range = range;
+ this.priority = priority;
+ this.enabled = true;
+ }
+
+ return CrosHighlightTag;
+ })();
+
+ /**
+ * @constructor
+ * @param {CrosLogAnalyzerView} logAnalyzerView A reference to
+ * CrosLogAnalyzerView.
+ */
+ function CrosLogMarker(logAnalyzerView) {
+ this.container = $(LOG_MARKER_CONTAINER_ID);
+ // Stores highlight objects for each entry.
+ this.entryHighlights = [];
+ // Stores all the filter queries.
+ this.markHistory = {};
+ // Object references from CrosLogAnalyzerView.
+ this.logEntries = logAnalyzerView.logEntries;
+ this.logAnalyzerView = logAnalyzerView;
+ // Counts how many highlights are created.
+ this.markCount = 0;
+ for (var i = 0; i < this.logEntries.length; i++) {
+ this.entryHighlights.push([]);
+ }
+ }
+
+ CrosLogMarker.prototype = {
+ /**
+ * Saves the query to the mark history and highlights the text
+ * based on the query.
+ */
+ addMarkHistory: function(query) {
+ // Increases the counter
+ this.markCount += 1;
+
+ // Find an avaiable color.
+ var color = this.pickColor();
+ if (!color) {
+ // If all colors are occupied.
+ alert('You can only add at most ' + COLOR_NUMBER + 'markers.');
+ return;
+ }
+
+ // Updates HTML elements.
+ var historyEntry = addNode(this.container, 'div');
+ historyEntry.className = LOG_MARKER_HISTORY_ENTRY_CLASS;
+
+ // A color tag that indicats the color used.
+ var colorTag = addNode(historyEntry, 'div');
+ colorTag.className = LOG_MARKER_HISTORY_COLOR_TAG_CLASS;
+ colorTag.style.background = color;
+
+ // Displays the query text.
+ var queryText = addNodeWithText(historyEntry, 'p', query);
+ queryText.style.color = color;
+
+ // Adds a button to remove the marker.
+ var removeBtn = addNodeWithText(historyEntry, 'a', 'Remove');
+ removeBtn.addEventListener(
+ 'click', this.onRemoveBtnClicked_.bind(this, historyEntry, color));
+
+ // A checkbox that lets user enable and disable the marker.
+ var enableCheckbox = addNode(historyEntry, 'input');
+ enableCheckbox.type = 'checkbox';
+ enableCheckbox.checked = true;
+ enableCheckbox.color = color;
+ enableCheckbox.addEventListener('change',
+ this.onEnableCheckboxChange_.bind(this, enableCheckbox), false);
+
+ // Searches log text for matched patterns and highlights them.
+ this.patternMatch(query, color);
+ },
+
+ /**
+ * Search the text for matched strings
+ */
+ patternMatch: function(query, color) {
+ var pattern = new RegExp(query, 'i');
+ for (var i = 0; i < this.logEntries.length; i++) {
+ var entry = this.logEntries[i];
+ // Search description of each log entry
+ // TODO(shinfan): Add more search fields
+ var positions = this.findPositions(
+ pattern, entry.description);
+ for (var j = 0; j < positions.length; j++) {
+ var pos = positions[j];
+ this.mark(entry, pos, 'description', color);
+ }
+ this.sortHighlightsByStartPosition_(this.entryHighlights[i]);
+ }
+ },
+
+ /**
+ * Highlights the text.
+ * @param {CrosLogEntry} entry The log entry to be highlighted
+ * @param {int|Array} position [start, end]
+ * @param {string} field The field of entry to be highlighted
+ * @param {string} color color used for highlighting
+ */
+ mark: function(entry, position, field, color) {
+ // Creates the highlight object
+ var tag = new CrosHighlightTag(color, field, position, this.markCount);
+ // Add the highlight into entryHighlights
+ this.entryHighlights[entry.rowNum].push(tag);
+ },
+
+ /**
+ * Find the highlight objects that covers the given position
+ * @param {CrosHighlightTag|Array} highlights highlights of a log entry
+ * @param {int} position The target index
+ * @param {string} field The target field
+ * @return {CrosHighlightTag|Array} Highlights that cover the position
+ */
+ getHighlight: function(highlights, index, field) {
+ var res = [];
+ for (var j = 0; j < highlights.length; j++) {
+ var highlight = highlights[j];
+ if (highlight.range[0] <= index &&
+ highlight.range[1] > index &&
+ highlight.field == field &&
+ highlight.enabled) {
+ res.push(highlight);
+ }
+ }
+ /**
+ * Sorts the result by priority so that the highlight with
+ * highest priority comes first.
+ */
+ this.sortHighlightsByPriority_(res);
+ return res;
+ },
+
+ /**
+ * This function highlights the entry by going through the text from left
+ * to right and searching for "key" positions.
+ * A "key" position is a position that one (or more) highlight
+ * starts or ends. We only care about "key" positions because this is where
+ * the text highlight status changes.
+ * At each key position, the function decides if the text between this
+ * position and previous position need to be highlighted and resolves
+ * highlight conflicts.
+ *
+ * @param {CrosLogEntry} entry The entry going to be highlighted.
+ * @param {string} field The specified field of the entry.
+ * @param {DOMElement} parent Parent node.
+ */
+ getHighlightedEntry: function(entry, field, parent) {
+ var rowNum = entry.rowNum;
+ // Get the original text content of the entry (without any highlights).
+ var content = this.logEntries[rowNum][field];
+ var index = 0;
+ while (index < content.length) {
+ var nextIndex = this.getNextIndex(
+ this.entryHighlights[rowNum], index, field, content);
+ // Searches for highlights that have the position in range.
+ var highlights = this.getHighlight(
+ this.entryHighlights[rowNum], index, field);
+ var text = content.substr(index, nextIndex - index);
+ if (highlights.length > 0) {
+ // Always picks the highlight with highest priority.
+ this.addSpan(text, highlights[0].color, parent);
+ } else {
+ addNodeWithText(parent, 'span', text);
+ }
+ index = nextIndex;
+ }
+ },
+
+ /**
+ * A helper function that is used by this.getHightlightedEntry
+ * It returns the first index where a highlight begins or ends from
+ * the given index.
+ * @param {CrosHighlightTag|Array} highlights An array of highlights
+ * of a log entry.
+ * @param {int} index The start position.
+ * @param {string} field The specified field of entry.
+ * Other fields are ignored.
+ * @param {string} content The text content of the log entry.
+ * @return {int} The first index where a highlight begins or ends.
+ */
+ getNextIndex: function(highlights, index, field, content) {
+ var minGap = Infinity;
+ var res = -1;
+ for (var i = 0; i < highlights.length; i++) {
+ if (highlights[i].field != field || !highlights[i].enabled)
+ continue;
+ // Distance between current index and the start index of highlight.
+ var gap1 = highlights[i].range[0] - index;
+ // Distance between current index and the end index of highlight.
+ var gap2 = highlights[i].range[1] - index;
+ if (gap1 > 0 && gap1 < minGap) {
+ minGap = gap1;
+ res = highlights[i].range[0];
+ }
+ if (gap2 > 0 && gap2 < minGap) {
+ minGap = gap2;
+ res = highlights[i].range[1];
+ }
+ }
+ // Returns |res| if found. Otherwise returns the end position of the text.
+ return res > 0 ? res : content.length;
+ },
+
+ /**
+ * A helper function that is used by this.getHightlightedEntry.
+ * It adds the HTML label to the text.
+ */
+ addSpan: function(text, color, parent) {
+ var span = addNodeWithText(parent, 'span', text);
+ span.style.color = color;
+ span.className = LOG_MARKER_HIGHLIGHT_CLASS;
+ },
+
+ /**
+ * A helper function that is used by this.getHightlightedEntry.
+ * It adds the HTML label to the text.
+ */
+ pickColor: function() {
+ for (var color in COLOR_USAGE_SET) {
+ if (!COLOR_USAGE_SET[color]) {
+ COLOR_USAGE_SET[color] = true;
+ return color;
+ }
+ }
+ return false;
+ },
+
+ /**
+ * A event handler that enables and disables the corresponding marker.
+ * @private
+ */
+ onEnableCheckboxChange_: function(checkbox) {
+ for (var i = 0; i < this.entryHighlights.length; i++) {
+ for (var j = 0; j < this.entryHighlights[i].length; j++) {
+ if (this.entryHighlights[i][j].color == checkbox.color) {
+ this.entryHighlights[i][j].enabled = checkbox.checked;
+ }
+ }
+ }
+ this.refreshLogTable();
+ },
+
+ /**
+ * A event handlier that removes the marker from history.
+ * @private
+ */
+ onRemoveBtnClicked_: function(entry, color) {
+ entry.parentNode.removeChild(entry);
+ COLOR_USAGE_SET[color] = false;
+ for (var i = 0; i < this.entryHighlights.length; i++) {
+ var highlights = this.entryHighlights[i];
+ while (true) {
+ var index = this.findHighlightByColor_(highlights, color);
+ if (index == -1)
+ break;
+ highlights.splice(index, 1);
+ }
+ }
+ this.refreshLogTable();
+ },
+
+ /**
+ * A helper function that returns the index of first highlight that
+ * has the target color. Otherwise returns -1.
+ * @private
+ */
+ findHighlightByColor_: function(highlights, color) {
+ for (var i = 0; i < highlights.length; i++) {
+ if (highlights[i].color == color)
+ return i;
+ }
+ return -1;
+ },
+
+ /**
+ * Refresh the log table in the CrosLogAnalyzerView.
+ */
+ refreshLogTable: function() {
+ this.logAnalyzerView.populateTable();
+ this.logAnalyzerView.filterLog();
+ },
+
+ /**
+ * A pattern can appear multiple times in a string.
+ * Returns positions of all the appearance.
+ */
+ findPositions: function(pattern, str) {
+ var res = [];
+ str = str.toLowerCase();
+ var match = str.match(pattern);
+ if (!match)
+ return res;
+ for (var i = 0; i < match.length; i++) {
+ var index = 0;
+ while (true) {
+ var start = str.indexOf(match[i].toLowerCase(), index);
+ if (start == -1)
+ break;
+ var end = start + match[i].length;
+ res.push([start, end]);
+ index = end + 1;
+ }
+ }
+ return res;
+ },
+
+ /**
+ * A helper function used in sorting highlights by start position.
+ * @param {HighlightTag} h1, h2 Two highlight tags in the array.
+ * @private
+ */
+ compareStartPosition_: function(h1, h2) {
+ return h1.range[0] - h2.range[0];
+ },
+
+ /**
+ * A helper function used in sorting highlights by priority.
+ * @param {HighlightTag} h1, h2 Two highlight tags in the array.
+ * @private
+ */
+ comparePriority_: function(h1, h2) {
+ return h2.priority - h1.priority;
+ },
+
+ /**
+ * A helper function that sorts the highlights array by start position.
+ * @private
+ */
+ sortHighlightsByStartPosition_: function(highlights) {
+ highlights.sort(this.compareStartPosition_);
+ },
+
+ /**
+ * A helper function that sorts the highlights array by priority.
+ * @private
+ */
+ sortHighlightsByPriority_: function(highlights) {
+ highlights.sort(this.comparePriority_);
+ }
+ };
+
+ return CrosLogMarker;
+})();
diff --git a/chrome/browser/resources/net_internals/index.js b/chrome/browser/resources/net_internals/index.js
index 762b036..44465fd 100644
--- a/chrome/browser/resources/net_internals/index.js
+++ b/chrome/browser/resources/net_internals/index.js
@@ -51,6 +51,7 @@
<include src="cros_log_analyzer_view.js"/>
<include src="cros_log_entry.js"/>
<include src="cros_log_visualizer.js" />
+<include src="cros_log_marker.js" />
document.addEventListener('DOMContentLoaded', function() {
MainView.getInstance(); // from main.js