// 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.

#ifndef CONTENT_PUBLIC_TEST_NAVIGATION_SIMULATOR_H_
#define CONTENT_PUBLIC_TEST_NAVIGATION_SIMULATOR_H_

#include <memory>
#include <string>

#include "base/memory/scoped_refptr.h"
#include "content/public/browser/navigation_throttle.h"
#include "content/public/browser/reload_type.h"
#include "net/dns/public/resolve_error_info.h"
#include "services/network/public/cpp/permissions_policy/permissions_policy_declaration.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "third_party/blink/public/mojom/loader/referrer.mojom-forward.h"
#include "ui/base/page_transition_types.h"

class GURL;

namespace net {
class IPEndPoint;
class HttpResponseHeaders;
class SSLInfo;
}  // namespace net

namespace content {

class NavigationController;
class NavigationHandle;
class NavigationThrottleRegistry;
class RenderFrameHost;
class WebContents;
struct GlobalRequestID;

// An interface for simulating a navigation in unit tests. Supports both
// renderer and browser-initiated navigations.
// Note: this should not be used in browser tests.
class NavigationSimulator {
 public:
  // Simulates a browser-initiated navigation to |url| started in
  // |web_contents| from start to commit. Returns the RenderFrameHost that
  // committed the navigation.
  static RenderFrameHost* NavigateAndCommitFromBrowser(
      WebContents* web_contents,
      const GURL& url);

  // Simulates the page reloading. Returns the RenderFrameHost that committed
  // the navigation.
  static RenderFrameHost* Reload(WebContents* web_contents);

  // Simulates a back navigation from start to commit. Returns the
  // RenderFrameHost that committed the navigation.
  static RenderFrameHost* GoBack(WebContents* web_contents);

  // Simulates a forward navigation from start to commit. Returns the
  // RenderFrameHost that committed the navigation.
  static RenderFrameHost* GoForward(WebContents* web_contents);

  // Simulates a navigation to the given offset of the web_contents navigation
  // controller, from start to finish.
  static RenderFrameHost* GoToOffset(WebContents* web_contents, int offset);

  // Simulates a renderer-initiated navigation to |url| started in
  // |render_frame_host| from start to commit. Returns the RenderFramehost that
  // committed the navigation.
  static RenderFrameHost* NavigateAndCommitFromDocument(
      const GURL& original_url,
      RenderFrameHost* render_frame_host);

  // Simulates a failed browser-initiated navigation to |url| started in
  // |web_contents| from start to commit. Returns the RenderFrameHost that
  // committed the error page for the navigation, or nullptr if the navigation
  // error did not result in an error page.
  static RenderFrameHost* NavigateAndFailFromBrowser(WebContents* web_contents,
                                                     const GURL& url,
                                                     int net_error_code);

  // Simulates the page reloading and failing. Returns the RenderFrameHost that
  // committed the error page for the navigation, or nullptr if the navigation
  // error did not result in an error page.
  static RenderFrameHost* ReloadAndFail(WebContents* web_contents,
                                        int net_error_code);

  // Simulates a failed back navigation. Returns the RenderFrameHost that
  // committed the error page for the navigation, or nullptr if the navigation
  // error did not result in an error page.
  static RenderFrameHost* GoBackAndFail(WebContents* web_contents,
                                        int net_error_code);

  // Simulates a failed offset navigation. Returns the RenderFrameHost that
  // committed the error page for the navigation, or nullptr if the navigation
  // error did not result in an error page.
  static RenderFrameHost* GoToOffsetAndFail(WebContents* web_contents,
                                            int offset,
                                            int net_error_code);

  // Simulates a failed renderer-initiated navigation to |url| started in
  // |render_frame_host| from start to commit. Returns the RenderFramehost that
  // committed the error page for the navigation, or nullptr if the navigation
  // error did not result in an error page.
  static RenderFrameHost* NavigateAndFailFromDocument(
      const GURL& original_url,
      int net_error_code,
      RenderFrameHost* render_frame_host);

  // ---------------------------------------------------------------------------

  // All the following methods should be used when more precise control over the
  // navigation is needed.

  // Creates a NavigationSimulator that will be used to simulate a
  // browser-initiated navigation to |original_url| started in |contents|.
  static std::unique_ptr<NavigationSimulator> CreateBrowserInitiated(
      const GURL& original_url,
      WebContents* contents);

  // Creates a NavigationSimulator that will be used to simulate a history
  // navigation to one of the |web_contents|'s navigation controller |offset|.
  // E.g. offset -1 for back navigations and 1 for forward navigations. If
  // |is_renderer_initiated| is true, the navigation will simulate a history
  // navigation initiated via JS.
  static std::unique_ptr<NavigationSimulator> CreateHistoryNavigation(
      int offset,
      WebContents* web_contents,
      bool is_renderer_initiated);

  // Creates a NavigationSimulator that will be used to simulate a
  // renderer-initiated navigation to |original_url| started by
  // |render_frame_host|.
  static std::unique_ptr<NavigationSimulator> CreateRendererInitiated(
      const GURL& original_url,
      RenderFrameHost* render_frame_host);

  // Creates a NavigationSimulator for an already-started navigation via
  // LoadURL / Reload / GoToOffset / history.GoBack() scripts, etc. Can be used
  // to drive the navigation to completion.
  static std::unique_ptr<NavigationSimulator> CreateFromPending(
      NavigationController& controller);

  virtual ~NavigationSimulator() = default;

  // --------------------------------------------------------------------------

  // The following functions should be used to simulate events happening during
  // a navigation.
  //
  // Example of usage for a successful renderer-initiated navigation:
  //   unique_ptr<NavigationSimulator> simulator =
  //       NavigationSimulator::CreateRendererInitiated(
  //           original_url, render_frame_host);
  //   simulator->SetTransition(ui::PAGE_TRANSITION_LINK);
  //   simulator->Start();
  //   for (GURL redirect_url : redirects)
  //     simulator->Redirect(redirect_url);
  //   simulator->Commit();
  //
  // Example of usage for a failed renderer-initiated navigation:
  //   unique_ptr<NavigationSimulator> simulator =
  //       NavigationSimulator::CreateRendererInitiated(
  //           original_url, render_frame_host);
  //   simulator->SetTransition(ui::PAGE_TRANSITION_LINK);
  //   simulator->Start();
  //   for (GURL redirect_url : redirects)
  //     simulator->Redirect(redirect_url);
  //   simulator->Fail(net::ERR_TIMED_OUT);
  //   simulator->CommitErrorPage();
  //
  // Example of usage for a same-page renderer-initiated navigation:
  //   unique_ptr<NavigationSimulator> simulator =
  //       NavigationSimulator::CreateRendererInitiated(
  //           original_url, render_frame_host);
  //   simulator->CommitSameDocument();
  //
  // Example of usage for a renderer-initiated navigation which is cancelled by
  // a throttle upon redirecting. Note that registering the throttle is done
  // elsewhere:
  //   unique_ptr<NavigationSimulator> simulator =
  //       NavigationSimulator::CreateRendererInitiated(
  //           original_url, render_frame_host);
  //   simulator->SetTransition(ui::PAGE_TRANSITION_LINK);
  //   simulator->Start();
  //   simulator->Redirect(redirect_url);
  //   EXPECT_EQ(NavigationThrottle::CANCEL,
  //             simulator->GetLastThrottleCheckResult());

  // Simulates the start of the navigation.
  virtual void Start() = 0;

  // Simulates a redirect to |new_url| for the navigation.
  virtual void Redirect(const GURL& new_url) = 0;

  // Simulates receiving the navigation response and choosing a final
  // RenderFrameHost to commit it.
  virtual void ReadyToCommit() = 0;

  // Simulates the commit of the navigation in the RenderFrameHost.
  virtual void Commit() = 0;

  // Simulates the commit of a navigation or an error page aborting.
  virtual void AbortCommit() = 0;

  // Simulates aborting the navigation from the renderer, e.g. window.stop(),
  // before it was committed in the renderer.
  // Note: this is only valid for renderer-initiated navigations.
  virtual void AbortFromRenderer() = 0;

  // Simulates the navigation failing with the error code |error_code|.
  // IMPORTANT NOTE: This is simulating a network connection error and implies
  // we do not get a response. Error codes like 204 are not properly managed.
  virtual void Fail(int error_code) = 0;

  // Simulates the commit of an error page following a navigation failure.
  virtual void CommitErrorPage() = 0;

  // Simulates the commit of a same-document navigation, ie fragment navigations
  // or pushState/popState navigations.
  virtual void CommitSameDocument() = 0;

  // Must be called after the simulated navigation or an error page has
  // committed. Returns the RenderFrameHost the navigation committed in.
  virtual RenderFrameHost* GetFinalRenderFrameHost() = 0;

  // Only used if AutoAdvance is turned off. Will wait until the current stage
  // of the navigation is complete.
  virtual void Wait() = 0;

  // Returns true if the navigation is deferred waiting for navigation throttles
  // to complete.
  virtual bool IsDeferred() = 0;

  // Returns true if a previous operation has caused the navigation to fail.
  virtual bool HasFailed() = 0;

  // --------------------------------------------------------------------------

  // The following functions are used to specify the parameters of the
  // navigation.

  // The following parameters are constant during the navigation and may only be
  // specified before calling |Start|.
  //
  // Sets the frame that initiated the navigation. Should only be specified for
  // renderer-initiated navigations. For now this frame must belong to the same
  // process as the frame that is navigating.
  //
  // TODO(crbug.com/40127276): Support cross-process initiators here by
  // using NavigationRequest::CreateBrowserInitiated() (like
  // RenderFrameProxyHost does) for the navigation.
  virtual void SetInitiatorFrame(RenderFrameHost* initiator_frame_host) = 0;
  virtual void SetTransition(ui::PageTransition transition) = 0;
  virtual void SetHasUserGesture(bool has_user_gesture) = 0;
  virtual void SetNavigationInputStart(
      base::TimeTicks navigation_input_start) = 0;
  virtual void SetNavigationStart(base::TimeTicks navigation_start) = 0;
  // Note: ReloadType should only be specified for browser-initiated
  // navigations.
  virtual void SetReloadType(ReloadType reload_type) = 0;

  // Sets the HTTP method for the navigation.
  virtual void SetMethod(const std::string& method) = 0;

  // Sets whether this navigation originated as the result of a form submission.
  virtual void SetIsFormSubmission(bool is_form_submission) = 0;

  // The following parameters can change during redirects. They should be
  // specified before calling |Start| if they need to apply to the navigation to
  // the original url. Otherwise, they should be specified before calling
  // |Redirect|.
  virtual void SetReferrer(blink::mojom::ReferrerPtr referrer) = 0;

  // The following parameters can change at any point until the page fails or
  // commits. They should be specified before calling |Fail| or |Commit|.
  virtual void SetSocketAddress(const net::IPEndPoint& remote_endpoint) = 0;

  // Pretend the navigation response is served from cache.
  virtual void SetWasFetchedViaCache(bool was_fetched_via_cache) = 0;

  // Pretend the navigation is against an inner response of a signed exchange.
  virtual void SetIsSignedExchangeInnerResponse(
      bool is_signed_exchange_inner_response) = 0;

  // Simulate receiving Permissions-Policy headers.
  virtual void SetPermissionsPolicyHeader(
      network::ParsedPermissionsPolicy permissions_policy_header) = 0;

  // Provides the contents mime type to be set at commit. It should be
  // specified before calling |ReadyToCommit| or |Commit|.
  virtual void SetContentsMimeType(const std::string& contents_mime_type) = 0;

  // Provides the response headers that should be received during the next
  // |Redirect| call. These headers will only be applied to the next
  // |Redirect| call, and will be reset afterwards.
  virtual void SetRedirectHeaders(
      scoped_refptr<net::HttpResponseHeaders> redirect_headers) = 0;

  // Provides the response headers received during |ReadyToCommit| specified
  // before calling |ReadyToCommit| or |Commit|.
  // Note that the mime type should be specified separately with
  // |SectContentsMimeType|.
  virtual void SetResponseHeaders(
      scoped_refptr<net::HttpResponseHeaders> response_headers) = 0;

  // Provides the response body received during |ReadyToCommit|. Must be
  // specified before calling |ReadyToCommit| or |Commit|.
  virtual void SetResponseBody(
      mojo::ScopedDataPipeConsumerHandle response_body) = 0;

  // Whether or not the NavigationSimulator automatically advances the
  // navigation past the stage requested (e.g. through asynchronous
  // NavigationThrottles). Defaults to true. Useful for testing throttles which
  // defer the navigation.
  //
  // If the test sets this to false, it should follow up any calls that result
  // in throttles deferring the navigation with a call to Wait().
  virtual void SetAutoAdvance(bool auto_advance) = 0;

  // Sets the ResolveErrorInfo to be set on the URLLoaderCompletionStatus.
  virtual void SetResolveErrorInfo(
      const net::ResolveErrorInfo& resolve_error_info) = 0;

  // Sets the SSLInfo to be set on the response. This should be called before
  // Commit().
  virtual void SetSSLInfo(const net::SSLInfo& ssl_info) = 0;

  // Sets the DNS aliases to be received in the URLResponseHead. The aliases
  // are what would be read from DNS CNAME records, and the alias chain should
  // be preserved in reverse order, from canonical name (i.e. address record
  // name) through to query name. This method should be called before Commit().
  virtual void SetResponseDnsAliases(std::vector<std::string> aliases) = 0;

  // Sets whether preload Link headers were received via Early Hints responses
  // during the navigation.
  virtual void SetEarlyHintsPreloadLinkHeaderReceived(bool received) = 0;

  // --------------------------------------------------------------------------

  // Gets the last throttle check result computed by the navigation throttles.
  // It is an error to call this before Start() is called.
  virtual NavigationThrottle::ThrottleCheckResult
  GetLastThrottleCheckResult() = 0;

  // Returns the NavigationHandle associated with the navigation being
  // simulated. It is an error to call this before Start() or after the
  // navigation has finished (successfully or not).
  virtual NavigationHandle* GetNavigationHandle() = 0;

  // Returns the NavigationThrottleRegistry associated with the navigation
  // being simulated. It is an error to call this before Start() or after the
  // navigation has finished (successfully or not) as the runner does not
  // outlive the NavigationHandle.
  virtual NavigationThrottleRegistry& GetNavigationThrottleRegistry() = 0;

  // Returns the GlobalRequestID for the simulated navigation request. Can be
  // invoked after the navigation has completed. It is an error to call this
  // before the simulated navigation has completed its WillProcessResponse
  // callback.
  virtual GlobalRequestID GetGlobalRequestID() = 0;

  // By default, committing a navigation will also simulate the load stopping.
  // In the cases where the NavigationSimulator needs to navigate but still be
  // in a loading state, use the functions below.

  // If |keep_loading| is true, maintain the loading state after committing.
  virtual void SetKeepLoading(bool keep_loading) = 0;

  // Simulate the ongoing load stopping successfully.
  virtual void StopLoading() = 0;

 private:
  // This interface should only be implemented inside content.
  friend class NavigationSimulatorImpl;
  NavigationSimulator() {}
};

}  // namespace content

#endif  // CONTENT_PUBLIC_TEST_NAVIGATION_SIMULATOR_H_
