// Copyright 2021 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_BROWSER_SITE_INSTANCE_GROUP_H_
#define CONTENT_BROWSER_SITE_INSTANCE_GROUP_H_

#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/safe_ref.h"
#include "base/memory/scoped_refptr.h"
#include "base/observer_list.h"
#include "base/types/id_type.h"
#include "base/unguessable_token.h"
#include "content/browser/browsing_instance.h"
#include "content/browser/renderer_host/agent_scheduling_group_host.h"
#include "content/common/content_export.h"
#include "content/public/browser/browsing_instance_id.h"
#include "content/public/browser/render_process_host_observer.h"
#include "content/public/browser/site_instance.h"
#include "third_party/perfetto/include/perfetto/tracing/traced_proto.h"

namespace perfetto::protos::pbzero {
class SiteInstanceGroup;
}  // namespace perfetto::protos::pbzero

namespace content {

class BrowserContext;
class RenderProcessHost;
class SiteInstance;
class SiteInstanceImpl;
struct ChildProcessTerminationInfo;

// A SiteInstanceGroup represents one view of a browsing context group's frame
// trees within a renderer process. It provides a tuning knob, allowing the
// number of groups to vary (for process allocation and
// painting/input/scheduling decisions) without affecting the number of security
// principals that are tracked with SiteInstances.
//
// Similar to layers composing an image from many colors, a set of
// SiteInstanceGroups compose a web page from many renderer processes. Each
// group represents one renderer process' view of a browsing context group,
// containing both local frames (organized into widgets of contiguous frames)
// and proxies for frames in other groups or processes.
//
// The documents in the local frames of a group are organized into
// SiteInstances, representing an atomic group of similar origin documents that
// can access each other directly. A group contains all the documents of one or
// more SiteInstances, all belonging to the same browsing context group (aka
// BrowsingInstance). Each browsing context group has its own set of
// SiteInstanceGroups.
//
// A SiteInstanceGroup is used for generating painted surfaces, directing input
// events, and facilitating communication between frames in different groups.
// The browser process coordinates activities across groups to produce a full
// web page.
//
// A SiteInstanceGroup always has a RenderProcessHost. If the RenderProcessHost
// itself (and not just the renderer process) goes away, then all
// RenderFrameHosts, RenderFrameProxyHosts, and workers using it are gone, and
// the SiteInstanceGroup itself goes away as well. SiteInstances in the group
// may outlive this (e.g., when kept alive by NavigationEntry), in which case
// they will get a new SiteInstanceGroup the next time one is needed.
// SiteInstanceGroups are refcounted by the SiteInstances using them, allowing
// for flexible policies. Currently, each SiteInstanceGroup has exactly one
// SiteInstance. See crbug.com/1195535.
class CONTENT_EXPORT SiteInstanceGroup
    : public base::RefCounted<SiteInstanceGroup>,
      public RenderProcessHostObserver {
 public:
  class CONTENT_EXPORT Observer : public base::CheckedObserver {
   public:
    // Called when this SiteInstanceGroup transitions to having no active
    // frames, as measured by active_frame_count().
    virtual void ActiveFrameCountIsZero(
        SiteInstanceGroup* site_instance_group) {}

    // Called when all NavigationStateKeepAlives associated with this
    // SiteInstanceGroup are gone.
    virtual void KeepAliveCountIsZero(SiteInstanceGroup* site_instance_group) {}

    // Called when the renderer process of this SiteInstanceGroup has exited.
    // Note that GetProcess() still returns the same RenderProcessHost instance.
    // You can reinitialize it by a call to SiteInstance::GetProcess()->Init().
    virtual void RenderProcessGone(SiteInstanceGroup* site_instance_group,
                                   const ChildProcessTerminationInfo& info) {}
  };

  SiteInstanceGroup(BrowsingInstance* browsing_instance,
                    RenderProcessHost* process);

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

  SiteInstanceGroupId GetId() const;

  base::SafeRef<SiteInstanceGroup> GetSafeRef();
  base::WeakPtr<SiteInstanceGroup> GetWeakPtr();
  // TODO(crbug.com/40258727): Remove this. Please don't use it.
  base::WeakPtr<SiteInstanceGroup> GetWeakPtrToAllowDangling();

  void AddObserver(Observer* observer);
  void RemoveObserver(Observer* observer);

  // Used to keep track of the SiteInstances that belong in this group, so they
  // can be notified to clear their references to `this` when it gets
  // destructed.
  void AddSiteInstance(SiteInstanceImpl* site_instance);
  void RemoveSiteInstance(SiteInstanceImpl* site_instance);

  // Increase the number of active frames in this SiteInstanceGroup. This is
  // increased when a frame is created.
  void IncrementActiveFrameCount();

  // Decrease the number of active frames in this SiteInstanceGroup. This is
  // decreased when a frame is destroyed. Decrementing this to zero will notify
  // observers, and may trigger deletion of proxies.
  void DecrementActiveFrameCount();

  // Increase the number of NavigationStateKeepAlives in this SiteInstanceGroup.
  // This increments when certain renderer-initiated navigations are scheduled
  // in this group.
  void IncrementKeepAliveCount();

  // Decrease the number of NavigationStateKeepAlives in this SiteInstanceGroup.
  // This decreases when the renderer has finished initiating the navigation.
  // Decrementing this to zero will notify observers and may trigger deletion of
  // proxies.
  void DecrementKeepAliveCount();

  // Returns true if `group` is in the same BrowsingInstance as `this`. This
  // behaves similarly to SiteInstance::IsRelatedSiteInstance, but can only be
  // used when a SiteInstanceGroup is available.
  bool IsRelatedSiteInstanceGroup(SiteInstanceGroup* group);

  // Get the number of active frames which belong to this SiteInstanceGroup. If
  // there are no active frames left, all frames in this SiteInstanceGroup can
  // be safely discarded.
  size_t active_frame_count() const { return active_frame_count_; }

  // Get the number of NavigationStateKeepAlives in this SiteInstanceGroup.
  size_t keep_alive_count() const { return keep_alive_count_; }

  RenderProcessHost* process() const { return &*process_; }

  BrowsingInstanceId browsing_instance_id() const {
    return browsing_instance_->isolation_context().browsing_instance_id();
  }

  // Returns the token uniquely identifying the BrowsingInstance this
  // SiteInstanceGroup belongs to.
  base::UnguessableToken browsing_instance_token() const {
    return browsing_instance_->token();
  }

  AgentSchedulingGroupHost& agent_scheduling_group() {
    DCHECK_EQ(agent_scheduling_group_->GetProcess(), &*process_);
    return *agent_scheduling_group_;
  }

  const StoragePartitionConfig& GetStoragePartitionConfig() const;

  const base::flat_set<raw_ptr<SiteInstanceImpl>>& site_instances_for_testing()
      const {
    return site_instances_;
  }

  // Creates a new SiteInstanceGroup in a new BrowsingInstance for testing.
  static SiteInstanceGroup* CreateForTesting(BrowserContext* browser_context,
                                             RenderProcessHost* process);
  // Creates a new SiteInstanceGroup in the same BrowsingInstance as `group`.
  static SiteInstanceGroup* CreateForTesting(SiteInstanceGroup* group,
                                             RenderProcessHost* process);

  using TraceProto = perfetto::protos::pbzero::SiteInstanceGroup;
  // Write a representation of this object into a trace.
  void WriteIntoTrace(perfetto::TracedProto<TraceProto> proto) const;

 private:
  friend class RefCounted<SiteInstanceGroup>;
  ~SiteInstanceGroup() override;

  BrowsingInstance* browsing_instance_for_testing() {
    return browsing_instance_.get();
  }

  // RenderProcessHostObserver implementation.
  void RenderProcessHostDestroyed(RenderProcessHost* host) override;
  void RenderProcessExited(RenderProcessHost* host,
                           const ChildProcessTerminationInfo& info) override;

  // A unique ID for this SiteInstanceGroup.
  SiteInstanceGroupId id_;

  // BrowsingInstance in which this SiteInstanceGroup exists. This is held as a
  // scoped_refptr since the BrowsingInstance must outlive all
  // SiteInstanceGroups in it.
  scoped_refptr<BrowsingInstance> browsing_instance_;

  // The number of active frames in this SiteInstanceGroup.
  size_t active_frame_count_ = 0;

  // The number of NavigationStateKeepAlives in this SiteInstanceGroup. Note
  // this is also tracked on RenderProcessHostImpl, which aggregates keep alives
  // across all SiteInstanceGroups in that process.
  size_t keep_alive_count_ = 0;

  // Current RenderProcessHost that is rendering pages for this
  // SiteInstanceGroup, and AgentSchedulingGroupHost (within the process) this
  // SiteInstanceGroup belongs to.
  // If the RenderProcessHost gets destroyed, `this` will also be destructed.
  // Any SiteInstances in the group will get a new process and group the next
  // time they need a process. If the process crashes, `this` will not be
  // destructed as long as the RenderProcessHost is still alive.
  const base::SafeRef<RenderProcessHost> process_;
  const base::SafeRef<AgentSchedulingGroupHost> agent_scheduling_group_;

  // List of SiteInstanceImpls that belong in this group. When any SiteInstance
  // in the set goes away, it must also be removed from `site_instances_` to
  // prevent UaF.
  base::flat_set<raw_ptr<SiteInstanceImpl>> site_instances_;

  base::ObserverList<Observer> observers_;
  bool is_notifying_observers_ = false;

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

}  // namespace content

#endif  // CONTENT_BROWSER_SITE_INSTANCE_GROUP_H_
