1<!DOCTYPE html>
2<!--
3Copyright (c) 2015 The Chromium Authors. All rights reserved.
4Use of this source code is governed by a BSD-style license that can be
5found in the LICENSE file.
6-->
7
8<link rel="import" href="/tracing/base/base.html">
9<link rel="import" href="/tracing/base/range_utils.html">
10<link rel="import" href="/tracing/core/auditor.html">
11<link rel="import" href="/tracing/model/event_info.html">
12<link rel="import" href="/tracing/model/user_model/animation_expectation.html">
13<link rel="import" href="/tracing/model/user_model/response_expectation.html">
14
15<script>
16'use strict';
17
18tr.exportTo('tr.importer', function() {
19  // This is an intermediate data format between InputLatencyAsyncSlices and
20  // Response and Animation IRs.
21  function ProtoExpectation(irType, name) {
22    this.irType = irType;
23    this.names = new Set(name ? [name] : undefined);
24    this.start = Infinity;
25    this.end = -Infinity;
26    this.associatedEvents = new tr.model.EventSet();
27    this.isAnimationBegin = false;
28  }
29
30  ProtoExpectation.RESPONSE_TYPE = 'r';
31  ProtoExpectation.ANIMATION_TYPE = 'a';
32
33  // Explicitly ignore some input events to allow
34  // UserModelBuilder.checkAllInputEventsHandled() to determine which events
35  // were unintentionally ignored due to a bug.
36  ProtoExpectation.IGNORED_TYPE = 'ignored';
37
38  ProtoExpectation.prototype = {
39    get isValid() {
40      return this.end > this.start;
41    },
42
43    // Return true if any associatedEvent's typeName is in typeNames.
44    containsTypeNames: function(typeNames) {
45      for (var i = 0; i < this.associatedEvents.length; ++i) {
46        if (typeNames.indexOf(this.associatedEvents[i].typeName) >= 0)
47          return true;
48      }
49      return false;
50    },
51
52    containsSliceTitle: function(title) {
53      for (var i = 0; i < this.associatedEvents.length; ++i) {
54        if (title === this.associatedEvents[i].title)
55          return true;
56      }
57      return false;
58    },
59
60    createInteractionRecord: function(model) {
61      if (!this.isValid) {
62        console.error('Invalid ProtoExpectation: ' + this.debug() +
63                      ' File a bug with this trace!');
64        return undefined;
65      }
66
67      var initiatorTitles = [];
68      this.names.forEach(function(name) {
69        initiatorTitles.push(name);
70      });
71      initiatorTitles = initiatorTitles.sort().join(',');
72
73      var duration = this.end - this.start;
74
75      var ir = undefined;
76      switch (this.irType) {
77        case ProtoExpectation.RESPONSE_TYPE:
78          ir = new tr.model.um.ResponseExpectation(
79              model, initiatorTitles, this.start, duration,
80              this.isAnimationBegin);
81          break;
82        case ProtoExpectation.ANIMATION_TYPE:
83          ir = new tr.model.um.AnimationExpectation(
84              model, initiatorTitles, this.start, duration);
85          break;
86      }
87      if (!ir)
88        return undefined;
89
90      ir.sourceEvents.addEventSet(this.associatedEvents);
91
92      function pushAssociatedEvents(event) {
93        ir.associatedEvents.push(event);
94
95        // |event| is either an InputLatencyAsyncSlice (which collects all of
96        // its associated events transitively) or a CSS Animation (which doesn't
97        // have any associated events). So this does not need to recurse.
98        if (event.associatedEvents)
99          ir.associatedEvents.addEventSet(event.associatedEvents);
100      }
101
102      this.associatedEvents.forEach(function(event) {
103        pushAssociatedEvents(event);
104
105        // Old-style InputLatencyAsyncSlices have subSlices.
106        if (event.subSlices)
107          event.subSlices.forEach(pushAssociatedEvents);
108      });
109
110      return ir;
111    },
112
113    // Merge the other ProtoExpectation into this one.
114    // The irTypes need not match: ignored ProtoExpectations might be merged
115    // into overlapping ProtoExpectations, and Touch-only Animations are merged
116    // into Tap Responses.
117    merge: function(other) {
118      other.names.forEach(function(name) { this.names.add(name); }.bind(this));
119
120      // Don't use pushEvent(), which would lose special start, end.
121      this.associatedEvents.addEventSet(other.associatedEvents);
122      this.start = Math.min(this.start, other.start);
123      this.end = Math.max(this.end, other.end);
124      if (other.isAnimationBegin)
125        this.isAnimationBegin = true;
126    },
127
128    // Include |event| in this ProtoExpectation, expanding start/end to include
129    // it.
130    pushEvent: function(event) {
131      // Usually, this method will be called while iterating over a list of
132      // events sorted by start time, so this method won't usually change
133      // this.start. However, this will sometimes be called for
134      // ProtoExpectations created by previous handlers, in which case
135      // event.start could possibly be before this.start.
136      this.start = Math.min(this.start, event.start);
137      this.end = Math.max(this.end, event.end);
138      this.associatedEvents.push(event);
139    },
140
141    // Returns true if timestamp is contained in this ProtoExpectation.
142    containsTimestampInclusive: function(timestamp) {
143      return (this.start <= timestamp) && (timestamp <= this.end);
144    },
145
146    // Return true if the other event intersects this ProtoExpectation.
147    intersects: function(other) {
148      // http://stackoverflow.com/questions/325933
149      return (other.start < this.end) && (other.end > this.start);
150    },
151
152    isNear: function(event, threshold) {
153      return (this.end + threshold) > event.start;
154    },
155
156    // Return a string describing this ProtoExpectation for debugging.
157    debug: function() {
158      var debugString = this.irType + '(';
159      debugString += parseInt(this.start) + ' ';
160      debugString += parseInt(this.end);
161      this.associatedEvents.forEach(function(event) {
162        debugString += ' ' + event.typeName;
163      });
164      return debugString + ')';
165    }
166  };
167
168  return {
169    ProtoExpectation: ProtoExpectation
170  };
171});
172</script>
173