From b51b266851c5aa7b8f77cda1d4ce54c012b844d0 Mon Sep 17 00:00:00 2001 From: yzshen Date: Tue, 26 May 2015 09:51:08 -0700 Subject: [PATCH] Mandoline DevTools service - part 1 This CL: - adds DevToolsCoordinatorImpl to handle DevToolsCoordinator interface calls and start HTTP server. - adds command-line flag remote-debugging-port. This CL doesn't make the DevTools service handle HTTP/WebSocket messages, which will be in follow-up CLs. BUG=478249 Review URL: https://codereview.chromium.org/1148383006 Cr-Commit-Position: refs/heads/master@{#331380} --- components/devtools_service/BUILD.gn | 5 + components/devtools_service/DEPS | 1 + .../devtools_coordinator_impl.cc | 177 ++++++++++++++++++ .../devtools_coordinator_impl.h | 82 ++++++++ .../devtools_service_delegate.cc | 44 ++++- .../devtools_service_delegate.h | 21 ++- mojo/runner/BUILD.gn | 6 +- mojo/runner/DEPS | 1 + mojo/runner/context.cc | 32 ++++ mojo/runner/switches.cc | 3 + mojo/runner/switches.h | 1 + 11 files changed, 364 insertions(+), 9 deletions(-) create mode 100644 components/devtools_service/devtools_coordinator_impl.cc create mode 100644 components/devtools_service/devtools_coordinator_impl.h diff --git a/components/devtools_service/BUILD.gn b/components/devtools_service/BUILD.gn index 22a27a8d7f4cdd..e39604d1694160 100644 --- a/components/devtools_service/BUILD.gn +++ b/components/devtools_service/BUILD.gn @@ -6,6 +6,8 @@ import("//third_party/mojo/src/mojo/public/mojo_application.gni") source_set("lib") { sources = [ + "devtools_coordinator_impl.cc", + "devtools_coordinator_impl.h", "devtools_service_delegate.cc", "devtools_service_delegate.h", ] @@ -14,7 +16,10 @@ source_set("lib") { "//base", "//components/devtools_service/public/interfaces", "//mojo/application/public/cpp", + "//mojo/common", + "//mojo/services/network/public/interfaces", "//third_party/mojo/src/mojo/public/cpp/bindings", + "//url", ] } diff --git a/components/devtools_service/DEPS b/components/devtools_service/DEPS index 646c49d985437a..98969e399b181a 100644 --- a/components/devtools_service/DEPS +++ b/components/devtools_service/DEPS @@ -1,5 +1,6 @@ include_rules = [ "+mojo/application", "+mojo/common", + "+mojo/services/network/public", "+third_party/mojo/src/mojo/public", ] diff --git a/components/devtools_service/devtools_coordinator_impl.cc b/components/devtools_service/devtools_coordinator_impl.cc new file mode 100644 index 00000000000000..a477f48920637f --- /dev/null +++ b/components/devtools_service/devtools_coordinator_impl.cc @@ -0,0 +1,177 @@ +// 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 "components/devtools_service/devtools_coordinator_impl.h" + +#include "base/logging.h" +#include "base/stl_util.h" +#include "base/strings/stringprintf.h" +#include "mojo/application/public/cpp/application_impl.h" +#include "mojo/services/network/public/interfaces/http_message.mojom.h" +#include "mojo/services/network/public/interfaces/net_address.mojom.h" +#include "mojo/services/network/public/interfaces/network_service.mojom.h" + +namespace devtools_service { + +class DevToolsCoordinatorImpl::HttpConnectionDelegateImpl + : public mojo::HttpConnectionDelegate, + public mojo::ErrorHandler { + public: + HttpConnectionDelegateImpl( + DevToolsCoordinatorImpl* owner, + mojo::HttpConnectionPtr connection, + mojo::InterfaceRequest delegate_request) + : owner_(owner), + connection_(connection.Pass()), + binding_(this, delegate_request.Pass()) { + DCHECK(owner_); + DCHECK(connection_); + DCHECK(binding_.is_bound()); + + connection_.set_error_handler(this); + binding_.set_error_handler(this); + } + + mojo::HttpConnection* connection() { return connection_.get(); } + + private: + // mojo::HttpConnectionDelegate implementation: + void OnReceivedRequest(mojo::HttpRequestPtr request, + const OnReceivedRequestCallback& callback) override { + owner_->OnReceivedRequest(this, request.Pass(), callback); + } + + void OnReceivedWebSocketRequest( + mojo::HttpRequestPtr request, + const OnReceivedWebSocketRequestCallback& callback) override { + owner_->OnReceivedWebSocketRequest(this, request.Pass(), callback); + } + + // mojo::ErrorHandler implementation. + void OnConnectionError() override { owner_->OnConnectionClosed(this); } + + DevToolsCoordinatorImpl* const owner_; + mojo::HttpConnectionPtr connection_; + mojo::Binding binding_; + + DISALLOW_COPY_AND_ASSIGN(HttpConnectionDelegateImpl); +}; + +DevToolsCoordinatorImpl::DevToolsCoordinatorImpl( + mojo::ApplicationImpl* application) + : application_(application) { + DCHECK(application_); +} + +DevToolsCoordinatorImpl::~DevToolsCoordinatorImpl() { + STLDeleteElements(&connections_); +} + +void DevToolsCoordinatorImpl::CreateAgentClient( + mojo::InterfaceRequest request) { + if (!IsInitialized()) { + // Ignore the request if remote debugging is not needed. + return; + } + + // TODO(yzshen): Implement it. + NOTIMPLEMENTED(); +} + +void DevToolsCoordinatorImpl::BindToCoordinatorRequest( + mojo::InterfaceRequest request) { + coordinator_bindings_.AddBinding(this, request.Pass()); +} + +void DevToolsCoordinatorImpl::Initialize(uint16_t remote_debugging_port) { + if (IsInitialized()) { + LOG(WARNING) << "DevTools service receives a " + << "DevToolsCoordinator.Initialize() call while it has " + << "already been initialized."; + return; + } + + VLOG(1) << "Remote debugging HTTP server is started on port " + << remote_debugging_port << "."; + mojo::NetworkServicePtr network_service; + mojo::URLRequestPtr request(mojo::URLRequest::New()); + request->url = "mojo:network_service"; + application_->ConnectToService(request.Pass(), &network_service); + + mojo::NetAddressPtr local_address(mojo::NetAddress::New()); + local_address->family = mojo::NET_ADDRESS_FAMILY_IPV4; + local_address->ipv4 = mojo::NetAddressIPv4::New(); + local_address->ipv4->port = remote_debugging_port; + local_address->ipv4->addr.resize(4); + local_address->ipv4->addr[0] = 127; + local_address->ipv4->addr[1] = 0; + local_address->ipv4->addr[2] = 0; + local_address->ipv4->addr[3] = 1; + + mojo::HttpServerDelegatePtr http_server_delegate; + http_server_delegate_binding_.reset( + new mojo::Binding(this, &http_server_delegate)); + network_service->CreateHttpServer( + local_address.Pass(), http_server_delegate.Pass(), + mojo::NetworkService::CreateHttpServerCallback()); +} + +void DevToolsCoordinatorImpl::OnConnected( + mojo::HttpConnectionPtr connection, + mojo::InterfaceRequest delegate) { + connections_.insert( + new HttpConnectionDelegateImpl(this, connection.Pass(), delegate.Pass())); +} + +void DevToolsCoordinatorImpl::OnReceivedRequest( + HttpConnectionDelegateImpl* connection, + mojo::HttpRequestPtr request, + const OnReceivedRequestCallback& callback) { + DCHECK(connections_.find(connection) != connections_.end()); + + // TODO(yzshen): Implement it. + static const char kNotImplemented[] = "Not implemented yet!"; + mojo::HttpResponsePtr response(mojo::HttpResponse::New()); + response->headers.resize(2); + response->headers[0] = mojo::HttpHeader::New(); + response->headers[0]->name = "Content-Length"; + response->headers[0]->value = base::StringPrintf( + "%lu", static_cast(sizeof(kNotImplemented))); + response->headers[1] = mojo::HttpHeader::New(); + response->headers[1]->name = "Content-Type"; + response->headers[1]->value = "text/html"; + + uint32_t num_bytes = sizeof(kNotImplemented); + MojoCreateDataPipeOptions options; + options.struct_size = sizeof(MojoCreateDataPipeOptions); + options.flags = MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE; + options.element_num_bytes = 1; + options.capacity_num_bytes = num_bytes; + mojo::DataPipe data_pipe(options); + response->body = data_pipe.consumer_handle.Pass(); + WriteDataRaw(data_pipe.producer_handle.get(), kNotImplemented, &num_bytes, + MOJO_WRITE_DATA_FLAG_ALL_OR_NONE); + + callback.Run(response.Pass()); +} + +void DevToolsCoordinatorImpl::OnReceivedWebSocketRequest( + HttpConnectionDelegateImpl* connection, + mojo::HttpRequestPtr request, + const OnReceivedWebSocketRequestCallback& callback) { + DCHECK(connections_.find(connection) != connections_.end()); + + // TODO(yzshen): Implement it. + NOTIMPLEMENTED(); +} + +void DevToolsCoordinatorImpl::OnConnectionClosed( + HttpConnectionDelegateImpl* connection) { + DCHECK(connections_.find(connection) != connections_.end()); + + delete connection; + connections_.erase(connection); +} + +} // namespace devtools_service diff --git a/components/devtools_service/devtools_coordinator_impl.h b/components/devtools_service/devtools_coordinator_impl.h new file mode 100644 index 00000000000000..582f0df7d2c02b --- /dev/null +++ b/components/devtools_service/devtools_coordinator_impl.h @@ -0,0 +1,82 @@ +// 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. + +#ifndef COMPONENTS_DEVTOOLS_SERVICE_DEVTOOLS_COORDINATOR_IMPL_H_ +#define COMPONENTS_DEVTOOLS_SERVICE_DEVTOOLS_COORDINATOR_IMPL_H_ + +#include + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "components/devtools_service/public/interfaces/devtools_service.mojom.h" +#include "mojo/common/weak_binding_set.h" +#include "mojo/services/network/public/interfaces/http_connection.mojom.h" +#include "mojo/services/network/public/interfaces/http_server.mojom.h" + +namespace mojo { +class ApplicationImpl; +} + +namespace devtools_service { + +// DevToolsCoordinatorImpl is the central control of the DevTools service. It +// manages the communication with DevTools agents (e.g., Web page renderers). It +// also starts an HTTP server to speak the Chrome remote debugging protocol. +class DevToolsCoordinatorImpl : public DevToolsCoordinator, + public mojo::HttpServerDelegate { + public: + // Doesn't take ownership of |application|, which must outlive this object. + explicit DevToolsCoordinatorImpl(mojo::ApplicationImpl* application); + ~DevToolsCoordinatorImpl() override; + + void CreateAgentClient(mojo::InterfaceRequest request); + void BindToCoordinatorRequest( + mojo::InterfaceRequest request); + + private: + class HttpConnectionDelegateImpl; + + // DevToolsCoordinator implementation. + void Initialize(uint16_t remote_debugging_port) override; + + // mojo::HttpServerDelegate implementation. + void OnConnected( + mojo::HttpConnectionPtr connection, + mojo::InterfaceRequest delegate) override; + + bool IsInitialized() const { return !!http_server_delegate_binding_; } + + // The following methods are called by HttpConnectionDelegateImpl. + using OnReceivedRequestCallback = + mojo::HttpConnectionDelegate::OnReceivedRequestCallback; + void OnReceivedRequest(HttpConnectionDelegateImpl* connection, + mojo::HttpRequestPtr request, + const OnReceivedRequestCallback& callback); + + using OnReceivedWebSocketRequestCallback = + mojo::HttpConnectionDelegate::OnReceivedWebSocketRequestCallback; + void OnReceivedWebSocketRequest( + HttpConnectionDelegateImpl* connection, + mojo::HttpRequestPtr request, + const OnReceivedWebSocketRequestCallback& callback); + + void OnConnectionClosed(HttpConnectionDelegateImpl* connection); + + // Not owned by this object. + mojo::ApplicationImpl* const application_; + + mojo::WeakBindingSet coordinator_bindings_; + + scoped_ptr> + http_server_delegate_binding_; + + // Owns the elements. + std::set connections_; + + DISALLOW_COPY_AND_ASSIGN(DevToolsCoordinatorImpl); +}; + +} // namespace devtools_service + +#endif // COMPONENTS_DEVTOOLS_SERVICE_DEVTOOLS_COORDINATOR_IMPL_H_ diff --git a/components/devtools_service/devtools_service_delegate.cc b/components/devtools_service/devtools_service_delegate.cc index 6e8c6d32be156a..19759c649c07c7 100644 --- a/components/devtools_service/devtools_service_delegate.cc +++ b/components/devtools_service/devtools_service_delegate.cc @@ -5,27 +5,59 @@ #include "components/devtools_service/devtools_service_delegate.h" #include "base/logging.h" +#include "components/devtools_service/devtools_coordinator_impl.h" #include "mojo/application/public/cpp/application_connection.h" #include "mojo/application/public/cpp/application_impl.h" +#include "mojo/common/url_type_converters.h" +#include "url/gurl.h" namespace devtools_service { -DevToolsServiceDelegate::DevToolsServiceDelegate() {} +namespace { -DevToolsServiceDelegate::~DevToolsServiceDelegate() {} +bool IsShell(const GURL& requestor_url) { + // TODO(yzshen): http://crbug.com/491656 "mojo://shell/" has to be used + // instead of "mojo:shell" because "mojo" is not treated as a standard scheme. + return requestor_url == GURL("mojo://shell/"); +} + +} // namespace + +DevToolsServiceDelegate::DevToolsServiceDelegate() { +} + +DevToolsServiceDelegate::~DevToolsServiceDelegate() { +} void DevToolsServiceDelegate::Initialize(mojo::ApplicationImpl* app) { - NOTIMPLEMENTED(); + coordinator_.reset(new DevToolsCoordinatorImpl(app)); } bool DevToolsServiceDelegate::ConfigureIncomingConnection( mojo::ApplicationConnection* connection) { - NOTIMPLEMENTED(); - return false; + connection->AddService(this); + + // DevToolsCoordinator is a privileged interface and only allowed for the + // shell. + if (IsShell(GURL(connection->GetRemoteApplicationURL()))) + connection->AddService(this); + return true; } void DevToolsServiceDelegate::Quit() { - NOTIMPLEMENTED(); + coordinator_.reset(); +} + +void DevToolsServiceDelegate::Create( + mojo::ApplicationConnection* connection, + mojo::InterfaceRequest request) { + coordinator_->CreateAgentClient(request.Pass()); +} + +void DevToolsServiceDelegate::Create( + mojo::ApplicationConnection* connection, + mojo::InterfaceRequest request) { + coordinator_->BindToCoordinatorRequest(request.Pass()); } } // namespace devtools_service diff --git a/components/devtools_service/devtools_service_delegate.h b/components/devtools_service/devtools_service_delegate.h index e0958a7a771211..0a0d17f47a44ad 100644 --- a/components/devtools_service/devtools_service_delegate.h +++ b/components/devtools_service/devtools_service_delegate.h @@ -6,23 +6,40 @@ #define COMPONENTS_DEVTOOLS_SERVICE_DEVTOOLS_SERVICE_DELEGATE_H_ #include "base/macros.h" +#include "base/memory/scoped_ptr.h" #include "components/devtools_service/public/interfaces/devtools_service.mojom.h" #include "mojo/application/public/cpp/application_delegate.h" +#include "mojo/application/public/cpp/interface_factory.h" namespace devtools_service { -class DevToolsServiceDelegate : public mojo::ApplicationDelegate { +class DevToolsCoordinatorImpl; + +class DevToolsServiceDelegate + : public mojo::ApplicationDelegate, + public mojo::InterfaceFactory, + public mojo::InterfaceFactory { public: DevToolsServiceDelegate(); ~DevToolsServiceDelegate() override; private: - // ApplicationDelegate implementation. + // mojo::ApplicationDelegate implementation. void Initialize(mojo::ApplicationImpl* app) override; bool ConfigureIncomingConnection( mojo::ApplicationConnection* connection) override; void Quit() override; + // mojo::InterfaceFactory implementation. + void Create(mojo::ApplicationConnection* connection, + mojo::InterfaceRequest request) override; + + // mojo::InterfaceFactory implementation. + void Create(mojo::ApplicationConnection* connection, + mojo::InterfaceRequest request) override; + + scoped_ptr coordinator_; + DISALLOW_COPY_AND_ASSIGN(DevToolsServiceDelegate); }; diff --git a/mojo/runner/BUILD.gn b/mojo/runner/BUILD.gn index a486b146a714d8..5e14a089b2d5f9 100644 --- a/mojo/runner/BUILD.gn +++ b/mojo/runner/BUILD.gn @@ -123,6 +123,7 @@ source_set("lib") { "//base", "//base/third_party/dynamic_annotations", "//base:base_static", + "//components/devtools_service/public/interfaces", "//mojo/application/public/cpp", "//mojo/common:tracing_impl", "//mojo/util:filename_util", @@ -138,7 +139,10 @@ source_set("lib") { ":switches", ] - data_deps = [ "//mojo/services/tracing" ] + data_deps = [ + "//components/devtools_service", + "//mojo/services/tracing", + ] if (is_android) { sources += [ diff --git a/mojo/runner/DEPS b/mojo/runner/DEPS index 759dccd31eb150..efce97414e3edc 100644 --- a/mojo/runner/DEPS +++ b/mojo/runner/DEPS @@ -1,4 +1,5 @@ include_rules = [ + "+components/devtools_service/public", "+components/view_manager", "+components/view_manager/gles2", "+components/view_manager/native_viewport", diff --git a/mojo/runner/context.cc b/mojo/runner/context.cc index 7c401b72a0ec47..649031bd49bf4a 100644 --- a/mojo/runner/context.cc +++ b/mojo/runner/context.cc @@ -15,10 +15,12 @@ #include "base/memory/scoped_vector.h" #include "base/path_service.h" #include "base/run_loop.h" +#include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/trace_event/trace_event.h" #include "build/build_config.h" +#include "components/devtools_service/public/interfaces/devtools_service.mojom.h" #include "mojo/application/public/cpp/application_connection.h" #include "mojo/application/public/cpp/application_delegate.h" #include "mojo/application/public/cpp/application_impl.h" @@ -170,6 +172,34 @@ void InitNativeOptions(shell::ApplicationManager* manager, } } +void InitDevToolsServiceIfNeeded(shell::ApplicationManager* manager, + const base::CommandLine& command_line) { + if (!command_line.HasSwitch(switches::kRemoteDebuggingPort)) + return; + + std::string port_str = + command_line.GetSwitchValueASCII(switches::kRemoteDebuggingPort); + unsigned port; + if (!base::StringToUint(port_str, &port) || port > 65535) { + LOG(ERROR) << "Invalid value for switch " << switches::kRemoteDebuggingPort + << ": '" << port_str << "' is not a valid port number."; + return; + } + + ServiceProviderPtr devtools_service_provider; + URLRequestPtr request(URLRequest::New()); + request->url = "mojo:devtools_service"; + manager->ConnectToApplication(request.Pass(), GURL("mojo:shell"), + GetProxy(&devtools_service_provider), nullptr, + base::Closure()); + + devtools_service::DevToolsCoordinatorPtr devtools_coordinator; + devtools_service_provider->ConnectToService( + devtools_service::DevToolsCoordinator::Name_, + GetProxy(&devtools_coordinator).PassMessagePipe()); + devtools_coordinator->Initialize(static_cast(port)); +} + class TracingServiceProvider : public ServiceProvider { public: explicit TracingServiceProvider(InterfaceRequest request) @@ -278,6 +308,8 @@ bool Context::Init() { tracing_service_provider_ptr.Pass(), base::Closure()); + InitDevToolsServiceIfNeeded(&application_manager_, command_line); + return true; } diff --git a/mojo/runner/switches.cc b/mojo/runner/switches.cc index 1ed957ec293d8c..5d400807182a41 100644 --- a/mojo/runner/switches.cc +++ b/mojo/runner/switches.cc @@ -40,6 +40,9 @@ const char kMapOrigin[] = "map-origin"; // url_resolver.cc for details. const char kOrigin[] = "origin"; +// Enables remote debug over HTTP on the specified port. +const char kRemoteDebuggingPort[] = "remote-debugging-port"; + // Starts tracing when the shell starts up, saving a trace file on disk after 5 // seconds or when the shell exits. const char kTraceStartup[] = "trace-startup"; diff --git a/mojo/runner/switches.h b/mojo/runner/switches.h index a95a4b229c9427..3c3da6003b8a97 100644 --- a/mojo/runner/switches.h +++ b/mojo/runner/switches.h @@ -20,6 +20,7 @@ extern const char kForceInProcess[]; extern const char kHelp[]; extern const char kMapOrigin[]; extern const char kOrigin[]; +extern const char kRemoteDebuggingPort[]; extern const char kTraceStartup[]; extern const char kURLMappings[];