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