| // Copyright 2015 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. |
| |
| #include "components/page_load_metrics/renderer/metrics_render_frame_observer.h" |
| |
| #include <string> |
| #include <utility> |
| |
| #include "base/memory/ptr_util.h" |
| #include "base/time/time.h" |
| #include "base/timer/timer.h" |
| #include "components/page_load_metrics/renderer/page_timing_metrics_sender.h" |
| #include "components/page_load_metrics/renderer/page_timing_sender.h" |
| #include "content/public/renderer/render_frame.h" |
| #include "services/network/public/mojom/url_response_head.mojom.h" |
| #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" |
| #include "third_party/blink/public/common/loader/resource_type_util.h" |
| #include "third_party/blink/public/common/mobile_metrics/mobile_friendliness.h" |
| #include "third_party/blink/public/web/web_document.h" |
| #include "third_party/blink/public/web/web_document_loader.h" |
| #include "third_party/blink/public/web/web_local_frame.h" |
| #include "third_party/blink/public/web/web_performance.h" |
| #include "url/gurl.h" |
| |
| namespace page_load_metrics { |
| |
| namespace { |
| |
| base::TimeDelta ClampDelta(double event, double start) { |
| if (event - start < 0) |
| event = start; |
| return base::Time::FromDoubleT(event) - base::Time::FromDoubleT(start); |
| } |
| |
| base::TimeTicks ClampToStart(base::TimeTicks event, base::TimeTicks start) { |
| return event < start ? start : event; |
| } |
| |
| class MojoPageTimingSender : public PageTimingSender { |
| public: |
| explicit MojoPageTimingSender(content::RenderFrame* render_frame, |
| bool limited_sending_mode) |
| : limited_sending_mode_(limited_sending_mode) { |
| DCHECK(render_frame); |
| render_frame->GetRemoteAssociatedInterfaces()->GetInterface( |
| &page_load_metrics_); |
| } |
| |
| ~MojoPageTimingSender() override = default; |
| |
| void SendTiming(const mojom::PageLoadTimingPtr& timing, |
| const mojom::FrameMetadataPtr& metadata, |
| const std::vector<blink::UseCounterFeature>& new_features, |
| std::vector<mojom::ResourceDataUpdatePtr> resources, |
| const mojom::FrameRenderDataUpdate& render_data, |
| const mojom::CpuTimingPtr& cpu_timing, |
| mojom::DeferredResourceCountsPtr new_deferred_resource_data, |
| mojom::InputTimingPtr input_timing_delta, |
| const absl::optional<blink::MobileFriendliness>& |
| mobile_friendliness) override { |
| DCHECK(page_load_metrics_); |
| page_load_metrics_->UpdateTiming( |
| limited_sending_mode_ ? CreatePageLoadTiming() : timing->Clone(), |
| metadata->Clone(), new_features, std::move(resources), |
| render_data.Clone(), cpu_timing->Clone(), |
| std::move(new_deferred_resource_data), std::move(input_timing_delta), |
| std::move(mobile_friendliness)); |
| } |
| |
| void SetUpSmoothnessReporting( |
| base::ReadOnlySharedMemoryRegion shared_memory) override { |
| DCHECK(page_load_metrics_); |
| page_load_metrics_->SetUpSharedMemoryForSmoothness( |
| std::move(shared_memory)); |
| } |
| |
| private: |
| // Indicates that this sender should not send timing updates or frame render |
| // data updates. |
| // TODO(https://crbug.com/1097127): When timing updates are handled for cases |
| // where we have a subframe document and no committed navigation, this can be |
| // removed. |
| bool limited_sending_mode_ = false; |
| |
| // Use associated interface to make sure mojo messages are ordered with regard |
| // to legacy IPC messages. |
| mojo::AssociatedRemote<mojom::PageLoadMetrics> page_load_metrics_; |
| }; |
| |
| } // namespace |
| |
| MetricsRenderFrameObserver::MetricsRenderFrameObserver( |
| content::RenderFrame* render_frame) |
| : content::RenderFrameObserver(render_frame) {} |
| |
| MetricsRenderFrameObserver::~MetricsRenderFrameObserver() { |
| if (page_timing_metrics_sender_) |
| page_timing_metrics_sender_->SendLatest(); |
| } |
| |
| void MetricsRenderFrameObserver::DidChangePerformanceTiming() { |
| SendMetrics(); |
| } |
| |
| void MetricsRenderFrameObserver::DidObserveInputDelay( |
| base::TimeDelta input_delay) { |
| if (!page_timing_metrics_sender_ || HasNoRenderFrame()) { |
| return; |
| } |
| page_timing_metrics_sender_->DidObserveInputDelay(input_delay); |
| } |
| |
| void MetricsRenderFrameObserver::DidObserveUserInteraction( |
| base::TimeDelta max_event_duration, |
| base::TimeDelta total_event_duration, |
| blink::UserInteractionType interaction_type) { |
| if (!page_timing_metrics_sender_ || HasNoRenderFrame()) { |
| return; |
| } |
| page_timing_metrics_sender_->DidObserveUserInteraction( |
| max_event_duration, total_event_duration, interaction_type); |
| } |
| |
| void MetricsRenderFrameObserver::DidChangeCpuTiming(base::TimeDelta time) { |
| if (!page_timing_metrics_sender_) |
| return; |
| if (HasNoRenderFrame()) |
| return; |
| page_timing_metrics_sender_->UpdateCpuTiming(time); |
| } |
| |
| void MetricsRenderFrameObserver::DidObserveLoadingBehavior( |
| blink::LoadingBehaviorFlag behavior) { |
| if (page_timing_metrics_sender_) |
| page_timing_metrics_sender_->DidObserveLoadingBehavior(behavior); |
| } |
| |
| void MetricsRenderFrameObserver::DidObserveNewFeatureUsage( |
| const blink::UseCounterFeature& feature) { |
| if (page_timing_metrics_sender_) |
| page_timing_metrics_sender_->DidObserveNewFeatureUsage(feature); |
| } |
| |
| void MetricsRenderFrameObserver::DidObserveLayoutShift( |
| double score, |
| bool after_input_or_scroll) { |
| if (page_timing_metrics_sender_) |
| page_timing_metrics_sender_->DidObserveLayoutShift(score, |
| after_input_or_scroll); |
| } |
| |
| void MetricsRenderFrameObserver::DidObserveLayoutNg( |
| uint32_t all_block_count, |
| uint32_t ng_block_count, |
| uint32_t all_call_count, |
| uint32_t ng_call_count, |
| uint32_t flexbox_ng_block_count, |
| uint32_t grid_ng_block_count) { |
| if (page_timing_metrics_sender_) |
| page_timing_metrics_sender_->DidObserveLayoutNg( |
| all_block_count, ng_block_count, all_call_count, ng_call_count, |
| flexbox_ng_block_count, grid_ng_block_count); |
| } |
| |
| void MetricsRenderFrameObserver::DidObserveLazyLoadBehavior( |
| blink::WebLocalFrameClient::LazyLoadBehavior lazy_load_behavior) { |
| if (page_timing_metrics_sender_) |
| page_timing_metrics_sender_->DidObserveLazyLoadBehavior(lazy_load_behavior); |
| } |
| |
| void MetricsRenderFrameObserver::DidStartResponse( |
| const GURL& response_url, |
| int request_id, |
| const network::mojom::URLResponseHead& response_head, |
| network::mojom::RequestDestination request_destination) { |
| if (provisional_frame_resource_data_use_ && |
| blink::IsRequestDestinationFrame(request_destination)) { |
| // TODO(rajendrant): This frame request might start before the provisional |
| // load starts, and data use of the frame request might be missed in that |
| // case. There should be a guarantee that DidStartProvisionalLoad be called |
| // before DidStartResponse for the frame request. |
| provisional_frame_resource_data_use_->DidStartResponse( |
| response_url, request_id, response_head, request_destination); |
| } else if (page_timing_metrics_sender_) { |
| page_timing_metrics_sender_->DidStartResponse( |
| response_url, request_id, response_head, request_destination); |
| UpdateResourceMetadata(request_id); |
| } |
| } |
| |
| void MetricsRenderFrameObserver::DidCompleteResponse( |
| int request_id, |
| const network::URLLoaderCompletionStatus& status) { |
| if (provisional_frame_resource_data_use_ && |
| provisional_frame_resource_data_use_->resource_id() == request_id) { |
| provisional_frame_resource_data_use_->DidCompleteResponse(status); |
| } else if (page_timing_metrics_sender_) { |
| page_timing_metrics_sender_->DidCompleteResponse(request_id, status); |
| MaybeSetCompletedBeforeFCP(request_id); |
| UpdateResourceMetadata(request_id); |
| } |
| } |
| |
| void MetricsRenderFrameObserver::DidCancelResponse(int request_id) { |
| if (provisional_frame_resource_data_use_ && |
| provisional_frame_resource_data_use_->resource_id() == request_id) { |
| provisional_frame_resource_data_use_.reset(); |
| } else if (page_timing_metrics_sender_) { |
| page_timing_metrics_sender_->DidCancelResponse(request_id); |
| UpdateResourceMetadata(request_id); |
| } |
| } |
| |
| void MetricsRenderFrameObserver::DidReceiveTransferSizeUpdate( |
| int request_id, |
| int received_data_length) { |
| if (provisional_frame_resource_data_use_ && |
| provisional_frame_resource_data_use_->resource_id() == request_id) { |
| provisional_frame_resource_data_use_->DidReceiveTransferSizeUpdate( |
| received_data_length); |
| } else if (page_timing_metrics_sender_) { |
| page_timing_metrics_sender_->DidReceiveTransferSizeUpdate( |
| request_id, received_data_length); |
| UpdateResourceMetadata(request_id); |
| } |
| } |
| |
| void MetricsRenderFrameObserver::DidLoadResourceFromMemoryCache( |
| const GURL& response_url, |
| int request_id, |
| int64_t encoded_body_length, |
| const std::string& mime_type, |
| bool from_archive) { |
| // Resources from archives, such as subresources from a MHTML archive, do not |
| // have valid request ids and should not be reported to PLM. |
| if (from_archive) |
| return; |
| |
| // A provisional frame resource cannot be serviced from the memory cache, so |
| // we do not need to check |provisional_frame_resource_data_use_|. |
| if (page_timing_metrics_sender_) { |
| page_timing_metrics_sender_->DidLoadResourceFromMemoryCache( |
| response_url, request_id, encoded_body_length, mime_type); |
| MaybeSetCompletedBeforeFCP(request_id); |
| UpdateResourceMetadata(request_id); |
| } |
| } |
| |
| void MetricsRenderFrameObserver::WillDetach() { |
| if (page_timing_metrics_sender_) { |
| page_timing_metrics_sender_->SendLatest(); |
| page_timing_metrics_sender_.reset(); |
| } |
| } |
| |
| void MetricsRenderFrameObserver::DidStartNavigation( |
| const GURL& url, |
| absl::optional<blink::WebNavigationType> navigation_type) { |
| // Send current metrics, as we might create a new RenderFrame later due to |
| // this navigation (that might end up in a different process entirely, and |
| // won't notify us until the current RenderFrameHost in the browser changed). |
| // If that happens, it will be too late to send the metrics from WillDetach |
| // or the destructor, because the browser ignores metrics update from |
| // non-current RenderFrameHosts. See crbug.com/1150242 for more details. |
| // TODO(crbug.com/1150242): Remove this when we have the full fix for the bug. |
| if (page_timing_metrics_sender_) |
| page_timing_metrics_sender_->SendLatest(); |
| } |
| |
| void MetricsRenderFrameObserver::DidSetPageLifecycleState() { |
| // Send current metrics, as this RenderFrame might be replaced by a new |
| // RenderFrame or its process might be killed, and this might be the last |
| // point we can send the metrics to the browser. See crbug.com/1150242 for |
| // more details. |
| // TODO(crbug.com/1150242): Remove this when we have the full fix for the bug. |
| if (page_timing_metrics_sender_) |
| page_timing_metrics_sender_->SendLatest(); |
| } |
| |
| void MetricsRenderFrameObserver::ReadyToCommitNavigation( |
| blink::WebDocumentLoader* document_loader) { |
| // Create a new data use tracker for the new document load. |
| provisional_frame_resource_data_use_ = |
| std::make_unique<PageResourceDataUse>(); |
| provisional_frame_resource_id_ = 0; |
| |
| // Send current metrics before the next page load commits. Don't reset here |
| // as it may be a same document load. |
| if (page_timing_metrics_sender_) |
| page_timing_metrics_sender_->SendLatest(); |
| } |
| |
| void MetricsRenderFrameObserver::DidFailProvisionalLoad() { |
| // Clear the data use tracker for the provisional navigation that started. |
| provisional_frame_resource_data_use_.reset(); |
| } |
| |
| void MetricsRenderFrameObserver::DidCreateDocumentElement() { |
| // If we do not have a render frame or are already tracking this frame, ignore |
| // the new document element. |
| if (HasNoRenderFrame() || page_timing_metrics_sender_) |
| return; |
| |
| // We should only track committed navigations for the main frame so ignore new |
| // document elements in the main frame. |
| if (render_frame()->IsMainFrame()) |
| return; |
| |
| // A new document element was created in a frame that did not commit a |
| // provisional load. This can be due to a doc.write in the frame that aborted |
| // a navigation. Create a page timing sender to track this load. This sender |
| // will only send resource usage updates to the browser process. There |
| // currently is not infrastructure in the browser process to monitor this case |
| // and properly handle timing updates without a committed load. |
| // TODO(https://crbug.com/1097127): Implement proper handling of timing |
| // updates in the browser process and create a normal page timing sender. |
| |
| // It should not be possible to have a |provisional_frame_resource_data_use_| |
| // object at this point. If we did, it means we reached |
| // ReadyToCommitNavigation() and aborted prior to load commit which should not |
| // be possible. |
| DCHECK(!provisional_frame_resource_data_use_); |
| |
| Timing timing = GetTiming(); |
| page_timing_metrics_sender_ = std::make_unique<PageTimingMetricsSender>( |
| CreatePageTimingSender(true /* limited_sending_mode */), CreateTimer(), |
| std::move(timing.relative_timing), timing.monotonic_timing, |
| std::make_unique<PageResourceDataUse>()); |
| if (ukm_smoothness_data_.IsValid()) { |
| page_timing_metrics_sender_->SetUpSmoothnessReporting( |
| std::move(ukm_smoothness_data_)); |
| } |
| } |
| |
| void MetricsRenderFrameObserver::DidCommitProvisionalLoad( |
| ui::PageTransition transition) { |
| // Make sure to release the sender for a previous navigation, if we have one. |
| page_timing_metrics_sender_.reset(); |
| |
| if (HasNoRenderFrame()) |
| return; |
| |
| // Update metadata once the load has been committed. There is no guarantee |
| // that the provisional resource will have been reported as an ad by this |
| // point. Therefore, need to update metadata for the resource after the load. |
| // Consumers may receive the correct ad information late. |
| UpdateResourceMetadata(provisional_frame_resource_data_use_->resource_id()); |
| |
| provisional_frame_resource_id_ = |
| provisional_frame_resource_data_use_->resource_id(); |
| |
| Timing timing = GetTiming(); |
| page_timing_metrics_sender_ = std::make_unique<PageTimingMetricsSender>( |
| CreatePageTimingSender(false /* limited_sending_mode*/), CreateTimer(), |
| std::move(timing.relative_timing), timing.monotonic_timing, |
| std::move(provisional_frame_resource_data_use_)); |
| if (ukm_smoothness_data_.IsValid()) { |
| page_timing_metrics_sender_->SetUpSmoothnessReporting( |
| std::move(ukm_smoothness_data_)); |
| } |
| } |
| |
| void MetricsRenderFrameObserver::SetAdResourceTracker( |
| subresource_filter::AdResourceTracker* ad_resource_tracker) { |
| // Remove all sources and set a new source for the observer. |
| scoped_ad_resource_observation_.Reset(); |
| scoped_ad_resource_observation_.Observe(ad_resource_tracker); |
| } |
| |
| void MetricsRenderFrameObserver::OnAdResourceTrackerGoingAway() { |
| DCHECK(scoped_ad_resource_observation_.IsObserving()); |
| |
| scoped_ad_resource_observation_.Reset(); |
| } |
| |
| void MetricsRenderFrameObserver::OnAdResourceObserved(int request_id) { |
| ad_request_ids_.insert(request_id); |
| } |
| |
| void MetricsRenderFrameObserver::OnMainFrameIntersectionChanged( |
| const gfx::Rect& main_frame_intersection) { |
| if (page_timing_metrics_sender_) |
| page_timing_metrics_sender_->OnMainFrameIntersectionChanged( |
| main_frame_intersection); |
| } |
| |
| void MetricsRenderFrameObserver::OnMobileFriendlinessChanged( |
| const blink::MobileFriendliness& mf) { |
| if (page_timing_metrics_sender_) |
| page_timing_metrics_sender_->DidObserveMobileFriendlinessChanged(mf); |
| } |
| |
| bool MetricsRenderFrameObserver::SetUpSmoothnessReporting( |
| base::ReadOnlySharedMemoryRegion& shared_memory) { |
| if (page_timing_metrics_sender_) { |
| page_timing_metrics_sender_->SetUpSmoothnessReporting( |
| std::move(shared_memory)); |
| } else { |
| ukm_smoothness_data_ = std::move(shared_memory); |
| } |
| return true; |
| } |
| |
| void MetricsRenderFrameObserver::MaybeSetCompletedBeforeFCP(int request_id) { |
| if (HasNoRenderFrame()) |
| return; |
| |
| const blink::WebPerformance& perf = |
| render_frame()->GetWebFrame()->Performance(); |
| |
| // Blink returns 0 if the performance metrics are unavailable. Check that |
| // navigation start is set to determine if performance metrics are |
| // available. |
| if (perf.NavigationStart() == 0) |
| return; |
| |
| // This should not be possible, but none the less occasionally fails in edge |
| // case tests. Since we don't expect this to be valid, throw out this entry. |
| // See crbug.com/1027535. |
| if (base::Time::Now() < base::Time::FromDoubleT(perf.NavigationStart())) |
| return; |
| |
| if (perf.FirstContentfulPaint() == 0) |
| before_fcp_request_ids_.insert(request_id); |
| } |
| |
| MetricsRenderFrameObserver::Timing::Timing( |
| mojom::PageLoadTimingPtr relative_timing, |
| const PageTimingMetadataRecorder::MonotonicTiming& monotonic_timing) |
| : relative_timing(std::move(relative_timing)), |
| monotonic_timing(monotonic_timing) {} |
| |
| MetricsRenderFrameObserver::Timing::~Timing() = default; |
| |
| MetricsRenderFrameObserver::Timing::Timing(Timing&&) = default; |
| MetricsRenderFrameObserver::Timing& MetricsRenderFrameObserver::Timing:: |
| operator=(Timing&&) = default; |
| |
| void MetricsRenderFrameObserver::UpdateResourceMetadata(int request_id) { |
| if (!page_timing_metrics_sender_) |
| return; |
| |
| // If the request is an ad, pop it off the list of known ad requests. |
| auto ad_resource_it = ad_request_ids_.find(request_id); |
| bool reported_as_ad_resource = |
| ad_request_ids_.find(request_id) != ad_request_ids_.end(); |
| if (reported_as_ad_resource) |
| ad_request_ids_.erase(ad_resource_it); |
| |
| // If the request completed before fcp, pop it off the list of known |
| // before-fcp requests. |
| auto before_fcp_it = before_fcp_request_ids_.find(request_id); |
| bool completed_before_fcp = before_fcp_it != before_fcp_request_ids_.end(); |
| if (completed_before_fcp) { |
| before_fcp_request_ids_.erase(before_fcp_it); |
| } |
| |
| bool is_main_frame_resource = render_frame()->IsMainFrame(); |
| |
| if (provisional_frame_resource_data_use_ && |
| provisional_frame_resource_data_use_->resource_id() == request_id) { |
| if (reported_as_ad_resource) |
| provisional_frame_resource_data_use_->SetReportedAsAdResource( |
| reported_as_ad_resource); |
| provisional_frame_resource_data_use_->SetIsMainFrameResource( |
| is_main_frame_resource); |
| // Don't bother with before-fcp metrics for a provisional frame. |
| } else { |
| page_timing_metrics_sender_->UpdateResourceMetadata( |
| request_id, reported_as_ad_resource, is_main_frame_resource, |
| completed_before_fcp); |
| } |
| } |
| |
| void MetricsRenderFrameObserver::SendMetrics() { |
| if (!page_timing_metrics_sender_) |
| return; |
| if (HasNoRenderFrame()) |
| return; |
| Timing timing = GetTiming(); |
| page_timing_metrics_sender_->Update(std::move(timing.relative_timing), |
| timing.monotonic_timing); |
| } |
| |
| MetricsRenderFrameObserver::Timing MetricsRenderFrameObserver::GetTiming() |
| const { |
| const blink::WebPerformance& perf = |
| render_frame()->GetWebFrame()->Performance(); |
| |
| mojom::PageLoadTimingPtr timing(CreatePageLoadTiming()); |
| PageTimingMetadataRecorder::MonotonicTiming monotonic_timing; |
| double start = perf.NavigationStart(); |
| timing->navigation_start = base::Time::FromDoubleT(start); |
| monotonic_timing.navigation_start = perf.NavigationStartAsMonotonicTime(); |
| if (perf.InputForNavigationStart() > 0.0) { |
| timing->input_to_navigation_start = |
| ClampDelta(start, perf.InputForNavigationStart()); |
| } |
| if (perf.FirstInputDelay().has_value()) { |
| timing->interactive_timing->first_input_delay = *perf.FirstInputDelay(); |
| } |
| if (perf.FirstInputTimestamp().has_value()) { |
| timing->interactive_timing->first_input_timestamp = |
| ClampDelta((*perf.FirstInputTimestamp()).InSecondsF(), start); |
| } |
| if (perf.LongestInputDelay().has_value()) { |
| timing->interactive_timing->longest_input_delay = *perf.LongestInputDelay(); |
| } |
| if (perf.LongestInputTimestamp().has_value()) { |
| timing->interactive_timing->longest_input_timestamp = |
| ClampDelta((*perf.LongestInputTimestamp()).InSecondsF(), start); |
| } |
| if (perf.FirstInputProcessingTime().has_value()) { |
| timing->interactive_timing->first_input_processing_time = |
| *perf.FirstInputProcessingTime(); |
| } |
| if (perf.FirstScrollDelay().has_value()) { |
| timing->interactive_timing->first_scroll_delay = *perf.FirstScrollDelay(); |
| } |
| if (perf.FirstScrollTimestamp().has_value()) { |
| timing->interactive_timing->first_scroll_timestamp = |
| ClampDelta((*perf.FirstScrollTimestamp()).InSecondsF(), start); |
| } |
| if (perf.ResponseStart() > 0.0) |
| timing->response_start = ClampDelta(perf.ResponseStart(), start); |
| if (perf.DomContentLoadedEventStart() > 0.0) { |
| timing->document_timing->dom_content_loaded_event_start = |
| ClampDelta(perf.DomContentLoadedEventStart(), start); |
| } |
| if (perf.LoadEventStart() > 0.0) { |
| timing->document_timing->load_event_start = |
| ClampDelta(perf.LoadEventStart(), start); |
| } |
| if (perf.FirstPaint() > 0.0) |
| timing->paint_timing->first_paint = ClampDelta(perf.FirstPaint(), start); |
| if (!perf.BackForwardCacheRestore().empty()) { |
| blink::WebPerformance::BackForwardCacheRestoreTimings restore_timings = |
| perf.BackForwardCacheRestore(); |
| for (const auto& restore_timing : restore_timings) { |
| double navigation_start = restore_timing.navigation_start; |
| double first_paint = restore_timing.first_paint; |
| absl::optional<base::TimeDelta> first_input_delay = |
| restore_timing.first_input_delay; |
| |
| auto back_forward_cache_timing = mojom::BackForwardCacheTiming::New(); |
| if (first_paint) { |
| back_forward_cache_timing |
| ->first_paint_after_back_forward_cache_restore = |
| ClampDelta(first_paint, navigation_start); |
| } |
| for (double raf : restore_timing.request_animation_frames) { |
| if (!raf) |
| break; |
| back_forward_cache_timing |
| ->request_animation_frames_after_back_forward_cache_restore |
| .push_back(ClampDelta(raf, navigation_start)); |
| } |
| if (first_input_delay.has_value()) { |
| back_forward_cache_timing |
| ->first_input_delay_after_back_forward_cache_restore = |
| first_input_delay; |
| } |
| timing->back_forward_cache_timings.push_back( |
| std::move(back_forward_cache_timing)); |
| } |
| } |
| if (perf.FirstImagePaint() > 0.0) { |
| timing->paint_timing->first_image_paint = |
| ClampDelta(perf.FirstImagePaint(), start); |
| } |
| if (perf.FirstContentfulPaint() > 0.0) { |
| DCHECK(perf.FirstEligibleToPaint() > 0); |
| timing->paint_timing->first_contentful_paint = |
| ClampDelta(perf.FirstContentfulPaint(), start); |
| monotonic_timing.first_contentful_paint = |
| ClampToStart(perf.FirstContentfulPaintAsMonotonicTime(), |
| perf.NavigationStartAsMonotonicTime()); |
| } |
| if (perf.FirstMeaningfulPaint() > 0.0) { |
| timing->paint_timing->first_meaningful_paint = |
| ClampDelta(perf.FirstMeaningfulPaint(), start); |
| } |
| if (perf.LargestImagePaintSize() > 0) { |
| timing->paint_timing->largest_contentful_paint->largest_image_paint_size = |
| perf.LargestImagePaintSize(); |
| // Note that size can be nonzero while the time is 0 since a time of 0 is |
| // sent when the image is painting. We assign the time even when it is 0 so |
| // that it's not ignored, but need to be careful when doing operations on |
| // the value. |
| timing->paint_timing->largest_contentful_paint->largest_image_paint = |
| perf.LargestImagePaint() == 0.0 |
| ? base::TimeDelta() |
| : ClampDelta(perf.LargestImagePaint(), start); |
| timing->paint_timing->largest_contentful_paint->type = |
| perf.LargestContentfulPaintType(); |
| timing->paint_timing->largest_contentful_paint->image_bpp = |
| perf.LargestContentfulPaintImageBPP(); |
| } |
| if (perf.LargestTextPaintSize() > 0) { |
| // LargestTextPaint and LargestTextPaintSize should be available at the |
| // same time. This is a renderer side DCHECK to ensure this. |
| DCHECK(perf.LargestTextPaint()); |
| timing->paint_timing->largest_contentful_paint->largest_text_paint = |
| ClampDelta(perf.LargestTextPaint(), start); |
| timing->paint_timing->largest_contentful_paint->largest_text_paint_size = |
| perf.LargestTextPaintSize(); |
| } |
| if (perf.ExperimentalLargestImagePaintSize() > 0) { |
| timing->paint_timing->experimental_largest_contentful_paint |
| ->largest_image_paint_size = perf.ExperimentalLargestImagePaintSize(); |
| // Note that size can be nonzero while the time is 0 since a time of 0 is |
| // sent when the image is painting. We assign the time even when it is 0 so |
| // that it's not ignored, but need to be careful when doing operations on |
| // the value. |
| timing->paint_timing->experimental_largest_contentful_paint |
| ->largest_image_paint = |
| perf.ExperimentalLargestImagePaint() == 0.0 |
| ? base::TimeDelta() |
| : ClampDelta(perf.ExperimentalLargestImagePaint(), start); |
| timing->paint_timing->experimental_largest_contentful_paint->type = |
| perf.LargestContentfulPaintType(); |
| } |
| if (perf.ExperimentalLargestTextPaintSize() > 0) { |
| // ExperimentalLargestTextPaint and ExperimentalLargestTextPaintSize should |
| // be available at the same time. This is a renderer side DCHECK to ensure |
| // this. |
| DCHECK(perf.ExperimentalLargestTextPaint()); |
| timing->paint_timing->experimental_largest_contentful_paint |
| ->largest_text_paint = |
| ClampDelta(perf.ExperimentalLargestTextPaint(), start); |
| timing->paint_timing->experimental_largest_contentful_paint |
| ->largest_text_paint_size = perf.ExperimentalLargestTextPaintSize(); |
| } |
| // It is possible for a frame to switch from eligible for painting to |
| // ineligible for it prior to the first paint. If this occurs, we need to |
| // propagate the null value. |
| if (perf.FirstEligibleToPaint() > 0) { |
| timing->paint_timing->first_eligible_to_paint = |
| ClampDelta(perf.FirstEligibleToPaint(), start); |
| } else { |
| timing->paint_timing->first_eligible_to_paint.reset(); |
| } |
| if (perf.FirstInputOrScrollNotifiedTimestamp() > 0) { |
| timing->paint_timing->first_input_or_scroll_notified_timestamp = |
| ClampDelta(perf.FirstInputOrScrollNotifiedTimestamp(), start); |
| } |
| if (perf.ParseStart() > 0.0) |
| timing->parse_timing->parse_start = ClampDelta(perf.ParseStart(), start); |
| if (perf.ParseStop() > 0.0) |
| timing->parse_timing->parse_stop = ClampDelta(perf.ParseStop(), start); |
| if (timing->parse_timing->parse_start) { |
| // If we started parsing, record all parser durations such as the amount of |
| // time blocked on script load, even if those values are zero. |
| timing->parse_timing->parse_blocked_on_script_load_duration = |
| base::Seconds(perf.ParseBlockedOnScriptLoadDuration()); |
| timing->parse_timing |
| ->parse_blocked_on_script_load_from_document_write_duration = |
| base::Seconds(perf.ParseBlockedOnScriptLoadFromDocumentWriteDuration()); |
| timing->parse_timing->parse_blocked_on_script_execution_duration = |
| base::Seconds(perf.ParseBlockedOnScriptExecutionDuration()); |
| timing->parse_timing |
| ->parse_blocked_on_script_execution_from_document_write_duration = |
| base::Seconds( |
| perf.ParseBlockedOnScriptExecutionFromDocumentWriteDuration()); |
| } |
| if (perf.LastPortalActivatedPaint().has_value()) { |
| timing->paint_timing->portal_activated_paint = |
| *perf.LastPortalActivatedPaint(); |
| } |
| if (perf.PrerenderActivationStart().has_value()) |
| timing->activation_start = perf.PrerenderActivationStart(); |
| |
| if (perf.UserTimingMarkFullyLoaded().has_value()) |
| timing->user_timing_mark_fully_loaded = perf.UserTimingMarkFullyLoaded(); |
| |
| if (perf.UserTimingMarkFullyVisible().has_value()) |
| timing->user_timing_mark_fully_visible = perf.UserTimingMarkFullyVisible(); |
| |
| if (perf.UserTimingMarkInteractive().has_value()) |
| timing->user_timing_mark_interactive = perf.UserTimingMarkInteractive(); |
| |
| return Timing(std::move(timing), monotonic_timing); |
| } |
| |
| std::unique_ptr<base::OneShotTimer> MetricsRenderFrameObserver::CreateTimer() { |
| return std::make_unique<base::OneShotTimer>(); |
| } |
| |
| std::unique_ptr<PageTimingSender> |
| MetricsRenderFrameObserver::CreatePageTimingSender(bool limited_sending_mode) { |
| return base::WrapUnique<PageTimingSender>( |
| new MojoPageTimingSender(render_frame(), limited_sending_mode)); |
| } |
| |
| bool MetricsRenderFrameObserver::HasNoRenderFrame() const { |
| bool no_frame = !render_frame() || !render_frame()->GetWebFrame(); |
| DCHECK(!no_frame); |
| return no_frame; |
| } |
| |
| void MetricsRenderFrameObserver::OnDestruct() { |
| delete this; |
| } |
| |
| } // namespace page_load_metrics |