Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2572)

Unified Diff: netlog_viewer/events_view.js

Issue 2154753002: Serving version 1 of the web app. All code has been copied from the (Closed) Base URL: https://github.com/catapult-project/catapult.git@gh-pages
Patch Set: Removed some unecessary files. Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « netlog_viewer/events_view.html ('k') | netlog_viewer/horizontal_scrollbar_view.js » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: netlog_viewer/events_view.js
diff --git a/netlog_viewer/events_view.js b/netlog_viewer/events_view.js
new file mode 100644
index 0000000000000000000000000000000000000000..49e06221abaf3ad0ae5ab8fe4620752bd65e85de
--- /dev/null
+++ b/netlog_viewer/events_view.js
@@ -0,0 +1,579 @@
+// Copyright (c) 2012 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.
+
+/**
+ * EventsView displays a filtered list of all events sharing a source, and
+ * a details pane for the selected sources.
+ *
+ * +----------------------++----------------+
+ * | filter box || |
+ * +----------------------+| |
+ * | || |
+ * | || |
+ * | || |
+ * | || |
+ * | source list || details |
+ * | || view |
+ * | || |
+ * | || |
+ * | || |
+ * | || |
+ * | || |
+ * | || |
+ * +----------------------++----------------+
+ */
+var EventsView = (function() {
+ 'use strict';
+
+ // How soon after updating the filter list the counter should be updated.
+ var REPAINT_FILTER_COUNTER_TIMEOUT_MS = 0;
+
+ // We inherit from View.
+ var superClass = View;
+
+ /*
+ * @constructor
+ */
+ function EventsView() {
+ assertFirstConstructorCall(EventsView);
+
+ // Call superclass's constructor.
+ superClass.call(this);
+
+ // Initialize the sub-views.
+ var leftPane = new VerticalSplitView(new DivView(EventsView.TOPBAR_ID),
+ new DivView(EventsView.LIST_BOX_ID));
+
+ this.detailsView_ = new DetailsView(EventsView.DETAILS_LOG_BOX_ID);
+
+ this.splitterView_ = new ResizableVerticalSplitView(
+ leftPane, this.detailsView_, new DivView(EventsView.SIZER_ID));
+
+ SourceTracker.getInstance().addSourceEntryObserver(this);
+
+ this.tableBody_ = $(EventsView.TBODY_ID);
+
+ this.filterInput_ = $(EventsView.FILTER_INPUT_ID);
+ this.filterCount_ = $(EventsView.FILTER_COUNT_ID);
+
+ this.filterInput_.addEventListener('search',
+ this.onFilterTextChanged_.bind(this), true);
+
+ $(EventsView.SELECT_ALL_ID).addEventListener(
+ 'click', this.selectAll_.bind(this), true);
+
+ $(EventsView.SORT_BY_ID_ID).addEventListener(
+ 'click', this.sortById_.bind(this), true);
+
+ $(EventsView.SORT_BY_SOURCE_TYPE_ID).addEventListener(
+ 'click', this.sortBySourceType_.bind(this), true);
+
+ $(EventsView.SORT_BY_DESCRIPTION_ID).addEventListener(
+ 'click', this.sortByDescription_.bind(this), true);
+
+ new MouseOverHelp(EventsView.FILTER_HELP_ID,
+ EventsView.FILTER_HELP_HOVER_ID);
+
+ // Sets sort order and filter.
+ this.setFilter_('');
+
+ this.initializeSourceList_();
+ }
+
+ EventsView.TAB_ID = 'tab-handle-events';
+ EventsView.TAB_NAME = 'Events';
+ EventsView.TAB_HASH = '#events';
+
+ // IDs for special HTML elements in events_view.html
+ EventsView.TBODY_ID = 'events-view-source-list-tbody';
+ EventsView.FILTER_INPUT_ID = 'events-view-filter-input';
+ EventsView.FILTER_COUNT_ID = 'events-view-filter-count';
+ EventsView.FILTER_HELP_ID = 'events-view-filter-help';
+ EventsView.FILTER_HELP_HOVER_ID = 'events-view-filter-help-hover';
+ EventsView.SELECT_ALL_ID = 'events-view-select-all';
+ EventsView.SORT_BY_ID_ID = 'events-view-sort-by-id';
+ EventsView.SORT_BY_SOURCE_TYPE_ID = 'events-view-sort-by-source';
+ EventsView.SORT_BY_DESCRIPTION_ID = 'events-view-sort-by-description';
+ EventsView.DETAILS_LOG_BOX_ID = 'events-view-details-log-box';
+ EventsView.TOPBAR_ID = 'events-view-filter-box';
+ EventsView.LIST_BOX_ID = 'events-view-source-list';
+ EventsView.SIZER_ID = 'events-view-splitter-box';
+
+ cr.addSingletonGetter(EventsView);
+
+ EventsView.prototype = {
+ // Inherit the superclass's methods.
+ __proto__: superClass.prototype,
+
+ /**
+ * Initializes the list of source entries. If source entries are already,
+ * being displayed, removes them all in the process.
+ */
+ initializeSourceList_: function() {
+ this.currentSelectedRows_ = [];
+ this.sourceIdToRowMap_ = {};
+ this.tableBody_.innerHTML = '';
+ this.numPrefilter_ = 0;
+ this.numPostfilter_ = 0;
+ this.invalidateFilterCounter_();
+ this.invalidateDetailsView_();
+ },
+
+ setGeometry: function(left, top, width, height) {
+ superClass.prototype.setGeometry.call(this, left, top, width, height);
+ this.splitterView_.setGeometry(left, top, width, height);
+ },
+
+ show: function(isVisible) {
+ superClass.prototype.show.call(this, isVisible);
+ this.splitterView_.show(isVisible);
+ },
+
+ getFilterText_: function() {
+ return this.filterInput_.value;
+ },
+
+ setFilterText_: function(filterText) {
+ this.filterInput_.value = filterText;
+ this.onFilterTextChanged_();
+ },
+
+ onFilterTextChanged_: function() {
+ this.setFilter_(this.getFilterText_());
+ },
+
+ /**
+ * Updates text in the details view when privacy stripping is toggled.
+ */
+ onPrivacyStrippingChanged: function() {
+ this.invalidateDetailsView_();
+ },
+
+ /**
+ * Updates text in the details view when time display mode is toggled.
+ */
+ onUseRelativeTimesChanged: function() {
+ this.invalidateDetailsView_();
+ },
+
+ comparisonFuncWithReversing_: function(a, b) {
+ var result = this.comparisonFunction_(a, b);
+ if (this.doSortBackwards_)
+ result *= -1;
+ return result;
+ },
+
+ sort_: function() {
+ var sourceEntries = [];
+ for (var id in this.sourceIdToRowMap_) {
+ sourceEntries.push(this.sourceIdToRowMap_[id].getSourceEntry());
+ }
+ sourceEntries.sort(this.comparisonFuncWithReversing_.bind(this));
+
+ // Reposition source rows from back to front.
+ for (var i = sourceEntries.length - 2; i >= 0; --i) {
+ var sourceRow = this.sourceIdToRowMap_[sourceEntries[i].getSourceId()];
+ var nextSourceId = sourceEntries[i + 1].getSourceId();
+ if (sourceRow.getNextNodeSourceId() != nextSourceId) {
+ var nextSourceRow = this.sourceIdToRowMap_[nextSourceId];
+ sourceRow.moveBefore(nextSourceRow);
+ }
+ }
+ },
+
+ setFilter_: function(filterText) {
+ var lastComparisonFunction = this.comparisonFunction_;
+ var lastDoSortBackwards = this.doSortBackwards_;
+
+ var filterParser = new SourceFilterParser(filterText);
+ this.currentFilter_ = filterParser.filter;
+
+ this.pickSortFunction_(filterParser.sort);
+
+ if (lastComparisonFunction != this.comparisonFunction_ ||
+ lastDoSortBackwards != this.doSortBackwards_) {
+ this.sort_();
+ }
+
+ // Iterate through all of the rows and see if they match the filter.
+ for (var id in this.sourceIdToRowMap_) {
+ var entry = this.sourceIdToRowMap_[id];
+ entry.setIsMatchedByFilter(this.currentFilter_(entry.getSourceEntry()));
+ }
+ },
+
+ /**
+ * Given a "sort" object with "method" and "backwards" keys, looks up and
+ * sets |comparisonFunction_| and |doSortBackwards_|. If the ID does not
+ * correspond to a sort function, defaults to sorting by ID.
+ */
+ pickSortFunction_: function(sort) {
+ this.doSortBackwards_ = sort.backwards;
+ this.comparisonFunction_ = COMPARISON_FUNCTION_TABLE[sort.method];
+ if (!this.comparisonFunction_) {
+ this.doSortBackwards_ = false;
+ this.comparisonFunction_ = compareSourceId_;
+ }
+ },
+
+ /**
+ * Repositions |sourceRow|'s in the table using an insertion sort.
+ * Significantly faster than sorting the entire table again, when only
+ * one entry has changed.
+ */
+ insertionSort_: function(sourceRow) {
+ // SourceRow that should be after |sourceRow|, if it needs
+ // to be moved earlier in the list.
+ var sourceRowAfter = sourceRow;
+ while (true) {
+ var prevSourceId = sourceRowAfter.getPreviousNodeSourceId();
+ if (prevSourceId == null)
+ break;
+ var prevSourceRow = this.sourceIdToRowMap_[prevSourceId];
+ if (this.comparisonFuncWithReversing_(
+ sourceRow.getSourceEntry(),
+ prevSourceRow.getSourceEntry()) >= 0) {
+ break;
+ }
+ sourceRowAfter = prevSourceRow;
+ }
+ if (sourceRowAfter != sourceRow) {
+ sourceRow.moveBefore(sourceRowAfter);
+ return;
+ }
+
+ var sourceRowBefore = sourceRow;
+ while (true) {
+ var nextSourceId = sourceRowBefore.getNextNodeSourceId();
+ if (nextSourceId == null)
+ break;
+ var nextSourceRow = this.sourceIdToRowMap_[nextSourceId];
+ if (this.comparisonFuncWithReversing_(
+ sourceRow.getSourceEntry(),
+ nextSourceRow.getSourceEntry()) <= 0) {
+ break;
+ }
+ sourceRowBefore = nextSourceRow;
+ }
+ if (sourceRowBefore != sourceRow)
+ sourceRow.moveAfter(sourceRowBefore);
+ },
+
+ /**
+ * Called whenever SourceEntries are updated with new log entries. Updates
+ * the corresponding table rows, sort order, and the details view as needed.
+ */
+ onSourceEntriesUpdated: function(sourceEntries) {
+ var isUpdatedSourceSelected = false;
+ var numNewSourceEntries = 0;
+
+ for (var i = 0; i < sourceEntries.length; ++i) {
+ var sourceEntry = sourceEntries[i];
+
+ // Lookup the row.
+ var sourceRow = this.sourceIdToRowMap_[sourceEntry.getSourceId()];
+
+ if (!sourceRow) {
+ sourceRow = new SourceRow(this, sourceEntry);
+ this.sourceIdToRowMap_[sourceEntry.getSourceId()] = sourceRow;
+ ++numNewSourceEntries;
+ } else {
+ sourceRow.onSourceUpdated();
+ }
+
+ if (sourceRow.isSelected())
+ isUpdatedSourceSelected = true;
+
+ // TODO(mmenke): Fix sorting when sorting by duration.
+ // Duration continuously increases for all entries that
+ // are still active. This can result in incorrect
+ // sorting, until sort_ is called.
+ this.insertionSort_(sourceRow);
+ }
+
+ if (isUpdatedSourceSelected)
+ this.invalidateDetailsView_();
+ if (numNewSourceEntries)
+ this.incrementPrefilterCount(numNewSourceEntries);
+ },
+
+ /**
+ * Returns the SourceRow with the specified ID, if there is one.
+ * Otherwise, returns undefined.
+ */
+ getSourceRow: function(id) {
+ return this.sourceIdToRowMap_[id];
+ },
+
+ /**
+ * Called whenever all log events are deleted.
+ */
+ onAllSourceEntriesDeleted: function() {
+ this.initializeSourceList_();
+ },
+
+ /**
+ * Called when either a log file is loaded, after clearing the old entries,
+ * but before getting any new ones.
+ */
+ onLoadLogStart: function() {
+ // Needed to sort new sourceless entries correctly.
+ this.maxReceivedSourceId_ = 0;
+ },
+
+ onLoadLogFinish: function(data) {
+ return true;
+ },
+
+ incrementPrefilterCount: function(offset) {
+ this.numPrefilter_ += offset;
+ this.invalidateFilterCounter_();
+ },
+
+ incrementPostfilterCount: function(offset) {
+ this.numPostfilter_ += offset;
+ this.invalidateFilterCounter_();
+ },
+
+ onSelectionChanged: function() {
+ this.invalidateDetailsView_();
+ },
+
+ clearSelection: function() {
+ var prevSelection = this.currentSelectedRows_;
+ this.currentSelectedRows_ = [];
+
+ // Unselect everything that is currently selected.
+ for (var i = 0; i < prevSelection.length; ++i) {
+ prevSelection[i].setSelected(false);
+ }
+
+ this.onSelectionChanged();
+ },
+
+ selectAll_: function(event) {
+ for (var id in this.sourceIdToRowMap_) {
+ var sourceRow = this.sourceIdToRowMap_[id];
+ if (sourceRow.isMatchedByFilter()) {
+ sourceRow.setSelected(true);
+ }
+ }
+ event.preventDefault();
+ },
+
+ unselectAll_: function() {
+ var entries = this.currentSelectedRows_.slice(0);
+ for (var i = 0; i < entries.length; ++i) {
+ entries[i].setSelected(false);
+ }
+ },
+
+ /**
+ * If |params| includes a query, replaces the current filter and unselects.
+ * all items. If it includes a selection, tries to select the relevant
+ * item.
+ */
+ setParameters: function(params) {
+ if (params.q) {
+ this.unselectAll_();
+ this.setFilterText_(params.q);
+ }
+
+ if (params.s) {
+ var sourceRow = this.sourceIdToRowMap_[params.s];
+ if (sourceRow) {
+ sourceRow.setSelected(true);
+ this.scrollToSourceId(params.s);
+ }
+ }
+ },
+
+ /**
+ * Scrolls to the source indicated by |sourceId|, if displayed.
+ */
+ scrollToSourceId: function(sourceId) {
+ this.detailsView_.scrollToSourceId(sourceId);
+ },
+
+ /**
+ * If already using the specified sort method, flips direction. Otherwise,
+ * removes pre-existing sort parameter before adding the new one.
+ */
+ toggleSortMethod_: function(sortMethod) {
+ // Get old filter text and remove old sort directives, if any.
+ var filterParser = new SourceFilterParser(this.getFilterText_());
+ var filterText = filterParser.filterTextWithoutSort;
+
+ filterText = 'sort:' + sortMethod + ' ' + filterText;
+
+ // If already using specified sortMethod, sort backwards.
+ if (!this.doSortBackwards_ &&
+ COMPARISON_FUNCTION_TABLE[sortMethod] == this.comparisonFunction_) {
+ filterText = '-' + filterText;
+ }
+
+ this.setFilterText_(filterText.trim());
+ },
+
+ sortById_: function(event) {
+ this.toggleSortMethod_('id');
+ },
+
+ sortBySourceType_: function(event) {
+ this.toggleSortMethod_('source');
+ },
+
+ sortByDescription_: function(event) {
+ this.toggleSortMethod_('desc');
+ },
+
+ /**
+ * Modifies the map of selected rows to include/exclude the one with
+ * |sourceId|, if present. Does not modify checkboxes or the LogView.
+ * Should only be called by a SourceRow in response to its selection
+ * state changing.
+ */
+ modifySelectionArray: function(sourceId, addToSelection) {
+ var sourceRow = this.sourceIdToRowMap_[sourceId];
+ if (!sourceRow)
+ return;
+ // Find the index for |sourceEntry| in the current selection list.
+ var index = -1;
+ for (var i = 0; i < this.currentSelectedRows_.length; ++i) {
+ if (this.currentSelectedRows_[i] == sourceRow) {
+ index = i;
+ break;
+ }
+ }
+
+ if (index != -1 && !addToSelection) {
+ // Remove from the selection.
+ this.currentSelectedRows_.splice(index, 1);
+ }
+
+ if (index == -1 && addToSelection) {
+ this.currentSelectedRows_.push(sourceRow);
+ }
+ },
+
+ getSelectedSourceEntries_: function() {
+ var sourceEntries = [];
+ for (var i = 0; i < this.currentSelectedRows_.length; ++i) {
+ sourceEntries.push(this.currentSelectedRows_[i].getSourceEntry());
+ }
+ return sourceEntries;
+ },
+
+ invalidateDetailsView_: function() {
+ this.detailsView_.setData(this.getSelectedSourceEntries_());
+ },
+
+ invalidateFilterCounter_: function() {
+ if (!this.outstandingRepaintFilterCounter_) {
+ this.outstandingRepaintFilterCounter_ = true;
+ window.setTimeout(this.repaintFilterCounter_.bind(this),
+ REPAINT_FILTER_COUNTER_TIMEOUT_MS);
+ }
+ },
+
+ repaintFilterCounter_: function() {
+ this.outstandingRepaintFilterCounter_ = false;
+ this.filterCount_.innerHTML = '';
+ addTextNode(this.filterCount_,
+ this.numPostfilter_ + ' of ' + this.numPrefilter_);
+ }
+ }; // end of prototype.
+
+ // ------------------------------------------------------------------------
+ // Helper code for comparisons
+ // ------------------------------------------------------------------------
+
+ var COMPARISON_FUNCTION_TABLE = {
+ // sort: and sort:- are allowed
+ '': compareSourceId_,
+ 'active': compareActive_,
+ 'desc': compareDescription_,
+ 'description': compareDescription_,
+ 'duration': compareDuration_,
+ 'id': compareSourceId_,
+ 'source': compareSourceType_,
+ 'type': compareSourceType_
+ };
+
+ /**
+ * Sorts active entries first. If both entries are inactive, puts the one
+ * that was active most recently first. If both are active, uses source ID,
+ * which puts longer lived events at the top, and behaves better than using
+ * duration or time of first event.
+ */
+ function compareActive_(source1, source2) {
+ if (!source1.isInactive() && source2.isInactive())
+ return -1;
+ if (source1.isInactive() && !source2.isInactive())
+ return 1;
+ if (source1.isInactive()) {
+ var deltaEndTime = source1.getEndTicks() - source2.getEndTicks();
+ if (deltaEndTime != 0) {
+ // The one that ended most recently (Highest end time) should be sorted
+ // first.
+ return -deltaEndTime;
+ }
+ // If both ended at the same time, then odds are they were related events,
+ // started one after another, so sort in the opposite order of their
+ // source IDs to get a more intuitive ordering.
+ return -compareSourceId_(source1, source2);
+ }
+ return compareSourceId_(source1, source2);
+ }
+
+ function compareDescription_(source1, source2) {
+ var source1Text = source1.getDescription().toLowerCase();
+ var source2Text = source2.getDescription().toLowerCase();
+ var compareResult = source1Text.localeCompare(source2Text);
+ if (compareResult != 0)
+ return compareResult;
+ return compareSourceId_(source1, source2);
+ }
+
+ function compareDuration_(source1, source2) {
+ var durationDifference = source2.getDuration() - source1.getDuration();
+ if (durationDifference)
+ return durationDifference;
+ return compareSourceId_(source1, source2);
+ }
+
+ /**
+ * For the purposes of sorting by source IDs, entries without a source
+ * appear right after the SourceEntry with the highest source ID received
+ * before the sourceless entry. Any ambiguities are resolved by ordering
+ * the entries without a source by the order in which they were received.
+ */
+ function compareSourceId_(source1, source2) {
+ var sourceId1 = source1.getSourceId();
+ if (sourceId1 < 0)
+ sourceId1 = source1.getMaxPreviousEntrySourceId();
+ var sourceId2 = source2.getSourceId();
+ if (sourceId2 < 0)
+ sourceId2 = source2.getMaxPreviousEntrySourceId();
+
+ if (sourceId1 != sourceId2)
+ return sourceId1 - sourceId2;
+
+ // One or both have a negative ID. In either case, the source with the
+ // highest ID should be sorted first.
+ return source2.getSourceId() - source1.getSourceId();
+ }
+
+ function compareSourceType_(source1, source2) {
+ var source1Text = source1.getSourceTypeString();
+ var source2Text = source2.getSourceTypeString();
+ var compareResult = source1Text.localeCompare(source2Text);
+ if (compareResult != 0)
+ return compareResult;
+ return compareSourceId_(source1, source2);
+ }
+
+ return EventsView;
+})();
+
« no previous file with comments | « netlog_viewer/events_view.html ('k') | netlog_viewer/horizontal_scrollbar_view.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698