| <!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="/extras/about_tracing/record_and_capture_controller.html"> |
| <link rel="import" href="/extras/about_tracing/inspector_tracing_controller_client.html"> |
| <link rel="import" href="/extras/about_tracing/xhr_based_tracing_controller_client.html"> |
| <link rel="import" href="/base/key_event_manager.html"> |
| <link rel="import" href="/base/ui/info_bar_group.html"> |
| <link rel="import" href="/base/ui/overlay.html"> |
| <link rel="import" href="/trace_viewer.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 .controls #upload-button { |
| display: none; |
| } |
| |
| x-profiling-view > x-timeline-view { |
| -webkit-flex: 1 1 auto; |
| } |
| |
| .report-id-message { |
| -webkit-user-select: text; |
| } |
| |
| x-timeline-view-buttons, |
| x-timeline-view-buttons > #monitoring-elements { |
| display: flex; |
| } |
| </style> |
| |
| <template id="profiling-view-template"> |
| <tv-b-ui-info-bar-group></tv-b-ui-info-bar-group> |
| <x-timeline-view> |
| <x-timeline-view-buttons> |
| <button id="record-button">Record</button> |
| <span id="monitoring-elements"> |
| <input id="monitor-checkbox" type="checkbox"> |
| <label for="monitor-checkbox">Monitoring</label></input> |
| <button id="capture-button">Capture Monitoring Snapshot</button> |
| </span> |
| <button id="save-button">Save</button> |
| <button id="upload-button">Upload</button> |
| <button id="load-button">Load</button> |
| </x-timeline-view-buttons> |
| </x-timeline-view> |
| </template> |
| |
| <script> |
| 'use strict'; |
| |
| /** |
| * @fileoverview ProfilingView glues the View control to |
| * TracingController. |
| */ |
| tv.exportTo('tv.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 = tv.b.ui.define('x-profiling-view'); |
| var THIS_DOC = document.currentScript.ownerDocument; |
| var REPORT_UPLOAD_URL = 'http://crash-staging/'; |
| |
| ProfilingView.prototype = { |
| __proto__: HTMLUnknownElement.prototype, |
| |
| decorate: function(tracingControllerClient) { |
| this.appendChild(tv.b.instantiateTemplate('#profiling-view-template', |
| THIS_DOC)); |
| |
| this.timelineView_ = this.querySelector('x-timeline-view'); |
| tv.b.ui.decorate(this.timelineView_, tv.c.TimelineView); |
| |
| this.infoBarGroup_ = this.querySelector('tv-b-ui-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. |
| var buttons = this.querySelector('x-timeline-view-buttons'); |
| buttons.parentElement.removeChild(buttons); |
| this.timelineView_.leftControls.appendChild(buttons); |
| this.initButtons_(buttons); |
| |
| tv.b.KeyEventManager.instance.addListener( |
| 'keypress', this.onKeypress_, this); |
| |
| this.initDragAndDrop_(); |
| |
| if (tracingControllerClient) { |
| this.tracingControllerClient_ = tracingControllerClient; |
| } else if (window.DevToolsHost !== undefined) { |
| this.tracingControllerClient_ = |
| new tv.e.about_tracing.InspectorTracingControllerClient(); |
| } else { |
| this.tracingControllerClient_ = |
| new tv.e.about_tracing.XhrBasedTracingControllerClient(); |
| } |
| |
| this.isRecording_ = false; |
| this.isMonitoring_ = false; |
| this.activeTrace_ = undefined; |
| |
| window.onMonitoringStateChanged = function(is_monitoring) { |
| this.onMonitoringStateChanged_(is_monitoring); |
| }.bind(this); |
| |
| window.onUploadError = function(error_message) { |
| this.setUploadOverlayText_(['Trace upload failed: ' + error_message]); |
| }.bind(this); |
| window.onUploadProgress = function(percent, currentAsString, |
| totalAsString) { |
| this.setUploadOverlayText_( |
| ['Upload progress: ' + percent + '% (' + currentAsString + ' of ' + |
| currentAsString + ' bytes)']); |
| }.bind(this); |
| window.onUploadComplete = function(reportId) { |
| var messageDiv = document.createElement('div'); |
| var textNode = document.createTextNode( |
| 'Trace uploaded successfully. Report id: '); |
| messageDiv.appendChild(textNode); |
| var reportLink = document.createElement('a'); |
| messageDiv.appendChild(reportLink); |
| reportLink.href = REPORT_UPLOAD_URL + reportId; |
| reportLink.text = reportId; |
| reportLink.className = 'report-id-message'; |
| reportLink.target = '_blank'; |
| this.setUploadOverlayContent_(messageDiv); |
| }.bind(this); |
| |
| this.getMonitoringStatus(); |
| 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_; |
| }, |
| |
| get isMonitoring() { |
| return this.isMonitoring_; |
| }, |
| |
| set tracingControllerClient(tracingControllerClient) { |
| this.tracingControllerClient_ = tracingControllerClient; |
| this.updateTracingControllerSpecificState_(); |
| }, |
| |
| updateTracingControllerSpecificState_: function() { |
| var isInspector = this.tracingControllerClient_ instanceof |
| tv.e.about_tracing.InspectorTracingControllerClient; |
| |
| if (isInspector) { |
| this.infoBarGroup_.addMessage( |
| 'This about:tracing is connected to a remote device...', |
| [{buttonText: 'Wow!', onClick: function() {}}]); |
| } |
| |
| var monitoringElementsEl = this.querySelector('#monitoring-elements'); |
| if (isInspector) |
| monitoringElementsEl.style.display = 'none'; |
| else |
| monitoringElementsEl.style.display = ''; |
| }, |
| |
| beginRecording: function() { |
| if (this.isRecording_) |
| throw new Error('Already recording'); |
| if (this.isMonitoring_) |
| throw new Error('Already monitoring'); |
| this.isRecording_ = true; |
| var buttons = this.querySelector('x-timeline-view-buttons'); |
| buttons.querySelector('#monitor-checkbox').disabled = true; |
| buttons.querySelector('#monitor-checkbox').checked = false; |
| var resultPromise = tv.e.about_tracing.beginRecording( |
| this.tracingControllerClient_); |
| resultPromise.then( |
| function(data) { |
| this.isRecording_ = false; |
| buttons.querySelector('#monitor-checkbox').disabled = false; |
| this.setActiveTrace('trace.json', data, false); |
| }.bind(this), |
| function(err) { |
| this.isRecording_ = false; |
| buttons.querySelector('#monitor-checkbox').disabled = false; |
| if (err instanceof tv.e.about_tracing.UserCancelledError) |
| return; |
| tv.b.ui.Overlay.showError('Error while recording', err); |
| }.bind(this)); |
| return resultPromise; |
| }, |
| |
| beginMonitoring: function() { |
| if (this.isRecording_) |
| throw new Error('Already recording'); |
| if (this.isMonitoring_) |
| throw new Error('Already monitoring'); |
| var buttons = this.querySelector('x-timeline-view-buttons'); |
| var resultPromise = |
| tv.e.about_tracing.beginMonitoring(this.tracingControllerClient_); |
| resultPromise.then( |
| function() { |
| }.bind(this), |
| function(err) { |
| if (err instanceof tv.e.about_tracing.UserCancelledError) |
| return; |
| tv.b.ui.Overlay.showError('Error while monitoring', err); |
| }.bind(this)); |
| return resultPromise; |
| }, |
| |
| endMonitoring: function() { |
| if (this.isRecording_) |
| throw new Error('Already recording'); |
| if (!this.isMonitoring_) |
| throw new Error('Monitoring is disabled'); |
| var buttons = this.querySelector('x-timeline-view-buttons'); |
| var resultPromise = |
| tv.e.about_tracing.endMonitoring(this.tracingControllerClient_); |
| resultPromise.then( |
| function() { |
| }.bind(this), |
| function(err) { |
| if (err instanceof tv.e.about_tracing.UserCancelledError) |
| return; |
| tv.b.ui.Overlay.showError('Error while monitoring', err); |
| }.bind(this)); |
| return resultPromise; |
| }, |
| |
| captureMonitoring: function() { |
| if (!this.isMonitoring_) |
| throw new Error('Monitoring is disabled'); |
| var resultPromise = |
| tv.e.about_tracing.captureMonitoring(this.tracingControllerClient_); |
| resultPromise.then( |
| function(data) { |
| this.setActiveTrace('trace.json', data, true); |
| }.bind(this), |
| function(err) { |
| if (err instanceof tv.e.about_tracing.UserCancelledError) |
| return; |
| tv.b.ui.Overlay.showError('Error while monitoring', err); |
| }.bind(this)); |
| return resultPromise; |
| }, |
| |
| getMonitoringStatus: function() { |
| var resultPromise = |
| tv.e.about_tracing.getMonitoringStatus(this.tracingControllerClient_); |
| resultPromise.then( |
| function(status) { |
| this.onMonitoringStateChanged_(status.isMonitoring); |
| }.bind(this), |
| function(err) { |
| if (err instanceof tv.e.about_tracing.UserCancelledError) |
| return; |
| tv.b.ui.Overlay.showError('Error while updating tracing states', |
| err); |
| }.bind(this)); |
| return resultPromise; |
| }, |
| |
| onMonitoringStateChanged_: function(is_monitoring) { |
| this.isMonitoring_ = is_monitoring; |
| var buttons = this.querySelector('x-timeline-view-buttons'); |
| buttons.querySelector('#record-button').disabled = is_monitoring; |
| buttons.querySelector('#capture-button').disabled = !is_monitoring; |
| buttons.querySelector('#monitor-checkbox').checked = is_monitoring; |
| }, |
| |
| onKeypress_: function(event) { |
| if (document.activeElement.nodeName === 'INPUT') |
| return; |
| |
| if (!this.isRecording && |
| event.keyCode === 'r'.charCodeAt(0)) { |
| this.beginRecording(); |
| event.preventDefault(); |
| event.stopPropagation(); |
| return true; |
| } |
| }, |
| |
| get timelineView() { |
| return this.timelineView_; |
| }, |
| |
| /////////////////////////////////////////////////////////////////////////// |
| |
| clearActiveTrace: function() { |
| this.saveButton_.disabled = true; |
| this.uploadButton_.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.uploadButton_.disabled = false; |
| this.timelineView_.viewTitle = filename; |
| |
| var m = new tv.c.TraceModel(); |
| var p = m.importTracesWithProgressDialog([data], true); |
| p.then( |
| function() { |
| this.timelineView_.model = m; |
| this.timelineView_.updateDocumentFavicon(); |
| }.bind(this), |
| function(err) { |
| tv.b.ui.Overlay.showError('While importing: ', err); |
| }.bind(this)); |
| }, |
| |
| /////////////////////////////////////////////////////////////////////////// |
| |
| initButtons_: function(buttons) { |
| buttons.querySelector('#record-button').addEventListener( |
| 'click', function(event) { |
| event.stopPropagation(); |
| this.beginRecording(); |
| }.bind(this)); |
| |
| buttons.querySelector('#monitor-checkbox').addEventListener( |
| 'click', function(event) { |
| event.stopPropagation(); |
| if (this.isMonitoring_) |
| this.endMonitoring(); |
| else |
| this.beginMonitoring(); |
| }.bind(this)); |
| |
| buttons.querySelector('#capture-button').addEventListener( |
| 'click', function(event) { |
| event.stopPropagation(); |
| this.captureMonitoring(); |
| }.bind(this)); |
| buttons.querySelector('#capture-button').disabled = true; |
| |
| buttons.querySelector('#load-button').addEventListener( |
| 'click', function(event) { |
| event.stopPropagation(); |
| this.onLoadClicked_(); |
| }.bind(this)); |
| |
| this.saveButton_ = buttons.querySelector('#save-button'); |
| this.saveButton_.addEventListener('click', |
| this.onSaveClicked_.bind(this)); |
| this.saveButton_.disabled = true; |
| |
| this.uploadButton_ = buttons.querySelector('#upload-button'); |
| this.uploadButton_.addEventListener('click', |
| this.onUploadClicked_.bind(this)); |
| if (typeof(chrome.send) === 'function') { |
| this.uploadButton_.style.display = 'inline-block'; |
| } |
| this.uploadButton_.disabled = true; |
| this.uploadOverlay_ = null; |
| }, |
| |
| 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 custom = prompt('Filename? (.json appended) Or leave blank:'); |
| if (custom === null) |
| return undefined; |
| |
| if (custom) { |
| filename = defaultName.replace(/\.json$/, ' ' + custom) + '.json'; |
| } else { |
| var date = new Date(); |
| var dateText = ' ' + date.toDateString() + |
| ' ' + date.toLocaleTimeString(); |
| filename = defaultName.replace(/\.json$/, dateText + '.json'); |
| } |
| |
| 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(); |
| } |
| }, |
| |
| onUploadClicked_: function() { |
| if (this.uploadOverlay_) { |
| throw new Error('Already uploading'); |
| } |
| this.initUploadStatusOverlay_(); |
| }, |
| |
| initUploadStatusOverlay_: function() { |
| this.uploadOverlay_ = tv.b.ui.Overlay(); |
| this.uploadOverlay_.title = 'Uploading trace...'; |
| this.uploadOverlay_.userCanClose = false; |
| this.uploadOverlay_.visible = true; |
| |
| this.setUploadOverlayText_([ |
| 'You are about to upload trace data to Google server.', |
| 'Would you like to proceed?' |
| ]); |
| var okButton = document.createElement('button'); |
| okButton.textContent = 'Ok'; |
| okButton.addEventListener('click', this.doTraceUpload_.bind(this)); |
| this.uploadOverlay_.buttons.appendChild(okButton); |
| |
| var cancelButton = document.createElement('button'); |
| cancelButton.textContent = 'Cancel'; |
| cancelButton.addEventListener('click', |
| this.hideUploadOverlay_.bind(this)); |
| this.uploadOverlay_.buttons.appendChild(cancelButton); |
| }, |
| |
| setUploadOverlayContent_: function(content) { |
| if (!this.uploadOverlay_) |
| throw new Error('Not uploading'); |
| |
| this.uploadOverlay_.textContent = ''; |
| this.uploadOverlay_.appendChild(content); |
| }, |
| |
| setUploadOverlayText_: function(messages) { |
| var contentDiv = document.createElement('div'); |
| |
| for (var i = 0; i < messages.length; ++i) { |
| var messageDiv = document.createElement('div'); |
| messageDiv.textContent = messages[i]; |
| contentDiv.appendChild(messageDiv); |
| } |
| this.setUploadOverlayContent_(contentDiv); |
| }, |
| |
| doTraceUpload_: function() { |
| this.setUploadOverlayText_(['Uploading trace data...']); |
| this.uploadOverlay_.buttons.removeChild( |
| this.uploadOverlay_.buttons.firstChild); |
| this.uploadOverlay_.buttons.firstChild.textContent = 'Close'; |
| chrome.send('doUpload', [this.activeTrace_.data]); |
| }, |
| |
| hideUploadOverlay_: function() { |
| if (!this.uploadOverlay_) |
| throw new Error('Not uploading'); |
| |
| this.uploadOverlay_.visible = false; |
| this.uploadOverlay_ = null; |
| }, |
| |
| 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) { |
| tv.b.ui.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) { |
| tv.b.ui.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) { |
| tv.b.ui.Overlay.showError('Error while loading file: ' + err); |
| }); |
| return false; |
| } |
| }; |
| |
| return { |
| ProfilingView: ProfilingView |
| }; |
| }); |
| </script> |