| // Copyright 2017 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/loader/navigation_url_loader_impl.h" | 
 |  | 
 | #include <map> | 
 | #include <memory> | 
 | #include <optional> | 
 | #include <string_view> | 
 | #include <utility> | 
 |  | 
 | #include "base/command_line.h" | 
 | #include "base/containers/contains.h" | 
 | #include "base/debug/crash_logging.h" | 
 | #include "base/debug/dump_without_crashing.h" | 
 | #include "base/feature_list.h" | 
 | #include "base/functional/bind.h" | 
 | #include "base/functional/callback_helpers.h" | 
 | #include "base/memory/scoped_refptr.h" | 
 | #include "base/metrics/histogram_functions.h" | 
 | #include "base/metrics/histogram_macros.h" | 
 | #include "base/strings/strcat.h" | 
 | #include "base/strings/string_number_conversions.h" | 
 | #include "base/trace_event/trace_event.h" | 
 | #include "build/build_config.h" | 
 | #include "components/download/public/common/download_stats.h" | 
 | #include "content/browser/about_url_loader_factory.h" | 
 | #include "content/browser/attribution_reporting/attribution_manager.h" | 
 | #include "content/browser/blob_storage/chrome_blob_storage_context.h" | 
 | #include "content/browser/client_hints/client_hints.h" | 
 | #include "content/browser/data_url_loader_factory.h" | 
 | #include "content/browser/devtools/devtools_instrumentation.h" | 
 | #include "content/browser/file_system/file_system_url_loader_factory.h" | 
 | #include "content/browser/loader/file_url_loader_factory.h" | 
 | #include "content/browser/loader/navigation_early_hints_manager.h" | 
 | #include "content/browser/loader/navigation_loader_interceptor.h" | 
 | #include "content/browser/loader/navigation_url_loader_delegate.h" | 
 | #include "content/browser/loader/response_head_update_params.h" | 
 | #include "content/browser/loader/subresource_proxying_url_loader_service.h" | 
 | #include "content/browser/loader/url_loader_factory_utils.h" | 
 | #include "content/browser/navigation_subresource_loader_params.h" | 
 | #include "content/browser/preloading/prefetch/prefetch_features.h" | 
 | #include "content/browser/preloading/prefetch/prefetch_url_loader_interceptor.h" | 
 | #include "content/browser/renderer_host/frame_tree_node.h" | 
 | #include "content/browser/renderer_host/navigation_request.h" | 
 | #include "content/browser/renderer_host/navigation_request_info.h" | 
 | #include "content/browser/service_worker/service_worker_client.h" | 
 | #include "content/browser/service_worker/service_worker_main_resource_handle.h" | 
 | #include "content/browser/service_worker/service_worker_main_resource_loader_interceptor.h" | 
 | #include "content/browser/storage_partition_impl.h" | 
 | #include "content/browser/web_contents/web_contents_impl.h" | 
 | #include "content/browser/web_package/prefetched_signed_exchange_cache.h" | 
 | #include "content/browser/web_package/signed_exchange_consts.h" | 
 | #include "content/browser/web_package/signed_exchange_request_handler.h" | 
 | #include "content/browser/web_package/signed_exchange_utils.h" | 
 | #include "content/browser/webui/url_data_manager_backend.h" | 
 | #include "content/public/browser/browser_context.h" | 
 | #include "content/public/browser/browser_task_traits.h" | 
 | #include "content/public/browser/browser_thread.h" | 
 | #include "content/public/browser/client_hints.h" | 
 | #include "content/public/browser/client_hints_controller_delegate.h" | 
 | #include "content/public/browser/content_browser_client.h" | 
 | #include "content/public/browser/download_utils.h" | 
 | #include "content/public/browser/frame_accept_header.h" | 
 | #include "content/public/browser/navigation_ui_data.h" | 
 | #include "content/public/browser/network_service_instance.h" | 
 | #include "content/public/browser/network_service_util.h" | 
 | #include "content/public/browser/shared_cors_origin_access_list.h" | 
 | #include "content/public/browser/ssl_status.h" | 
 | #include "content/public/browser/url_loader_request_interceptor.h" | 
 | #include "content/public/browser/url_loader_throttles.h" | 
 | #include "content/public/browser/web_ui_url_loader_factory.h" | 
 | #include "content/public/common/buildflags.h" | 
 | #include "content/public/common/content_client.h" | 
 | #include "content/public/common/content_features.h" | 
 | #include "content/public/common/content_switches.h" | 
 | #include "content/public/common/referrer.h" | 
 | #include "content/public/common/url_constants.h" | 
 | #include "content/public/common/url_utils.h" | 
 | #include "content/public/common/webplugininfo.h" | 
 | #include "media/media_buildflags.h" | 
 | #include "mojo/public/cpp/bindings/receiver.h" | 
 | #include "mojo/public/cpp/bindings/remote.h" | 
 | #include "net/base/load_flags.h" | 
 | #include "net/base/load_timing_info.h" | 
 | #include "net/cert/sct_status_flags.h" | 
 | #include "net/cert/signed_certificate_timestamp_and_status.h" | 
 | #include "net/cookies/cookie_setting_override.h" | 
 | #include "net/http/http_content_disposition.h" | 
 | #include "net/http/http_request_headers.h" | 
 | #include "net/http/http_status_code.h" | 
 | #include "net/ssl/ssl_info.h" | 
 | #include "net/traffic_annotation/network_traffic_annotation.h" | 
 | #include "net/url_request/redirect_util.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/metrics/public/cpp/ukm_source_id.h" | 
 | #include "services/network/public/cpp/constants.h" | 
 | #include "services/network/public/cpp/features.h" | 
 | #include "services/network/public/cpp/parsed_headers.h" | 
 | #include "services/network/public/cpp/request_destination.h" | 
 | #include "services/network/public/cpp/url_loader_completion_status.h" | 
 | #include "services/network/public/cpp/url_loader_factory_builder.h" | 
 | #include "services/network/public/cpp/url_util.h" | 
 | #include "services/network/public/cpp/web_sandbox_flags.h" | 
 | #include "services/network/public/cpp/wrapper_shared_url_loader_factory.h" | 
 | #include "services/network/public/mojom/network_context.mojom-forward.h" | 
 | #include "services/network/public/mojom/network_service.mojom.h" | 
 | #include "third_party/blink/public/common/features.h" | 
 | #include "third_party/blink/public/common/loader/mime_sniffing_throttle.h" | 
 | #include "third_party/blink/public/common/loader/record_load_histograms.h" | 
 | #include "third_party/blink/public/common/loader/throttling_url_loader.h" | 
 | #include "third_party/blink/public/common/mime_util/mime_util.h" | 
 | #include "third_party/blink/public/common/storage_key/storage_key.h" | 
 | #include "third_party/blink/public/mojom/service_worker/service_worker_router_rule.mojom.h" | 
 | #include "url/origin.h" | 
 |  | 
 | #if BUILDFLAG(IS_ANDROID) | 
 | #include "content/browser/android/content_url_loader_factory.h" | 
 | #endif | 
 |  | 
 | #if BUILDFLAG(ENABLE_PLUGINS) | 
 | #include "content/public/browser/plugin_service.h" | 
 | #endif | 
 |  | 
 | namespace content { | 
 |  | 
 | namespace { | 
 |  | 
 | class NavigationLoaderInterceptorBrowserContainer | 
 |     : public NavigationLoaderInterceptor { | 
 |  public: | 
 |   explicit NavigationLoaderInterceptorBrowserContainer( | 
 |       std::unique_ptr<URLLoaderRequestInterceptor> browser_interceptor) | 
 |       : browser_interceptor_(std::move(browser_interceptor)) {} | 
 |  | 
 |   ~NavigationLoaderInterceptorBrowserContainer() override = default; | 
 |  | 
 |   void MaybeCreateLoader( | 
 |       const network::ResourceRequest& tentative_resource_request, | 
 |       BrowserContext* browser_context, | 
 |       LoaderCallback callback, | 
 |       FallbackCallback fallback_callback) override { | 
 |     browser_interceptor_->MaybeCreateLoader( | 
 |         tentative_resource_request, browser_context, | 
 |         base::BindOnce( | 
 |             [](LoaderCallback callback, | 
 |                URLLoaderRequestInterceptor::RequestHandler handler) { | 
 |               if (handler) { | 
 |                 std::move(callback).Run(NavigationLoaderInterceptor::Result( | 
 |                     base::MakeRefCounted< | 
 |                         network::SingleRequestURLLoaderFactory>( | 
 |                         std::move(handler)), | 
 |                     /*subresource_loader_params=*/{})); | 
 |               } else { | 
 |                 std::move(callback).Run(std::nullopt); | 
 |               } | 
 |             }, | 
 |             std::move(callback))); | 
 |   } | 
 |  | 
 |   bool MaybeCreateLoaderForResponse( | 
 |       const network::URLLoaderCompletionStatus& status, | 
 |       const network::ResourceRequest& request, | 
 |       network::mojom::URLResponseHeadPtr* response_head, | 
 |       mojo::ScopedDataPipeConsumerHandle* response_body, | 
 |       mojo::PendingRemote<network::mojom::URLLoader>* loader, | 
 |       mojo::PendingReceiver<network::mojom::URLLoaderClient>* client_receiver, | 
 |       blink::ThrottlingURLLoader* url_loader, | 
 |       bool* skip_other_interceptors) override { | 
 |     return browser_interceptor_->MaybeCreateLoaderForResponse( | 
 |         status, request, response_head, response_body, loader, client_receiver, | 
 |         url_loader); | 
 |   } | 
 |  | 
 |  private: | 
 |   std::unique_ptr<URLLoaderRequestInterceptor> browser_interceptor_; | 
 | }; | 
 |  | 
 | class NavigationTimingThrottle : public blink::URLLoaderThrottle { | 
 |  public: | 
 |   NavigationTimingThrottle(bool is_outermost_main_frame, base::TimeTicks start) | 
 |       : is_outermost_main_frame_(is_outermost_main_frame), start_(start) {} | 
 |  | 
 |   void WillStartRequest(network::ResourceRequest* request, | 
 |                         bool* defer) override { | 
 |     base::UmaHistogramTimes( | 
 |         base::StrCat({"Navigation.LoaderCreateToRequestStart.", | 
 |                       is_outermost_main_frame_ ? "MainFrame" : "Subframe"}), | 
 |         base::TimeTicks::Now() - start_); | 
 |   } | 
 |  | 
 |  private: | 
 |   bool is_outermost_main_frame_; | 
 |   base::TimeTicks start_; | 
 | }; | 
 |  | 
 | const net::NetworkTrafficAnnotationTag kNavigationUrlLoaderTrafficAnnotation = | 
 |     net::DefineNetworkTrafficAnnotation("navigation_url_loader", R"( | 
 |       semantics { | 
 |         sender: "Navigation URL Loader" | 
 |         description: | 
 |           "This request is issued by a main frame navigation to fetch the " | 
 |           "content of the page that is being navigated to." | 
 |         trigger: | 
 |           "Navigating Chrome (by clicking on a link, bookmark, history item, " | 
 |           "using session restore, etc)." | 
 |         data: | 
 |           "Arbitrary site-controlled data can be included in the URL, HTTP " | 
 |           "headers, and request body. Requests may include cookies and " | 
 |           "site-specific credentials." | 
 |         destination: WEBSITE | 
 |       } | 
 |       policy { | 
 |         cookies_allowed: YES | 
 |         cookies_store: "user" | 
 |         setting: "This feature cannot be disabled." | 
 |         chrome_policy { | 
 |           URLBlocklist { | 
 |             URLBlocklist: { entries: '*' } | 
 |           } | 
 |         } | 
 |         chrome_policy { | 
 |           URLAllowlist { | 
 |             URLAllowlist { } | 
 |           } | 
 |         } | 
 |       } | 
 |       comments: | 
 |         "Chrome would be unable to navigate to websites without this type of " | 
 |         "request. Using either URLBlocklist or URLAllowlist policies (or a " | 
 |         "combination of both) limits the scope of these requests." | 
 |       )"); | 
 |  | 
 | std::unique_ptr<network::ResourceRequest> CreateResourceRequest( | 
 |     const NavigationRequestInfo& request_info, | 
 |     FrameTreeNode* frame_tree_node, | 
 |     ClientHintsControllerDelegate* client_hints_controller_delegate, | 
 |     mojo::PendingRemote<network::mojom::CookieAccessObserver> cookie_observer, | 
 |     mojo::PendingRemote<network::mojom::TrustTokenAccessObserver> | 
 |         trust_token_observer, | 
 |     mojo::PendingRemote<network::mojom::SharedDictionaryAccessObserver> | 
 |         shared_dictionary_observer, | 
 |     mojo::PendingRemote<network::mojom::URLLoaderNetworkServiceObserver> | 
 |         url_loader_network_observer, | 
 |     mojo::PendingRemote<network::mojom::DevToolsObserver> devtools_observer, | 
 |     mojo::PendingRemote<network::mojom::DeviceBoundSessionAccessObserver> | 
 |         device_bound_session_observer, | 
 |     mojo::PendingRemote<network::mojom::AcceptCHFrameObserver> | 
 |         accept_ch_frame_observer) { | 
 |   auto new_request = CreateResourceRequestForNavigation( | 
 |       request_info.common_params->method, request_info.common_params->url, | 
 |       request_info.common_params->request_destination, | 
 |       *request_info.common_params->referrer, request_info.isolation_info, | 
 |       std::move(devtools_observer), /*priority=*/net::HIGHEST, | 
 |       request_info.is_main_frame); | 
 |  | 
 |   new_request->trusted_params->cookie_observer = std::move(cookie_observer); | 
 |   new_request->trusted_params->trust_token_observer = | 
 |       std::move(trust_token_observer); | 
 |   new_request->trusted_params->shared_dictionary_observer = | 
 |       std::move(shared_dictionary_observer); | 
 |   new_request->trusted_params->url_loader_network_observer = | 
 |       std::move(url_loader_network_observer); | 
 |   new_request->trusted_params->device_bound_session_observer = | 
 |       std::move(device_bound_session_observer); | 
 |   new_request->trusted_params->client_security_state = | 
 |       request_info.client_security_state.Clone(); | 
 |   new_request->trusted_params->accept_ch_frame_observer = | 
 |       std::move(accept_ch_frame_observer); | 
 |   new_request->trusted_params->allow_cookies_from_browser = | 
 |       request_info.allow_cookies_from_browser; | 
 |   new_request->is_outermost_main_frame = request_info.is_outermost_main_frame; | 
 |   new_request->request_initiator = request_info.common_params->initiator_origin; | 
 |   new_request->headers.AddHeadersFromString(request_info.begin_params->headers); | 
 |   new_request->cors_exempt_headers = request_info.cors_exempt_headers; | 
 |   new_request->devtools_accepted_stream_types = | 
 |       request_info.devtools_accepted_stream_types; | 
 |   // For ResourceType purposes, fenced frames are considered a kSubFrame. | 
 |   new_request->resource_type = | 
 |       static_cast<int>(request_info.is_outermost_main_frame | 
 |                            ? blink::mojom::ResourceType::kMainFrame | 
 |                            : blink::mojom::ResourceType::kSubFrame); | 
 |  | 
 |   int load_flags = request_info.begin_params->load_flags; | 
 |   if (request_info.is_outermost_main_frame) { | 
 |     load_flags |= net::LOAD_MAIN_FRAME_DEPRECATED; | 
 |     load_flags |= net::LOAD_CAN_USE_RESTRICTED_PREFETCH_FOR_MAIN_FRAME; | 
 |   } | 
 |  | 
 |   // Sync loads should have maximum priority and should be the only | 
 |   // requests that have the ignore limits flag set. | 
 |   DCHECK(!(load_flags & net::LOAD_IGNORE_LIMITS)); | 
 |  | 
 |   new_request->load_flags = load_flags; | 
 |  | 
 |   new_request->request_body = request_info.common_params->post_data.get(); | 
 |   new_request->has_user_gesture = request_info.common_params->has_user_gesture; | 
 |  | 
 |   if (ui::PageTransitionIsWebTriggerable( | 
 |           ui::PageTransitionFromInt(request_info.common_params->transition))) { | 
 |     new_request->trusted_params->has_user_activation = | 
 |         request_info.common_params->has_user_gesture; | 
 |   } else { | 
 |     new_request->trusted_params->has_user_activation = true; | 
 |   } | 
 |  | 
 |   new_request->upgrade_if_insecure = request_info.upgrade_if_insecure; | 
 |   new_request->throttling_profile_id = request_info.devtools_frame_token; | 
 |   new_request->transition_type = request_info.common_params->transition; | 
 |   devtools_instrumentation::MaybeAssignResourceRequestId( | 
 |       frame_tree_node, request_info.devtools_navigation_token.ToString(), | 
 |       *new_request); | 
 |   if (request_info.begin_params->trust_token_params) { | 
 |     new_request->trust_token_params = | 
 |         *request_info.begin_params->trust_token_params; | 
 |   } | 
 |  | 
 |   new_request->storage_access_api_status = | 
 |       request_info.begin_params->storage_access_api_status; | 
 |  | 
 |   WebContentsImpl* web_contents = static_cast<WebContentsImpl*>( | 
 |       WebContents::FromFrameTreeNodeId(frame_tree_node->frame_tree_node_id())); | 
 |   new_request->attribution_reporting_support = | 
 |       web_contents ? web_contents->GetAttributionSupport() | 
 |                    : AttributionManager::GetAttributionSupport( | 
 |                          /*client_os_disabled=*/false); | 
 |  | 
 |   new_request->attribution_reporting_eligibility = | 
 |       request_info.begin_params->impression.has_value() | 
 |           ? network::mojom::AttributionReportingEligibility::kNavigationSource | 
 |           : network::mojom::AttributionReportingEligibility::kUnset; | 
 |  | 
 |   new_request->shared_storage_writable_eligible = | 
 |       request_info.shared_storage_writable_eligible; | 
 |   new_request->is_ad_tagged = request_info.is_ad_tagged; | 
 |  | 
 |   // TODO(crbug.com/382291442): Remove feature guarding once launched. | 
 |   if (base::FeatureList::IsEnabled( | 
 |           network::features::kPopulatePermissionsPolicyOnRequest) && | 
 |       frame_tree_node && frame_tree_node->current_frame_host() && | 
 |       frame_tree_node->current_frame_host()->GetPermissionsPolicy()) { | 
 |     new_request->permissions_policy = | 
 |         *frame_tree_node->current_frame_host()->GetPermissionsPolicy(); | 
 |   } | 
 |  | 
 |   base::UmaHistogramBoolean( | 
 |       "Navigation.URLLoader.HasClientHintsControllerDelegate", | 
 |       client_hints_controller_delegate != nullptr); | 
 |   if (base::FeatureList::IsEnabled( | 
 |           network::features::kOffloadAcceptCHFrameCheck) && | 
 |       client_hints_controller_delegate) { | 
 |     new_request->trusted_params->enabled_client_hints = GetEnabledClientHints( | 
 |         url::Origin::Create(new_request->url), frame_tree_node, | 
 |         client_hints_controller_delegate); | 
 |   } | 
 |  | 
 |   return new_request; | 
 | } | 
 |  | 
 | // Called for requests that we don't have a URLLoaderFactory for. | 
 | void UnknownSchemeCallback( | 
 |     bool handled_externally, | 
 |     const network::ResourceRequest& /* resource_request */, | 
 |     mojo::PendingReceiver<network::mojom::URLLoader> receiver, | 
 |     mojo::PendingRemote<network::mojom::URLLoaderClient> client) { | 
 |   mojo::Remote<network::mojom::URLLoaderClient>(std::move(client)) | 
 |       ->OnComplete(network::URLLoaderCompletionStatus( | 
 |           handled_externally ? net::ERR_ABORTED : net::ERR_UNKNOWN_URL_SCHEME)); | 
 | } | 
 |  | 
 | void LogQueueTimeHistogram(std::string_view name, | 
 |                            bool is_outermost_main_frame) { | 
 |   auto* task = base::TaskAnnotator::CurrentTaskForThread(); | 
 |   // Only log for non-delayed tasks with a valid queue_time. | 
 |   if (!task || task->queue_time.is_null() || | 
 |       !task->delayed_run_time.is_null()) { | 
 |     return; | 
 |   } | 
 |  | 
 |   base::UmaHistogramTimes( | 
 |       base::StrCat( | 
 |           {name, is_outermost_main_frame ? ".MainFrame" : ".Subframe"}), | 
 |       base::TimeTicks::Now() - task->queue_time); | 
 | } | 
 |  | 
 | void LogAcceptCHFrameStatus(AcceptCHFrameRestart status) { | 
 |   base::UmaHistogramEnumeration("ClientHints.AcceptCHFrame", status); | 
 | } | 
 |  | 
 | bool IsSameOriginRedirect(const std::vector<GURL>& url_chain) { | 
 |   if (url_chain.size() < 2) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   auto previous_origin = url::Origin::Create(url_chain[url_chain.size() - 2]); | 
 |   return previous_origin.IsSameOriginWith(url_chain[url_chain.size() - 1]); | 
 | } | 
 |  | 
 | // Return whether the inherited frame policy or iframe sandbox attribute | 
 | // contains the 'allow-same-site-none-cookies' value and the override should be | 
 | // applied as the frame's ancestors are all same-site. | 
 | bool ShouldAllowSameSiteNoneCookiesInSandbox(FrameTreeNode& frame_tree_node) { | 
 |   if (frame_tree_node.IsInFencedFrameTree()) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   RenderFrameHostImpl* frame = frame_tree_node.current_frame_host(); | 
 |   return frame && | 
 |          frame->active_sandbox_flags() != | 
 |              network::mojom::WebSandboxFlags::kNone && | 
 |          !frame->IsSandboxed( | 
 |              network::mojom::WebSandboxFlags::kAllowSameSiteNoneCookies) && | 
 |          frame->IsSandboxed(network::mojom::WebSandboxFlags::kOrigin) && | 
 |          frame->AncestorsAllowSameSiteNoneCookiesOverride( | 
 |              frame_tree_node.navigation_request() | 
 |                  ->GetTentativeOriginAtRequestTime()); | 
 | } | 
 |  | 
 | // TODO(https://crbug.com/346000235) there is a known failure with extensions | 
 | // See test: ExtensionWebRequestApiTestWithContextType.HSTSUpgradeAfterRedirect | 
 | // We still want to check this in debug mode to avoid further regressions, but | 
 | // because it was affecting to many developers with DCHECK_IS_ON() this was | 
 | // downgraded as a DEBUG only check. | 
 | // After getting the approval to rewrite the CSP parser in Rust, we should be | 
 | // able to remove "pre-parsing" of CSP in the network process and directly | 
 | // parse them later in the browser process. This will make this check to become | 
 | // unnecessary. | 
 | #ifndef NDEBUG | 
 | void CheckParsedHeadersEquals(const network::mojom::ParsedHeadersPtr& lhs, | 
 |                               const network::mojom::ParsedHeadersPtr& rhs, | 
 |                               const GURL& url) { | 
 |   // If we're running this function it means we're re-parsing the headers from | 
 |   // cache and checking if they equal the prior parsing results. As the | 
 |   // Clear-Site-Data header isn't cached we want to be sure not to fail just | 
 |   // because the two parsing results mismatch in this expected way. | 
 |   network::mojom::ParsedHeadersPtr adjusted_lhs = lhs->Clone(); | 
 |   if (rhs->client_hints_ignored_due_to_clear_site_data_header) { | 
 |     CHECK(!rhs->accept_ch); | 
 |     CHECK(!rhs->critical_ch); | 
 |     adjusted_lhs->accept_ch = std::nullopt; | 
 |     adjusted_lhs->critical_ch = std::nullopt; | 
 |     adjusted_lhs->client_hints_ignored_due_to_clear_site_data_header = true; | 
 |   } | 
 |   if (mojo::Equals(adjusted_lhs, rhs)) { | 
 |     return; | 
 |   } | 
 |  | 
 |   // TODO(crbug.com/40864513) Remove this instrumentation once fixed. | 
 |   auto to_string = [](const auto& policies) { | 
 |     std::string out; | 
 |     for (const auto& csp : policies) { | 
 |       out += csp->header->header_value + " | "; | 
 |     } | 
 |     return out; | 
 |   }; | 
 |   SCOPED_CRASH_KEY_STRING256("bug1362779", "csp_adjusted_lhs", | 
 |                              to_string(adjusted_lhs->content_security_policy)); | 
 |   SCOPED_CRASH_KEY_STRING256("bug1362779", "csp_rhs", | 
 |                              to_string(rhs->content_security_policy)); | 
 |   SCOPED_CRASH_KEY_STRING32("bug1362779", "url", url.possibly_invalid_spec()); | 
 |  | 
 |   CHECK(mojo::Equals(adjusted_lhs->content_security_policy, | 
 |                      rhs->content_security_policy)); | 
 |   CHECK(mojo::Equals(adjusted_lhs->allow_csp_from, rhs->allow_csp_from)); | 
 |   CHECK(mojo::Equals(adjusted_lhs->cross_origin_embedder_policy, | 
 |                      rhs->cross_origin_embedder_policy)); | 
 |   CHECK(mojo::Equals(adjusted_lhs->cross_origin_opener_policy, | 
 |                      rhs->cross_origin_opener_policy)); | 
 |   CHECK(mojo::Equals(adjusted_lhs->document_isolation_policy, | 
 |                      rhs->document_isolation_policy)); | 
 |   CHECK(mojo::Equals(adjusted_lhs->origin_agent_cluster, | 
 |                      rhs->origin_agent_cluster)); | 
 |   CHECK(mojo::Equals(adjusted_lhs->accept_ch, rhs->accept_ch)); | 
 |   CHECK(mojo::Equals(adjusted_lhs->critical_ch, rhs->critical_ch)); | 
 |   CHECK_EQ(adjusted_lhs->xfo, rhs->xfo); | 
 |   CHECK(mojo::Equals(adjusted_lhs->link_headers, rhs->link_headers)); | 
 |   CHECK(mojo::Equals(adjusted_lhs->timing_allow_origin, | 
 |                      rhs->timing_allow_origin)); | 
 |   CHECK(mojo::Equals(adjusted_lhs->supports_loading_mode, | 
 |                      rhs->supports_loading_mode)); | 
 |   CHECK(mojo::Equals(adjusted_lhs->reporting_endpoints, | 
 |                      rhs->reporting_endpoints)); | 
 |   CHECK(mojo::Equals(adjusted_lhs->cookie_indices, rhs->cookie_indices)); | 
 |   CHECK(mojo::Equals(adjusted_lhs->avail_language, rhs->avail_language)); | 
 |   CHECK(mojo::Equals(adjusted_lhs->content_language, rhs->content_language)); | 
 |   CHECK(mojo::Equals(adjusted_lhs->no_vary_search_with_parse_error, | 
 |                      rhs->no_vary_search_with_parse_error)); | 
 |   CHECK(mojo::Equals(adjusted_lhs->observe_browsing_topics, | 
 |                      rhs->observe_browsing_topics)); | 
 |   CHECK(mojo::Equals(adjusted_lhs->allow_cross_origin_event_reporting, | 
 |                      rhs->allow_cross_origin_event_reporting)); | 
 |   NOTREACHED() << "The parsed headers don't match, but we don't know which " | 
 |                   "field does not match. Please add a DCHECK before this one " | 
 |                   "checking for the missing field."; | 
 | } | 
 | #endif  // NDEBUG | 
 |  | 
 | }  // namespace | 
 |  | 
 | std::unique_ptr<network::ResourceRequest> CreateResourceRequestForNavigation( | 
 |     const std::string& method, | 
 |     const GURL& url, | 
 |     network::mojom::RequestDestination destination, | 
 |     const blink::mojom::Referrer& referrer, | 
 |     const net::IsolationInfo& isolation_info, | 
 |     mojo::PendingRemote<network::mojom::DevToolsObserver> devtools_observer, | 
 |     net::RequestPriority priority, | 
 |     bool is_main_frame) { | 
 |   auto new_request = std::make_unique<network::ResourceRequest>(); | 
 |  | 
 |   // - Step 3 of | 
 |   // https://html.spec.whatwg.org/multipage/browsing-the-web.html#create-navigation-params-by-fetching | 
 |   // - Step 2 of | 
 |   // https://wicg.github.io/nav-speculation/prefetch.html#create-a-navigation-request | 
 |  | 
 |   // url: entry's URL [spec text] | 
 |   new_request->url = url; | 
 |   new_request->navigation_redirect_chain.push_back(new_request->url); | 
 |  | 
 |   // client: sourceSnapshotParams's fetch client [spec text] | 
 |  | 
 |   // destination: "document" [spec text] | 
 |   new_request->destination = destination; | 
 |  | 
 |   // credentials mode: "include" [spec text] | 
 |   new_request->credentials_mode = network::mojom::CredentialsMode::kInclude; | 
 |  | 
 |   // use-URL-credentials flag: set [spec text] | 
 |  | 
 |   // redirect mode: "manual" [spec text] | 
 |   new_request->redirect_mode = network::mojom::RedirectMode::kManual; | 
 |  | 
 |   // replaces client id: navigable's active document's relevant settings | 
 |   // object's id [spec text] | 
 |   // Not implemented (https://crbug.com/40287592). | 
 |  | 
 |   // mode: "navigate"  [spec text] | 
 |   new_request->mode = network::mojom::RequestMode::kNavigate; | 
 |  | 
 |   // referrer: entry's document state's request referrer [spec text] | 
 |   new_request->referrer = referrer.url; | 
 |  | 
 |   // referrer policy: entry's document state's request referrer policy [spec | 
 |   // text] | 
 |   new_request->referrer_policy = | 
 |       Referrer::ReferrerPolicyForUrlRequest(referrer.policy); | 
 |  | 
 |   new_request->method = method; | 
 |  | 
 |   new_request->site_for_cookies = isolation_info.site_for_cookies(); | 
 |  | 
 |   new_request->trusted_params = network::ResourceRequest::TrustedParams(); | 
 |   new_request->trusted_params->isolation_info = isolation_info; | 
 |   new_request->trusted_params->devtools_observer = std::move(devtools_observer); | 
 |  | 
 |   new_request->priority = priority; | 
 |  | 
 |   if (is_main_frame) { | 
 |     // When set, `update_first_party_url_on_redirect` will cause a | 
 |     // server-redirect to update the URL used to determine if cookies are | 
 |     // first-party. Since fenced frames are main frames in terms of cookie | 
 |     // partitioning, this needs to be `is_main_frame` rather than | 
 |     // `is_outermost_main_frame`. | 
 |     new_request->update_first_party_url_on_redirect = true; | 
 |  | 
 |     // Navigation responses for the top-level document are able to be used as | 
 |     // compression dictionaries. | 
 |     new_request->shared_dictionary_writer_enabled = true; | 
 |   } | 
 |  | 
 |   new_request->enable_load_timing = true; | 
 |  | 
 |   if (base::FeatureList::IsEnabled( | 
 |           network::features::kRendererSideContentDecoding)) { | 
 |     new_request->client_side_content_decoding_enabled = true; | 
 |   } | 
 |  | 
 |   return new_request; | 
 | } | 
 |  | 
 | // TODO(kinuko): Fix the method ordering and move these methods after the ctor. | 
 | NavigationURLLoaderImpl::~NavigationURLLoaderImpl() { | 
 |   TRACE_EVENT_WITH_FLOW0("navigation", | 
 |                          "NavigationURLLoaderImpl::~NavigationURLLoaderImpl", | 
 |                          TRACE_ID_LOCAL(this), TRACE_EVENT_FLAG_FLOW_IN); | 
 |   // If neither OnCompleted nor OnReceivedResponse has been invoked, the | 
 |   // request was canceled before receiving a response, so log a cancellation. | 
 |   // Results after receiving a non-error response are logged in the renderer, | 
 |   // if the request is passed to one. If it's a download, or not passed to a | 
 |   // renderer for some other reason, results will not be logged for the | 
 |   // request. The net::OK check may not be necessary - the case where OK is | 
 |   // received without receiving any headers looks broken, anyways. | 
 |   if (!received_response_ && (!status_ || status_->error_code != net::OK)) { | 
 |     blink::RecordLoadHistograms( | 
 |         url::Origin::Create(url_), resource_request_->destination, | 
 |         status_ ? status_->error_code : net::ERR_ABORTED); | 
 |   } | 
 | } | 
 |  | 
 | void NavigationURLLoaderImpl::Start() { | 
 |   TRACE_EVENT_WITH_FLOW0("navigation", "NavigationURLLoaderImpl::Start", | 
 |                          TRACE_ID_LOCAL(this), | 
 |                          TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   DCHECK(!started_); | 
 |   started_ = true; | 
 |  | 
 |   resource_request_->headers.SetHeader( | 
 |       net::HttpRequestHeaders::kAccept, | 
 |       FrameAcceptHeaderValue(/*allow_sxg_responses=*/true, browser_context_)); | 
 |  | 
 |   // If not performing a PDF navigation, allow certain schemes to create loaders | 
 |   // directly, bypassing interceptors. (In the case of PDF navigation, | 
 |   // interception is required, but these loaders are not; see crbug.com/1253314 | 
 |   // and crbug.com/1253984.) | 
 |   // | 
 |   // TODO(crbug.com/40794764): Consider getting rid of these exceptions. | 
 |   if (!request_info_->is_pdf) { | 
 |     // Requests to WebUI scheme won't get redirected to/from other schemes | 
 |     // or be intercepted, so we just let it go here. | 
 |     std::string scheme = request_info_->common_params->url.scheme(); | 
 |     if (base::Contains(URLDataManagerBackend::GetWebUISchemes(), scheme)) { | 
 |       FrameTreeNode* frame_tree_node = | 
 |           FrameTreeNode::GloballyFindByID(frame_tree_node_id_); | 
 |       CHECK(frame_tree_node); | 
 |       CHECK(frame_tree_node->navigation_request()); | 
 |  | 
 |       CreateThrottlingLoaderAndStart( | 
 |           url_loader_factory::Create( | 
 |               ContentBrowserClient::URLLoaderFactoryType::kNavigation, | 
 |               url_loader_factory::TerminalParams::ForNonNetwork( | 
 |                   CreateWebUIURLLoaderFactory( | 
 |                       frame_tree_node->current_frame_host(), scheme, {}), | 
 |                   network::mojom::kBrowserProcessId), | 
 |               url_loader_factory::ContentClientParams( | 
 |                   browser_context_, frame_tree_node->current_frame_host(), | 
 |                   frame_tree_node->current_frame_host() | 
 |                       ->GetProcess() | 
 |                       ->GetDeprecatedID(), | 
 |                   resource_request_->request_initiator.value_or(url::Origin()), | 
 |                   net::IsolationInfo(), | 
 |                   ukm::SourceIdObj::FromInt64(ukm_source_id_), | 
 |                   /*bypass_redirect_checks=*/nullptr, | 
 |                   frame_tree_node->navigation_request()->GetNavigationId(), | 
 |                   GetUIThreadTaskRunner( | 
 |                       {BrowserTaskType::kNavigationNetworkResponse}))), | 
 |           /*additional_throttles=*/{}); | 
 |       return; | 
 |     } | 
 |  | 
 |     // Requests to Blob scheme won't get redirected to/from other schemes or be | 
 |     // intercepted, so we just let it go here. | 
 |     if (request_info_->common_params->url.SchemeIsBlob() && | 
 |         request_info_->blob_url_loader_factory) { | 
 |       CreateThrottlingLoaderAndStart( | 
 |           network::SharedURLLoaderFactory::Create( | 
 |               std::move(request_info_->blob_url_loader_factory)), | 
 |           /*additional_throttles=*/{}); | 
 |       return; | 
 |     } | 
 |   } | 
 |  | 
 |   CreateInterceptors(); | 
 |   Restart(); | 
 | } | 
 |  | 
 | void NavigationURLLoaderImpl::CreateInterceptors() { | 
 |   TRACE_EVENT_WITH_FLOW0("navigation", | 
 |                          "NavigationURLLoaderImpl::CreateInterceptors", | 
 |                          TRACE_ID_LOCAL(this), | 
 |                          TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); | 
 |   if (prefetched_signed_exchange_cache_) { | 
 |     std::unique_ptr<NavigationLoaderInterceptor> | 
 |         prefetched_signed_exchange_interceptor = | 
 |             prefetched_signed_exchange_cache_->MaybeCreateInterceptor( | 
 |                 url_, frame_tree_node_id_, | 
 |                 resource_request_->trusted_params->isolation_info); | 
 |     prefetched_signed_exchange_cache_.reset(); | 
 |     if (prefetched_signed_exchange_interceptor) { | 
 |       interceptors_.push_back( | 
 |           std::move(prefetched_signed_exchange_interceptor)); | 
 |     } | 
 |   } | 
 |  | 
 |   // Set up an interceptor for service workers. | 
 |   if (service_worker_handle_) { | 
 |     auto service_worker_interceptor = | 
 |         ServiceWorkerMainResourceLoaderInterceptor::CreateForNavigation( | 
 |             resource_request_->url, service_worker_handle_->AsWeakPtr(), | 
 |             *request_info_); | 
 |     // The interceptor may not be created in certain cases (e.g., the origin | 
 |     // is not secure). | 
 |     if (service_worker_interceptor) { | 
 |       if (features::IsPrefetchServiceWorkerEnabled(browser_context_)) { | 
 |         // Set up an interceptor for ServiceWorker-controlled prefetches. This | 
 |         // is needed before the ServiceWorkerMainResourceLoaderInterceptor which | 
 |         // would also intercept the request for ServiceWorker-controlled URLs. | 
 |         // See the design docs at https://crbug.com/40947546. | 
 |         interceptors_.push_back(std::make_unique<PrefetchURLLoaderInterceptor>( | 
 |             PrefetchServiceWorkerState::kControlled, | 
 |             service_worker_handle_->AsWeakPtr(), frame_tree_node_id_, | 
 |             request_info_->initiator_document_token, | 
 |             request_info_->prefetch_serving_page_metrics_container)); | 
 |       } | 
 |  | 
 |       interceptors_.push_back(std::move(service_worker_interceptor)); | 
 |     } | 
 |   } | 
 |  | 
 |   // Set-up an interceptor for SignedExchange handling if it is enabled. | 
 |   if (signed_exchange_utils::IsSignedExchangeHandlingEnabled( | 
 |           browser_context_)) { | 
 |     interceptors_.push_back(CreateSignedExchangeRequestHandler( | 
 |         *request_info_, network_loader_factory_)); | 
 |   } | 
 |  | 
 |   // Set up an interceptor for prefetch. | 
 |   // When `features::kPrefetchServiceWorker` is enabled, we intentionally add | 
 |   // two `PrefetchURLLoaderInterceptor`s, one for ServiceWorker-controlled | 
 |   // prefetches above, and one for non-ServiceWorker-controlled prefetches here. | 
 |   // See the design docs at https://crbug.com/40947546. | 
 |   interceptors_.push_back(std::make_unique<PrefetchURLLoaderInterceptor>( | 
 |       PrefetchServiceWorkerState::kDisallowed, | 
 |       /*service_worker_handle=*/nullptr, frame_tree_node_id_, | 
 |       request_info_->initiator_document_token, | 
 |       request_info_->prefetch_serving_page_metrics_container)); | 
 |  | 
 |   // See if embedders want to add interceptors. | 
 |   std::vector<std::unique_ptr<URLLoaderRequestInterceptor>> | 
 |       browser_interceptors = | 
 |           GetContentClient()->browser()->WillCreateURLLoaderRequestInterceptors( | 
 |               navigation_ui_data_.get(), frame_tree_node_id_, | 
 |               request_info_->navigation_id, | 
 |               request_info_->force_no_https_upgrade, | 
 |               GetUIThreadTaskRunner( | 
 |                   {BrowserTaskType::kNavigationNetworkResponse})); | 
 |   if (!browser_interceptors.empty()) { | 
 |     for (auto& browser_interceptor : browser_interceptors) { | 
 |       interceptors_.push_back( | 
 |           std::make_unique<NavigationLoaderInterceptorBrowserContainer>( | 
 |               std::move(browser_interceptor))); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void NavigationURLLoaderImpl::Restart() { | 
 |   TRACE_EVENT_WITH_FLOW0("navigation", "NavigationURLLoaderImpl::Restart", | 
 |                          TRACE_ID_LOCAL(this), | 
 |                          TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); | 
 |   // Cancel all inflight early hints preloads except for same origin redirects. | 
 |   if (!IsSameOriginRedirect(resource_request_->navigation_redirect_chain)) { | 
 |     early_hints_manager_.reset(); | 
 |   } | 
 |  | 
 |   // Clear `url_loader_` if it's not the default one (network). This allows | 
 |   // the restarted request to use a new loader, instead of, e.g., reusing the | 
 |   // service worker loader. For an optimization, we keep and reuse | 
 |   // the default url loader if the all `interceptors_` doesn't handle the | 
 |   // redirected request. If the network service is enabled, reset the loader | 
 |   // if the redirected URL's scheme and the previous URL scheme don't match in | 
 |   // their use or disuse of the network service loader. | 
 |   if (!default_loader_used_ || | 
 |       (resource_request_->navigation_redirect_chain.size() > 1 && | 
 |        network::IsURLHandledByNetworkService( | 
 |            resource_request_->navigation_redirect_chain | 
 |                [resource_request_->navigation_redirect_chain.size() - 1]) != | 
 |            network::IsURLHandledByNetworkService( | 
 |                resource_request_->navigation_redirect_chain | 
 |                    [resource_request_->navigation_redirect_chain.size() - | 
 |                     2]))) { | 
 |     loader_holder_.ResetForFollowRedirect(*resource_request_.get()); | 
 |   } | 
 |   received_response_ = false; | 
 |   head_update_params_ = ResponseHeadUpdateParams(); | 
 |   MaybeStartLoader(/*next_interceptor_index=*/0, | 
 |                    /*interceptor_result=*/std::nullopt); | 
 | } | 
 |  | 
 | void NavigationURLLoaderImpl::MaybeStartLoader( | 
 |     size_t next_interceptor_index, | 
 |     std::optional<NavigationLoaderInterceptor::Result> interceptor_result) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   DCHECK(started_); | 
 |  | 
 |   if (interceptor_result) { | 
 |     subresource_loader_params_ = | 
 |         std::move(interceptor_result->subresource_loader_params); | 
 |     if (!interceptor_result->single_request_factory) { | 
 |       // Skip the subsequent interceptors and start with the default behavior. | 
 |       // | 
 |       // Here `subresource_loader_params_` can still have non-default values | 
 |       // e.g. when there's a controlling service worker that doesn't have a | 
 |       // fetch event handler so it doesn't intercept requests. | 
 |       StartNonInterceptedRequest( | 
 |           std::move(interceptor_result->response_head_update_params)); | 
 |       return; | 
 |     } | 
 |  | 
 |     // Intercept the request with `interceptor_result->single_request_factory`. | 
 |     StartInterceptedRequest( | 
 |         std::move(interceptor_result->single_request_factory)); | 
 |     return; | 
 |   } | 
 |  | 
 |   subresource_loader_params_ = {}; | 
 |  | 
 |   if (next_interceptor_index >= interceptors_.size()) { | 
 |     // All interceptors have been checked and none has elected to handle the | 
 |     // request. Start with the default behavior. | 
 |     StartNonInterceptedRequest(ResponseHeadUpdateParams()); | 
 |     return; | 
 |   } | 
 |  | 
 |   // Fallback to the next interceptor. | 
 |   auto* next_interceptor = interceptors_[next_interceptor_index].get(); | 
 |   next_interceptor->MaybeCreateLoader( | 
 |       *resource_request_, browser_context_, | 
 |       base::BindOnce(&NavigationURLLoaderImpl::MaybeStartLoader, | 
 |                      weak_factory_.GetWeakPtr(), next_interceptor_index + 1), | 
 |       base::BindOnce(&NavigationURLLoaderImpl::FallbackToNonInterceptedRequest, | 
 |                      weak_factory_.GetWeakPtr())); | 
 | } | 
 |  | 
 | void NavigationURLLoaderImpl::StartInterceptedRequest( | 
 |     scoped_refptr<network::SharedURLLoaderFactory> single_request_factory) { | 
 |   std::vector<std::unique_ptr<blink::URLLoaderThrottle>> additional_throttles; | 
 |   // Intercepted requests need MimeSniffingThrottle to do mime sniffing. | 
 |   // Non-intercepted requests usually go through the regular network | 
 |   // URLLoader, which does mime sniffing. | 
 |   additional_throttles.push_back(std::make_unique<blink::MimeSniffingThrottle>( | 
 |       GetUIThreadTaskRunner({BrowserTaskType::kNavigationNetworkResponse}))); | 
 |  | 
 |   default_loader_used_ = false; | 
 |  | 
 |   // The receiver should be already reset at `Restart()`. | 
 |   // TODO(https://crbug.com/434182226): Remove DUMP_WILL_BE_. | 
 |   DUMP_WILL_BE_CHECK(!loader_holder_.receiver_is_bound_for_check()); | 
 |  | 
 |   // If `url_loader_` already exists, this means we are following a redirect | 
 |   // using an interceptor. In this case we should make sure to reset the | 
 |   // loader, similar to what is done in Restart(). | 
 |   loader_holder_.ResetForFollowRedirect(*resource_request_.get()); | 
 |  | 
 |   CreateThrottlingLoaderAndStart(std::move(single_request_factory), | 
 |                                  std::move(additional_throttles)); | 
 | } | 
 |  | 
 | NavigationURLLoaderImpl::LoaderHolder::LoaderHolder( | 
 |     network::mojom::URLLoaderClient* receiver) | 
 |     : response_loader_receiver_(receiver) {} | 
 |  | 
 | NavigationURLLoaderImpl::LoaderHolder::~LoaderHolder() = default; | 
 |  | 
 | void NavigationURLLoaderImpl::LoaderHolder::Reset() { | 
 |   response_loader_receiver_.reset(); | 
 |   url_loader_.reset(); | 
 | } | 
 |  | 
 | void NavigationURLLoaderImpl::LoaderHolder::BindReceiver( | 
 |     mojo::PendingReceiver<network::mojom::URLLoaderClient> pending_receiver, | 
 |     scoped_refptr<base::SequencedTaskRunner> task_runner) { | 
 |   response_loader_receiver_.reset(); | 
 |   response_loader_receiver_.Bind(std::move(pending_receiver), | 
 |                                  std::move(task_runner)); | 
 |   url_loader_.reset(); | 
 | } | 
 |  | 
 | void NavigationURLLoaderImpl::LoaderHolder::SetLoader( | 
 |     std::unique_ptr<blink::ThrottlingURLLoader> url_loader) { | 
 |   url_loader_ = std::move(url_loader); | 
 | } | 
 |  | 
 | network::mojom::URLLoaderClientEndpointsPtr | 
 | NavigationURLLoaderImpl::LoaderHolder::Unbind() { | 
 |   if (url_loader_) { | 
 |     // Even after this point `url_loader_` should be alive and accessed via | 
 |     // `url_loader()`. | 
 |     // TODO(https://crbug.com/40251638): Clean up this behavior if needed. | 
 |     return url_loader_->Unbind(); | 
 |   } else { | 
 |     return network::mojom::URLLoaderClientEndpoints::New( | 
 |         std::move(response_url_loader_), response_loader_receiver_.Unbind()); | 
 |   } | 
 | } | 
 |  | 
 | NavigationURLLoaderImpl::LoaderHolder::ModifiedHeadersOnRedirect:: | 
 |     ModifiedHeadersOnRedirect( | 
 |         std::vector<std::string> removed_headers, | 
 |         net::HttpRequestHeaders modified_headers, | 
 |         net::HttpRequestHeaders modified_cors_exempt_headers) | 
 |     : removed_headers_(std::move(removed_headers)), | 
 |       modified_headers_(std::move(modified_headers)), | 
 |       modified_cors_exempt_headers_(std::move(modified_cors_exempt_headers)) {} | 
 |  | 
 | NavigationURLLoaderImpl::LoaderHolder::ModifiedHeadersOnRedirect:: | 
 |     ~ModifiedHeadersOnRedirect() = default; | 
 |  | 
 | void NavigationURLLoaderImpl::LoaderHolder::SetModifiedHeadersOnRedirect( | 
 |     std::vector<std::string> removed_headers, | 
 |     net::HttpRequestHeaders modified_headers, | 
 |     net::HttpRequestHeaders modified_cors_exempt_headers) { | 
 |   modified_headers_on_redirect_.emplace( | 
 |       std::move(removed_headers), std::move(modified_headers), | 
 |       std::move(modified_cors_exempt_headers)); | 
 | } | 
 |  | 
 | void NavigationURLLoaderImpl::LoaderHolder::ResetForFollowRedirect( | 
 |     network::ResourceRequest& resource_request) { | 
 |   if (url_loader_) { | 
 |     CHECK(modified_headers_on_redirect_); | 
 |     url_loader_->ResetForFollowRedirect( | 
 |         resource_request, modified_headers_on_redirect_->removed_headers_, | 
 |         modified_headers_on_redirect_->modified_headers_, | 
 |         modified_headers_on_redirect_->modified_cors_exempt_headers_); | 
 |     modified_headers_on_redirect_.reset(); | 
 |   } | 
 |   Reset(); | 
 | } | 
 |  | 
 | void NavigationURLLoaderImpl::LoaderHolder::FollowRedirect() { | 
 |   CHECK(url_loader_); | 
 |   CHECK(modified_headers_on_redirect_); | 
 |   url_loader_->FollowRedirect( | 
 |       std::move(modified_headers_on_redirect_->removed_headers_), | 
 |       std::move(modified_headers_on_redirect_->modified_headers_), | 
 |       std::move(modified_headers_on_redirect_->modified_cors_exempt_headers_)); | 
 | } | 
 |  | 
 | bool NavigationURLLoaderImpl::LoaderHolder::receiver_is_bound_for_check() | 
 |     const { | 
 |   return response_loader_receiver_.is_bound(); | 
 | } | 
 |  | 
 | void NavigationURLLoaderImpl::StartNonInterceptedRequest( | 
 |     ResponseHeadUpdateParams head_update_params) { | 
 |   // If we already have the default `url_loader_` we must come here after a | 
 |   // redirect. No interceptors wanted to intercept the redirected request, | 
 |   // so let the loader just follow the redirect. | 
 |   if (loader_holder_.url_loader()) { | 
 |     DCHECK(!redirect_info_.new_url.is_empty()); | 
 |     loader_holder_.FollowRedirect(); | 
 |     return; | 
 |   } | 
 |  | 
 |   head_update_params_ = std::move(head_update_params); | 
 |   scoped_refptr<network::SharedURLLoaderFactory> factory; | 
 |   if (network::IsURLHandledByNetworkService(resource_request_->url)) { | 
 |     factory = network_loader_factory_; | 
 |     default_loader_used_ = true; | 
 |   } else { | 
 |     factory = GetOrCreateNonNetworkLoaderFactory(); | 
 |   } | 
 |  | 
 |   loader_holder_.Reset(); | 
 |   CreateThrottlingLoaderAndStart(std::move(factory), | 
 |                                  /*additional_throttles=*/{}); | 
 | } | 
 |  | 
 | network::mojom::URLLoaderFactory* | 
 | NavigationURLLoaderImpl::FallbackToNonInterceptedRequest( | 
 |     base::WeakPtr<NavigationURLLoaderImpl> self, | 
 |     ResponseHeadUpdateParams head_update_params) { | 
 |   if (!self) { | 
 |     return nullptr; | 
 |   } | 
 |  | 
 |   self->head_update_params_ = std::move(head_update_params); | 
 |   if (network::IsURLHandledByNetworkService(self->resource_request_->url)) { | 
 |     // `NavigationURLLoaderImpl::default_loader_used_` is NOT set to true here, | 
 |     // because the underlying URLLoaderFactory of | 
 |     // `NavigationURLLoaderImpl::url_loader_` is still ServiceWorker-provided | 
 |     // one (that finally delegates to `network_loader_factory_` though) and thus | 
 |     // isn't e.g. unsafe to reuse after redirects. | 
 |     return self->network_loader_factory_.get(); | 
 |   } else { | 
 |     return self->GetOrCreateNonNetworkLoaderFactory().get(); | 
 |   } | 
 | } | 
 |  | 
 | scoped_refptr<network::SharedURLLoaderFactory> | 
 | NavigationURLLoaderImpl::GetOrCreateNonNetworkLoaderFactory() { | 
 |   scoped_refptr<network::SharedURLLoaderFactory>& cached_factory = | 
 |       non_network_url_loader_factories_[resource_request_->url.scheme()]; | 
 |  | 
 |   if (cached_factory) { | 
 |     return cached_factory; | 
 |   } | 
 |  | 
 |   auto [is_cacheable, factory] = CreateNonNetworkLoaderFactory( | 
 |       browser_context_, storage_partition_, | 
 |       FrameTreeNode::GloballyFindByID(frame_tree_node_id_), | 
 |       ukm::SourceIdObj::FromInt64(ukm_source_id_), navigation_ui_data_.get(), | 
 |       *request_info_, web_contents_getter_, *resource_request_); | 
 |  | 
 |   if (is_cacheable) { | 
 |     cached_factory = factory; | 
 |   } | 
 |  | 
 |   return factory; | 
 | } | 
 |  | 
 | // static | 
 | std::pair</*is_cacheable=*/bool, scoped_refptr<network::SharedURLLoaderFactory>> | 
 | NavigationURLLoaderImpl::CreateNonNetworkLoaderFactory( | 
 |     BrowserContext* browser_context, | 
 |     StoragePartitionImpl* storage_partition, | 
 |     FrameTreeNode* frame_tree_node, | 
 |     const ukm::SourceIdObj& ukm_id, | 
 |     NavigationUIData* navigation_ui_data, | 
 |     const NavigationRequestInfo& request_info, | 
 |     base::RepeatingCallback<WebContents*()> web_contents_getter, | 
 |     const network::ResourceRequest& resource_request) { | 
 |   CHECK(frame_tree_node); | 
 |   CHECK(frame_tree_node->navigation_request()); | 
 |  | 
 |   // First, check known schemes. | 
 |   if (mojo::PendingRemote<network::mojom::URLLoaderFactory> terminal = | 
 |           CreateTerminalNonNetworkLoaderFactory( | 
 |               browser_context, storage_partition, frame_tree_node, | 
 |               resource_request.url)) { | 
 |     auto* frame = frame_tree_node->current_frame_host(); | 
 |  | 
 |     // TODO(lukasza, jam): It is unclear why FileURLLoaderFactory is the only | 
 |     // non-http factory that allows DevTools interception. For comparison all | 
 |     // non-WebUI cases in RFHI::CommitNavigation allow DevTools interception. | 
 |     // Let's try to be more consistent / less ad-hoc. | 
 |     std::optional<devtools_instrumentation::WillCreateURLLoaderFactoryParams> | 
 |         devtools_params = | 
 |             resource_request.url.SchemeIs(url::kFileScheme) | 
 |                 ? std::make_optional( | 
 |                       devtools_instrumentation:: | 
 |                           WillCreateURLLoaderFactoryParams::ForFrame(frame)) | 
 |                 : std::nullopt; | 
 |     return std::make_pair( | 
 |         /*is_cacheable=*/true, | 
 |         url_loader_factory::Create( | 
 |             ContentBrowserClient::URLLoaderFactoryType::kNavigation, | 
 |             url_loader_factory::TerminalParams::ForNonNetwork( | 
 |                 std::move(terminal), network::mojom::kBrowserProcessId), | 
 |             url_loader_factory::ContentClientParams( | 
 |                 frame->GetSiteInstance()->GetBrowserContext(), frame, | 
 |                 frame->GetProcess()->GetDeprecatedID(), url::Origin(), | 
 |                 net::IsolationInfo(), ukm_id, | 
 |                 /*bypass_redirect_checks=*/nullptr, | 
 |                 frame_tree_node->navigation_request()->GetNavigationId(), | 
 |                 GetUIThreadTaskRunner( | 
 |                     {BrowserTaskType::kNavigationNetworkResponse})), | 
 |             devtools_params)); | 
 |   } | 
 |  | 
 |   // Second, check external protocols. | 
 |   std::optional<url::Origin> initiating_origin; | 
 |   if (resource_request.navigation_redirect_chain.size() > 1) { | 
 |     // The last URL in `navigation_redirect_chain` is an external-protocol URL | 
 |     // (if handled by `HandleExternalProtocol`), and the second-to-last URL is | 
 |     // the URL that initiated the redirect to the external-protocol URL. | 
 |     initiating_origin = url::Origin::Create( | 
 |         resource_request.navigation_redirect_chain | 
 |             [resource_request.navigation_redirect_chain.size() - 2]); | 
 |   } else { | 
 |     initiating_origin = resource_request.request_initiator; | 
 |   } | 
 |   mojo::PendingRemote<network::mojom::URLLoaderFactory> | 
 |       terminal_external_protocol; | 
 |   bool handled = GetContentClient()->browser()->HandleExternalProtocol( | 
 |       resource_request.url, std::move(web_contents_getter), | 
 |       frame_tree_node->frame_tree_node_id(), navigation_ui_data, | 
 |       request_info.is_primary_main_frame, | 
 |       frame_tree_node->IsInFencedFrameTree(), request_info.sandbox_flags, | 
 |       static_cast<ui::PageTransition>(resource_request.transition_type), | 
 |       resource_request.has_user_gesture, initiating_origin, | 
 |       request_info.initiator_document_token | 
 |           ? RenderFrameHostImpl::FromDocumentToken( | 
 |                 request_info.initiator_process_id, | 
 |                 *request_info.initiator_document_token) | 
 |           : nullptr, | 
 |       request_info.isolation_info, &terminal_external_protocol); | 
 |   if (terminal_external_protocol) { | 
 |     return std::make_pair( | 
 |         /*is_cacheable=*/false, | 
 |         url_loader_factory::Create( | 
 |             ContentBrowserClient::URLLoaderFactoryType::kNavigation, | 
 |             url_loader_factory::TerminalParams::ForNonNetwork( | 
 |                 std::move(terminal_external_protocol), | 
 |                 network::mojom::kBrowserProcessId))); | 
 |   } | 
 |  | 
 |   // Finally handle as an unknown scheme. | 
 |   return std::make_pair( | 
 |       /*is_cacheable=*/false, | 
 |       url_loader_factory::Create( | 
 |           ContentBrowserClient::URLLoaderFactoryType::kNavigation, | 
 |           url_loader_factory::TerminalParams::ForNonNetwork( | 
 |               base::MakeRefCounted<network::SingleRequestURLLoaderFactory>( | 
 |                   base::BindOnce(UnknownSchemeCallback, handled)), | 
 |               network::mojom::kBrowserProcessId))); | 
 | } | 
 |  | 
 | void NavigationURLLoaderImpl::CreateThrottlingLoaderAndStart( | 
 |     scoped_refptr<network::SharedURLLoaderFactory> factory, | 
 |     std::vector<std::unique_ptr<blink::URLLoaderThrottle>> | 
 |         additional_throttles) { | 
 |   TRACE_EVENT_WITH_FLOW0( | 
 |       "navigation", "NavigationURLLoaderImpl::CreateThrottlingLoaderAndStart", | 
 |       TRACE_ID_LOCAL(this), | 
 |       TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); | 
 |   CHECK(!loader_holder_.url_loader()); | 
 |  | 
 |   std::vector<std::unique_ptr<blink::URLLoaderThrottle>> throttles = | 
 |       CreateURLLoaderThrottles(); | 
 |   for (auto&& throttle : additional_throttles) { | 
 |     throttles.push_back(std::move(throttle)); | 
 |   } | 
 |  | 
 |   uint32_t options = | 
 |       GetURLLoaderOptions(resource_request_->is_outermost_main_frame); | 
 |  | 
 |   loader_holder_.SetLoader(blink::ThrottlingURLLoader::CreateLoader( | 
 |       std::move(throttles), /*client=*/this, | 
 |       kNavigationUrlLoaderTrafficAnnotation, | 
 |       /*client_receiver_delegate=*/nullptr)); | 
 |   loader_holder_.url_loader()->Start( | 
 |       std::move(factory), global_request_id_.request_id, options, | 
 |       resource_request_.get(), | 
 |       GetUIThreadTaskRunner({BrowserTaskType::kNavigationNetworkResponse}), | 
 |       /*cors_exempt_header_list=*/std::nullopt, | 
 |       &request_info_->common_params->initiator_origin_trial_features); | 
 | } | 
 |  | 
 | const network::ResourceRequest& | 
 | NavigationURLLoaderImpl::GetResourceRequestForTesting() const { | 
 |   return *resource_request_; | 
 | } | 
 |  | 
 | void NavigationURLLoaderImpl::OnReceiveEarlyHints( | 
 |     network::mojom::EarlyHintsPtr early_hints) { | 
 |   // Early Hints should not come after actual response. | 
 |   DCHECK(!received_response_); | 
 |   DCHECK_NE(early_hints->ip_address_space, | 
 |             network::mojom::IPAddressSpace::kUnknown); | 
 |  | 
 |   // Ignore Early Hints for embed and object destination. | 
 |   if (request_info_->common_params->request_destination == | 
 |           network::mojom::RequestDestination::kEmbed || | 
 |       request_info_->common_params->request_destination == | 
 |           network::mojom::RequestDestination::kObject) { | 
 |     return; | 
 |   } | 
 |  | 
 |   FrameTreeNode* frame_tree_node = | 
 |       FrameTreeNode::GloballyFindByID(frame_tree_node_id_); | 
 |  | 
 |   // Allow Early Hints preload only for outermost main frames. Calculating | 
 |   // appropriate parameters to create URLLoaderFactory for subframes and fenced | 
 |   // frames are complicated and not supported yet. | 
 |   if (frame_tree_node->GetParentOrOuterDocument()) { | 
 |     return; | 
 |   } | 
 |  | 
 |   if (!early_hints_manager_) { | 
 |     std::optional<NavigationEarlyHintsManagerParams> params = | 
 |         delegate_->CreateNavigationEarlyHintsManagerParams(*early_hints); | 
 |     if (!params) { | 
 |       return; | 
 |     } | 
 |     early_hints_manager_ = std::make_unique<NavigationEarlyHintsManager>( | 
 |         *browser_context_, *storage_partition_, frame_tree_node_id_, | 
 |         std::move(*params)); | 
 |   } | 
 |  | 
 |   early_hints_manager_->HandleEarlyHints(std::move(early_hints), | 
 |                                          *resource_request_.get()); | 
 | } | 
 |  | 
 | void NavigationURLLoaderImpl::OnReceiveResponse( | 
 |     network::mojom::URLResponseHeadPtr head, | 
 |     mojo::ScopedDataPipeConsumerHandle response_body, | 
 |     std::optional<mojo_base::BigBuffer> cached_metadata) { | 
 |   DCHECK(!cached_metadata); | 
 |   LogQueueTimeHistogram("Navigation.QueueTime.OnReceiveResponse", | 
 |                         resource_request_->is_outermost_main_frame); | 
 |  | 
 |   // Early Hints preloads should not be committed for PDF. | 
 |   // See https://github.com/whatwg/html/issues/7823 | 
 |   if (head->mime_type == "application/pdf" || head->mime_type == "text/pdf") { | 
 |     early_hints_manager_.reset(); | 
 |   } | 
 |  | 
 |   //  TODO(crbug.com/40496584):Resolved an issue where creating RPHI would cause | 
 |   //  a crash when the browser context was shut down. We are actively exploring | 
 |   //  the appropriate long-term solution. Please remove this condition once the | 
 |   //  final fix is implemented. | 
 |   if (browser_context_->ShutdownStarted()) { | 
 |     return; | 
 |   } | 
 |  | 
 |   if (!response_body) { | 
 |     return; | 
 |   } | 
 |  | 
 |   response_body_ = std::move(response_body); | 
 |   received_response_ = true; | 
 |  | 
 |   if (!head_update_params_.load_timing_info.service_worker_start_time | 
 |            .is_null()) { | 
 |     head->load_timing.service_worker_start_time = | 
 |         head_update_params_.load_timing_info.service_worker_start_time; | 
 |     head->load_timing.service_worker_ready_time = | 
 |         head_update_params_.load_timing_info.service_worker_ready_time; | 
 |   } | 
 |   if (head_update_params_.initial_service_worker_status.has_value()) { | 
 |     head->initial_service_worker_status = | 
 |         head_update_params_.initial_service_worker_status; | 
 |   } | 
 |   if (!head_update_params_.router_info.is_null()) { | 
 |     head->service_worker_router_info = | 
 |         std::move(head_update_params_.router_info); | 
 |   } | 
 |   if (!head_update_params_.load_timing_info | 
 |            .service_worker_router_evaluation_start.is_null()) { | 
 |     head->load_timing.service_worker_router_evaluation_start = | 
 |         head_update_params_.load_timing_info | 
 |             .service_worker_router_evaluation_start; | 
 |   } | 
 |  | 
 |   // If the default loader (network) was used to handle the URL load request | 
 |   // we need to see if the interceptors want to potentially create a new | 
 |   // loader for the response. e.g. service workers. | 
 |   // | 
 |   // As the navigation request has received a response, the URLLoader has | 
 |   // completed without any network errors. Some interceptors may still wish | 
 |   // to handle the response. | 
 |   auto status = network::URLLoaderCompletionStatus(net::OK); | 
 |   if (MaybeCreateLoaderForResponse(status, &head)) { | 
 |     return; | 
 |   } | 
 |  | 
 |   network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints = | 
 |       loader_holder_.Unbind(); | 
 |  | 
 |   // 304 responses should abort the navigation, rather than display the page. | 
 |   // This needs to be after the URLLoader has been moved to | 
 |   // `url_loader_client_endpoints` in order to abort the request, to avoid | 
 |   // receiving unexpected call. | 
 |   if (head->headers && | 
 |       head->headers->response_code() == net::HTTP_NOT_MODIFIED) { | 
 |     // Call CancelWithError instead of OnComplete so that if there is an | 
 |     // intercepting URLLoaderFactory it gets notified. | 
 |     loader_holder_.url_loader()->CancelWithError( | 
 |         net::ERR_ABORTED, | 
 |         std::string_view(base::NumberToString(net::ERR_ABORTED))); | 
 |     return; | 
 |   } | 
 |  | 
 |   bool must_download = download_utils::MustDownload( | 
 |       browser_context_, url_, head->headers.get(), head->mime_type); | 
 |   bool known_mime_type = blink::IsSupportedMimeType(head->mime_type); | 
 |  | 
 | #if BUILDFLAG(ENABLE_PLUGINS) | 
 |   if (!head->intercepted_by_plugin && !must_download && !known_mime_type) { | 
 |     // No plugin throttles intercepted the response. Ask if the plugin | 
 |     // registered to PluginService wants to handle the request. | 
 |     CheckPluginAndContinueOnReceiveResponse( | 
 |         std::move(head), std::move(url_loader_client_endpoints), | 
 |         /*is_download_if_not_handled_by_plugin=*/true, | 
 |         std::vector<WebPluginInfo>()); | 
 |     return; | 
 |   } | 
 | #endif | 
 |  | 
 |   // When a plugin intercepted the response, we don't want to download it. | 
 |   bool is_download = | 
 |       !head->intercepted_by_plugin && (must_download || !known_mime_type); | 
 |  | 
 |   CallOnReceivedResponse(std::move(head), | 
 |                          std::move(url_loader_client_endpoints), is_download); | 
 | } | 
 |  | 
 | #if BUILDFLAG(ENABLE_PLUGINS) | 
 | void NavigationURLLoaderImpl::CheckPluginAndContinueOnReceiveResponse( | 
 |     network::mojom::URLResponseHeadPtr head, | 
 |     network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints, | 
 |     bool is_download_if_not_handled_by_plugin, | 
 |     const std::vector<WebPluginInfo>& plugins) { | 
 |   bool stale; | 
 |   WebPluginInfo plugin; | 
 |   bool has_plugin = PluginService::GetInstance()->GetPluginInfo( | 
 |       browser_context_, resource_request_->url, head->mime_type, | 
 |       /*allow_wildcard=*/false, &stale, &plugin, nullptr); | 
 |  | 
 |   if (stale) { | 
 |     // Refresh the plugins asynchronously. | 
 |     PluginService::GetInstance()->GetPlugins(base::BindOnce( | 
 |         &NavigationURLLoaderImpl::CheckPluginAndContinueOnReceiveResponse, | 
 |         weak_factory_.GetWeakPtr(), std::move(head), | 
 |         std::move(url_loader_client_endpoints), | 
 |         is_download_if_not_handled_by_plugin)); | 
 |     return; | 
 |   } | 
 |  | 
 |   bool is_download = !has_plugin && is_download_if_not_handled_by_plugin; | 
 |   CallOnReceivedResponse(std::move(head), | 
 |                          std::move(url_loader_client_endpoints), is_download); | 
 | } | 
 | #endif | 
 |  | 
 | void NavigationURLLoaderImpl::CallOnReceivedResponse( | 
 |     network::mojom::URLResponseHeadPtr head, | 
 |     network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints, | 
 |     bool is_download) { | 
 |   // Record navigation loader response metrics.  We don't want to record the | 
 |   // metrics for requests that had redirects to avoid adding noise to the | 
 |   // latency measurements. | 
 |   if (resource_request_->is_outermost_main_frame && | 
 |       resource_request_->navigation_redirect_chain.size() == 1) { | 
 |     RecordReceivedResponseUkmForOutermostMainFrame(); | 
 |   } | 
 |  | 
 |   // Record ServiceWorker and the Static Routing API metrics. | 
 |   MaybeRecordServiceWorkerMainResourceInfo(head); | 
 |  | 
 |   auto on_receive_response = base::BindOnce( | 
 |       &NavigationURLLoaderImpl::NotifyResponseStarted, | 
 |       weak_factory_.GetWeakPtr(), std::move(url_loader_client_endpoints), | 
 |       std::move(response_body_), global_request_id_, is_download); | 
 |  | 
 |   ParseHeaders(url_, std::move(head), std::move(on_receive_response)); | 
 | } | 
 |  | 
 | void NavigationURLLoaderImpl::OnReceiveRedirect( | 
 |     const net::RedirectInfo& redirect_info, | 
 |     network::mojom::URLResponseHeadPtr head) { | 
 |   LogQueueTimeHistogram("Navigation.QueueTime.OnReceiveRedirect", | 
 |                         resource_request_->is_outermost_main_frame); | 
 |   net::Error error = net::OK; | 
 |  | 
 |   bool bypass_redirect_checks = | 
 |       base::FeatureList::IsEnabled(features::kBypassRedirectChecksPerRequest) | 
 |           ? head->bypass_redirect_checks | 
 |           : bypass_redirect_checks_; | 
 |  | 
 |   if (!bypass_redirect_checks && | 
 |       !IsSafeRedirectTarget(url_, redirect_info.new_url)) { | 
 |     error = net::ERR_UNSAFE_REDIRECT; | 
 |   } else if (--redirect_limit_ == 0) { | 
 |     error = net::ERR_TOO_MANY_REDIRECTS; | 
 |     if (redirect_info.is_signed_exchange_fallback_redirect) { | 
 |       UMA_HISTOGRAM_BOOLEAN("SignedExchange.FallbackRedirectLoop", true); | 
 |     } | 
 |   } | 
 |   if (error != net::OK) { | 
 |     if (loader_holder_.url_loader()) { | 
 |       // Call CancelWithError instead of OnComplete so that if there is an | 
 |       // intercepting URLLoaderFactory (created through the embedder's | 
 |       // ContentBrowserClient::WillCreateURLLoaderFactory) it gets notified. | 
 |       loader_holder_.url_loader()->CancelWithError( | 
 |           error, std::string_view(base::NumberToString(error))); | 
 |     } else { | 
 |       // TODO(crbug.com/40118809): Make sure ResetWithReason() is called | 
 |       // on the original `url_loader_`. | 
 |       OnComplete(network::URLLoaderCompletionStatus(error)); | 
 |     } | 
 |     return; | 
 |   } | 
 |  | 
 |   // Store the redirect_info for later use in FollowRedirect where we give | 
 |   // our interceptors_ a chance to intercept the request for the new location. | 
 |   redirect_info_ = redirect_info; | 
 |  | 
 |   GURL previous_url = url_; | 
 |   url_ = redirect_info.new_url; | 
 |  | 
 |   auto on_receive_redirect = | 
 |       base::BindOnce(&NavigationURLLoaderImpl::NotifyRequestRedirected, | 
 |                      weak_factory_.GetWeakPtr(), redirect_info); | 
 |   ParseHeaders(previous_url, std::move(head), std::move(on_receive_redirect)); | 
 | } | 
 |  | 
 | void NavigationURLLoaderImpl::OnUploadProgress( | 
 |     int64_t current_position, | 
 |     int64_t total_size, | 
 |     OnUploadProgressCallback callback) { | 
 |   NOTREACHED(); | 
 | } | 
 |  | 
 | void NavigationURLLoaderImpl::OnTransferSizeUpdated( | 
 |     int32_t transfer_size_diff) { | 
 |   network::RecordOnTransferSizeUpdatedUMA( | 
 |       network::OnTransferSizeUpdatedFrom::kNavigationURLLoaderImpl); | 
 | } | 
 |  | 
 | void NavigationURLLoaderImpl::OnComplete( | 
 |     const network::URLLoaderCompletionStatus& status) { | 
 |   // Successful load must have used OnResponseStarted first. In this case, the | 
 |   // URLLoaderClient has already been transferred to the renderer process and | 
 |   // OnComplete is not expected to be called here. | 
 |   if (status.error_code == net::OK) { | 
 |     SCOPED_CRASH_KEY_STRING256("NavigationURLLoader_Complete", "url", | 
 |                                url_.spec()); | 
 |     base::debug::DumpWithoutCrashing(); | 
 |     return; | 
 |   } | 
 |  | 
 |   // If the default loader (network) was used to handle the URL load request | 
 |   // we need to see if the interceptors want to potentially create a new | 
 |   // loader for the response. e.g. service worker. | 
 |   // | 
 |   // Note: Despite having received a response, the HTTP_NOT_MODIFIED(304) ones | 
 |   //       are ignored using OnComplete(net::ERR_ABORTED). No interceptor must | 
 |   //       be used in this case. | 
 |   if (!received_response_) { | 
 |     auto response = network::mojom::URLResponseHead::New(); | 
 |     if (MaybeCreateLoaderForResponse(status, &response)) { | 
 |       return; | 
 |     } | 
 |   } | 
 |  | 
 |   status_ = status; | 
 |   GetUIThreadTaskRunner({})->PostTask( | 
 |       FROM_HERE, base::BindOnce(&NavigationURLLoaderImpl::NotifyRequestFailed, | 
 |                                 weak_factory_.GetWeakPtr(), status)); | 
 | } | 
 |  | 
 | namespace { | 
 |  | 
 | // These values are persisted to logs. Entries should not be renumbered and | 
 | // numeric values should never be reused. | 
 | // LINT.IfChange(OnAcceptCHFrameReceivedReturnLocation) | 
 | enum class OnAcceptCHFrameReceivedReturnLocation { | 
 |   kUnknown = 0, | 
 |   kNotEnabled = 1, | 
 |   kNoClientHintDelegate = 2, | 
 |   kNoCriticalHintsMissing = 3, | 
 |   kNoRestart = 4, | 
 |   kTooManyRestart = 5, | 
 |   kSendingErrorAborted = 6, | 
 |   kMaxValue = kSendingErrorAborted, | 
 | }; | 
 | // LINT.ThenChange(//tools/metrics/histograms/metadata/navigation/enums.xml:OnAcceptCHFrameReceivedReturnLocation) | 
 |  | 
 | void RecordOnAcceptCHFrameReceivedReturnLocation( | 
 |     OnAcceptCHFrameReceivedReturnLocation location) { | 
 |   base::UmaHistogramEnumeration( | 
 |       "Navigation.URLLoader.OnAcceptCHFrameReceived.ReturnLocation", location); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | void NavigationURLLoaderImpl::OnAcceptCHFrameReceived( | 
 |     const url::Origin& origin, | 
 |     const std::vector<network::mojom::WebClientHintsType>& accept_ch_frame, | 
 |     OnAcceptCHFrameReceivedCallback callback) { | 
 |   LogQueueTimeHistogram("Navigation.QueueTime.OnAcceptCHFrameReceived", | 
 |                         resource_request_->is_outermost_main_frame); | 
 |   base::ScopedUmaHistogramTimer timer( | 
 |       "Navigation.URLLoader.OnAcceptCHFrameReceived.ExecutionTime", | 
 |       base::ScopedUmaHistogramTimer::ScopedHistogramTiming::kMicrosecondTimes); | 
 |   TRACE_EVENT("navigation", "NavigationURLLoaderImpl::OnAcceptCHFrameReceived"); | 
 |   received_accept_ch_frame_ = true; | 
 |   if (!base::FeatureList::IsEnabled(network::features::kAcceptCHFrame)) { | 
 |     std::move(callback).Run(net::OK); | 
 |     RecordOnAcceptCHFrameReceivedReturnLocation( | 
 |         OnAcceptCHFrameReceivedReturnLocation::kNotEnabled); | 
 |     return; | 
 |   } | 
 |  | 
 |   LogAcceptCHFrameStatus(AcceptCHFrameRestart::kFramePresent); | 
 |  | 
 |   // Given that this is happening in the middle of navigation, there should | 
 |   // always be an owning frame tree node | 
 |   FrameTreeNode* frame_tree_node = | 
 |       FrameTreeNode::GloballyFindByID(frame_tree_node_id_); | 
 |   DCHECK(frame_tree_node); | 
 |   // Log each hint requested via an ACCEPT_CH Frame whether or not this caused | 
 |   // the connection to be restarted. | 
 |   auto* ukm_recorder = ukm::UkmRecorder::Get(); | 
 |   for (const auto& hint : accept_ch_frame) { | 
 |     ukm::builders::ClientHints_AcceptCHFrameUsage(ukm_source_id_) | 
 |         .SetType(static_cast<int64_t>(hint)) | 
 |         .Record(ukm_recorder->Get()); | 
 |   } | 
 |  | 
 |   ClientHintsControllerDelegate* client_hint_delegate = | 
 |       browser_context_->GetClientHintsControllerDelegate(); | 
 |  | 
 |   if (!client_hint_delegate) { | 
 |     std::move(callback).Run(net::OK); | 
 |     RecordOnAcceptCHFrameReceivedReturnLocation( | 
 |         OnAcceptCHFrameReceivedReturnLocation::kNoClientHintDelegate); | 
 |     return; | 
 |   } | 
 |  | 
 |   // Filter out hints that are disabled by features and the like. | 
 |   blink::EnabledClientHints filtered_enabled_hints; | 
 |   for (const auto& hint : accept_ch_frame) { | 
 |     filtered_enabled_hints.SetIsEnabled(hint, true); | 
 |   } | 
 |   const std::vector<network::mojom::WebClientHintsType>& filtered_hints = | 
 |       filtered_enabled_hints.GetEnabledHints(); | 
 |  | 
 |   if (!AreCriticalHintsMissing(origin, frame_tree_node, client_hint_delegate, | 
 |                                filtered_hints)) { | 
 |     std::move(callback).Run(net::OK); | 
 |     RecordOnAcceptCHFrameReceivedReturnLocation( | 
 |         OnAcceptCHFrameReceivedReturnLocation::kNoCriticalHintsMissing); | 
 |     return; | 
 |   } | 
 |  | 
 |   net::HttpRequestHeaders modified_headers; | 
 |   client_hint_delegate->SetAdditionalClientHints(filtered_hints); | 
 |   AddNavigationRequestClientHintsHeaders( | 
 |       origin, &modified_headers, browser_context_, client_hint_delegate, | 
 |       frame_tree_node->navigation_request()->is_overriding_user_agent(), | 
 |       frame_tree_node, | 
 |       frame_tree_node->navigation_request() | 
 |           ->commit_params() | 
 |           .frame_policy.container_policy); | 
 |   client_hint_delegate->ClearAdditionalClientHints(); | 
 |  | 
 |   LogAcceptCHFrameStatus(AcceptCHFrameRestart::kNavigationRestarted); | 
 |  | 
 |   // Only restart if new headers are actually added. Given that header values | 
 |   // can be changed via the navigation interceptors or previous restarts, the | 
 |   // header values are ignored and only the presence of header names are | 
 |   // checked. | 
 |   bool restart = false; | 
 |   net::HttpRequestHeaders::Iterator header_iter(modified_headers); | 
 |   while (header_iter.GetNext()) { | 
 |     if (!resource_request_->headers.HasHeader(header_iter.name())) { | 
 |       restart = true; | 
 |       break; | 
 |     } | 
 |   } | 
 |  | 
 |   if (!restart) { | 
 |     std::move(callback).Run(net::OK); | 
 |     RecordOnAcceptCHFrameReceivedReturnLocation( | 
 |         OnAcceptCHFrameReceivedReturnLocation::kNoRestart); | 
 |     return; | 
 |   } | 
 |  | 
 |   // While not a true redirect, a redirect loop can be simulated by repeatedly | 
 |   // closing the socket and presenting a different ALPS setting with each new | 
 |   // handshake. | 
 |   if (--accept_ch_restart_limit_ == 0) { | 
 |     LogAcceptCHFrameStatus(AcceptCHFrameRestart::kRedirectOverflow); | 
 |     OnComplete(network::URLLoaderCompletionStatus( | 
 |         net::ERR_TOO_MANY_ACCEPT_CH_RESTARTS)); | 
 |     std::move(callback).Run(net::ERR_TOO_MANY_ACCEPT_CH_RESTARTS); | 
 |     RecordOnAcceptCHFrameReceivedReturnLocation( | 
 |         OnAcceptCHFrameReceivedReturnLocation::kTooManyRestart); | 
 |     return; | 
 |   } | 
 |  | 
 |   std::move(callback).Run(net::ERR_ABORTED); | 
 |   RecordOnAcceptCHFrameReceivedReturnLocation( | 
 |       OnAcceptCHFrameReceivedReturnLocation::kSendingErrorAborted); | 
 |  | 
 |   // If the request is restarted, all of the client hints should be replaced | 
 |   // the "original"/non-edited values. | 
 |   resource_request_->headers.MergeFrom(modified_headers); | 
 |  | 
 |   // Calling `OnAcceptCHFrameReceived()` implies the loading is ongoing via | 
 |   // `url_loader_` and thus the receiver should be unbound. | 
 |   // TODO(https://crbug.com/434182226): Remove DUMP_WILL_BE_. | 
 |   DUMP_WILL_BE_CHECK(!loader_holder_.receiver_is_bound_for_check()); | 
 |  | 
 |   loader_holder_.Reset(); | 
 |   Restart(); | 
 | } | 
 |  | 
 | void NavigationURLLoaderImpl::Clone( | 
 |     mojo::PendingReceiver<network::mojom::AcceptCHFrameObserver> listener) { | 
 |   // Use |kNavigationNetworkResponse| thread runner. Messages received related | 
 |   // to AcceptCHFrame are not order dependent and can restart the navigation, | 
 |   // blocking navigation when they do. | 
 |   accept_ch_frame_observers_.Add( | 
 |       this, std::move(listener), | 
 |       GetUIThreadTaskRunner({BrowserTaskType::kNavigationNetworkResponse})); | 
 | } | 
 |  | 
 | // Returns true if an interceptor wants to handle the response, i.e. return a | 
 | // different response, e.g. service workers. | 
 | bool NavigationURLLoaderImpl::MaybeCreateLoaderForResponse( | 
 |     const network::URLLoaderCompletionStatus& status, | 
 |     network::mojom::URLResponseHeadPtr* response) { | 
 |   if (!default_loader_used_) { | 
 |     return false; | 
 |   } | 
 |   for (auto& interceptor : interceptors_) { | 
 |     mojo::PendingReceiver<network::mojom::URLLoaderClient> | 
 |         response_client_receiver; | 
 |     bool skip_other_interceptors = false; | 
 |     if (interceptor->MaybeCreateLoaderForResponse( | 
 |             status, *resource_request_, response, &response_body_, | 
 |             loader_holder_.response_url_loader(), &response_client_receiver, | 
 |             loader_holder_.url_loader(), &skip_other_interceptors)) { | 
 |       loader_holder_.BindReceiver( | 
 |           std::move(response_client_receiver), | 
 |           GetUIThreadTaskRunner({BrowserTaskType::kNavigationNetworkResponse})); | 
 |       default_loader_used_ = false; | 
 |       response_body_.reset();  // Consumed above. | 
 |       if (skip_other_interceptors) { | 
 |         std::vector<std::unique_ptr<NavigationLoaderInterceptor>> | 
 |             new_interceptors; | 
 |         new_interceptors.push_back(std::move(interceptor)); | 
 |         new_interceptors.swap(interceptors_); | 
 |         // Reset the state of ServiceWorkerClient. | 
 |         // Currently we don't support Service Worker in Signed Exchange | 
 |         // pages. The page will not be controlled by service workers. And | 
 |         // Service Worker related APIs will fail with NoDocumentURL error. | 
 |         // TODO(https://crbug/898733): Support SignedExchange loading and | 
 |         // Service Worker integration. Properly populate all params below, and | 
 |         // storage key in particular, when we want to support it. | 
 |         if (service_worker_handle_) { | 
 |           base::WeakPtr<ServiceWorkerClient> service_worker_client = | 
 |               service_worker_handle_->service_worker_client(); | 
 |           if (service_worker_client) { | 
 |             service_worker_client->SetControllerRegistration( | 
 |                 nullptr, /*notify_controllerchange=*/false); | 
 |             service_worker_client->UpdateUrls(GURL(), std::nullopt, | 
 |                                               blink::StorageKey()); | 
 |           } | 
 |         } | 
 |       } | 
 |       return true; | 
 |     } | 
 |   } | 
 |   return false; | 
 | } | 
 |  | 
 | std::vector<std::unique_ptr<blink::URLLoaderThrottle>> | 
 | NavigationURLLoaderImpl::CreateURLLoaderThrottles() { | 
 |   TRACE_EVENT_WITH_FLOW0("navigation", | 
 |                          "NavigationURLLoaderImpl::CreateURLLoaderThrottles", | 
 |                          TRACE_ID_LOCAL(this), | 
 |                          TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); | 
 |   auto throttles = CreateContentBrowserURLLoaderThrottles( | 
 |       *resource_request_, browser_context_, web_contents_getter_, | 
 |       navigation_ui_data_.get(), frame_tree_node_id_, | 
 |       request_info_->navigation_id); | 
 |   throttles.push_back(std::make_unique<NavigationTimingThrottle>( | 
 |       resource_request_->is_outermost_main_frame, loader_creation_time_)); | 
 |   return throttles; | 
 | } | 
 |  | 
 | std::unique_ptr<SignedExchangeRequestHandler> | 
 | NavigationURLLoaderImpl::CreateSignedExchangeRequestHandler( | 
 |     const NavigationRequestInfo& request_info, | 
 |     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) { | 
 |   // It is safe to pass the callback of CreateURLLoaderThrottles with the | 
 |   // unretained `this`, because the passed callback will be used by a | 
 |   // SignedExchangeHandler which is indirectly owned by `this` until its | 
 |   // header is verified and parsed, that's where the getter is used. | 
 |   return std::make_unique<SignedExchangeRequestHandler>( | 
 |       GetURLLoaderOptions(request_info.is_outermost_main_frame), | 
 |       request_info.frame_tree_node_id, request_info.devtools_navigation_token, | 
 |       std::move(url_loader_factory), | 
 |       base::BindRepeating(&NavigationURLLoaderImpl::CreateURLLoaderThrottles, | 
 |                           base::Unretained(this)), | 
 |       GetContentClient()->browser()->GetAcceptLangs(browser_context_)); | 
 | } | 
 |  | 
 | void NavigationURLLoaderImpl::ParseHeaders( | 
 |     const GURL& url, | 
 |     network::mojom::URLResponseHeadPtr head, | 
 |     base::OnceCallback<void(network::mojom::URLResponseHeadPtr)> continuation) { | 
 |   // As an optimization, when we know the parsed headers will be empty, we can | 
 |   // skip the network process roundtrip. | 
 |   // TODO(arthursonzogni): If there are any performance issues, consider | 
 |   // checking the `head->headers` contains at least one header to be parsed. | 
 |   if (!head->headers) { | 
 |     head->parsed_headers = network::mojom::ParsedHeaders::New(); | 
 |   } | 
 |  | 
 |   // If the network service is running in process, skip unnecessary thread hops. | 
 |   if (IsInProcessNetworkService() && !head->parsed_headers) { | 
 |     base::ScopedUmaHistogramTimer in_process( | 
 |         "Navigation.URLLoader.ParseHeaders.InProcessTime", | 
 |         base::ScopedUmaHistogramTimer::ScopedHistogramTiming:: | 
 |             kMicrosecondTimes); | 
 |     head->parsed_headers = | 
 |         network::PopulateParsedHeaders(head->headers.get(), url); | 
 |   } | 
 |  | 
 |   // The main path: | 
 |   // -------------- | 
 |   // The ParsedHeaders are already provided. No more work needed. | 
 |   // | 
 |   // Currently used when the response is coming from: | 
 |   // - Network | 
 |   // - ServiceWorker | 
 |   // - WebUI | 
 |   base::UmaHistogramBoolean("Navigation.URLLoader.InMainPath", | 
 |                             static_cast<bool>(head->parsed_headers)); | 
 |   if (head->parsed_headers) { | 
 | #ifndef NDEBUG | 
 |     // In debug mode, force reparsing the headers and check that they match. | 
 |     auto check = [](base::OnceCallback<void(network::mojom::URLResponseHeadPtr)> | 
 |                         continuation, | 
 |                     network::mojom::URLResponseHeadPtr head, GURL url, | 
 |                     base::TimeTicks call_time, | 
 |                     network::mojom::ParsedHeadersPtr parsed_headers) { | 
 |       base::UmaHistogramMicrosecondsTimes( | 
 |           "Navigation.URLLoader.ParseHeaders.RoundTripTimeForVerify", | 
 |           base::TimeTicks::Now() - call_time); | 
 |       CheckParsedHeadersEquals(parsed_headers, head->parsed_headers, url); | 
 |       std::move(continuation).Run(std::move(head)); | 
 |     }; | 
 |     scoped_refptr<net::HttpResponseHeaders> headers = head->headers; | 
 |     GetNetworkService()->ParseHeaders( | 
 |         url, std::move(headers), | 
 |         base::BindOnce(check, std::move(continuation), std::move(head), url, | 
 |                        base::TimeTicks::Now())); | 
 | #else   // NDEBUG | 
 |     std::move(continuation).Run(std::move(head)); | 
 | #endif  // NDEBUG | 
 |     return; | 
 |   } | 
 |  | 
 |   auto assign = [](base::OnceCallback<void(network::mojom::URLResponseHeadPtr)> | 
 |                        continuation, | 
 |                    network::mojom::URLResponseHeadPtr head, | 
 |                    base::TimeTicks call_time, | 
 |                    network::mojom::ParsedHeadersPtr parsed_headers) { | 
 |     base::UmaHistogramMicrosecondsTimes( | 
 |         "Navigation.URLLoader.ParseHeaders.RoundTripTimeForNonNetworkResponse", | 
 |         base::TimeTicks::Now() - call_time); | 
 |     head->parsed_headers = std::move(parsed_headers); | 
 |     std::move(continuation).Run(std::move(head)); | 
 |   }; | 
 |  | 
 |   scoped_refptr<net::HttpResponseHeaders> headers = head->headers; | 
 |   GetNetworkService()->ParseHeaders( | 
 |       url, std::move(headers), | 
 |       base::BindOnce(assign, std::move(continuation), std::move(head), | 
 |                      base::TimeTicks::Now())); | 
 | } | 
 |  | 
 | // TODO(crbug.com/40552600): pass `navigation_ui_data` along with the | 
 | // request so that it could be modified. | 
 | NavigationURLLoaderImpl::NavigationURLLoaderImpl( | 
 |     BrowserContext* browser_context, | 
 |     StoragePartition* storage_partition, | 
 |     std::unique_ptr<NavigationRequestInfo> request_info, | 
 |     std::unique_ptr<NavigationUIData> navigation_ui_data, | 
 |     ServiceWorkerMainResourceHandle* service_worker_handle, | 
 |     scoped_refptr<PrefetchedSignedExchangeCache> | 
 |         prefetched_signed_exchange_cache, | 
 |     NavigationURLLoaderDelegate* delegate, | 
 |     mojo::PendingRemote<network::mojom::CookieAccessObserver> cookie_observer, | 
 |     mojo::PendingRemote<network::mojom::TrustTokenAccessObserver> | 
 |         trust_token_observer, | 
 |     mojo::PendingRemote<network::mojom::SharedDictionaryAccessObserver> | 
 |         shared_dictionary_observer, | 
 |     mojo::PendingRemote<network::mojom::URLLoaderNetworkServiceObserver> | 
 |         url_loader_network_observer, | 
 |     mojo::PendingRemote<network::mojom::DevToolsObserver> devtools_observer, | 
 |     mojo::PendingRemote<network::mojom::DeviceBoundSessionAccessObserver> | 
 |         device_bound_session_observer, | 
 |     std::vector<std::unique_ptr<NavigationLoaderInterceptor>> | 
 |         initial_interceptors) | 
 |     : delegate_(delegate), | 
 |       browser_context_(browser_context), | 
 |       storage_partition_(static_cast<StoragePartitionImpl*>(storage_partition)), | 
 |       service_worker_handle_(service_worker_handle), | 
 |       request_info_(std::move(request_info)), | 
 |       url_(request_info_->common_params->url), | 
 |       frame_tree_node_id_(request_info_->frame_tree_node_id), | 
 |       global_request_id_(GlobalRequestID::MakeBrowserInitiated()), | 
 |       web_contents_getter_( | 
 |           base::BindRepeating(&WebContents::FromFrameTreeNodeId, | 
 |                               frame_tree_node_id_)), | 
 |       navigation_ui_data_(std::move(navigation_ui_data)), | 
 |       interceptors_(std::move(initial_interceptors)), | 
 |       prefetched_signed_exchange_cache_( | 
 |           std::move(prefetched_signed_exchange_cache)), | 
 |       loader_creation_time_(base::TimeTicks::Now()), | 
 |       ukm_source_id_(FrameTreeNode::GloballyFindByID(frame_tree_node_id_) | 
 |                          ->navigation_request() | 
 |                          ->GetNextPageUkmSourceId()) { | 
 |   TRACE_EVENT_WITH_FLOW0("navigation", | 
 |                          "NavigationURLLoaderImpl::NavigationURLLoaderImpl", | 
 |                          TRACE_ID_LOCAL(this), TRACE_EVENT_FLAG_FLOW_OUT); | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |  | 
 |   TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP1( | 
 |       "navigation", "Navigation timeToResponseStarted", TRACE_ID_LOCAL(this), | 
 |       request_info_->common_params->navigation_start, "FrameTreeNode id", | 
 |       frame_tree_node_id_); | 
 |  | 
 |   mojo::PendingRemote<network::mojom::AcceptCHFrameObserver> | 
 |       accept_ch_frame_observer; | 
 |   // Use |kNavigationNetworkResponse| thread runner. Messages received related | 
 |   // to AcceptCHFrame are not order dependent and can restart the navigation, | 
 |   // blocking navigation when they do. | 
 |   accept_ch_frame_observers_.Add( | 
 |       this, accept_ch_frame_observer.InitWithNewPipeAndPassReceiver(), | 
 |       GetUIThreadTaskRunner({BrowserTaskType::kNavigationNetworkResponse})); | 
 |  | 
 |   FrameTreeNode* frame_tree_node = | 
 |       FrameTreeNode::GloballyFindByID(frame_tree_node_id_); | 
 |   DCHECK(frame_tree_node); | 
 |   DCHECK(frame_tree_node->navigation_request()); | 
 |  | 
 |   resource_request_ = CreateResourceRequest( | 
 |       *request_info_, frame_tree_node, | 
 |       browser_context_->GetClientHintsControllerDelegate(), | 
 |       std::move(cookie_observer), std::move(trust_token_observer), | 
 |       std::move(shared_dictionary_observer), | 
 |       std::move(url_loader_network_observer), std::move(devtools_observer), | 
 |       std::move(device_bound_session_observer), | 
 |       std::move(accept_ch_frame_observer)); | 
 |  | 
 |   network_loader_factory_ = CreateNetworkLoaderFactory( | 
 |       browser_context_, storage_partition_, frame_tree_node, | 
 |       ukm::SourceIdObj::FromInt64(ukm_source_id_), &bypass_redirect_checks_); | 
 | } | 
 |  | 
 | // static | 
 | mojo::PendingRemote<network::mojom::URLLoaderFactory> | 
 | NavigationURLLoaderImpl::CreateTerminalNonNetworkLoaderFactory( | 
 |     BrowserContext* browser_context, | 
 |     StoragePartitionImpl* storage_partition, | 
 |     FrameTreeNode* frame_tree_node, | 
 |     const GURL& url) { | 
 |   // Use `ContentBrowserClient`-supplied factory if non-null, to allow the | 
 |   // embedder to override the default factories below. | 
 |   if (auto factory_from_client = | 
 |           GetContentClient() | 
 |               ->browser() | 
 |               ->CreateNonNetworkNavigationURLLoaderFactory( | 
 |                   url.scheme(), frame_tree_node->frame_tree_node_id())) { | 
 |     return factory_from_client; | 
 |   } | 
 |  | 
 |   if (url.scheme() == url::kFileSystemScheme) { | 
 |     bool is_nav_allowed = | 
 |         base::FeatureList::IsEnabled( | 
 |             blink::features::kFileSystemUrlNavigationForChromeAppsOnly) && | 
 |         GetContentClient()->browser()->IsFileSystemURLNavigationAllowed( | 
 |             storage_partition->browser_context(), url); | 
 |     if (is_nav_allowed || | 
 |         base::FeatureList::IsEnabled( | 
 |             blink::features::kFileSystemUrlNavigation) || | 
 |         !frame_tree_node->navigation_request()->IsRendererInitiated()) { | 
 |       // TODO(crbug.com/40323778): Once DevTools has support for | 
 |       // sandboxed file system inspection there isn't much reason anymore to | 
 |       // support browser initiated filesystem: navigations, so remove this | 
 |       // entirely at that point. | 
 |  | 
 |       // Navigations in to filesystem: URLs are deprecated entirely for | 
 |       // renderer-initiated navigations except for those explicitly allowed by | 
 |       // the embedder. The logic below is appropriate for browser-initiated | 
 |       // navigations, but it is incorrect to always use first-party | 
 |       // StorageKeys for renderer-initiated navigations when third party | 
 |       // storage partitioning is enabled. | 
 |       const std::string storage_domain; | 
 |       return CreateFileSystemURLLoaderFactory( | 
 |           ChildProcessHost::kInvalidUniqueID, | 
 |           frame_tree_node->frame_tree_node_id(), | 
 |           storage_partition->GetFileSystemContext(), storage_domain, | 
 |           blink::StorageKey::CreateFirstParty(url::Origin::Create(url))); | 
 |     } | 
 |  | 
 |     return {}; | 
 |   } | 
 |  | 
 |   if (url.scheme() == url::kAboutScheme) { | 
 |     return AboutURLLoaderFactory::Create(); | 
 |   } | 
 |   if (url.scheme() == url::kDataScheme) { | 
 |     return DataURLLoaderFactory::Create(); | 
 |   } | 
 |  | 
 |   if (url.scheme() == url::kFileScheme) { | 
 |     // USER_BLOCKING because this scenario is exactly one of the examples | 
 |     // given by the doc comment for USER_BLOCKING: | 
 |     // Loading and rendering a web page after the user clicks a link. | 
 |     base::TaskPriority file_factory_priority = | 
 |         base::TaskPriority::USER_BLOCKING; | 
 |     return FileURLLoaderFactory::Create( | 
 |         browser_context->GetPath(), | 
 |         browser_context->GetSharedCorsOriginAccessList(), | 
 |         file_factory_priority); | 
 |   } | 
 |  | 
 | #if BUILDFLAG(IS_ANDROID) | 
 |   if (url.scheme() == url::kContentScheme) { | 
 |     return ContentURLLoaderFactory::Create(); | 
 |   } | 
 | #endif | 
 |  | 
 |   return {}; | 
 | } | 
 |  | 
 | scoped_refptr<network::SharedURLLoaderFactory> | 
 | NavigationURLLoaderImpl::CreateNetworkLoaderFactory( | 
 |     BrowserContext* browser_context, | 
 |     StoragePartitionImpl* storage_partition, | 
 |     FrameTreeNode* frame_tree_node, | 
 |     const ukm::SourceIdObj& ukm_id, | 
 |     bool* bypass_redirect_checks) { | 
 |   mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient> | 
 |       header_client; | 
 |  | 
 |   // The embedder may want to proxy all network-bound URLLoaderFactory | 
 |   // receivers that it can. If it elects to do so, those proxies will be | 
 |   // connected when loader is created if the request type supports proxying. | 
 |   network::URLLoaderFactoryBuilder factory_builder; | 
 |   // Here we give nullptr for `factory_override`, because CORS is no-op for | 
 |   // navigations. | 
 |   GetContentClient()->browser()->WillCreateURLLoaderFactory( | 
 |       browser_context, frame_tree_node->current_frame_host(), | 
 |       frame_tree_node->current_frame_host()->GetProcess()->GetDeprecatedID(), | 
 |       ContentBrowserClient::URLLoaderFactoryType::kNavigation, url::Origin(), | 
 |       net::IsolationInfo(), | 
 |       frame_tree_node->navigation_request()->GetNavigationId(), ukm_id, | 
 |       factory_builder, &header_client, bypass_redirect_checks, | 
 |       /*disable_secure_dns=*/nullptr, /*factory_override=*/nullptr, | 
 |       GetUIThreadTaskRunner({BrowserTaskType::kNavigationNetworkResponse})); | 
 |  | 
 |   auto devtools_params = | 
 |       devtools_instrumentation::WillCreateURLLoaderFactoryParams::ForFrame( | 
 |           frame_tree_node->current_frame_host()); | 
 |   devtools_params.Run(/*is_navigation=*/true, | 
 |                       /*is_download=*/false, factory_builder, | 
 |                       /*factory_override=*/nullptr); | 
 |   net::CookieSettingOverrides devtools_cookie_overrides; | 
 |   devtools_instrumentation::ApplyNetworkCookieControlsOverrides( | 
 |       devtools_params.agent_host(), devtools_cookie_overrides); | 
 |  | 
 |   net::CookieSettingOverrides cookie_overrides; | 
 |   if (ShouldAllowSameSiteNoneCookiesInSandbox(*frame_tree_node)) { | 
 |     // Include a CookieSettingOverride in the UrlLoaderFactoryParams for the | 
 |     // frame's SharedURLLoaderFactory if the frame contains the | 
 |     // `allow-same-site-none-cookies` value in its sandbox policy. | 
 |     cookie_overrides.Put( | 
 |         net::CookieSettingOverride::kAllowSameSiteNoneCookiesInSandbox); | 
 |   } | 
 |  | 
 |   if (header_client) { | 
 |     return base::MakeRefCounted<network::WrapperSharedURLLoaderFactory>( | 
 |         CreateURLLoaderFactoryWithHeaderClient( | 
 |             std::move(header_client), std::move(factory_builder), | 
 |             storage_partition, std::move(devtools_cookie_overrides), | 
 |             std::move(cookie_overrides))); | 
 |   } else { | 
 |     if (!devtools_cookie_overrides.empty() || !cookie_overrides.empty()) { | 
 |       network::mojom::URLLoaderFactoryParamsPtr params = | 
 |           storage_partition->CreateURLLoaderFactoryParams(); | 
 |       params->devtools_cookie_setting_overrides = | 
 |           std::move(devtools_cookie_overrides); | 
 |       params->cookie_setting_overrides = std::move(cookie_overrides); | 
 |       return std::move(factory_builder) | 
 |           .Finish(storage_partition->GetNetworkContext(), std::move(params)); | 
 |     } | 
 |     return std::move(factory_builder) | 
 |         .Finish(storage_partition->GetURLLoaderFactoryForBrowserProcess()); | 
 |   } | 
 | } | 
 |  | 
 | void NavigationURLLoaderImpl::FollowRedirect( | 
 |     std::vector<std::string> removed_headers, | 
 |     net::HttpRequestHeaders modified_headers, | 
 |     net::HttpRequestHeaders modified_cors_exempt_headers) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   DCHECK(!redirect_info_.new_url.is_empty()); | 
 |  | 
 |   // Don't send Accept: application/signed-exchange for fallback redirects. | 
 |   // This is also applied to `resource_request_->headers` via | 
 |   // `net::RedirectUtil::UpdateHttpRequest()`. | 
 |   if (redirect_info_.is_signed_exchange_fallback_redirect) { | 
 |     modified_headers.SetHeader( | 
 |         net::HttpRequestHeaders::kAccept, | 
 |         FrameAcceptHeaderValue(/*allow_sxg_responses=*/false, | 
 |                                browser_context_)); | 
 |   } | 
 |  | 
 |   // Update `resource_request_` and call Restart to give our `interceptors_` a | 
 |   // chance at handling the new location. If no interceptor wants to take | 
 |   // over, we'll use the existing url_loader to follow the redirect, see | 
 |   // MaybeStartLoader. | 
 |   // TODO(michaeln): This is still WIP and is based on URLRequest::Redirect, | 
 |   // there likely remains more to be done. | 
 |   // a. For subframe navigations, the Origin header may need to be modified | 
 |   //    differently? | 
 |  | 
 |   bool should_clear_upload = false; | 
 |   net::RedirectUtil::UpdateHttpRequest( | 
 |       resource_request_->url, resource_request_->method, redirect_info_, | 
 |       removed_headers, modified_headers, &resource_request_->headers, | 
 |       &should_clear_upload); | 
 |   if (should_clear_upload) { | 
 |     // The request body is no longer applicable. | 
 |     resource_request_->request_body.reset(); | 
 |   } | 
 |  | 
 |   const GURL previous_url = resource_request_->url; | 
 |   resource_request_->url = redirect_info_.new_url; | 
 |   resource_request_->method = redirect_info_.new_method; | 
 |   resource_request_->site_for_cookies = redirect_info_.new_site_for_cookies; | 
 |  | 
 |   // See if navigation network isolation key needs to be updated. | 
 |   resource_request_->trusted_params->isolation_info = | 
 |       resource_request_->trusted_params->isolation_info.CreateForRedirect( | 
 |           url::Origin::Create(resource_request_->url)); | 
 |  | 
 |   resource_request_->referrer = GURL(redirect_info_.new_referrer); | 
 |   resource_request_->referrer_policy = redirect_info_.new_referrer_policy; | 
 |   resource_request_->navigation_redirect_chain.push_back( | 
 |       redirect_info_.new_url); | 
 |  | 
 |   if (base::FeatureList::IsEnabled( | 
 |           network::features::kOffloadAcceptCHFrameCheck)) { | 
 |     const url::Origin new_origin = url::Origin::Create(resource_request_->url); | 
 |     const url::Origin old_origin = url::Origin::Create(previous_url); | 
 |     if (!new_origin.IsSameOriginWith(old_origin)) { | 
 |       // For cross-origin redirects, the existing client hints are invalid. | 
 |       // Clear them to avoid sending unintentional hints. | 
 |       resource_request_->trusted_params->enabled_client_hints.reset(); | 
 |  | 
 |       if (network::features::kAcceptCHOffloadWithRedirect.Get()) { | 
 |         FrameTreeNode* frame_tree_node = | 
 |             FrameTreeNode::GloballyFindByID(frame_tree_node_id_); | 
 |         ClientHintsControllerDelegate* client_hints_controller_delegate = | 
 |             browser_context_->GetClientHintsControllerDelegate(); | 
 |         if (client_hints_controller_delegate && frame_tree_node) { | 
 |           resource_request_->trusted_params->enabled_client_hints = | 
 |               GetEnabledClientHints(new_origin, frame_tree_node, | 
 |                                     client_hints_controller_delegate); | 
 |         } | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   // Need to cache modified headers for `url_loader_` since it doesn't use | 
 |   // `resource_request_` during redirect. | 
 |   loader_holder_.SetModifiedHeadersOnRedirect( | 
 |       std::move(removed_headers), std::move(modified_headers), | 
 |       std::move(modified_cors_exempt_headers)); | 
 |  | 
 |   Restart(); | 
 | } | 
 |  | 
 | bool NavigationURLLoaderImpl::SetNavigationTimeout(base::TimeDelta timeout) { | 
 |   // If the timer has already been started, don't change it. | 
 |   if (timeout_timer_.IsRunning()) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   // Fail the navigation with error code ERR_TIMED_OUT if the timer triggers | 
 |   // before the navigation commits. (This triggers OnComplete() rather than | 
 |   // NotifyRequestFailed() to make sure that any NavigationLoaderInterceptors | 
 |   // can handle the result if needed.) | 
 |   timeout_timer_.Start( | 
 |       FROM_HERE, timeout, | 
 |       base::BindOnce(&NavigationURLLoaderImpl::OnComplete, | 
 |                      base::Unretained(this), | 
 |                      network::URLLoaderCompletionStatus(net::ERR_TIMED_OUT))); | 
 |   return true; | 
 | } | 
 |  | 
 | void NavigationURLLoaderImpl::CancelNavigationTimeout() { | 
 |   timeout_timer_.Stop(); | 
 | } | 
 |  | 
 | void NavigationURLLoaderImpl::NotifyResponseStarted( | 
 |     network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints, | 
 |     mojo::ScopedDataPipeConsumerHandle response_body, | 
 |     const GlobalRequestID& global_request_id, | 
 |     bool is_download, | 
 |     network::mojom::URLResponseHeadPtr response_head) { | 
 |   TRACE_EVENT_NESTABLE_ASYNC_END2( | 
 |       "navigation", "Navigation timeToResponseStarted", TRACE_ID_LOCAL(this), | 
 |       "&NavigationURLLoaderImpl", static_cast<void*>(this), "success", true); | 
 |  | 
 |   NavigationURLLoaderDelegate::EarlyHints early_hints; | 
 |   if (early_hints_manager_) { | 
 |     early_hints.was_resource_hints_received = | 
 |         early_hints_manager_->WasResourceHintsReceived(); | 
 |  | 
 |     // Make Early Hints manager outlive this loader only when the response | 
 |     // headers are available. Dropping the manager cancels inflight preloads. | 
 |     if (response_head && response_head->headers) { | 
 |       early_hints.manager = std::move(early_hints_manager_); | 
 |     } | 
 |   } | 
 |  | 
 |   // TODO(scottmg): This needs to do more of what | 
 |   // NavigationResourceHandler::OnResponseStarted() does. | 
 |   delegate_->OnResponseStarted( | 
 |       std::move(url_loader_client_endpoints), std::move(response_head), | 
 |       std::move(response_body), global_request_id, is_download, | 
 |       resource_request_->trusted_params->isolation_info | 
 |           .network_anonymization_key(), | 
 |       std::move(subresource_loader_params_), std::move(early_hints)); | 
 | } | 
 |  | 
 | void NavigationURLLoaderImpl::NotifyRequestRedirected( | 
 |     net::RedirectInfo redirect_info, | 
 |     network::mojom::URLResponseHeadPtr response_head) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |   delegate_->OnRequestRedirected( | 
 |       redirect_info, | 
 |       resource_request_->trusted_params->isolation_info | 
 |           .network_anonymization_key(), | 
 |       std::move(response_head)); | 
 | } | 
 |  | 
 | void NavigationURLLoaderImpl::NotifyRequestFailed( | 
 |     const network::URLLoaderCompletionStatus& status) { | 
 |   TRACE_EVENT_NESTABLE_ASYNC_END2( | 
 |       "navigation", "Navigation timeToResponseStarted", TRACE_ID_LOCAL(this), | 
 |       "&NavigationURLLoaderImpl", static_cast<void*>(this), "success", false); | 
 |   delegate_->OnRequestFailed(status); | 
 | } | 
 |  | 
 | // static | 
 | mojo::PendingRemote<network::mojom::URLLoaderFactory> | 
 | NavigationURLLoaderImpl::CreateURLLoaderFactoryWithHeaderClient( | 
 |     mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient> | 
 |         header_client, | 
 |     network::URLLoaderFactoryBuilder factory_builder, | 
 |     StoragePartitionImpl* partition, | 
 |     std::optional<net::CookieSettingOverrides> devtools_cookie_overrides, | 
 |     std::optional<net::CookieSettingOverrides> cookie_overrides) { | 
 |   DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |  | 
 |   if (url_loader_factory::GetTestingInterceptor()) { | 
 |     url_loader_factory::GetTestingInterceptor().Run( | 
 |         network::mojom::kBrowserProcessId, factory_builder); | 
 |   } | 
 |  | 
 |   network::mojom::URLLoaderFactoryParamsPtr params = | 
 |       network::mojom::URLLoaderFactoryParams::New(); | 
 |   params->header_client = std::move(header_client); | 
 |   params->process_id = network::mojom::kBrowserProcessId; | 
 |   params->is_trusted = true; | 
 |   params->is_orb_enabled = false; | 
 |   params->disable_web_security = | 
 |       base::CommandLine::ForCurrentProcess()->HasSwitch( | 
 |           switches::kDisableWebSecurity); | 
 |   if (devtools_cookie_overrides.has_value()) { | 
 |     params->devtools_cookie_setting_overrides = | 
 |         std::move(devtools_cookie_overrides.value()); | 
 |   } | 
 |   if (cookie_overrides.has_value()) { | 
 |     params->cookie_setting_overrides = std::move(cookie_overrides.value()); | 
 |   } | 
 |   return std::move(factory_builder) | 
 |       .Finish<mojo::PendingRemote<network::mojom::URLLoaderFactory>>( | 
 |           partition->GetNetworkContext(), std::move(params)); | 
 | } | 
 |  | 
 | void NavigationURLLoaderImpl::RecordReceivedResponseUkmForOutermostMainFrame() { | 
 |   FrameTreeNode* frame_tree_node = | 
 |       FrameTreeNode::GloballyFindByID(frame_tree_node_id_); | 
 |   DCHECK(frame_tree_node); | 
 |  | 
 |   auto* ukm_recorder = ukm::UkmRecorder::Get(); | 
 |   ukm::builders::Navigation_ReceivedResponse builder(ukm_source_id_); | 
 |   base::TimeDelta latency = base::TimeTicks::Now() - loader_creation_time_; | 
 |   builder.SetHasAcceptCHFrame(received_accept_ch_frame_) | 
 |       .SetNavigationFirstResponseLatency(latency.InMilliseconds()); | 
 |   builder.Record(ukm_recorder->Get()); | 
 |  | 
 |   // Reset whether the ACCEPT_CH frame was received for the navigation. | 
 |   received_accept_ch_frame_ = false; | 
 | } | 
 |  | 
 | void NavigationURLLoaderImpl::MaybeRecordServiceWorkerMainResourceInfo( | 
 |     const network::mojom::URLResponseHeadPtr& head) { | 
 |   CHECK(head); | 
 |   if (!head->initial_service_worker_status.has_value() && | 
 |       !head->service_worker_router_info) { | 
 |     return; | 
 |   } | 
 |  | 
 |   ukm::builders::ServiceWorker_MainResourceLoadCompleted builder( | 
 |       ukm_source_id_); | 
 |  | 
 |   CHECK(head->initial_service_worker_status.has_value()); | 
 |   builder.SetInitialWorkerStatus( | 
 |       static_cast<int64_t>(head->initial_service_worker_status.value())); | 
 |  | 
 |   if (head->service_worker_router_info) { | 
 |     network::mojom::ServiceWorkerRouterInfo* router_info = | 
 |         head->service_worker_router_info.get(); | 
 |     if (router_info->evaluation_worker_status.has_value()) { | 
 |       builder.SetWorkerStatusOnEvaluation( | 
 |           static_cast<int64_t>(router_info->evaluation_worker_status.value())); | 
 |     } | 
 |     // Check if `matched_source_type` and `actual_source_type` exists. If | 
 |     // `matched_source_type` exists, `actual_source_type` should also exist. | 
 |     // Likewise, if `matched_source_type` does not exist, `actual_source_type` | 
 |     // should also not exist. | 
 |     CHECK_EQ(router_info->matched_source_type.has_value(), | 
 |              router_info->actual_source_type.has_value()); | 
 |     if (router_info->matched_source_type) { | 
 |       builder.SetMatchedFirstRouterSourceType( | 
 |           static_cast<int64_t>(*router_info->matched_source_type)); | 
 |       if (router_info->matched_source_type == | 
 |           network::mojom::ServiceWorkerRouterSourceType::kCache) { | 
 |         builder.SetCacheLookupTime( | 
 |             router_info->cache_lookup_time.InMilliseconds()); | 
 |       } | 
 |     } | 
 |     if (router_info->actual_source_type) { | 
 |       builder.SetActualRouterSourceType( | 
 |           static_cast<int64_t>(*router_info->actual_source_type)); | 
 |     } | 
 |     builder | 
 |         .SetRouterRuleCount(ukm::GetExponentialBucketMinForCounts1000( | 
 |             router_info->route_rule_num)) | 
 |         .SetRouterEvaluationTime( | 
 |             router_info->router_evaluation_time.InMicroseconds()); | 
 |   } | 
 |   builder.Record(ukm::UkmRecorder::Get()); | 
 | } | 
 |  | 
 | }  // namespace content |