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

content / public / test / test_navigation_observer.h [blame]

// Copyright 2012 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_TEST_NAVIGATION_OBSERVER_H_
#define CONTENT_PUBLIC_TEST_TEST_NAVIGATION_OBSERVER_H_

#include <map>
#include <memory>
#include <optional>

#include "base/callback_list.h"
#include "base/containers/unique_ptr_adapters.h"
#include "base/functional/callback.h"
#include "content/public/browser/child_process_host.h"
#include "content/public/test/test_utils.h"
#include "net/base/net_errors.h"
#include "net/http/http_status_code.h"
#include "third_party/blink/public/common/chrome_debug_urls.h"
#include "third_party/blink/public/mojom/navigation/navigation_initiator_activation_and_ad_status.mojom.h"
#include "url/gurl.h"

namespace content {
class NavigationHandle;
class NavigationRequest;
class WebContents;

// For browser_tests, which run on the UI thread, run a second
// MessageLoop and quit when the navigation completes loading.
class TestNavigationObserver {
 public:
  enum class WaitEvent {
    kLoadStopped,
    kNavigationFinished,
  };

  // Create and register a new TestNavigationObserver against the
  // |web_contents|.
  TestNavigationObserver(WebContents* web_contents,
                         int expected_number_of_navigations,
                         MessageLoopRunner::QuitMode quit_mode =
                             MessageLoopRunner::QuitMode::IMMEDIATE,
                         bool ignore_uncommitted_navigations = true);
  // Like above but waits for one navigation.
  explicit TestNavigationObserver(WebContents* web_contents,
                                  MessageLoopRunner::QuitMode quit_mode =
                                      MessageLoopRunner::QuitMode::IMMEDIATE,
                                  bool ignore_uncommitted_navigations = true);
  // Create and register a new TestNavigationObserver that will wait for
  // a navigation with |target_error|.
  explicit TestNavigationObserver(WebContents* web_contents,
                                  net::Error expected_target_error,
                                  MessageLoopRunner::QuitMode quit_mode =
                                      MessageLoopRunner::QuitMode::IMMEDIATE,
                                  bool ignore_uncommitted_navigations = true);

  // Create and register a new TestNavigationObserver that will wait for
  // |target_url| to complete loading or for a finished navigation to
  // |target_url|.
  explicit TestNavigationObserver(const GURL& expected_target_url,
                                  MessageLoopRunner::QuitMode quit_mode =
                                      MessageLoopRunner::QuitMode::IMMEDIATE,
                                  bool ignore_uncommitted_navigations = true);

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

  virtual ~TestNavigationObserver();

  void set_expected_initial_url(const GURL& url) {
    // Debug URLs do not go through NavigationRequest and therefore cannot be
    // used as an `expected_initial_url_`.
    DCHECK(!blink::IsRendererDebugURL(url));

    expected_initial_url_ = url;
  }

  void set_wait_event(WaitEvent event) { wait_event_ = event; }

  // Runs a nested run loop and blocks until the expected number of navigations
  // stop loading or |target_url| has loaded.
  void Wait();

  // Runs a nested run loop and blocks until the expected number of navigations
  // finished or a navigation to |target_url| has finished.
  void WaitForNavigationFinished();

  // Start/stop watching newly created WebContents.
  void StartWatchingNewWebContents();
  void StopWatchingNewWebContents();

  // Makes this TestNavigationObserver an observer of all previously created
  // WebContents.
  void WatchExistingWebContents();

  // The URL of the last finished navigation (that matched URL / net error
  // filters, if set).
  const GURL& last_navigation_url() const { return last_navigation_url_; }

  // Returns true if the last finished navigation (that matched URL / net error
  // filters, if set) succeeded.
  bool last_navigation_succeeded() const { return last_navigation_succeeded_; }

  // The last navigation initiator's user activation and ad status.
  blink::mojom::NavigationInitiatorActivationAndAdStatus
  last_navigation_initiator_activation_and_ad_status() const {
    return last_navigation_initiator_activation_and_ad_status_;
  }

  // Returns the initiator origin of the last finished navigation (that matched
  // URL / net error filters, if set).
  const std::optional<url::Origin>& last_initiator_origin() const {
    return last_navigation_initiator_origin_;
  }

  // Returns the frame token of the initiator RenderFrameHost of the last
  // finished navigation. This is defined if and only if
  // last_initiator_process_id below is.
  const std::optional<blink::LocalFrameToken>& last_initiator_frame_token()
      const {
    return last_initiator_frame_token_;
  }

  // Returns the process id of the initiator RenderFrameHost of the last
  // finished navigation. This is defined if and only if
  // last_initiator_frame_token above is, and it is valid only in conjunction
  // with it.
  int last_initiator_process_id() const { return last_initiator_process_id_; }

  // Returns the net::Error origin of the last finished navigation (that matched
  // URL / net error filters, if set).
  net::Error last_net_error_code() const { return last_net_error_code_; }

  // Returns the HTTP response code of the last navigation, if applicable
  std::optional<net::HttpStatusCode> last_http_response_code() const {
    return last_http_response_code_;
  }

  // Returns the navigation entry ID of the last finished navigation (that
  // matched URL if set).
  int last_nav_entry_id() const { return last_nav_entry_id_; }

  SiteInstance* last_source_site_instance() const {
    return last_source_site_instance_.get();
  }

  ukm::SourceId next_page_ukm_source_id() const {
    return next_page_ukm_source_id_;
  }

 protected:
  // Register this TestNavigationObserver as an observer of the |web_contents|.
  void RegisterAsObserver(WebContents* web_contents);

  // Protected so that subclasses can retrieve extra information from the
  // |navigation_handle|.
  virtual void OnDidStartNavigation(NavigationHandle* navigation_handle);

  // Protected so that subclasses can retrieve extra information from the
  // |navigation_handle|.
  virtual void OnDidFinishNavigation(NavigationHandle* navigation_handle);

  // NavigationOfInterestDidFinish is called by OnDidFinishNavigation if it was
  // determined that the finished navigation is on the correct URL, in the
  // correct state, etc. This is the method that classes extending
  // TestNavigationObserver should override, if they wish to intercept data
  // carried in |navigation_handle|.
  virtual void NavigationOfInterestDidFinish(
      NavigationHandle* navigation_handle);

 private:
  class TestWebContentsObserver;

  // State of a WebContents* known to this TestNavigationObserver.
  // Move-only.
  struct WebContentsState {
    WebContentsState();

    WebContentsState(const WebContentsState& other) = delete;
    WebContentsState& operator=(const WebContentsState& other) = delete;
    WebContentsState(WebContentsState&& other);
    WebContentsState& operator=(WebContentsState&& other);

    ~WebContentsState();

    // Observes the WebContents this state has been created for and relays
    // events to the TestNavigationObserver.
    std::unique_ptr<TestWebContentsObserver> observer;

    // If true, a navigation is in progress in the WebContents.
    bool navigation_started = false;
    // If true, the last navigation that finished in this WebContents matched
    // the filter criteria (|target_url_| or |target_error_|).
    // Only relevant if a filter is configured.
    bool last_navigation_matches_filter = false;
  };

  TestNavigationObserver(WebContents* web_contents,
                         int expected_number_of_navigations,
                         const std::optional<GURL>& expected_target_url,
                         std::optional<net::Error> expected_target_error,
                         MessageLoopRunner::QuitMode quit_mode =
                             MessageLoopRunner::QuitMode::IMMEDIATE,
                         bool ignore_uncommitted_navigations = true);

  // Callbacks for WebContents-related events.
  void OnWebContentsCreated(WebContents* web_contents);
  void OnWebContentsDestroyed(TestWebContentsObserver* observer,
                              WebContents* web_contents);
  void OnNavigationEntryCommitted(
      TestWebContentsObserver* observer,
      WebContents* web_contents,
      const LoadCommittedDetails& load_details);
  void OnDidStartLoading(WebContents* web_contents);
  void OnDidStopLoading(WebContents* web_contents);
  void EventTriggered(WebContentsState* web_contents_state);

  // Returns true of |expected_initial_url_| is missing, or if it matches the
  // original URL of the NavigationRequest (stripping the initial view-source:
  // if necessary).
  bool DoesNavigationMatchExpectedInitialUrl(
      NavigationRequest* navigation_request);

  // Returns true if |target_url_| or |target_error_| is configured.
  bool HasFilter();

  // Returns the WebContentsState for |web_contents|.
  WebContentsState* GetWebContentsState(WebContents* web_contents);

  // The event that once triggered will quit the run loop.
  WaitEvent wait_event_;

  // Tracks WebContents and their loading/navigation state.
  std::map<WebContents*, WebContentsState> web_contents_state_;

  // The number of navigations that have been completed.
  int navigations_completed_;

  // The number of navigations to wait for.
  // If |target_url_| and/or |target_error_| are set, only navigations that
  // match those criteria will count towards this.
  int expected_number_of_navigations_;

  // The target URL to wait for.  If this is nullopt, any URL counts.
  const std::optional<GURL> expected_target_url_;

  // The initial URL to wait for.  If this is nullopt, any URL counts.
  std::optional<GURL> expected_initial_url_;

  // The net error of the finished navigation to wait for.
  // If this is nullopt, any net::Error counts.
  const std::optional<net::Error> expected_target_error_;

  // Whether to ignore navigations that finish but don't commit.
  bool ignore_uncommitted_navigations_;

  // The url of the navigation that last committed.
  GURL last_navigation_url_;

  // True if the last navigation succeeded.
  bool last_navigation_succeeded_;

  // The last navigation initiator's user activation and ad status.
  blink::mojom::NavigationInitiatorActivationAndAdStatus
      last_navigation_initiator_activation_and_ad_status_ =
          blink::mojom::NavigationInitiatorActivationAndAdStatus::
              kDidNotStartWithTransientActivation;

  // True if we have called EventTriggered following wait. This is used for
  // internal checks-- we expect certain conditions to be valid until we call
  // EventTriggered at which point we reset state.
  bool was_event_consumed_ = false;

  // The initiator origin of the last navigation.
  std::optional<url::Origin> last_navigation_initiator_origin_;

  // The frame token of the initiator frame for the last observed
  // navigation. This parameter is defined if and only if
  // |initiator_process_id_| below is.
  std::optional<blink::LocalFrameToken> last_initiator_frame_token_;

  // The process id of the initiator frame for the last observed navigation.
  // This is defined if and only if |initiator_frame_token_| above is, and it is
  // only valid in conjunction with it.
  int last_initiator_process_id_ = ChildProcessHost::kInvalidUniqueID;

  // The net error code of the last navigation.
  net::Error last_net_error_code_;

  // HTTP status code of the last navigation.
  std::optional<net::HttpStatusCode> last_http_response_code_ = std::nullopt;

  // The navigation entry ID of the last navigation.
  int last_nav_entry_id_ = 0;

  scoped_refptr<SiteInstance> last_source_site_instance_;

  // The UKM source ID of the next page.
  //
  // For prerender activations, this will retain a bit different UKM source ID
  // from usual. See NavigationHandle::GetNextPageUkmSourceId() for details.
  ukm::SourceId next_page_ukm_source_id_ = ukm::kInvalidSourceId;

  // The MessageLoopRunner used to spin the message loop.
  scoped_refptr<MessageLoopRunner> message_loop_runner_;

  base::CallbackListSubscription creation_subscription_;
};

}  // namespace content

#endif  // CONTENT_PUBLIC_TEST_TEST_NAVIGATION_OBSERVER_H_