// Copyright 2015 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_PAGE_LOAD_METRICS_BROWSER_METRICS_WEB_CONTENTS_OBSERVER_H_
#define COMPONENTS_PAGE_LOAD_METRICS_BROWSER_METRICS_WEB_CONTENTS_OBSERVER_H_

#include <map>
#include <memory>
#include <set>
#include <string_view>
#include <vector>

#include "base/memory/raw_ptr.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/time/time.h"
#include "components/page_load_metrics/common/page_end_reason.h"
#include "components/page_load_metrics/common/page_load_metrics.mojom-forward.h"
#include "components/page_load_metrics/common/page_load_timing.h"
#include "content/public/browser/auction_result.h"
#include "content/public/browser/render_frame_host_receiver_set.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"
#include "net/cookies/canonical_cookie.h"
#include "services/network/public/mojom/fetch_api.mojom-forward.h"
#include "third_party/blink/public/common/input/web_input_event.h"
#include "third_party/blink/public/mojom/loader/resource_load_info.mojom-forward.h"
#include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom-forward.h"
#include "third_party/blink/public/mojom/use_counter/metrics/webdx_feature.mojom.h"

namespace content {
class NavigationHandle;
class RenderFrameHost;
}  // namespace content

namespace page_load_metrics {

class MetricsLifecycleObserver;
class PageLoadMetricsEmbedderInterface;
class PageLoadMetricsMemoryTracker;
class PageLoadMetricsObserverDelegate;
class PageLoadMetricsObserverInterface;
class PageLoadTracker;
enum class StorageType;
struct UserInitiatedInfo;

// MetricsWebContentsObserver tracks page loads and loading metrics
// related data based on IPC messages received from a
// MetricsRenderFrameObserver.
class MetricsWebContentsObserver
    : public content::WebContentsObserver,
      public content::WebContentsUserData<MetricsWebContentsObserver>,
      public content::RenderWidgetHost::InputEventObserver,
      public mojom::PageLoadMetrics {
 public:
  // Record a set of WebFeatures or WebDXFeatures directly from the browser
  // process. This should only be used for features that were detected
  // browser-side; features sources from the renderer should go via
  // MetricsRenderFrameObserver.
  static void RecordFeatureUsage(
      content::RenderFrameHost* render_frame_host,
      const std::vector<blink::mojom::WebFeature>& features);
  static void RecordFeatureUsage(content::RenderFrameHost* render_frame_host,
                                 blink::mojom::WebFeature feature);
  static void RecordFeatureUsage(
      content::RenderFrameHost* render_frame_host,
      const std::vector<blink::mojom::WebDXFeature>& features);
  static void RecordFeatureUsage(content::RenderFrameHost* render_frame_host,
                                 blink::mojom::WebDXFeature feature);

  // Note that the returned metrics is owned by the web contents.
  static MetricsWebContentsObserver* CreateForWebContents(
      content::WebContents* web_contents,
      std::unique_ptr<PageLoadMetricsEmbedderInterface> embedder_interface);

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

  ~MetricsWebContentsObserver() override;

  // Binds a Mojo receiver to the instance associated with the RenderFrameHost.
  static void BindPageLoadMetrics(
      mojo::PendingAssociatedReceiver<mojom::PageLoadMetrics> receiver,
      content::RenderFrameHost* rfh);

  // Any visibility changes that occur after this method should be ignored since
  // they are just clean up prior to destroying the WebContents instance.
  void WebContentsWillSoonBeDestroyed();

  // content::WebContentsObserver implementation:
  void DidStartNavigation(
      content::NavigationHandle* navigation_handle) override;
  void ReadyToCommitNavigation(
      content::NavigationHandle* navigation_handle) override;
  void DidFinishNavigation(
      content::NavigationHandle* navigation_handle) override;
  void DidRedirectNavigation(
      content::NavigationHandle* navigation_handle) override;
  void DidUpdateNavigationHandleTiming(
      content::NavigationHandle* navigation_handle) override;
  void NavigationStopped() override;
  void OnInputEvent(const content::RenderWidgetHost& widget,
                    const blink::WebInputEvent& event,
                    input::InputEventSource source) override;
  void OnVisibilityChanged(content::Visibility visibility) override;
  void PrimaryMainFrameRenderProcessGone(
      base::TerminationStatus status) override;
  void RenderFrameHostChanged(content::RenderFrameHost* old_host,
                              content::RenderFrameHost* new_host) override;
  void FrameDeleted(content::FrameTreeNodeId frame_tree_node_id) override;
  void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;
  void MediaStartedPlaying(
      const content::WebContentsObserver::MediaPlayerInfo& video_type,
      const content::MediaPlayerId& id) override;
  void WebContentsDestroyed() override;
  void ResourceLoadComplete(
      content::RenderFrameHost* render_frame_host,
      const content::GlobalRequestID& request_id,
      const GURL& original_url,
      const blink::mojom::ResourceLoadInfo& resource_load_info) override;
  void DidLoadResourceFromMemoryCache(
      content::RenderFrameHost* render_frame_host,
      const GURL& url,
      const std::string& mime_type,
      network::mojom::RequestDestination request_destination) override;
  void FrameReceivedUserActivation(
      content::RenderFrameHost* render_frame_host) override;
  void FrameDisplayStateChanged(content::RenderFrameHost* render_frame_host,
                                bool is_display_none) override;
  void FrameSizeChanged(content::RenderFrameHost* render_frame_host,
                        const gfx::Size& frame_size) override;
  void OnCookiesAccessed(content::NavigationHandle* navigation,
                         const content::CookieAccessDetails& details) override;
  void OnCookiesAccessed(content::RenderFrameHost* rfh,
                         const content::CookieAccessDetails& details) override;

  void OnStorageAccessed(content::RenderFrameHost* rfh,
                         const GURL& url,
                         const GURL& first_party_url,
                         bool blocked_by_policy,
                         StorageType storage_type);

  // These methods are forwarded from the MetricsNavigationThrottle.
  void WillProcessNavigationResponse(
      content::NavigationHandle* navigation_handle);

  // Flush any buffered metrics, as part of the metrics subsystem persisting
  // metrics as the application goes into the background. The application may be
  // killed at any time after this method is invoked without further
  // notification.
  void FlushMetricsOnAppEnterBackground();

  // Returns the delegate for the current committed primary page load, required
  // for `MetricsLifecycleObserver`s.
  const PageLoadMetricsObserverDelegate& GetDelegateForCommittedLoad();

  // Returns the delegate for the committed load if one is being tracked. This
  // method is a safer alternative to
  // `GetDelegateForCommittedLoad()` for callers that may be operating on a
  // WebContents where page load metrics are not being actively tracked.
  //
  // Unlike `GetDelegateForCommittedLoad()`, which will CHECK-fail if called at
  // an invalid time (e.g., before a primary navigation has committed or on a
  // page type that is not tracked), this method will return a nullptr.
  //
  // Callers must check the returned pointer for null before using it.
  const PageLoadMetricsObserverDelegate* GetDelegateForCommittedLoadOrNull();

  // Returns the embedder interface. Public for testing.
  PageLoadMetricsEmbedderInterface* GetEmbedderInterfaceForTesting() const {
    return embedder_interface_.get();
  }

  // Register / unregister `MetricsLifecycleObserver`s.
  void AddLifecycleObserver(MetricsLifecycleObserver* observer);
  void RemoveLifecycleObserver(MetricsLifecycleObserver* observer);

  // public only for testing
  void OnTimingUpdated(
      content::RenderFrameHost* render_frame_host,
      mojom::PageLoadTimingPtr timing,
      mojom::FrameMetadataPtr metadata,
      const std::vector<blink::UseCounterFeature>& new_features,
      const std::vector<mojom::ResourceDataUpdatePtr>& resources,
      mojom::FrameRenderDataUpdatePtr render_data,
      mojom::CpuTimingPtr cpu_timing,
      std::vector<mojom::EventTimingPtr> event_timings,
      const std::optional<blink::SubresourceLoadMetrics>&
          subresource_load_metrics,
      std::vector<mojom::SoftNavigationMetricsPtr> soft_navigation_metrics,
      std::vector<mojom::LargestContentfulPaintTimingPtr>
          soft_largest_contentful_paint,
      std::vector<mojom::CustomUserTimingMarkPtr> user_timings,
      mojom::FontLoadingMetricsPtr font_loading_metrics);

  void OnCustomUserTimingUpdated(content::RenderFrameHost* rfh,
                                 mojom::CustomUserTimingMarkPtr custom_timing);

  // Called when a `SharedStorageWorkletHost` is created for `rfh`.
  void OnSharedStorageWorkletHostCreated(content::RenderFrameHost* rfh);

  // Called when `sharedStorage.selectURL()` is called for some frame on a page
  // whose main frame is `main_rfh`.
  void OnSharedStorageSelectURLCalled(content::RenderFrameHost* main_rfh);

  // Returns the time this MetricsWebContentsObserver was created.
  base::TimeTicks GetCreated();

  // Retrieves the PageLoadMetricsObserverInterface matching `name` for the
  // specified `render_frame_host`. Returns null if the observer is not found.
  base::WeakPtr<PageLoadMetricsObserverInterface> GetMetricsObserver(
      content::RenderFrameHost* render_frame_host,
      const char* name);

  base::WeakPtr<MetricsWebContentsObserver> AsWeakPtr() {
    return weak_ptr_factory_.GetWeakPtr();
  }

 protected:
  // Protected rather than private so that derived test classes can call
  // constructor.
  MetricsWebContentsObserver(
      content::WebContents* web_contents,
      std::unique_ptr<PageLoadMetricsEmbedderInterface> embedder_interface);

 private:
  friend class content::WebContentsUserData<MetricsWebContentsObserver>;
  friend class MetricsLifeCycleObserver;

  // Gets the PageLoadTracker associated with `rfh` if it exists, or nullptr
  // otherwise.
  //
  // Don't use GetPageLoadTrackerLegacy in new code. See also the comment around
  // implementation.
  // TODO(crbug.com/40216775): Remove this.
  PageLoadTracker* GetPageLoadTrackerLegacy(content::RenderFrameHost* rfh);
  PageLoadTracker* GetPageLoadTracker(content::RenderFrameHost* rfh);
  // Gets the alive PageLoadTracker corresponding to the nearest ancestral page
  // if it exists, or nullptr otherwise.
  //
  // Consider to use this instead of GetPageLoadTracker if
  //
  // - There is a race and the target PageLoadTracker can be deleted before
  //   receiving a event; and
  // - PageLoadTracker forwards the event unconditionally with respect to
  //   ObservePolicy.
  PageLoadTracker* GetAncestralAlivePageLoadTracker(
      content::RenderFrameHost* rfh);

  PageLoadTracker* GetPageLoadTrackerIfValid(
      content::RenderFrameHost* render_frame_host);

  // Gets the memory tracker for the BrowserContext if it exists, or nullptr
  // otherwise. The tracker measures per-frame memory usage by V8.
  PageLoadMetricsMemoryTracker* GetMemoryTracker() const;

  void DidStartNavigationImpl(content::NavigationHandle* navigation_handle);

  // page_load_metrics::mojom::PageLoadMetrics implementation.
  void UpdateTiming(
      mojom::PageLoadTimingPtr timing,
      mojom::FrameMetadataPtr metadata,
      const std::vector<blink::UseCounterFeature>& new_features,
      std::vector<mojom::ResourceDataUpdatePtr> resources,
      mojom::FrameRenderDataUpdatePtr render_data,
      mojom::CpuTimingPtr cpu_timing,
      std::vector<mojom::EventTimingPtr> event_timings,
      const std::optional<blink::SubresourceLoadMetrics>&
          subresource_load_metrics,
      std::vector<mojom::SoftNavigationMetricsPtr> soft_navigation_metrics,
      std::vector<mojom::LargestContentfulPaintTimingPtr>
          soft_largest_contentful_paint,
      std::vector<mojom::CustomUserTimingMarkPtr> user_timings,
      mojom::FontLoadingMetricsPtr font_loading_metrics) override;
  void AddCustomUserTiming(
      mojom::CustomUserTimingMarkPtr custom_timing) override;

  // Common part for UpdateThroughput and OnTimingUpdated.
  bool DoesTimingUpdateHaveError(PageLoadTracker* tracker);

  void HandleFailedNavigationForTrackedLoad(
      content::NavigationHandle* navigation_handle,
      std::unique_ptr<PageLoadTracker> tracker);

  void HandleCommittedNavigationForTrackedLoad(
      content::NavigationHandle* navigation_handle,
      std::unique_ptr<PageLoadTracker> tracker);

  void HandleCommittedNavigationForPrerendering(
      content::NavigationHandle* navigation_handle,
      std::unique_ptr<PageLoadTracker> tracker);

  void FinalizeCurrentlyCommittedLoad(
      content::NavigationHandle* newly_committed_navigation,
      PageLoadTracker* newly_committed_navigation_tracker);

  // Return a PageLoadTracker (either provisional or committed) that matches the
  // given request attributes, or nullptr if there are no matching
  // PageLoadTrackers.
  PageLoadTracker* GetTrackerOrNullForRequest(
      const content::GlobalRequestID& request_id,
      content::RenderFrameHost* render_frame_host_or_null,
      network::mojom::RequestDestination request_destination,
      base::TimeTicks creation_time);

  // Notify all loads, provisional and committed, that we performed an action
  // that might abort them.
  void NotifyPageEndAllLoads(PageEndReason page_end_reason,
                             UserInitiatedInfo user_initiated_info);
  void NotifyPageEndAllLoadsWithTimestamp(PageEndReason page_end_reason,
                                          UserInitiatedInfo user_initiated_info,
                                          base::TimeTicks timestamp,
                                          bool is_certainly_browser_timestamp);

  // Register / Unregister input event callback to given RenderFrameHost
  void RegisterInputEventObserver(content::RenderFrameHost* host);
  void UnregisterInputEventObserver(content::RenderFrameHost* host);

  // Notify aborted provisional loads that a new navigation occurred. This is
  // used for more consistent attribution tracking for aborted provisional
  // loads. This method returns the provisional load that was likely aborted
  // by this navigation, to help instantiate the new PageLoadTracker.
  std::unique_ptr<PageLoadTracker> NotifyAbortedProvisionalLoadsNewNavigation(
      content::NavigationHandle* new_navigation,
      UserInitiatedInfo user_initiated_info);

  // Whether metrics should be tracked, and a PageLoadTracker should be created,
  // for the given main frame navigation.
  bool ShouldTrackMainFrameNavigation(
      content::NavigationHandle* navigation_handle) const;

  // Determines if metrics should be collected for a given URL scheme.
  // This is used for both navigation and resource timing updates.
  // If this returns false, the navigation to the URL will not be tracked, and
  // timing updates for resources loaded from the URL will not be propagated to
  // metrics observers.
  bool ShouldTrackScheme(std::string_view scheme) const;

  bool ShouldTrackSchemeForNonWebUI(std::string_view scheme) const;

  void OnBrowserFeatureUsage(
      content::RenderFrameHost* render_frame_host,
      const std::vector<blink::UseCounterFeature>& new_features);

  // Before deleting PageLoadTracker, check if we need to keep it alive as the
  // page is stored in back-forward cache. The page can either be restored later
  // (we will be notified via DidFinishNavigation and NavigationHandle::
  // IsServedFromBackForwardCache) or will be evicted from the cache (we will be
  // notified via RenderFrameDeleted).
  void MaybeStorePageLoadTrackerForBackForwardCache(
      content::NavigationHandle* next_navigation_handle,
      std::unique_ptr<PageLoadTracker> page_load_tracker);

  // Tries to move a PageLoadTracker from `inactive_pages_` to
  // `primary_page_`, when a navigation activates a back/forward-cached or
  // prerendered page. Returns true if `primary_page_` is updated.
  // Note that FencedFrames is not supported by back/forward-cache and
  // prerendering and this method doesn't support handling both containing
  // FencedFrames.
  bool MaybeActivatePageLoadTracker(
      content::NavigationHandle* navigation_handle);

  // Notifies `tracker` about cookie read or write.
  void OnCookiesAccessedImpl(PageLoadTracker& tracker,
                             const content::CookieAccessDetails& details);

  // True if the web contents is currently in the foreground.
  bool in_foreground_;

  // The PageLoadTrackers must be deleted before the `embedder_interface_`,
  // because they hold a pointer to the `embedder_interface_`.
  std::unique_ptr<PageLoadMetricsEmbedderInterface> embedder_interface_;

  // This map tracks all of the navigations ongoing that are not committed
  // yet. Once a navigation is committed, it moves from the map to
  // `primary_page_`, `active_pages_, or `inactive_pages_`. Note that a
  // PageLoadTracker's NavigationHandle is only valid until commit time, when we
  // remove it from the map.
  std::map<content::NavigationHandle*, std::unique_ptr<PageLoadTracker>>
      provisional_loads_;

  // Tracks aborted provisional loads for a little bit longer than usual (one
  // more navigation commit at the max), in order to better understand how the
  // navigation failed. This is because most provisional loads are destroyed
  // and vanish before we get signal about what caused the abort (new
  // navigation, stop button, etc.).
  std::vector<std::unique_ptr<PageLoadTracker>> aborted_provisional_loads_;

  // This stores the PageLoadTracker for the primary page. GetPageLoadTracker()
  // is available to find a PageLoadTracker for non-primary pages.
  std::unique_ptr<PageLoadTracker> primary_page_;

  // This stores the PageLoadTracker for non-primary pages, such as
  // FencedFrames, or MPArch based Portals in the future.
  base::flat_map<content::RenderFrameHost*, std::unique_ptr<PageLoadTracker>>
      active_pages_;

  // This stores the PageLoadTracker for each main frame of inactive pages,
  // including pages in the back/forward cache and prerendered pages. (The main
  // frame of the active page is in `primary_page_`.)
  base::flat_map<content::RenderFrameHost*, std::unique_ptr<PageLoadTracker>>
      inactive_pages_;

  // Has the MWCO observed at least one navigation?
  bool has_navigated_;

  // Is the main frame a WebUI page?
  bool main_frame_is_webui_ = false;

  base::ObserverList<MetricsLifecycleObserver> lifecycle_observers_;
  content::RenderFrameHostReceiverSet<mojom::PageLoadMetrics>
      page_load_metrics_receivers_;

  bool web_contents_will_soon_be_destroyed_ = false;

  base::TimeTicks created_;

  base::WeakPtrFactory<MetricsWebContentsObserver> weak_ptr_factory_{this};

  WEB_CONTENTS_USER_DATA_KEY_DECL();
};

}  // namespace page_load_metrics

#endif  // COMPONENTS_PAGE_LOAD_METRICS_BROWSER_METRICS_WEB_CONTENTS_OBSERVER_H_
