| OLD | NEW |
| (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 'use strict'; | |
| 6 | |
| 7 /** | |
| 8 * @fileoverview TimelineView visualizes TRACE_EVENT events using the | |
| 9 * tracing.Timeline component and adds in selection summary and control buttons. | |
| 10 */ | |
| 11 cr.define('tracing', function() { | |
| 12 /** | |
| 13 * TimelineFindControl | |
| 14 * @constructor | |
| 15 * @extends {tracing.Overlay} | |
| 16 */ | |
| 17 var TimelineFindControl = cr.ui.define('div'); | |
| 18 | |
| 19 TimelineFindControl.prototype = { | |
| 20 __proto__: tracing.Overlay.prototype, | |
| 21 | |
| 22 decorate: function() { | |
| 23 tracing.Overlay.prototype.decorate.call(this); | |
| 24 | |
| 25 this.className = 'timeline-find-control'; | |
| 26 | |
| 27 this.hitCountEl_ = document.createElement('div'); | |
| 28 this.hitCountEl_.className = 'hit-count-label'; | |
| 29 this.hitCountEl_.textContent = '1 of 7'; | |
| 30 | |
| 31 var findPreviousBn = document.createElement('div'); | |
| 32 findPreviousBn.className = 'timeline-button find-previous'; | |
| 33 findPreviousBn.textContent = '\u2190'; | |
| 34 findPreviousBn.addEventListener('click', function() { | |
| 35 this.controller.findPrevious(); | |
| 36 this.updateHitCountEl_(); | |
| 37 }.bind(this)); | |
| 38 | |
| 39 var findNextBn = document.createElement('div'); | |
| 40 findNextBn.className = 'timeline-button find-next'; | |
| 41 findNextBn.textContent = '\u2192'; | |
| 42 findNextBn.addEventListener('click', function() { | |
| 43 this.controller.findNext(); | |
| 44 this.updateHitCountEl_(); | |
| 45 }.bind(this)); | |
| 46 | |
| 47 // Filter input element. | |
| 48 this.filterEl_ = document.createElement('input'); | |
| 49 this.filterEl_.type = 'input'; | |
| 50 | |
| 51 this.filterEl_.addEventListener('input', function(e) { | |
| 52 this.controller.filterText = this.filterEl_.value; | |
| 53 this.updateHitCountEl_(); | |
| 54 }.bind(this)); | |
| 55 | |
| 56 this.filterEl_.addEventListener('keydown', function(e) { | |
| 57 if (e.keyCode == 13) { | |
| 58 findNextBn.click(); | |
| 59 } else if (e.keyCode == 27) { | |
| 60 this.filterEl_.blur(); | |
| 61 this.updateHitCountEl_(); | |
| 62 } | |
| 63 }.bind(this)); | |
| 64 | |
| 65 this.filterEl_.addEventListener('blur', function(e) { | |
| 66 this.updateHitCountEl_(); | |
| 67 }.bind(this)); | |
| 68 | |
| 69 this.filterEl_.addEventListener('focus', function(e) { | |
| 70 this.updateHitCountEl_(); | |
| 71 }.bind(this)); | |
| 72 | |
| 73 // Attach everything. | |
| 74 this.appendChild(this.filterEl_); | |
| 75 | |
| 76 this.appendChild(findPreviousBn); | |
| 77 this.appendChild(findNextBn); | |
| 78 this.appendChild(this.hitCountEl_); | |
| 79 | |
| 80 this.updateHitCountEl_(); | |
| 81 }, | |
| 82 | |
| 83 get controller() { | |
| 84 return this.controller_; | |
| 85 }, | |
| 86 | |
| 87 set controller(c) { | |
| 88 this.controller_ = c; | |
| 89 this.updateHitCountEl_(); | |
| 90 }, | |
| 91 | |
| 92 focus: function() { | |
| 93 this.filterEl_.selectionStart = 0; | |
| 94 this.filterEl_.selectionEnd = this.filterEl_.value.length; | |
| 95 this.filterEl_.focus(); | |
| 96 }, | |
| 97 | |
| 98 updateHitCountEl_: function() { | |
| 99 if (!this.controller || document.activeElement != this.filterEl_) { | |
| 100 this.hitCountEl_.textContent = ''; | |
| 101 return; | |
| 102 } | |
| 103 var i = this.controller.currentHitIndex; | |
| 104 var n = this.controller.filterHits.length; | |
| 105 if (n == 0) | |
| 106 this.hitCountEl_.textContent = '0 of 0'; | |
| 107 else | |
| 108 this.hitCountEl_.textContent = (i + 1) + ' of ' + n; | |
| 109 } | |
| 110 }; | |
| 111 | |
| 112 function TimelineFindController() { | |
| 113 this.timeline_ = undefined; | |
| 114 this.model_ = undefined; | |
| 115 this.filterText_ = ''; | |
| 116 this.filterHits_ = new tracing.TimelineSelection(); | |
| 117 this.filterHitsDirty_ = true; | |
| 118 this.currentHitIndex_ = 0; | |
| 119 }; | |
| 120 | |
| 121 TimelineFindController.prototype = { | |
| 122 __proto__: Object.prototype, | |
| 123 | |
| 124 get timeline() { | |
| 125 return this.timeline_; | |
| 126 }, | |
| 127 | |
| 128 set timeline(t) { | |
| 129 this.timeline_ = t; | |
| 130 this.filterHitsDirty_ = true; | |
| 131 }, | |
| 132 | |
| 133 get filterText() { | |
| 134 return this.filterText_; | |
| 135 }, | |
| 136 | |
| 137 set filterText(f) { | |
| 138 if (f == this.filterText_) | |
| 139 return; | |
| 140 this.filterText_ = f; | |
| 141 this.filterHitsDirty_ = true; | |
| 142 this.findNext(); | |
| 143 }, | |
| 144 | |
| 145 get filterHits() { | |
| 146 if (this.filterHitsDirty_) { | |
| 147 this.filterHitsDirty_ = false; | |
| 148 if (this.timeline_) { | |
| 149 var filter = new tracing.TimelineFilter(this.filterText); | |
| 150 this.filterHits_.clear(); | |
| 151 this.timeline.addAllObjectsMatchingFilterToSelection( | |
| 152 filter, this.filterHits_); | |
| 153 this.currentHitIndex_ = this.filterHits_.length - 1; | |
| 154 } else { | |
| 155 this.filterHits_.clear(); | |
| 156 this.currentHitIndex_ = 0; | |
| 157 } | |
| 158 } | |
| 159 return this.filterHits_; | |
| 160 }, | |
| 161 | |
| 162 get currentHitIndex() { | |
| 163 return this.currentHitIndex_; | |
| 164 }, | |
| 165 | |
| 166 find_: function(dir) { | |
| 167 if (!this.timeline) | |
| 168 return; | |
| 169 | |
| 170 var N = this.filterHits.length; | |
| 171 this.currentHitIndex_ = this.currentHitIndex_ + dir; | |
| 172 | |
| 173 if (this.currentHitIndex_ < 0) this.currentHitIndex_ = N - 1; | |
| 174 if (this.currentHitIndex_ >= N) this.currentHitIndex_ = 0; | |
| 175 | |
| 176 if (this.currentHitIndex_ < 0 || this.currentHitIndex_ >= N) { | |
| 177 this.timeline.selection = new tracing.TimelineSelection(); | |
| 178 return; | |
| 179 } | |
| 180 | |
| 181 // We allow the zoom level to change on the first hit level. But, when | |
| 182 // then cycling through subsequent changes, restrict it to panning. | |
| 183 var zoomAllowed = this.currentHitIndex_ == 0; | |
| 184 var subSelection = this.filterHits.subSelection(this.currentHitIndex_); | |
| 185 this.timeline.setSelectionAndMakeVisible(subSelection, zoomAllowed); | |
| 186 }, | |
| 187 | |
| 188 findNext: function() { | |
| 189 this.find_(1); | |
| 190 }, | |
| 191 | |
| 192 findPrevious: function() { | |
| 193 this.find_(-1); | |
| 194 }, | |
| 195 }; | |
| 196 | |
| 197 /** | |
| 198 * TimelineView | |
| 199 * @constructor | |
| 200 * @extends {HTMLDivElement} | |
| 201 */ | |
| 202 var TimelineView = cr.ui.define('div'); | |
| 203 | |
| 204 TimelineView.prototype = { | |
| 205 __proto__: HTMLDivElement.prototype, | |
| 206 | |
| 207 decorate: function() { | |
| 208 this.classList.add('timeline-view'); | |
| 209 | |
| 210 // Create individual elements. | |
| 211 this.titleEl_ = document.createElement('div'); | |
| 212 this.titleEl_.textContent = 'Tracing: '; | |
| 213 | |
| 214 this.controlDiv_ = document.createElement('div'); | |
| 215 this.controlDiv_.className = 'control'; | |
| 216 | |
| 217 this.leftControlsEl_ = document.createElement('div'); | |
| 218 this.leftControlsEl_.className = 'controls'; | |
| 219 this.rightControlsEl_ = document.createElement('div'); | |
| 220 this.rightControlsEl_.className = 'controls'; | |
| 221 | |
| 222 var spacingEl = document.createElement('div'); | |
| 223 spacingEl.className = 'spacer'; | |
| 224 | |
| 225 this.timelineContainer_ = document.createElement('div'); | |
| 226 this.timelineContainer_.className = 'timeline-container'; | |
| 227 | |
| 228 var analysisContainer_ = document.createElement('div'); | |
| 229 analysisContainer_.className = 'analysis-container'; | |
| 230 | |
| 231 this.analysisEl_ = new tracing.TimelineAnalysisView(); | |
| 232 | |
| 233 this.findCtl_ = new TimelineFindControl(); | |
| 234 this.findCtl_.controller = new TimelineFindController(); | |
| 235 | |
| 236 // Connect everything up. | |
| 237 this.rightControls.appendChild(this.findCtl_); | |
| 238 this.controlDiv_.appendChild(this.titleEl_); | |
| 239 this.controlDiv_.appendChild(this.leftControlsEl_); | |
| 240 this.controlDiv_.appendChild(spacingEl); | |
| 241 this.controlDiv_.appendChild(this.rightControlsEl_); | |
| 242 this.appendChild(this.controlDiv_); | |
| 243 | |
| 244 this.appendChild(this.timelineContainer_); | |
| 245 | |
| 246 analysisContainer_.appendChild(this.analysisEl_); | |
| 247 this.appendChild(analysisContainer_); | |
| 248 | |
| 249 this.rightControls.appendChild(this.createHelpButton_()); | |
| 250 | |
| 251 // Bookkeeping. | |
| 252 this.onSelectionChangedBoundToThis_ = this.onSelectionChanged_.bind(this); | |
| 253 document.addEventListener('keypress', this.onKeypress_.bind(this), true); | |
| 254 }, | |
| 255 | |
| 256 createHelpButton_: function() { | |
| 257 var dlg = new tracing.Overlay(); | |
| 258 dlg.classList.add('timeline-view-help-overlay'); | |
| 259 | |
| 260 var showHelpEl = document.createElement('div'); | |
| 261 showHelpEl.className = 'timeline-button timeline-view-help-button'; | |
| 262 showHelpEl.textContent = '?'; | |
| 263 | |
| 264 var helpTextEl = document.createElement('div'); | |
| 265 helpTextEl.style.whiteSpace = 'pre'; | |
| 266 helpTextEl.style.fontFamily = 'monospace'; | |
| 267 | |
| 268 function onClick() { | |
| 269 dlg.visible = true; | |
| 270 helpTextEl.textContent = this.timeline_.keyHelp; | |
| 271 document.addEventListener('keydown', onKey, true); | |
| 272 } | |
| 273 | |
| 274 function onKey(e) { | |
| 275 if (!dlg.visible) | |
| 276 return; | |
| 277 | |
| 278 if (e.keyCode == 27 || e.keyCode == '?'.charCodeAt(0)) { | |
| 279 e.preventDefault(); | |
| 280 document.removeEventListener('keydown', onKey); | |
| 281 dlg.visible = false; | |
| 282 } | |
| 283 } | |
| 284 showHelpEl.addEventListener('click', onClick.bind(this)); | |
| 285 | |
| 286 dlg.appendChild(helpTextEl); | |
| 287 | |
| 288 return showHelpEl; | |
| 289 }, | |
| 290 | |
| 291 get leftControls() { | |
| 292 return this.leftControlsEl_; | |
| 293 }, | |
| 294 | |
| 295 get rightControls() { | |
| 296 return this.rightControlsEl_; | |
| 297 }, | |
| 298 | |
| 299 get title() { | |
| 300 return this.titleEl_.textContent.substring( | |
| 301 this.titleEl_.textContent.length - 2); | |
| 302 }, | |
| 303 | |
| 304 set title(text) { | |
| 305 this.titleEl_.textContent = text + ':'; | |
| 306 }, | |
| 307 | |
| 308 set traceData(traceData) { | |
| 309 this.model = new tracing.TimelineModel(traceData); | |
| 310 }, | |
| 311 | |
| 312 get model() { | |
| 313 return this.timelineModel_; | |
| 314 }, | |
| 315 | |
| 316 set model(model) { | |
| 317 this.timelineModel_ = model; | |
| 318 | |
| 319 // remove old timeline | |
| 320 this.timelineContainer_.textContent = ''; | |
| 321 | |
| 322 // create new timeline if needed | |
| 323 if (this.timelineModel_.minTimestamp !== undefined) { | |
| 324 if (this.timeline_) | |
| 325 this.timeline_.detach(); | |
| 326 this.timeline_ = new tracing.Timeline(); | |
| 327 this.timeline_.model = this.timelineModel_; | |
| 328 this.timeline_.focusElement = | |
| 329 this.focusElement_ ? this.focusElement_ : this.parentElement; | |
| 330 this.timelineContainer_.appendChild(this.timeline_); | |
| 331 this.timeline_.addEventListener('selectionChange', | |
| 332 this.onSelectionChangedBoundToThis_); | |
| 333 | |
| 334 this.findCtl_.controller.timeline = this.timeline_; | |
| 335 this.onSelectionChanged_(); | |
| 336 } else { | |
| 337 this.timeline_ = undefined; | |
| 338 this.findCtl_.controller.timeline = undefined; | |
| 339 } | |
| 340 }, | |
| 341 | |
| 342 get timeline() { | |
| 343 return this.timeline_; | |
| 344 }, | |
| 345 | |
| 346 /** | |
| 347 * Sets the element whose focus state will determine whether | |
| 348 * to respond to keybaord input. | |
| 349 */ | |
| 350 set focusElement(value) { | |
| 351 this.focusElement_ = value; | |
| 352 if (this.timeline_) | |
| 353 this.timeline_.focusElement = value; | |
| 354 }, | |
| 355 | |
| 356 /** | |
| 357 * @return {Element} The element whose focused state determines | |
| 358 * whether to respond to keyboard inputs. | |
| 359 * Defaults to the parent element. | |
| 360 */ | |
| 361 get focusElement() { | |
| 362 if (this.focusElement_) | |
| 363 return this.focusElement_; | |
| 364 return this.parentElement; | |
| 365 }, | |
| 366 | |
| 367 /** | |
| 368 * @return {boolean} Whether the current timeline is attached to the | |
| 369 * document. | |
| 370 */ | |
| 371 get isAttachedToDocument_() { | |
| 372 var cur = this; | |
| 373 while (cur.parentNode) | |
| 374 cur = cur.parentNode; | |
| 375 return cur == this.ownerDocument; | |
| 376 }, | |
| 377 | |
| 378 get listenToKeys_() { | |
| 379 if (!this.isAttachedToDocument_) | |
| 380 return; | |
| 381 if (!this.focusElement_) | |
| 382 return true; | |
| 383 if (this.focusElement.tabIndex >= 0) | |
| 384 return document.activeElement == this.focusElement; | |
| 385 return true; | |
| 386 }, | |
| 387 | |
| 388 onKeypress_: function(e) { | |
| 389 if (!this.listenToKeys_) | |
| 390 return; | |
| 391 | |
| 392 if (event.keyCode == '/'.charCodeAt(0)) { // / key | |
| 393 this.findCtl_.focus(); | |
| 394 event.preventDefault(); | |
| 395 return; | |
| 396 } else if (e.keyCode == '?'.charCodeAt(0)) { | |
| 397 this.querySelector('.timeline-view-help-button').click(); | |
| 398 e.preventDefault(); | |
| 399 } | |
| 400 }, | |
| 401 | |
| 402 beginFind: function() { | |
| 403 if (this.findInProgress_) | |
| 404 return; | |
| 405 this.findInProgress_ = true; | |
| 406 var dlg = TimelineFindControl(); | |
| 407 dlg.controller = new TimelineFindController(); | |
| 408 dlg.controller.timeline = this.timeline; | |
| 409 dlg.visible = true; | |
| 410 dlg.addEventListener('close', function() { | |
| 411 this.findInProgress_ = false; | |
| 412 }.bind(this)); | |
| 413 dlg.addEventListener('findNext', function() { | |
| 414 }); | |
| 415 dlg.addEventListener('findPrevious', function() { | |
| 416 }); | |
| 417 }, | |
| 418 | |
| 419 onSelectionChanged_: function(e) { | |
| 420 var oldScrollTop = this.timelineContainer_.scrollTop; | |
| 421 this.analysisEl_.selection = this.timeline_.selection; | |
| 422 this.timelineContainer_.scrollTop = oldScrollTop; | |
| 423 } | |
| 424 }; | |
| 425 | |
| 426 return { | |
| 427 TimelineFindControl: TimelineFindControl, | |
| 428 TimelineFindController: TimelineFindController, | |
| 429 TimelineView: TimelineView | |
| 430 }; | |
| 431 }); | |
| OLD | NEW |