1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208
  209
  210

content / browser / loader / navigation_early_hints_manager.h [blame]

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CONTENT_BROWSER_LOADER_NAVIGATION_EARLY_HINTS_MANAGER_H_
#define CONTENT_BROWSER_LOADER_NAVIGATION_EARLY_HINTS_MANAGER_H_

#include <optional>

#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/functional/callback_forward.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "content/common/content_export.h"
#include "content/public/browser/frame_tree_node_id.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/isolation_info.h"
#include "net/base/request_priority.h"
#include "net/url_request/referrer_policy.h"
#include "services/network/public/cpp/url_loader_completion_status.h"
#include "services/network/public/mojom/early_hints.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "url/gurl.h"

namespace blink {
class ThrottlingURLLoader;
}  // namespace blink

namespace network {
namespace mojom {
class NetworkContext;
}  // namespace mojom

struct ResourceRequest;
class SharedURLLoaderFactory;
}  // namespace network

namespace content {

class BrowserContext;
class StoragePartition;

// Contains parameters to create NavigationEarlyHintsManager.
struct CONTENT_EXPORT NavigationEarlyHintsManagerParams {
  NavigationEarlyHintsManagerParams(
      const url::Origin& origin,
      net::IsolationInfo isolation_info,
      mojo::Remote<network::mojom::URLLoaderFactory> loader_factory);
  ~NavigationEarlyHintsManagerParams();

  NavigationEarlyHintsManagerParams(NavigationEarlyHintsManagerParams&&);
  NavigationEarlyHintsManagerParams& operator=(
      NavigationEarlyHintsManagerParams&&);

  NavigationEarlyHintsManagerParams(const NavigationEarlyHintsManagerParams&) =
      delete;
  NavigationEarlyHintsManagerParams& operator=(
      const NavigationEarlyHintsManagerParams&) = delete;

  url::Origin origin;
  net::IsolationInfo isolation_info;
  mojo::Remote<network::mojom::URLLoaderFactory> loader_factory;
};

// Handles 103 Early Hints responses for navigation. Responsible for resource
// hints in Early Hints responses. Created when the first 103 response is
// received and owned by NavigationURLLoaderImpl until the final response to the
// navigation request is received. NavigationURLLoaderImpl transfers the
// ownership of this instance to RenderFrameHostImpl via NavigationRequest when
// the navigation is committed so that this can outlive NavigationURLLoaderImpl
// until inflight preloads finish.
class CONTENT_EXPORT NavigationEarlyHintsManager {
 public:
  // Contains results of a preload request.
  struct CONTENT_EXPORT PreloadedResource {
    PreloadedResource();
    ~PreloadedResource();
    PreloadedResource(const PreloadedResource&);
    PreloadedResource& operator=(const PreloadedResource&);

    // Completion error code. Set only when network request is completed.
    std::optional<int> error_code;
    // Optional CORS error details.
    std::optional<network::CorsErrorStatus> cors_error_status;
    // True when the preload was canceled. When true, the response was already
    // in the disk cache.
    bool was_canceled = false;
  };
  using PreloadedResources = base::flat_map<GURL, PreloadedResource>;

  NavigationEarlyHintsManager(BrowserContext& browser_context,
                              StoragePartition& storage_partition,
                              FrameTreeNodeId frame_tree_node_id,
                              NavigationEarlyHintsManagerParams params);

  ~NavigationEarlyHintsManager();

  NavigationEarlyHintsManager(const NavigationEarlyHintsManager&) = delete;
  NavigationEarlyHintsManager& operator=(const NavigationEarlyHintsManager&) =
      delete;
  NavigationEarlyHintsManager(NavigationEarlyHintsManager&&) = delete;
  NavigationEarlyHintsManager& operator=(NavigationEarlyHintsManager&&) =
      delete;

  // Handles an Early Hints response. Can be called multiple times during a
  // navigation. When `early_hints` contains a preload Link header, starts
  // preloading it if preloading hasn't started for the same URL.
  void HandleEarlyHints(network::mojom::EarlyHintsPtr early_hints,
                        const network::ResourceRequest& request_for_navigation);

  // True when at least one preload or preconnect Link header was received via
  // Early Hints responses for main frame navigation.
  bool WasResourceHintsReceived() const;

  std::vector<GURL> TakePreloadedResourceURLs();

  // True when there are at least one inflight preloads.
  bool HasInflightPreloads() const;

  std::optional<base::TimeTicks> first_early_hints_receive_time() const {
    return first_early_hints_receive_time_;
  }

  void WaitForPreloadsFinishedForTesting(
      base::OnceCallback<void(PreloadedResources)> callback);

  void SetNetworkContextForTesting(
      network::mojom::NetworkContext* network_context);

 private:
  FRIEND_TEST_ALL_PREFIXES(NavigationEarlyHintsManagerTest, PreloadPriority);
  class PreloadURLLoaderClient;

  struct PreconnectEntry;

  network::mojom::NetworkContext* GetNetworkContext();

  void MaybePreconnect(const network::mojom::LinkHeaderPtr& link);

  void MaybePreloadHintedResource(
      const network::mojom::LinkHeaderPtr& link,
      const network::ResourceRequest& request_for_navigation,
      const std::vector<network::mojom::ContentSecurityPolicyPtr>&
          content_security_policies,
      net::ReferrerPolicy referrer_policy);

  // Determines whether resource hints like preload and preconnect should be
  // handled or not.
  bool ShouldHandleResourceHints(const network::mojom::LinkHeaderPtr& link);

  void OnPreloadComplete(const GURL& url, const PreloadedResource& result);

  static net::RequestPriority CalculateRequestPriority(
      const network::mojom::LinkHeaderPtr& link);

  const raw_ref<BrowserContext> browser_context_;
  const raw_ref<StoragePartition> storage_partition_;
  const FrameTreeNodeId frame_tree_node_id_;
  mojo::Remote<network::mojom::URLLoaderFactory> loader_factory_;
  // This needs to be declared last because it holds a pointer on
  // `loader_factory`, and thus needs to be destroyed before factory gets
  // destroyed.
  scoped_refptr<network::SharedURLLoaderFactory> shared_loader_factory_;
  const url::Origin origin_;
  const net::IsolationInfo isolation_info_;

  base::flat_set<PreconnectEntry> preconnect_entries_;

  struct InflightPreload {
    InflightPreload(std::unique_ptr<blink::ThrottlingURLLoader> loader,
                    std::unique_ptr<PreloadURLLoaderClient> client);
    ~InflightPreload();
    InflightPreload(const InflightPreload&) = delete;
    InflightPreload& operator=(const InflightPreload&) = delete;
    InflightPreload(InflightPreload&&) = delete;
    InflightPreload& operator=(InflightPreload&&) = delete;

    // `loader` holds a raw_ptr on `client`, so it needs to be declared last to
    // avoid holding a dangling reference to `client` at destruction.
    std::unique_ptr<PreloadURLLoaderClient> client;
    std::unique_ptr<blink::ThrottlingURLLoader> loader;
  };
  // Using flat_map because the number of preloads are expected to be small.
  // Early Hints preloads should be requested for critical subresources such as
  // style sheets and fonts.
  base::flat_map<GURL, std::unique_ptr<InflightPreload>> inflight_preloads_;

  PreloadedResources preloaded_resources_;

  std::vector<GURL> preloaded_urls_;

  // Set to true when HandleEarlyHints() is called for the first time. Used to
  // ignore following responses.
  std::optional<base::TimeTicks> first_early_hints_receive_time_;
  // Set to true when preload or preconnect Link headers are received. Used for
  // metrics recording.
  bool was_resource_hints_received_ = false;

  base::OnceCallback<void(PreloadedResources)>
      preloads_completion_callback_for_testing_;

  raw_ptr<network::mojom::NetworkContext, DanglingUntriaged>
      network_context_for_testing_ = nullptr;
};

}  // namespace content

#endif  // CONTENT_BROWSER_LOADER_NAVIGATION_EARLY_HINTS_MANAGER_H_