// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/performance_manager/performance_manager_registry_impl.h"

#include <iterator>
#include <utility>

#include "base/stl_util.h"
#include "components/performance_manager/embedder/binders.h"
#include "components/performance_manager/performance_manager_tab_helper.h"
#include "components/performance_manager/public/mojom/coordination_unit.mojom.h"
#include "components/performance_manager/public/performance_manager.h"
#include "components/performance_manager/public/performance_manager_main_thread_mechanism.h"
#include "components/performance_manager/public/performance_manager_main_thread_observer.h"
#include "components/performance_manager/public/performance_manager_owned.h"
#include "components/performance_manager/service_worker_context_adapter.h"
#include "components/performance_manager/worker_watcher.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_throttle.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/storage_partition.h"

namespace performance_manager {

namespace {

PerformanceManagerRegistryImpl* g_instance = nullptr;

}  // namespace

PerformanceManagerRegistryImpl::PerformanceManagerRegistryImpl() {
  DCHECK(!g_instance);
  g_instance = this;

  // The registry should be created after the PerformanceManager.
  DCHECK(PerformanceManager::IsAvailable());
}

PerformanceManagerRegistryImpl::~PerformanceManagerRegistryImpl() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // TearDown() should have been invoked to reset |g_instance| and clear
  // |web_contents_| and |render_process_user_data_| prior to destroying the
  // registry.
  DCHECK(!g_instance);
  DCHECK(web_contents_.empty());
  DCHECK(render_process_hosts_.empty());
  DCHECK(pm_owned_.empty());
  DCHECK(pm_registered_.empty());
  // TODO(crbug.com/1084611): |observers_| and |mechanisms_| should also be
  // empty by now!
}

// static
PerformanceManagerRegistryImpl* PerformanceManagerRegistryImpl::GetInstance() {
  return g_instance;
}

void PerformanceManagerRegistryImpl::AddObserver(
    PerformanceManagerMainThreadObserver* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  observers_.AddObserver(observer);
}

void PerformanceManagerRegistryImpl::RemoveObserver(
    PerformanceManagerMainThreadObserver* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  observers_.RemoveObserver(observer);
}

void PerformanceManagerRegistryImpl::AddMechanism(
    PerformanceManagerMainThreadMechanism* mechanism) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  mechanisms_.AddObserver(mechanism);
}

void PerformanceManagerRegistryImpl::RemoveMechanism(
    PerformanceManagerMainThreadMechanism* mechanism) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  mechanisms_.RemoveObserver(mechanism);
}

bool PerformanceManagerRegistryImpl::HasMechanism(
    PerformanceManagerMainThreadMechanism* mechanism) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return mechanisms_.HasObserver(mechanism);
}

void PerformanceManagerRegistryImpl::PassToPM(
    std::unique_ptr<PerformanceManagerOwned> pm_owned) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  pm_owned_.PassObject(std::move(pm_owned));
}

std::unique_ptr<PerformanceManagerOwned>
PerformanceManagerRegistryImpl::TakeFromPM(PerformanceManagerOwned* pm_owned) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return pm_owned_.TakeObject(pm_owned);
}

void PerformanceManagerRegistryImpl::RegisterObject(
    PerformanceManagerRegistered* pm_object) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  pm_registered_.RegisterObject(pm_object);
}

void PerformanceManagerRegistryImpl::UnregisterObject(
    PerformanceManagerRegistered* pm_object) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  pm_registered_.UnregisterObject(pm_object);
}

PerformanceManagerRegistered*
PerformanceManagerRegistryImpl::GetRegisteredObject(uintptr_t type_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return pm_registered_.GetRegisteredObject(type_id);
}

void PerformanceManagerRegistryImpl::CreatePageNodeForWebContents(
    content::WebContents* web_contents) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  auto result = web_contents_.insert(web_contents);
  DCHECK(result.second);

  PerformanceManagerTabHelper::CreateForWebContents(web_contents);
  PerformanceManagerTabHelper* tab_helper =
      PerformanceManagerTabHelper::FromWebContents(web_contents);
  DCHECK(tab_helper);
  tab_helper->SetDestructionObserver(this);

  for (auto& observer : observers_)
    observer.OnPageNodeCreatedForWebContents(web_contents);
}

PerformanceManagerRegistryImpl::Throttles
PerformanceManagerRegistryImpl::CreateThrottlesForNavigation(
    content::NavigationHandle* handle) {
  Throttles combined_throttles;
  for (auto& mechanism : mechanisms_) {
    Throttles throttles = mechanism.CreateThrottlesForNavigation(handle);
    combined_throttles.insert(combined_throttles.end(),
                              std::make_move_iterator(throttles.begin()),
                              std::make_move_iterator(throttles.end()));
  }
  return combined_throttles;
}

void PerformanceManagerRegistryImpl::NotifyBrowserContextAdded(
    content::BrowserContext* browser_context) {
  content::StoragePartition* storage_partition =
      content::BrowserContext::GetDefaultStoragePartition(browser_context);

  // Create an adapter for the service worker context.
  auto insertion_result = service_worker_context_adapters_.emplace(
      browser_context, std::make_unique<ServiceWorkerContextAdapter>(
                           storage_partition->GetServiceWorkerContext()));
  DCHECK(insertion_result.second);
  ServiceWorkerContextAdapter* service_worker_context_adapter =
      insertion_result.first->second.get();

  auto worker_watcher = std::make_unique<WorkerWatcher>(
      browser_context->UniqueId(),
      storage_partition->GetDedicatedWorkerService(),
      storage_partition->GetSharedWorkerService(),
      service_worker_context_adapter, &process_node_source_,
      &frame_node_source_);
  bool inserted =
      worker_watchers_.emplace(browser_context, std::move(worker_watcher))
          .second;
  DCHECK(inserted);
}

void PerformanceManagerRegistryImpl::
    CreateProcessNodeAndExposeInterfacesToRendererProcess(
        service_manager::BinderRegistry* registry,
        content::RenderProcessHost* render_process_host) {
  registry->AddInterface(base::BindRepeating(&BindProcessCoordinationUnit,
                                             render_process_host->GetID()),
                         base::SequencedTaskRunnerHandle::Get());

  // Ideally this would strictly be a "Create", but when a
  // RenderFrameHost is "resurrected" with a new process it will
  // already have user data attached. This will happen on renderer
  // crash.
  EnsureProcessNodeForRenderProcessHost(render_process_host);
}

void PerformanceManagerRegistryImpl::ExposeInterfacesToRenderFrame(
    mojo::BinderMapWithContext<content::RenderFrameHost*>* map) {
  map->Add<performance_manager::mojom::DocumentCoordinationUnit>(
      base::BindRepeating(&BindDocumentCoordinationUnit));
}

void PerformanceManagerRegistryImpl::NotifyBrowserContextRemoved(
    content::BrowserContext* browser_context) {
  auto it = worker_watchers_.find(browser_context);
  DCHECK(it != worker_watchers_.end());
  it->second->TearDown();
  worker_watchers_.erase(it);

  // Remove the adapter.
  size_t removed = service_worker_context_adapters_.erase(browser_context);
  DCHECK_EQ(removed, 1u);
}

void PerformanceManagerRegistryImpl::TearDown() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // The registry should be torn down before the PerformanceManager.
  DCHECK(PerformanceManager::IsAvailable());

  // Notify any observers of the tear down. This lets them unregister things,
  // etc.
  for (auto& observer : observers_)
    observer.OnBeforePerformanceManagerDestroyed();

  DCHECK_EQ(g_instance, this);
  g_instance = nullptr;

  // Destroy WorkerNodes before ProcessNodes, because ProcessNode checks that it
  // has no associated WorkerNode when torn down.
  for (auto& kv : worker_watchers_) {
    kv.second->TearDown();
  }
  worker_watchers_.clear();

  service_worker_context_adapters_.clear();

  for (auto* web_contents : web_contents_) {
    PerformanceManagerTabHelper* tab_helper =
        PerformanceManagerTabHelper::FromWebContents(web_contents);
    DCHECK(tab_helper);
    // Clear the destruction observer to avoid a nested notification.
    tab_helper->SetDestructionObserver(nullptr);
    // Destroy the tab helper.
    tab_helper->TearDown();
    web_contents->RemoveUserData(PerformanceManagerTabHelper::UserDataKey());
  }
  web_contents_.clear();

  for (auto* render_process_host : render_process_hosts_) {
    RenderProcessUserData* user_data =
        RenderProcessUserData::GetForRenderProcessHost(render_process_host);
    DCHECK(user_data);
    // Clear the destruction observer to avoid a nested notification.
    user_data->SetDestructionObserver(nullptr);
    // Destroy the user data.
    render_process_host->RemoveUserData(RenderProcessUserData::UserDataKey());
  }
  render_process_hosts_.clear();

  // Tear down PM owned objects. This lets them clear up object registrations,
  // observers, mechanisms, etc.
  pm_owned_.ReleaseObjects();

  DCHECK(pm_owned_.empty());
  DCHECK(pm_registered_.empty());
  // TODO(crbug.com/1084611): |observers_| and |mechanisms_| should also be
  // empty by now!
}

void PerformanceManagerRegistryImpl::OnPerformanceManagerTabHelperDestroying(
    content::WebContents* web_contents) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  const size_t num_removed = web_contents_.erase(web_contents);
  DCHECK_EQ(1U, num_removed);
}

void PerformanceManagerRegistryImpl::OnRenderProcessUserDataDestroying(
    content::RenderProcessHost* render_process_host) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  const size_t num_removed = render_process_hosts_.erase(render_process_host);
  DCHECK_EQ(1U, num_removed);
}

void PerformanceManagerRegistryImpl::EnsureProcessNodeForRenderProcessHost(
    content::RenderProcessHost* render_process_host) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  auto result = render_process_hosts_.insert(render_process_host);
  if (result.second) {
    // Create a RenderProcessUserData if |render_process_host| doesn't already
    // have one.
    RenderProcessUserData* user_data =
        RenderProcessUserData::CreateForRenderProcessHost(render_process_host);
    user_data->SetDestructionObserver(this);
  }
}

}  // namespace performance_manager
