text: // Copyright 2020 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_subresource_manager.h"
  
  #include "base/metrics/histogram_macros.h"
  #include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_prefetch_metrics_collector.h"
  #include "chrome/browser/prefetch/prefetch_proxy/prefetched_mainframe_response_container.h"
  #include "content/public/browser/browser_context.h"
  #include "content/public/browser/render_frame_host.h"
  #include "content/public/browser/render_process_host.h"
  #include "content/public/browser/web_contents.h"
  
  PrefetchProxySubresourceManager::PrefetchProxySubresourceManager(
      const GURL& url,
      std::unique_ptr<PrefetchedMainframeResponseContainer> mainframe_response)
      : url_(url), mainframe_response_(std::move(mainframe_response)) {}
  
  PrefetchProxySubresourceManager::~PrefetchProxySubresourceManager() {
    if (nsp_handle_) {
      nsp_handle_->SetObserver(nullptr);
      nsp_handle_->OnCancel();
    }
    UMA_HISTOGRAM_COUNTS_100("PrefetchProxy.Prefetch.Subresources.Quantity",
                             successfully_loaded_subresources_.size());
  }
  
  void PrefetchProxySubresourceManager::ManageNoStatePrefetch(
      std::unique_ptr<prerender::NoStatePrefetchHandle> handle,
      base::OnceClosure on_nsp_done_callback) {
    on_nsp_done_callback_ = std::move(on_nsp_done_callback);
    nsp_handle_ = std::move(handle);
    nsp_handle_->SetObserver(this);
  }
  
  std::unique_ptr<PrefetchedMainframeResponseContainer>
  PrefetchProxySubresourceManager::TakeMainframeResponse() {
    return std::move(mainframe_response_);
  }
  
  void PrefetchProxySubresourceManager::SetCreateIsolatedLoaderFactoryCallback(
      CreateIsolatedLoaderFactoryRepeatingCallback callback) {
    create_isolated_loader_factory_callback_ = std::move(callback);
  }
  
  void PrefetchProxySubresourceManager::NotifyPageNavigatedToAfterSRP() {
    DCHECK(create_isolated_loader_factory_callback_);
    // We're navigating so take the extra work off the CPU.
    if (nsp_handle_) {
      OnPrefetchStop(nsp_handle_.get());
    }
  
    was_navigated_to_after_srp_ = true;
  }
  
  void PrefetchProxySubresourceManager::OnPrefetchStop(
      prerender::NoStatePrefetchHandle* handle) {
    DCHECK_EQ(nsp_handle_.get(), handle);
  
    if (on_nsp_done_callback_) {
      std::move(on_nsp_done_callback_).Run();
    }
  
    // The handle must be canceled before it can be destroyed.
    nsp_handle_->OnCancel();
    nsp_handle_.reset();
  }
  
  bool PrefetchProxySubresourceManager::ShouldProxyForPrerenderNavigation(
      int render_process_id,
      content::ContentBrowserClient::URLLoaderFactoryType type) {
    if (type != content::ContentBrowserClient::URLLoaderFactoryType::
                    kDocumentSubResource) {
      return false;
    }
  
    if (!nsp_handle_) {
      return false;
    }
  
    content::WebContents* web_contents =
        nsp_handle_->contents()->no_state_prefetch_contents();
    if (!web_contents) {
      // This shouldn't happen, so abort the prerender just to be safe.
      OnPrefetchStop(nsp_handle_.get());
      NOTREACHED();
      return false;
    }
  
    int prerender_process_id =
        web_contents->GetMainFrame()->GetProcess()->GetID();
    if (prerender_process_id != render_process_id) {
      return false;
    }
  
    if (!create_isolated_loader_factory_callback_) {
      // This also shouldn't happen, and would imply that there is a bug in the
      // code where a prerender was triggered without having an isolated URL
      // Loader Factory callback to use. Abort the prerender just to be safe.
      OnPrefetchStop(nsp_handle_.get());
      NOTREACHED();
      return false;
    }
  
    return true;
  }
  
  bool PrefetchProxySubresourceManager::ShouldProxyForAfterSRPNavigation() const {
    return was_navigated_to_after_srp_;
  }
  
  bool PrefetchProxySubresourceManager::MaybeProxyURLLoaderFactory(
      content::RenderFrameHost* frame,
      int render_process_id,
      content::ContentBrowserClient::URLLoaderFactoryType type,
      mojo::PendingReceiver<network::mojom::URLLoaderFactory>* factory_receiver) {
    if (!ShouldProxyForPrerenderNavigation(render_process_id, type) &&
        !ShouldProxyForAfterSRPNavigation()) {
      return false;
    }
  
    auto proxied_receiver = std::move(*factory_receiver);
    mojo::PendingRemote<network::mojom::URLLoaderFactory>
        network_process_factory_remote;
    *factory_receiver =
        network_process_factory_remote.InitWithNewPipeAndPassReceiver();
  
    mojo::PendingRemote<network::mojom::URLLoaderFactory> isolated_factory_remote;
    create_isolated_loader_factory_callback_.Run(
        isolated_factory_remote.InitWithNewPipeAndPassReceiver(),
        frame->GetPendingIsolationInfoForSubresources());
  
    auto proxy = std::make_unique<PrefetchProxyProxyingURLLoaderFactory>(
        this, frame->GetFrameTreeNodeId(), std::move(proxied_receiver),
        std::move(network_process_factory_remote),
        std::move(isolated_factory_remote),
        base::BindOnce(
            &PrefetchProxySubresourceManager::RemoveProxiedURLLoaderFactory,
            weak_factory_.GetWeakPtr()),
        base::BindRepeating(
            &PrefetchProxySubresourceManager::OnSubresourceLoadSuccessful,
            weak_factory_.GetWeakPtr()));
  
    if (ShouldProxyForAfterSRPNavigation()) {
      proxy->NotifyPageNavigatedToAfterSRP(successfully_loaded_subresources_);
    }
  
    proxied_loader_factories_.emplace(std::move(proxy));
  
    return true;
  }
  
  void PrefetchProxySubresourceManager::OnSubresourceLoadSuccessful(
      const GURL& url) {
    successfully_loaded_subresources_.emplace(url);
  }
  
  void PrefetchProxySubresourceManager::NotifyProbeFailed() {
    successfully_loaded_subresources_.clear();
  }
  
  void PrefetchProxySubresourceManager::RemoveProxiedURLLoaderFactory(
      PrefetchProxyProxyingURLLoaderFactory* factory) {
    auto it = proxied_loader_factories_.find(factory);
    DCHECK(it != proxied_loader_factories_.end());
    proxied_loader_factories_.erase(it);
  }
  
  void PrefetchProxySubresourceManager::SetPrefetchMetricsCollector(
      scoped_refptr<PrefetchProxyPrefetchMetricsCollector> collector) {
    metrics_collector_ = collector;
  }
  
  void PrefetchProxySubresourceManager::OnResourceFetchComplete(
      const GURL& url,
      network::mojom::URLResponseHeadPtr head,
      const network::URLLoaderCompletionStatus& status) {
    if (!metrics_collector_)
      return;
  
    metrics_collector_->OnSubresourcePrefetched(
        /*mainframe_url=*/url_,
        /*subresource_url=*/url, std::move(head), status);
  }
  
  void PrefetchProxySubresourceManager::OnResourceNotEligible(
      const GURL& url,
      PrefetchProxyPrefetchStatus status) {
    if (!metrics_collector_)
      return;
    metrics_collector_->OnSubresourceNotEligible(
        /*mainframe_url=*/url_,
        /*subresource_url=*/url, status);
  }
  
  void PrefetchProxySubresourceManager::OnResourceThrottled(const GURL& url) {
    if (!metrics_collector_)
      return;
    metrics_collector_->OnSubresourceNotEligible(
        /*mainframe_url=*/url_,
        /*subresource_url=*/url,
        PrefetchProxyPrefetchStatus::kSubresourceThrottled);
  }
  
  void PrefetchProxySubresourceManager::OnProxyUnavailableForResource(
      const GURL& url) {
    if (!metrics_collector_)
      return;
    metrics_collector_->OnSubresourceNotEligible(
        /*mainframe_url=*/url_,
        /*subresource_url=*/url,
        PrefetchProxyPrefetchStatus::kPrefetchProxyNotAvailable);
  }
  
  void PrefetchProxySubresourceManager::OnResourceUsedFromCache(const GURL& url) {
    if (!metrics_collector_)
      return;
    metrics_collector_->OnCachedSubresourceUsed(/*mainframe_url=*/url_,
                                                /*subresource_url=*/url);
  }
  