1<!DOCTYPE html> 2<!-- 3Copyright (c) 2013 The Chromium Authors. All rights reserved. 4Use of this source code is governed by a BSD-style license that can be 5found in the LICENSE file. 6--> 7 8<link rel="stylesheet" href="/tracing/ui/extras/chrome/cc/picture_ops_list_view.css"> 9 10<link rel="import" href="/tracing/extras/chrome/cc/constants.html"> 11<link rel="import" href="/tracing/ui/base/list_view.html"> 12<link rel="import" href="/tracing/ui/base/dom_helpers.html"> 13<link rel="import" href="/tracing/ui/base/utils.html"> 14<link rel="import" href="/tracing/ui/extras/chrome/cc/selection.html"> 15 16<script> 17'use strict'; 18 19tr.exportTo('tr.ui.e.chrome.cc', function() { 20 var OPS_TIMING_ITERATIONS = 3; // Iterations to average op timing info over. 21 var ANNOTATION = 'Comment'; 22 var BEGIN_ANNOTATION = 'BeginCommentGroup'; 23 var END_ANNOTATION = 'EndCommentGroup'; 24 var ANNOTATION_ID = 'ID: '; 25 var ANNOTATION_CLASS = 'CLASS: '; 26 var ANNOTATION_TAG = 'TAG: '; 27 28 var constants = tr.e.cc.constants; 29 30 /** 31 * @constructor 32 */ 33 var PictureOpsListView = 34 tr.ui.b.define('tr-ui-e-chrome-cc-picture-ops-list-view'); 35 36 PictureOpsListView.prototype = { 37 __proto__: HTMLUnknownElement.prototype, 38 39 decorate: function() { 40 this.opsList_ = new tr.ui.b.ListView(); 41 this.appendChild(this.opsList_); 42 43 this.selectedOp_ = undefined; 44 this.selectedOpIndex_ = undefined; 45 this.opsList_.addEventListener( 46 'selection-changed', this.onSelectionChanged_.bind(this)); 47 48 this.picture_ = undefined; 49 }, 50 51 get picture() { 52 return this.picture_; 53 }, 54 55 set picture(picture) { 56 this.picture_ = picture; 57 this.updateContents_(); 58 }, 59 60 updateContents_: function() { 61 this.opsList_.clear(); 62 63 if (!this.picture_) 64 return; 65 66 var ops = this.picture_.getOps(); 67 if (!ops) 68 return; 69 70 ops = this.picture_.tagOpsWithTimings(ops); 71 72 ops = this.opsTaggedWithAnnotations_(ops); 73 74 for (var i = 0; i < ops.length; i++) { 75 var op = ops[i]; 76 var item = document.createElement('div'); 77 item.opIndex = op.opIndex; 78 item.textContent = i + ') ' + op.cmd_string; 79 80 // Display the element info associated with the op, if available. 81 if (op.elementInfo.tag || op.elementInfo.id || op.elementInfo.class) { 82 var elementInfo = document.createElement('span'); 83 elementInfo.classList.add('elementInfo'); 84 var tag = op.elementInfo.tag ? op.elementInfo.tag : 'unknown'; 85 var id = op.elementInfo.id ? 'id=' + op.elementInfo.id : undefined; 86 var className = op.elementInfo.class ? 'class=' + 87 op.elementInfo.class : undefined; 88 elementInfo.textContent = 89 '<' + tag + (id ? ' ' : '') + 90 (id ? id : '') + (className ? ' ' : '') + 91 (className ? className : '') + '>'; 92 item.appendChild(elementInfo); 93 } 94 95 // Display the Skia params. 96 // FIXME: now that we have structured data, we should format it. 97 // (https://github.com/google/trace-viewer/issues/782) 98 if (op.info.length > 0) { 99 var infoItem = document.createElement('div'); 100 infoItem.textContent = JSON.stringify(op.info); 101 item.appendChild(infoItem); 102 } 103 104 // Display the op timing, if available. 105 if (op.cmd_time && op.cmd_time >= 0.0001) { 106 var time = document.createElement('span'); 107 time.classList.add('time'); 108 var rounded = op.cmd_time.toFixed(4); 109 time.textContent = '(' + rounded + 'ms)'; 110 item.appendChild(time); 111 } 112 113 this.opsList_.appendChild(item); 114 } 115 }, 116 117 onSelectionChanged_: function(e) { 118 var beforeSelectedOp = true; 119 120 // Deselect on re-selection. 121 if (this.opsList_.selectedElement === this.selectedOp_) { 122 this.opsList_.selectedElement = undefined; 123 beforeSelectedOp = false; 124 this.selectedOpIndex_ = undefined; 125 } 126 127 this.selectedOp_ = this.opsList_.selectedElement; 128 129 // Set selection on all previous ops. 130 var ops = this.opsList_.children; 131 for (var i = 0; i < ops.length; i++) { 132 var op = ops[i]; 133 if (op === this.selectedOp_) { 134 beforeSelectedOp = false; 135 this.selectedOpIndex_ = op.opIndex; 136 } else if (beforeSelectedOp) { 137 op.setAttribute('beforeSelection', 'beforeSelection'); 138 } else { 139 op.removeAttribute('beforeSelection'); 140 } 141 } 142 143 tr.b.dispatchSimpleEvent(this, 'selection-changed', false); 144 }, 145 146 get numOps() { 147 return this.opsList_.children.length; 148 }, 149 150 get selectedOpIndex() { 151 return this.selectedOpIndex_; 152 }, 153 154 set selectedOpIndex(s) { 155 this.selectedOpIndex_ = s; 156 157 if (s === undefined) { 158 this.opsList_.selectedElement = this.selectedOp_; 159 this.onSelectionChanged_(); 160 } else { 161 if (s < 0) throw new Error('Invalid index'); 162 if (s >= this.numOps) throw new Error('Invalid index'); 163 this.opsList_.selectedElement = this.opsList_.getElementByIndex(s + 1); 164 tr.ui.b.scrollIntoViewIfNeeded(this.opsList_.selectedElement); 165 } 166 }, 167 168 /** 169 * Return Skia operations tagged by annotation. 170 * 171 * The ops returned from Picture.getOps() contain both Skia ops and 172 * annotations threaded together. This function removes all annotations 173 * from the list and tags each op with the associated annotations. 174 * Additionally, the last {tag, id, class} is stored as elementInfo on 175 * each op. 176 * 177 * @param {Array} ops Array of Skia operations and annotations. 178 * @return {Array} Skia ops where op.annotations contains the associated 179 * annotations for a given op. 180 */ 181 opsTaggedWithAnnotations_: function(ops) { 182 // This algorithm works by walking all the ops and pushing any 183 // annotations onto a stack. When a non-annotation op is found, the 184 // annotations stack is traversed and stored with the op. 185 var annotationGroups = new Array(); 186 var opsWithoutAnnotations = new Array(); 187 for (var opIndex = 0; opIndex < ops.length; opIndex++) { 188 var op = ops[opIndex]; 189 op.opIndex = opIndex; 190 switch (op.cmd_string) { 191 case BEGIN_ANNOTATION: 192 annotationGroups.push(new Array()); 193 break; 194 case END_ANNOTATION: 195 annotationGroups.pop(); 196 break; 197 case ANNOTATION: 198 annotationGroups[annotationGroups.length - 1].push(op); 199 break; 200 default: 201 var annotations = new Array(); 202 var elementInfo = {}; 203 annotationGroups.forEach(function(annotationGroup) { 204 elementInfo = {}; 205 annotationGroup.forEach(function(annotation) { 206 annotation.info.forEach(function(info) { 207 if (info.indexOf(ANNOTATION_TAG) != -1) 208 elementInfo.tag = info.substring( 209 info.indexOf(ANNOTATION_TAG) + 210 ANNOTATION_TAG.length).toLowerCase(); 211 else if (info.indexOf(ANNOTATION_ID) != -1) 212 elementInfo.id = info.substring( 213 info.indexOf(ANNOTATION_ID) + 214 ANNOTATION_ID.length); 215 else if (info.indexOf(ANNOTATION_CLASS) != -1) 216 elementInfo.class = info.substring( 217 info.indexOf(ANNOTATION_CLASS) + 218 ANNOTATION_CLASS.length); 219 220 annotations.push(info); 221 }); 222 }); 223 }); 224 op.annotations = annotations; 225 op.elementInfo = elementInfo; 226 opsWithoutAnnotations.push(op); 227 } 228 } 229 230 return opsWithoutAnnotations; 231 } 232 }; 233 234 return { 235 PictureOpsListView: PictureOpsListView 236 }; 237}); 238</script> 239