// 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_PAGE_NODE_IMPL_H_
#define COMPONENTS_PERFORMANCE_MANAGER_GRAPH_PAGE_NODE_IMPL_H_

#include <memory>
#include <optional>
#include <string>

#include "base/byte_size.h"
#include "base/containers/enum_set.h"
#include "base/containers/flat_set.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "base/types/pass_key.h"
#include "base/types/token_type.h"
#include "base/unguessable_token.h"
#include "build/build_config.h"
#include "components/performance_manager/decorators/page_aggregator_data.h"
#include "components/performance_manager/decorators/page_load_tracker_decorator_data.h"
#include "components/performance_manager/decorators/site_data_node_data.h"
#include "components/performance_manager/freezing/frozen_data.h"
#include "components/performance_manager/graph/node_attached_data_storage.h"
#include "components/performance_manager/graph/node_base.h"
#include "components/performance_manager/public/graph/page_node.h"
#include "components/performance_manager/resource_attribution/cpu_measurement_data.h"
#include "components/performance_manager/scenarios/loading_scenario_data.h"
#include "content/public/browser/web_contents.h"
#include "third_party/blink/public/mojom/favicon/favicon_url.mojom.h"
#include "third_party/perfetto/include/perfetto/tracing/track.h"
#include "url/gurl.h"

namespace performance_manager {

class FrameNodeImpl;
class FrozenFrameAggregator;

// The starting state of various boolean properties of the PageNode.
enum class PagePropertyFlag {
  kIsVisible,  // initializes PageNode::IsVisible()
  kMin = kIsVisible,
  kIsAudible,            // initializes PageNode::IsAudible()
  kHasPictureInPicture,  // initializes PageNode::HasPictureInPicture()
  kIsOffTheRecord,       // initializes PageNode::IsOffTheRecord()
  kMax = kIsOffTheRecord,
};
using PagePropertyFlags = base::
    EnumSet<PagePropertyFlag, PagePropertyFlag::kMin, PagePropertyFlag::kMax>;

class PageNodeImpl
    : public PublicNodeImpl<PageNodeImpl, PageNode>,
      public TypedNodeBase<PageNodeImpl, PageNode, PageNodeObserver>,
      public SupportsNodeInlineData<
          PageLoadTrackerDecoratorData,
          PageAggregatorData,
          SiteDataNodeData,
          FrozenData,
          LoadingScenarioPageFrameCounts,
          resource_attribution::SharedCPUTimeResultData,
          // Keep this last to avoid merge conflicts.
          NodeAttachedDataStorage> {
 public:
  using PassKey = base::PassKey<PageNodeImpl>;

  using TypedNodeBase<PageNodeImpl, PageNode, PageNodeObserver>::FromNode;

  PageNodeImpl(base::WeakPtr<content::WebContents> web_contents,
               const content::WebContents::UniqueToken& page_token,
               const base::UnguessableToken& browser_context_id,
               const GURL& visible_url,
               PagePropertyFlags initial_properties,
               base::TimeTicks visibility_change_time,
               const perfetto::Track& tracing_track);

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

  ~PageNodeImpl() override;

  // Partial PageNode implementation:
  const base::UnguessableToken& GetBrowserContextID() const override;
  resource_attribution::PageContext GetResourceContext() const override;
  PageType GetType() const override;
  bool IsFocused() const override;
  bool IsVisible() const override;
  base::TimeTicks GetLastVisibilityChangeTime() const override;
  bool IsAudible() const override;
  std::optional<base::TimeDelta> GetTimeSinceLastAudibleChange() const override;
  bool HasPictureInPicture() const override;
  bool HasFreezingOriginTrialOptOut() const override;
  bool IsOffTheRecord() const override;
  LoadingState GetLoadingState() const override;
  ukm::SourceId GetUkmSourceID() const override;
  LifecycleState GetLifecycleState() const override;
  bool IsHoldingWebLock() const override;
  bool IsHoldingBlockingIndexedDBLock() const override;
  bool UsesWebRTC() const override;
  int64_t GetNavigationID() const override;
  const std::string& GetContentsMimeType() const override;
  std::optional<blink::mojom::PermissionStatus>
  GetNotificationPermissionStatus() const override;
  base::TimeDelta GetTimeSinceLastNavigation() const override;
  const GURL& GetMainFrameUrl() const override;
  base::ByteSize EstimateMainFramePrivateFootprintSize() const override;
  bool HadFormInteraction() const override;
  bool HadUserEdits() const override;
  base::WeakPtr<content::WebContents> GetWebContents() const override;
  base::ByteSize EstimateResidentSetSize() const override;
  base::ByteSize EstimatePrivateFootprintSize() const override;
  base::WeakPtr<PageNode> GetWeakPtr() override;
  base::WeakPtr<const PageNode> GetWeakPtr() const override;

  // Returns the unique token for the page node. This function can be called
  // from any thread.
  const content::WebContents::UniqueToken& page_token() const {
    return page_token_;
  }

  // Initializes the page type. The current type must be kUnknown.
  void SetType(PageType type);

  void SetIsFocused(bool is_focused);
  void SetIsVisible(bool is_visible);
  void SetIsAudible(bool is_audible);
  void SetHasPictureInPicture(bool has_picture_in_picture);
  void SetLoadingState(LoadingState loading_state);
  void SetUkmSourceId(ukm::SourceId ukm_source_id);
  void OnFaviconUpdated(blink::mojom::FaviconUpdateReason reason);
  void OnTitleUpdated();
  void OnAboutToBeDiscarded(base::WeakPtr<PageNode> new_page_node);
  // Set main frame information of a restored page before the first navigation
  // is committed.
  void SetMainFrameRestoredState(
      const GURL& url,
      blink::mojom::PermissionStatus notification_permission_status);
  // Invoked when a main frame navigation is committed.
  void OnMainFrameNavigationCommitted(
      bool same_document,
      base::TimeTicks navigation_committed_time,
      int64_t navigation_id,
      const GURL& url,
      const std::string& contents_mime_type,
      std::optional<blink::mojom::PermissionStatus>
          notification_permission_status);
  // While notification permission status is most often updated on main frame
  // navigation, it can also be updated independently from main frame navigation
  // when the user grants/revokes the permission.
  void OnNotificationPermissionStatusChange(
      blink::mojom::PermissionStatus permission_status);

  // Accessors.
  FrameNodeImpl* opener_frame_node() const;
  FrameNodeImpl* embedder_frame_node() const;
  FrameNodeImpl* primary_main_frame_node() const;
  NodeSetView<FrameNodeImpl*> main_frame_nodes() const;

  // Invoked to set/clear the opener of this page.
  void SetOpenerFrameNode(FrameNodeImpl* opener);
  void ClearOpenerFrameNode();

  // Invoked to set/clear the embedder of this page.
  void SetEmbedderFrameNode(FrameNodeImpl* embedder);
  void ClearEmbedderFrameNode();

  void set_has_nonempty_beforeunload(bool has_nonempty_beforeunload);

  void SetLifecycleStateForTesting(LifecycleState lifecycle_state) {
    SetLifecycleState(lifecycle_state);
  }

  void SetHasFreezingOriginTrialOptOutForTesting(
      bool has_freezing_origin_trial_opt_out) {
    SetHasFreezingOriginTrialOptOut(has_freezing_origin_trial_opt_out);
  }

  void SetIsHoldingWebLockForTesting(bool is_holding_weblock) {
    SetIsHoldingWebLock(is_holding_weblock);
  }

  void SetIsHoldingBlockingIndexedDBLockForTesting(
      bool is_holding_blocking_indexeddb_lock) {
    SetIsHoldingBlockingIndexedDBLock(is_holding_blocking_indexeddb_lock);
  }

  void SetUsesWebRTCForTesting(bool uses_webrtc) { SetUsesWebRTC(uses_webrtc); }

  void SetHadFormInteractionForTesting(bool had_form_interaction) {
    SetHadFormInteraction(had_form_interaction);
  }

  void SetHadUserEditsForTesting(bool had_user_edits) {
    SetHadUserEdits(had_user_edits);
  }

  // Functions meant to be called by a FrameNodeImpl:
  void AddFrame(base::PassKey<FrameNodeImpl>, FrameNodeImpl* frame_node);
  void TraceFrame(base::PassKey<FrameNodeImpl>, FrameNodeImpl* frame_node);
  void RemoveFrame(base::PassKey<FrameNodeImpl>, FrameNodeImpl* frame_node);

  void SetLifecycleState(base::PassKey<FrozenFrameAggregator>,
                         LifecycleState lifecycle_state) {
    SetLifecycleState(lifecycle_state);
  }

  // Functions meant to be called by PageAggregatorData:
  void SetIsHoldingWebLock(base::PassKey<PageAggregatorData>,
                           bool is_holding_weblock) {
    SetIsHoldingWebLock(is_holding_weblock);
  }
  void SetIsHoldingBlockingIndexedDBLock(
      base::PassKey<PageAggregatorData>,
      bool is_holding_blocking_indexeddb_lock) {
    SetIsHoldingBlockingIndexedDBLock(is_holding_blocking_indexeddb_lock);
  }
  void SetUsesWebRTC(base::PassKey<PageAggregatorData>, bool uses_web_rtc) {
    SetUsesWebRTC(uses_web_rtc);
  }
  void SetHadFormInteraction(base::PassKey<PageAggregatorData>,
                             bool had_form_interaction) {
    SetHadFormInteraction(had_form_interaction);
  }

  void SetHadUserEdits(base::PassKey<PageAggregatorData>, bool had_user_edits) {
    SetHadUserEdits(had_user_edits);
  }

  void SetHasFreezingOriginTrialOptOut(base::PassKey<PageAggregatorData>,
                                       bool has_freezing_origin_trial_opt_out) {
    SetHasFreezingOriginTrialOptOut(has_freezing_origin_trial_opt_out);
  }

 private:
  friend class PageNodeImplDescriber;

  // Partial PageNode implementation:
  const FrameNode* GetOpenerFrameNode() const override;
  const FrameNode* GetEmbedderFrameNode() const override;
  const FrameNode* GetPrimaryMainFrameNode() const override;
  NodeSetView<const FrameNode*> GetMainFrameNodes() const override;

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

  void SetLifecycleState(LifecycleState lifecycle_state);
  void SetIsHoldingWebLock(bool is_holding_weblock);
  void SetIsHoldingBlockingIndexedDBLock(
      bool is_holding_blocking_indexeddb_lock);
  void SetUsesWebRTC(bool uses_web_rtc);
  void SetHadFormInteraction(bool had_form_interaction);
  void SetHadUserEdits(bool had_user_edits);
  void SetHasFreezingOriginTrialOptOut(bool has_freezing_origin_trial_opt_out);

  // Emits an instant event recording when the main frame url changed to `url`.
  // Also includes `navigation_id` if it's not nullopt.
  void EmitMainFrameUrlChangedEvent(
      const GURL& url,
      std::optional<int64_t> navigation_id = std::nullopt) const;

  // The WebContents associated with this page.
  const base::WeakPtr<content::WebContents> web_contents_;

  // The unique token that identifies this PageNode for the life of the browser.
  const content::WebContents::UniqueToken page_token_;

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

  // The main frame nodes of this page. There can be more than one main frame
  // in a page, among other reasons because during main frame navigation, the
  // pending navigation will coexist with the existing main frame until it's
  // committed.
  NodeSet main_frame_nodes_ GUARDED_BY_CONTEXT(sequence_checker_);

  // The total count of frames that tally up to this page.
  size_t frame_node_count_ GUARDED_BY_CONTEXT(sequence_checker_) = 0;

  // The last time at which the page visibility changed.
  base::TimeTicks visibility_change_time_ GUARDED_BY_CONTEXT(sequence_checker_);

  // The last time at which the audible property changed, or nullopt if the node
  // has never been audible.
  std::optional<base::TimeTicks> audible_change_time_
      GUARDED_BY_CONTEXT(sequence_checker_);

  // The last time at which a main frame navigation was committed.
  base::TimeTicks navigation_committed_time_
      GUARDED_BY_CONTEXT(sequence_checker_);

  // Indicates whether or not this page has a non-empty beforeunload handler.
  // This is an aggregation of the same value on each frame in the page's frame
  // tree. The aggregation is made at the moment all frames associated with a
  // page have transition to frozen.
  bool has_nonempty_beforeunload_ GUARDED_BY_CONTEXT(sequence_checker_) = false;

  // The URL the main frame last committed, or the initial URL a page was
  // initialized with. The latter case is distinguished by a zero navigation ID.
  ObservedProperty::NotifiesOnlyOnChangesWithPreviousValue<
      GURL,
      &PageNodeObserver::OnMainFrameUrlChanged>
      main_frame_url_ GUARDED_BY_CONTEXT(sequence_checker_);

  // The unique ID of the navigation handle the main frame last committed, or
  // zero if the page has never committed a navigation.
  int64_t navigation_id_ GUARDED_BY_CONTEXT(sequence_checker_) = 0;

  // The MIME type of the content associated with the last committed navigation
  // event for the main frame of this page or an empty string if the page has
  // never committed a navigation
  std::string contents_mime_type_ GUARDED_BY_CONTEXT(sequence_checker_);

  // The notification permission status for the last committed main frame
  // navigation.
  ObservedProperty::NotifiesOnlyOnChangesWithPreviousValue<
      std::optional<blink::mojom::PermissionStatus>,
      &PageNodeObserver::OnPageNotificationPermissionStatusChange>
      notification_permission_status_ GUARDED_BY_CONTEXT(sequence_checker_);

  // The unique ID of the browser context that this page belongs to.
  const base::UnguessableToken browser_context_id_;

  // The opener of this page, if there is one.
  raw_ptr<FrameNodeImpl> opener_frame_node_
      GUARDED_BY_CONTEXT(sequence_checker_) = nullptr;

  // The embedder of this page, if there is one.
  raw_ptr<FrameNodeImpl> embedder_frame_node_
      GUARDED_BY_CONTEXT(sequence_checker_) = nullptr;

  // The type of the page.
  ObservedProperty::NotifiesOnlyOnChangesWithPreviousValue<
      PageType,
      &PageNodeObserver::OnTypeChanged>
      type_ GUARDED_BY_CONTEXT(sequence_checker_){PageType::kUnknown};

  // Whether or not the page is focused. Driven by browser instrumentation.
  ObservedProperty::NotifiesOnlyOnChanges<bool,
                                          &PageNodeObserver::OnIsFocusedChanged,
                                          TracedWrapper<bool>>
      is_focused_ GUARDED_BY_CONTEXT(sequence_checker_);
  // Whether or not the page is visible. Driven by browser instrumentation.
  // Initialized on construction.
  ObservedProperty::NotifiesOnlyOnChanges<bool,
                                          &PageNodeObserver::OnIsVisibleChanged,
                                          TracedWrapper<bool>>
      is_visible_ GUARDED_BY_CONTEXT(sequence_checker_);
  // Whether or not the page is audible. Driven by browser instrumentation.
  // Initialized on construction.
  ObservedProperty::NotifiesOnlyOnChanges<bool,
                                          &PageNodeObserver::OnIsAudibleChanged,
                                          TracedWrapper<bool>>
      is_audible_ GUARDED_BY_CONTEXT(sequence_checker_);
  // Whether or not the page is displaying content in picture-in-picture. Driven
  // by browser instrumentation. Initialized on construction.
  ObservedProperty::NotifiesOnlyOnChanges<
      bool,
      &PageNodeObserver::OnHasPictureInPictureChanged>
      has_picture_in_picture_ GUARDED_BY_CONTEXT(sequence_checker_){false};
  // Whether the page is opted-out from freezing via origin trial, i.e. if any
  // of its current frames sets the origin trial.
  ObservedProperty::NotifiesOnlyOnChanges<
      bool,
      &PageNodeObserver::OnPageHasFreezingOriginTrialOptOutChanged>
      has_freezing_origin_trial_opt_out_ GUARDED_BY_CONTEXT(sequence_checker_){
          false};

  const bool is_off_the_record_;

  // The loading state. This is driven by instrumentation in the browser
  // process.
  ObservedProperty::NotifiesOnlyOnChangesWithPreviousValue<
      LoadingState,
      &PageNodeObserver::OnLoadingStateChanged,
      TracedWrapper<LoadingState>>
      loading_state_ GUARDED_BY_CONTEXT(sequence_checker_);
  // The UKM source ID associated with the URL of the main frame of this page.
  ObservedProperty::NotifiesOnlyOnChanges<
      ukm::SourceId,
      &PageNodeObserver::OnUkmSourceIdChanged>
      ukm_source_id_ GUARDED_BY_CONTEXT(sequence_checker_){
          ukm::kInvalidSourceId};
  // The lifecycle state of this page. This is aggregated from the lifecycle
  // state of each frame in the frame tree.
  ObservedProperty::NotifiesOnlyOnChanges<
      LifecycleState,
      &PageNodeObserver::OnPageLifecycleStateChanged>
      lifecycle_state_ GUARDED_BY_CONTEXT(sequence_checker_){
          LifecycleState::kRunning};
  // Indicates if at least one frame of the page is currently holding a WebLock.
  ObservedProperty::NotifiesOnlyOnChanges<
      bool,
      &PageNodeObserver::OnPageIsHoldingWebLockChanged>
      is_holding_weblock_ GUARDED_BY_CONTEXT(sequence_checker_){false};
  // Indicates if at least one frame of the page is currently holding an
  // IndexedDB lock that is blocking another client.
  ObservedProperty::NotifiesOnlyOnChanges<
      bool,
      &PageNodeObserver::OnPageIsHoldingBlockingIndexedDBLockChanged>
      is_holding_blocking_indexeddb_lock_ GUARDED_BY_CONTEXT(sequence_checker_){
          false};
  // Indicates if at least one frame of the page currently uses WebRTC.
  ObservedProperty::
      NotifiesOnlyOnChanges<bool, &PageNodeObserver::OnPageUsesWebRTCChanged>
          uses_web_rtc_ GUARDED_BY_CONTEXT(sequence_checker_){false};
  // Indicates if at least one frame of the page has received some form
  // interactions.
  ObservedProperty::NotifiesOnlyOnChanges<
      bool,
      &PageNodeObserver::OnHadFormInteractionChanged>
      had_form_interaction_ GUARDED_BY_CONTEXT(sequence_checker_){false};
  // Indicates if at least one frame of the page has received some
  // user-initiated edits.
  ObservedProperty::
      NotifiesOnlyOnChanges<bool, &PageNodeObserver::OnHadUserEditsChanged>
          had_user_edits_ GUARDED_BY_CONTEXT(sequence_checker_){false};

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

}  // namespace performance_manager

#endif  // COMPONENTS_PERFORMANCE_MANAGER_GRAPH_PAGE_NODE_IMPL_H_
