blob: ace9bd8a7d06c44308fae7b20a82e881dd54f91b [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="stylesheet" href="/extras/chrome/cc/picture_ops_list_view.css">
<link rel="import" href="/extras/chrome/cc/constants.html">
<link rel="import" href="/extras/chrome/cc/selection.html">
<link rel="import" href="/base/ui/list_view.html">
<link rel="import" href="/base/ui/dom_helpers.html">
<script>
'use strict';
tr.exportTo('tr.e.cc', function() {
var OPS_TIMING_ITERATIONS = 3; // Iterations to average op timing info over.
var ANNOTATION = 'Comment';
var BEGIN_ANNOTATION = 'BeginCommentGroup';
var END_ANNOTATION = 'EndCommentGroup';
var ANNOTATION_ID = 'ID: ';
var ANNOTATION_CLASS = 'CLASS: ';
var ANNOTATION_TAG = 'TAG: ';
var constants = tr.e.cc.constants;
/**
* @constructor
*/
var PictureOpsListView = tr.b.ui.define('picture-ops-list-view');
PictureOpsListView.prototype = {
__proto__: HTMLUnknownElement.prototype,
decorate: function() {
this.opsList_ = new tr.b.ui.ListView();
this.appendChild(this.opsList_);
this.selectedOp_ = undefined;
this.selectedOpIndex_ = undefined;
this.opsList_.addEventListener(
'selection-changed', this.onSelectionChanged_.bind(this));
this.picture_ = undefined;
},
get picture() {
return this.picture_;
},
set picture(picture) {
this.picture_ = picture;
this.updateContents_();
},
updateContents_: function() {
this.opsList_.clear();
if (!this.picture_)
return;
var ops = this.picture_.getOps();
if (!ops)
return;
ops = this.picture_.tagOpsWithTimings(ops);
ops = this.opsTaggedWithAnnotations_(ops);
for (var i = 0; i < ops.length; i++) {
var op = ops[i];
var item = document.createElement('div');
item.opIndex = op.opIndex;
item.textContent = i + ') ' + op.cmd_string;
// Display the element info associated with the op, if available.
if (op.elementInfo.tag || op.elementInfo.id || op.elementInfo.class) {
var elementInfo = document.createElement('span');
elementInfo.classList.add('elementInfo');
var tag = op.elementInfo.tag ? op.elementInfo.tag : 'unknown';
var id = op.elementInfo.id ? 'id=' + op.elementInfo.id : undefined;
var className = op.elementInfo.class ? 'class=' +
op.elementInfo.class : undefined;
elementInfo.textContent =
'<' + tag + (id ? ' ' : '') +
(id ? id : '') + (className ? ' ' : '') +
(className ? className : '') + '>';
item.appendChild(elementInfo);
}
// Display the Skia params.
// FIXME: now that we have structured data, we should format it.
// (https://github.com/google/trace-viewer/issues/782)
if (op.info.length > 0) {
var infoItem = document.createElement('div');
infoItem.textContent = JSON.stringify(op.info);
item.appendChild(infoItem);
}
// Display the op timing, if available.
if (op.cmd_time && op.cmd_time >= 0.0001) {
var time = document.createElement('span');
time.classList.add('time');
var rounded = op.cmd_time.toFixed(4);
time.textContent = '(' + rounded + 'ms)';
item.appendChild(time);
}
this.opsList_.appendChild(item);
}
},
onSelectionChanged_: function(e) {
var beforeSelectedOp = true;
// Deselect on re-selection.
if (this.opsList_.selectedElement === this.selectedOp_) {
this.opsList_.selectedElement = undefined;
beforeSelectedOp = false;
this.selectedOpIndex_ = undefined;
}
this.selectedOp_ = this.opsList_.selectedElement;
// Set selection on all previous ops.
var ops = this.opsList_.children;
for (var i = 0; i < ops.length; i++) {
var op = ops[i];
if (op === this.selectedOp_) {
beforeSelectedOp = false;
this.selectedOpIndex_ = op.opIndex;
} else if (beforeSelectedOp) {
op.setAttribute('beforeSelection', 'beforeSelection');
} else {
op.removeAttribute('beforeSelection');
}
}
tr.b.dispatchSimpleEvent(this, 'selection-changed', false);
},
get numOps() {
return this.opsList_.children.length;
},
get selectedOpIndex() {
return this.selectedOpIndex_;
},
set selectedOpIndex(s) {
this.selectedOpIndex_ = s;
if (s === undefined) {
this.opsList_.selectedElement = this.selectedOp_;
this.onSelectionChanged_();
} else {
if (s < 0) throw new Error('Invalid index');
if (s >= this.numOps) throw new Error('Invalid index');
this.opsList_.selectedElement = this.opsList_.getElementByIndex(s + 1);
tr.b.scrollIntoViewIfNeeded(this.opsList_.selectedElement);
}
},
/**
* Return Skia operations tagged by annotation.
*
* The ops returned from Picture.getOps() contain both Skia ops and
* annotations threaded together. This function removes all annotations
* from the list and tags each op with the associated annotations.
* Additionally, the last {tag, id, class} is stored as elementInfo on
* each op.
*
* @param {Array} ops Array of Skia operations and annotations.
* @return {Array} Skia ops where op.annotations contains the associated
* annotations for a given op.
*/
opsTaggedWithAnnotations_: function(ops) {
// This algorithm works by walking all the ops and pushing any
// annotations onto a stack. When a non-annotation op is found, the
// annotations stack is traversed and stored with the op.
var annotationGroups = new Array();
var opsWithoutAnnotations = new Array();
for (var opIndex = 0; opIndex < ops.length; opIndex++) {
var op = ops[opIndex];
op.opIndex = opIndex;
switch (op.cmd_string) {
case BEGIN_ANNOTATION:
annotationGroups.push(new Array());
break;
case END_ANNOTATION:
annotationGroups.pop();
break;
case ANNOTATION:
annotationGroups[annotationGroups.length - 1].push(op);
break;
default:
var annotations = new Array();
var elementInfo = {};
annotationGroups.forEach(function(annotationGroup) {
elementInfo = {};
annotationGroup.forEach(function(annotation) {
annotation.info.forEach(function(info) {
if (info.indexOf(ANNOTATION_TAG) != -1)
elementInfo.tag = info.substring(
info.indexOf(ANNOTATION_TAG) +
ANNOTATION_TAG.length).toLowerCase();
else if (info.indexOf(ANNOTATION_ID) != -1)
elementInfo.id = info.substring(
info.indexOf(ANNOTATION_ID) +
ANNOTATION_ID.length);
else if (info.indexOf(ANNOTATION_CLASS) != -1)
elementInfo.class = info.substring(
info.indexOf(ANNOTATION_CLASS) +
ANNOTATION_CLASS.length);
annotations.push(info);
});
});
});
op.annotations = annotations;
op.elementInfo = elementInfo;
opsWithoutAnnotations.push(op);
}
}
return opsWithoutAnnotations;
}
};
return {
PictureOpsListView: PictureOpsListView
};
});
</script>