blob: c61cca1047dc08e6230316618d31cda259c3776d [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright 2019 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="/tracing/base/base.html">
<script>
'use strict';
tr.exportTo('tr.e.chrome', function() {
const LCP_CANDIDATE_EVENT_TITLE =
'NavStartToLargestContentfulPaint::Candidate::AllFrames::UKM';
const LCP_INVALIDATE_EVENT_TITLE =
'NavStartToLargestContentfulPaint::Invalidate::AllFrames::UKM';
class LcpEvent {
constructor(event) {
if (!LcpInvalidateEvent.isLcpInvalidateEvent(event) &&
!LcpCandidateEvent.isLcpCandidateEvent(event)) {
throw new Error('The LCP event should be either a candidate event or' +
'an invalidate event.');
}
if (event.start === undefined ||
event.args.main_frame_tree_node_id === undefined) {
throw new Error('The LCP event is in unexpected format.');
}
this.start = event.start;
this.mainFrameTreeNodeId = event.args.main_frame_tree_node_id;
}
}
/**
* This class is a wrapper of the Largest Contentful Paint Candidate event
* from Chrome trace. It verifies Telemetry's assumption of the event
* format.
*/
class LcpCandidateEvent extends LcpEvent {
constructor(event) {
super(event);
const { durationInMilliseconds, size, type,
inMainFrame} = event.args.data;
if (durationInMilliseconds === undefined || size === undefined ||
type === undefined || inMainFrame === undefined ||
event.args.main_frame_tree_node_id === undefined ||
!LcpCandidateEvent.isLcpCandidateEvent(event)) {
throw new Error('The LCP candidate event is in unexpected format.');
}
this.durationInMilliseconds = durationInMilliseconds;
this.size = size;
this.type = type;
this.inMainFrame = inMainFrame;
}
static isLcpCandidateEvent(event) {
return event.title === LCP_CANDIDATE_EVENT_TITLE;
}
}
/**
* This class is a wrapper of the Largest Contentful Paint invalidate event
* from Chrome trace. It verifies Telemetry's assumption of the event
* format.
*/
class LcpInvalidateEvent extends LcpEvent {
constructor(event) {
super(event);
if (!LcpInvalidateEvent.isLcpInvalidateEvent(event)) {
throw new Error('The LCP invalidate event is in unexpected format.');
}
}
static isLcpInvalidateEvent(event) {
return event.title === LCP_INVALIDATE_EVENT_TITLE;
}
}
/**
* |LargestContentfulPaint| is responsible for finding out the
* largest-contentful-paints from the browser events.
*/
class LargestContentfulPaint {
constructor(allBrowserEvents) {
this.allBrowserEvents = allBrowserEvents;
}
findCandidates() {
const finalLcpEvents = this.findFinalLcpEventOfEachNavigation(
this.allBrowserEvents);
const finalCandidates = finalLcpEvents
.filter(finalLcpEvent =>
!LcpInvalidateEvent.isLcpInvalidateEvent(finalLcpEvent));
return finalCandidates;
}
/**
* Returns an array of |LcpEvent|, each |LcpEvent| represents the last
* |LcpEvent| of a navigation.
*
* @param {Array.<!tr.model.TimedEvent>} browser events.
* @returns {Array.<!Endpoint>}
*/
findFinalLcpEventOfEachNavigation(allBrowserEvents) {
const lcpEvents = [];
for (const lcpEvent of allBrowserEvents) {
if (LcpCandidateEvent.isLcpCandidateEvent(lcpEvent)) {
lcpEvents.push(new LcpCandidateEvent(lcpEvent));
} else if (LcpInvalidateEvent.isLcpInvalidateEvent(lcpEvent)) {
lcpEvents.push(new LcpInvalidateEvent(lcpEvent));
}
}
const lcpEventsGroupedByNavigation = new Map();
for (const e of lcpEvents) {
const key = e.mainFrameTreeNodeId;
if (!lcpEventsGroupedByNavigation.has(key)) {
lcpEventsGroupedByNavigation.set(key, []);
}
lcpEventsGroupedByNavigation.get(key).push(e);
}
const finalLcpEventOfEachNavigation = [];
for (const lcpEventList of lcpEventsGroupedByNavigation.values()) {
lcpEventList.sort((a, b) => a.start - b.start);
finalLcpEventOfEachNavigation.push(
lcpEventList[lcpEventList.length - 1]);
}
return finalLcpEventOfEachNavigation;
}
}
return {
LCP_CANDIDATE_EVENT_TITLE,
LCP_INVALIDATE_EVENT_TITLE,
LargestContentfulPaint,
};
});
</script>