blob: c3bbb02b2612a8863c9880504991cf6b7d2f0891 [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright 2015 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="/base/task.html">
<link rel="import" href="/core/filter.html">
<link rel="import" href="/core/selection.html">
<link rel="import" href="/core/scripting_object.html">
<link rel="import" href="/extras/tquery/context.html">
<link rel="import" href="/extras/tquery/filter_all_of.html">
<link rel="import" href="/extras/tquery/filter_any_of.html">
<link rel="import" href="/extras/tquery/filter_has_ancestor.html">
<link rel="import" href="/extras/tquery/filter_has_duration.html">
<link rel="import" href="/extras/tquery/filter_has_title.html">
<link rel="import" href="/extras/tquery/filter_is_top_level.html">
<script>
'use strict';
tr.exportTo('tr.e.tquery', function() {
function TQuery(selectionController) {
tr.c.ScriptingObject.call(this);
this.selectionController_ = selectionController;
this.parent_ = undefined;
this.filterExpression_ = undefined;
// Memoized filtering result.
this.selection_ = undefined;
};
TQuery.prototype = {
__proto__: tr.c.ScriptingObject.prototype,
onModelChanged: function() {
this.selection_ = undefined;
},
get selectionController() {
return this.selectionController_;
},
// Append a new filter expression to this query and return a query node
// that represents the result.
filter: function(filterExpression) {
var result = new TQuery(this.selectionController_);
result.parent_ = this;
result.filterExpression_ =
tr.e.tquery.Filter.normalizeFilterExpression(filterExpression);
return result;
},
// Creates a graph of {Task} objects which will compute the selections for
// this filter object and all of its parents. The return value is an object
// with the following fields:
// - rootTask: {Task} which should be executed to kick off processing for
// the entire task graph.
// - lastTask: The final {Task} of the graph. Can be used by the caller to
// enqueue additional processing at the end.
// - lastNode: The last filter object in the task. It's selection property
// will contain the filtering result once |finalTask|
// completes.
createFilterTaskGraph_: function() {
// List of nodes in order from the current one to the root.
var nodes = [];
var node = this;
while (node !== undefined) {
nodes.push(node);
node = node.parent_;
}
var rootTask = new tr.b.Task();
var lastTask = rootTask;
for (var i = nodes.length - 1; i >= 0; i--) {
var node = nodes[i];
// Reuse any memoized result.
if (node.selection_ !== undefined)
continue;
node.selection_ = new tr.c.Selection();
if (node.parent_ === undefined) {
// If this is the root, start by collecting all objects from the
// selection controller.
lastTask = lastTask.after(
this.selectEverythingAsTask_(node.selection_));
} else {
// Otherwise execute the filter expression for this node and fill
// in its selection.
var prevNode = nodes[i + 1];
lastTask = this.createFilterTaskForNode_(lastTask, node, prevNode);
}
}
return {rootTask: rootTask, lastTask: lastTask, lastNode: node};
},
createFilterTaskForNode_: function(lastTask, node, prevNode) {
return lastTask.after(function() {
// TODO(skyostil): Break into subtasks.
node.evaluateFilterExpression_(
prevNode.selection_, node.selection_);
}, this);
},
// Applies the result of a filter expression for a given event and all
// of its subslices and adds the matching events to an output selection.
evaluateFilterExpression_: function(inputSelection, outputSelection) {
var seenEvents = {};
inputSelection.forEach(function(event) {
var context = new tr.e.tquery.Context();
context.event = event;
this.evaluateFilterExpressionForEvent_(
context, inputSelection, outputSelection, seenEvents);
}.bind(this));
},
evaluateFilterExpressionForEvent_: function(
context, inputSelection, outputSelection, seenEvents) {
var event = context.event;
if (inputSelection.contains(event) && !seenEvents[event.guid]) {
seenEvents[event.guid] = true;
if (!this.filterExpression_ ||
this.filterExpression_.evaluate(context))
outputSelection.push(event);
}
if (!event.subSlices)
return;
context = context.push(event);
for (var i = 0; i < event.subSlices.length; i++) {
context.event = event.subSlices[i];
this.evaluateFilterExpressionForEvent_(
context, inputSelection, outputSelection, seenEvents);
}
},
// Show the result as a highlight on the selection controller. Returns a
// {Task} which runs the query and sets the highlight.
show: function() {
var graph = this.createFilterTaskGraph_();
graph.lastTask = graph.lastTask.after(function() {
this.selectionController.showScriptControlSelection(
graph.lastNode.selection_);
}, this);
return graph.rootTask;
},
// Returns a task that fills the given selection with everything reachable
// by the selection controller.
selectEverythingAsTask_: function(selection) {
var passThroughFilter = new tr.c.Filter();
var filterTask =
this.selectionController.addAllEventsMatchingFilterToSelectionAsTask(
passThroughFilter, selection);
return filterTask;
},
get selection() {
if (this.selection_ === undefined) {
var graph = this.createFilterTaskGraph_();
tr.b.Task.RunSynchronously(graph.rootTask);
}
return this.selection_;
}
};
tr.c.ScriptingObjectRegistry.register(
new TQuery(),
{
name: '$t'
}
);
return {
TQuery: TQuery
};
});
</script>