blob: 66bae5772d4bfdf785124c2f0adb16b0b98e0a39 [file] [log] [blame]
<!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>