// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
module page_load_metrics.mojom;
import "ui/gfx/geometry/mojom/geometry.mojom";
import "mojo/public/mojom/base/shared_memory.mojom";
import "mojo/public/mojom/base/time.mojom";
import
"third_party/blink/public/mojom/loader/javascript_framework_detection.mojom";
import "services/network/public/mojom/request_priority.mojom";
import "third_party/blink/public/mojom/use_counter/use_counter_feature.mojom";
// TimeDeltas below relative to navigation start.
struct DocumentTiming {
// Time immediately before the DOMContentLoaded event is fired.
mojo_base.mojom.TimeDelta? dom_content_loaded_event_start;
// Time immediately before the load event is fired.
mojo_base.mojom.TimeDelta? load_event_start;
};
struct LcpResourceLoadTimings {
// Time when the underlying LCP resource is requested.
mojo_base.mojom.TimeDelta? discovery_time;
// Time when the underlying LCP resource starts loading.
mojo_base.mojom.TimeDelta? load_start;
// Time when the underlying LCP resource finishes loading.
mojo_base.mojom.TimeDelta? load_end;
};
struct LargestContentfulPaintTiming {
// Time when the page's largest image is painted.
mojo_base.mojom.TimeDelta? largest_image_paint;
// Size of the largest image of the largest image paint, by
// Size = Height * Width. Removed images are excluded.
uint64 largest_image_paint_size;
// Time when the page's largest text is painted.
mojo_base.mojom.TimeDelta? largest_text_paint;
// Size of the largest text of the largest text paint, by
// Size = Height * Width. Removed text is excluded.
uint64 largest_text_paint_size;
LcpResourceLoadTimings resource_load_timings;
// These are packed blink::LargestContentfulPaintType enums, indicating
// the largest LCP candidate's type characteristics.
uint64 type;
// Computed entropy of the page's largest image, calculated as the image file
// size, in bits, divided by the image's rendered size, in pixels.
double image_bpp;
// Loading priority used for the LCP image.
// When the priority is not available or the LCP is a video,
// `image_request_priority_valid` is false.
// Otherwise, `image_request_priority_value` contains the priority.
// TODO(crbug.com/40489779): Use an optional enum.
bool image_request_priority_valid = false;
network.mojom.RequestPriority image_request_priority_value;
};
// TimeDeltas below relative to navigation start.
struct PaintTiming {
// Time when the first paint is performed.
mojo_base.mojom.TimeDelta? first_paint;
// Time when the first image is painted.
mojo_base.mojom.TimeDelta? first_image_paint;
// Time when the first contentful thing (image, text, etc.) is painted.
mojo_base.mojom.TimeDelta? first_contentful_paint;
// (Experimental) Time when the page's primary content is painted.
mojo_base.mojom.TimeDelta? first_meaningful_paint;
// Largest contentful paint, which includes removed content.
LargestContentfulPaintTiming largest_contentful_paint;
// (Experimental) largest contentful paint excluding removed content.
LargestContentfulPaintTiming experimental_largest_contentful_paint;
// (Experimental) Time when the frame is first eligible to be painted, i.e.
// is first not render-throttled. Will be null if frame is throttled,
// unless there has already been a |first_paint|.
mojo_base.mojom.TimeDelta? first_eligible_to_paint;
// (Experimental) Time when first input or scroll is received, causing the
// largest contentful paint algorithm to stop.
mojo_base.mojom.TimeDelta? first_input_or_scroll_notified_timestamp;
};
// TimeDeltas below represent durations of time during the page load.
struct ParseTiming {
// Time that the document's parser started and stopped parsing main resource
// content.
mojo_base.mojom.TimeDelta? parse_start;
mojo_base.mojom.TimeDelta? parse_stop;
// Sum of times when the parser is blocked waiting on the load of a script.
// This duration takes place between parser_start and parser_stop, and thus
// must be less than or equal to parser_stop - parser_start. Note that this
// value may be updated multiple times during the period between parse_start
// and parse_stop.
mojo_base.mojom.TimeDelta? parse_blocked_on_script_load_duration;
// Sum of times when the parser is blocked waiting on the load of a script
// that was inserted from document.write. This duration must be less than or
// equal to parse_blocked_on_script_load_duration. Note that this value may be
// updated multiple times during the period between parse_start and
// parse_stop. Note that some uncommon cases where scripts are loaded via
// document.write are not currently covered by this field. See crbug/600711
// for details.
mojo_base.mojom.TimeDelta? parse_blocked_on_script_load_from_document_write_duration;
// Sum of times when the parser is executing a script. This duration takes
// place between parser_start and parser_stop, and thus must be less than or
// equal to parser_stop - parser_start. Note that this value may be updated
// multiple times during the period between parse_start and parse_stop.
mojo_base.mojom.TimeDelta? parse_blocked_on_script_execution_duration;
// Sum of times when the parser is executing a script that was inserted from
// document.write. This duration must be less than or equal to
// parse_blocked_on_script_load_duration. Note that this value may be updated
// multiple times during the period between parse_start and parse_stop. Note
// that some uncommon cases where scripts are loaded via document.write are
// not currently covered by this field. See crbug/600711 for details.
mojo_base.mojom.TimeDelta? parse_blocked_on_script_execution_from_document_write_duration;
};
struct InteractiveTiming {
// Queueing Time of the first click, tap, key press, cancellable touchstart,
// or pointer down followed by a pointer up.
mojo_base.mojom.TimeDelta? first_input_delay;
// The timestamp of the event whose delay is reported by GetFirstInputDelay().
mojo_base.mojom.TimeDelta? first_input_timestamp;
// The latency between user input and display update for the first scroll after
// a navigation.
mojo_base.mojom.TimeDelta? first_scroll_delay;
// The timestamp of the user's first scroll after a navigation.
mojo_base.mojom.TimeDelta? first_scroll_timestamp;
};
struct CustomUserTimingMark {
// The mark name from performance.mark().
string mark_name;
// The startTime from performance.mark().
mojo_base.mojom.TimeDelta start_time;
};
struct DomainLookupTiming {
// Time that the domain lookup started relative to navigation start time.
mojo_base.mojom.TimeDelta? domain_lookup_start;
// Time that the domain lookup ended relative to navigation start time.
mojo_base.mojom.TimeDelta? domain_lookup_end;
};
// PageLoadTiming contains timing metrics associated with a page load. Many of
// the metrics here are based on the Navigation Timing spec:
// http://www.w3.org/TR/navigation-timing/.
struct PageLoadTiming {
// Time that the navigation for the associated page was initiated. Note that
// this field is only used for internal tracking purposes and should not be
// used by PageLoadMetricsObservers. This field will likely be removed in the
// future.
mojo_base.mojom.Time navigation_start;
// Time relative to navigation_start that the network connection is going to
// be established.
mojo_base.mojom.TimeDelta? connect_start;
// Time relative to navigation_start that the first byte of the response is
// received.
mojo_base.mojom.TimeDelta? response_start;
DocumentTiming document_timing;
InteractiveTiming interactive_timing;
PaintTiming paint_timing;
ParseTiming parse_timing;
DomainLookupTiming domain_lookup_timing;
// List of back-forward cache timings, one for each time a page was restored
// from the cache.
array<BackForwardCacheTiming> back_forward_cache_timings;
// Time relative to navigation_start that the prerender activation navigation
// was initiated. This is set for prerendered page loads that were later
// activated.
mojo_base.mojom.TimeDelta? activation_start;
// Time between user input and navigation start. This is set for navigations
// where the input start timing is known; currently when the navigation is
// initiated by a link click in the renderer, or from the desktop omnibox.
mojo_base.mojom.TimeDelta? input_to_navigation_start;
// Time when the standard UserTiming mark `mark_fully_loaded` occurs.
mojo_base.mojom.TimeDelta? user_timing_mark_fully_loaded;
// Time when the standard UserTiming mark `mark_fully_visible` occurs.
mojo_base.mojom.TimeDelta? user_timing_mark_fully_visible;
// Time when the standard UserTiming mark `mark_interactive` occurs.
mojo_base.mojom.TimeDelta? user_timing_mark_interactive;
// If you add additional members, also be sure to update page_load_timing.h.
};
struct FrameMetadata {
// These are packed blink::LoadingBehaviorFlag enums.
int32 behavior_flags = 0;
// For the main frame, the rect is the main frame document size (at (0,0));
// for a subframe, the rect is frame's intersection rect with the main frame
// in the main frame's coordinate system, and is an empty rect when there is
// no intersection with the main frame. This is only set for the first time
// the intersection rectangle is initially computed, and for any subsequent
// changes, and is null otherwise (i.e. hasn't changed).
gfx.mojom.Rect? main_frame_intersection_rect;
// The main frame's viewport rectangle (encapsulating the dimensions and the
// scroll position) in the main frame's coordinate system. This is only set
// for the main frame, for the first time the viewport rectangle is initially
// computed, and for any subsequent changes, and is null otherwise (i.e.
// hasn't changed).
gfx.mojom.Rect? main_frame_viewport_rect;
// The image ad rectangles within the main frame. Empty rectangles are used to
// signal the removal of the rectangle. The map keys are the element ids. This
// is only populated for the main frame, for the first time
// each rectangle is initially computed and for any subsequent changes.
map<int32, gfx.mojom.Rect> main_frame_image_ad_rects;
// Detected versions of JavaScript frameworks in the page.
blink.mojom.JavaScriptFrameworkDetectionResult framework_detection_result;
};
struct SubresourceLoadMetrics {
// Number of subresources loads for the frame.
// Note that this include subresources where a service worker responded.
uint32 number_of_subresources_loaded;
// Number of subresources loads that is responded by the service worker.
// i.e. `respondWith` was called.
uint32 number_of_subresource_loads_handled_by_service_worker;
// Metrics for service workers.
ServiceWorkerSubresourceLoadMetrics? service_worker_subresource_load_metrics;
};
struct ServiceWorkerSubresourceLoadMetrics {
// True even if one image sub resource is handled by a service worker.
// i.e. the service worker called `respondWith` to return the resource.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was handled.
bool image_handled;
// True even if one image sub resource caused a network fallback.
// i.e. the service worker did not call `respondWith`, and network fallback.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was network fallback.
bool image_fallback;
// True even if one CSS sub resource is handled by a service worker.
// i.e. the service worker called `respondWith` to return the resource.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was handled.
bool css_handled;
// True even if one CSS sub resource caused a network fallback.
// i.e. the service worker did not call `respondWith`, and network fallback.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was network fallback.
bool css_fallback;
// True even if one script sub resource is handled by a service worker.
// i.e. the service worker called `respondWith` to return the resource.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was handled.
bool script_handled;
// True even if one script sub resource caused a network fallback.
// i.e. the service worker did not call `respondWith`, and network fallback.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was network fallback.
bool script_fallback;
// True even if one font sub resource is handled by a service worker.
// i.e. the service worker called `respondWith` to return the resource.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was handled.
bool font_handled;
// True even if one font sub resource caused a network fallback.
// i.e. the service worker did not call `respondWith`, and network fallback.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was network fallback.
bool font_fallback;
// True even if one raw sub resource is handled by a service worker.
// i.e. the service worker called `respondWith` to return the resource.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was handled.
bool raw_handled;
// True even if one raw sub resource caused a network fallback.
// i.e. the service worker did not call `respondWith`, and network fallback.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was network fallback.
bool raw_fallback;
// True even if one SVG sub resource is handled by a service worker.
// i.e. the service worker called `respondWith` to return the resource.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was handled.
bool svg_handled;
// True even if one SVG sub resource caused a network fallback.
// i.e. the service worker did not call `respondWith`, and network fallback.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was network fallback.
bool svg_fallback;
// True even if one XSL sub resource is handled by a service worker.
// i.e. the service worker called `respondWith` to return the resource.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was handled.
bool xsl_handled;
// True even if one XSL sub resource caused a network fallback.
// i.e. the service worker did not call `respondWith`, and network fallback.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was network fallback.
bool xsl_fallback;
// True even if one Link Prefetch sub resource is handled by a service worker.
// i.e. the service worker called `respondWith` to return the resource.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was handled.
bool link_prefetch_handled;
// True even if one Link Prefetch sub resource caused a network fallback.
// i.e. the service worker did not call `respondWith`, and network fallback.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was network fallback.
bool link_prefetch_fallback;
// True even if one Text Track sub resource is handled by a service worker.
// i.e. the service worker called `respondWith` to return the resource.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was handled.
bool text_track_handled;
// True even if one Text Track sub resource caused a network fallback.
// i.e. the service worker did not call `respondWith`, and network fallback.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was network fallback.
bool text_track_fallback;
// True even if one audio sub resource is handled by a service worker.
// i.e. the service worker called `respondWith` to return the resource.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was handled.
bool audio_handled;
// True even if one audio sub resource caused a network fallback.
// i.e. the service worker did not call `respondWith`, and network fallback.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was network fallback.
bool audio_fallback;
// True even if one video sub resource is handled by a service worker.
// i.e. the service worker called `respondWith` to return the resource.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was handled.
bool video_handled;
// True even if one video sub resource caused a network fallback.
// i.e. the service worker did not call `respondWith`, and network fallback.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was network fallback.
bool video_fallback;
// True even if one manifest sub resource is handled by a service worker.
// i.e. the service worker called `respondWith` to return the resource.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was handled.
bool manifest_handled;
// True even if one manifest sub resource caused a network fallback.
// i.e. the service worker did not call `respondWith`, and network fallback.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was network fallback.
bool manifest_fallback;
// True even if one speculation rules sub resource is handled by a service
// worker.
// i.e. the service worker called `respondWith` to return the resource.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was handled.
bool speculation_rules_handled;
// True even if one speculation rules sub resource caused a network fallback.
// i.e. the service worker did not call `respondWith`, and network fallback.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was network fallback.
bool speculation_rules_fallback;
// True even if one mock sub resource is handled by a service worker.
// i.e. the service worker called `respondWith` to return the resource.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was handled.
bool mock_handled;
// True even if one mock sub resource caused a network fallback.
// i.e. the service worker did not call `respondWith`, and network fallback.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was network fallback.
bool mock_fallback;
// True even if one dictionary sub resource is handled by a service worker.
// i.e. the service worker called `respondWith` to return the resource.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was handled.
bool dictionary_handled;
// True even if one dictionary sub resource caused a network fallback.
// i.e. the service worker did not call `respondWith`, and network fallback.
// e.g. if there are two resources, and one is handed by the service worker,
// and one did not, this flag will be true because one was network fallback.
bool dictionary_fallback;
// Total number of sub-resources which were matched to
// `RouterSourceEnum.cache` in ServiceWorker Static Routing API, from
// navigation start until onload event.
uint32 matched_cache_router_source_count = 0;
// Total number of sub-resources which were matched to
// `RouterSourceEnum.fetch-event` in ServiceWorker Static Routing API, from
// navigation start until onload event.
uint32 matched_fetch_event_router_source_count = 0;
// Total number of sub-resources which were matched to
// `RouterSourceEnum.network` in ServiceWorker Static Routing API, from
// navigation start until onload event.
uint32 matched_network_router_source_count = 0;
// Total number of sub-resources which were matched to the
// `RouterSourceEnum.race-network-and-fetch-handler` in ServiceWorker Static
// Routing API, from navigation start till onload event.
uint32 matched_race_network_and_fetch_router_source_count = 0;
// Total router evaluation time of ServiceWorker Static Routing API for
// sub-resources.
mojo_base.mojom.TimeDelta total_router_evaluation_time_for_subresources;
};
// Enumeration of distinct cache types.
enum CacheType {
kNotCached, // Resource came from network.
kHttp, // Resource was serviced by the http cache.
kMemory, // Resource was serviced by the Renderer's MemoryCache.
};
struct ResourceDataUpdate {
// The id for the resource request.
int32 request_id = 0;
// Network bytes received for the resource since the last timing update
// from renderer to browser.
int64 delta_bytes = 0;
// Total network bytes received for the resource across timing updates. This
// is the aggregate of the |delta_bytes| from each timing update.
int64 received_data_length = 0;
// The length of the response body for the resource before removing any
// content encodings. Only set for complete resources.
int64 encoded_body_length = 0;
// The length of the response body in bytes for the resource after decoding.
// Only set for complete resources.
int64 decoded_body_length = 0;
// Whether this resource load has completed.
bool is_complete;
// Whether this resource was tagged as an ad in the renderer. This flag can
// be set to true at any point during a resource load. A more recent
// ResourceDataUpdate can have a different flag than the previous update.
// Once this is set to true, it will be true for all future updates.
bool reported_as_ad_resource;
// Whether this resource was loaded in the top-level frame.
bool is_main_frame_resource;
// Which cache this resource originated from, if any.
CacheType cache_type;
// Whether this resource is the primary resource for a frame.
bool is_primary_frame_resource;
// Mime type for the resource found in the network response header.
string mime_type;
// Whether the scheme of this resource indicates a secure connection.
bool is_secure_scheme;
// Whether this resource was fetched via proxy.
bool proxy_used;
};
// Timestamp and layout shift score of a layout shift.
struct LayoutShift {
mojo_base.mojom.TimeTicks layout_shift_time;
double layout_shift_score;
};
// Metrics about how a RenderFrame rendered since the last UpdateTiming call.
struct FrameRenderDataUpdate {
// How much visible elements in the frame shifted (https://bit.ly/3fQz29y) since
// the last timing update.
float layout_shift_delta;
// How much visible elements in the frame shifted (https://bit.ly/3fQz29y),
// before a user input or document scroll, since the last timing update.
float layout_shift_delta_before_input_or_scroll;
// New layout shifts with timestamps.
array<LayoutShift> new_layout_shifts;
};
// Metrics about the time spent in tasks (cpu time) by a frame.
struct CpuTiming {
// Time spent in tasks measured in wall time.
mojo_base.mojom.TimeDelta task_time;
};
// Metrics about general input delay.
struct InputTiming {
// The number of user interactions, including click, tap and key press.
uint64 num_interactions = 0;
// Includes an array of max_event_duration which is the longest input event
// duration within a user interaction. The unit of duration is ms. Currently,
// we send this array to the browser process behind a Finch flag.
// TODO(crbug.com/40198645): Once the experiment is done, we could simplify
// this part.
UserInteractionLatencies max_event_durations;
};
// Data for user interaction latencies which can be meausred in different ways.
// Currently, the array user_interaction_latencies is sent behind a Finch flag.
// If we don't send the array, the maximum value will be sent instead.
union UserInteractionLatencies {
array<UserInteractionLatency> user_interaction_latencies;
// The maximum value in user_interaction_latencies.
mojo_base.mojom.TimeDelta worst_interaction_latency;
};
// The latency and the type of a user interaction.
struct UserInteractionLatency {
mojo_base.mojom.TimeDelta interaction_latency;
UserInteractionType interaction_type;
// The one-based offset of the interaction in time-based order; 1 for the
// first interaction, 2 for the second, etc.
uint64 interaction_offset;
// The time the interaction occurred, relative to navigation start.
mojo_base.mojom.TimeTicks interaction_time;
};
// The type of a user interaction, including keyboard, click or tap and drag.
enum UserInteractionType {
kKeyboard,
kTapOrClick,
kDrag,
};
// Sent from renderer to browser process when the PageLoadTiming for the
// associated frame changed.
interface PageLoadMetrics {
// Called when an update is ready to be sent from renderer to browser.
// UpdateTiming calls are buffered, and contain all updates that have been
// received in the last buffer window. Some of the update data may be empty.
// Only called when at least one change has been observed within the frame.
// Note that counts in `subresource_load_metrics` are cumulative and not a
// delta.
UpdateTiming(PageLoadTiming page_load_timing,
FrameMetadata frame_metadata,
// `new_features` will not contain any previously seen values.
array<blink.mojom.UseCounterFeature> new_features,
array<ResourceDataUpdate> resources,
FrameRenderDataUpdate render_data,
CpuTiming cpu_load_timing,
InputTiming input_timing_delta,
SubresourceLoadMetrics? subresource_load_metrics,
SoftNavigationMetrics soft_navigation_metrics);
// Set up a shared memory used to transfer smoothness data from the renderer
// to the browser. The structure is defined in
// //cc/metrics/ukm_smoothness_data.h
SetUpSharedMemoryForSmoothness(
mojo_base.mojom.ReadOnlySharedMemoryRegion shared_memory);
// Called when performance entries are added via `performance.mark()` from the
// outermost main frame in renderer, except for the case when standard
// UserTiming marks `mark_fully_loaded`, `mark_fully_visible`, and
// `mark_interactive` occur. Unlike `UpdateTiming`, this calls are not
// buffered, and expects it always contains the user timing entry which is not
// sent yet before.
//
// `AddCustomUserTiming` should be used when the expected transport data size
// can be larger. Otherwise `UpdateTiming` is recommended.
AddCustomUserTiming(CustomUserTimingMark custom_user_timing);
};
// TimeDelta below relative to the navigation start of the navigation restoring
// page from the back- forward cache.
struct BackForwardCacheTiming {
// Time when the first paint is performed after the time when the page
// is restored from the back-forward cache.
mojo_base.mojom.TimeDelta first_paint_after_back_forward_cache_restore;
// Times on requestAnimationFrame when the page is restored from the back-
// forward cache.
array<mojo_base.mojom.TimeDelta> request_animation_frames_after_back_forward_cache_restore;
// Queueing Time of the first click, tap, key press, cancellable touchstart,
// or pointer down followed by a pointer up after the time when the page is
// restored from the back-forward cache.
mojo_base.mojom.TimeDelta? first_input_delay_after_back_forward_cache_restore;
};
// Metrics about soft navigation. See soft navigation at https://github.com/WICG/soft-navigations.
// The metrics are to be added in a SoftNavigation Event which is parallel to
// a PageLoad event.
struct SoftNavigationMetrics {
// The number of soft navigations that occur during a page load.
uint64 count;
// The start time of a soft navigation. It is the time the user initializes
// the soft navigation. It is relative to navigation start.
mojo_base.mojom.TimeDelta start_time;
// The renderer side navigation id that increments when a soft navigation
// happens.
string navigation_id;
// The LargestContentfulPaint of a soft navigation, that is, the largest
// contentful paint that happened after a soft navigation is detected and
// before the next soft navigation is detected or page end.
// The timings are relative to the soft navigation start.
LargestContentfulPaintTiming largest_contentful_paint;
};