| // Copyright 2020 The Chromium Authors | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "content/browser/renderer_host/cookie_utils.h" | 
 |  | 
 | #include <algorithm> | 
 | #include <cstddef> | 
 | #include <ostream> | 
 | #include <string> | 
 |  | 
 | #include "base/feature_list.h" | 
 | #include "base/logging.h" | 
 | #include "base/strings/string_util.h" | 
 | #include "base/unguessable_token.h" | 
 | #include "content/browser/devtools/devtools_instrumentation.h" | 
 | #include "content/browser/navigation_or_document_handle.h" | 
 | #include "content/browser/renderer_host/navigation_request.h" | 
 | #include "content/browser/renderer_host/render_frame_host_impl.h" | 
 | #include "content/public/browser/browser_context.h" | 
 | #include "content/public/browser/cookie_access_details.h" | 
 | #include "content/public/browser/legacy_tech_cookie_issue_details.h" | 
 | #include "content/public/common/content_client.h" | 
 | #include "content/public/common/content_features.h" | 
 | #include "net/cookies/cookie_constants.h" | 
 | #include "net/cookies/cookie_inclusion_status.h" | 
 | #include "net/cookies/cookie_setting_override.h" | 
 | #include "services/metrics/public/cpp/metrics_utils.h" | 
 | #include "services/metrics/public/cpp/ukm_builders.h" | 
 | #include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom-shared.h" | 
 | #include "url/gurl.h" | 
 |  | 
 | namespace content { | 
 |  | 
 | namespace { | 
 |  | 
 | void PotentiallyRecordNonAsciiCookieNameValue( | 
 |     RenderFrameHost* rfh, | 
 |     CookieAccessDetails::Type access_type, | 
 |     const std::string& name, | 
 |     const std::string& value) { | 
 |   CHECK(rfh); | 
 |  | 
 |   if (access_type != CookieAccessDetails::Type::kChange) { | 
 |     return; | 
 |   } | 
 |  | 
 |   // Our data collection policy disallows collecting UKMs while prerendering. | 
 |   // See //content/browser/preloading/prerender/README.md and ask the team to | 
 |   // explore options to record data for prerendering pages if we need to | 
 |   // support the case. | 
 |   if (rfh->IsInLifecycleState(RenderFrameHost::LifecycleState::kPrerendering)) { | 
 |     return; | 
 |   } | 
 |  | 
 |   bool name_has_non_ascii = !base::IsStringASCII(name); | 
 |   bool value_has_non_ascii = !base::IsStringASCII(value); | 
 |  | 
 |   if (name_has_non_ascii || value_has_non_ascii) { | 
 |     ukm::SourceId source_id = rfh->GetPageUkmSourceId(); | 
 |  | 
 |     auto event = ukm::builders::CookieHasNonAsciiCharacter(source_id); | 
 |  | 
 |     // The event itself is what we're interested in, the value of "true" here | 
 |     // can be ignored. | 
 |     if (name_has_non_ascii) { | 
 |       event.SetName(true); | 
 |     } | 
 |  | 
 |     if (value_has_non_ascii) { | 
 |       event.SetValue(true); | 
 |     } | 
 |  | 
 |     event.Record(ukm::UkmRecorder::Get()); | 
 |   } | 
 | } | 
 |  | 
 | void PotentiallyRecordCookieOriginMismatch( | 
 |     RenderFrameHost* rfh, | 
 |     CookieAccessDetails::Type access_type, | 
 |     const net::CookieInclusionStatus& status) { | 
 |   CHECK(rfh); | 
 |  | 
 |   if (access_type != CookieAccessDetails::Type::kRead) { | 
 |     return; | 
 |   } | 
 |  | 
 |   // Our data collection policy disallows collecting UKMs while prerendering. | 
 |   // See //content/browser/preloading/prerender/README.md and ask the team to | 
 |   // explore options to record data for prerendering pages if we need to | 
 |   // support the case. | 
 |   if (rfh->IsInLifecycleState(RenderFrameHost::LifecycleState::kPrerendering)) { | 
 |     return; | 
 |   } | 
 |   const bool port_mismatch = | 
 |       status.HasWarningReason( | 
 |           net::CookieInclusionStatus::WarningReason::WARN_PORT_MISMATCH) || | 
 |       status.HasExclusionReason( | 
 |           net::CookieInclusionStatus::ExclusionReason::EXCLUDE_PORT_MISMATCH); | 
 |  | 
 |   const bool scheme_mismatch = | 
 |       status.HasWarningReason( | 
 |           net::CookieInclusionStatus::WarningReason::WARN_SCHEME_MISMATCH) || | 
 |       status.HasExclusionReason( | 
 |           net::CookieInclusionStatus::ExclusionReason::EXCLUDE_SCHEME_MISMATCH); | 
 |  | 
 |   if (port_mismatch || scheme_mismatch) { | 
 |     ukm::SourceId source_id = rfh->GetPageUkmSourceId(); | 
 |  | 
 |     auto event = ukm::builders::Cookies_Blocked_DueToOriginMismatch(source_id); | 
 |  | 
 |     // The event itself is what we're interested in, the value of "true" here | 
 |     // can be ignored. | 
 |     if (port_mismatch) { | 
 |       event.SetPortMismatch(true); | 
 |     } | 
 |  | 
 |     if (scheme_mismatch) { | 
 |       event.SetSchemeMismatch(true); | 
 |     } | 
 |  | 
 |     event.Record(ukm::UkmRecorder::Get()); | 
 |   } | 
 | } | 
 |  | 
 | void RecordCookiesExemptedByTopLevelStorage(ukm::SourceId source_id, | 
 |                                             int affected_cookies_in_request) { | 
 |   ukm::builders::RequestStorageAccessFor_TopLevelStorageIsExemptionReason( | 
 |       source_id) | 
 |       .SetNumberOfCookies(ukm::GetExponentialBucketMin( | 
 |           affected_cookies_in_request, /*bucket_spacing=*/2.0)) | 
 |       .Record(ukm::UkmRecorder::Get()); | 
 | } | 
 |  | 
 | // Relies on checks in RecordPartitionedCookiesUKMs to confirm that that the | 
 | // cookie name is not "receive-cookie-deprecation", that cookie is first party | 
 | // partitioned and the RenderFrameHost is not prerendering. | 
 | void RecordFirstPartyPartitionedCookieCrossSiteContextUKM( | 
 |     RenderFrameHostImpl* render_frame_host_impl, | 
 |     const net::CanonicalCookie& cookie, | 
 |     const ukm::SourceId& source_id) { | 
 |   // Same-site embed with cross-site ancestors (ABA embeds) have a null site | 
 |   // for cookies since it is a cross-site context. If the result of | 
 |   // ComputeSiteForCookies is first-party that means we are not in an ABA | 
 |   // embedded context. | 
 |   bool has_cross_site_ancestor = | 
 |       !render_frame_host_impl->ComputeSiteForCookies().IsFirstParty( | 
 |           GURL(base::StrCat({url::kHttpsScheme, url::kStandardSchemeSeparator, | 
 |                              cookie.DomainWithoutDot()}))); | 
 |  | 
 |   ukm::builders::Cookies_FirstPartyPartitionedInCrossSiteContextV3(source_id) | 
 |       .SetCookiePresent(has_cross_site_ancestor) | 
 |       .Record(ukm::UkmRecorder::Get()); | 
 | } | 
 |  | 
 | // Relies on checks in RecordPartitionedCookiesUKMs to confirm that that the | 
 | // cookie is partitioned, the cookie name is not | 
 | // "receive-cookie-deprecation" and the RenderFrameHost is not prerendering. | 
 | void RecordPartitionedCookieUseV2UKM(RenderFrameHost* rfh, | 
 |                                      const net::CanonicalCookie& cookie, | 
 |                                      const ukm::SourceId& source_id) { | 
 |   ukm::builders::PartitionedCookiePresentV2(source_id) | 
 |       .SetPartitionedCookiePresentV2(true) | 
 |       .Record(ukm::UkmRecorder::Get()); | 
 | } | 
 |  | 
 | void RecordPartitionedCookiesUKMs(RenderFrameHostImpl* render_frame_host_impl, | 
 |                                   const net::CanonicalCookie& cookie) { | 
 |   // Our data collection policy disallows collecting UKMs while prerendering. | 
 |   // See //content/browser/preloading/prerender/README.md and ask the team to | 
 |   // explore options to record data for prerendering pages if we need to | 
 |   // support the case. | 
 |   if (render_frame_host_impl->IsInLifecycleState( | 
 |           RenderFrameHost::LifecycleState::kPrerendering)) { | 
 |     return; | 
 |   } | 
 |  | 
 |   // Cookies_FirstPartyPartitionedInCrossSiteContextV3 and | 
 |   // PartitionedCookiePresentV2 both measure cookies | 
 |   // without the name of 'receive-cookie-deprecation'. Return here to ensure | 
 |   // that the metrics do not include those cookies. | 
 |   if (cookie.Name() == "receive-cookie-deprecation") { | 
 |     return; | 
 |   } | 
 |  | 
 |   ukm::SourceId source_id = render_frame_host_impl->GetPageUkmSourceId(); | 
 |  | 
 |   if (cookie.IsFirstPartyPartitioned()) { | 
 |     RecordFirstPartyPartitionedCookieCrossSiteContextUKM(render_frame_host_impl, | 
 |                                                          cookie, source_id); | 
 |   } | 
 |  | 
 |   RecordPartitionedCookieUseV2UKM(render_frame_host_impl, cookie, source_id); | 
 | } | 
 |  | 
 | void RecordRedirectContextDowngradeUKM(RenderFrameHost* rfh, | 
 |                                        CookieAccessDetails::Type access_type, | 
 |                                        const net::CanonicalCookie& cookie, | 
 |                                        const GURL& url) { | 
 |   CHECK(rfh); | 
 |  | 
 |   // Our data collection policy disallows collecting UKMs while prerendering. | 
 |   // See //content/browser/preloading/prerender/README.md and ask the team to | 
 |   // explore options to record data for prerendering pages if we need to | 
 |   // support the case. | 
 |   if (rfh->IsInLifecycleState(RenderFrameHost::LifecycleState::kPrerendering)) { | 
 |     return; | 
 |   } | 
 |  | 
 |   ukm::SourceId source_id = rfh->GetPageUkmSourceId(); | 
 |  | 
 |   int64_t samesite_value = static_cast<int64_t>(cookie.SameSite()); | 
 |   if (access_type == CookieAccessDetails::Type::kRead) { | 
 |     base::TimeDelta cookie_age = base::Time::Now() - cookie.CreationDate(); | 
 |  | 
 |     ukm::builders::SamesiteRedirectContextDowngrade(source_id) | 
 |         .SetSamesiteValueReadPerCookie(samesite_value) | 
 |         .SetAgePerCookie( | 
 |             ukm::GetExponentialBucketMinForUserTiming(cookie_age.InMinutes())) | 
 |         .Record(ukm::UkmRecorder::Get()); | 
 |   } else { | 
 |     CHECK(access_type == CookieAccessDetails::Type::kChange); | 
 |     ukm::builders::SamesiteRedirectContextDowngrade(source_id) | 
 |         .SetSamesiteValueWritePerCookie(samesite_value) | 
 |         .Record(ukm::UkmRecorder::Get()); | 
 |   } | 
 | } | 
 |  | 
 | void RecordSchemefulContextDowngradeUKM( | 
 |     RenderFrameHost* rfh, | 
 |     CookieAccessDetails::Type access_type, | 
 |     const net::CookieInclusionStatus& status, | 
 |     const GURL& url) { | 
 |   CHECK(rfh); | 
 |  | 
 |   // Our data collection policy disallows collecting UKMs while prerendering. | 
 |   // See //content/browser/preloading/prerender/README.md and ask the team to | 
 |   // explore options to record data for prerendering pages if we need to | 
 |   // support the case. | 
 |   if (rfh->IsInLifecycleState(RenderFrameHost::LifecycleState::kPrerendering)) { | 
 |     return; | 
 |   } | 
 |  | 
 |   ukm::SourceId source_id = rfh->GetPageUkmSourceId(); | 
 |  | 
 |   auto downgrade_metric = | 
 |       static_cast<int64_t>(status.GetBreakingDowngradeMetricsEnumValue(url)); | 
 |   if (access_type == CookieAccessDetails::Type::kRead) { | 
 |     ukm::builders::SchemefulSameSiteContextDowngrade(source_id) | 
 |         .SetRequestPerCookie(downgrade_metric) | 
 |         .Record(ukm::UkmRecorder::Get()); | 
 |   } else { | 
 |     CHECK(access_type == CookieAccessDetails::Type::kChange); | 
 |     ukm::builders::SchemefulSameSiteContextDowngrade(source_id) | 
 |         .SetResponsePerCookie(downgrade_metric) | 
 |         .Record(ukm::UkmRecorder::Get()); | 
 |   } | 
 | } | 
 |  | 
 | bool ShouldReportDevToolsIssueForStatus( | 
 |     const net::CookieInclusionStatus& status) { | 
 |   return status.ShouldWarn() || | 
 |          status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason:: | 
 |                                        EXCLUDE_DOMAIN_NON_ASCII) || | 
 |          status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason:: | 
 |                                        EXCLUDE_PORT_MISMATCH) || | 
 |          status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason:: | 
 |                                        EXCLUDE_SCHEME_MISMATCH) || | 
 |          status.HasExclusionReason( | 
 |              net::CookieInclusionStatus::ExclusionReason:: | 
 |                  EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET) || | 
 |          status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason:: | 
 |                                        EXCLUDE_THIRD_PARTY_PHASEOUT) || | 
 |          status.exemption_reason() == | 
 |              net::CookieInclusionStatus::ExemptionReason::k3PCDMetadata || | 
 |          status.exemption_reason() == | 
 |              net::CookieInclusionStatus::ExemptionReason::k3PCDHeuristics; | 
 | } | 
 |  | 
 | bool IsCrossOriginSameSiteNetworkAccessWithStorageAccessEligible( | 
 |     const network::mojom::CookieAccessDetailsPtr& cookie_details) { | 
 |   if (!cookie_details->frame_origin || | 
 |       !cookie_details->cookie_setting_overrides.Has( | 
 |           net::CookieSettingOverride::kStorageAccessGrantEligible)) { | 
 |     // `frame_origin` is unset for script accesses, and network accesses whose | 
 |     // IsolationInfo's `frame_origin` was nullptr. | 
 |     return false; | 
 |   } | 
 |   const url::Origin origin = url::Origin::Create(cookie_details->url); | 
 |   return !origin.IsSameOriginWith(cookie_details->frame_origin.value()) && | 
 |          net::SchemefulSite::IsSameSite(origin, | 
 |                                         cookie_details->frame_origin.value()); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | void SplitCookiesIntoAllowedAndBlocked( | 
 |     const network::mojom::CookieAccessDetailsPtr& cookie_details, | 
 |     CookieAccessDetails::Source source, | 
 |     CookieAccessDetails* allowed, | 
 |     CookieAccessDetails* blocked) { | 
 |   // For some cases `site_for_cookies` representative url is empty when | 
 |   // OnCookieAccess is triggered for a third party. For example iframe third | 
 |   // party accesses cookies when TPCD Metadata allows third party cookie access. | 
 |   // | 
 |   // Make `first_party_url` considering both `top_frame_origin` and | 
 |   // `site_for_cookies` which is similar with GetFirstPartyURL() in | 
 |   // components/content_settings/core/common/cookie_settings_base.h. | 
 |   // If the `top_frame_origin` is non-opaque, it is chosen; otherwise, the | 
 |   // `site_for_cookies` representative url is used. | 
 |   const GURL first_party_url = | 
 |       cookie_details->top_frame_origin.opaque() | 
 |           ? cookie_details->site_for_cookies.RepresentativeUrl() | 
 |           : cookie_details->top_frame_origin.GetURL(); | 
 |  | 
 |   *allowed = CookieAccessDetails({cookie_details->type, | 
 |                                   cookie_details->url, | 
 |                                   first_party_url, | 
 |                                   {}, | 
 |                                   /* blocked_by_policy=*/false, | 
 |                                   cookie_details->is_ad_tagged, | 
 |                                   cookie_details->cookie_setting_overrides, | 
 |                                   cookie_details->site_for_cookies, | 
 |                                   source}); | 
 |   int allowed_count = std::ranges::count_if( | 
 |       cookie_details->cookie_list, | 
 |       [](const network::mojom::CookieOrLineWithAccessResultPtr& | 
 |              cookie_and_access_result) { | 
 |         // "Included" cookies have no exclusion reasons so we don't also have to | 
 |         // check for | 
 |         // !(net::CookieInclusionStatus::ExclusionReason::EXCLUDE_USER_PREFERENCES). | 
 |         return cookie_and_access_result->access_result.status.IsInclude(); | 
 |       }); | 
 |   allowed->cookie_access_result_list.reserve(allowed_count); | 
 |  | 
 |   *blocked = CookieAccessDetails({cookie_details->type, | 
 |                                   cookie_details->url, | 
 |                                   first_party_url, | 
 |                                   {}, | 
 |                                   /* blocked_by_policy=*/true, | 
 |                                   cookie_details->is_ad_tagged, | 
 |                                   cookie_details->cookie_setting_overrides, | 
 |                                   cookie_details->site_for_cookies, | 
 |                                   source}); | 
 |   int blocked_count = std::ranges::count_if( | 
 |       cookie_details->cookie_list, | 
 |       [](const network::mojom::CookieOrLineWithAccessResultPtr& | 
 |              cookie_and_access_result) { | 
 |         return cookie_and_access_result->access_result.status | 
 |             .ExcludedByUserPreferencesOrTPCD(); | 
 |       }); | 
 |   blocked->cookie_access_result_list.reserve(blocked_count); | 
 |  | 
 |   for (const auto& cookie_and_access_result : cookie_details->cookie_list) { | 
 |     if (cookie_and_access_result->access_result.status | 
 |             .ExcludedByUserPreferencesOrTPCD()) { | 
 |       blocked->cookie_access_result_list.emplace_back( | 
 |           std::move(cookie_and_access_result->cookie_or_line->get_cookie()), | 
 |           cookie_and_access_result->access_result); | 
 |     } else if (cookie_and_access_result->access_result.status.IsInclude()) { | 
 |       allowed->cookie_access_result_list.emplace_back( | 
 |           std::move(cookie_and_access_result->cookie_or_line->get_cookie()), | 
 |           cookie_and_access_result->access_result); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | // Logs cookie issues to DevTools Issues Panel and logs events to UseCounters | 
 | // and UKM for a single cookie-accessed event. | 
 | // TODO(crbug.com/40632967): Remove when no longer needed. | 
 | void EmitCookieWarningsAndMetrics( | 
 |     RenderFrameHostImpl* rfh, | 
 |     const network::mojom::CookieAccessDetailsPtr& cookie_details) { | 
 |   RenderFrameHostImpl* root_frame_host = rfh->GetMainFrame(); | 
 |  | 
 |   if (!root_frame_host->IsActive()) | 
 |     return; | 
 |  | 
 |   bool samesite_treated_as_lax_cookies = false; | 
 |   bool samesite_none_insecure_cookies = false; | 
 |   bool breaking_context_downgrade = false; | 
 |   bool lax_allow_unsafe_cookies = false; | 
 |  | 
 |   bool samesite_cookie_inclusion_changed_by_cross_site_redirect = false; | 
 |  | 
 |   bool partitioned_cookies_exist = false; | 
 |  | 
 |   bool cookie_has_not_been_refreshed_in_201_to_300_days = false; | 
 |   bool cookie_has_not_been_refreshed_in_301_to_350_days = false; | 
 |   bool cookie_has_not_been_refreshed_in_351_to_400_days = false; | 
 |  | 
 |   bool cookie_has_domain_non_ascii = false; | 
 |  | 
 |   int cookies_exempted_by_top_level_storage_access = 0; | 
 |  | 
 |   const bool cross_origin_same_site_with_storage_access_eligible = | 
 |       IsCrossOriginSameSiteNetworkAccessWithStorageAccessEligible( | 
 |           cookie_details); | 
 |   bool cross_origin_same_site_cookie_via_storage_access_api = false; | 
 |  | 
 |   for (const network::mojom::CookieOrLineWithAccessResultPtr& cookie : | 
 |        cookie_details->cookie_list) { | 
 |     const net::CookieInclusionStatus& status = cookie->access_result.status; | 
 |     if (ShouldReportDevToolsIssueForStatus(status)) { | 
 |       std::optional<std::string> devtools_issue_id; | 
 |       if (status.HasExclusionReason( | 
 |               net::CookieInclusionStatus::ExclusionReason:: | 
 |                   EXCLUDE_THIRD_PARTY_PHASEOUT) || | 
 |           status.HasWarningReason(net::CookieInclusionStatus::WarningReason:: | 
 |                                       WARN_THIRD_PARTY_PHASEOUT)) { | 
 |         devtools_issue_id = base::UnguessableToken::Create().ToString(); | 
 |       } | 
 |       devtools_instrumentation::ReportCookieIssue( | 
 |           root_frame_host, cookie, cookie_details->url, | 
 |           cookie_details->site_for_cookies, | 
 |           cookie_details->type == CookieAccessDetails::Type::kRead | 
 |               ? blink::mojom::CookieOperation::kReadCookie | 
 |               : blink::mojom::CookieOperation::kSetCookie, | 
 |           cookie_details->devtools_request_id, devtools_issue_id); | 
 |     } | 
 |  | 
 |     if (cookie->access_result.status.ShouldWarn()) { | 
 |       samesite_treated_as_lax_cookies = | 
 |           samesite_treated_as_lax_cookies || | 
 |           status.HasWarningReason( | 
 |               net::CookieInclusionStatus::WarningReason:: | 
 |                   WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT) || | 
 |           status.HasWarningReason( | 
 |               net::CookieInclusionStatus::WarningReason:: | 
 |                   WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE); | 
 |  | 
 |       samesite_none_insecure_cookies = | 
 |           samesite_none_insecure_cookies || | 
 |           status.HasWarningReason(net::CookieInclusionStatus::WarningReason:: | 
 |                                       WARN_SAMESITE_NONE_INSECURE); | 
 |  | 
 |       lax_allow_unsafe_cookies = | 
 |           lax_allow_unsafe_cookies || | 
 |           status.HasWarningReason( | 
 |               net::CookieInclusionStatus::WarningReason:: | 
 |                   WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE); | 
 |  | 
 |       samesite_cookie_inclusion_changed_by_cross_site_redirect = | 
 |           samesite_cookie_inclusion_changed_by_cross_site_redirect || | 
 |           status.HasWarningReason( | 
 |               net::CookieInclusionStatus::WarningReason:: | 
 |                   WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION); | 
 |     } | 
 |  | 
 |     cookie_has_domain_non_ascii = | 
 |         cookie_has_domain_non_ascii || | 
 |         status.HasWarningReason( | 
 |             net::CookieInclusionStatus::WarningReason::WARN_DOMAIN_NON_ASCII) || | 
 |         status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason:: | 
 |                                       EXCLUDE_DOMAIN_NON_ASCII); | 
 |  | 
 |     partitioned_cookies_exist = | 
 |         partitioned_cookies_exist || | 
 |         (cookie->cookie_or_line->is_cookie() && | 
 |          cookie->cookie_or_line->get_cookie().IsPartitioned() && | 
 |          // Ignore nonced partition keys since this metric is meant to track | 
 |          // usage of the Partitioned attribute. | 
 |          !cookie->cookie_or_line->get_cookie().PartitionKey()->nonce()); | 
 |  | 
 |  | 
 |     if (partitioned_cookies_exist) { | 
 |       RecordPartitionedCookiesUKMs(rfh, cookie->cookie_or_line->get_cookie()); | 
 |     } | 
 |  | 
 |     if (cookie->access_result.status.exemption_reason() == | 
 |         net::CookieInclusionStatus::ExemptionReason::kTopLevelStorageAccess) { | 
 |       cookies_exempted_by_top_level_storage_access++; | 
 |     } | 
 |  | 
 |     breaking_context_downgrade = | 
 |         breaking_context_downgrade || | 
 |         cookie->access_result.status.HasSchemefulDowngradeWarning(); | 
 |  | 
 |     if (cookie->access_result.status.HasSchemefulDowngradeWarning()) { | 
 |       // Unlike with UMA, do not record cookies that have no schemeful downgrade | 
 |       // warning. | 
 |       RecordSchemefulContextDowngradeUKM(rfh, cookie_details->type, | 
 |                                          cookie->access_result.status, | 
 |                                          cookie_details->url); | 
 |     } | 
 |  | 
 |     if (status.HasWarningReason( | 
 |             net::CookieInclusionStatus::WarningReason:: | 
 |                 WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION) && | 
 |         cookie->cookie_or_line->is_cookie()) { | 
 |       RecordRedirectContextDowngradeUKM(rfh, cookie_details->type, | 
 |                                         cookie->cookie_or_line->get_cookie(), | 
 |                                         cookie_details->url); | 
 |     } | 
 |  | 
 |     if (cookie->cookie_or_line->is_cookie()) { | 
 |       PotentiallyRecordNonAsciiCookieNameValue( | 
 |           rfh, cookie_details->type, | 
 |           cookie->cookie_or_line->get_cookie().Name(), | 
 |           cookie->cookie_or_line->get_cookie().Value()); | 
 |     } | 
 |  | 
 |     PotentiallyRecordCookieOriginMismatch(rfh, cookie_details->type, status); | 
 |  | 
 |     // In order to anticipate the potential effects of the expiry limit in | 
 |     // rfc6265bis, we need to check how long it's been since the cookie was | 
 |     // refreshed (if LastUpdateDate is populated). These three buckets were | 
 |     // picked so we could engage sites with some granularity around urgency. | 
 |     // We ignore the space under 200 days as these cookies are not at risk | 
 |     // of expiring and we ignore the space over 400 days as these cookies | 
 |     // have already expired. Metrics will take 200 days from M103 to populate. | 
 |     base::Time last_update_date = | 
 |         cookie->cookie_or_line->is_cookie() | 
 |             ? cookie->cookie_or_line->get_cookie().LastUpdateDate() | 
 |             : base::Time(); | 
 |     if (!last_update_date.is_null()) { | 
 |       int days_since_refresh = (base::Time::Now() - last_update_date).InDays(); | 
 |       cookie_has_not_been_refreshed_in_201_to_300_days |= | 
 |           days_since_refresh > 200 && days_since_refresh <= 300; | 
 |       cookie_has_not_been_refreshed_in_301_to_350_days |= | 
 |           days_since_refresh > 300 && days_since_refresh <= 350; | 
 |       cookie_has_not_been_refreshed_in_351_to_400_days |= | 
 |           days_since_refresh > 350 && days_since_refresh <= 400; | 
 |     } | 
 |  | 
 |     cross_origin_same_site_cookie_via_storage_access_api |= | 
 |         cross_origin_same_site_with_storage_access_eligible && | 
 |         cookie->access_result.status.IsInclude() && | 
 |         cookie->access_result.status.exemption_reason() == | 
 |             net::CookieInclusionStatus::ExemptionReason::kStorageAccess; | 
 |   } | 
 |  | 
 |   if (samesite_treated_as_lax_cookies) { | 
 |     GetContentClient()->browser()->LogWebFeatureForCurrentPage( | 
 |         rfh, blink::mojom::WebFeature::kCookieNoSameSite); | 
 |   } | 
 |  | 
 |   if (samesite_none_insecure_cookies) { | 
 |     GetContentClient()->browser()->LogWebFeatureForCurrentPage( | 
 |         rfh, blink::mojom::WebFeature::kCookieInsecureAndSameSiteNone); | 
 |   } | 
 |  | 
 |   if (breaking_context_downgrade) { | 
 |     GetContentClient()->browser()->LogWebFeatureForCurrentPage( | 
 |         rfh, blink::mojom::WebFeature::kSchemefulSameSiteContextDowngrade); | 
 |   } | 
 |  | 
 |   if (lax_allow_unsafe_cookies) { | 
 |     GetContentClient()->browser()->LogWebFeatureForCurrentPage( | 
 |         rfh, blink::mojom::WebFeature::kLaxAllowingUnsafeCookies); | 
 |   } | 
 |  | 
 |   if (samesite_cookie_inclusion_changed_by_cross_site_redirect) { | 
 |     GetContentClient()->browser()->LogWebFeatureForCurrentPage( | 
 |         rfh, blink::mojom::WebFeature:: | 
 |                  kSameSiteCookieInclusionChangedByCrossSiteRedirect); | 
 |   } | 
 |  | 
 |   if (partitioned_cookies_exist) { | 
 |     GetContentClient()->browser()->LogWebFeatureForCurrentPage( | 
 |         rfh, blink::mojom::WebFeature::kPartitionedCookies); | 
 |   } | 
 |  | 
 |   if (cookie_has_not_been_refreshed_in_201_to_300_days) { | 
 |     GetContentClient()->browser()->LogWebFeatureForCurrentPage( | 
 |         rfh, | 
 |         blink::mojom::WebFeature::kCookieHasNotBeenRefreshedIn201To300Days); | 
 |   } | 
 |  | 
 |   if (cookie_has_not_been_refreshed_in_301_to_350_days) { | 
 |     GetContentClient()->browser()->LogWebFeatureForCurrentPage( | 
 |         rfh, | 
 |         blink::mojom::WebFeature::kCookieHasNotBeenRefreshedIn301To350Days); | 
 |   } | 
 |  | 
 |   if (cookie_has_not_been_refreshed_in_351_to_400_days) { | 
 |     GetContentClient()->browser()->LogWebFeatureForCurrentPage( | 
 |         rfh, | 
 |         blink::mojom::WebFeature::kCookieHasNotBeenRefreshedIn351To400Days); | 
 |   } | 
 |  | 
 |   if (cookie_has_domain_non_ascii) { | 
 |     GetContentClient()->browser()->LogWebFeatureForCurrentPage( | 
 |         rfh, blink::mojom::WebFeature::kCookieDomainNonASCII); | 
 |   } | 
 |  | 
 |   if (cookies_exempted_by_top_level_storage_access) { | 
 |     RecordCookiesExemptedByTopLevelStorage( | 
 |         rfh->GetPageUkmSourceId(), | 
 |         cookies_exempted_by_top_level_storage_access); | 
 |   } | 
 |  | 
 |   if (cross_origin_same_site_cookie_via_storage_access_api) { | 
 |     GetContentClient()->browser()->LogWebFeatureForCurrentPage( | 
 |         rfh, blink::mojom::WebFeature:: | 
 |                  kCrossOriginSameSiteCookieAccessViaStorageAccessAPI); | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace content |