// Copyright 2018 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 "content/browser/devtools/devtools_renderer_channel.h"

#include "content/browser/devtools/devtools_agent_host_impl.h"
#include "content/browser/devtools/devtools_session.h"
#include "content/browser/devtools/protocol/devtools_domain_handler.h"
#include "content/browser/devtools/protocol/target_auto_attacher.h"
#include "content/browser/devtools/worker_devtools_agent_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/child_process_host.h"
#include "ui/gfx/geometry/point.h"

namespace content {

DevToolsRendererChannel::DevToolsRendererChannel(DevToolsAgentHostImpl* owner)
    : owner_(owner),
      binding_(this),
      associated_binding_(this),
      process_id_(ChildProcessHost::kInvalidUniqueID),
      weak_factory_(this) {}

DevToolsRendererChannel::~DevToolsRendererChannel() = default;

void DevToolsRendererChannel::SetRenderer(
    blink::mojom::DevToolsAgentPtr agent_ptr,
    blink::mojom::DevToolsAgentHostRequest host_request,
    int process_id,
    RenderFrameHostImpl* frame_host) {
  CleanupConnection();
  agent_ptr_ = std::move(agent_ptr);
  if (host_request)
    binding_.Bind(std::move(host_request));
  SetRendererInternal(agent_ptr_.get(), process_id, frame_host);
}

void DevToolsRendererChannel::SetRendererAssociated(
    blink::mojom::DevToolsAgentAssociatedPtr agent_ptr,
    blink::mojom::DevToolsAgentHostAssociatedRequest host_request,
    int process_id,
    RenderFrameHostImpl* frame_host) {
  CleanupConnection();
  associated_agent_ptr_ = std::move(agent_ptr);
  if (host_request)
    associated_binding_.Bind(std::move(host_request));
  SetRendererInternal(associated_agent_ptr_.get(), process_id, frame_host);
}

void DevToolsRendererChannel::CleanupConnection() {
  binding_.Close();
  associated_binding_.Close();
  associated_agent_ptr_ = nullptr;
  agent_ptr_ = nullptr;
}

void DevToolsRendererChannel::SetRendererInternal(
    blink::mojom::DevToolsAgent* agent,
    int process_id,
    RenderFrameHostImpl* frame_host) {
  // Child workers will eventually disconnect, but timing depends on the
  // renderer process. To ensure consistent view over protocol, disconnect them
  // right now.
  for (WorkerDevToolsAgentHost* host : child_workers_)
    host->ForceDetachAllSessions();
  process_id_ = process_id;
  frame_host_ = frame_host;
  if (agent && !report_attachers_.empty()) {
    agent->ReportChildWorkers(true /* report */,
                              !wait_for_debugger_attachers_.empty());
  }
  for (DevToolsSession* session : owner_->sessions()) {
    for (auto& pair : session->handlers())
      pair.second->SetRenderer(process_id_, frame_host_);
    session->AttachToAgent(agent);
  }
}

void DevToolsRendererChannel::AttachSession(DevToolsSession* session) {
  if (!agent_ptr_ && !associated_agent_ptr_)
    owner_->UpdateRendererChannel(true /* force */);
  for (auto& pair : session->handlers())
    pair.second->SetRenderer(process_id_, frame_host_);
  if (agent_ptr_)
    session->AttachToAgent(agent_ptr_.get());
  else if (associated_agent_ptr_)
    session->AttachToAgent(associated_agent_ptr_.get());
}

void DevToolsRendererChannel::InspectElement(const gfx::Point& point) {
  if (!agent_ptr_ && !associated_agent_ptr_)
    owner_->UpdateRendererChannel(true /* force */);
  // Previous call might update |agent_ptr_| or |associated_agent_ptr_|
  // via SetRenderer(), so we should check them again.
  if (agent_ptr_)
    agent_ptr_->InspectElement(point);
  else if (associated_agent_ptr_)
    associated_agent_ptr_->InspectElement(point);
}

void DevToolsRendererChannel::SetReportChildWorkers(
    protocol::TargetAutoAttacher* attacher,
    bool report,
    bool wait_for_debugger) {
  if (report) {
    if (report_attachers_.find(attacher) == report_attachers_.end()) {
      report_attachers_.insert(attacher);
      for (DevToolsAgentHostImpl* host : child_workers_)
        attacher->ChildWorkerCreated(host, false /* waiting_for_debugger */);
    }
  } else {
    report_attachers_.erase(attacher);
  }
  if (wait_for_debugger)
    wait_for_debugger_attachers_.insert(attacher);
  else
    wait_for_debugger_attachers_.erase(attacher);
  if (agent_ptr_) {
    agent_ptr_->ReportChildWorkers(!report_attachers_.empty(),
                                   !wait_for_debugger_attachers_.empty());
  } else if (associated_agent_ptr_) {
    associated_agent_ptr_->ReportChildWorkers(
        !report_attachers_.empty(), !wait_for_debugger_attachers_.empty());
  }
}

void DevToolsRendererChannel::ChildWorkerCreated(
    blink::mojom::DevToolsAgentPtr worker_devtools_agent,
    blink::mojom::DevToolsAgentHostRequest host_request,
    const GURL& url,
    const base::UnguessableToken& devtools_worker_token,
    bool waiting_for_debugger) {
  if (content::DevToolsAgentHost::GetForId(devtools_worker_token.ToString())) {
    mojo::ReportBadMessage("Workers should have unique tokens.");
    return;
  }
  RenderProcessHost* process = RenderProcessHost::FromID(process_id_);
  if (!process)
    return;
  GURL filtered_url = url;
  process->FilterURL(true /* empty_allowed */, &filtered_url);
  auto agent_host = base::MakeRefCounted<WorkerDevToolsAgentHost>(
      process_id_, std::move(worker_devtools_agent), std::move(host_request),
      filtered_url, devtools_worker_token, owner_->GetId(),
      base::BindOnce(&DevToolsRendererChannel::ChildWorkerDestroyed,
                     weak_factory_.GetWeakPtr()));
  child_workers_.insert(agent_host.get());
  for (protocol::TargetAutoAttacher* attacher : report_attachers_)
    attacher->ChildWorkerCreated(agent_host.get(), waiting_for_debugger);
}

void DevToolsRendererChannel::ChildWorkerDestroyed(
    DevToolsAgentHostImpl* host) {
  child_workers_.erase(host);
}

}  // namespace content
