| <!DOCTYPE html> |
| <!-- |
| Copyright (c) 2013 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. |
| --> |
| |
| <link rel="import" href="/tracing/base/base64.html"> |
| <link rel="import" |
| href="/tracing/ui/extras/about_tracing/record_controller.html"> |
| <link rel="import" |
| href="/tracing/ui/extras/about_tracing/inspector_tracing_controller_client.html"> |
| <link rel="import" |
| href="/tracing/ui/extras/about_tracing/xhr_based_tracing_controller_client.html"> |
| <link rel="import" href="/tracing/importer/import.html"> |
| <link rel="import" href="/tracing/ui/base/info_bar_group.html"> |
| <link rel="import" href="/tracing/ui/base/hotkey_controller.html"> |
| <link rel="import" href="/tracing/ui/base/overlay.html"> |
| <link rel="import" href="/tracing/ui/base/utils.html"> |
| <link rel="import" href="/tracing/ui/timeline_view.html"> |
| |
| <style> |
| x-profiling-view { |
| -webkit-flex-direction: column; |
| display: -webkit-flex; |
| padding: 0; |
| } |
| |
| x-profiling-view .controls #save-button { |
| margin-left: 64px !important; |
| } |
| |
| x-profiling-view > tr-ui-timeline-view { |
| -webkit-flex: 1 1 auto; |
| min-height: 0; |
| } |
| |
| .report-id-message { |
| -webkit-user-select: text; |
| } |
| |
| x-timeline-view-buttons { |
| display: flex; |
| align-items: center; |
| } |
| </style> |
| |
| <template id="profiling-view-template"> |
| <tr-ui-b-info-bar-group></tr-ui-b-info-bar-group> |
| <x-timeline-view-buttons> |
| <button id="record-button">Record</button> |
| <button id="save-button">Save</button> |
| <button id="load-button">Load</button> |
| </x-timeline-view-buttons> |
| <tr-ui-timeline-view> |
| <track-view-container id='track_view_container'></track-view-container> |
| </tr-ui-timeline-view> |
| </template> |
| |
| <script> |
| 'use strict'; |
| |
| /** |
| * @fileoverview ProfilingView glues the View control to |
| * TracingController. |
| */ |
| tr.exportTo('tr.ui.e.about_tracing', function() { |
| function readFile(file) { |
| return new Promise(function(resolve, reject) { |
| var reader = new FileReader(); |
| var filename = file.name; |
| reader.onload = function(data) { |
| resolve(data.target.result); |
| }; |
| reader.onerror = function(err) { |
| reject(err); |
| }; |
| |
| var is_binary = /[.]gz$/.test(filename) || /[.]zip$/.test(filename); |
| if (is_binary) |
| reader.readAsArrayBuffer(file); |
| else |
| reader.readAsText(file); |
| }); |
| } |
| |
| /** |
| * ProfilingView |
| * @constructor |
| * @extends {HTMLUnknownElement} |
| */ |
| var ProfilingView = tr.ui.b.define('x-profiling-view'); |
| var THIS_DOC = document.currentScript.ownerDocument; |
| |
| ProfilingView.prototype = { |
| __proto__: HTMLUnknownElement.prototype, |
| |
| decorate: function(tracingControllerClient) { |
| this.appendChild(tr.ui.b.instantiateTemplate('#profiling-view-template', |
| THIS_DOC)); |
| |
| this.timelineView_ = this.querySelector('tr-ui-timeline-view'); |
| this.infoBarGroup_ = this.querySelector('tr-ui-b-info-bar-group'); |
| |
| // Detach the buttons. We will reattach them to the timeline view. |
| // TODO(nduca): Make timeline-view have a content select="x-buttons" |
| // that pulls in any buttons. |
| this.recordButton_ = this.querySelector('#record-button'); |
| this.loadButton_ = this.querySelector('#load-button'); |
| this.saveButton_ = this.querySelector('#save-button'); |
| |
| var buttons = this.querySelector('x-timeline-view-buttons'); |
| buttons.parentElement.removeChild(buttons); |
| this.timelineView_.leftControls.appendChild(buttons); |
| this.initButtons_(); |
| |
| this.timelineView_.hotkeyController.addHotKey(new tr.ui.b.HotKey({ |
| eventType: 'keypress', |
| keyCode: 'r'.charCodeAt(0), |
| callback: function(e) { |
| this.beginRecording(); |
| event.stopPropagation(); |
| }, |
| thisArg: this |
| })); |
| |
| this.initDragAndDrop_(); |
| |
| if (tracingControllerClient) { |
| this.tracingControllerClient_ = tracingControllerClient; |
| } else if (window.DevToolsHost !== undefined) { |
| this.tracingControllerClient_ = |
| new tr.ui.e.about_tracing.InspectorTracingControllerClient(); |
| } else { |
| this.tracingControllerClient_ = |
| new tr.ui.e.about_tracing.XhrBasedTracingControllerClient(); |
| } |
| |
| this.isRecording_ = false; |
| this.activeTrace_ = undefined; |
| |
| this.updateTracingControllerSpecificState_(); |
| }, |
| |
| // Detach all document event listeners. Without this the tests can get |
| // confused as the element may still be listening when the next test runs. |
| detach_: function() { |
| this.detachDragAndDrop_(); |
| }, |
| |
| get isRecording() { |
| return this.isRecording_; |
| }, |
| |
| set tracingControllerClient(tracingControllerClient) { |
| this.tracingControllerClient_ = tracingControllerClient; |
| this.updateTracingControllerSpecificState_(); |
| }, |
| |
| updateTracingControllerSpecificState_: function() { |
| var isInspector = this.tracingControllerClient_ instanceof |
| tr.ui.e.about_tracing.InspectorTracingControllerClient; |
| |
| if (isInspector) { |
| this.infoBarGroup_.addMessage( |
| 'This about:tracing is connected to a remote device...', |
| [{buttonText: 'Wow!', onClick: function() {}}]); |
| } |
| }, |
| |
| beginRecording: function() { |
| if (this.isRecording_) |
| throw new Error('Already recording'); |
| this.isRecording_ = true; |
| var resultPromise = tr.ui.e.about_tracing.beginRecording( |
| this.tracingControllerClient_); |
| resultPromise.then( |
| function(data) { |
| this.isRecording_ = false; |
| var traceName = tr.ui.e.about_tracing.defaultTraceName( |
| this.tracingControllerClient_); |
| this.setActiveTrace(traceName, data, false); |
| }.bind(this), |
| function(err) { |
| this.isRecording_ = false; |
| if (err instanceof tr.ui.e.about_tracing.UserCancelledError) |
| return; |
| tr.ui.b.Overlay.showError('Error while recording', err); |
| }.bind(this)); |
| return resultPromise; |
| }, |
| |
| get timelineView() { |
| return this.timelineView_; |
| }, |
| |
| /////////////////////////////////////////////////////////////////////////// |
| |
| clearActiveTrace: function() { |
| this.saveButton_.disabled = true; |
| this.activeTrace_ = undefined; |
| }, |
| |
| setActiveTrace: function(filename, data) { |
| this.activeTrace_ = { |
| filename: filename, |
| data: data |
| }; |
| |
| this.infoBarGroup_.clearMessages(); |
| this.updateTracingControllerSpecificState_(); |
| this.saveButton_.disabled = false; |
| this.timelineView_.viewTitle = filename; |
| |
| var m = new tr.Model(); |
| var i = new tr.importer.Import(m); |
| var p = i.importTracesWithProgressDialog([data]); |
| p.then( |
| function() { |
| this.timelineView_.model = m; |
| this.timelineView_.updateDocumentFavicon(); |
| }.bind(this), |
| function(err) { |
| tr.ui.b.Overlay.showError('While importing: ', err); |
| }.bind(this)); |
| }, |
| |
| /////////////////////////////////////////////////////////////////////////// |
| |
| initButtons_: function() { |
| this.recordButton_.addEventListener( |
| 'click', function(event) { |
| event.stopPropagation(); |
| this.beginRecording(); |
| }.bind(this)); |
| |
| this.loadButton_.addEventListener( |
| 'click', function(event) { |
| event.stopPropagation(); |
| this.onLoadClicked_(); |
| }.bind(this)); |
| |
| this.saveButton_.addEventListener('click', |
| this.onSaveClicked_.bind(this)); |
| this.saveButton_.disabled = true; |
| }, |
| |
| requestFilename_: function() { |
| |
| // unsafe filename patterns: |
| var illegalRe = /[\/\?<>\\:\*\|":]/g; |
| var controlRe = /[\x00-\x1f\x80-\x9f]/g; |
| var reservedRe = /^\.+$/; |
| |
| var filename; |
| var defaultName = this.activeTrace_.filename; |
| var fileExtension = '.json'; |
| var fileRegex = /\.json$/; |
| if (/[.]gz$/.test(defaultName)) { |
| fileExtension += '.gz'; |
| fileRegex = /\.json\.gz$/; |
| } else if (/[.]zip$/.test(defaultName)) { |
| fileExtension = '.zip'; |
| fileRegex = /\.zip$/; |
| } |
| |
| var custom = prompt('Filename? (' + fileExtension + |
| ' appended) Or leave blank:'); |
| if (custom === null) |
| return undefined; |
| |
| var name; |
| if (custom) { |
| name = ' ' + custom; |
| } else { |
| var date = new Date(); |
| var dateText = ' ' + date.toDateString() + |
| ' ' + date.toLocaleTimeString(); |
| name = dateText; |
| } |
| |
| filename = defaultName.replace(fileRegex, name) + fileExtension; |
| |
| return filename |
| .replace(illegalRe, '.') |
| .replace(controlRe, '\u2022') |
| .replace(reservedRe, '') |
| .replace(/\s+/g, '_'); |
| }, |
| |
| onSaveClicked_: function() { |
| // Create a blob URL from the binary array. |
| var blob = new Blob([this.activeTrace_.data], |
| {type: 'application/octet-binary'}); |
| var blobUrl = window.webkitURL.createObjectURL(blob); |
| |
| // Create a link and click on it. BEST API EVAR! |
| var link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a'); |
| link.href = blobUrl; |
| var filename = this.requestFilename_(); |
| if (filename) { |
| link.download = filename; |
| link.click(); |
| } |
| }, |
| |
| onLoadClicked_: function() { |
| var inputElement = document.createElement('input'); |
| inputElement.type = 'file'; |
| inputElement.multiple = false; |
| |
| var changeFired = false; |
| inputElement.addEventListener( |
| 'change', |
| function(e) { |
| if (changeFired) |
| return; |
| changeFired = true; |
| |
| var file = inputElement.files[0]; |
| readFile(file).then( |
| function(data) { |
| this.setActiveTrace(file.name, data); |
| }.bind(this), |
| function(err) { |
| tr.ui.b.Overlay.showError('Error while loading file: ' + err); |
| }); |
| }.bind(this), false); |
| inputElement.click(); |
| }, |
| |
| /////////////////////////////////////////////////////////////////////////// |
| |
| initDragAndDrop_: function() { |
| this.dropHandler_ = this.dropHandler_.bind(this); |
| this.ignoreDragEvent_ = this.ignoreDragEvent_.bind(this); |
| document.addEventListener('dragstart', this.ignoreDragEvent_, false); |
| document.addEventListener('dragend', this.ignoreDragEvent_, false); |
| document.addEventListener('dragenter', this.ignoreDragEvent_, false); |
| document.addEventListener('dragleave', this.ignoreDragEvent_, false); |
| document.addEventListener('dragover', this.ignoreDragEvent_, false); |
| document.addEventListener('drop', this.dropHandler_, false); |
| }, |
| |
| detachDragAndDrop_: function() { |
| document.removeEventListener('dragstart', this.ignoreDragEvent_); |
| document.removeEventListener('dragend', this.ignoreDragEvent_); |
| document.removeEventListener('dragenter', this.ignoreDragEvent_); |
| document.removeEventListener('dragleave', this.ignoreDragEvent_); |
| document.removeEventListener('dragover', this.ignoreDragEvent_); |
| document.removeEventListener('drop', this.dropHandler_); |
| }, |
| |
| ignoreDragEvent_: function(e) { |
| e.preventDefault(); |
| return false; |
| }, |
| |
| dropHandler_: function(e) { |
| if (this.isAnyDialogUp_) |
| return; |
| |
| e.stopPropagation(); |
| e.preventDefault(); |
| |
| var files = e.dataTransfer.files; |
| if (files.length !== 1) { |
| tr.ui.b.Overlay.showError('1 file supported at a time.'); |
| return; |
| } |
| |
| readFile(files[0]).then( |
| function(data) { |
| this.setActiveTrace(files[0].name, data); |
| }.bind(this), |
| function(err) { |
| tr.ui.b.Overlay.showError('Error while loading file: ' + err); |
| }); |
| return false; |
| } |
| }; |
| |
| return { |
| ProfilingView: ProfilingView |
| }; |
| }); |
| </script> |