| <!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> |