// 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 COMPONENTS_NO_STATE_PREFETCH_BROWSER_NO_STATE_PREFETCH_CONTENTS_H_
#define COMPONENTS_NO_STATE_PREFETCH_BROWSER_NO_STATE_PREFETCH_CONTENTS_H_

#include <stdint.h>

#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/no_state_prefetch/browser/no_state_prefetch_contents_delegate.h"
#include "components/no_state_prefetch/common/no_state_prefetch_canceler.mojom.h"
#include "components/no_state_prefetch/common/no_state_prefetch_final_status.h"
#include "components/no_state_prefetch/common/no_state_prefetch_origin.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/referrer.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "services/resource_coordinator/public/mojom/memory_instrumentation/memory_instrumentation.mojom-forward.h"
#include "ui/gfx/geometry/rect.h"
#include "url/origin.h"

namespace base {
class ProcessMetrics;
}

namespace content {
class BrowserContext;
class RenderViewHost;
class SessionStorageNamespace;
class WebContents;
class PreloadingAttempt;
}  // namespace content

namespace memory_instrumentation {
class GlobalMemoryDump;
}

namespace prerender {

class NoStatePrefetchManager;

class NoStatePrefetchContents
    : public content::WebContentsObserver,
      public prerender::mojom::NoStatePrefetchCanceler {
 public:
  // NoStatePrefetchContents::Create uses the currently registered Factory to
  // create the NoStatePrefetchContents. Factory is intended for testing.
  class Factory {
   public:
    Factory() = default;

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

    virtual ~Factory() = default;

    // Ownership is not transferred through this interface as
    // no_state_prefetch_manager and browser_context are stored as weak
    // pointers.
    virtual NoStatePrefetchContents* CreateNoStatePrefetchContents(
        std::unique_ptr<NoStatePrefetchContentsDelegate> delegate,
        NoStatePrefetchManager* no_state_prefetch_manager,
        content::BrowserContext* browser_context,
        const GURL& url,
        const content::Referrer& referrer,
        const std::optional<url::Origin>& initiator_origin,
        Origin origin) = 0;
  };

  class Observer {
   public:
    // Signals that the prefetch has started running.
    virtual void OnPrefetchStart(NoStatePrefetchContents* contents) {}

    // Signals that the prefetch has had its load event.
    virtual void OnPrefetchStopLoading(NoStatePrefetchContents* contents) {}

    // Signals that the prefetch has stopped running.
    // A NoStatePrefetchContents with an unset final status will always call
    // OnPrefetchStop before being destroyed.
    virtual void OnPrefetchStop(NoStatePrefetchContents* contents) {}

   protected:
    Observer() = default;
    virtual ~Observer() = 0;
  };

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

  ~NoStatePrefetchContents() override;

  // All observers of a NoStatePrefetchContents are removed after the
  // OnPrefetchStop event is sent, so there is no need to call RemoveObserver()
  // in the normal use case.
  void AddObserver(Observer* observer);
  void RemoveObserver(Observer* observer);

  bool Init();

  static Factory* CreateFactory();

  // Starts rendering the contents in the prerendered state.
  // |bounds| indicates the rectangle that the prerendered page should be in.
  // |session_storage_namespace| indicates the namespace of the launching tab
  // and is recorded for use in Matches(); the hidden WebContents is given its
  // own independent namespace. |preloading_attempt| allows to log metrics for
  // this NoStatePrefetch attempt.
  virtual void StartPrerendering(
      const gfx::Rect& bounds,
      content::SessionStorageNamespace* session_storage_namespace,
      base::WeakPtr<content::PreloadingAttempt> preloading_attempt);

  // Verifies that the prerendering is not using too many resources, and kills
  // it if not.
  void DestroyWhenUsingTooManyResources();

  content::RenderFrameHost* GetPrimaryMainFrame();

  NoStatePrefetchManager* no_state_prefetch_manager() {
    return no_state_prefetch_manager_;
  }

  const GURL& prefetch_url() const { return prefetch_url_; }
  bool has_finished_loading() const { return has_finished_loading_; }
  bool prefetching_has_started() const { return prefetching_has_started_; }

  FinalStatus final_status() const { return final_status_; }

  Origin origin() const { return origin_; }

  base::TimeTicks load_start_time() const { return load_start_time_; }

  // Indicates whether this prerendered page can be used for the provided
  // |url| and |session_storage_namespace|.
  bool Matches(
      const GURL& url,
      content::SessionStorageNamespace* session_storage_namespace) const;

  // content::WebContentsObserver implementation.
  void RenderFrameCreated(content::RenderFrameHost* render_frame_host) override;
  void DidStopLoading() override;
  void DidStartNavigation(
      content::NavigationHandle* navigation_handle) override;
  void DidRedirectNavigation(
      content::NavigationHandle* navigation_handle) override;
  void DidFinishLoad(content::RenderFrameHost* render_frame_host,
                     const GURL& validated_url) override;
  void DidFinishNavigation(
      content::NavigationHandle* navigation_handle) override;

  void PrimaryMainFrameRenderProcessGone(
      base::TerminationStatus status) override;

  // Checks that a URL may be prerendered, for one of the many redirections. If
  // the URL can not be prerendered - for example, it's an ftp URL - |this| will
  // be destroyed and false is returned. Otherwise, true is returned.
  virtual bool CheckURL(const GURL& url);

  // Adds an alias URL. If the URL can not be prerendered, |this| will be
  // destroyed and false is returned.
  bool AddAliasURL(const GURL& url);

  // The WebContents for NoStatePrefetch (may be NULL).
  content::WebContents* no_state_prefetch_contents() const {
    return no_state_prefetch_contents_.get();
  }

  // Sets the final status, calls OnDestroy and adds |this| to the
  // NoStatePrefetchManager's pending deletes list.
  void Destroy(FinalStatus reason);

  std::optional<base::DictValue> GetAsDict() const;

  // This function is not currently called in production since prerendered
  // contents are never used (only prefetch is supported), but it may be used in
  // the future: https://crbug.com/1126305
  void MarkAsUsedForTesting();

  bool prefetching_has_been_cancelled() const {
    return prefetching_has_been_cancelled_;
  }

  void AddNoStatePrefetchCancelerReceiver(
      mojo::PendingReceiver<prerender::mojom::NoStatePrefetchCanceler>
          receiver);

 protected:
  NoStatePrefetchContents(
      std::unique_ptr<NoStatePrefetchContentsDelegate> delegate,
      NoStatePrefetchManager* no_state_prefetch_manager,
      content::BrowserContext* browser_context,
      const GURL& url,
      const content::Referrer& referrer,
      const std::optional<url::Origin>& initiator_origin,
      Origin origin);

  // Set the final status for how the NoStatePrefetchContents was used. This
  // should only be called once, and should be called before the prerender
  // contents are destroyed.
  void SetFinalStatus(FinalStatus final_status);

  // These call out to methods on our Observers, using our observer_list_. Note
  // that NotifyPrefetchStop() also clears the observer list.
  void NotifyPrefetchStart();
  void NotifyPrefetchStopLoading();
  void NotifyPrefetchStop();

  std::unique_ptr<content::WebContents> CreateWebContents();

  bool prefetching_has_started_ = false;

  // Time at which we started to load the URL.  This is used to compute
  // the time elapsed from initiating a prerender until the time the
  // (potentially only partially) prerendered page is shown to the user.
  base::TimeTicks load_start_time_;

  // The WebContents for NoStatePrefetch; may be null.
  std::unique_ptr<content::WebContents> no_state_prefetch_contents_;

  // The session storage namespace id for use in matching. We must save it
  // rather than get it from the RenderViewHost since in the control group
  // we won't have a RenderViewHost.
  std::string session_storage_namespace_id_;

 private:
  class WebContentsDelegateImpl;

  // Needs to be able to call the constructor.
  friend class NoStatePrefetchContentsFactoryImpl;

  // Returns the ProcessMetrics for the render process, if it exists.
  void DidGetMemoryUsage(
      memory_instrumentation::mojom::RequestOutcome outcome,
      std::unique_ptr<memory_instrumentation::GlobalMemoryDump> dump);

  // Sets PreloadingFailureReason based on status corresponding to the
  // |attempt_|.
  void SetPreloadingFailureReason(FinalStatus status);

  // prerender::mojom::NoStatePrefetchCanceler:
  void CancelNoStatePrefetchForUnsupportedScheme() override;
  void CancelNoStatePrefetchAfterSubresourcesDiscovered() override;

  mojo::ReceiverSet<prerender::mojom::NoStatePrefetchCanceler>
      no_state_prefetch_canceler_receiver_set_;

  base::ObserverList<Observer>::UncheckedAndDanglingUntriaged observer_list_;

  // The prefetch manager owning this object.
  raw_ptr<NoStatePrefetchManager> no_state_prefetch_manager_;

  // The delegate that content embedders use to override this class's logic.
  std::unique_ptr<NoStatePrefetchContentsDelegate> delegate_;

  // The URL being prefetched.
  const GURL prefetch_url_;

  // Store the PreloadingAttempt for this NoStatePrefetch attempt. We store
  // WeakPtr as it is possible that the PreloadingAttempt is deleted before the
  // NoStatePrefetch is deleted.
  base::WeakPtr<content::PreloadingAttempt> attempt_ = nullptr;

  // The referrer.
  const content::Referrer referrer_;

  // The origin of the page requesting the prerender. Empty when the prerender
  // is browser initiated.
  const std::optional<url::Origin> initiator_origin_;

  // The browser context being used
  raw_ptr<content::BrowserContext> browser_context_;

  // A vector of URLs that this prerendered page matches against.
  // This array can contain more than element as a result of redirects,
  // such as HTTP redirects or javascript redirects.
  std::vector<GURL> alias_urls_;

  // True when the main frame has finished loading.
  bool has_finished_loading_ = false;

  FinalStatus final_status_;

  // Tracks whether or not prefetching has been cancelled by calling Destroy.
  // Used solely to prevent double deletion.
  bool prefetching_has_been_cancelled_ = false;

  // Pid of the render process associated with the RenderViewHost for this
  // object.
  base::ProcessId process_pid_;

  std::unique_ptr<WebContentsDelegateImpl> web_contents_delegate_;

  // Origin for this prerender.
  const Origin origin_;

  // The bounds of the WebView from the launching page.
  gfx::Rect bounds_;

  base::WeakPtrFactory<NoStatePrefetchContents> weak_factory_{this};
};

}  // namespace prerender

#endif  // COMPONENTS_NO_STATE_PREFETCH_BROWSER_NO_STATE_PREFETCH_CONTENTS_H_
