| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/browsing_topics/browsing_topics_page_load_data_tracker.h" |
| |
| #include "base/containers/contains.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "components/browsing_topics/util.h" |
| #include "components/history/content/browser/history_context_helper.h" |
| #include "components/history/core/browser/history_service.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browsing_topics_site_data_manager.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/browser/web_contents.h" |
| #include "services/metrics/public/cpp/metrics_utils.h" |
| #include "services/metrics/public/cpp/ukm_builders.h" |
| #include "services/metrics/public/cpp/ukm_recorder.h" |
| #include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom.h" |
| #include "third_party/blink/public/common/features.h" |
| |
| namespace browsing_topics { |
| |
| // Max number of context domains to keep track of per page load to limit in-use |
| // memory. This should also be greater than |
| // `kBrowsingTopicsMaxNumberOfApiUsageContextDomainsToStorePerPageLoad`. |
| constexpr int kMaxNumberOfContextDomainsToMonitor = 1000; |
| |
| BrowsingTopicsPageLoadDataTracker::~BrowsingTopicsPageLoadDataTracker() { |
| if (observed_hashed_context_domains_.empty()) |
| return; |
| |
| // TODO(yaoxia): consider also recording metrics when the Chrome app goes into |
| // background, in which case the destructor may never be called. |
| ukm::UkmRecorder* ukm_recorder = ukm::UkmRecorder::Get(); |
| ukm::builders::BrowsingTopics_PageLoad builder(source_id_); |
| |
| builder.SetTopicsRequestingContextDomainsCount( |
| ukm::GetExponentialBucketMinForCounts1000( |
| observed_hashed_context_domains_.size())); |
| |
| builder.Record(ukm_recorder->Get()); |
| } |
| |
| BrowsingTopicsPageLoadDataTracker::BrowsingTopicsPageLoadDataTracker( |
| content::Page& page) |
| : BrowsingTopicsPageLoadDataTracker( |
| page, |
| /*redirect_hosts_with_topics_invoked=*/{}, |
| /*source_id_before_redirects=*/ |
| page.GetMainDocument().GetPageUkmSourceId()) {} |
| |
| BrowsingTopicsPageLoadDataTracker::BrowsingTopicsPageLoadDataTracker( |
| content::Page& page, |
| std::set<HashedHost> redirect_hosts_with_topics_invoked, |
| ukm::SourceId source_id_before_redirects) |
| : content::PageUserData<BrowsingTopicsPageLoadDataTracker>(page), |
| hashed_main_frame_host_(HashMainFrameHostForStorage( |
| page.GetMainDocument().GetLastCommittedOrigin().host())), |
| redirect_hosts_with_topics_invoked_( |
| std::move(redirect_hosts_with_topics_invoked)), |
| source_id_before_redirects_(source_id_before_redirects) { |
| source_id_ = page.GetMainDocument().GetPageUkmSourceId(); |
| |
| // TODO(yaoxia): consider dropping the permissions policy checks. We require |
| // that the API is used in the page, and that already implies that the |
| // permissions policy is allowed. |
| |
| if ((page.GetMainDocument().IsLastCommitIPAddressPubliclyRoutable() || |
| base::FeatureList::IsEnabled( |
| blink::features::kBrowsingTopicsBypassIPIsPubliclyRoutableCheck)) && |
| page.GetMainDocument().IsFeatureEnabled( |
| network::mojom::PermissionsPolicyFeature::kBrowsingTopics) && |
| page.GetMainDocument().IsFeatureEnabled( |
| network::mojom::PermissionsPolicyFeature:: |
| kBrowsingTopicsBackwardCompatible) && |
| page.GetMainDocument().IsLastCrossDocumentNavigationStartedByUser()) { |
| eligible_to_observe_ = true; |
| } |
| } |
| |
| void BrowsingTopicsPageLoadDataTracker::OnBrowsingTopicsApiUsed( |
| const HashedDomain& hashed_context_domain, |
| const std::string& context_domain, |
| history::HistoryService* history_service, |
| bool observe) { |
| CHECK(page().IsPrimary()); |
| |
| if (!topics_invoked_) { |
| if (redirect_hosts_with_topics_invoked_.size() < 5) { |
| bool host_inserted = |
| redirect_hosts_with_topics_invoked_.insert(hashed_main_frame_host_) |
| .second; |
| |
| if (host_inserted) { |
| // If this is the first Topics call on the page, and this site wasn't |
| // part of a previous redirect chain that invoked Topics, record the |
| // number of distinct sites in the current redirect chain (including |
| // this page) that have called Topics, capped at 5. This ensures each |
| // count (from 1 to the maximum count) is emitted exactly once per |
| // redirect chain. |
| int kExclusiveMaxBucket = 6; |
| base::UmaHistogramExactLinear( |
| "BrowsingTopics.RedirectChain.OnTopicsFirstInvokedForSite." |
| "TopicsCallingSitesCount", |
| redirect_hosts_with_topics_invoked_.size(), kExclusiveMaxBucket); |
| } |
| } |
| |
| topics_invoked_ = true; |
| } |
| |
| if (!observe || !eligible_to_observe_) { |
| return; |
| } |
| |
| // On the first Topics observation in the page, set the allowed bit in |
| // history. |
| if (observed_hashed_context_domains_.empty()) { |
| content::WebContents* web_contents = |
| content::WebContents::FromRenderFrameHost(&page().GetMainDocument()); |
| |
| history_service->SetBrowsingTopicsAllowed( |
| history::ContextIDForWebContents(web_contents), |
| web_contents->GetController().GetLastCommittedEntry()->GetUniqueID(), |
| web_contents->GetLastCommittedURL()); |
| } |
| |
| CHECK_LE( |
| blink::features:: |
| kBrowsingTopicsMaxNumberOfApiUsageContextDomainsToStorePerPageLoad |
| .Get(), |
| kMaxNumberOfContextDomainsToMonitor); |
| |
| // Cap the number of context domains to keep track of per page load to limit |
| // in-use memory. |
| if (observed_hashed_context_domains_.size() >= |
| kMaxNumberOfContextDomainsToMonitor) { |
| return; |
| } |
| |
| bool is_new_domain = |
| observed_hashed_context_domains_.insert(hashed_context_domain).second; |
| |
| // Ignore this context if we've already added it. |
| if (!is_new_domain) |
| return; |
| |
| // Cap the number of context domains to store to disk per page load to limit |
| // disk memory usage. |
| if (observed_hashed_context_domains_.size() > |
| static_cast<size_t>( |
| blink::features:: |
| kBrowsingTopicsMaxNumberOfApiUsageContextDomainsToStorePerPageLoad |
| .Get())) { |
| return; |
| } |
| |
| // Persist the usage now rather than at the end of the page load, as when the |
| // app enters background, it may be killed without further notification. |
| page() |
| .GetMainDocument() |
| .GetBrowserContext() |
| ->GetDefaultStoragePartition() |
| ->GetBrowsingTopicsSiteDataManager() |
| ->OnBrowsingTopicsApiUsed(hashed_main_frame_host_, hashed_context_domain, |
| context_domain, base::Time::Now()); |
| } |
| |
| PAGE_USER_DATA_KEY_IMPL(BrowsingTopicsPageLoadDataTracker); |
| |
| } // namespace browsing_topics |