blob: 5970c245e54f8640f4974bbe8e796926b8c46772 [file] [log] [blame]
// Copyright 2015 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/service_worker_handler.h"
#include "base/bind.h"
#include "base/containers/scoped_ptr_hash_map.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "content/browser/devtools/service_worker_devtools_agent_host.h"
#include "content/browser/devtools/service_worker_devtools_manager.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"
#include "content/browser/service_worker/service_worker_context_watcher.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/browser/service_worker/service_worker_version.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/devtools_agent_host.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/push_messaging_status.h"
#include "url/gurl.h"
// Windows headers will redefine SendMessage.
#ifdef SendMessage
#undef SendMessage
#endif
namespace content {
namespace devtools {
namespace service_worker {
using Response = DevToolsProtocolClient::Response;
namespace {
void ResultNoOp(bool success) {
}
void StatusNoOp(ServiceWorkerStatusCode status) {
}
void PushDeliveryNoOp(PushDeliveryStatus status) {
}
const std::string GetVersionRunningStatusString(
content::ServiceWorkerVersion::RunningStatus running_status) {
switch (running_status) {
case content::ServiceWorkerVersion::STOPPED:
return kServiceWorkerVersionRunningStatusStopped;
case content::ServiceWorkerVersion::STARTING:
return kServiceWorkerVersionRunningStatusStarting;
case content::ServiceWorkerVersion::RUNNING:
return kServiceWorkerVersionRunningStatusRunning;
case content::ServiceWorkerVersion::STOPPING:
return kServiceWorkerVersionRunningStatusStopping;
}
return std::string();
}
const std::string GetVersionStatusString(
content::ServiceWorkerVersion::Status status) {
switch (status) {
case content::ServiceWorkerVersion::NEW:
return kServiceWorkerVersionStatusNew;
case content::ServiceWorkerVersion::INSTALLING:
return kServiceWorkerVersionStatusInstalling;
case content::ServiceWorkerVersion::INSTALLED:
return kServiceWorkerVersionStatusInstalled;
case content::ServiceWorkerVersion::ACTIVATING:
return kServiceWorkerVersionStatusActivating;
case content::ServiceWorkerVersion::ACTIVATED:
return kServiceWorkerVersionStatusActivated;
case content::ServiceWorkerVersion::REDUNDANT:
return kServiceWorkerVersionStatusRedundant;
}
return std::string();
}
scoped_refptr<ServiceWorkerVersion> CreateVersionDictionaryValue(
const ServiceWorkerVersionInfo& version_info) {
std::vector<std::string> clients;
for (const auto& client : version_info.clients) {
if (client.second.type == SERVICE_WORKER_PROVIDER_FOR_WINDOW) {
RenderFrameHostImpl* render_frame_host = RenderFrameHostImpl::FromID(
client.second.process_id, client.second.route_id);
WebContents* web_contents =
WebContents::FromRenderFrameHost(render_frame_host);
// There is a possibility that the frame is already deleted because of the
// thread hopping.
if (!web_contents)
continue;
scoped_refptr<DevToolsAgentHost> agent_host(
DevToolsAgentHost::GetOrCreateFor(web_contents));
if (agent_host)
clients.push_back(agent_host->GetId());
} else if (client.second.type ==
SERVICE_WORKER_PROVIDER_FOR_SHARED_WORKER) {
scoped_refptr<DevToolsAgentHost> agent_host(
DevToolsAgentHost::GetForWorker(client.second.process_id,
client.second.route_id));
if (agent_host)
clients.push_back(agent_host->GetId());
}
}
scoped_refptr<ServiceWorkerVersion> version(
ServiceWorkerVersion::Create()
->set_version_id(base::Int64ToString(version_info.version_id))
->set_registration_id(
base::Int64ToString(version_info.registration_id))
->set_script_url(version_info.script_url.spec())
->set_running_status(
GetVersionRunningStatusString(version_info.running_status))
->set_status(GetVersionStatusString(version_info.status))
->set_script_last_modified(
version_info.script_last_modified.ToDoubleT())
->set_script_response_time(
version_info.script_response_time.ToDoubleT())
->set_controlled_clients(clients));
return version;
}
scoped_refptr<ServiceWorkerRegistration> CreateRegistrationDictionaryValue(
const ServiceWorkerRegistrationInfo& registration_info) {
scoped_refptr<ServiceWorkerRegistration> registration(
ServiceWorkerRegistration::Create()
->set_registration_id(
base::Int64ToString(registration_info.registration_id))
->set_scope_url(registration_info.pattern.spec())
->set_is_deleted(registration_info.delete_flag ==
ServiceWorkerRegistrationInfo::IS_DELETED));
return registration;
}
scoped_refptr<ServiceWorkerDevToolsAgentHost> GetMatchingServiceWorker(
const ServiceWorkerDevToolsAgentHost::List& agent_hosts,
const GURL& url) {
scoped_refptr<ServiceWorkerDevToolsAgentHost> best_host;
std::string best_scope;
for (auto host : agent_hosts) {
if (host->GetURL().host() != url.host())
continue;
std::string path = host->GetURL().path();
std::string file = host->GetURL().ExtractFileName();
std::string scope = path.substr(0, path.length() - file.length());
if (scope.length() > best_scope.length()) {
best_host = host;
best_scope = scope;
}
}
return best_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);
for (const GURL& url : urls) {
scoped_refptr<ServiceWorkerDevToolsAgentHost> host =
GetMatchingServiceWorker(agent_hosts, url);
if (host)
result[host->GetId()] = host;
}
return result;
}
bool CollectURLs(std::set<GURL>* urls, FrameTreeNode* tree_node) {
urls->insert(tree_node->current_url());
return false;
}
void StopServiceWorkerOnIO(scoped_refptr<ServiceWorkerContextWrapper> context,
int64 version_id) {
if (content::ServiceWorkerVersion* version =
context->GetLiveVersion(version_id)) {
version->StopWorker(base::Bind(&StatusNoOp));
}
}
void GetDevToolsRouteInfoOnIO(
scoped_refptr<ServiceWorkerContextWrapper> context,
int64 version_id,
const base::Callback<void(int, int)>& callback) {
if (content::ServiceWorkerVersion* version =
context->GetLiveVersion(version_id)) {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(
callback, version->embedded_worker()->process_id(),
version->embedded_worker()->worker_devtools_agent_route_id()));
}
}
Response CreateContextErrorResponse() {
return Response::InternalError("Could not connect to the context");
}
Response CreateInvalidVersionIdErrorResponse() {
return Response::InternalError("Invalid version ID");
}
const std::string GetDevToolsAgentHostTypeString(
content::DevToolsAgentHost::Type type) {
switch (type) {
case DevToolsAgentHost::TYPE_WEB_CONTENTS:
return "web_contents";
case DevToolsAgentHost::TYPE_FRAME:
return "frame";
case DevToolsAgentHost::TYPE_SHARED_WORKER:
return "shared_worker";
case DevToolsAgentHost::TYPE_SERVICE_WORKER:
return "service_worker";
case DevToolsAgentHost::TYPE_EXTERNAL:
return "external";
case DevToolsAgentHost::TYPE_BROWSER:
return "browser";
}
NOTREACHED() << type;
return std::string();
}
} // namespace
ServiceWorkerHandler::ServiceWorkerHandler()
: enabled_(false), render_frame_host_(nullptr), weak_factory_(this) {
}
ServiceWorkerHandler::~ServiceWorkerHandler() {
Disable();
}
void ServiceWorkerHandler::SetRenderFrameHost(
RenderFrameHostImpl* render_frame_host) {
render_frame_host_ = render_frame_host;
// Do not call UpdateHosts yet, wait for load to commit.
if (!render_frame_host) {
context_ = nullptr;
return;
}
StoragePartition* partition = BrowserContext::GetStoragePartition(
render_frame_host->GetProcess()->GetBrowserContext(),
render_frame_host->GetSiteInstance());
DCHECK(partition);
context_ = static_cast<ServiceWorkerContextWrapper*>(
partition->GetServiceWorkerContext());
}
void ServiceWorkerHandler::SetClient(scoped_ptr<Client> client) {
client_.swap(client);
}
void ServiceWorkerHandler::UpdateHosts() {
if (!enabled_)
return;
urls_.clear();
BrowserContext* browser_context = nullptr;
if (render_frame_host_) {
render_frame_host_->frame_tree_node()->frame_tree()->ForEach(
base::Bind(&CollectURLs, &urls_));
browser_context = render_frame_host_->GetProcess()->GetBrowserContext();
}
ServiceWorkerDevToolsAgentHost::Map old_hosts = attached_hosts_;
ServiceWorkerDevToolsAgentHost::Map new_hosts =
GetMatchingServiceWorkers(browser_context, urls_);
for (auto pair : old_hosts) {
if (new_hosts.find(pair.first) == new_hosts.end())
ReportWorkerTerminated(pair.second.get());
}
for (auto pair : new_hosts) {
if (old_hosts.find(pair.first) == old_hosts.end())
ReportWorkerCreated(pair.second.get());
}
}
void ServiceWorkerHandler::Detached() {
Disable();
}
Response ServiceWorkerHandler::Enable() {
if (enabled_)
return Response::OK();
if (!context_)
return Response::InternalError("Could not connect to the context");
enabled_ = true;
ServiceWorkerDevToolsManager::GetInstance()->AddObserver(this);
client_->DebugOnStartUpdated(
DebugOnStartUpdatedParams::Create()->set_debug_on_start(
ServiceWorkerDevToolsManager::GetInstance()
->debug_service_worker_on_start()));
context_watcher_ = new ServiceWorkerContextWatcher(
context_, base::Bind(&ServiceWorkerHandler::OnWorkerRegistrationUpdated,
weak_factory_.GetWeakPtr()),
base::Bind(&ServiceWorkerHandler::OnWorkerVersionUpdated,
weak_factory_.GetWeakPtr()),
base::Bind(&ServiceWorkerHandler::OnErrorReported,
weak_factory_.GetWeakPtr()));
context_watcher_->Start();
UpdateHosts();
return Response::OK();
}
Response ServiceWorkerHandler::Disable() {
if (!enabled_)
return Response::OK();
enabled_ = false;
ServiceWorkerDevToolsManager::GetInstance()->RemoveObserver(this);
for (const auto& pair : attached_hosts_)
pair.second->DetachClient();
attached_hosts_.clear();
DCHECK(context_watcher_);
context_watcher_->Stop();
context_watcher_ = nullptr;
return Response::OK();
}
Response ServiceWorkerHandler::SendMessage(
const std::string& worker_id,
const std::string& message) {
auto it = attached_hosts_.find(worker_id);
if (it == attached_hosts_.end())
return Response::InternalError("Not connected to the worker");
it->second->DispatchProtocolMessage(message);
return Response::OK();
}
Response ServiceWorkerHandler::Stop(
const std::string& worker_id) {
auto it = attached_hosts_.find(worker_id);
if (it == attached_hosts_.end())
return Response::InternalError("Not connected to the worker");
it->second->UnregisterWorker();
return Response::OK();
}
Response ServiceWorkerHandler::Unregister(const std::string& scope_url) {
if (!enabled_)
return Response::OK();
if (!context_)
return CreateContextErrorResponse();
context_->UnregisterServiceWorker(GURL(scope_url), base::Bind(&ResultNoOp));
return Response::OK();
}
Response ServiceWorkerHandler::StartWorker(const std::string& scope_url) {
if (!enabled_)
return Response::OK();
if (!context_)
return CreateContextErrorResponse();
context_->StartServiceWorker(GURL(scope_url), base::Bind(&StatusNoOp));
return Response::OK();
}
Response ServiceWorkerHandler::StopWorker(const std::string& version_id) {
if (!enabled_)
return Response::OK();
if (!context_)
return CreateContextErrorResponse();
int64 id = 0;
if (!base::StringToInt64(version_id, &id))
return CreateInvalidVersionIdErrorResponse();
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::Bind(&StopServiceWorkerOnIO, context_, id));
return Response::OK();
}
Response ServiceWorkerHandler::UpdateRegistration(
const std::string& scope_url) {
if (!enabled_)
return Response::OK();
if (!context_)
return CreateContextErrorResponse();
context_->UpdateRegistration(GURL(scope_url));
return Response::OK();
}
Response ServiceWorkerHandler::InspectWorker(const std::string& version_id) {
if (!enabled_)
return Response::OK();
if (!context_)
return CreateContextErrorResponse();
int64 id = 0;
if (!base::StringToInt64(version_id, &id))
return CreateInvalidVersionIdErrorResponse();
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&GetDevToolsRouteInfoOnIO, context_, id,
base::Bind(&ServiceWorkerHandler::OpenNewDevToolsWindow,
weak_factory_.GetWeakPtr())));
return Response::OK();
}
Response ServiceWorkerHandler::SkipWaiting(const std::string& version_id) {
if (!enabled_)
return Response::OK();
if (!context_)
return CreateContextErrorResponse();
int64 id = 0;
if (!base::StringToInt64(version_id, &id))
return CreateInvalidVersionIdErrorResponse();
context_->SimulateSkipWaiting(id);
return Response::OK();
}
Response ServiceWorkerHandler::SetDebugOnStart(bool debug_on_start) {
ServiceWorkerDevToolsManager::GetInstance()
->set_debug_service_worker_on_start(debug_on_start);
return Response::OK();
}
Response ServiceWorkerHandler::DeliverPushMessage(
const std::string& origin,
const std::string& registration_id,
const std::string& data) {
if (!enabled_)
return Response::OK();
if (!render_frame_host_)
return CreateContextErrorResponse();
int64 id = 0;
if (!base::StringToInt64(registration_id, &id))
return CreateInvalidVersionIdErrorResponse();
BrowserContext::DeliverPushMessage(
render_frame_host_->GetProcess()->GetBrowserContext(), GURL(origin), id,
data, base::Bind(&PushDeliveryNoOp));
return Response::OK();
}
Response ServiceWorkerHandler::GetTargetInfo(
const std::string& target_id,
scoped_refptr<TargetInfo>* target_info) {
scoped_refptr<DevToolsAgentHost> agent_host(
DevToolsAgentHost::GetForId(target_id));
if (!agent_host)
return Response::InvalidParams("targetId");
*target_info =
TargetInfo::Create()
->set_id(agent_host->GetId())
->set_type(GetDevToolsAgentHostTypeString(agent_host->GetType()))
->set_title(agent_host->GetTitle())
->set_url(agent_host->GetURL().spec());
return Response::OK();
}
Response ServiceWorkerHandler::ActivateTarget(const std::string& target_id) {
scoped_refptr<DevToolsAgentHost> agent_host(
DevToolsAgentHost::GetForId(target_id));
if (!agent_host)
return Response::InvalidParams("targetId");
agent_host->Activate();
return Response::OK();
}
void ServiceWorkerHandler::OpenNewDevToolsWindow(int process_id,
int devtools_agent_route_id) {
scoped_refptr<DevToolsAgentHostImpl> agent_host(
ServiceWorkerDevToolsManager::GetInstance()
->GetDevToolsAgentHostForWorker(process_id, devtools_agent_route_id));
if (!agent_host.get())
return;
agent_host->Inspect(render_frame_host_->GetProcess()->GetBrowserContext());
}
void ServiceWorkerHandler::OnWorkerRegistrationUpdated(
const std::vector<ServiceWorkerRegistrationInfo>& registrations) {
std::vector<scoped_refptr<ServiceWorkerRegistration>> registration_values;
for (const auto& registration : registrations) {
registration_values.push_back(
CreateRegistrationDictionaryValue(registration));
}
client_->WorkerRegistrationUpdated(
WorkerRegistrationUpdatedParams::Create()->set_registrations(
registration_values));
}
void ServiceWorkerHandler::OnWorkerVersionUpdated(
const std::vector<ServiceWorkerVersionInfo>& versions) {
std::vector<scoped_refptr<ServiceWorkerVersion>> version_values;
for (const auto& version : versions) {
version_values.push_back(CreateVersionDictionaryValue(version));
}
client_->WorkerVersionUpdated(
WorkerVersionUpdatedParams::Create()->set_versions(version_values));
}
void ServiceWorkerHandler::OnErrorReported(
int64 registration_id,
int64 version_id,
const ServiceWorkerContextObserver::ErrorInfo& info) {
client_->WorkerErrorReported(
WorkerErrorReportedParams::Create()->set_error_message(
ServiceWorkerErrorMessage::Create()
->set_error_message(base::UTF16ToUTF8(info.error_message))
->set_registration_id(base::Int64ToString(registration_id))
->set_version_id(base::Int64ToString(version_id))
->set_source_url(info.source_url.spec())
->set_line_number(info.line_number)
->set_column_number(info.column_number)));
}
void ServiceWorkerHandler::DispatchProtocolMessage(
DevToolsAgentHost* host,
const std::string& message) {
auto it = attached_hosts_.find(host->GetId());
if (it == attached_hosts_.end())
return; // Already disconnected.
client_->DispatchMessage(
DispatchMessageParams::Create()->
set_worker_id(host->GetId())->
set_message(message));
}
void ServiceWorkerHandler::AgentHostClosed(
DevToolsAgentHost* host,
bool replaced_with_another_client) {
client_->WorkerTerminated(WorkerTerminatedParams::Create()->
set_worker_id(host->GetId()));
attached_hosts_.erase(host->GetId());
}
void ServiceWorkerHandler::WorkerCreated(
ServiceWorkerDevToolsAgentHost* host) {
BrowserContext* browser_context = nullptr;
if (render_frame_host_)
browser_context = render_frame_host_->GetProcess()->GetBrowserContext();
auto hosts = GetMatchingServiceWorkers(browser_context, urls_);
if (hosts.find(host->GetId()) != hosts.end() && !host->IsAttached() &&
!host->IsPausedForDebugOnStart())
host->PauseForDebugOnStart();
}
void ServiceWorkerHandler::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 opend in ServiceWorkerDevToolsManager::WorkerReadyForInspection.
return;
}
UpdateHosts();
}
void ServiceWorkerHandler::WorkerDestroyed(
ServiceWorkerDevToolsAgentHost* host) {
UpdateHosts();
}
void ServiceWorkerHandler::DebugOnStartUpdated(bool debug_on_start) {
client_->DebugOnStartUpdated(
DebugOnStartUpdatedParams::Create()->set_debug_on_start(debug_on_start));
}
void ServiceWorkerHandler::ReportWorkerCreated(
ServiceWorkerDevToolsAgentHost* host) {
if (host->IsAttached())
return;
attached_hosts_[host->GetId()] = host;
host->AttachClient(this);
client_->WorkerCreated(WorkerCreatedParams::Create()->
set_worker_id(host->GetId())->
set_url(host->GetURL().spec()));
}
void ServiceWorkerHandler::ReportWorkerTerminated(
ServiceWorkerDevToolsAgentHost* host) {
auto it = attached_hosts_.find(host->GetId());
if (it == attached_hosts_.end())
return;
host->DetachClient();
client_->WorkerTerminated(WorkerTerminatedParams::Create()->
set_worker_id(host->GetId()));
attached_hosts_.erase(it);
}
} // namespace service_worker
} // namespace devtools
} // namespace content