profiling_view.html revision edfe2194ee8a857cc1e78b4e4020f9b5e7210029
1<!DOCTYPE html> 2<!-- 3Copyright (c) 2013 The Chromium Authors. All rights reserved. 4Use of this source code is governed by a BSD-style license that can be 5found in the LICENSE file. 6--> 7 8<link rel="import" href="/tracing/base/base64.html"> 9<link rel="import" 10 href="/tracing/ui/extras/about_tracing/record_and_capture_controller.html"> 11<link rel="import" 12 href="/tracing/ui/extras/about_tracing/inspector_tracing_controller_client.html"> 13<link rel="import" 14 href="/tracing/ui/extras/about_tracing/xhr_based_tracing_controller_client.html"> 15<link rel="import" href="/tracing/importer/import.html"> 16<link rel="import" href="/tracing/ui/base/info_bar_group.html"> 17<link rel="import" href="/tracing/ui/base/hotkey_controller.html"> 18<link rel="import" href="/tracing/ui/base/overlay.html"> 19<link rel="import" href="/tracing/ui/base/utils.html"> 20<link rel="import" href="/tracing/ui/timeline_view.html"> 21 22<style> 23x-profiling-view { 24 -webkit-flex-direction: column; 25 display: -webkit-flex; 26 padding: 0; 27} 28 29x-profiling-view .controls #save-button { 30 margin-left: 64px !important; 31} 32 33x-profiling-view .controls #upload-button { 34 display: none; 35} 36 37x-profiling-view > tr-ui-timeline-view { 38 -webkit-flex: 1 1 auto; 39 min-height: 0; 40} 41 42.report-id-message { 43 -webkit-user-select: text; 44} 45 46x-timeline-view-buttons, 47x-timeline-view-buttons > #monitoring-elements { 48 display: flex; 49 align-items: center; 50} 51</style> 52 53<template id="profiling-view-template"> 54 <tr-ui-b-info-bar-group></tr-ui-b-info-bar-group> 55 <x-timeline-view-buttons> 56 <button id="record-button">Record</button> 57 <span id="monitoring-elements"> 58 <input id="monitor-checkbox" type="checkbox"> 59 <label for="monitor-checkbox">Monitoring</label></input> 60 <button id="capture-button">Capture Monitoring Snapshot</button> 61 </span> 62 <button id="save-button">Save</button> 63 <button id="upload-button">Upload</button> 64 <button id="load-button">Load</button> 65 </x-timeline-view-buttons> 66 <tr-ui-timeline-view> 67 <track-view-container id='track_view_container'></track-view-container> 68 </tr-ui-timeline-view> 69</template> 70 71<script> 72'use strict'; 73 74/** 75 * @fileoverview ProfilingView glues the View control to 76 * TracingController. 77 */ 78tr.exportTo('tr.ui.e.about_tracing', function() { 79 function readFile(file) { 80 return new Promise(function(resolve, reject) { 81 var reader = new FileReader(); 82 var filename = file.name; 83 reader.onload = function(data) { 84 resolve(data.target.result); 85 }; 86 reader.onerror = function(err) { 87 reject(err); 88 } 89 90 var is_binary = /[.]gz$/.test(filename) || /[.]zip$/.test(filename); 91 if (is_binary) 92 reader.readAsArrayBuffer(file); 93 else 94 reader.readAsText(file); 95 }); 96 } 97 98 /** 99 * ProfilingView 100 * @constructor 101 * @extends {HTMLUnknownElement} 102 */ 103 var ProfilingView = tr.ui.b.define('x-profiling-view'); 104 var THIS_DOC = document.currentScript.ownerDocument; 105 var REPORT_UPLOAD_URL = 'http://crash-staging/'; 106 107 ProfilingView.prototype = { 108 __proto__: HTMLUnknownElement.prototype, 109 110 decorate: function(tracingControllerClient) { 111 this.appendChild(tr.ui.b.instantiateTemplate('#profiling-view-template', 112 THIS_DOC)); 113 114 this.timelineView_ = this.querySelector('tr-ui-timeline-view'); 115 this.infoBarGroup_ = this.querySelector('tr-ui-b-info-bar-group'); 116 117 // Detach the buttons. We will reattach them to the timeline view. 118 // TODO(nduca): Make timeline-view have a content select="x-buttons" 119 // that pulls in any buttons. 120 this.monitoringElements_ = this.querySelector('#monitoring-elements'); 121 this.monitorCheckbox_ = this.querySelector('#monitor-checkbox'); 122 this.captureButton_ = this.querySelector('#capture-button'); 123 this.recordButton_ = this.querySelector('#record-button'); 124 this.loadButton_ = this.querySelector('#load-button'); 125 this.saveButton_ = this.querySelector('#save-button'); 126 this.uploadButton_ = this.querySelector('#upload-button'); 127 128 var buttons = this.querySelector('x-timeline-view-buttons'); 129 buttons.parentElement.removeChild(buttons); 130 this.timelineView_.leftControls.appendChild(buttons); 131 this.initButtons_(); 132 133 this.timelineView_.hotkeyController.addHotKey(new tr.ui.b.HotKey({ 134 eventType: 'keypress', 135 keyCode: 'r'.charCodeAt(0), 136 callback: function(e) { 137 this.beginRecording(); 138 event.stopPropagation(); 139 }, 140 thisArg: this 141 })); 142 143 this.initDragAndDrop_(); 144 145 if (tracingControllerClient) { 146 this.tracingControllerClient_ = tracingControllerClient; 147 } else if (window.DevToolsHost !== undefined) { 148 this.tracingControllerClient_ = 149 new tr.ui.e.about_tracing.InspectorTracingControllerClient(); 150 } else { 151 this.tracingControllerClient_ = 152 new tr.ui.e.about_tracing.XhrBasedTracingControllerClient(); 153 } 154 155 this.isRecording_ = false; 156 this.isMonitoring_ = false; 157 this.activeTrace_ = undefined; 158 159 window.onMonitoringStateChanged = function(is_monitoring) { 160 this.onMonitoringStateChanged_(is_monitoring); 161 }.bind(this); 162 163 window.onUploadError = function(error_message) { 164 this.setUploadOverlayText_(['Trace upload failed: ' + error_message]); 165 }.bind(this); 166 window.onUploadProgress = function(percent, currentAsString, 167 totalAsString) { 168 this.setUploadOverlayText_( 169 ['Upload progress: ' + percent + '% (' + currentAsString + ' of ' + 170 currentAsString + ' bytes)']); 171 }.bind(this); 172 window.onUploadComplete = function(reportId) { 173 var messageDiv = document.createElement('div'); 174 var textNode = document.createTextNode( 175 'Trace uploaded successfully. Report id: '); 176 messageDiv.appendChild(textNode); 177 var reportLink = document.createElement('a'); 178 messageDiv.appendChild(reportLink); 179 reportLink.href = REPORT_UPLOAD_URL + reportId; 180 reportLink.text = reportId; 181 reportLink.className = 'report-id-message'; 182 reportLink.target = '_blank'; 183 this.setUploadOverlayContent_(messageDiv); 184 }.bind(this); 185 186 this.getMonitoringStatus(); 187 this.updateTracingControllerSpecificState_(); 188 }, 189 190 // Detach all document event listeners. Without this the tests can get 191 // confused as the element may still be listening when the next test runs. 192 detach_: function() { 193 this.detachDragAndDrop_(); 194 }, 195 196 get isRecording() { 197 return this.isRecording_; 198 }, 199 200 get isMonitoring() { 201 return this.isMonitoring_; 202 }, 203 204 set tracingControllerClient(tracingControllerClient) { 205 this.tracingControllerClient_ = tracingControllerClient; 206 this.updateTracingControllerSpecificState_(); 207 }, 208 209 updateTracingControllerSpecificState_: function() { 210 var isInspector = this.tracingControllerClient_ instanceof 211 tr.ui.e.about_tracing.InspectorTracingControllerClient; 212 213 if (isInspector) { 214 this.infoBarGroup_.addMessage( 215 'This about:tracing is connected to a remote device...', 216 [{buttonText: 'Wow!', onClick: function() {}}]); 217 } 218 219 this.monitoringElements_.style.display = isInspector ? 'none' : ''; 220 }, 221 222 beginRecording: function() { 223 if (this.isRecording_) 224 throw new Error('Already recording'); 225 if (this.isMonitoring_) 226 throw new Error('Already monitoring'); 227 this.isRecording_ = true; 228 this.monitorCheckbox_.disabled = true; 229 this.monitorCheckbox_.checked = false; 230 var resultPromise = tr.ui.e.about_tracing.beginRecording( 231 this.tracingControllerClient_); 232 resultPromise.then( 233 function(data) { 234 this.isRecording_ = false; 235 this.monitorCheckbox_.disabled = false; 236 var traceName = tr.ui.e.about_tracing.defaultTraceName( 237 this.tracingControllerClient_); 238 this.setActiveTrace(traceName, data, false); 239 }.bind(this), 240 function(err) { 241 this.isRecording_ = false; 242 this.monitorCheckbox_.disabled = false; 243 if (err instanceof tr.ui.e.about_tracing.UserCancelledError) 244 return; 245 tr.ui.b.Overlay.showError('Error while recording', err); 246 }.bind(this)); 247 return resultPromise; 248 }, 249 250 beginMonitoring: function() { 251 if (this.isRecording_) 252 throw new Error('Already recording'); 253 if (this.isMonitoring_) 254 throw new Error('Already monitoring'); 255 var resultPromise = 256 tr.ui.e.about_tracing.beginMonitoring(this.tracingControllerClient_); 257 resultPromise.then( 258 function() { 259 }.bind(this), 260 function(err) { 261 if (err instanceof tr.ui.e.about_tracing.UserCancelledError) 262 return; 263 tr.ui.b.Overlay.showError('Error while monitoring', err); 264 }.bind(this)); 265 return resultPromise; 266 }, 267 268 endMonitoring: function() { 269 if (this.isRecording_) 270 throw new Error('Already recording'); 271 if (!this.isMonitoring_) 272 throw new Error('Monitoring is disabled'); 273 var resultPromise = 274 tr.ui.e.about_tracing.endMonitoring(this.tracingControllerClient_); 275 resultPromise.then( 276 function() { 277 }.bind(this), 278 function(err) { 279 if (err instanceof tr.ui.e.about_tracing.UserCancelledError) 280 return; 281 tr.ui.b.Overlay.showError('Error while monitoring', err); 282 }.bind(this)); 283 return resultPromise; 284 }, 285 286 captureMonitoring: function() { 287 if (!this.isMonitoring_) 288 throw new Error('Monitoring is disabled'); 289 var resultPromise = 290 tr.ui.e.about_tracing.captureMonitoring( 291 this.tracingControllerClient_); 292 resultPromise.then( 293 function(data) { 294 var traceName = tr.ui.e.about_tracing.defaultTraceName( 295 this.tracingControllerClient_); 296 this.setActiveTrace(traceName, data, true); 297 }.bind(this), 298 function(err) { 299 if (err instanceof tr.ui.e.about_tracing.UserCancelledError) 300 return; 301 tr.ui.b.Overlay.showError('Error while monitoring', err); 302 }.bind(this)); 303 return resultPromise; 304 }, 305 306 getMonitoringStatus: function() { 307 var resultPromise = 308 tr.ui.e.about_tracing.getMonitoringStatus( 309 this.tracingControllerClient_); 310 resultPromise.then( 311 function(status) { 312 this.onMonitoringStateChanged_(status.isMonitoring); 313 }.bind(this), 314 function(err) { 315 if (err instanceof tr.ui.e.about_tracing.UserCancelledError) 316 return; 317 tr.ui.b.Overlay.showError('Error while updating tracing states', 318 err); 319 }.bind(this)); 320 return resultPromise; 321 }, 322 323 onMonitoringStateChanged_: function(is_monitoring) { 324 this.isMonitoring_ = is_monitoring; 325 this.recordButton_.disabled = is_monitoring; 326 this.captureButton_.disabled = !is_monitoring; 327 this.monitorCheckbox_.checked = is_monitoring; 328 }, 329 330 get timelineView() { 331 return this.timelineView_; 332 }, 333 334 /////////////////////////////////////////////////////////////////////////// 335 336 clearActiveTrace: function() { 337 this.saveButton_.disabled = true; 338 this.uploadButton_.disabled = true; 339 this.activeTrace_ = undefined; 340 }, 341 342 setActiveTrace: function(filename, data) { 343 this.activeTrace_ = { 344 filename: filename, 345 data: data 346 }; 347 348 this.infoBarGroup_.clearMessages(); 349 this.updateTracingControllerSpecificState_(); 350 this.saveButton_.disabled = false; 351 this.uploadButton_.disabled = false; 352 this.timelineView_.viewTitle = filename; 353 354 var m = new tr.Model(); 355 var i = new tr.importer.Import(m); 356 var p = i.importTracesWithProgressDialog([data]); 357 p.then( 358 function() { 359 this.timelineView_.model = m; 360 this.timelineView_.updateDocumentFavicon(); 361 }.bind(this), 362 function(err) { 363 tr.ui.b.Overlay.showError('While importing: ', err); 364 }.bind(this)); 365 }, 366 367 /////////////////////////////////////////////////////////////////////////// 368 369 initButtons_: function() { 370 this.recordButton_.addEventListener( 371 'click', function(event) { 372 event.stopPropagation(); 373 this.beginRecording(); 374 }.bind(this)); 375 376 this.monitorCheckbox_.addEventListener( 377 'click', function(event) { 378 event.stopPropagation(); 379 if (this.isMonitoring_) 380 this.endMonitoring(); 381 else 382 this.beginMonitoring(); 383 }.bind(this)); 384 385 this.captureButton_.addEventListener( 386 'click', function(event) { 387 event.stopPropagation(); 388 this.captureMonitoring(); 389 }.bind(this)); 390 this.captureButton_.disabled = true; 391 392 this.loadButton_.addEventListener( 393 'click', function(event) { 394 event.stopPropagation(); 395 this.onLoadClicked_(); 396 }.bind(this)); 397 398 this.saveButton_.addEventListener('click', 399 this.onSaveClicked_.bind(this)); 400 this.saveButton_.disabled = true; 401 402 this.uploadButton_.addEventListener('click', 403 this.onUploadClicked_.bind(this)); 404 if (typeof(chrome.send) === 'function') { 405 this.uploadButton_.style.display = 'inline-block'; 406 } 407 this.uploadButton_.disabled = true; 408 this.uploadOverlay_ = null; 409 }, 410 411 requestFilename_: function() { 412 413 // unsafe filename patterns: 414 var illegalRe = /[\/\?<>\\:\*\|":]/g; 415 var controlRe = /[\x00-\x1f\x80-\x9f]/g; 416 var reservedRe = /^\.+$/; 417 418 var filename; 419 var defaultName = this.activeTrace_.filename; 420 var fileExtension = '.json'; 421 var fileRegex = /\.json$/; 422 if (/[.]gz$/.test(defaultName)) { 423 fileExtension += '.gz'; 424 fileRegex = /\.json\.gz$/; 425 } else if (/[.]zip$/.test(defaultName)) { 426 fileExtension = '.zip'; 427 fileRegex = /\.zip$/; 428 } 429 430 var custom = prompt('Filename? (' + fileExtension + 431 ' appended) Or leave blank:'); 432 if (custom === null) 433 return undefined; 434 435 var name; 436 if (custom) { 437 name = ' ' + custom; 438 } else { 439 var date = new Date(); 440 var dateText = ' ' + date.toDateString() + 441 ' ' + date.toLocaleTimeString(); 442 name = dateText; 443 } 444 445 filename = defaultName.replace(fileRegex, name) + fileExtension; 446 447 return filename 448 .replace(illegalRe, '.') 449 .replace(controlRe, '\u2022') 450 .replace(reservedRe, '') 451 .replace(/\s+/g, '_'); 452 }, 453 454 onSaveClicked_: function() { 455 // Create a blob URL from the binary array. 456 var blob = new Blob([this.activeTrace_.data], 457 {type: 'application/octet-binary'}); 458 var blobUrl = window.webkitURL.createObjectURL(blob); 459 460 // Create a link and click on it. BEST API EVAR! 461 var link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a'); 462 link.href = blobUrl; 463 var filename = this.requestFilename_(); 464 if (filename) { 465 link.download = filename; 466 link.click(); 467 } 468 }, 469 470 onUploadClicked_: function() { 471 if (this.uploadOverlay_) { 472 throw new Error('Already uploading'); 473 } 474 this.initUploadStatusOverlay_(); 475 }, 476 477 initUploadStatusOverlay_: function() { 478 this.uploadOverlay_ = tr.ui.b.Overlay(); 479 this.uploadOverlay_.title = 'Uploading trace...'; 480 this.uploadOverlay_.userCanClose = false; 481 this.uploadOverlay_.visible = true; 482 483 this.setUploadOverlayText_([ 484 'You are about to upload trace data to Google server.', 485 'Would you like to proceed?' 486 ]); 487 var okButton = document.createElement('button'); 488 okButton.textContent = 'Ok'; 489 okButton.addEventListener('click', this.doTraceUpload_.bind(this)); 490 this.uploadOverlay_.buttons.appendChild(okButton); 491 492 var cancelButton = document.createElement('button'); 493 cancelButton.textContent = 'Cancel'; 494 cancelButton.addEventListener('click', 495 this.hideUploadOverlay_.bind(this)); 496 this.uploadOverlay_.buttons.appendChild(cancelButton); 497 }, 498 499 setUploadOverlayContent_: function(content) { 500 if (!this.uploadOverlay_) 501 throw new Error('Not uploading'); 502 503 this.uploadOverlay_.textContent = ''; 504 this.uploadOverlay_.appendChild(content); 505 }, 506 507 setUploadOverlayText_: function(messages) { 508 var contentDiv = document.createElement('div'); 509 510 for (var i = 0; i < messages.length; ++i) { 511 var messageDiv = document.createElement('div'); 512 messageDiv.textContent = messages[i]; 513 contentDiv.appendChild(messageDiv); 514 } 515 this.setUploadOverlayContent_(contentDiv); 516 }, 517 518 doTraceUpload_: function() { 519 this.setUploadOverlayText_(['Uploading trace data...']); 520 this.uploadOverlay_.buttons.removeChild( 521 this.uploadOverlay_.buttons.firstChild); 522 this.uploadOverlay_.buttons.firstChild.textContent = 'Close'; 523 524 var filename = this.activeTrace_.filename; 525 var isBinary = /[.]gz$/.test(filename) || /[.]zip$/.test(filename); 526 if (isBinary) { 527 var data_base64 = tr.b.Base64.EncodeArrayBufferToString( 528 this.activeTrace_.data); 529 chrome.send('doUploadBase64', [data_base64]); 530 } else { 531 chrome.send('doUpload', [this.activeTrace_.data]); 532 } 533 }, 534 535 hideUploadOverlay_: function() { 536 if (!this.uploadOverlay_) 537 throw new Error('Not uploading'); 538 539 this.uploadOverlay_.visible = false; 540 this.uploadOverlay_ = null; 541 }, 542 543 onLoadClicked_: function() { 544 var inputElement = document.createElement('input'); 545 inputElement.type = 'file'; 546 inputElement.multiple = false; 547 548 var changeFired = false; 549 inputElement.addEventListener( 550 'change', 551 function(e) { 552 if (changeFired) 553 return; 554 changeFired = true; 555 556 var file = inputElement.files[0]; 557 readFile(file).then( 558 function(data) { 559 this.setActiveTrace(file.name, data); 560 }.bind(this), 561 function(err) { 562 tr.ui.b.Overlay.showError('Error while loading file: ' + err); 563 }); 564 }.bind(this), false); 565 inputElement.click(); 566 }, 567 568 /////////////////////////////////////////////////////////////////////////// 569 570 initDragAndDrop_: function() { 571 this.dropHandler_ = this.dropHandler_.bind(this); 572 this.ignoreDragEvent_ = this.ignoreDragEvent_.bind(this); 573 document.addEventListener('dragstart', this.ignoreDragEvent_, false); 574 document.addEventListener('dragend', this.ignoreDragEvent_, false); 575 document.addEventListener('dragenter', this.ignoreDragEvent_, false); 576 document.addEventListener('dragleave', this.ignoreDragEvent_, false); 577 document.addEventListener('dragover', this.ignoreDragEvent_, false); 578 document.addEventListener('drop', this.dropHandler_, false); 579 }, 580 581 detachDragAndDrop_: function() { 582 document.removeEventListener('dragstart', this.ignoreDragEvent_); 583 document.removeEventListener('dragend', this.ignoreDragEvent_); 584 document.removeEventListener('dragenter', this.ignoreDragEvent_); 585 document.removeEventListener('dragleave', this.ignoreDragEvent_); 586 document.removeEventListener('dragover', this.ignoreDragEvent_); 587 document.removeEventListener('drop', this.dropHandler_); 588 }, 589 590 ignoreDragEvent_: function(e) { 591 e.preventDefault(); 592 return false; 593 }, 594 595 dropHandler_: function(e) { 596 if (this.isAnyDialogUp_) 597 return; 598 599 e.stopPropagation(); 600 e.preventDefault(); 601 602 var files = e.dataTransfer.files; 603 if (files.length !== 1) { 604 tr.ui.b.Overlay.showError('1 file supported at a time.'); 605 return; 606 } 607 608 readFile(files[0]).then( 609 function(data) { 610 this.setActiveTrace(files[0].name, data); 611 }.bind(this), 612 function(err) { 613 tr.ui.b.Overlay.showError('Error while loading file: ' + err); 614 }); 615 return false; 616 } 617 }; 618 619 return { 620 ProfilingView: ProfilingView 621 }; 622}); 623</script> 624