blob: 254a4ac1f18d2bf98d56f8994177e9dab1d841a7 [file] [log] [blame]
// Copyright 2016 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/protocol/target_handler.h"
#include "content/browser/devtools/devtools_manager.h"
#include "content/browser/devtools/service_worker_devtools_agent_host.h"
#include "content/browser/frame_host/frame_tree.h"
#include "content/browser/frame_host/frame_tree_node.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
namespace content {
namespace devtools {
namespace target {
using Response = DevToolsProtocolClient::Response;
namespace {
using ScopeAgentsMap =
std::map<GURL, std::unique_ptr<ServiceWorkerDevToolsAgentHost::List>>;
void GetMatchingHostsByScopeMap(
const ServiceWorkerDevToolsAgentHost::List& agent_hosts,
const std::set<GURL>& urls,
ScopeAgentsMap* scope_agents_map) {
std::set<base::StringPiece> host_name_set;
for (const GURL& url : urls)
host_name_set.insert(url.host_piece());
for (const auto& host : agent_hosts) {
if (host_name_set.find(host->scope().host_piece()) == host_name_set.end())
continue;
const auto& it = scope_agents_map->find(host->scope());
if (it == scope_agents_map->end()) {
std::unique_ptr<ServiceWorkerDevToolsAgentHost::List> new_list(
new ServiceWorkerDevToolsAgentHost::List());
new_list->push_back(host);
(*scope_agents_map)[host->scope()] = std::move(new_list);
} else {
it->second->push_back(host);
}
}
}
void AddEligibleHosts(const ServiceWorkerDevToolsAgentHost::List& list,
ServiceWorkerDevToolsAgentHost::Map* result) {
base::Time last_installed_time;
base::Time last_doomed_time;
for (const auto& host : list) {
if (host->version_installed_time() > last_installed_time)
last_installed_time = host->version_installed_time();
if (host->version_doomed_time() > last_doomed_time)
last_doomed_time = host->version_doomed_time();
}
for (const auto& host : list) {
// We don't attech old redundant Service Workers when there is newer
// installed Service Worker.
if (host->version_doomed_time().is_null() ||
(last_installed_time < last_doomed_time &&
last_doomed_time == host->version_doomed_time())) {
(*result)[host->GetId()] = host;
}
}
}
ServiceWorkerDevToolsAgentHost::Map GetMatchingServiceWorkers(
BrowserContext* browser_context,
const std::set<GURL>& urls) {
ServiceWorkerDevToolsAgentHost::Map result;
if (!browser_context)
return result;
ServiceWorkerDevToolsAgentHost::List agent_hosts;
ServiceWorkerDevToolsManager::GetInstance()
->AddAllAgentHostsForBrowserContext(browser_context, &agent_hosts);
ScopeAgentsMap scope_agents_map;
GetMatchingHostsByScopeMap(agent_hosts, urls, &scope_agents_map);
for (const auto& it : scope_agents_map)
AddEligibleHosts(*it.second.get(), &result);
return result;
}
scoped_refptr<TargetInfo> CreateInfo(DevToolsAgentHost* host) {
return TargetInfo::Create()
->set_target_id(host->GetId())
->set_title(host->GetTitle())
->set_url(host->GetURL().spec())
->set_type(host->GetType());
}
} // namespace
TargetHandler::TargetHandler()
: discover_(false),
auto_attach_(false),
wait_for_debugger_on_start_(false),
attach_to_frames_(false),
render_frame_host_(nullptr) {
}
TargetHandler::~TargetHandler() {
Detached();
}
void TargetHandler::SetRenderFrameHost(RenderFrameHostImpl* render_frame_host) {
render_frame_host_ = render_frame_host;
UpdateFrames();
}
void TargetHandler::SetClient(std::unique_ptr<Client> client) {
client_.swap(client);
}
void TargetHandler::Detached() {
SetAutoAttach(false, false);
SetDiscoverTargets(false);
for (const auto& id_host : attached_hosts_)
id_host.second->DetachClient(this);
attached_hosts_.clear();
}
void TargetHandler::UpdateServiceWorkers() {
UpdateServiceWorkers(false);
}
void TargetHandler::UpdateFrames() {
if (!auto_attach_ || !attach_to_frames_)
return;
HostsMap new_hosts;
if (render_frame_host_) {
FrameTreeNode* root = render_frame_host_->frame_tree_node();
std::queue<FrameTreeNode*> queue;
queue.push(root);
while (!queue.empty()) {
FrameTreeNode* node = queue.front();
queue.pop();
bool cross_process = node->current_frame_host()->IsCrossProcessSubframe();
if (node != root && cross_process) {
scoped_refptr<DevToolsAgentHost> new_host =
DevToolsAgentHost::GetOrCreateFor(node->current_frame_host());
new_hosts[new_host->GetId()] = new_host;
} else {
for (size_t i = 0; i < node->child_count(); ++i)
queue.push(node->child_at(i));
}
}
}
// TODO(dgozman): support wait_for_debugger_on_start_.
ReattachTargetsOfType(new_hosts, DevToolsAgentHost::kTypeFrame, false);
}
void TargetHandler::UpdateServiceWorkers(bool waiting_for_debugger) {
if (!auto_attach_)
return;
frame_urls_.clear();
BrowserContext* browser_context = nullptr;
if (render_frame_host_) {
// TODO(dgozman): do not traverse inside cross-process subframes.
for (FrameTreeNode* node :
render_frame_host_->frame_tree_node()->frame_tree()->Nodes()) {
frame_urls_.insert(node->current_url());
}
browser_context = render_frame_host_->GetProcess()->GetBrowserContext();
}
auto matching = GetMatchingServiceWorkers(browser_context, frame_urls_);
HostsMap new_hosts;
for (const auto& pair : matching)
new_hosts[pair.first] = pair.second;
ReattachTargetsOfType(
new_hosts, DevToolsAgentHost::kTypeServiceWorker, waiting_for_debugger);
}
void TargetHandler::ReattachTargetsOfType(
const HostsMap& new_hosts,
const std::string& type,
bool waiting_for_debugger) {
HostsMap old_hosts = attached_hosts_;
for (const auto& pair : old_hosts) {
if (pair.second->GetType() == type &&
new_hosts.find(pair.first) == new_hosts.end()) {
DetachFromTargetInternal(pair.second.get());
}
}
for (const auto& pair : new_hosts) {
if (old_hosts.find(pair.first) == old_hosts.end())
AttachToTargetInternal(pair.second.get(), waiting_for_debugger);
}
}
void TargetHandler::TargetCreatedInternal(DevToolsAgentHost* host) {
if (reported_hosts_.find(host->GetId()) != reported_hosts_.end())
return;
client_->TargetCreated(
TargetCreatedParams::Create()->set_target_info(CreateInfo(host)));
reported_hosts_[host->GetId()] = host;
}
void TargetHandler::TargetDestroyedInternal(
DevToolsAgentHost* host) {
auto it = reported_hosts_.find(host->GetId());
if (it == reported_hosts_.end())
return;
client_->TargetDestroyed(TargetDestroyedParams::Create()
->set_target_id(host->GetId()));
reported_hosts_.erase(it);
}
bool TargetHandler::AttachToTargetInternal(
DevToolsAgentHost* host, bool waiting_for_debugger) {
if (!host->AttachClient(this))
return false;
attached_hosts_[host->GetId()] = host;
client_->AttachedToTarget(AttachedToTargetParams::Create()
->set_target_info(CreateInfo(host))
->set_waiting_for_debugger(waiting_for_debugger));
return true;
}
void TargetHandler::DetachFromTargetInternal(DevToolsAgentHost* host) {
auto it = attached_hosts_.find(host->GetId());
if (it == attached_hosts_.end())
return;
host->DetachClient(this);
client_->DetachedFromTarget(DetachedFromTargetParams::Create()->
set_target_id(host->GetId()));
attached_hosts_.erase(it);
}
// ----------------- Protocol ----------------------
Response TargetHandler::SetDiscoverTargets(bool discover) {
if (discover_ == discover)
return Response::OK();
discover_ = discover;
if (discover_) {
DevToolsAgentHost::AddObserver(this);
} else {
DevToolsAgentHost::RemoveObserver(this);
RawHostsMap copy = reported_hosts_;
for (const auto& id_host : copy)
TargetDestroyedInternal(id_host.second);
}
return Response::OK();
}
Response TargetHandler::SetAutoAttach(
bool auto_attach, bool wait_for_debugger_on_start) {
wait_for_debugger_on_start_ = wait_for_debugger_on_start;
if (auto_attach_ == auto_attach)
return Response::FallThrough();
auto_attach_ = auto_attach;
if (auto_attach_) {
ServiceWorkerDevToolsManager::GetInstance()->AddObserver(this);
UpdateServiceWorkers();
UpdateFrames();
} else {
ServiceWorkerDevToolsManager::GetInstance()->RemoveObserver(this);
HostsMap empty;
ReattachTargetsOfType(empty, DevToolsAgentHost::kTypeFrame, false);
ReattachTargetsOfType(empty, DevToolsAgentHost::kTypeServiceWorker, false);
}
return Response::FallThrough();
}
Response TargetHandler::SetAttachToFrames(bool value) {
if (attach_to_frames_ == value)
return Response::OK();
attach_to_frames_ = value;
if (attach_to_frames_) {
UpdateFrames();
} else {
HostsMap empty;
ReattachTargetsOfType(empty, DevToolsAgentHost::kTypeFrame, false);
}
return Response::OK();
}
Response TargetHandler::SetRemoteLocations(
const std::vector<std::unique_ptr<base::DictionaryValue>>& locations) {
return Response::ServerError("Not supported");
}
Response TargetHandler::AttachToTarget(const std::string& target_id,
bool* out_success) {
// TODO(dgozman): only allow reported hosts.
scoped_refptr<DevToolsAgentHost> agent_host =
DevToolsAgentHost::GetForId(target_id);
if (!agent_host)
return Response::ServerError("No target with given id found");
*out_success = AttachToTargetInternal(agent_host.get(), false);
return Response::OK();
}
Response TargetHandler::DetachFromTarget(const std::string& target_id) {
auto it = attached_hosts_.find(target_id);
if (it == attached_hosts_.end())
return Response::InternalError("Not attached to the target");
DevToolsAgentHost* agent_host = it->second.get();
DetachFromTargetInternal(agent_host);
return Response::OK();
}
Response TargetHandler::SendMessageToTarget(
const std::string& target_id,
const std::string& message) {
auto it = attached_hosts_.find(target_id);
if (it == attached_hosts_.end())
return Response::FallThrough();
it->second->DispatchProtocolMessage(this, message);
return Response::OK();
}
Response TargetHandler::GetTargetInfo(
const std::string& target_id,
scoped_refptr<TargetInfo>* target_info) {
// TODO(dgozman): only allow reported hosts.
scoped_refptr<DevToolsAgentHost> agent_host(
DevToolsAgentHost::GetForId(target_id));
if (!agent_host)
return Response::InvalidParams("No target with given id found");
*target_info = CreateInfo(agent_host.get());
return Response::OK();
}
Response TargetHandler::ActivateTarget(const std::string& target_id) {
// TODO(dgozman): only allow reported hosts.
scoped_refptr<DevToolsAgentHost> agent_host(
DevToolsAgentHost::GetForId(target_id));
if (!agent_host)
return Response::InvalidParams("No target with given id found");
agent_host->Activate();
return Response::OK();
}
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::ServerError("No target with given id found");
*out_success = agent_host->Close();
return Response::OK();
}
Response TargetHandler::CreateBrowserContext(std::string* out_context_id) {
return Response::ServerError("Not supported");
}
Response TargetHandler::DisposeBrowserContext(const std::string& context_id,
bool* out_success) {
return Response::ServerError("Not supported");
}
Response TargetHandler::CreateTarget(const std::string& url,
const int* width,
const int* height,
const std::string* context_id,
std::string* out_target_id) {
DevToolsManagerDelegate* delegate =
DevToolsManager::GetInstance()->delegate();
if (!delegate)
return Response::ServerError("Not supported");
scoped_refptr<content::DevToolsAgentHost> agent_host =
delegate->CreateNewTarget(GURL(url));
if (!agent_host)
return Response::ServerError("Not supported");
*out_target_id = agent_host->GetId();
return Response::OK();
}
Response TargetHandler::GetTargets(
std::vector<scoped_refptr<TargetInfo>>* target_infos) {
for (const auto& host : DevToolsAgentHost::GetOrCreateAll())
target_infos->push_back(CreateInfo(host.get()));
return Response::OK();
}
// ---------------- DevToolsAgentHostClient ----------------
void TargetHandler::DispatchProtocolMessage(
DevToolsAgentHost* host,
const std::string& message) {
auto it = attached_hosts_.find(host->GetId());
if (it == attached_hosts_.end())
return; // Already disconnected.
client_->ReceivedMessageFromTarget(
ReceivedMessageFromTargetParams::Create()->
set_target_id(host->GetId())->
set_message(message));
}
void TargetHandler::AgentHostClosed(
DevToolsAgentHost* host,
bool replaced_with_another_client) {
client_->DetachedFromTarget(DetachedFromTargetParams::Create()->
set_target_id(host->GetId()));
attached_hosts_.erase(host->GetId());
}
// -------------- DevToolsAgentHostObserver -----------------
bool TargetHandler::ShouldForceDevToolsAgentHostCreation() {
return true;
}
void TargetHandler::DevToolsAgentHostCreated(DevToolsAgentHost* agent_host) {
DCHECK(attached_hosts_.find(agent_host->GetId()) == attached_hosts_.end());
TargetCreatedInternal(agent_host);
}
void TargetHandler::DevToolsAgentHostDestroyed(DevToolsAgentHost* agent_host) {
DCHECK(attached_hosts_.find(agent_host->GetId()) == attached_hosts_.end());
TargetDestroyedInternal(agent_host);
}
// -------- ServiceWorkerDevToolsManager::Observer ----------
void TargetHandler::WorkerCreated(
ServiceWorkerDevToolsAgentHost* host) {
BrowserContext* browser_context = nullptr;
if (render_frame_host_)
browser_context = render_frame_host_->GetProcess()->GetBrowserContext();
auto hosts = GetMatchingServiceWorkers(browser_context, frame_urls_);
if (hosts.find(host->GetId()) != hosts.end() && !host->IsAttached() &&
!host->IsPausedForDebugOnStart() && wait_for_debugger_on_start_) {
host->PauseForDebugOnStart();
}
}
void TargetHandler::WorkerReadyForInspection(
ServiceWorkerDevToolsAgentHost* host) {
if (ServiceWorkerDevToolsManager::GetInstance()
->debug_service_worker_on_start()) {
// When debug_service_worker_on_start is true, a new DevTools window will
// be opened in ServiceWorkerDevToolsManager::WorkerReadyForInspection.
return;
}
UpdateServiceWorkers(host->IsPausedForDebugOnStart());
}
void TargetHandler::WorkerVersionInstalled(
ServiceWorkerDevToolsAgentHost* host) {
UpdateServiceWorkers();
}
void TargetHandler::WorkerVersionDoomed(
ServiceWorkerDevToolsAgentHost* host) {
UpdateServiceWorkers();
}
void TargetHandler::WorkerDestroyed(
ServiceWorkerDevToolsAgentHost* host) {
UpdateServiceWorkers();
}
} // namespace target
} // namespace devtools
} // namespace content