910e62b5创建于 1月15日历史提交
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/devtools/protocol/target_handler.h"

#include <algorithm>
#include <memory>
#include <optional>
#include <string_view>

#include "base/base64.h"
#include "base/containers/contains.h"
#include "base/containers/flat_map.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/stringprintf.h"
#include "base/unguessable_token.h"
#include "base/values.h"
#include "build/build_config.h"
#include "content/browser/devtools/browser_devtools_agent_host.h"
#include "content/browser/devtools/devtools_agent_host_impl.h"
#include "content/browser/devtools/devtools_manager.h"
#include "content/browser/devtools/protocol/browser_handler.h"
#include "content/browser/devtools/protocol/target_auto_attacher.h"
#include "content/browser/devtools/render_frame_devtools_agent_host.h"
#include "content/browser/devtools/web_contents_devtools_agent_host.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/cors_origin_pattern_setter.h"
#include "content/public/browser/devtools_agent_host_client.h"
#include "content/public/browser/devtools_manager_delegate.h"
#include "content/public/browser/navigation_throttle.h"
#include "url/url_constants.h"

namespace content::protocol {

namespace {

constexpr net::NetworkTrafficAnnotationTag
    kSettingsProxyConfigTrafficAnnotation =
        net::DefineNetworkTrafficAnnotation("devtools_proxy_config", R"(
      semantics {
        sender: "Proxy Configuration over Developer Tools"
        description:
          "Used to fetch HTTP/HTTPS/SOCKS5/PAC proxy configuration when "
          "proxy is configured by DevTools. It is equivalent to the one "
          "configured via the --proxy-server command line flag. "
          "When proxy implies automatic configuration, it can send network "
          "requests in the scope of this annotation."
        trigger:
          "Whenever a network request is made when the system proxy settings "
          "are used, and they indicate to use a proxy server."
        data:
          "Proxy configuration."
        destination: OTHER
        destination_other: "The proxy server specified in the configuration."
      }
      policy {
        cookies_allowed: NO
        setting:
          "This request cannot be disabled in settings. However it will never "
          "be made if user does not run with '--remote-debugging-*' switches "
          "and does not explicitly send this data over Chrome remote debugging."
        policy_exception_justification:
          "Not implemented, only used in DevTools and is behind a switch."
      })");

static const char kNotAllowedError[] = "Not allowed";
static const char kMethod[] = "method";
static const char kResumeMethod[] = "Runtime.runIfWaitingForDebugger";

static const char kInitializerScript[] = R"(
  (function() {
    const bindingName = "%s";
    const binding = window[bindingName];
    delete window[bindingName];
    if (window.self === window.top) {
      window[bindingName] = {
        onmessage: () => {},
        send: binding
      };
    }
  })();
)";

static const char kTargetNotFound[] = "No target with given id found";

std::unique_ptr<Target::TargetInfo> BuildTargetInfo(
    DevToolsAgentHost* agent_host) {
  auto* host = static_cast<DevToolsAgentHostImpl*>(agent_host);
  std::unique_ptr<Target::TargetInfo> target_info =
      Target::TargetInfo::Create()
          .SetTargetId(host->GetId())
          .SetTitle(host->GetTitle())
          .SetUrl(host->GetURL().spec())
          .SetType(host->GetType())
          .SetAttached(host->IsAttached())
          .SetCanAccessOpener(host->CanAccessOpener())
          .Build();
  if (!host->GetOpenerId().empty()) {
    target_info->SetOpenerId(host->GetOpenerId());
  }
  if (!host->GetOpenerFrameId().empty()) {
    target_info->SetOpenerFrameId(host->GetOpenerFrameId());
  }
  if (!host->GetParentFrameId().empty()) {
    target_info->SetParentFrameId(host->GetParentFrameId());
  }
  if (host->GetBrowserContext()) {
    target_info->SetBrowserContextId(host->GetBrowserContext()->UniqueId());
  }
  std::string subtype = host->GetSubtype();
  if (!subtype.empty()) {
    target_info->SetSubtype(subtype);
  }
  return target_info;
}

static std::string TerminationStatusToString(base::TerminationStatus status) {
  switch (status) {
    case base::TERMINATION_STATUS_NORMAL_TERMINATION:
      return "normal";
    case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
      return "abnormal";
    case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
      return "killed";
    case base::TERMINATION_STATUS_PROCESS_CRASHED:
      return "crashed";
    case base::TERMINATION_STATUS_STILL_RUNNING:
      return "still running";
#if BUILDFLAG(IS_CHROMEOS)
    // Used for the case when oom-killer kills a process on ChromeOS.
    case base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM:
      return "oom killed";
#endif
#if BUILDFLAG(IS_ANDROID)
    // On Android processes are spawned from the system Zygote and we do not get
    // the termination status.  We can't know if the termination was a crash or
    // an oom kill for sure: but we can use status of the strong process
    // bindings as a hint.
    case base::TERMINATION_STATUS_OOM_PROTECTED:
      return "oom protected";
#endif
    case base::TERMINATION_STATUS_LAUNCH_FAILED:
      return "failed to launch";
    case base::TERMINATION_STATUS_OOM:
      return "oom";
#if BUILDFLAG(IS_WIN)
    case base::TERMINATION_STATUS_INTEGRITY_FAILURE:
      return "integrity failure";
#endif
    case base::TERMINATION_STATUS_EVICTED_FOR_MEMORY:
      return "evicted for memory";
    case base::TERMINATION_STATUS_MAX_ENUM:
      break;
  }
  NOTREACHED() << "Unknown Termination Status.";
}

class BrowserToPageConnector;

// Contains permissions for the instances of BrowserConnectorHostClient.
// Currently, only permissions for
// DevToolsAgentHostClient::AllowUnsafeOperations are implemented.
struct BrowserConnectorHostClientPermissions {
  // Defines what DevToolsAgentHostClient::AllowUnsafeOperations
  // returns for BrowserConnectorHostClient instances. See
  // DevToolsAgentHostClient::AllowUnsafeOperations for more details.
  bool allow_unsafe_operations = false;
};

class BrowserToPageConnector {
 public:
  class BrowserConnectorHostClient : public DevToolsAgentHostClient {
   public:
    BrowserConnectorHostClient(
        BrowserToPageConnector* connector,
        DevToolsAgentHost* host,
        const BrowserConnectorHostClientPermissions& permissions)
        : connector_(connector), permissions_(permissions) {
      // TODO(dgozman): handle return value of AttachClient.
      host->AttachClient(this);
    }

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

    void DispatchProtocolMessage(DevToolsAgentHost* agent_host,
                                 base::span<const uint8_t> message) override {
      connector_->DispatchProtocolMessage(agent_host, message);
    }
    void AgentHostClosed(DevToolsAgentHost* agent_host) override {
      connector_->AgentHostClosed(agent_host);
    }

    bool AllowUnsafeOperations() override {
      return permissions_.allow_unsafe_operations;
    }

   private:
    raw_ptr<BrowserToPageConnector> connector_;
    BrowserConnectorHostClientPermissions permissions_;
  };

  BrowserToPageConnector(
      const std::string& binding_name,
      DevToolsAgentHost* page_host,
      BrowserConnectorHostClientPermissions permissions,
      std::unique_ptr<Target::Backend::ExposeDevToolsProtocolCallback> callback)
      : binding_name_(binding_name),
        page_host_(page_host),
        pending_callback_(std::move(callback)) {
    browser_host_ = BrowserDevToolsAgentHost::CreateForDiscovery();
    browser_host_client_ = std::make_unique<BrowserConnectorHostClient>(
        this, browser_host_.get(), permissions);
    page_host_client_ = std::make_unique<BrowserConnectorHostClient>(
        this, page_host_.get(), BrowserConnectorHostClientPermissions());

    SendProtocolMessageToPage("Page.enable", base::Value());
    SendProtocolMessageToPage("Runtime.enable", base::Value());

    base::Value::Dict add_binding_params;
    add_binding_params.Set("name", binding_name);
    SendProtocolMessageToPage("Runtime.addBinding",
                              base::Value(std::move(add_binding_params)));

    std::string initializer_script =
        base::StringPrintf(kInitializerScript, binding_name.c_str());

    base::Value::Dict params;
    params.Set("scriptSource", initializer_script);
    SendProtocolMessageToPage("Page.addScriptToEvaluateOnLoad",
                              base::Value(std::move(params)));

    base::Value::Dict evaluate_params;
    evaluate_params.Set("expression", initializer_script);
    pending_request_id_ = SendProtocolMessageToPage(
        "Runtime.evaluate", base::Value(std::move(evaluate_params)));
    GetInstanceMap()[page_host_.get()].reset(this);
  }

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

  using BrowserToPageConnectorMap =
      base::flat_map<DevToolsAgentHost*,
                     std::unique_ptr<BrowserToPageConnector>>;
  static BrowserToPageConnectorMap& GetInstanceMap() {
    static base::NoDestructor<BrowserToPageConnectorMap> map;
    return *map;
  }

 private:
  int SendProtocolMessageToPage(const char* method, base::Value params) {
    base::Value::Dict message_dict;
    int id = page_message_id_++;
    message_dict.Set("id", id);
    message_dict.Set("method", method);
    message_dict.Set("params", std::move(params));
    base::Value message(std::move(message_dict));
    std::string json_message = base::WriteJson(message).value_or("");
    page_host_->DispatchProtocolMessage(page_host_client_.get(),
                                        base::as_byte_span(json_message));
    return id;
  }

  void DispatchProtocolMessage(DevToolsAgentHost* agent_host,
                               base::span<const uint8_t> message) {
    std::string_view message_sp(reinterpret_cast<const char*>(message.data()),
                                message.size());
    if (agent_host == page_host_.get()) {
      std::optional<base::Value::Dict> value = base::JSONReader::ReadDict(
          message_sp, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
      if (!value) {
        return;
      }

      std::optional<int> id = value->FindInt("id");
      if (id && *id == pending_request_id_ && pending_callback_) {
        pending_callback_->sendSuccess();
        pending_callback_.reset();
        pending_request_id_ = -1;
        return;
      }

      // Make sure this is a binding call.
      const std::string* method = value->FindString("method");
      if (!method || *method != "Runtime.bindingCalled") {
        return;
      }

      const base::Value::Dict* params = value->FindDict("params");
      if (!params) {
        return;
      }

      const std::string* name = params->FindString("name");
      if (!name || *name != binding_name_) {
        return;
      }

      const std::string* payload = params->FindString("payload");
      if (!payload) {
        return;
      }
      browser_host_->DispatchProtocolMessage(browser_host_client_.get(),
                                             base::as_byte_span(*payload));
      return;
    }
    DCHECK(agent_host == browser_host_.get());

    std::string encoded = base::Base64Encode(message_sp);
    std::string eval_code =
        "try { window." + binding_name_ + ".onmessage(atob(\"";
    std::string eval_suffix = "\")); } catch(e) { console.error(e); }";
    eval_code.reserve(eval_code.size() + encoded.size() + eval_suffix.size());
    eval_code.append(encoded);
    eval_code.append(eval_suffix);

    base::Value::Dict params;
    params.Set("expression", std::move(eval_code));
    SendProtocolMessageToPage("Runtime.evaluate",
                              base::Value(std::move(params)));
  }

  void AgentHostClosed(DevToolsAgentHost* agent_host) {
    if (agent_host == browser_host_.get()) {
      page_host_->DetachClient(page_host_client_.get());
    } else {
      DCHECK(agent_host == page_host_.get());
      browser_host_->DetachClient(browser_host_client_.get());
    }
    GetInstanceMap().erase(page_host_.get());
  }

  std::string binding_name_;
  scoped_refptr<DevToolsAgentHost> browser_host_;
  scoped_refptr<DevToolsAgentHost> page_host_;
  std::unique_ptr<BrowserConnectorHostClient> browser_host_client_;
  std::unique_ptr<BrowserConnectorHostClient> page_host_client_;
  int page_message_id_ = 0;
  std::unique_ptr<Target::Backend::ExposeDevToolsProtocolCallback>
      pending_callback_;
  int pending_request_id_ = -1;
};

}  // namespace

// Throttle is owned externally by the navigation subsystem.
class TargetHandler::Throttle : public NavigationThrottle {
 public:
  Throttle(const Throttle&) = delete;
  Throttle& operator=(const Throttle&) = delete;

  ~Throttle() override { CleanupPointers(); }
  TargetAutoAttacher* auto_attacher() const { return auto_attacher_; }
  void Clear();
  // NavigationThrottle implementation:
  const char* GetNameForLogging() override;

 protected:
  Throttle(base::WeakPtr<protocol::TargetHandler> target_handler,
           TargetAutoAttacher* auto_attacher,
           NavigationThrottleRegistry& registry)
      : NavigationThrottle(registry),
        target_handler_(target_handler),
        auto_attacher_(auto_attacher) {
    target_handler->throttles_.insert(this);
  }
  void SetThrottledAgentHost(DevToolsAgentHost* agent_host);

  bool is_deferring_ = false;
  scoped_refptr<DevToolsAgentHost> agent_host_;
  base::WeakPtr<protocol::TargetHandler> target_handler_;

 private:
  void CleanupPointers();
  raw_ptr<TargetAutoAttacher> auto_attacher_;
};

class TargetHandler::ResponseThrottle : public TargetHandler::Throttle {
 public:
  ResponseThrottle(base::WeakPtr<protocol::TargetHandler> target_handler,
                   TargetAutoAttacher* auto_attacher,
                   NavigationThrottleRegistry& registry)
      : Throttle(target_handler, auto_attacher, registry) {}
  ~ResponseThrottle() override = default;

 private:
  // NavigationThrottle implementation:
  ThrottleCheckResult WillProcessResponse() override { return MaybeThrottle(); }

  ThrottleCheckResult WillFailRequest() override { return MaybeThrottle(); }

  ThrottleCheckResult MaybeThrottle() {
    if (target_handler_ && auto_attacher()) {
      NavigationRequest* request = NavigationRequest::From(navigation_handle());
      const bool wait_for_debugger_on_start =
          target_handler_->ShouldWaitForDebuggerOnStart(request);
      scoped_refptr<RenderFrameDevToolsAgentHost> new_host =
          auto_attacher()->HandleNavigation(request,
                                            wait_for_debugger_on_start);
      if (new_host &&
          target_handler_->AutoAttach(auto_attacher(), new_host.get(),
                                      wait_for_debugger_on_start) &&
          wait_for_debugger_on_start) {
        SetThrottledAgentHost(new_host.get());
      } else {
        SetThrottledAgentHost(nullptr);
      }
    }
    is_deferring_ = !!agent_host_;
    return is_deferring_ ? DEFER : PROCEED;
  }
};

class TargetHandler::RequestThrottle : public TargetHandler::Throttle {
 public:
  RequestThrottle(base::WeakPtr<protocol::TargetHandler> target_handler,
                  NavigationThrottleRegistry& registry,
                  DevToolsAgentHost* throttled_agent_host)
      : Throttle(target_handler, target_handler->auto_attacher_, registry) {
    SetThrottledAgentHost(throttled_agent_host);
  }
  ~RequestThrottle() override = default;

 private:
  // NavigationThrottle implementation:
  ThrottleCheckResult WillStartRequest() override {
    is_deferring_ = !!agent_host_;
    return is_deferring_ ? DEFER : PROCEED;
  }
};

class TargetHandler::Session : public DevToolsAgentHostClient {
 public:
  static std::string Attach(TargetHandler* handler,
                            DevToolsAgentHost* agent_host,
                            bool waiting_for_debugger,
                            bool flatten_protocol) {
    std::string id = base::UnguessableToken::Create().ToString();
    // We don't support or allow the non-flattened protocol when in binary mode.
    // So, we coerce the setting to true, as the non-flattened mode is
    // deprecated anyway.
    if (handler->root_session_->GetClient()->UsesBinaryProtocol()) {
      flatten_protocol = true;
    }
    Session* session = new Session(handler, agent_host, id, flatten_protocol);
    handler->attached_sessions_[id].reset(session);
    DevToolsAgentHostImpl* agent_host_impl =
        static_cast<DevToolsAgentHostImpl*>(agent_host);
    if (flatten_protocol) {
      using Mode = DevToolsSession::Mode;
      const Mode mode =
          agent_host_impl->GetSessionMode() == Mode::kSupportsTabTarget
              ? Mode::kSupportsTabTarget
              : handler->session_mode_;

      base::OnceClosure resume_callback;
      if (waiting_for_debugger) {
        resume_callback = base::BindOnce(&Session::ResumeIfThrottled,
                                         base::Unretained(session));
      }
      DevToolsSession* devtools_session =
          handler->root_session_->AttachChildSession(
              id, agent_host_impl, session, mode, std::move(resume_callback));
      session->devtools_session_ = devtools_session;
    } else {
      agent_host_impl->AttachClient(session);
    }
    handler->frontend_->AttachedToTarget(id, BuildTargetInfo(agent_host),
                                         waiting_for_debugger);
    return id;
  }

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

  ~Session() override {
    if (!agent_host_) {
      return;
    }
    if (flatten_protocol_) {
      handler_->root_session_->DetachChildSession(id_);
    }
    agent_host_->DetachClient(this);
  }

  std::string GetTypeForMetrics() override { return "DevTools"; }

  void Detach(bool host_closed) {
    handler_->frontend_->DetachedFromTarget(id_, agent_host_->GetId());
    if (flatten_protocol_) {
      handler_->root_session_->DetachChildSession(id_);
    }
    if (!host_closed) {
      agent_host_->DetachClient(this);
    }
    handler_->auto_attached_sessions_.erase(agent_host_.get());
    devtools_session_ = nullptr;
    agent_host_ = nullptr;
    handler_->attached_sessions_.erase(id_);
  }

  bool IsWaitingForDebuggerOnStart() const {
    return devtools_session_ &&
           devtools_session_->IsWaitingForDebuggerOnStart();
  }

  void ResumeSendingMessagesToAgent() const {
    if (devtools_session_) {
      devtools_session_->ResumeSendingMessagesToAgent();
    }
  }

  void SetThrottle(Throttle* throttle) { throttle_ = throttle; }
  void SetWorkerThrottle(
      scoped_refptr<DevToolsThrottleHandle> worker_throttle) {
    worker_throttle_ = std::move(worker_throttle);
  }

  void ResumeIfThrottled() {
    if (throttle_) {
      throttle_->Clear();
    }
    worker_throttle_.reset();
  }

  void SendMessageToAgentHost(base::span<const uint8_t> message) {
    // This method is only used in the non-flat mode, it's the implementation
    // for Target.SendMessageToTarget. And since the binary mode implies
    // flatten_protocol_ (we force the flag to true), we can assume in this
    // method that |message| is JSON.
    DCHECK(!flatten_protocol_);

    if (throttle_ || worker_throttle_) {
      std::optional<base::Value::Dict> value = base::JSONReader::ReadDict(
          std::string_view(reinterpret_cast<const char*>(message.data()),
                           message.size()),
          base::JSON_PARSE_CHROMIUM_EXTENSIONS);
      const std::string* method;
      if (value && (method = value->FindString(kMethod)) &&
          *method == kResumeMethod) {
        ResumeIfThrottled();
      }
    }

    agent_host_->DispatchProtocolMessage(this, message);
  }

  bool IsAttachedTo(const std::string& target_id) {
    return agent_host_->GetId() == target_id;
  }

  bool UsesBinaryProtocol() override {
    return handler_->root_session_->GetClient()->UsesBinaryProtocol();
  }

 private:
  friend class TargetHandler;

  Session(TargetHandler* handler,
          DevToolsAgentHost* agent_host,
          const std::string& id,
          bool flatten_protocol)
      : handler_(handler),
        agent_host_(agent_host),
        id_(id),
        flatten_protocol_(flatten_protocol) {}

  // DevToolsAgentHostClient implementation.
  void DispatchProtocolMessage(DevToolsAgentHost* agent_host,
                               base::span<const uint8_t> message) override {
    DCHECK(agent_host == agent_host_.get());
    if (flatten_protocol_) {
      // TODO(johannes): It's not clear that this check is useful, but
      // a similar check has been in the code ever since the flattened protocol
      // was introduced. Try a DCHECK instead and possibly remove the check.
      if (!handler_->root_session_->HasChildSession(id_)) {
        return;
      }
      GetRootClient()->DispatchProtocolMessage(
          handler_->root_session_->GetAgentHost(), message);
      return;
    }
    // TODO(johannes): For now, We need to copy here because
    // ReceivedMessageFromTarget is generated code and we're using const
    // std::string& for such parameters. Perhaps we should switch this to
    // std::string_view?
    std::string message_copy(message.begin(), message.end());
    handler_->frontend_->ReceivedMessageFromTarget(id_, message_copy,
                                                   agent_host_->GetId());
  }

  void AgentHostClosed(DevToolsAgentHost* agent_host) override {
    DCHECK(agent_host == agent_host_.get());
    Detach(true);
  }

  bool MayAttachToURL(const GURL& url, bool is_webui) override {
    return GetRootClient()->MayAttachToURL(url, is_webui);
  }

  bool IsTrusted() override { return GetRootClient()->IsTrusted(); }

  bool MayReadLocalFiles() override {
    return GetRootClient()->MayReadLocalFiles();
  }

  bool MayWriteLocalFiles() override {
    return GetRootClient()->MayWriteLocalFiles();
  }

  bool AllowUnsafeOperations() override {
    return GetRootClient()->AllowUnsafeOperations();
  }

  DevToolsAgentHostClient* GetRootClient() {
    return handler_->root_session_->GetClient();
  }

  raw_ptr<TargetHandler> handler_;
  scoped_refptr<DevToolsAgentHost> agent_host_;
  std::string id_;
  bool flatten_protocol_;
  raw_ptr<DevToolsSession, DanglingUntriaged> devtools_session_ = nullptr;
  raw_ptr<Throttle> throttle_ = nullptr;
  scoped_refptr<DevToolsThrottleHandle> worker_throttle_;
  // This is needed to identify sessions associated with given
  // AutoAttacher to properly support SetAttachedTargetsOfType()
  // for a TargetHandler that serves as a client to multiple
  // different TargetAttachers. We don't want a pointer here,
  // because a session may survive the source AutoAttacher.
  uintptr_t auto_attacher_id_ = 0;
};

void TargetHandler::Throttle::CleanupPointers() {
  if (target_handler_ && agent_host_) {
    auto it = target_handler_->auto_attached_sessions_.find(agent_host_.get());
    if (it != target_handler_->auto_attached_sessions_.end()) {
      it->second->SetThrottle(nullptr);
    }
  }
  if (target_handler_) {
    target_handler_->throttles_.erase(this);
    target_handler_ = nullptr;
  }
}

void TargetHandler::Throttle::SetThrottledAgentHost(
    DevToolsAgentHost* agent_host) {
  agent_host_ = agent_host;
  if (agent_host_) {
    target_handler_->auto_attached_sessions_[agent_host_.get()]->SetThrottle(
        this);
  }
}

const char* TargetHandler::Throttle::GetNameForLogging() {
  return "DevToolsTargetNavigationThrottle";
}

void TargetHandler::Throttle::Clear() {
  CleanupPointers();
  agent_host_ = nullptr;
  auto_attacher_ = nullptr;
  if (is_deferring_) {
    is_deferring_ = false;
    Resume();
    // DO NOT ADD CODE after this. The callback above might have destroyed the
    // NavigationHandle that owns this NavigationThrottle.
  }
}

class TargetHandler::TargetFilter {
 public:
  using Filter = std::vector<std::unique_ptr<protocol::Target::FilterEntry>>;

  static std::unique_ptr<TargetFilter> CreateDefault() {
    Filter default_filter;
    // - Exclude `browser`.
    default_filter.push_back(protocol::Target::FilterEntry::Create()
                                 .SetExclude(true)
                                 .SetType(DevToolsAgentHost::kTypeBrowser)
                                 .Build());
    // - Exclude `tab`.
    default_filter.push_back(protocol::Target::FilterEntry::Create()
                                 .SetExclude(true)
                                 .SetType(DevToolsAgentHost::kTypeTab)
                                 .Build());
    // - Allow everything else.
    default_filter.push_back(protocol::Target::FilterEntry::Create().Build());
    return base::WrapUnique(new TargetFilter(std::move(default_filter)));
  }
  static std::unique_ptr<TargetFilter> Create(std::unique_ptr<Filter> filter) {
    if (!filter) {
      return CreateDefault();
    }
    return base::WrapUnique(new TargetFilter(std::move(*filter)));
  }

  bool Match(DevToolsAgentHost& host) const { return Match(host.GetType()); }

  bool Match(std::string_view type) const {
    for (const auto& entry : entries_) {
      if (!entry->HasType() || entry->GetType("") == type) {
        return !entry->GetExclude(false);
      }
    }
    return false;
  }

 private:
  explicit TargetFilter(Filter entries) : entries_(std::move(entries)) {}

  const Filter entries_;
};

TargetHandler::TargetHandler(AccessMode access_mode,
                             const std::string& owner_target_id,
                             TargetAutoAttacher* auto_attacher,
                             DevToolsSession* session)
    : DevToolsDomainHandler(Target::Metainfo::domainName),
      access_mode_(access_mode),
      owner_target_id_(owner_target_id),
      session_mode_(session->session_mode()),
      root_session_(session->GetRootSession()),
      auto_attacher_(auto_attacher) {}

TargetHandler::~TargetHandler() = default;

// static
std::vector<TargetHandler*> TargetHandler::ForAgentHost(
    DevToolsAgentHostImpl* host) {
  return host->HandlersByName<TargetHandler>(Target::Metainfo::domainName);
}

void TargetHandler::Wire(UberDispatcher* dispatcher) {
  frontend_ = std::make_unique<Target::Frontend>(dispatcher->channel());
  Target::Dispatcher::wire(dispatcher, this);
}

Response TargetHandler::Disable() {
  SetAutoAttachInternal(false, false, false, base::DoNothing());
  SetDiscoverTargets(false, {});
  hidden_target_manager_.Clear();
  auto_attached_sessions_.clear();
  attached_sessions_.clear();

  DevToolsManagerDelegate* delegate =
      DevToolsManager::GetInstance()->delegate();
  if (!delegate) {
    return Response::Success();
  }

  if (dispose_on_detach_context_ids_.size()) {
    for (auto* context : delegate->GetBrowserContexts()) {
      if (!dispose_on_detach_context_ids_.contains(context->UniqueId())) {
        continue;
      }
      delegate->DisposeBrowserContext(context, base::DoNothing());
    }
    dispose_on_detach_context_ids_.clear();
  }
  contexts_with_overridden_proxy_.clear();
  return Response::Success();
}

void TargetHandler::MaybeCreateAndAddNavigationThrottle(
    TargetAutoAttacher* auto_attacher,
    NavigationThrottleRegistry& registry) {
  DCHECK(auto_attach_ || !auto_attach_related_targets_.empty());
  auto* navigation_handle = &registry.GetNavigationHandle();
  FrameTreeNode* frame_tree_node =
      NavigationRequest::From(navigation_handle)->frame_tree_node();
  DCHECK(access_mode_ != AccessMode::kBrowser ||
         !auto_attach_related_targets_.empty() || !frame_tree_node->parent());
  // All child frames start navigating with their parent settings applied and
  // are only throttled at response where we know if they require a new host.
  // Note that fenced frames start as remote frames right away and get a RFDTAH
  // of their own, so they require a RequestThrottle rather than a Response one.
  if (!frame_tree_node->IsMainFrame()) {
    registry.AddThrottle(std::make_unique<ResponseThrottle>(
        weak_factory_.GetWeakPtr(), auto_attacher, registry));
    return;
  }
  // If we got here for main frame, it must be either browser or tab target.
  DCHECK(auto_attacher == auto_attacher_);
  DevToolsAgentHost* host =
      RenderFrameDevToolsAgentHost::GetFor(frame_tree_node);
  TargetHandler::Session* waiting_session = FindWaitingSession(host);
  if (waiting_session) {
    // RFDTAHs created during auto-attach had no renderer allocated originally,
    // and hence have messages paused, but with navigation we're supposed to
    // have a live host, so we can send messages to renderer now.
    DCHECK(frame_tree_node->current_frame_host()->IsRenderFrameLive());
    // Only resume sending messages to frame agents (i.e. skip for WebContents
    // ones).
    waiting_session->ResumeSendingMessagesToAgent();
  } else {
    // Currently, either RFDTAH or WCDTAH may be waiting for debugger (when
    // `waitForDebuggerOnStart` is honored for the Tab target, it is ignored
    // for the Page target), so in case no Page-level sessions are waiting,
    // also check the tab target.
    host = WebContentsDevToolsAgentHost::GetFor(
        WebContentsImpl::FromFrameTreeNode(frame_tree_node));
    waiting_session = FindWaitingSession(host);
    if (!waiting_session) {
      return;
    }
  }
  // window.open() navigations are throttled on the renderer side and the main
  // request will not be sent until runIfWaitingForDebugger is received from
  // the client, so there is no need to throttle the navigation in the
  // browser.
  //
  // New window navigations (such as ctrl+click) should be throttled before
  // the main request is sent to apply user agent and other overrides.
  if (frame_tree_node->opener()) {
    return;
  }
  registry.AddThrottle(std::make_unique<RequestThrottle>(
      weak_factory_.GetWeakPtr(), registry, host));
}

TargetHandler::Session* TargetHandler::FindWaitingSession(
    DevToolsAgentHost* host) {
  if (!host) {
    return nullptr;
  }
  auto it = auto_attached_sessions_.find(host);
  if (it == auto_attached_sessions_.end()) {
    return nullptr;
  }
  if (!it->second->IsWaitingForDebuggerOnStart()) {
    return nullptr;
  }
  return it->second;
}

void TargetHandler::ClearThrottles() {
  base::flat_set<raw_ptr<Throttle, CtnExperimental>> copy(throttles_);
  for (Throttle* throttle : copy) {
    throttle->Clear();
  }
  throttles_.clear();
}

void TargetHandler::SetAutoAttachInternal(bool auto_attach,
                                          bool wait_for_debugger_on_start,
                                          bool flatten,
                                          base::OnceClosure callback) {
  for (auto& entry : auto_attach_related_targets_) {
    entry.first->RemoveClient(this);
  }
  auto_attach_related_targets_.clear();
  flatten_auto_attach_ = flatten;
  if (auto_attach_) {
    auto_attacher_->RemoveClient(this);
  }
  auto_attach_ = auto_attach;
  wait_for_debugger_on_start_ = wait_for_debugger_on_start;
  if (auto_attach_) {
    auto_attacher_->AddClient(this, wait_for_debugger_on_start,
                              std::move(callback));
  } else {
    while (!auto_attached_sessions_.empty()) {
      auto_attached_sessions_.begin()->second->Detach(false);
    }
    ClearThrottles();
    auto_attach_target_filter_.reset();
    std::move(callback).Run();
  }
}

void TargetHandler::UpdateAgentHostObserver() {
  if (discover() == observing_agent_hosts_) {
    return;
  }
  observing_agent_hosts_ = discover();
  if (observing_agent_hosts_) {
    DevToolsAgentHost::AddObserver(this);
  } else {
    DevToolsAgentHost::RemoveObserver(this);
  }
}

bool TargetHandler::AutoAttach(TargetAutoAttacher* source,
                               DevToolsAgentHost* host,
                               bool waiting_for_debugger) {
  DCHECK(host);
  DCHECK(auto_attach_target_filter_);
  if (!auto_attach_target_filter_->Match(*host)) {
    return false;
  }
  if (base::Contains(auto_attached_sessions_, host)) {
    return false;
  }
  if (!auto_attach_service_workers_ &&
      host->GetType() == DevToolsAgentHost::kTypeServiceWorker) {
    return false;
  }
  std::string session_id =
      Session::Attach(this, host, waiting_for_debugger, flatten_auto_attach_);
  Session* session = attached_sessions_[session_id].get();
  session->auto_attacher_id_ = reinterpret_cast<uintptr_t>(source);
  auto_attached_sessions_[host] = session;
  return true;
}

void TargetHandler::AutoDetach(TargetAutoAttacher* source,
                               DevToolsAgentHost* host) {
  auto it = auto_attached_sessions_.find(host);
  if (it == auto_attached_sessions_.end()) {
    return;
  }
  it->second->Detach(false);
}

void TargetHandler::SetAttachedTargetsOfType(
    TargetAutoAttacher* source,
    const base::flat_set<scoped_refptr<DevToolsAgentHost>>& new_hosts,
    const std::string& type) {
  DCHECK(!type.empty());
  auto old_sessions = auto_attached_sessions_;
  for (auto& entry : old_sessions) {
    scoped_refptr<DevToolsAgentHost> host(entry.first);
    if (host->GetType() == type &&
        entry.second->auto_attacher_id_ ==
            reinterpret_cast<uintptr_t>(source) &&
        !base::Contains(new_hosts, host)) {
      AutoDetach(source, host.get());
    }
  }
  for (auto& host : new_hosts) {
    if (!base::Contains(old_sessions, host.get())) {
      AutoAttach(source, host.get(), false);
    }
  }
}

void TargetHandler::TargetInfoChanged(DevToolsAgentHost* host) {
  // Only send target info for targets we reported in any way.
  if (!base::Contains(reported_hosts_, host) &&
      auto_attached_sessions_.find(host) == auto_attached_sessions_.end()) {
    return;
  }
  frontend_->TargetInfoChanged(BuildTargetInfo(host));
}

void TargetHandler::AutoAttacherDestroyed(TargetAutoAttacher* auto_attacher) {
  auto throttles = throttles_;
  for (Throttle* throttle : throttles_) {
    if (throttle->auto_attacher() == auto_attacher) {
      throttle->Clear();
    }
  }
  for (auto& entry : auto_attached_sessions_) {
    if (entry.second->auto_attacher_id_ ==
        reinterpret_cast<uintptr_t>(auto_attacher)) {
      entry.second->auto_attacher_id_ = 0;
    }
  }
  auto_attach_related_targets_.erase(auto_attacher);
}

bool TargetHandler::ShouldWaitForDebuggerOnStart(
    NavigationRequest* navigation_request) const {
  if (auto_attach_) {
    return wait_for_debugger_on_start_;
  }
  DCHECK(!auto_attach_related_targets_.empty());
  auto* host = RenderFrameDevToolsAgentHost::GetFor(
      navigation_request->frame_tree_node());
  if (!host) {
    return false;
  }
  auto it = auto_attach_related_targets_.find(host->auto_attacher());
  return it != auto_attach_related_targets_.end() && it->second;
}

bool TargetHandler::ShouldThrottlePopups() const {
  return auto_attach_;
}

void TargetHandler::DisableAutoAttachOfServiceWorkers() {
  auto_attach_service_workers_ = false;
}

Response TargetHandler::FindSession(std::optional<std::string> session_id,
                                    std::optional<std::string> target_id,
                                    Session** session) {
  *session = nullptr;
  if (session_id.has_value()) {
    auto it = attached_sessions_.find(session_id.value());
    if (it == attached_sessions_.end()) {
      return Response::InvalidParams("No session with given id");
    }
    *session = it->second.get();
    return Response::Success();
  }
  if (target_id.has_value()) {
    for (auto& it : attached_sessions_) {
      if (it.second->IsAttachedTo(target_id.value())) {
        if (*session) {
          return Response::ServerError(
              "Multiple sessions attached, specify id.");
        }
        *session = it.second.get();
      }
    }
    if (!*session) {
      return Response::InvalidParams("No session for given target id");
    }
    return Response::Success();
  }
  return Response::InvalidParams("Session id must be specified");
}

// ----------------- Protocol ----------------------

Response TargetHandler::SetDiscoverTargets(
    bool discover,
    std::unique_ptr<protocol::Array<protocol::Target::FilterEntry>> filter) {
  if (access_mode_ == AccessMode::kAutoAttachOnly) {
    return Response::ServerError(kNotAllowedError);
  }
  if (!discover && filter && !filter->empty()) {
    return Response::InvalidParams(
        "Filter should not be present with `discover` is off");
  }
  const bool old_discover = TargetHandler::discover();
  discover_target_filter_ =
      discover ? TargetFilter::Create(std::move(filter)) : nullptr;
  if (old_discover == discover) {
    // Report the newly matching targets that were not yet reported.
    if (discover) {
      for (const auto& target : DevToolsAgentHost::GetOrCreateAll()) {
        DevToolsAgentHostCreated(target.get());
      }
    }
    return Response::Success();
  }
  UpdateAgentHostObserver();
  if (!TargetHandler::discover()) {
    reported_hosts_.clear();
  }
  return Response::Success();
}

void TargetHandler::SetAutoAttach(
    bool auto_attach,
    bool wait_for_debugger_on_start,
    std::optional<bool> flatten,
    std::unique_ptr<protocol::Array<protocol::Target::FilterEntry>> filter,
    std::unique_ptr<SetAutoAttachCallback> callback) {
  if (access_mode_ == AccessMode::kBrowser && !flatten.value_or(false)) {
    callback->sendFailure(Response::InvalidParams(
        "Only flatten protocol is supported with browser level auto-attach"));
    return;
  }
  if (!auto_attach && filter && !filter->empty()) {
    callback->sendFailure(Response::InvalidParams(
        "Target filter should be empty when disabling auto-attach"));
    return;
  }
  auto_attach_target_filter_ =
      auto_attach ? TargetFilter::Create(std::move(filter)) : nullptr;
  if (auto_attach_target_filter_ && access_mode_ == AccessMode::kBrowser &&
      auto_attach_target_filter_->Match(DevToolsAgentHost::kTypeTab) &&
      auto_attach_target_filter_->Match(DevToolsAgentHost::kTypePage)) {
    callback->sendFailure(Response::InvalidParams(
        "Filter should not simultaneously allow \"tab\" and \"page\", "
        "page targets are attached via tab targets"));
    return;
  }
  SetAutoAttachInternal(
      auto_attach, wait_for_debugger_on_start, flatten.value_or(false),
      base::BindOnce(&SetAutoAttachCallback::sendSuccess, std::move(callback)));
}

void TargetHandler::AutoAttachRelated(
    const std::string& targetId,
    bool wait_for_debugger_on_start,
    std::unique_ptr<protocol::Array<protocol::Target::FilterEntry>> filter,
    std::unique_ptr<AutoAttachRelatedCallback> callback) {
  if (access_mode_ != AccessMode::kBrowser) {
    callback->sendFailure(Response::ServerError(
        "Target.autoAttachRelated is only supported on the Browser target"));
    return;
  }
  scoped_refptr<DevToolsAgentHostImpl> host =
      DevToolsAgentHostImpl::GetForId(targetId);
  if (!host) {
    callback->sendFailure(Response::InvalidParams(kTargetNotFound));
    return;
  }
  TargetAutoAttacher* auto_attacher = host->auto_attacher();
  if (!auto_attacher) {
    callback->sendFailure(
        Response::InvalidParams("Target does not support auto-attaching"));
    return;
  }
  if (auto_attach_) {
    DCHECK(auto_attach_related_targets_.empty());
    SetAutoAttachInternal(false, false, true, base::DoNothing());
  }
  flatten_auto_attach_ = true;
  auto_attach_target_filter_ = TargetFilter::Create(std::move(filter));
  AutoAttach(auto_attacher_, host.get(), false);
  auto inserted = auto_attach_related_targets_.insert(
      std::make_pair(auto_attacher, wait_for_debugger_on_start));
  if (!inserted.second) {
    auto_attacher->UpdateWaitForDebuggerOnStart(
        this, wait_for_debugger_on_start,
        base::BindOnce(&AutoAttachRelatedCallback::sendSuccess,
                       std::move(callback)));
    inserted.first->second = wait_for_debugger_on_start;
    return;
  }
  auto_attacher->AddClient(
      this, wait_for_debugger_on_start,
      base::BindOnce(&AutoAttachRelatedCallback::sendSuccess,
                     std::move(callback)));
}

Response TargetHandler::SetRemoteLocations(
    std::unique_ptr<protocol::Array<Target::RemoteLocation>>) {
  return Response::ServerError("Not supported");
}

Response TargetHandler::AttachToTarget(const std::string& target_id,
                                       std::optional<bool> flatten,
                                       std::string* out_session_id) {
  if (access_mode_ == AccessMode::kAutoAttachOnly) {
    return Response::ServerError(kNotAllowedError);
  }
  // TODO(dgozman): only allow reported hosts.
  scoped_refptr<DevToolsAgentHost> agent_host =
      DevToolsAgentHost::GetForId(target_id);
  if (!agent_host) {
    return Response::InvalidParams(kTargetNotFound);
  }
  *out_session_id =
      Session::Attach(this, agent_host.get(), false, flatten.value_or(false));
  return Response::Success();
}

Response TargetHandler::AttachToBrowserTarget(std::string* out_session_id) {
  if (access_mode_ != AccessMode::kBrowser) {
    return Response::ServerError(kNotAllowedError);
  }
  scoped_refptr<DevToolsAgentHost> agent_host =
      DevToolsAgentHost::CreateForBrowser(
          nullptr, DevToolsAgentHost::CreateServerSocketCallback());
  *out_session_id = Session::Attach(this, agent_host.get(), false, true);
  return Response::Success();
}

Response TargetHandler::DetachFromTarget(std::optional<std::string> session_id,
                                         std::optional<std::string> target_id) {
  Session* session = nullptr;
  Response response =
      FindSession(std::move(session_id), std::move(target_id), &session);
  if (!response.IsSuccess()) {
    return response;
  }
  session->Detach(false);
  return Response::Success();
}

Response TargetHandler::SendMessageToTarget(
    const std::string& message,
    std::optional<std::string> session_id,
    std::optional<std::string> target_id) {
  Session* session = nullptr;
  Response response =
      FindSession(std::move(session_id), std::move(target_id), &session);
  if (!response.IsSuccess()) {
    return response;
  }
  if (session->flatten_protocol_) {
    return Response::ServerError(
        "When using flat protocol, messages are routed to the target "
        "via the sessionId attribute.");
  }
  session->SendMessageToAgentHost(base::as_byte_span(message));
  return Response::Success();
}

Response TargetHandler::GetTargetInfo(
    std::optional<std::string> maybe_target_id,
    std::unique_ptr<Target::TargetInfo>* target_info) {
  const std::string& target_id = maybe_target_id.value_or(owner_target_id_);
  if (access_mode_ == AccessMode::kAutoAttachOnly &&
      target_id != owner_target_id_) {
    return Response::ServerError(kNotAllowedError);
  }
  // TODO(dgozman): only allow reported hosts.
  scoped_refptr<DevToolsAgentHost> agent_host(
      DevToolsAgentHost::GetForId(target_id));
  if (!agent_host) {
    return Response::InvalidParams(kTargetNotFound);
  }
  *target_info = BuildTargetInfo(agent_host.get());
  return Response::Success();
}

Response TargetHandler::ActivateTarget(const std::string& target_id) {
  if (access_mode_ == AccessMode::kAutoAttachOnly) {
    return Response::ServerError(kNotAllowedError);
  }
  // TODO(dgozman): only allow reported hosts.
  scoped_refptr<DevToolsAgentHost> agent_host(
      DevToolsAgentHost::GetForId(target_id));
  if (!agent_host) {
    return Response::InvalidParams(kTargetNotFound);
  }
  agent_host->Activate();
  return Response::Success();
}

Response TargetHandler::CloseTarget(const std::string& target_id,
                                    bool* out_success) {
  scoped_refptr<DevToolsAgentHost> agent_host =
      DevToolsAgentHost::GetForId(target_id);
  if (!agent_host) {
    return Response::InvalidParams(kTargetNotFound);
  }
  if (access_mode_ == AccessMode::kAutoAttachOnly) {
    // Only allow to close the targets that we are attached to.
    if (target_id != owner_target_id_ &&
        !base::Contains(auto_attached_sessions_, agent_host.get())) {
      return Response::ServerError(kNotAllowedError);
    }
  }
  if (!agent_host->Close()) {
    return Response::InvalidParams("Specified target doesn't support closing");
  }
  *out_success = true;
  return Response::Success();
}

void TargetHandler::ExposeDevToolsProtocol(
    const std::string& target_id,
    std::optional<std::string> binding_name,
    std::optional<bool> inherit_permissions,
    std::unique_ptr<ExposeDevToolsProtocolCallback> callback) {
  if (access_mode_ != AccessMode::kBrowser) {
    callback->sendFailure(Response::InvalidParams(kNotAllowedError));
  }
  scoped_refptr<DevToolsAgentHost> agent_host =
      DevToolsAgentHost::GetForId(target_id);
  if (!agent_host) {
    callback->sendFailure(Response::InvalidParams(kTargetNotFound));
  }

  if (BrowserToPageConnector::GetInstanceMap()[agent_host.get()]) {
    callback->sendFailure(Response::ServerError(base::StringPrintf(
        "Target with id %s is already granted remote debugging bindings.",
        target_id.c_str())));
  }
  if (!agent_host->GetWebContents()) {
    callback->sendFailure(Response::ServerError(
        "RemoteDebuggingBinding can be granted only to page targets"));
  }

  BrowserConnectorHostClientPermissions permissions;
  if (inherit_permissions.value_or(false)) {
    permissions.allow_unsafe_operations =
        root_session_->GetClient()->AllowUnsafeOperations();
  }

  new BrowserToPageConnector(binding_name.value_or("cdp"), agent_host.get(),
                             permissions, std::move(callback));
}

Response TargetHandler::CreateTarget(
    const std::string& url,
    std::optional<int> left,
    std::optional<int> top,
    std::optional<int> width,
    std::optional<int> height,
    std::optional<std::string> window_state,
    std::optional<std::string> browser_context_id,
    std::optional<bool> enable_begin_frame_control,
    std::optional<bool> new_window,
    std::optional<bool> background,
    std::optional<bool> for_tab,
    std::optional<bool> hidden,
    std::string* out_target_id) {
  if (access_mode_ == AccessMode::kAutoAttachOnly) {
    return Response::ServerError(kNotAllowedError);
  }

  GURL gurl(url);
  if (gurl.is_empty()) {
    gurl = GURL(url::kAboutBlankURL);
  }

  if (hidden.value_or(false)) {
    // Hidden target can be created only when remote debugging is enabled.
    DevToolsManagerDelegate* delegate =
        DevToolsManager::GetInstance()->delegate();
    if (!delegate || delegate
                         ->RemoteDebuggingTargets(
                             DevToolsManagerDelegate::TargetType::kFrame)
                         .empty()) {
      return protocol::Response::ServerError(
          "Hidden target can be created only when remote debugging is enabled");
    }
    if (for_tab.value_or(false)) {
      return protocol::Response::InvalidParams(
          "Hidden target cannot be created for tab");
    }
    if (new_window) {
      return protocol::Response::InvalidParams(
          "Hidden target cannot be created in a new window");
    }
    if (!background.value_or(true)) {
      return protocol::Response::InvalidParams(
          "Hidden target can be created only in background");
    }

    BrowserContext* browser_context = nullptr;
    Response response = BrowserHandler::FindBrowserContext(browser_context_id,
                                                           &browser_context);
    if (!response.IsSuccess()) {
      return response;
    }

    *out_target_id =
        hidden_target_manager_.CreateHiddenTarget(gurl, browser_context);
    return Response::Success();
  }

  DevToolsManagerDelegate* delegate =
      DevToolsManager::GetInstance()->delegate();
  if (!delegate) {
    return Response::ServerError("Not supported");
  }
  DevToolsManagerDelegate::TargetType target_type =
      for_tab.value_or(session_mode_ ==
                       DevToolsSession::Mode::kSupportsTabTarget)
          ? DevToolsManagerDelegate::kTab
          : DevToolsManagerDelegate::kFrame;
  scoped_refptr<DevToolsAgentHost> agent_host =
      delegate->CreateNewTarget(gurl, target_type, new_window.value_or(false));
  if (!agent_host) {
    return Response::ServerError("Not supported");
  }
  *out_target_id = agent_host->GetId();
  return Response::Success();
}

Response TargetHandler::GetTargets(
    std::unique_ptr<protocol::Array<protocol::Target::FilterEntry>> filter,
    std::unique_ptr<protocol::Array<Target::TargetInfo>>* target_infos) {
  if (access_mode_ == AccessMode::kAutoAttachOnly) {
    return Response::ServerError(kNotAllowedError);
  }
  std::unique_ptr<TargetFilter> passed_filter =
      filter || !discover_target_filter_
          ? TargetFilter::Create(std::move(filter))
          : nullptr;
  const TargetFilter* effective_filter =
      passed_filter ? passed_filter.get() : discover_target_filter_.get();
  DCHECK(effective_filter);
  *target_infos = std::make_unique<protocol::Array<Target::TargetInfo>>();
  for (const auto& host : DevToolsAgentHost::GetOrCreateAll()) {
    if (effective_filter->Match(*host)) {
      (*target_infos)->emplace_back(BuildTargetInfo(host.get()));
    }
  }
  return Response::Success();
}

// -------------- DevToolsAgentHostObserver -----------------

bool TargetHandler::ShouldForceDevToolsAgentHostCreation() {
  return true;
}

void TargetHandler::DevToolsAgentHostCreated(DevToolsAgentHost* host) {
  DCHECK(discover());
  DCHECK(host);
  if (!discover_target_filter_->Match(*host)) {
    return;
  }
  // If we start discovering late, all existing agent hosts will be reported,
  // but we could have already attached to some.
  if (!base::Contains(reported_hosts_, host)) {
    frontend_->TargetCreated(BuildTargetInfo(host));
    reported_hosts_.insert(host);
  }
}

void TargetHandler::DevToolsAgentHostNavigated(DevToolsAgentHost* host) {
  TargetInfoChanged(host);
}

void TargetHandler::DevToolsAgentHostDestroyed(DevToolsAgentHost* host) {
  if (!base::Contains(reported_hosts_, host)) {
    return;
  }
  frontend_->TargetDestroyed(host->GetId());
  reported_hosts_.erase(host);
}

void TargetHandler::DevToolsAgentHostAttached(DevToolsAgentHost* host) {
  TargetInfoChanged(host);
}

void TargetHandler::DevToolsAgentHostDetached(DevToolsAgentHost* host) {
  TargetInfoChanged(host);
}

void TargetHandler::DevToolsAgentHostCrashed(DevToolsAgentHost* host,
                                             base::TerminationStatus status) {
  if (!base::Contains(reported_hosts_, host)) {
    return;
  }
  frontend_->TargetCrashed(host->GetId(), TerminationStatusToString(status),
                           host->GetWebContents()
                               ? host->GetWebContents()->GetCrashedErrorCode()
                               : 0);
}

// ----------------- More protocol methods -------------------

void TargetHandler::CreateBrowserContext(
    std::optional<bool> in_disposeOnDetach,
    std::optional<String> in_proxyServer,
    std::optional<String> in_proxyBypassList,
    std::unique_ptr<protocol::Array<String>>
        in_originsToGrantUniversalNetworkAccess,
    std::unique_ptr<CreateBrowserContextCallback> callback) {
  if (access_mode_ != AccessMode::kBrowser) {
    callback->sendFailure(Response::ServerError(kNotAllowedError));
    return;
  }
  DevToolsManagerDelegate* delegate =
      DevToolsManager::GetInstance()->delegate();
  if (!delegate) {
    callback->sendFailure(
        Response::ServerError("Browser context management is not supported."));
    return;
  }

  if (in_proxyServer.has_value()) {
    pending_proxy_config_ = net::ProxyConfig();
    pending_proxy_config_->proxy_rules().ParseFromString(
        in_proxyServer.value());
    if (in_proxyBypassList.has_value()) {
      pending_proxy_config_->proxy_rules().bypass_rules.ParseFromString(
          in_proxyBypassList.value());
    }
  }

  // Pre-process universal network access origins before actual context creation
  // in case we need to bail out with error.
  std::vector<url::Origin> originsToGrantUniversalNetworkAccess;
  if (in_originsToGrantUniversalNetworkAccess) {
    for (const auto& origin_str : *in_originsToGrantUniversalNetworkAccess) {
      GURL url(origin_str);
      url::Origin origin = url::Origin::Create(url);
      if (!url.is_valid() || origin.opaque()) {
        callback->sendFailure(
            Response::InvalidParams("Invalid origin " + origin_str));
        return;
      }
      originsToGrantUniversalNetworkAccess.push_back(std::move(origin));
    }
  }

  BrowserContext* context = delegate->CreateBrowserContext();
  if (!context) {
    callback->sendFailure(
        Response::ServerError("Failed to create browser context."));
    pending_proxy_config_.reset();
    return;
  }

  for (const auto& origin : originsToGrantUniversalNetworkAccess) {
    std::vector<network::mojom::CorsOriginPatternPtr> allow_patterns;
    allow_patterns.push_back(network::mojom::CorsOriginPattern::New());
    allow_patterns.back()->protocol = "http";
    allow_patterns.back()->priority =
        network::mojom::CorsOriginAccessMatchPriority::kMaxPriority;
    allow_patterns.push_back(network::mojom::CorsOriginPattern::New());
    allow_patterns.back()->protocol = "https";
    allow_patterns.back()->priority =
        network::mojom::CorsOriginAccessMatchPriority::kMaxPriority;

    // It's fine to not await the completion here -- this is implicitly
    // serialized with the actual URLLoaderFactory / URLLoader creation.
    CorsOriginPatternSetter::Set(
        context, origin, std::move(allow_patterns), {}, base::DoNothing());
  }

  if (pending_proxy_config_) {
    contexts_with_overridden_proxy_[context->UniqueId()] =
        std::move(*pending_proxy_config_);
    pending_proxy_config_.reset();
  }

  if (in_disposeOnDetach.value_or(false)) {
    dispose_on_detach_context_ids_.insert(context->UniqueId());
  }
  callback->sendSuccess(context->UniqueId());
}

protocol::Response TargetHandler::GetBrowserContexts(
    std::unique_ptr<protocol::Array<protocol::String>>* browser_context_ids) {
  if (access_mode_ != AccessMode::kBrowser) {
    return Response::ServerError(kNotAllowedError);
  }
  DevToolsManagerDelegate* delegate =
      DevToolsManager::GetInstance()->delegate();
  if (!delegate) {
    return Response::ServerError(
        "Browser context management is not supported.");
  }
  std::vector<BrowserContext*> contexts =
      delegate->GetBrowserContexts();
  *browser_context_ids = std::make_unique<protocol::Array<protocol::String>>();
  for (auto* context : contexts) {
    (*browser_context_ids)->emplace_back(context->UniqueId());
  }
  return Response::Success();
}

void TargetHandler::DisposeBrowserContext(
    const std::string& context_id,
    std::unique_ptr<DisposeBrowserContextCallback> callback) {
  if (access_mode_ != AccessMode::kBrowser) {
    callback->sendFailure(Response::ServerError(kNotAllowedError));
    return;
  }
  DevToolsManagerDelegate* delegate =
      DevToolsManager::GetInstance()->delegate();
  if (!delegate) {
    callback->sendFailure(
        Response::ServerError("Browser context management is not supported."));
    return;
  }
  std::vector<BrowserContext*> contexts =
      delegate->GetBrowserContexts();
  auto context_it = std::ranges::find(contexts, context_id,
                                      &BrowserContext::UniqueId);
  if (context_it == contexts.end()) {
    callback->sendFailure(
        Response::ServerError("Failed to find context with id " + context_id));
    return;
  }
  dispose_on_detach_context_ids_.erase(context_id);
  delegate->DisposeBrowserContext(
      *context_it,
      base::BindOnce(
          [](std::unique_ptr<DisposeBrowserContextCallback> callback,
             bool success, const std::string& error) {
            if (success) {
              callback->sendSuccess();
            } else {
              callback->sendFailure(Response::ServerError(error));
            }
          },
          std::move(callback)));
}

void TargetHandler::ApplyNetworkContextParamsOverrides(
    BrowserContext* browser_context,
    network::mojom::NetworkContextParams* context_params) {
  //   Note #1: below we clear the proxy config client receiver,
  // and effectively disable proxy updates based on the OS settings.
  // This way our "initial proxy config" is not overridden by any
  // OS settings and stays the same.
  //   This relies on ApplyNetworkContextParamsOverrides() being called
  // after the client receiver was setup for the network context.
  //
  //   Note #2: Under certain conditions, storage partition is created
  // synchronously for
  // the browser context. Account for this use case.
  if (pending_proxy_config_) {
    context_params->initial_proxy_config =
        net::ProxyConfigWithAnnotation(std::move(*pending_proxy_config_),
                                       kSettingsProxyConfigTrafficAnnotation);
    context_params->proxy_config_client_receiver = mojo::NullReceiver();
    pending_proxy_config_.reset();
    return;
  }
  auto it = contexts_with_overridden_proxy_.find(browser_context->UniqueId());
  if (it != contexts_with_overridden_proxy_.end()) {
    context_params->initial_proxy_config = net::ProxyConfigWithAnnotation(
        std::move(it->second), kSettingsProxyConfigTrafficAnnotation);
    context_params->proxy_config_client_receiver = mojo::NullReceiver();
    contexts_with_overridden_proxy_.erase(browser_context->UniqueId());
  }
}

void TargetHandler::AddWorkerThrottle(
    DevToolsAgentHost* agent_host,
    scoped_refptr<DevToolsThrottleHandle> throttle_handle) {
  if (!agent_host) {
    return;
  }

  if (auto_attached_sessions_.count(agent_host)) {
    if (auto_attached_sessions_[agent_host]->IsWaitingForDebuggerOnStart()) {
      auto_attached_sessions_[agent_host]->SetWorkerThrottle(
          std::move(throttle_handle));
    }
  }
}

Response TargetHandler::GetDevToolsTarget(
    const std::string& target_id,
    std::optional<std::string>* out_target_id) {
  if (access_mode_ != AccessMode::kBrowser) {
    return protocol::Response::ServerError(kNotAllowedError);
  }
  scoped_refptr<DevToolsAgentHostImpl> agent_host =
      DevToolsAgentHostImpl::GetForId(target_id);

  if (!agent_host) {
    return protocol::Response::InvalidParams(kTargetNotFound);
  }

  if (scoped_refptr<DevToolsAgentHost> devtools_agent_host =
          agent_host->GetDevToolsAgentHost()) {
    *out_target_id = devtools_agent_host->GetId();
  }

  return protocol::Response::Success();
}

Response TargetHandler::OpenDevTools(const std::string& target_id,
                                     std::optional<std::string> panel_id,
                                     std::string* out_target_id) {
  if (access_mode_ != AccessMode::kBrowser) {
    return protocol::Response::ServerError(kNotAllowedError);
  }
  scoped_refptr<DevToolsAgentHostImpl> agent_host =
      DevToolsAgentHostImpl::GetForId(target_id);

  if (!agent_host) {
    return protocol::Response::InvalidParams(kTargetNotFound);
  }

  scoped_refptr<DevToolsAgentHost> devtools_agent_host =
      agent_host->OpenDevTools(
          content::DevToolsManagerDelegate::DevToolsOptions(panel_id));
  if (!devtools_agent_host) {
    return protocol::Response::ServerError("Failed to create DevTools window");
  }

  *out_target_id = devtools_agent_host->GetId();

  return protocol::Response::Success();
}

}  // namespace content::protocol