1 <!DOCTYPE html> 2 <!-- 3 Copyright 2015 The Chromium Authors. All rights reserved. 4 Use of this source code is governed by a BSD-style license that can be 5 found in the LICENSE file. 6 --> 7 8 <link rel="import" href="/base/task.html"> 9 <link rel="import" href="/core/filter.html"> 10 <link rel="import" href="/core/scripting_object.html"> 11 <link rel="import" href="/extras/tquery/context.html"> 12 <link rel="import" href="/extras/tquery/filter_all_of.html"> 13 <link rel="import" href="/extras/tquery/filter_any_of.html"> 14 <link rel="import" href="/extras/tquery/filter_has_ancestor.html"> 15 <link rel="import" href="/extras/tquery/filter_has_duration.html"> 16 <link rel="import" href="/extras/tquery/filter_has_title.html"> 17 <link rel="import" href="/extras/tquery/filter_is_top_level.html"> 18 <link rel="import" href="/model/event_set.html"> 19 20 <script> 21 'use strict'; 22 23 tr.exportTo('tr.e.tquery', function() { 24 function TQuery(brushingStateController) { 25 tr.c.ScriptingObject.call(this); 26 27 this.brushingStateController_ = brushingStateController; 28 this.parent_ = undefined; 29 this.filterExpression_ = undefined; 30 // Memoized filtering result. 31 this.selection_ = undefined; 32 }; 33 34 TQuery.prototype = { 35 __proto__: tr.c.ScriptingObject.prototype, 36 37 onModelChanged: function() { 38 this.selection_ = undefined; 39 }, 40 41 get brushingStateController() { 42 return this.brushingStateController_; 43 }, 44 45 // Append a new filter expression to this query and return a query node 46 // that represents the result. 47 filter: function(filterExpression) { 48 var result = new TQuery(this.brushingStateController_); 49 result.parent_ = this; 50 result.filterExpression_ = 51 tr.e.tquery.Filter.normalizeFilterExpression(filterExpression); 52 return result; 53 }, 54 55 // Creates a graph of {Task} objects which will compute the selections for 56 // this filter object and all of its parents. The return value is an object 57 // with the following fields: 58 // - rootTask: {Task} which should be executed to kick off processing for 59 // the entire task graph. 60 // - lastTask: The final {Task} of the graph. Can be used by the caller to 61 // enqueue additional processing at the end. 62 // - lastNode: The last filter object in the task. It's selection property 63 // will contain the filtering result once |finalTask| 64 // completes. 65 createFilterTaskGraph_: function() { 66 // List of nodes in order from the current one to the root. 67 var nodes = []; 68 var node = this; 69 while (node !== undefined) { 70 nodes.push(node); 71 node = node.parent_; 72 } 73 74 var rootTask = new tr.b.Task(); 75 var lastTask = rootTask; 76 for (var i = nodes.length - 1; i >= 0; i--) { 77 var node = nodes[i]; 78 // Reuse any memoized result. 79 if (node.selection_ !== undefined) 80 continue; 81 node.selection_ = new tr.model.EventSet(); 82 if (node.parent_ === undefined) { 83 // If this is the root, start by collecting all objects from the 84 // brushing state controller. 85 lastTask = lastTask.after( 86 this.selectEverythingAsTask_(node.selection_)); 87 } else { 88 // Otherwise execute the filter expression for this node and fill 89 // in its selection. 90 var prevNode = nodes[i + 1]; 91 lastTask = this.createFilterTaskForNode_(lastTask, node, prevNode); 92 } 93 } 94 return {rootTask: rootTask, lastTask: lastTask, lastNode: node}; 95 }, 96 97 createFilterTaskForNode_: function(lastTask, node, prevNode) { 98 return lastTask.after(function() { 99 // TODO(skyostil): Break into subtasks. 100 node.evaluateFilterExpression_( 101 prevNode.selection_, node.selection_); 102 }, this); 103 }, 104 105 // Applies the result of a filter expression for a given event and all 106 // of its subslices and adds the matching events to an output selection. 107 evaluateFilterExpression_: function(inputSelection, outputSelection) { 108 var seenEvents = {}; 109 inputSelection.forEach(function(event) { 110 var context = new tr.e.tquery.Context(); 111 context.event = event; 112 this.evaluateFilterExpressionForEvent_( 113 context, inputSelection, outputSelection, seenEvents); 114 }.bind(this)); 115 }, 116 117 evaluateFilterExpressionForEvent_: function( 118 context, inputSelection, outputSelection, seenEvents) { 119 var event = context.event; 120 if (inputSelection.contains(event) && !seenEvents[event.guid]) { 121 seenEvents[event.guid] = true; 122 if (!this.filterExpression_ || 123 this.filterExpression_.evaluate(context)) 124 outputSelection.push(event); 125 } 126 if (!event.subSlices) 127 return; 128 context = context.push(event); 129 for (var i = 0; i < event.subSlices.length; i++) { 130 context.event = event.subSlices[i]; 131 this.evaluateFilterExpressionForEvent_( 132 context, inputSelection, outputSelection, seenEvents); 133 } 134 }, 135 136 // Show the result as a highlight on the brushing state controller. Returns 137 // a {Task} which runs the query and sets the highlight. 138 show: function() { 139 var graph = this.createFilterTaskGraph_(); 140 141 graph.lastTask = graph.lastTask.after(function() { 142 this.brushingStateController.showScriptControlSelection( 143 graph.lastNode.selection_); 144 }, this); 145 return graph.rootTask; 146 }, 147 148 // Returns a task that fills the given selection with everything reachable 149 // by the brushing state controller. 150 selectEverythingAsTask_: function(selection) { 151 var passThroughFilter = new tr.c.Filter(); 152 var filterTask = this.brushingStateController. 153 addAllEventsMatchingFilterToSelectionAsTask(passThroughFilter, 154 selection); 155 return filterTask; 156 }, 157 158 get selection() { 159 if (this.selection_ === undefined) { 160 var graph = this.createFilterTaskGraph_(); 161 tr.b.Task.RunSynchronously(graph.rootTask); 162 } 163 return this.selection_; 164 } 165 }; 166 tr.c.ScriptingObjectRegistry.register( 167 new TQuery(), 168 { 169 name: '$t' 170 } 171 ); 172 173 return { 174 TQuery: TQuery 175 }; 176 }); 177 </script> 178