| // Copyright 2026 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/devtools/protocol/ads_handler.h" |
| |
| #include "components/page_load_metrics/browser/metrics_web_contents_observer.h" |
| #include "components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/web_contents.h" |
| |
| AdsHandler::AdsHandler(content::WebContents* web_contents, |
| protocol::UberDispatcher* dispatcher, |
| bool is_trusted) |
| : content::WebContentsObserver(web_contents) { |
| protocol::Ads::Dispatcher::wire(dispatcher, this); |
| } |
| |
| AdsHandler::~AdsHandler() = default; |
| |
| void AdsHandler::PrimaryPageChanged(content::Page& page) { |
| previous_ad_frame_data_.clear(); |
| } |
| |
| protocol::Response AdsHandler::GetAdMetrics( |
| std::unique_ptr<protocol::Ads::AdMetrics>* out_metrics) { |
| auto* ads_observer = GetAdsPageLoadMetricsObserver(); |
| |
| base::flat_map< |
| base::UnguessableToken, |
| page_load_metrics::AdsPageLoadMetricsObserver::AdFrameLiveStats> |
| new_ad_frame_data; |
| |
| double viewport_ad_density_by_area = 0; |
| double average_viewport_ad_density_by_area = 0; |
| int viewport_ad_count = 0; |
| double average_viewport_ad_count = 0; |
| double total_ad_cpu_time = 0; |
| double total_ad_network_bytes = 0; |
| |
| if (ads_observer) { |
| auto density_stats = ads_observer->GetAdDensityLiveStats(); |
| viewport_ad_density_by_area = density_stats.viewport_ad_density_by_area; |
| average_viewport_ad_density_by_area = |
| density_stats.average_viewport_ad_density_by_area; |
| viewport_ad_count = density_stats.viewport_ad_count; |
| average_viewport_ad_count = density_stats.average_viewport_ad_count; |
| total_ad_cpu_time = ads_observer->GetTotalAdCpuTime().InMillisecondsF(); |
| total_ad_network_bytes = ads_observer->GetTotalAdNetworkBytes(); |
| new_ad_frame_data = ads_observer->GetAdFrameLiveStats(); |
| } |
| |
| // Identify ad frames that have been added, updated, or removed since the |
| // previous metric collection. |
| auto update_ad_frames = |
| std::make_unique<protocol::Array<protocol::Ads::AdFrameData>>(); |
| auto remove_ad_frames = std::make_unique<protocol::Array<protocol::String>>(); |
| |
| for (const auto& [frame_id, frame] : new_ad_frame_data) { |
| auto it = previous_ad_frame_data_.find(frame_id); |
| if (it == previous_ad_frame_data_.end() || |
| it->second.initial_origin != frame.initial_origin || |
| it->second.network_bytes != frame.network_bytes || |
| it->second.cpu_time != frame.cpu_time) { |
| auto protocol_frame = protocol::Ads::AdFrameData::Create() |
| .SetFrameId(frame_id.ToString()) |
| .SetNetworkBytes(frame.network_bytes) |
| .SetCpuTime(frame.cpu_time.InMillisecondsF()) |
| .Build(); |
| |
| // To minimize protocol payload size, `initial_origin` is only sent when |
| // it changes from the previous message for a given frame. Typically, this |
| // is sent only once per frame. We retain this check to handle potential |
| // edge cases (e.g., AdsPLMO might stop tracking a frame that subsequently |
| // navigates cross-origin before being re-added to the tracker). |
| if (it == previous_ad_frame_data_.end() || |
| it->second.initial_origin != frame.initial_origin) { |
| protocol_frame->SetInitialOrigin(frame.initial_origin.Serialize()); |
| } |
| |
| update_ad_frames->emplace_back(std::move(protocol_frame)); |
| } |
| } |
| |
| for (const auto& [frame_id, frame] : previous_ad_frame_data_) { |
| if (!new_ad_frame_data.contains(frame_id)) { |
| remove_ad_frames->emplace_back(frame_id.ToString()); |
| } |
| } |
| |
| previous_ad_frame_data_ = std::move(new_ad_frame_data); |
| |
| *out_metrics = protocol::Ads::AdMetrics::Create() |
| .SetViewportAdDensityByArea(viewport_ad_density_by_area) |
| .SetAverageViewportAdDensityByArea( |
| average_viewport_ad_density_by_area) |
| .SetViewportAdCount(viewport_ad_count) |
| .SetAverageViewportAdCount(average_viewport_ad_count) |
| .SetTotalAdCpuTime(total_ad_cpu_time) |
| .SetTotalAdNetworkBytes(total_ad_network_bytes) |
| .SetUpdateAdFrames(std::move(update_ad_frames)) |
| .SetRemoveAdFrames(std::move(remove_ad_frames)) |
| .Build(); |
| |
| return protocol::Response::Success(); |
| } |
| |
| page_load_metrics::AdsPageLoadMetricsObserver* |
| AdsHandler::GetAdsPageLoadMetricsObserver() { |
| if (!web_contents()) { |
| return nullptr; |
| } |
| |
| content::RenderFrameHost* main_frame = web_contents()->GetPrimaryMainFrame(); |
| if (!main_frame) { |
| return nullptr; |
| } |
| |
| auto* metrics_web_contents_observer = |
| page_load_metrics::MetricsWebContentsObserver::FromWebContents( |
| web_contents()); |
| if (!metrics_web_contents_observer) { |
| return nullptr; |
| } |
| |
| base::WeakPtr<page_load_metrics::PageLoadMetricsObserverInterface> |
| ads_observer = metrics_web_contents_observer->GetMetricsObserver( |
| main_frame, |
| page_load_metrics::AdsPageLoadMetricsObserver::kObserverName); |
| if (ads_observer) { |
| return static_cast<page_load_metrics::AdsPageLoadMetricsObserver*>( |
| ads_observer.get()); |
| } |
| return nullptr; |
| } |