| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "services/network/prefetch_matches.h" |
| |
| #include <algorithm> |
| #include <array> |
| #include <functional> |
| #include <iomanip> |
| #include <ios> |
| #include <optional> |
| #include <ostream> |
| #include <string> |
| #include <string_view> |
| #include <type_traits> |
| #include <utility> |
| |
| #include "base/check.h" |
| #include "base/compiler_specific.h" |
| #include "base/containers/contains.h" |
| #include "base/containers/fixed_flat_map.h" |
| #include "base/containers/fixed_flat_set.h" |
| #include "base/containers/span.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/memory/stack_allocated.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/string_util.h" |
| #include "build/buildflag.h" |
| #include "net/base/load_flags.h" |
| #include "net/base/load_flags_to_string.h" |
| #include "net/cookies/site_for_cookies.h" |
| #include "net/filter/source_stream_type.h" |
| #include "net/http/http_request_headers.h" |
| #include "services/network/public/cpp/data_element.h" |
| #include "services/network/public/cpp/resource_request.h" |
| #include "services/network/public/cpp/resource_request_body.h" |
| #include "services/network/request_header_to_enum.h" |
| #include "services/network/stringify_enum.h" |
| #include "third_party/abseil-cpp/absl/container/flat_hash_map.h" |
| |
| namespace network { |
| |
| namespace { |
| |
| // A macro to run the DO_FIELD() macro over every member of the ResourceRequest |
| // struct. This list of fields must be complete so that we can verify at compile |
| // time that it matches the struct. The optional argument can be |
| // used to place a comma after every field but the last one, ie. |
| // DO_FIELD_FOR_ALL_FIELDS(COMMA). |
| #define COMMA , |
| |
| // clang-format off |
| |
| #define DO_FIELD_FOR_ALL_FIELDS(...) \ |
| DO_FIELD(method) __VA_ARGS__ \ |
| DO_FIELD(url) __VA_ARGS__ \ |
| DO_FIELD(site_for_cookies) __VA_ARGS__ \ |
| DO_FIELD(update_first_party_url_on_redirect) __VA_ARGS__ \ |
| DO_FIELD(request_initiator) __VA_ARGS__ \ |
| DO_FIELD(isolated_world_origin) __VA_ARGS__ \ |
| DO_FIELD(navigation_redirect_chain) __VA_ARGS__ \ |
| DO_FIELD(referrer) __VA_ARGS__ \ |
| DO_FIELD(referrer_policy) __VA_ARGS__ \ |
| DO_FIELD(headers) __VA_ARGS__ \ |
| DO_FIELD(cors_exempt_headers) __VA_ARGS__ \ |
| DO_FIELD(load_flags) __VA_ARGS__ \ |
| DO_FIELD(resource_type) __VA_ARGS__ \ |
| DO_FIELD(priority) __VA_ARGS__ \ |
| DO_FIELD(priority_incremental) __VA_ARGS__ \ |
| DO_FIELD(cors_preflight_policy) __VA_ARGS__ \ |
| DO_FIELD(originated_from_service_worker) __VA_ARGS__ \ |
| DO_FIELD(skip_service_worker) __VA_ARGS__ \ |
| DO_FIELD(mode) __VA_ARGS__ \ |
| DO_FIELD(required_ip_address_space) __VA_ARGS__ \ |
| DO_FIELD(credentials_mode) __VA_ARGS__ \ |
| DO_FIELD(redirect_mode) __VA_ARGS__ \ |
| DO_FIELD(fetch_integrity) __VA_ARGS__ \ |
| DO_FIELD(expected_public_keys) __VA_ARGS__ \ |
| DO_FIELD(destination) __VA_ARGS__ \ |
| DO_FIELD(original_destination) __VA_ARGS__ \ |
| DO_FIELD(request_body) __VA_ARGS__ \ |
| DO_FIELD(keepalive) __VA_ARGS__ \ |
| DO_FIELD(browsing_topics) __VA_ARGS__ \ |
| DO_FIELD(ad_auction_headers) __VA_ARGS__ \ |
| DO_FIELD(shared_storage_writable_eligible) __VA_ARGS__ \ |
| DO_FIELD(has_user_gesture) __VA_ARGS__ \ |
| DO_FIELD(enable_load_timing) __VA_ARGS__ \ |
| DO_FIELD(enable_upload_progress) __VA_ARGS__ \ |
| DO_FIELD(do_not_prompt_for_login) __VA_ARGS__ \ |
| DO_FIELD(is_outermost_main_frame) __VA_ARGS__ \ |
| DO_FIELD(transition_type) __VA_ARGS__ \ |
| DO_FIELD(previews_state) __VA_ARGS__ \ |
| DO_FIELD(upgrade_if_insecure) __VA_ARGS__ \ |
| DO_FIELD(is_revalidating) __VA_ARGS__ \ |
| DO_FIELD(throttling_profile_id) __VA_ARGS__ \ |
| DO_FIELD(fetch_window_id) __VA_ARGS__ \ |
| DO_FIELD(devtools_request_id) __VA_ARGS__ \ |
| DO_FIELD(devtools_stack_id) __VA_ARGS__ \ |
| DO_FIELD(is_fetch_like_api) __VA_ARGS__ \ |
| DO_FIELD(is_fetch_later_api) __VA_ARGS__ \ |
| DO_FIELD(is_favicon) __VA_ARGS__ \ |
| DO_FIELD(recursive_prefetch_token) __VA_ARGS__ \ |
| DO_FIELD(trusted_params) __VA_ARGS__ \ |
| DO_FIELD(trust_token_params) __VA_ARGS__ \ |
| DO_FIELD(web_bundle_token_params) __VA_ARGS__ \ |
| DO_FIELD(devtools_accepted_stream_types) __VA_ARGS__ \ |
| DO_FIELD(net_log_create_info) __VA_ARGS__ \ |
| DO_FIELD(net_log_reference_info) __VA_ARGS__ \ |
| DO_FIELD(storage_access_api_status) __VA_ARGS__ \ |
| DO_FIELD(attribution_reporting_support) __VA_ARGS__ \ |
| DO_FIELD(attribution_reporting_eligibility) __VA_ARGS__ \ |
| DO_FIELD(shared_dictionary_writer_enabled) __VA_ARGS__ \ |
| DO_FIELD(attribution_reporting_src_token) __VA_ARGS__ \ |
| DO_FIELD(is_ad_tagged) __VA_ARGS__ \ |
| DO_FIELD(client_side_content_decoding_enabled) __VA_ARGS__ \ |
| DO_FIELD(prefetch_token) __VA_ARGS__ \ |
| DO_FIELD(socket_tag) __VA_ARGS__ \ |
| DO_FIELD(keepalive_token) __VA_ARGS__ \ |
| DO_FIELD(allows_device_bound_session_registration) __VA_ARGS__ \ |
| DO_FIELD(permissions_policy) __VA_ARGS__ \ |
| DO_FIELD(fetch_retry_options) |
| |
| // clang-format on |
| |
| constexpr bool FieldCountIsCorrect(const ResourceRequest& request) { |
| if ((false)) { |
| // This will fail to compile if the number of fields in ResourceRequest is |
| // different from the number this code knows about. It doesn't actually need |
| // to be executed. The double extra parenthesis around "(false)" indicate to |
| // the compiler that this code is intentionally unreachable. |
| #define DO_FIELD(name) unused_##name |
| [[maybe_unused]] const auto& [DO_FIELD_FOR_ALL_FIELDS(COMMA)] = request; |
| #undef DO_FIELD |
| } |
| return true; |
| } |
| |
| enum class Fields { |
| #define DO_FIELD(name) k##name, |
| DO_FIELD_FOR_ALL_FIELDS() |
| #undef DO_FIELD |
| }; |
| |
| // These values are persisted to logs. Entries should not be renumbered and |
| // numeric values should never be reused. Fields that have been removed from |
| // ResourceRequest should have "Obsolete" added to their names. The `kUnknown` |
| // value is used for fields that do not have an entry in the mapping below. If |
| // it shows up in UMA then this enum needs to be updated to include the new |
| // field(s). Fields that are not used for matching do not need to be included in |
| // this enum. |
| // |
| // LINT.IfChange(FieldsForUma) |
| enum class FieldsForUma { |
| kUnknown = 0, |
| kMethod = 1, |
| kUrl = 2, |
| kSiteForCookies = 3, |
| kUpdateFirstPartyUrlOnRedirect = 4, |
| kRequestInitiator = 5, |
| kIsolatedWorldOrigin = 6, |
| kNavigationRedirectChain = 7, |
| kReferrer = 8, |
| kReferrerPolicy = 9, |
| kHeaders = 10, |
| kCorsExemptHeaders = 11, |
| kLoadFlags = 12, |
| kResourceType = 13, |
| kPriority = 14, |
| kPriorityIncremental = 15, |
| kCorsPreflightPolicy = 16, |
| kOriginatedFromServiceWorker = 17, |
| kSkipServiceWorker = 18, |
| kMode = 19, |
| kRequiredIpAddressSpace = 20, |
| kCredentialsMode = 21, |
| kRedirectMode = 22, |
| kFetchIntegrity = 23, |
| kDestination = 24, |
| kOriginalDestination = 25, |
| kRequestBody = 26, |
| kKeepalive = 27, |
| kBrowsingTopics = 28, |
| kAdAuctionHeaders = 29, |
| kSharedStorageWritableEligible = 30, |
| kHasUserGesture = 31, |
| kEnableLoadTiming = 32, |
| kEnableUploadProgress = 33, |
| kDoNotPromptForLogin = 34, |
| kIsOutermostMainFrame = 35, |
| kTransitionType = 36, |
| kPreviewsState = 37, |
| kUpgradeIfInsecure = 38, |
| kIsRevalidating = 39, |
| kThrottlingProfileId = 40, |
| // DEPRECATED: kCustomProxyPreCacheHeaders = 41, |
| // DEPRECATED: kCustomProxyPostCacheHeaders = 42, |
| kFetchWindowId = 43, |
| kDevtoolsRequestId = 44, |
| kDevtoolsStackId = 45, |
| kIsFetchLikeApi = 46, |
| kIsFetchLaterApi = 47, |
| kIsFavicon = 48, |
| kRecursivePrefetchToken = 49, |
| kTrustedParams = 50, |
| kTrustTokenParams = 51, |
| kWebBundleTokenParams = 52, |
| kDevtoolsAcceptedStreamTypes = 53, |
| kNetLogCreateInfo = 54, |
| kNetLogReferenceInfo = 55, |
| // DEPRECATED: kTargetIpAddressSpace = 56, |
| kStorageAccessApiStatus = 57, |
| kAttributionReportingSupport = 58, |
| kAttributionReportingEligibility = 59, |
| kSharedDictionaryWriterEnabled = 60, |
| kAttributionReportingSrcToken = 61, |
| kIsAdTagged = 62, |
| kKeepaliveToken = 63, |
| kExpectedPublicKeys = 64, |
| kPermissionsPolicy = 65, |
| kClientSideContentDecodingEnabled = 66, |
| kMaxValue = kClientSideContentDecodingEnabled, |
| }; |
| // LINT.ThenChange(//tools/metrics/histograms/metadata/network/enums.xml:PrefetchMatchesResourceRequestField) |
| |
| constexpr auto kUmaEnumMap = base::MakeFixedFlatMap<Fields, FieldsForUma>({ |
| {Fields::kmethod, FieldsForUma::kMethod}, |
| {Fields::kurl, FieldsForUma::kUrl}, |
| {Fields::ksite_for_cookies, FieldsForUma::kSiteForCookies}, |
| {Fields::kupdate_first_party_url_on_redirect, |
| FieldsForUma::kUpdateFirstPartyUrlOnRedirect}, |
| {Fields::krequest_initiator, FieldsForUma::kRequestInitiator}, |
| {Fields::kisolated_world_origin, FieldsForUma::kIsolatedWorldOrigin}, |
| {Fields::knavigation_redirect_chain, |
| FieldsForUma::kNavigationRedirectChain}, |
| {Fields::kreferrer, FieldsForUma::kReferrer}, |
| {Fields::kreferrer_policy, FieldsForUma::kReferrerPolicy}, |
| {Fields::kheaders, FieldsForUma::kHeaders}, |
| {Fields::kcors_exempt_headers, FieldsForUma::kCorsExemptHeaders}, |
| {Fields::kload_flags, FieldsForUma::kLoadFlags}, |
| {Fields::kresource_type, FieldsForUma::kResourceType}, |
| {Fields::kpriority, FieldsForUma::kPriority}, |
| {Fields::kpriority_incremental, FieldsForUma::kPriorityIncremental}, |
| {Fields::kcors_preflight_policy, FieldsForUma::kCorsPreflightPolicy}, |
| {Fields::koriginated_from_service_worker, |
| FieldsForUma::kOriginatedFromServiceWorker}, |
| {Fields::kskip_service_worker, FieldsForUma::kSkipServiceWorker}, |
| {Fields::kmode, FieldsForUma::kMode}, |
| {Fields::krequired_ip_address_space, FieldsForUma::kRequiredIpAddressSpace}, |
| {Fields::kcredentials_mode, FieldsForUma::kCredentialsMode}, |
| {Fields::kredirect_mode, FieldsForUma::kRedirectMode}, |
| {Fields::kfetch_integrity, FieldsForUma::kFetchIntegrity}, |
| {Fields::kexpected_public_keys, FieldsForUma::kExpectedPublicKeys}, |
| {Fields::kdestination, FieldsForUma::kDestination}, |
| {Fields::koriginal_destination, FieldsForUma::kOriginalDestination}, |
| {Fields::krequest_body, FieldsForUma::kRequestBody}, |
| {Fields::kkeepalive, FieldsForUma::kKeepalive}, |
| {Fields::kbrowsing_topics, FieldsForUma::kBrowsingTopics}, |
| {Fields::kad_auction_headers, FieldsForUma::kAdAuctionHeaders}, |
| {Fields::kshared_storage_writable_eligible, |
| FieldsForUma::kSharedStorageWritableEligible}, |
| {Fields::khas_user_gesture, FieldsForUma::kHasUserGesture}, |
| {Fields::kenable_load_timing, FieldsForUma::kEnableLoadTiming}, |
| {Fields::kenable_upload_progress, FieldsForUma::kEnableUploadProgress}, |
| {Fields::kdo_not_prompt_for_login, FieldsForUma::kDoNotPromptForLogin}, |
| {Fields::kis_outermost_main_frame, FieldsForUma::kIsOutermostMainFrame}, |
| {Fields::ktransition_type, FieldsForUma::kTransitionType}, |
| {Fields::kpreviews_state, FieldsForUma::kPreviewsState}, |
| {Fields::kupgrade_if_insecure, FieldsForUma::kUpgradeIfInsecure}, |
| {Fields::kis_revalidating, FieldsForUma::kIsRevalidating}, |
| {Fields::kthrottling_profile_id, FieldsForUma::kThrottlingProfileId}, |
| {Fields::kfetch_window_id, FieldsForUma::kFetchWindowId}, |
| {Fields::kdevtools_request_id, FieldsForUma::kDevtoolsRequestId}, |
| {Fields::kdevtools_stack_id, FieldsForUma::kDevtoolsStackId}, |
| {Fields::kis_fetch_like_api, FieldsForUma::kIsFetchLikeApi}, |
| {Fields::kis_fetch_later_api, FieldsForUma::kIsFetchLaterApi}, |
| {Fields::kis_favicon, FieldsForUma::kIsFavicon}, |
| {Fields::krecursive_prefetch_token, FieldsForUma::kRecursivePrefetchToken}, |
| {Fields::ktrust_token_params, FieldsForUma::kTrustTokenParams}, |
| {Fields::kweb_bundle_token_params, FieldsForUma::kWebBundleTokenParams}, |
| {Fields::kdevtools_accepted_stream_types, |
| FieldsForUma::kDevtoolsAcceptedStreamTypes}, |
| {Fields::knet_log_create_info, FieldsForUma::kNetLogCreateInfo}, |
| {Fields::knet_log_reference_info, FieldsForUma::kNetLogReferenceInfo}, |
| {Fields::kstorage_access_api_status, FieldsForUma::kStorageAccessApiStatus}, |
| {Fields::kattribution_reporting_support, |
| FieldsForUma::kAttributionReportingSupport}, |
| {Fields::kattribution_reporting_eligibility, |
| FieldsForUma::kAttributionReportingEligibility}, |
| {Fields::kshared_dictionary_writer_enabled, |
| FieldsForUma::kSharedDictionaryWriterEnabled}, |
| {Fields::kattribution_reporting_src_token, |
| FieldsForUma::kAttributionReportingSrcToken}, |
| {Fields::kis_ad_tagged, FieldsForUma::kIsAdTagged}, |
| {Fields::kclient_side_content_decoding_enabled, |
| FieldsForUma::kClientSideContentDecodingEnabled}, |
| {Fields::kkeepalive_token, FieldsForUma::kKeepaliveToken}, |
| {Fields::kpermissions_policy, FieldsForUma::kPermissionsPolicy}, |
| }); |
| |
| // Fields that should be completely ignored for the purposes of matching should |
| // be added to this array. |
| constexpr std::array kIgnoredFields = { |
| // TODO(crbug.com/342445996): Dynamically adjust the priority of the request |
| // once the real request arrives. |
| Fields::kpriority, |
| Fields::kpriority_incremental, |
| |
| // Prefefches can't prompt for login, but render processes generally allow |
| // it. |
| // TODO(crbug.com/342445996): Is this really okay? What's the right answer |
| // here? |
| Fields::kdo_not_prompt_for_login, |
| |
| // TODO(crbug.com/342445996): Figure out how to plumb the |
| // throttling_profile_id through to the prefetch machinery so that |
| // prefetches will be throttled correctly. Then remove this. |
| Fields::kthrottling_profile_id, |
| |
| // fetch_window_id is used to associate auth/client certificate UI with the |
| // correct window. Prefetches should never display UI, so don't need this. |
| Fields::kfetch_window_id, |
| |
| // TODO(crbug.com/342445996): Wire up devtools to prefetches somehow. |
| Fields::kdevtools_request_id, |
| Fields::kdevtools_stack_id, |
| // trusted_params are used by the prefetch machinery, but are prohibited |
| // from being sent by a render process, so won't match. |
| Fields::ktrusted_params, |
| |
| // These are not expected to match. |
| Fields::knet_log_create_info, |
| Fields::knet_log_reference_info, |
| |
| // Currently ignored. |
| Fields::kprefetch_token, |
| |
| // It doesn't matter if they match. |
| Fields::ksocket_tag, |
| }; |
| |
| // These headers are completely ignored for the purposes of matching when they |
| // appear in the `headers` field. |
| constexpr auto kIgnoredHeaders = base::MakeFixedFlatSet<std::string_view>({ |
| "purpose", |
| "sec-purpose", |
| }); |
| using IgnoredHeadersType = decltype(kIgnoredHeaders); |
| |
| bool MatchHeadersWithExceptions(const net::HttpRequestHeaders& prefetch_headers, |
| const net::HttpRequestHeaders& real_headers, |
| const IgnoredHeadersType& ignored_headers) { |
| absl::flat_hash_map<std::string, std::string_view> lowered_prefetch_headers; |
| const net::HttpRequestHeaders::HeaderVector& prefetch_headers_vector = |
| prefetch_headers.GetHeaderVector(); |
| lowered_prefetch_headers.reserve(prefetch_headers_vector.size()); |
| for (const auto& keyvalue : prefetch_headers_vector) { |
| std::string lowered_key = base::ToLowerASCII(keyvalue.key); |
| if (ignored_headers.contains(lowered_key)) { |
| continue; |
| } |
| auto [_, inserted] = lowered_prefetch_headers.emplace( |
| std::move(lowered_key), keyvalue.value); |
| CHECK(inserted) << "There should be no duplicate header keys in an " |
| "HttpRequestHeaders object, and certainly not '" |
| << lowered_key << "'"; |
| } |
| const net::HttpRequestHeaders::HeaderVector& real_headers_vector = |
| real_headers.GetHeaderVector(); |
| for (const auto& keyvalue : real_headers_vector) { |
| std::string lowered_key = base::ToLowerASCII(keyvalue.key); |
| if (ignored_headers.contains(lowered_key)) { |
| continue; |
| } |
| auto it = lowered_prefetch_headers.find(lowered_key); |
| if (it == lowered_prefetch_headers.end()) { |
| LogLowerCaseRequestHeaderToUma( |
| "Network.PrefetchMatches.FirstHeaderMissingFromPrefetch", |
| lowered_key); |
| return false; |
| } |
| if (it->second != keyvalue.value) { |
| LogLowerCaseRequestHeaderToUma( |
| "Network.PrefetchMatches.FirstHeaderValueMismatch", lowered_key); |
| return false; |
| } |
| lowered_prefetch_headers.erase(it); |
| } |
| if (lowered_prefetch_headers.empty()) { |
| return true; |
| } |
| |
| // Any value in `lowered_prefetch_headers` that hasn't been erased was missing |
| // from the real headers. Prefetches never have a lot of headers, so just |
| // record all of them. |
| for (const auto& [name, _] : lowered_prefetch_headers) { |
| LogLowerCaseRequestHeaderToUma( |
| "Network.PrefetchMatches.HeaderOnlyInPrefetch", name); |
| } |
| return false; |
| } |
| |
| // MatchByType() can be overloaded to provide special behavior for specific |
| // types. This will apply to all fields of this type that do not have a |
| // specialization of FieldMatcher. |
| |
| // This is the generic implementation that uses operator==. |
| template <typename T> |
| bool MatchByType(const T& prefetch_value, const T& real_value) { |
| return prefetch_value == real_value; |
| } |
| |
| // SiteForCookies does not have operator==. |
| bool MatchByType(const net::SiteForCookies& prefetch_value, |
| const net::SiteForCookies& real_value) { |
| return prefetch_value.IsEquivalent(real_value); |
| } |
| |
| bool MatchByType(const net::HttpRequestHeaders& prefetch_headers, |
| const net::HttpRequestHeaders& real_headers) { |
| return MatchHeadersWithExceptions(prefetch_headers, real_headers, {}); |
| } |
| |
| bool MatchByType( |
| const std::optional<ResourceRequest::WebBundleTokenParams>& prefetch_params, |
| const std::optional<ResourceRequest::WebBundleTokenParams>& real_params) { |
| if (prefetch_params.has_value() != real_params.has_value()) { |
| return false; |
| } |
| if (!prefetch_params.has_value()) { |
| return true; |
| } |
| // Neither `handle` nor `render_process_id` would be expected to match anyway. |
| return prefetch_params->bundle_url == real_params->bundle_url && |
| prefetch_params->token == real_params->token; |
| } |
| |
| bool MatchByType(const DataElementBytes& prefetch_value, |
| const DataElementBytes& real_value) { |
| return prefetch_value.bytes() == real_value.bytes(); |
| } |
| |
| bool MatchByType(const DataElementFile& prefetch_value, |
| const DataElementFile& real_value) { |
| return prefetch_value.path() == real_value.path() && |
| prefetch_value.offset() == real_value.offset() && |
| prefetch_value.length() == real_value.length() && |
| prefetch_value.expected_modification_time() == |
| real_value.expected_modification_time(); |
| } |
| |
| bool MatchByType(const DataElement& prefetch_value, |
| const DataElement& real_value) { |
| using Tag = DataElement::Tag; |
| const Tag prefetch_tag = prefetch_value.type(); |
| const Tag real_tag = real_value.type(); |
| if (prefetch_tag != real_tag) { |
| return false; |
| } |
| switch (real_tag) { |
| case Tag::kBytes: |
| return MatchByType(prefetch_value.As<DataElementBytes>(), |
| real_value.As<DataElementBytes>()); |
| case Tag::kFile: |
| return MatchByType(prefetch_value.As<DataElementFile>(), |
| real_value.As<DataElementFile>()); |
| default: |
| // For now, every other type just returns false. If we needed to support |
| // DataElementDataPipe or DataElementChunkedDataPipe we'd need to somehow |
| // make this function asynchronous. |
| return false; |
| } |
| } |
| |
| bool MatchByType(const ResourceRequestBody& prefetch_value, |
| const ResourceRequestBody& real_value) { |
| const auto& prefetch_elements = *prefetch_value.elements(); |
| const auto& real_elements = *real_value.elements(); |
| if (prefetch_elements.size() != real_elements.size()) { |
| return false; |
| } |
| for (size_t i = 0; i < prefetch_elements.size(); ++i) { |
| if (!MatchByType(prefetch_elements[i], real_elements[i])) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // Unwrap a scoped_refptr to perform a match on the contained objects. |
| template <typename T> |
| bool MatchByType(const scoped_refptr<T>& prefetch_value, |
| const scoped_refptr<T>& real_value) { |
| if (prefetch_value == nullptr && real_value == nullptr) { |
| return true; |
| } |
| if (prefetch_value == nullptr || real_value == nullptr) { |
| // Since both of them aren't null, one of them must be null and the other |
| // not, so they don't match. |
| return false; |
| } |
| return MatchByType(*prefetch_value, *real_value); |
| } |
| |
| // FieldMatcher is the configuration point for matches whose behaviour varies |
| // depending on which field is involved. Specializations should be created for |
| // fields that need specific behavior. |
| |
| // This is the general implementation of FieldMatcher which doesn't care what |
| // field it is looking at, and only compares based on the type. |
| template <Fields f> |
| struct FieldMatcher { |
| template <typename T> |
| static bool Match(const T& prefetch_value, const T& real_value) { |
| // Immediately dispatch to a function that doesn't care about the field to |
| // encourage the compiler to reduce code size. |
| return MatchByType(prefetch_value, real_value); |
| } |
| }; |
| |
| template <Fields f> |
| constexpr bool kFieldIsIgnored = base::Contains(kIgnoredFields, f); |
| |
| // This is the implementation of FieldMatcher that completely ignores the |
| // contents of the field. Fields which should use this implementation should be |
| // added to `kIgnoredFields` above. |
| template <Fields f> |
| requires kFieldIsIgnored<f> |
| struct FieldMatcher<f> { |
| template <typename T> |
| static bool Match(const T& prefetch_request, const T& real_request) { |
| return true; |
| } |
| }; |
| |
| // We ignore some differences in the `headers` field. |
| template <> |
| struct FieldMatcher<Fields::kheaders> { |
| static bool Match(const net::HttpRequestHeaders& prefetch_headers, |
| const net::HttpRequestHeaders& real_headers) { |
| return MatchHeadersWithExceptions(prefetch_headers, real_headers, |
| kIgnoredHeaders); |
| } |
| }; |
| |
| // We ignore LOAD_PREFETCH in the `load_flags` field. |
| template <> |
| struct FieldMatcher<Fields::kload_flags> { |
| static bool Match(int prefetch_load_flags, int real_load_flags) { |
| static constexpr auto without_prefetch = [](int load_flags) { |
| return load_flags & ~net::LOAD_PREFETCH; |
| }; |
| return without_prefetch(prefetch_load_flags) == |
| without_prefetch(real_load_flags); |
| } |
| }; |
| |
| void LogMismatchToUma(Fields field) { |
| auto it = kUmaEnumMap.find(field); |
| FieldsForUma uma_field = FieldsForUma::kUnknown; |
| if (it != kUmaEnumMap.end()) { |
| uma_field = it->second; |
| } |
| // TODO(ricea): This can be switched to the function version whem mismatches |
| // become sufficiently rare that the overhead doesn't matter. |
| UMA_HISTOGRAM_ENUMERATION("Network.PrefetchMatches.FirstMismatch", uma_field); |
| } |
| |
| void PrintSpanifiedObject(std::ostream& os, base::span<const uint8_t> object) { |
| os << object.size() << "-byte-object<"; |
| size_t counter = 0; |
| for (uint8_t byte : object) { |
| auto oldflags = os.flags(); |
| os << '<' << std::hex << std::setfill('0') << std::setw(2) << uint32_t{byte} |
| << '>'; |
| os.flags(oldflags); |
| ++counter; |
| if (counter < object.size()) { |
| if (counter % 8 == 0) { |
| os << ' '; |
| } else { |
| os << '-'; |
| } |
| } |
| } |
| os << '>'; |
| } |
| |
| template <typename T> |
| void PrintAsBinary(std::ostream& os, const T& value) { |
| // Safety: `value` has size sizeof(T); aliasing via byte types is OK and |
| // shouldn't require any extra alignment. |
| PrintSpanifiedObject( |
| os, UNSAFE_BUFFERS( |
| base::span(reinterpret_cast<const uint8_t*>(&value), sizeof(T)))); |
| } |
| |
| // CustomPrinter() is a customization point for types that need custom handling |
| // to print the values. |
| |
| // This version enables printing anything that has a suitable ToString() |
| // method. |
| template <typename T> |
| requires requires(std::ostream& os, const T& value) { |
| os << value.ToString(); |
| } |
| void CustomPrinter(std::ostream& os, const T& value) { |
| os << value.ToString(); |
| } |
| |
| // This version enables printing anything that has a suitable ToDebugString() |
| // method. |
| template <typename T> |
| requires requires(std::ostream& os, const T& value) { |
| os << value.ToDebugString(); |
| } |
| void CustomPrinter(std::ostream& os, const T& value) { |
| os << value.ToDebugString(); |
| } |
| |
| // Print an enum. |
| template <typename T> |
| requires std::is_enum_v<T> |
| void CustomPrinter(std::ostream& os, const T& value) { |
| StreamEnumValueTo(os, value); |
| } |
| |
| // The printers for std::vector and std::optional can recursively call into |
| // other printers, so they need to go last. They also need to be preclared. |
| |
| template <typename T> |
| void CustomPrinter(std::ostream& os, const std::vector<T>& values); |
| template <typename T> |
| void CustomPrinter(std::ostream& os, const std::optional<T>& value); |
| |
| // Print a std::vector of something using a CustomPrinter if available. |
| template <typename T> |
| void CustomPrinter(std::ostream& os, const std::vector<T>& values) { |
| os << '{'; |
| for (size_t i = 0; i < values.size(); ++i) { |
| if constexpr (requires { CustomPrinter(os, values[i]); }) { |
| CustomPrinter(os, values[i]); |
| } else { |
| os << values[i]; |
| } |
| if (i < values.size() - 1) { |
| os << ", "; |
| } |
| } |
| os << '}'; |
| } |
| |
| // Print something wrapped in std::optional that otherwise would be printable. |
| template <typename T> |
| void CustomPrinter(std::ostream& os, const std::optional<T>& value) { |
| if (value.has_value()) { |
| const T& inner_value = value.value(); |
| if constexpr (requires { CustomPrinter(os, inner_value); }) { |
| CustomPrinter(os, inner_value); |
| } else if constexpr (requires { os << inner_value; }) { |
| os << inner_value; |
| } else { |
| PrintAsBinary(os, inner_value); |
| } |
| } else { |
| os << "std::nullopt"; |
| } |
| } |
| |
| // MakeStreamable makes it possible to stream a value of any type (except void) |
| // to a stream. It does this by using a CustomPrinter() if one is available, and |
| // falling back to printing bytes if all else fails. |
| template <typename T> |
| class MakeStreamable final { |
| STACK_ALLOCATED(); |
| |
| public: |
| explicit MakeStreamable(const T& value) : value_(value) {} |
| |
| MakeStreamable(const MakeStreamable&) = delete; |
| MakeStreamable& operator=(const MakeStreamable&) = delete; |
| |
| private: |
| friend std::ostream& operator<<(std::ostream& os, MakeStreamable<T>&& me) { |
| const T& value = me.value_; |
| if constexpr (requires { CustomPrinter(os, value); }) { |
| CustomPrinter(os, value); |
| } else if constexpr (requires { os << value; }) { |
| os << value; |
| } else { |
| PrintAsBinary(os, value); |
| } |
| return os; |
| } |
| |
| const T& value_; |
| }; |
| |
| template <typename T> |
| void LogMismatchVerbosely(const T& prefetch_value, |
| const T& real_value, |
| std::string_view name) { |
| DVLOG(1) << "Mismatch between prefetched\nResourceRequest and real " |
| << "ResourceRequest:\n" |
| << std::boolalpha << "prefetch_request." << name << " = " |
| << MakeStreamable(prefetch_value) << "\n real_request." << name |
| << " = " << MakeStreamable(real_value) << "\n"; |
| } |
| |
| // A special version of LogMismatchVerbosely just for load_flags. At this time |
| // we don't need a generalised method to change the logging per-field, so just |
| // overload for this specific case. |
| void LogMismatchVerbosely(int prefetch_value, |
| int real_value, |
| std::string_view name) { |
| if (!DCHECK_IS_ON()) { |
| return; |
| } |
| if (name == "load_flags") { |
| DVLOG(1) << "Mismatch between prefetched\nResourceRequest and real " |
| << "ResourceRequest:\nprefetch_request.load_flags = " |
| << net::LoadFlagsToString(prefetch_value) |
| << "\n real_request.load_flags = " |
| << net::LoadFlagsToString(real_value) << "\n "; |
| } else { |
| DVLOG(1) << "Mismatch between prefetched\nResourceRequest and real " |
| << "ResourceRequest:\nprefetch_request." << name << " = " |
| << prefetch_value << "\n real_request." << name << " = " |
| << real_value << "\n"; |
| } |
| } |
| |
| } // namespace |
| |
| bool PrefetchMatches(const ResourceRequest& prefetch_request, |
| const ResourceRequest& real_request) { |
| CHECK(!real_request.trusted_params.has_value()); |
| |
| CHECK(FieldCountIsCorrect(prefetch_request)); |
| |
| bool all_fields_match = true; |
| using enum Fields; |
| |
| #define DO_FIELD(name) \ |
| if (!FieldMatcher<k##name>::Match(prefetch_request.name, \ |
| real_request.name)) { \ |
| if (all_fields_match) { \ |
| LogMismatchToUma(k##name); \ |
| all_fields_match = false; \ |
| } \ |
| LogMismatchVerbosely(prefetch_request.name, real_request.name, #name); \ |
| } |
| |
| DO_FIELD_FOR_ALL_FIELDS() |
| |
| #undef DO_FIELD |
| |
| UMA_HISTOGRAM_BOOLEAN("Network.PrefetchMatches.Result", all_fields_match); |
| return all_fields_match; |
| } |
| |
| } // namespace network |