| <!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"> |
| |
| <polymer-element name='tv-e-tquery' extends='tv-c-scripting-object'> |
| <script> |
| 'use strict'; |
| |
| Polymer({ |
| ready: function() { |
| this.timeline_ = undefined; |
| this.parent_ = undefined; |
| this.filterExpression_ = undefined; |
| // Memoized filtering result. |
| this.selection_ = undefined; |
| }, |
| |
| get scriptName() { |
| return '$t'; |
| }, |
| |
| onTimelineChanged: function(t) { |
| this.timeline_ = t; |
| this.selection_ = undefined; |
| }, |
| |
| get timeline() { |
| return this.timeline_; |
| }, |
| |
| // Append a new filter expression to this query and return a query node |
| // that represents the result. |
| filter: function(filterExpression) { |
| var result = document.createElement('tv-e-tquery'); |
| result.timeline_ = this.timeline; |
| result.parent_ = this; |
| // TODO(skyostil): Can we use a static helper function for this? |
| result.filterExpression_ = |
| document.createElement('tv-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 tv.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 tv.c.Selection(); |
| if (node.parent_ === undefined) { |
| // If this is the root, start by collecting all objects from the |
| // timeline. |
| 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 = document.createElement('tv-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 of this query as a highlight on the timeline. Returns a |
| // {Task} which runs the query and sets the highlight. |
| show: function() { |
| var graph = this.createFilterTaskGraph_(); |
| |
| graph.lastTask = graph.lastTask.after(function() { |
| this.timeline.setHighlightAndClearSelection(graph.lastNode.selection_); |
| }, this); |
| return graph.rootTask; |
| }, |
| |
| // Returns a task that fills the given selection with everything in the |
| // timeline. |
| selectEverythingAsTask_: function(selection) { |
| var passThroughFilter = new tv.c.Filter(); |
| var filterTask = |
| this.timeline.addAllObjectsMatchingFilterToSelectionAsTask( |
| passThroughFilter, selection); |
| return filterTask; |
| }, |
| |
| get selection() { |
| if (this.selection_ === undefined) { |
| var graph = this.createFilterTaskGraph_(); |
| tv.b.Task.RunSynchronously(graph.rootTask); |
| } |
| return this.selection_; |
| } |
| }); |
| </script> |
| </polymer-element> |