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_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 > tr-ui-timeline-view { 34 -webkit-flex: 1 1 auto; 35 min-height: 0; 36} 37 38.report-id-message { 39 -webkit-user-select: text; 40} 41 42x-timeline-view-buttons { 43 display: flex; 44 align-items: center; 45} 46</style> 47 48<template id="profiling-view-template"> 49 <tr-ui-b-info-bar-group></tr-ui-b-info-bar-group> 50 <x-timeline-view-buttons> 51 <button id="record-button">Record</button> 52 <button id="save-button">Save</button> 53 <button id="load-button">Load</button> 54 </x-timeline-view-buttons> 55 <tr-ui-timeline-view> 56 <track-view-container id='track_view_container'></track-view-container> 57 </tr-ui-timeline-view> 58</template> 59 60<script> 61'use strict'; 62 63/** 64 * @fileoverview ProfilingView glues the View control to 65 * TracingController. 66 */ 67tr.exportTo('tr.ui.e.about_tracing', function() { 68 function readFile(file) { 69 return new Promise(function(resolve, reject) { 70 var reader = new FileReader(); 71 var filename = file.name; 72 reader.onload = function(data) { 73 resolve(data.target.result); 74 }; 75 reader.onerror = function(err) { 76 reject(err); 77 }; 78 79 var is_binary = /[.]gz$/.test(filename) || /[.]zip$/.test(filename); 80 if (is_binary) 81 reader.readAsArrayBuffer(file); 82 else 83 reader.readAsText(file); 84 }); 85 } 86 87 /** 88 * ProfilingView 89 * @constructor 90 * @extends {HTMLUnknownElement} 91 */ 92 var ProfilingView = tr.ui.b.define('x-profiling-view'); 93 var THIS_DOC = document.currentScript.ownerDocument; 94 95 ProfilingView.prototype = { 96 __proto__: HTMLUnknownElement.prototype, 97 98 decorate: function(tracingControllerClient) { 99 this.appendChild(tr.ui.b.instantiateTemplate('#profiling-view-template', 100 THIS_DOC)); 101 102 this.timelineView_ = this.querySelector('tr-ui-timeline-view'); 103 this.infoBarGroup_ = this.querySelector('tr-ui-b-info-bar-group'); 104 105 // Detach the buttons. We will reattach them to the timeline view. 106 // TODO(nduca): Make timeline-view have a content select="x-buttons" 107 // that pulls in any buttons. 108 this.recordButton_ = this.querySelector('#record-button'); 109 this.loadButton_ = this.querySelector('#load-button'); 110 this.saveButton_ = this.querySelector('#save-button'); 111 112 var buttons = this.querySelector('x-timeline-view-buttons'); 113 buttons.parentElement.removeChild(buttons); 114 this.timelineView_.leftControls.appendChild(buttons); 115 this.initButtons_(); 116 117 this.timelineView_.hotkeyController.addHotKey(new tr.ui.b.HotKey({ 118 eventType: 'keypress', 119 keyCode: 'r'.charCodeAt(0), 120 callback: function(e) { 121 this.beginRecording(); 122 event.stopPropagation(); 123 }, 124 thisArg: this 125 })); 126 127 this.initDragAndDrop_(); 128 129 if (tracingControllerClient) { 130 this.tracingControllerClient_ = tracingControllerClient; 131 } else if (window.DevToolsHost !== undefined) { 132 this.tracingControllerClient_ = 133 new tr.ui.e.about_tracing.InspectorTracingControllerClient(); 134 } else { 135 this.tracingControllerClient_ = 136 new tr.ui.e.about_tracing.XhrBasedTracingControllerClient(); 137 } 138 139 this.isRecording_ = false; 140 this.activeTrace_ = undefined; 141 142 this.updateTracingControllerSpecificState_(); 143 }, 144 145 // Detach all document event listeners. Without this the tests can get 146 // confused as the element may still be listening when the next test runs. 147 detach_: function() { 148 this.detachDragAndDrop_(); 149 }, 150 151 get isRecording() { 152 return this.isRecording_; 153 }, 154 155 set tracingControllerClient(tracingControllerClient) { 156 this.tracingControllerClient_ = tracingControllerClient; 157 this.updateTracingControllerSpecificState_(); 158 }, 159 160 updateTracingControllerSpecificState_: function() { 161 var isInspector = this.tracingControllerClient_ instanceof 162 tr.ui.e.about_tracing.InspectorTracingControllerClient; 163 164 if (isInspector) { 165 this.infoBarGroup_.addMessage( 166 'This about:tracing is connected to a remote device...', 167 [{buttonText: 'Wow!', onClick: function() {}}]); 168 } 169 }, 170 171 beginRecording: function() { 172 if (this.isRecording_) 173 throw new Error('Already recording'); 174 this.isRecording_ = true; 175 var resultPromise = tr.ui.e.about_tracing.beginRecording( 176 this.tracingControllerClient_); 177 resultPromise.then( 178 function(data) { 179 this.isRecording_ = false; 180 var traceName = tr.ui.e.about_tracing.defaultTraceName( 181 this.tracingControllerClient_); 182 this.setActiveTrace(traceName, data, false); 183 }.bind(this), 184 function(err) { 185 this.isRecording_ = false; 186 if (err instanceof tr.ui.e.about_tracing.UserCancelledError) 187 return; 188 tr.ui.b.Overlay.showError('Error while recording', err); 189 }.bind(this)); 190 return resultPromise; 191 }, 192 193 get timelineView() { 194 return this.timelineView_; 195 }, 196 197 /////////////////////////////////////////////////////////////////////////// 198 199 clearActiveTrace: function() { 200 this.saveButton_.disabled = true; 201 this.activeTrace_ = undefined; 202 }, 203 204 setActiveTrace: function(filename, data) { 205 this.activeTrace_ = { 206 filename: filename, 207 data: data 208 }; 209 210 this.infoBarGroup_.clearMessages(); 211 this.updateTracingControllerSpecificState_(); 212 this.saveButton_.disabled = false; 213 this.timelineView_.viewTitle = filename; 214 215 var m = new tr.Model(); 216 var i = new tr.importer.Import(m); 217 var p = i.importTracesWithProgressDialog([data]); 218 p.then( 219 function() { 220 this.timelineView_.model = m; 221 this.timelineView_.updateDocumentFavicon(); 222 }.bind(this), 223 function(err) { 224 tr.ui.b.Overlay.showError('While importing: ', err); 225 }.bind(this)); 226 }, 227 228 /////////////////////////////////////////////////////////////////////////// 229 230 initButtons_: function() { 231 this.recordButton_.addEventListener( 232 'click', function(event) { 233 event.stopPropagation(); 234 this.beginRecording(); 235 }.bind(this)); 236 237 this.loadButton_.addEventListener( 238 'click', function(event) { 239 event.stopPropagation(); 240 this.onLoadClicked_(); 241 }.bind(this)); 242 243 this.saveButton_.addEventListener('click', 244 this.onSaveClicked_.bind(this)); 245 this.saveButton_.disabled = true; 246 }, 247 248 requestFilename_: function() { 249 250 // unsafe filename patterns: 251 var illegalRe = /[\/\?<>\\:\*\|":]/g; 252 var controlRe = /[\x00-\x1f\x80-\x9f]/g; 253 var reservedRe = /^\.+$/; 254 255 var filename; 256 var defaultName = this.activeTrace_.filename; 257 var fileExtension = '.json'; 258 var fileRegex = /\.json$/; 259 if (/[.]gz$/.test(defaultName)) { 260 fileExtension += '.gz'; 261 fileRegex = /\.json\.gz$/; 262 } else if (/[.]zip$/.test(defaultName)) { 263 fileExtension = '.zip'; 264 fileRegex = /\.zip$/; 265 } 266 267 var custom = prompt('Filename? (' + fileExtension + 268 ' appended) Or leave blank:'); 269 if (custom === null) 270 return undefined; 271 272 var name; 273 if (custom) { 274 name = ' ' + custom; 275 } else { 276 var date = new Date(); 277 var dateText = ' ' + date.toDateString() + 278 ' ' + date.toLocaleTimeString(); 279 name = dateText; 280 } 281 282 filename = defaultName.replace(fileRegex, name) + fileExtension; 283 284 return filename 285 .replace(illegalRe, '.') 286 .replace(controlRe, '\u2022') 287 .replace(reservedRe, '') 288 .replace(/\s+/g, '_'); 289 }, 290 291 onSaveClicked_: function() { 292 // Create a blob URL from the binary array. 293 var blob = new Blob([this.activeTrace_.data], 294 {type: 'application/octet-binary'}); 295 var blobUrl = window.webkitURL.createObjectURL(blob); 296 297 // Create a link and click on it. BEST API EVAR! 298 var link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a'); 299 link.href = blobUrl; 300 var filename = this.requestFilename_(); 301 if (filename) { 302 link.download = filename; 303 link.click(); 304 } 305 }, 306 307 onLoadClicked_: function() { 308 var inputElement = document.createElement('input'); 309 inputElement.type = 'file'; 310 inputElement.multiple = false; 311 312 var changeFired = false; 313 inputElement.addEventListener( 314 'change', 315 function(e) { 316 if (changeFired) 317 return; 318 changeFired = true; 319 320 var file = inputElement.files[0]; 321 readFile(file).then( 322 function(data) { 323 this.setActiveTrace(file.name, data); 324 }.bind(this), 325 function(err) { 326 tr.ui.b.Overlay.showError('Error while loading file: ' + err); 327 }); 328 }.bind(this), false); 329 inputElement.click(); 330 }, 331 332 /////////////////////////////////////////////////////////////////////////// 333 334 initDragAndDrop_: function() { 335 this.dropHandler_ = this.dropHandler_.bind(this); 336 this.ignoreDragEvent_ = this.ignoreDragEvent_.bind(this); 337 document.addEventListener('dragstart', this.ignoreDragEvent_, false); 338 document.addEventListener('dragend', this.ignoreDragEvent_, false); 339 document.addEventListener('dragenter', this.ignoreDragEvent_, false); 340 document.addEventListener('dragleave', this.ignoreDragEvent_, false); 341 document.addEventListener('dragover', this.ignoreDragEvent_, false); 342 document.addEventListener('drop', this.dropHandler_, false); 343 }, 344 345 detachDragAndDrop_: function() { 346 document.removeEventListener('dragstart', this.ignoreDragEvent_); 347 document.removeEventListener('dragend', this.ignoreDragEvent_); 348 document.removeEventListener('dragenter', this.ignoreDragEvent_); 349 document.removeEventListener('dragleave', this.ignoreDragEvent_); 350 document.removeEventListener('dragover', this.ignoreDragEvent_); 351 document.removeEventListener('drop', this.dropHandler_); 352 }, 353 354 ignoreDragEvent_: function(e) { 355 e.preventDefault(); 356 return false; 357 }, 358 359 dropHandler_: function(e) { 360 if (this.isAnyDialogUp_) 361 return; 362 363 e.stopPropagation(); 364 e.preventDefault(); 365 366 var files = e.dataTransfer.files; 367 if (files.length !== 1) { 368 tr.ui.b.Overlay.showError('1 file supported at a time.'); 369 return; 370 } 371 372 readFile(files[0]).then( 373 function(data) { 374 this.setActiveTrace(files[0].name, data); 375 }.bind(this), 376 function(err) { 377 tr.ui.b.Overlay.showError('Error while loading file: ' + err); 378 }); 379 return false; 380 } 381 }; 382 383 return { 384 ProfilingView: ProfilingView 385 }; 386}); 387</script> 388