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/model/user_model/load_expectation.html">
9
10<script>
11'use strict';
12
13tr.exportTo('tr.importer', function() {
14  // This global instant event marks the start of a navigation.
15  var NAVIGATION_START = 'NavigationTiming navigationStart';
16
17  // This render-process instant event marks the first contentful paint in a
18  // main frame.
19  var FIRST_CONTENTFUL_PAINT_TITLE = 'firstContentfulPaint';
20
21  function getAllFrameEvents(modelHelper) {
22    var frameEvents = [];
23    frameEvents.push.apply(frameEvents,
24        modelHelper.browserHelper.getFrameEventsInRange(
25            tr.model.helpers.IMPL_FRAMETIME_TYPE, modelHelper.model.bounds));
26
27    tr.b.iterItems(modelHelper.rendererHelpers, function(pid, renderer) {
28      frameEvents.push.apply(frameEvents, renderer.getFrameEventsInRange(
29          tr.model.helpers.IMPL_FRAMETIME_TYPE, modelHelper.model.bounds));
30    });
31    return frameEvents.sort(tr.importer.compareEvents);
32  }
33
34  // If a thread contains a typical initialization slice, then the first event
35  // on that thread is a startup event.
36  function getStartupEvents(modelHelper) {
37    function isStartupSlice(slice) {
38      return slice.title === 'BrowserMainLoop::CreateThreads';
39    }
40    var events = modelHelper.browserHelper.getAllAsyncSlicesMatching(
41        isStartupSlice);
42    var deduper = new tr.model.EventSet();
43    events.forEach(function(event) {
44      var sliceGroup = event.parentContainer.sliceGroup;
45      var slice = sliceGroup && sliceGroup.findFirstSlice();
46      if (slice)
47        deduper.push(slice);
48    });
49    return deduper.toArray();
50  }
51
52  // Match every event in |openingEvents| to the first following event from
53  // |closingEvents| and return an array containing a load interaction record
54  // for each pair.
55  function findLoadExpectationsInternal(
56      modelHelper, subtypeName, openingEvents, closingEvents) {
57    var loads = [];
58    openingEvents.forEach(function(openingEvent) {
59      closingEvents.forEach(function(closingEvent) {
60        // Ignore opening event that already have a closing event.
61        if (openingEvent.closingEvent)
62          return;
63
64        // Ignore closing events that already belong to an opening event.
65        if (closingEvent.openingEvent)
66          return;
67
68        // Ignore closing events before |openingEvent|.
69        if (closingEvent.start <= openingEvent.start)
70          return;
71
72        // Ignore events from different threads.
73        if (openingEvent.parentContainer.parent.pid !==
74              closingEvent.parentContainer.parent.pid)
75          return;
76
77        // This is the first closing event for this opening event, record it.
78        openingEvent.closingEvent = closingEvent;
79        closingEvent.openingEvent = openingEvent;
80        var lir = new tr.model.um.LoadExpectation(
81            modelHelper.model, subtypeName, openingEvent.start,
82            closingEvent.end - openingEvent.start);
83        lir.associatedEvents.push(openingEvent);
84        lir.associatedEvents.push(closingEvent);
85        loads.push(lir);
86      });
87    });
88    return loads;
89  }
90
91  function findRenderLoadExpectations(modelHelper) {
92    var events = [];
93    modelHelper.model.iterateAllEvents(function(event) {
94      if ((event.title === NAVIGATION_START) ||
95          (event.title === FIRST_CONTENTFUL_PAINT_TITLE))
96        events.push(event);
97    });
98    events.sort(tr.importer.compareEvents);
99
100    var loads = [];
101    var startEvent = undefined;
102    events.forEach(function(event) {
103      if (event.title === NAVIGATION_START) {
104        startEvent = event;
105      } else if (event.title === FIRST_CONTENTFUL_PAINT_TITLE) {
106        if (startEvent) {
107          loads.push(new tr.model.um.LoadExpectation(
108              modelHelper.model, tr.model.um.LOAD_SUBTYPE_NAMES.SUCCESSFUL,
109              startEvent.start, event.start - startEvent.start));
110          startEvent = undefined;
111        }
112      }
113    });
114
115    // If the trace ended between navigation start and first contentful paint,
116    // then make a LoadExpectation that ends at the end of the trace.
117    if (startEvent) {
118      loads.push(new tr.model.um.LoadExpectation(
119            modelHelper.model, tr.model.um.LOAD_SUBTYPE_NAMES.SUCCESSFUL,
120            startEvent.start, modelHelper.model.bounds.max - startEvent.start));
121    }
122
123    return loads;
124  }
125
126  // Match up RenderFrameImpl events with frame render events.
127  function findLoadExpectations(modelHelper) {
128    var loads = [];
129
130    var commitLoadEvents =
131        modelHelper.browserHelper.getCommitProvisionalLoadEventsInRange(
132            modelHelper.model.bounds);
133
134    // Attach frame events to every startup events.
135    var startupEvents = getStartupEvents(modelHelper);
136    var frameEvents = getAllFrameEvents(modelHelper);
137    var startupLoads = findLoadExpectationsInternal(
138        modelHelper, tr.model.um.LOAD_SUBTYPE_NAMES.STARTUP,
139        startupEvents, frameEvents);
140    loads.push.apply(loads, startupLoads);
141
142    loads.push.apply(loads, findRenderLoadExpectations(modelHelper));
143
144    return loads;
145  }
146
147  return {
148    findLoadExpectations: findLoadExpectations
149  };
150});
151</script>
152