blob: 4856fcc7d8ce711dfee75c450f002dde96a03891 [file] [log] [blame]
<!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;
min-height: 0;
}
.report-id-message {
-webkit-user-select: text;
}
x-timeline-view-buttons,
x-timeline-view-buttons > #monitoring-elements {
display: flex;
align-items: center;
}
</style>
<template id="profiling-view-template">
<tr-b-ui-info-bar-group></tr-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.
*/
tr.exportTo('tr.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.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(tr.b.instantiateTemplate('#profiling-view-template',
THIS_DOC));
this.timelineView_ = this.querySelector('x-timeline-view');
tr.b.ui.decorate(this.timelineView_, tr.c.TimelineView);
this.infoBarGroup_ = this.querySelector('tr-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);
tr.b.KeyEventManager.instance.addListener(
'keypress', this.onKeypress_, this);
this.initDragAndDrop_();
if (tracingControllerClient) {
this.tracingControllerClient_ = tracingControllerClient;
} else if (window.DevToolsHost !== undefined) {
this.tracingControllerClient_ =
new tr.e.about_tracing.InspectorTracingControllerClient();
} else {
this.tracingControllerClient_ =
new tr.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
tr.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 = tr.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 tr.e.about_tracing.UserCancelledError)
return;
tr.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 =
tr.e.about_tracing.beginMonitoring(this.tracingControllerClient_);
resultPromise.then(
function() {
}.bind(this),
function(err) {
if (err instanceof tr.e.about_tracing.UserCancelledError)
return;
tr.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 =
tr.e.about_tracing.endMonitoring(this.tracingControllerClient_);
resultPromise.then(
function() {
}.bind(this),
function(err) {
if (err instanceof tr.e.about_tracing.UserCancelledError)
return;
tr.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 =
tr.e.about_tracing.captureMonitoring(this.tracingControllerClient_);
resultPromise.then(
function(data) {
this.setActiveTrace('trace.json', data, true);
}.bind(this),
function(err) {
if (err instanceof tr.e.about_tracing.UserCancelledError)
return;
tr.b.ui.Overlay.showError('Error while monitoring', err);
}.bind(this));
return resultPromise;
},
getMonitoringStatus: function() {
var resultPromise =
tr.e.about_tracing.getMonitoringStatus(this.tracingControllerClient_);
resultPromise.then(
function(status) {
this.onMonitoringStateChanged_(status.isMonitoring);
}.bind(this),
function(err) {
if (err instanceof tr.e.about_tracing.UserCancelledError)
return;
tr.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 tr.Model();
var p = m.importTracesWithProgressDialog([data], true);
p.then(
function() {
this.timelineView_.model = m;
this.timelineView_.updateDocumentFavicon();
}.bind(this),
function(err) {
tr.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_ = tr.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) {
tr.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) {
tr.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) {
tr.b.ui.Overlay.showError('Error while loading file: ' + err);
});
return false;
}
};
return {
ProfilingView: ProfilingView
};
});
</script>