blob: 1e6f06c6a2716cbb4a7967a86e88cd5152db45e3 [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="/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>