summaryrefslogtreecommitdiff
path: root/chromium/third_party/catapult/tracing/tracing/ui/analysis/analysis_sub_view.html
blob: 66bae5772d4bfdf785124c2f0adb16b0b98e0a39 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
<!DOCTYPE html>
<!--
Copyright 2014 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/base.html">
<link rel="import" href="/tracing/base/iteration_helpers.html">
<link rel="import" href="/tracing/model/event_set.html">

<!--
@fileoverview Polymer element for various analysis sub-views.
-->
<script>
'use strict';

tr.exportTo('tr.ui.analysis', function() {

  var AnalysisSubView = {
    set tabLabel(label) {
      Polymer.dom(this).setAttribute('tab-label', label);
    },

    get tabLabel() {
      return this.getAttribute('tab-label');
    },

    get requiresTallView() {
      return false;
    },

    get relatedEventsToHighlight() {
      return undefined;
    },

    /**
     * Each element extending this one must implement
     * a 'selection' property.
     */
    set selection(selection) {
      throw new Error('Not implemented!');
    },

    get selection() {
      throw new Error('Not implemented!');
    }
  };

  // Basic registry.
  var allTypeInfosByEventProto = new Map();
  var onlyRootTypeInfosByEventProto = undefined;
  var eventProtoToRootTypeInfoMap = undefined;

  function AnalysisSubViewTypeInfo(eventConstructor, options) {
    if (options.multi === undefined)
      throw new Error('missing field: multi');
    if (options.title === undefined)
      throw new Error('missing field: title');
    this.eventConstructor = eventConstructor;

    this.singleTagName = undefined;
    this.singleTitle = undefined;

    this.multiTagName = undefined;
    this.multiTitle = undefined;

    // This is computed by rebuildRootSubViewTypeInfos, so don't muck with it!
    this.childrenTypeInfos_ = undefined;
  };

  AnalysisSubViewTypeInfo.prototype = {
    get childrenTypeInfos() {
      return this.childrenTypeInfos_;
    },

    resetchildrenTypeInfos: function() {
      this.childrenTypeInfos_ = [];
    }
  };

  AnalysisSubView.register = function(tagName, eventConstructor, options) {
    var typeInfo = allTypeInfosByEventProto.get(eventConstructor.prototype);
    if (typeInfo === undefined) {
      typeInfo = new AnalysisSubViewTypeInfo(eventConstructor, options);
      allTypeInfosByEventProto.set(typeInfo.eventConstructor.prototype,
                                   typeInfo);

      onlyRootTypeInfosByEventProto = undefined;
    }

    if (!options.multi) {
      if (typeInfo.singleTagName !== undefined)
        throw new Error('SingleTagName already set');
      typeInfo.singleTagName = tagName;
      typeInfo.singleTitle = options.title;
    } else {
      if (typeInfo.multiTagName !== undefined)
        throw new Error('MultiTagName already set');
      typeInfo.multiTagName = tagName;
      typeInfo.multiTitle = options.title;
    }
    return typeInfo;
  };

  function rebuildRootSubViewTypeInfos() {
    onlyRootTypeInfosByEventProto = new Map();
    allTypeInfosByEventProto.forEach(function(typeInfo) {
      typeInfo.resetchildrenTypeInfos();
    });

    // Find all root typeInfos.
    allTypeInfosByEventProto.forEach(function(typeInfo, eventProto) {
      var eventPrototype = typeInfo.eventConstructor.prototype;

      var lastEventProto = eventPrototype;
      var curEventProto = eventPrototype.__proto__;
      while (true) {
        if (!allTypeInfosByEventProto.has(curEventProto)) {
          var rootTypeInfo = allTypeInfosByEventProto.get(lastEventProto);
          var rootEventProto = lastEventProto;

          var isNew = onlyRootTypeInfosByEventProto.has(rootEventProto);
          onlyRootTypeInfosByEventProto.set(rootEventProto,
                                            rootTypeInfo);
          break;
        }

        lastEventProto = curEventProto;
        curEventProto = curEventProto.__proto__;
      }
    });

    // Build the childrenTypeInfos array.
    allTypeInfosByEventProto.forEach(function(typeInfo, eventProto) {
      var eventPrototype = typeInfo.eventConstructor.prototype;
      var parentEventProto = eventPrototype.__proto__;
      var parentTypeInfo = allTypeInfosByEventProto.get(parentEventProto);
      if (!parentTypeInfo)
        return;
      parentTypeInfo.childrenTypeInfos.push(typeInfo);
    });

    // Build the eventProto to rootTypeInfo map.
    eventProtoToRootTypeInfoMap = new Map();
    allTypeInfosByEventProto.forEach(function(typeInfo, eventProto) {
      var eventPrototype = typeInfo.eventConstructor.prototype;

      var curEventProto = eventPrototype;
      while (true) {
        if (onlyRootTypeInfosByEventProto.has(curEventProto)) {
          var rootTypeInfo = onlyRootTypeInfosByEventProto.get(
              curEventProto);
          eventProtoToRootTypeInfoMap.set(eventPrototype,
                                          rootTypeInfo);
          break;
        }
        curEventProto = curEventProto.__proto__;
      }
    });
  }

  function findLowestTypeInfoForEvents(thisTypeInfo, events) {
    if (events.length === 0)
      return thisTypeInfo;
    var event0 = tr.b.getFirstElement(events);

    var candidateSubTypeInfo;
    for (var i = 0; i < thisTypeInfo.childrenTypeInfos.length; i++) {
      var childTypeInfo = thisTypeInfo.childrenTypeInfos[i];
      if (event0 instanceof childTypeInfo.eventConstructor) {
        candidateSubTypeInfo = childTypeInfo;
        break;
      }
    }
    if (!candidateSubTypeInfo)
      return thisTypeInfo;

    // Validate that all the other events are instances of the candidate type.
    var allMatch = true;
    for (var event of events) {
      if (event instanceof candidateSubTypeInfo.eventConstructor)
        continue;
      allMatch = false;
      break;
    }

    if (!allMatch) {
      return thisTypeInfo;
    }

    return findLowestTypeInfoForEvents(candidateSubTypeInfo, events);
  }

  var primaryEventProtoToTypeInfoMap = new Map();
  function getRootTypeInfoForEvent(event) {
    var curProto = event.__proto__;
    var typeInfo = primaryEventProtoToTypeInfoMap.get(curProto);
    if (typeInfo)
      return typeInfo;
    return getRootTypeInfoForEventSlow(event);
  }

  function getRootTypeInfoForEventSlow(event) {
    var typeInfo;
    var curProto = event.__proto__;
    while (true) {
      if (curProto === Object.prototype)
        throw new Error('No view registered for ' + event.toString());
      typeInfo = onlyRootTypeInfosByEventProto.get(curProto);
      if (typeInfo) {
        primaryEventProtoToTypeInfoMap.set(event.__proto__, typeInfo);
        return typeInfo;
      }
      curProto = curProto.__proto__;
    }
  }

  AnalysisSubView.getEventsOrganizedByTypeInfo = function(selection) {
    if (onlyRootTypeInfosByEventProto === undefined)
      rebuildRootSubViewTypeInfos();

    // Base grouping.
    var eventsByRootTypeInfo = tr.b.groupIntoMap(
      selection,
      function(event) {
        return getRootTypeInfoForEvent(event);
      },
      this, tr.model.EventSet);

    // Now, try to lower the typeinfo to the most specific type that still
    // encompasses the event group.
    //
    // For instance,  if we have 3 ThreadSlices, and all three are V8 slices,
    // then we can convert this to use the V8Slices's typeinfos. But, if one
    // of those slices was not a V8Slice, then we must still use
    // ThreadSlice.
    //
    // The reason for this is for the confusion that might arise from the
    // alternative. Suppose you click on a set of mixed slices, we want to show
    // you the most correct information, and let you navigate to . If we instead
    // showed you a V8 slices tab, and a Slices tab, we present the user with an
    // ambiguity: is the V8 slice also in the Slices tab? Or is it not? Better,
    // we think, to just only ever show an event in one place at a time, and
    // avoid the possible confusion.
    var eventsByLowestTypeInfo = new Map();
    eventsByRootTypeInfo.forEach(function(events, typeInfo) {
      var lowestTypeInfo = findLowestTypeInfoForEvents(typeInfo, events);
      eventsByLowestTypeInfo.set(lowestTypeInfo, events);
    });

    return eventsByLowestTypeInfo;
  };

  return {
    AnalysisSubView: AnalysisSubView,
    AnalysisSubViewTypeInfo: AnalysisSubViewTypeInfo
  };
});

// Dummy element for testing
Polymer({
  is: 'tr-ui-a-sub-view',
  behaviors: [tr.ui.analysis.AnalysisSubView]
});
</script>