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

Side by Side Diff: netlog_viewer/events_view.js

Issue 2162963002: [polymer] Merge of master into polymer10-migration (Closed) Base URL: git@github.com:catapult-project/catapult.git@polymer10-migration
Patch Set: Merge polymer10-migration int polymer10-merge 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 unified diff | 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 »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2012 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 * EventsView displays a filtered list of all events sharing a source, and
7 * a details pane for the selected sources.
8 *
9 * +----------------------++----------------+
10 * | filter box || |
11 * +----------------------+| |
12 * | || |
13 * | || |
14 * | || |
15 * | || |
16 * | source list || details |
17 * | || view |
18 * | || |
19 * | || |
20 * | || |
21 * | || |
22 * | || |
23 * | || |
24 * +----------------------++----------------+
25 */
26 var EventsView = (function() {
27 'use strict';
28
29 // How soon after updating the filter list the counter should be updated.
30 var REPAINT_FILTER_COUNTER_TIMEOUT_MS = 0;
31
32 // We inherit from View.
33 var superClass = View;
34
35 /*
36 * @constructor
37 */
38 function EventsView() {
39 assertFirstConstructorCall(EventsView);
40
41 // Call superclass's constructor.
42 superClass.call(this);
43
44 // Initialize the sub-views.
45 var leftPane = new VerticalSplitView(new DivView(EventsView.TOPBAR_ID),
46 new DivView(EventsView.LIST_BOX_ID));
47
48 this.detailsView_ = new DetailsView(EventsView.DETAILS_LOG_BOX_ID);
49
50 this.splitterView_ = new ResizableVerticalSplitView(
51 leftPane, this.detailsView_, new DivView(EventsView.SIZER_ID));
52
53 SourceTracker.getInstance().addSourceEntryObserver(this);
54
55 this.tableBody_ = $(EventsView.TBODY_ID);
56
57 this.filterInput_ = $(EventsView.FILTER_INPUT_ID);
58 this.filterCount_ = $(EventsView.FILTER_COUNT_ID);
59
60 this.filterInput_.addEventListener('search',
61 this.onFilterTextChanged_.bind(this), true);
62
63 $(EventsView.SELECT_ALL_ID).addEventListener(
64 'click', this.selectAll_.bind(this), true);
65
66 $(EventsView.SORT_BY_ID_ID).addEventListener(
67 'click', this.sortById_.bind(this), true);
68
69 $(EventsView.SORT_BY_SOURCE_TYPE_ID).addEventListener(
70 'click', this.sortBySourceType_.bind(this), true);
71
72 $(EventsView.SORT_BY_DESCRIPTION_ID).addEventListener(
73 'click', this.sortByDescription_.bind(this), true);
74
75 new MouseOverHelp(EventsView.FILTER_HELP_ID,
76 EventsView.FILTER_HELP_HOVER_ID);
77
78 // Sets sort order and filter.
79 this.setFilter_('');
80
81 this.initializeSourceList_();
82 }
83
84 EventsView.TAB_ID = 'tab-handle-events';
85 EventsView.TAB_NAME = 'Events';
86 EventsView.TAB_HASH = '#events';
87
88 // IDs for special HTML elements in events_view.html
89 EventsView.TBODY_ID = 'events-view-source-list-tbody';
90 EventsView.FILTER_INPUT_ID = 'events-view-filter-input';
91 EventsView.FILTER_COUNT_ID = 'events-view-filter-count';
92 EventsView.FILTER_HELP_ID = 'events-view-filter-help';
93 EventsView.FILTER_HELP_HOVER_ID = 'events-view-filter-help-hover';
94 EventsView.SELECT_ALL_ID = 'events-view-select-all';
95 EventsView.SORT_BY_ID_ID = 'events-view-sort-by-id';
96 EventsView.SORT_BY_SOURCE_TYPE_ID = 'events-view-sort-by-source';
97 EventsView.SORT_BY_DESCRIPTION_ID = 'events-view-sort-by-description';
98 EventsView.DETAILS_LOG_BOX_ID = 'events-view-details-log-box';
99 EventsView.TOPBAR_ID = 'events-view-filter-box';
100 EventsView.LIST_BOX_ID = 'events-view-source-list';
101 EventsView.SIZER_ID = 'events-view-splitter-box';
102
103 cr.addSingletonGetter(EventsView);
104
105 EventsView.prototype = {
106 // Inherit the superclass's methods.
107 __proto__: superClass.prototype,
108
109 /**
110 * Initializes the list of source entries. If source entries are already,
111 * being displayed, removes them all in the process.
112 */
113 initializeSourceList_: function() {
114 this.currentSelectedRows_ = [];
115 this.sourceIdToRowMap_ = {};
116 this.tableBody_.innerHTML = '';
117 this.numPrefilter_ = 0;
118 this.numPostfilter_ = 0;
119 this.invalidateFilterCounter_();
120 this.invalidateDetailsView_();
121 },
122
123 setGeometry: function(left, top, width, height) {
124 superClass.prototype.setGeometry.call(this, left, top, width, height);
125 this.splitterView_.setGeometry(left, top, width, height);
126 },
127
128 show: function(isVisible) {
129 superClass.prototype.show.call(this, isVisible);
130 this.splitterView_.show(isVisible);
131 },
132
133 getFilterText_: function() {
134 return this.filterInput_.value;
135 },
136
137 setFilterText_: function(filterText) {
138 this.filterInput_.value = filterText;
139 this.onFilterTextChanged_();
140 },
141
142 onFilterTextChanged_: function() {
143 this.setFilter_(this.getFilterText_());
144 },
145
146 /**
147 * Updates text in the details view when privacy stripping is toggled.
148 */
149 onPrivacyStrippingChanged: function() {
150 this.invalidateDetailsView_();
151 },
152
153 /**
154 * Updates text in the details view when time display mode is toggled.
155 */
156 onUseRelativeTimesChanged: function() {
157 this.invalidateDetailsView_();
158 },
159
160 comparisonFuncWithReversing_: function(a, b) {
161 var result = this.comparisonFunction_(a, b);
162 if (this.doSortBackwards_)
163 result *= -1;
164 return result;
165 },
166
167 sort_: function() {
168 var sourceEntries = [];
169 for (var id in this.sourceIdToRowMap_) {
170 sourceEntries.push(this.sourceIdToRowMap_[id].getSourceEntry());
171 }
172 sourceEntries.sort(this.comparisonFuncWithReversing_.bind(this));
173
174 // Reposition source rows from back to front.
175 for (var i = sourceEntries.length - 2; i >= 0; --i) {
176 var sourceRow = this.sourceIdToRowMap_[sourceEntries[i].getSourceId()];
177 var nextSourceId = sourceEntries[i + 1].getSourceId();
178 if (sourceRow.getNextNodeSourceId() != nextSourceId) {
179 var nextSourceRow = this.sourceIdToRowMap_[nextSourceId];
180 sourceRow.moveBefore(nextSourceRow);
181 }
182 }
183 },
184
185 setFilter_: function(filterText) {
186 var lastComparisonFunction = this.comparisonFunction_;
187 var lastDoSortBackwards = this.doSortBackwards_;
188
189 var filterParser = new SourceFilterParser(filterText);
190 this.currentFilter_ = filterParser.filter;
191
192 this.pickSortFunction_(filterParser.sort);
193
194 if (lastComparisonFunction != this.comparisonFunction_ ||
195 lastDoSortBackwards != this.doSortBackwards_) {
196 this.sort_();
197 }
198
199 // Iterate through all of the rows and see if they match the filter.
200 for (var id in this.sourceIdToRowMap_) {
201 var entry = this.sourceIdToRowMap_[id];
202 entry.setIsMatchedByFilter(this.currentFilter_(entry.getSourceEntry()));
203 }
204 },
205
206 /**
207 * Given a "sort" object with "method" and "backwards" keys, looks up and
208 * sets |comparisonFunction_| and |doSortBackwards_|. If the ID does not
209 * correspond to a sort function, defaults to sorting by ID.
210 */
211 pickSortFunction_: function(sort) {
212 this.doSortBackwards_ = sort.backwards;
213 this.comparisonFunction_ = COMPARISON_FUNCTION_TABLE[sort.method];
214 if (!this.comparisonFunction_) {
215 this.doSortBackwards_ = false;
216 this.comparisonFunction_ = compareSourceId_;
217 }
218 },
219
220 /**
221 * Repositions |sourceRow|'s in the table using an insertion sort.
222 * Significantly faster than sorting the entire table again, when only
223 * one entry has changed.
224 */
225 insertionSort_: function(sourceRow) {
226 // SourceRow that should be after |sourceRow|, if it needs
227 // to be moved earlier in the list.
228 var sourceRowAfter = sourceRow;
229 while (true) {
230 var prevSourceId = sourceRowAfter.getPreviousNodeSourceId();
231 if (prevSourceId == null)
232 break;
233 var prevSourceRow = this.sourceIdToRowMap_[prevSourceId];
234 if (this.comparisonFuncWithReversing_(
235 sourceRow.getSourceEntry(),
236 prevSourceRow.getSourceEntry()) >= 0) {
237 break;
238 }
239 sourceRowAfter = prevSourceRow;
240 }
241 if (sourceRowAfter != sourceRow) {
242 sourceRow.moveBefore(sourceRowAfter);
243 return;
244 }
245
246 var sourceRowBefore = sourceRow;
247 while (true) {
248 var nextSourceId = sourceRowBefore.getNextNodeSourceId();
249 if (nextSourceId == null)
250 break;
251 var nextSourceRow = this.sourceIdToRowMap_[nextSourceId];
252 if (this.comparisonFuncWithReversing_(
253 sourceRow.getSourceEntry(),
254 nextSourceRow.getSourceEntry()) <= 0) {
255 break;
256 }
257 sourceRowBefore = nextSourceRow;
258 }
259 if (sourceRowBefore != sourceRow)
260 sourceRow.moveAfter(sourceRowBefore);
261 },
262
263 /**
264 * Called whenever SourceEntries are updated with new log entries. Updates
265 * the corresponding table rows, sort order, and the details view as needed.
266 */
267 onSourceEntriesUpdated: function(sourceEntries) {
268 var isUpdatedSourceSelected = false;
269 var numNewSourceEntries = 0;
270
271 for (var i = 0; i < sourceEntries.length; ++i) {
272 var sourceEntry = sourceEntries[i];
273
274 // Lookup the row.
275 var sourceRow = this.sourceIdToRowMap_[sourceEntry.getSourceId()];
276
277 if (!sourceRow) {
278 sourceRow = new SourceRow(this, sourceEntry);
279 this.sourceIdToRowMap_[sourceEntry.getSourceId()] = sourceRow;
280 ++numNewSourceEntries;
281 } else {
282 sourceRow.onSourceUpdated();
283 }
284
285 if (sourceRow.isSelected())
286 isUpdatedSourceSelected = true;
287
288 // TODO(mmenke): Fix sorting when sorting by duration.
289 // Duration continuously increases for all entries that
290 // are still active. This can result in incorrect
291 // sorting, until sort_ is called.
292 this.insertionSort_(sourceRow);
293 }
294
295 if (isUpdatedSourceSelected)
296 this.invalidateDetailsView_();
297 if (numNewSourceEntries)
298 this.incrementPrefilterCount(numNewSourceEntries);
299 },
300
301 /**
302 * Returns the SourceRow with the specified ID, if there is one.
303 * Otherwise, returns undefined.
304 */
305 getSourceRow: function(id) {
306 return this.sourceIdToRowMap_[id];
307 },
308
309 /**
310 * Called whenever all log events are deleted.
311 */
312 onAllSourceEntriesDeleted: function() {
313 this.initializeSourceList_();
314 },
315
316 /**
317 * Called when either a log file is loaded, after clearing the old entries,
318 * but before getting any new ones.
319 */
320 onLoadLogStart: function() {
321 // Needed to sort new sourceless entries correctly.
322 this.maxReceivedSourceId_ = 0;
323 },
324
325 onLoadLogFinish: function(data) {
326 return true;
327 },
328
329 incrementPrefilterCount: function(offset) {
330 this.numPrefilter_ += offset;
331 this.invalidateFilterCounter_();
332 },
333
334 incrementPostfilterCount: function(offset) {
335 this.numPostfilter_ += offset;
336 this.invalidateFilterCounter_();
337 },
338
339 onSelectionChanged: function() {
340 this.invalidateDetailsView_();
341 },
342
343 clearSelection: function() {
344 var prevSelection = this.currentSelectedRows_;
345 this.currentSelectedRows_ = [];
346
347 // Unselect everything that is currently selected.
348 for (var i = 0; i < prevSelection.length; ++i) {
349 prevSelection[i].setSelected(false);
350 }
351
352 this.onSelectionChanged();
353 },
354
355 selectAll_: function(event) {
356 for (var id in this.sourceIdToRowMap_) {
357 var sourceRow = this.sourceIdToRowMap_[id];
358 if (sourceRow.isMatchedByFilter()) {
359 sourceRow.setSelected(true);
360 }
361 }
362 event.preventDefault();
363 },
364
365 unselectAll_: function() {
366 var entries = this.currentSelectedRows_.slice(0);
367 for (var i = 0; i < entries.length; ++i) {
368 entries[i].setSelected(false);
369 }
370 },
371
372 /**
373 * If |params| includes a query, replaces the current filter and unselects.
374 * all items. If it includes a selection, tries to select the relevant
375 * item.
376 */
377 setParameters: function(params) {
378 if (params.q) {
379 this.unselectAll_();
380 this.setFilterText_(params.q);
381 }
382
383 if (params.s) {
384 var sourceRow = this.sourceIdToRowMap_[params.s];
385 if (sourceRow) {
386 sourceRow.setSelected(true);
387 this.scrollToSourceId(params.s);
388 }
389 }
390 },
391
392 /**
393 * Scrolls to the source indicated by |sourceId|, if displayed.
394 */
395 scrollToSourceId: function(sourceId) {
396 this.detailsView_.scrollToSourceId(sourceId);
397 },
398
399 /**
400 * If already using the specified sort method, flips direction. Otherwise,
401 * removes pre-existing sort parameter before adding the new one.
402 */
403 toggleSortMethod_: function(sortMethod) {
404 // Get old filter text and remove old sort directives, if any.
405 var filterParser = new SourceFilterParser(this.getFilterText_());
406 var filterText = filterParser.filterTextWithoutSort;
407
408 filterText = 'sort:' + sortMethod + ' ' + filterText;
409
410 // If already using specified sortMethod, sort backwards.
411 if (!this.doSortBackwards_ &&
412 COMPARISON_FUNCTION_TABLE[sortMethod] == this.comparisonFunction_) {
413 filterText = '-' + filterText;
414 }
415
416 this.setFilterText_(filterText.trim());
417 },
418
419 sortById_: function(event) {
420 this.toggleSortMethod_('id');
421 },
422
423 sortBySourceType_: function(event) {
424 this.toggleSortMethod_('source');
425 },
426
427 sortByDescription_: function(event) {
428 this.toggleSortMethod_('desc');
429 },
430
431 /**
432 * Modifies the map of selected rows to include/exclude the one with
433 * |sourceId|, if present. Does not modify checkboxes or the LogView.
434 * Should only be called by a SourceRow in response to its selection
435 * state changing.
436 */
437 modifySelectionArray: function(sourceId, addToSelection) {
438 var sourceRow = this.sourceIdToRowMap_[sourceId];
439 if (!sourceRow)
440 return;
441 // Find the index for |sourceEntry| in the current selection list.
442 var index = -1;
443 for (var i = 0; i < this.currentSelectedRows_.length; ++i) {
444 if (this.currentSelectedRows_[i] == sourceRow) {
445 index = i;
446 break;
447 }
448 }
449
450 if (index != -1 && !addToSelection) {
451 // Remove from the selection.
452 this.currentSelectedRows_.splice(index, 1);
453 }
454
455 if (index == -1 && addToSelection) {
456 this.currentSelectedRows_.push(sourceRow);
457 }
458 },
459
460 getSelectedSourceEntries_: function() {
461 var sourceEntries = [];
462 for (var i = 0; i < this.currentSelectedRows_.length; ++i) {
463 sourceEntries.push(this.currentSelectedRows_[i].getSourceEntry());
464 }
465 return sourceEntries;
466 },
467
468 invalidateDetailsView_: function() {
469 this.detailsView_.setData(this.getSelectedSourceEntries_());
470 },
471
472 invalidateFilterCounter_: function() {
473 if (!this.outstandingRepaintFilterCounter_) {
474 this.outstandingRepaintFilterCounter_ = true;
475 window.setTimeout(this.repaintFilterCounter_.bind(this),
476 REPAINT_FILTER_COUNTER_TIMEOUT_MS);
477 }
478 },
479
480 repaintFilterCounter_: function() {
481 this.outstandingRepaintFilterCounter_ = false;
482 this.filterCount_.innerHTML = '';
483 addTextNode(this.filterCount_,
484 this.numPostfilter_ + ' of ' + this.numPrefilter_);
485 }
486 }; // end of prototype.
487
488 // ------------------------------------------------------------------------
489 // Helper code for comparisons
490 // ------------------------------------------------------------------------
491
492 var COMPARISON_FUNCTION_TABLE = {
493 // sort: and sort:- are allowed
494 '': compareSourceId_,
495 'active': compareActive_,
496 'desc': compareDescription_,
497 'description': compareDescription_,
498 'duration': compareDuration_,
499 'id': compareSourceId_,
500 'source': compareSourceType_,
501 'type': compareSourceType_
502 };
503
504 /**
505 * Sorts active entries first. If both entries are inactive, puts the one
506 * that was active most recently first. If both are active, uses source ID,
507 * which puts longer lived events at the top, and behaves better than using
508 * duration or time of first event.
509 */
510 function compareActive_(source1, source2) {
511 if (!source1.isInactive() && source2.isInactive())
512 return -1;
513 if (source1.isInactive() && !source2.isInactive())
514 return 1;
515 if (source1.isInactive()) {
516 var deltaEndTime = source1.getEndTicks() - source2.getEndTicks();
517 if (deltaEndTime != 0) {
518 // The one that ended most recently (Highest end time) should be sorted
519 // first.
520 return -deltaEndTime;
521 }
522 // If both ended at the same time, then odds are they were related events,
523 // started one after another, so sort in the opposite order of their
524 // source IDs to get a more intuitive ordering.
525 return -compareSourceId_(source1, source2);
526 }
527 return compareSourceId_(source1, source2);
528 }
529
530 function compareDescription_(source1, source2) {
531 var source1Text = source1.getDescription().toLowerCase();
532 var source2Text = source2.getDescription().toLowerCase();
533 var compareResult = source1Text.localeCompare(source2Text);
534 if (compareResult != 0)
535 return compareResult;
536 return compareSourceId_(source1, source2);
537 }
538
539 function compareDuration_(source1, source2) {
540 var durationDifference = source2.getDuration() - source1.getDuration();
541 if (durationDifference)
542 return durationDifference;
543 return compareSourceId_(source1, source2);
544 }
545
546 /**
547 * For the purposes of sorting by source IDs, entries without a source
548 * appear right after the SourceEntry with the highest source ID received
549 * before the sourceless entry. Any ambiguities are resolved by ordering
550 * the entries without a source by the order in which they were received.
551 */
552 function compareSourceId_(source1, source2) {
553 var sourceId1 = source1.getSourceId();
554 if (sourceId1 < 0)
555 sourceId1 = source1.getMaxPreviousEntrySourceId();
556 var sourceId2 = source2.getSourceId();
557 if (sourceId2 < 0)
558 sourceId2 = source2.getMaxPreviousEntrySourceId();
559
560 if (sourceId1 != sourceId2)
561 return sourceId1 - sourceId2;
562
563 // One or both have a negative ID. In either case, the source with the
564 // highest ID should be sorted first.
565 return source2.getSourceId() - source1.getSourceId();
566 }
567
568 function compareSourceType_(source1, source2) {
569 var source1Text = source1.getSourceTypeString();
570 var source2Text = source2.getSourceTypeString();
571 var compareResult = source1Text.localeCompare(source2Text);
572 if (compareResult != 0)
573 return compareResult;
574 return compareSourceId_(source1, source2);
575 }
576
577 return EventsView;
578 })();
579
OLDNEW
« 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