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

#include <memory>

#include "base/byte_size.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "base/types/pass_key.h"
#include "base/unguessable_token.h"
#include "components/performance_manager/execution_context/execution_context_impl.h"
#include "components/performance_manager/graph/node_attached_data_storage.h"
#include "components/performance_manager/graph/node_base.h"
#include "components/performance_manager/graph/node_inline_data.h"
#include "components/performance_manager/graph/tracing_observer.h"
#include "components/performance_manager/public/graph/frame_node.h"
#include "components/performance_manager/public/graph/node_attached_data.h"
#include "components/performance_manager/public/mojom/coordination_unit.mojom.h"
#include "components/performance_manager/public/mojom/web_memory.mojom.h"
#include "components/performance_manager/public/render_frame_host_proxy.h"
#include "components/performance_manager/resource_attribution/cpu_measurement_data.h"
#include "content/public/browser/browsing_instance_id.h"
#include "content/public/browser/site_instance.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "third_party/blink/public/common/tokens/tokens.h"
#include "third_party/perfetto/include/perfetto/tracing/track.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace performance_manager {

class FrameNodeImplDescriber;

// Frame nodes for a tree structure that is described in
// components/performance_manager/public/graph/frame_node.h.
class FrameNodeImpl
    : public PublicNodeImpl<FrameNodeImpl, FrameNode>,
      public TypedNodeBase<FrameNodeImpl, FrameNode, FrameNodeObserver>,
      public mojom::DocumentCoordinationUnit,
      public SupportsNodeInlineData<
          execution_context::FrameExecutionContext,
          resource_attribution::SharedCPUTimeResultData,
          // Keep this last to avoid merge conflicts.
          NodeAttachedDataStorage>,
      public TracingObserver {
 public:
  static const char kDefaultPriorityReason[];

  using TypedNodeBase<FrameNodeImpl, FrameNode, FrameNodeObserver>::FromNode;

  // Construct a frame node associated with a `process_node`, a `page_node` and
  // optionally with a `parent_frame_node`. For the main frame of `page_node`
  // the `parent_frame_node` parameter should be nullptr. For <fencedframe>s,
  // and MPArch aware <webview>s,  `outer_document_for_inner_frame_root` should
  // be set to its outer document, nullptr otherwise. `render_frame_id` is the
  // routing id of the frame (from RenderFrameHost::GetRoutingID).
  FrameNodeImpl(ProcessNodeImpl* process_node,
                PageNodeImpl* page_node,
                FrameNodeImpl* parent_frame_node,
                FrameNodeImpl* outer_document_for_inner_frame_root,
                int render_frame_id,
                const blink::LocalFrameToken& frame_token,
                const perfetto::Track& tracing_track,
                content::BrowsingInstanceId browsing_instance_id,
                content::SiteInstanceGroupId site_instance_group_id,
                bool is_current,
                bool is_active);

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

  ~FrameNodeImpl() override;

  void Bind(mojo::PendingReceiver<mojom::DocumentCoordinationUnit> receiver);

  // mojom::DocumentCoordinationUnit implementation.
  void SetNetworkAlmostIdle() override;
  void SetLifecycleState(LifecycleState state) override;
  void SetHasNonEmptyBeforeUnload(bool has_nonempty_beforeunload) override;
  void SetIsAdFrame(bool is_ad_frame) override;
  void SetHadFormInteraction() override;
  void SetHadUserEdits() override;
  void OnStartedUsingWebRTC() override;
  void OnStoppedUsingWebRTC() override;
  void OnNonPersistentNotificationCreated() override;
  void OnFirstContentfulPaint(
      base::TimeDelta time_since_navigation_start) override;
  void OnWebMemoryMeasurementRequested(
      mojom::WebMemoryMeasurement::Mode mode,
      OnWebMemoryMeasurementRequestedCallback callback) override;
  void OnFreezingOriginTrialOptOut() override;

  // Partial FrameNode implementation:
  const blink::LocalFrameToken& GetFrameToken() const override;
  content::BrowsingInstanceId GetBrowsingInstanceId() const override;
  content::SiteInstanceGroupId GetSiteInstanceGroupId() const override;
  resource_attribution::FrameContext GetResourceContext() const override;
  bool IsMainFrame() const override;

  LifecycleState GetLifecycleState() const override;
  bool HasNonemptyBeforeUnload() const override;
  const GURL& GetURL() const override;
  const std::optional<url::Origin>& GetOrigin() const override;
  bool IsCurrent() const override;
  bool IsActive() const override;
  const PriorityAndReason& GetPriorityAndReason() const override;
  bool GetNetworkAlmostIdle() const override;
  bool IsAdFrame() const override;
  bool IsHoldingWebLock() const override;
  bool IsHoldingBlockingIndexedDBLock() const override;
  bool UsesWebRTC() const override;
  bool HadUserActivation() const override;
  bool HadFormInteraction() const override;
  bool HadUserEdits() const override;
  bool IsAudible() const override;
  bool IsCapturingMediaStream() const override;
  bool HasFreezingOriginTrialOptOut() const override;
  ViewportIntersection GetViewportIntersection() const override;
  Visibility GetVisibility() const override;
  bool IsIntersectingLargeArea() const override;
  bool IsRendered() const override;
  bool IsImportant() const override;
  const RenderFrameHostProxy& GetRenderFrameHostProxy() const override;
  base::ByteSize GetResidentSetEstimate() const override;
  base::ByteSize GetPrivateFootprintEstimate() const override;
  void CrossProcessSubframeRenderProcessGone() override;

  // TracingObserver implementation:
  void OnTraceSessionStart() override;

  // Getters for const properties.
  FrameNodeImpl* parent_frame_node() const;
  FrameNodeImpl* parent_or_outer_document() const;
  FrameNodeImpl* parent_or_outer_document_or_embedder() const;
  PageNodeImpl* page_node() const;
  ProcessNodeImpl* process_node() const;
  int render_frame_id() const;

  // Getters for non-const properties. These are not thread safe.
  NodeSetView<FrameNodeImpl*> child_frame_nodes() const;
  NodeSetView<PageNodeImpl*> opened_page_nodes() const;
  NodeSetView<PageNodeImpl*> embedded_page_nodes() const;
  NodeSetView<WorkerNodeImpl*> child_worker_nodes() const;

  // Setters are not thread safe.
  // Updates the IsCurrent() property on both `previous_frame_node` and
  // `current_frame_node` and sends a single notification to FrameNodeObservers.
  static void UpdateCurrentFrame(FrameNodeImpl* previous_frame_node,
                                 FrameNodeImpl* current_frame_node,
                                 GraphImpl* graph);
  void SetIsActive(bool is_active);
  void SetHadUserActivation();
  void SetIsHoldingWebLock(bool is_holding_weblock);
  void SetIsHoldingBlockingIndexedDBLock(
      bool is_holding_blocking_indexeddb_lock);
  void SetIsAudible(bool is_audible);
  void SetIsCapturingMediaStream(bool is_capturing_media_stream);
  void SetViewportIntersection(ViewportIntersection viewport_intersection);
  void SetInitialVisibility(Visibility visibility);
  void SetVisibility(Visibility visibility);
  void SetIsRendered(bool is_rendered);
  void SetIsIntersectingLargeArea(bool is_intersecting_large_area);
  void SetIsImportant(bool is_important);
  void SetResidentSetEstimate(base::ByteSize rss_estimate);
  void SetPrivateFootprintEstimate(base::ByteSize private_footprint_estimate);

  // Invoked when a navigation is committed in the frame.
  void OnNavigationCommitted(GURL url,
                             url::Origin origin,
                             bool same_document,
                             bool is_served_from_back_forward_cache);

  // Traverses the frame tree and notifies all frames that their embedding
  // primary page is about to be discarded.
  void OnPrimaryPageAboutToBeDiscarded();

  // Invoked by |worker_node| when it starts/stops being a child of this frame.
  void AddChildWorker(WorkerNodeImpl* worker_node);
  void RemoveChildWorker(WorkerNodeImpl* worker_node);

  // Invoked to set the frame priority, and the reason behind it.
  void SetPriorityAndReason(const PriorityAndReason& priority_and_reason);

  base::WeakPtr<FrameNodeImpl> GetWeakPtr();

  void SeverPageRelationshipsAndMaybeReparentForTesting() {
    SeverPageRelationshipsAndMaybeReparent();
  }

  // Implementation details below this point.

  // Invoked by opened pages when this frame is set/cleared as their opener.
  // See PageNodeImpl::(Set|Clear)OpenerFrameNode.
  void AddOpenedPage(base::PassKey<PageNodeImpl> key, PageNodeImpl* page_node);
  void RemoveOpenedPage(base::PassKey<PageNodeImpl> key,
                        PageNodeImpl* page_node);

  // Invoked by embedded pages when this frame is set/cleared as their embedder.
  // See PageNodeImpl::(Set|Clear)EmbedderFrameNodeAndEmbeddingType.
  void AddEmbeddedPage(base::PassKey<PageNodeImpl> key,
                       PageNodeImpl* page_node);
  void RemoveEmbeddedPage(base::PassKey<PageNodeImpl> key,
                          PageNodeImpl* page_node);

  // Returns true if the mojom::DocumentCoordinationUnit connection is bound.
  bool IsDocumentCoordinationUnitBoundForTesting() const;

 private:
  friend class FrameNodeImplDescriber;
  friend class ProcessNodeImpl;

  // Rest of FrameNode implementation. These are private so that users of the
  // impl use the private getters rather than the public interface.
  const FrameNode* GetParentFrameNode() const override;
  const FrameNode* GetParentOrOuterDocument() const override;
  const FrameNode* GetParentOrOuterDocumentOrEmbedder() const override;
  const PageNode* GetPageNode() const override;
  const ProcessNode* GetProcessNode() const override;
  NodeSetView<const FrameNode*> GetChildFrameNodes() const override;
  NodeSetView<const PageNode*> GetOpenedPageNodes() const override;
  NodeSetView<const PageNode*> GetEmbeddedPageNodes() const override;
  NodeSetView<const WorkerNode*> GetChildWorkerNodes() const override;

  // Properties associated with a Document, which are reset when a
  // different-document navigation is committed in the frame.
  // LINT.IfChange(document_prop)
  struct DocumentProperties {
    DocumentProperties();
    ~DocumentProperties();

    void Reset(FrameNodeImpl* frame_node, GURL url_in, url::Origin origin_in);

    // FrameNodeObserver::OnURLChanged/OnOriginChanged() is invoked when the
    // URL/origin changes. Not using ObservedProperty here to allow updating
    // both properties before notifying observers (see
    // `FrameNodeImpl::DocumentProperties::Reset` implementation).
    GURL url;
    std::optional<url::Origin> origin;

    bool has_nonempty_beforeunload = false;

    // Network is considered almost idle when there are no more than 2 network
    // connections.
    ObservedProperty::NotifiesOnlyOnChanges<
        bool,
        &FrameNodeObserver::OnNetworkAlmostIdleChanged>
        network_almost_idle{false};

    // Indicates if a form in the frame has been interacted with.
    // TODO(crbug.com/40735910): Remove this once HadUserEdits is known to cover
    // all existing cases.
    ObservedProperty::NotifiesOnlyOnChanges<
        bool,
        &FrameNodeObserver::OnHadFormInteractionChanged>
        had_form_interaction{false};

    // Indicates that the user has made edits to the page. This is a superset of
    // `had_form_interaction`, but can also represent changes to
    // `contenteditable` elements.
    ObservedProperty::
        NotifiesOnlyOnChanges<bool, &FrameNodeObserver::OnHadUserEditsChanged>
            had_user_edits{false};

    // Whether the document uses WebRTC.
    ObservedProperty::NotifiesOnlyOnChanges<
        bool,
        &FrameNodeObserver::OnFrameUsesWebRTCChanged>
        uses_web_rtc{false};

    // Whether the document is opted-out from freezing via origin trial.
    ObservedProperty::NotifiesOnlyOnChanges<
        bool,
        &FrameNodeObserver::OnFrameHasFreezingOriginTrialOptOutChanged>
        has_freezing_origin_trial_opt_out{false};
  };
  // LINT.ThenChange(//components/performance_manager/graph/frame_node_impl.cc:document_prop_reset)

  // Invoked by subframes on joining/leaving the graph.
  void AddChildFrame(FrameNodeImpl* frame_node);
  void RemoveChildFrame(FrameNodeImpl* frame_node);

  // NodeBase:
  void OnInitializingProperties() override;
  void OnInitializingEdges() override;
  void OnBeforeLeavingGraph() override;
  void OnUninitializingEdges() override;
  void CleanUpNodeState() override;

  // Helper function to sever all opened/embedded page relationships. This is
  // called before destroying the frame node in "OnBeforeLeavingGraph". Note
  // that this will reparent embedded pages to this frame's parent so that
  // tracking is maintained.
  void SeverPageRelationshipsAndMaybeReparent();

  // This is not quite the same as GetMainFrame, because there can be multiple
  // main frames while the main frame is navigating. This explicitly walks up
  // the tree to find the main frame that corresponds to this frame tree node,
  // even if it is not current.
  FrameNodeImpl* GetFrameTreeRoot() const;

  void TraceEdges();

  bool HasFrameNodeInAncestors(FrameNodeImpl* frame_node) const;
  bool HasFrameNodeInDescendants(FrameNodeImpl* frame_node) const;
  bool HasFrameNodeInTree(FrameNodeImpl* frame_node) const;

  // Sets the `is_current_` property. Returns true if its value changed as a
  // result of this call.
  bool SetIsCurrent(bool is_current);

  // Updates the inherited `IsIntersectingLargeArea()` property of this frame.
  void SetInheritedIsIntersectingLargeArea(bool is_intersecting_large_area);

  void SetIsIntersectingLargeAreaImpl(bool is_intersecting_large_area);

  mojo::Receiver<mojom::DocumentCoordinationUnit> receiver_{this};

  const raw_ptr<FrameNodeImpl, DanglingUntriaged> parent_frame_node_;
  const raw_ptr<FrameNodeImpl, DanglingUntriaged>
      outer_document_for_inner_frame_root_;
  const raw_ptr<PageNodeImpl, DanglingUntriaged> page_node_;
  const raw_ptr<ProcessNodeImpl, DanglingUntriaged> process_node_;
  // The routing id of the frame.
  const int render_frame_id_;

  // This is the unique token for this frame instance as per e.g.
  // RenderFrameHost::GetFrameToken().
  const blink::LocalFrameToken frame_token_;

  // The unique ID of the BrowsingInstance this frame belongs to. Frames in the
  // same BrowsingInstance are allowed to script each other at least
  // asynchronously (if cross-site), and sometimes synchronously (if same-site,
  // and thus same SiteInstance).
  const content::BrowsingInstanceId browsing_instance_id_;

  // The unique ID of the SiteInstanceGroup this frame belongs to. Frames in the
  // same SiteInstanceGroup are in the same process and exist as LocalFrames in
  // the same blink::FrameTree. Frames with the same |site_instance_group_id_|
  // will also have the same |browsing_instance_id_|.
  const content::SiteInstanceGroupId site_instance_group_id_;

  // A proxy object that lets the underlying RFH be safely dereferenced on the
  // UI thread.
  const RenderFrameHostProxy render_frame_host_proxy_;

  // Perfetto track that can record trace events for the page.
  const perfetto::Track tracing_track_;

  base::ScopedObservation<TracingObserverList, TracingObserver>
      tracing_observation_{this};

  NodeSet child_frame_nodes_;

  // The set of pages that have been opened by this frame.
  NodeSet opened_page_nodes_;

  // The set of pages that have been embedded by this frame.
  NodeSet embedded_page_nodes_;

  base::ByteSize resident_set_estimate_;

  base::ByteSize private_footprint_estimate_;

  // Does *not* change when a navigation is committed.
  ObservedProperty::NotifiesOnlyOnChanges<
      LifecycleState,
      &FrameNodeObserver::OnFrameLifecycleStateChanged>
      lifecycle_state_{LifecycleState::kRunning};

  // Indicates if the frame has been interacted with.
  ObservedProperty::NotifiesOnlyOnChanges<
      bool,
      &FrameNodeObserver::OnHadUserActivationChanged>
      had_user_activation_{false};

  ObservedProperty::
      NotifiesOnlyOnChanges<bool, &FrameNodeObserver::OnIsAdFrameChanged>
          is_ad_frame_{false};

  // Locks held by a frame are tracked independently from navigation
  // (specifically, a few tasks must run in the Web Lock and IndexedDB
  // subsystems after a navigation for locks to be released).
  ObservedProperty::NotifiesOnlyOnChanges<
      bool,
      &FrameNodeObserver::OnFrameIsHoldingWebLockChanged>
      is_holding_weblock_{false};
  ObservedProperty::NotifiesOnlyOnChanges<
      bool,
      &FrameNodeObserver::OnFrameIsHoldingBlockingIndexedDBLockChanged>
      is_holding_blocking_indexeddb_lock_{false};

  bool is_current_{false};
  bool is_active_{false};

  // Properties associated with a Document, which are reset when a
  // different-document navigation is committed in the frame.
  //
  // TODO(fdoray): Cleanup this once there is a 1:1 mapping between
  // RenderFrameHost and Document https://crbug.com/936696.
  DocumentProperties document_;

  // The child workers of this frame.
  NodeSet child_worker_nodes_;

  // Frame priority information. Set via ExecutionContextPriorityDecorator.
  ObservedProperty::NotifiesOnlyOnChangesWithPreviousValue<
      PriorityAndReason,
      &FrameNodeObserver::OnPriorityAndReasonChanged,
      TracedWrapper<PriorityAndReason>>
      priority_and_reason_;

  // Indicates if the frame is audible. This is tracked independently of a
  // document, and if a document swap occurs the audio stream monitor machinery
  // will keep this up to date.
  ObservedProperty::NotifiesOnlyOnChanges<
      bool,
      &FrameNodeObserver::OnIsAudibleChanged,
      TracedWrapper<bool>>
      is_audible_;

  // Indicates if the frame is capturing at least one media stream.
  ObservedProperty::NotifiesOnlyOnChanges<
      bool,
      &FrameNodeObserver::OnIsCapturingMediaStreamChanged>
      is_capturing_media_stream_{false};

  // Indicates the intersection between the frame and the viewport.
  //
  // Note that calling `GetViewportIntersection()` will always returns
  // ViewportIntersection::kIntersecting for the outermost main frame. This is
  // because it always occupies the entirety of the viewport, so there is no
  // point in tracking it.
  ObservedProperty::NotifiesOnlyOnChanges<
      ViewportIntersection,
      &FrameNodeObserver::OnViewportIntersectionChanged>
      viewport_intersection_{ViewportIntersection::kUnknown};

  // Indicates if the frame is visible. This is maintained by the
  // FrameVisibilityDecorator.
  ObservedProperty::NotifiesOnlyOnChangesWithPreviousValue<
      Visibility,
      &FrameNodeObserver::OnFrameVisibilityChanged,
      TracedWrapper<Visibility>>
      visibility_;

  // Indicates if this frame is rendered.
  bool is_rendered_ = false;

  // Indicates if this frame intersects with a large area of the viewport.
  // Defaults to true when its value is unknown.
  bool is_intersecting_large_area_ = true;

  // Indicates that SetIsIntersectingLargeArea() was called for this frame. This
  // is only called for local root frames and means this frame should not
  // inherit the value of its parent.
  bool has_is_intersecting_large_area_updates_ = false;

  ObservedProperty::
      NotifiesOnlyOnChanges<bool, &FrameNodeObserver::OnIsImportantChanged>
          is_important_{true};

  base::WeakPtrFactory<FrameNodeImpl> weak_factory_
      GUARDED_BY_CONTEXT(sequence_checker_){this};
};

}  // namespace performance_manager

#endif  // COMPONENTS_PERFORMANCE_MANAGER_GRAPH_FRAME_NODE_IMPL_H_
