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
  211
  212
  213
  214
  215
  216
  217
  218
  219
  220
  221
  222
  223
  224
  225
  226
  227
  228
  229
  230
  231
  232
  233
  234
  235
  236
  237
  238
  239
  240
  241
  242
  243
  244
  245
  246
  247
  248
  249
  250
  251
  252
  253
  254
  255
  256
  257
  258
  259
  260
  261
  262
  263
  264
  265
  266
  267
  268
  269
  270
  271
  272
  273
  274
  275
  276
  277
  278
  279
  280
  281
  282
  283
  284
  285
  286
  287
  288
  289
  290
  291
  292
  293
  294
  295
  296
  297
  298
  299
  300
  301
  302
  303
  304
  305
  306
  307
  308
  309
  310
  311
  312
  313
  314
  315
  316
  317
  318
  319
  320
  321
  322
  323
  324
  325
  326
  327
  328
  329
  330
  331
  332

content / public / test / prerender_test_util.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_PUBLIC_TEST_PRERENDER_TEST_UTIL_H_
#define CONTENT_PUBLIC_TEST_PRERENDER_TEST_UTIL_H_

#include "base/functional/callback.h"
#include "base/test/scoped_feature_list.h"
#include "content/public/browser/preloading_trigger_type.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/isolated_world_ids.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/preloading_test_util.h"
#include "content/public/test/test_navigation_observer.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/speculation_rules/speculation_rules.mojom.h"
#include "url/gurl.h"

namespace content {

namespace test {

class PrerenderHostRegistryObserverImpl;

// The PrerenderHostRegistryObserver permits waiting for a host to be created
// for a given URL.
class PrerenderHostRegistryObserver {
 public:
  explicit PrerenderHostRegistryObserver(WebContents& web_contents);
  ~PrerenderHostRegistryObserver();
  PrerenderHostRegistryObserver(const PrerenderHostRegistryObserver&) = delete;
  PrerenderHostRegistryObserver& operator=(
      const PrerenderHostRegistryObserver&) = delete;

  // Returns immediately if |url| was ever triggered before. Otherwise blocks
  // on a RunLoop until a prerender of |url| is triggered.
  void WaitForTrigger(const GURL& url);

  // Blocks on a RunLoop until a next prerender is triggered. Returns a URL of
  // the prerender.
  GURL WaitForNextTrigger();

  // Invokes |callback| immediately if |url| was ever triggered before.
  // Otherwise invokes |callback| when a prerender for |url| is triggered.
  void NotifyOnTrigger(const GURL& url, base::OnceClosure callback);

  // Returns a set of URLs that have been triggered so far.
  base::flat_set<GURL> GetTriggeredUrls() const;

 private:
  std::unique_ptr<PrerenderHostRegistryObserverImpl> impl_;
};

class PrerenderHostObserverImpl;

// The PrerenderHostObserver permits listening for host activation and
// destruction
class PrerenderHostObserver {
 public:
  // Begins observing the given PrerenderHost immediately. DCHECKs if |host_id|
  // does not identify a live PrerenderHost.
  PrerenderHostObserver(WebContents& web_contents, FrameTreeNodeId host_id);

  // Will start observing a PrerenderHost for |url| as soon as it is
  // triggered.
  PrerenderHostObserver(WebContents& web_contents, const GURL& url);

  ~PrerenderHostObserver();
  PrerenderHostObserver(const PrerenderHostObserver&) = delete;
  PrerenderHostObserver& operator=(const PrerenderHostObserver&) = delete;

  // Returns immediately if the PrerenderHost was already activated, otherwise
  // spins a RunLoop until the observed host is activated.
  void WaitForActivation();

  // Returns immediately if the PrerenderHost has already received headers,
  // otherwise spins a RunLoop until the observed host receives headers.
  void WaitForHeaders();

  // Returns immediately if the PrerenderHost was already destroyed, otherwise
  // spins a RunLoop until the observed host is destroyed.
  void WaitForDestroyed();

  // True if the PrerenderHost was activated to be the primary page.
  bool was_activated() const;

 private:
  std::unique_ptr<PrerenderHostObserverImpl> impl_;
};

// This waits for creation of PrerenderHost and then returns its host id.
class PrerenderHostCreationWaiter {
 public:
  PrerenderHostCreationWaiter();
  ~PrerenderHostCreationWaiter() = default;

  FrameTreeNodeId Wait();

 private:
  base::RunLoop run_loop_;
  FrameTreeNodeId created_host_id_;
};

// Enables appropriate features for Prerender2.
// This also disables the memory requirement of Prerender2 on Android so that
// test can run on any bot.
class ScopedPrerenderFeatureList {
 public:
  ScopedPrerenderFeatureList();
  ScopedPrerenderFeatureList(const ScopedPrerenderFeatureList&) = delete;
  ScopedPrerenderFeatureList& operator=(const ScopedPrerenderFeatureList&) =
      delete;

 private:
  base::test::ScopedFeatureList feature_list_;
};

// Browser tests can use this class to more conveniently leverage prerendering.
class PrerenderTestHelper {
 public:
  explicit PrerenderTestHelper(const WebContents::Getter& fn);
  ~PrerenderTestHelper();
  PrerenderTestHelper(const PrerenderTestHelper&) = delete;
  PrerenderTestHelper& operator=(const PrerenderTestHelper&) = delete;

  // This installs a network monitor on the http server. Be sure to call this
  // before starting the server. This is typically done from SetUp, but it is
  // fine to call from SetUpOnMainThread if ordering constraints make that
  // impossible (eg, if the test helper is created later to avoid problematic
  // creation/destruction relative to other ScopedFeatureLists or if the fixture
  // creates test server after SetUp).
  void RegisterServerRequestMonitor(
      net::test_server::EmbeddedTestServer* http_server);
  void RegisterServerRequestMonitor(
      net::test_server::EmbeddedTestServer& test_server);

  // Attempts to lookup the host for the given |url|. Returns an invalid frame
  // id upon failure.
  static FrameTreeNodeId GetHostForUrl(WebContents& web_contents,
                                       const GURL& url);
  FrameTreeNodeId GetHostForUrl(const GURL& url);

  // Returns whether the registry holds the handler for prerender-into-new-tab.
  bool HasNewTabHandle(FrameTreeNodeId host_id);

  // Waits until a prerender has finished loading.
  //
  // - `WaitForPrerenderLoadCompletion()` waits for
  //   `PrerenderHost::LoadingOutcome::kLoadingCompleted`. Note: this may not be
  //   called when the load fails (e.g. because it was blocked by a
  //   NavigationThrottle, or the WebContents is destroyed).
  // - `WaitForPrerenderLoadCancellation()` waits for
  //    `PrerenderHost::LoadingOutcome::kPrerenderingCancelled`.
  //
  // If the prerender doesn't yet exist, these will wait until it is triggered.
  static void WaitForPrerenderLoadCompletion(WebContents& web_contents,
                                             const GURL& url);
  void WaitForPrerenderLoadCompletion(const GURL& url);
  void WaitForPrerenderLoadCompletion(FrameTreeNodeId host_id);
  static void WaitForPrerenderLoadCancellation(WebContents& web_contents,
                                               const GURL& url);
  void WaitForPrerenderLoadCancellation(const GURL& url);

  // Adds <script type="speculationrules"> in the current main frame and waits
  // until the completion of prerendering. Returns the id of the resulting
  // prerendering host.
  FrameTreeNodeId AddPrerender(const GURL& prerendering_url,
                               int32_t world_id = ISOLATED_WORLD_ID_GLOBAL);
  FrameTreeNodeId AddPrerender(
      const GURL& prerendering_url,
      std::optional<blink::mojom::SpeculationEagerness> eagerness,
      const std::string& target_hint,
      int32_t world_id = ISOLATED_WORLD_ID_GLOBAL);
  FrameTreeNodeId AddPrerender(
      const GURL& prerendering_url,
      std::optional<blink::mojom::SpeculationEagerness> eagerness,
      std::optional<std::string> no_vary_search_hint,
      const std::string& target_hint,
      int32_t world_id = ISOLATED_WORLD_ID_GLOBAL);
  // AddPrerenderAsync() is the same as AddPrerender(), but does not wait until
  // the completion of prerendering.
  void AddPrerenderAsync(const GURL& prerendering_url,
                         int32_t world_id = ISOLATED_WORLD_ID_GLOBAL);
  void AddPrerendersAsync(
      const std::vector<GURL>& prerendering_urls,
      std::optional<blink::mojom::SpeculationEagerness> eagerness,
      const std::string& target_hint,
      int32_t world_id = ISOLATED_WORLD_ID_GLOBAL);
  void AddPrerendersAsync(
      const std::vector<GURL>& prerendering_urls,
      std::optional<blink::mojom::SpeculationEagerness> eagerness,
      std::optional<std::string> no_vary_search_hint,
      const std::string& target_hint,
      int32_t world_id = ISOLATED_WORLD_ID_GLOBAL);

  void AddPrefetchAsync(const GURL& prefetch_url);

  // Starts prerendering and returns a PrerenderHandle that should be kept alive
  // until prerender activation. Note that it returns before the completion of
  // the prerendering navigation.
  std::unique_ptr<PrerenderHandle> AddEmbedderTriggeredPrerenderAsync(
      const GURL& prerendering_url,
      PreloadingTriggerType trigger_type,
      const std::string& embedder_histogram_suffix,
      ui::PageTransition page_transition);

  // This navigates, but does not activate, the prerendered page.
  void NavigatePrerenderedPage(FrameTreeNodeId host_id, const GURL& url);

  // This cancels the prerendered page.
  void CancelPrerenderedPage(FrameTreeNodeId host_id);

  // Navigates the primary page to the URL and waits until the completion of
  // the navigation.
  //
  // Navigations that could activate a prerendered page should use this function
  // instead of the NavigateToURL() test helper. This is because the test helper
  // could access a navigating frame being destroyed during activation and fail.
  static void NavigatePrimaryPage(WebContents& web_contents, const GURL& url);
  void NavigatePrimaryPage(const GURL& url);

  // Navigates the primary page to the URL but does not wait until the
  // completion of the navigation. Instead it returns a
  // content::TestNavigationObserver.
  static std::unique_ptr<content::TestNavigationObserver>
  NavigatePrimaryPageAsync(WebContents& web_contents, const GURL& url);
  std::unique_ptr<content::TestNavigationObserver> NavigatePrimaryPageAsync(
      const GURL& url);

  // Opens a new window without an opener on the primary page of `web_contents`.
  // This is intended for activating a prerendered page initiated for a new
  // window.
  static void OpenNewWindowWithoutOpener(WebContents& web_contents,
                                         const GURL& url);

  // Confirms that, internally, appropriate subframes report that they are
  // prerendering (and that each frame tree type is kPrerender).
  [[nodiscard]] ::testing::AssertionResult VerifyPrerenderingState(
      const GURL& url);

  // Returns RenderFrameHost corresponding to `host_id` or `url`.
  static RenderFrameHost* GetPrerenderedMainFrameHost(WebContents& web_contents,
                                                      FrameTreeNodeId host_id);
  static RenderFrameHost* GetPrerenderedMainFrameHost(WebContents& web_contents,
                                                      const GURL& url);
  RenderFrameHost* GetPrerenderedMainFrameHost(FrameTreeNodeId host_id);
  RenderFrameHost* GetPrerenderedMainFrameHost(const GURL& url);

  int GetRequestCount(const GURL& url);
  net::test_server::HttpRequest::HeaderMap GetRequestHeaders(const GURL& url);

  // Waits until the request count for `url` reaches `count`.
  void WaitForRequest(const GURL& url, int count);

  // Generates the histogram name by appending the trigger type and the embedder
  // suffix to the base name.
  std::string GenerateHistogramName(const std::string& histogram_base_name,
                                    content::PreloadingTriggerType trigger_type,
                                    const std::string& embedder_suffix);

  // Updates the settings in PreloadingConfigOverride.
  void SetHoldback(PreloadingType preloading_type,
                   PreloadingPredictor predictor,
                   bool holdback);
  void SetHoldback(std::string_view preloading_type,
                   std::string_view predictor,
                   bool holdback);

 private:
  void MonitorResourceRequest(const net::test_server::HttpRequest& request);

  WebContents* GetWebContents();

  // Counts of requests sent to the server. Keyed by path (not by full URL)
  // because the host part of the requests is translated ("a.test" to
  // "127.0.0.1") before the server handles them.
  // This is accessed from the UI thread and `EmbeddedTestServer::io_thread_`.
  std::map<std::string, int> request_count_by_path_ GUARDED_BY(lock_);
  std::map<std::string, net::test_server::HttpRequest::HeaderMap>
      request_headers_by_path_ GUARDED_BY(lock_);
  ScopedPrerenderFeatureList feature_list_;
  base::OnceClosure monitor_callback_ GUARDED_BY(lock_);
  base::Lock lock_;
  PreloadingConfigOverride preloading_config_override_;
  WebContents::Getter get_web_contents_fn_;
};

// This test delegate is used for prerender-tests, in order to support
// prerendering going through the WebContentsDelegate.
class ScopedPrerenderWebContentsDelegate : public WebContentsDelegate {
 public:
  explicit ScopedPrerenderWebContentsDelegate(WebContents& web_contents);

  ~ScopedPrerenderWebContentsDelegate() override;

  // WebContentsDelegate override.
  PreloadingEligibility IsPrerender2Supported(
      WebContents& web_contents) override;

 private:
  base::WeakPtr<WebContents> web_contents_;
};

// This test delegate is used for link preview tests, in order to check
// whether the delegate receives `InitiatePreview` function call.
class MockLinkPreviewWebContentsDelegate : public WebContentsDelegate {
 public:
  MockLinkPreviewWebContentsDelegate();

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

  ~MockLinkPreviewWebContentsDelegate() override;

  MOCK_METHOD(void,
              InitiatePreview,
              (WebContents & web_contents, const GURL& url),
              (override));
};

}  // namespace test

}  // namespace content

#endif  // CONTENT_PUBLIC_TEST_PRERENDER_TEST_UTIL_H_