1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/android/dev_tools_server.h"
10 #include "base/android/jni_string.h"
11 #include "base/basictypes.h"
12 #include "base/bind.h"
13 #include "base/callback.h"
14 #include "base/command_line.h"
15 #include "base/compiler_specific.h"
16 #include "base/files/file_path.h"
17 #include "base/logging.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/strings/stringprintf.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "chrome/browser/android/tab_android.h"
22 #include "chrome/browser/browser_process.h"
23 #include "chrome/browser/history/top_sites.h"
24 #include "chrome/browser/profiles/profile_manager.h"
25 #include "chrome/browser/ui/android/tab_model/tab_model.h"
26 #include "chrome/browser/ui/android/tab_model/tab_model_list.h"
27 #include "content/public/browser/android/devtools_auth.h"
28 #include "content/public/browser/browser_thread.h"
29 #include "content/public/browser/devtools_agent_host.h"
30 #include "content/public/browser/devtools_http_handler.h"
31 #include "content/public/browser/devtools_http_handler_delegate.h"
32 #include "content/public/browser/devtools_target.h"
33 #include "content/public/browser/favicon_status.h"
34 #include "content/public/browser/navigation_entry.h"
35 #include "content/public/browser/render_view_host.h"
36 #include "content/public/browser/web_contents.h"
37 #include "content/public/browser/web_contents_delegate.h"
38 #include "content/public/common/content_switches.h"
39 #include "content/public/common/url_constants.h"
40 #include "content/public/common/user_agent.h"
41 #include "grit/browser_resources.h"
42 #include "jni/DevToolsServer_jni.h"
43 #include "net/base/net_errors.h"
44 #include "net/socket/unix_domain_listen_socket_posix.h"
45 #include "net/socket/unix_domain_server_socket_posix.h"
46 #include "net/url_request/url_request_context_getter.h"
47 #include "ui/base/resource/resource_bundle.h"
49 using content::DevToolsAgentHost
;
50 using content::RenderViewHost
;
51 using content::WebContents
;
55 // TL;DR: Do not change this string.
57 // Desktop Chrome relies on this format to identify debuggable apps on Android
58 // (see the code under chrome/browser/devtools/device).
59 // If this string ever changes it would not be sufficient to change the
60 // corresponding string on the client side. Since debugging an older version of
61 // Chrome for Android from a newer version of desktop Chrome is a very common
62 // scenario, the client code will have to be modified to recognize both the old
63 // and the new format.
64 const char kDevToolsChannelNameFormat
[] = "%s_devtools_remote";
66 const char kFrontEndURL
[] =
67 "http://chrome-devtools-frontend.appspot.com/serve_rev/%s/devtools.html";
68 const char kTetheringSocketName
[] = "chrome_devtools_tethering_%d_%d";
70 const char kTargetTypePage
[] = "page";
71 const char kTargetTypeServiceWorker
[] = "service_worker";
72 const char kTargetTypeOther
[] = "other";
74 static GURL
GetFaviconURLForContents(WebContents
* web_contents
) {
75 content::NavigationController
& controller
= web_contents
->GetController();
76 content::NavigationEntry
* entry
= controller
.GetActiveEntry();
77 if (entry
!= NULL
&& entry
->GetURL().is_valid())
78 return entry
->GetFavicon().url
;
82 static GURL
GetFaviconURLForAgentHost(
83 scoped_refptr
<DevToolsAgentHost
> agent_host
) {
84 if (WebContents
* web_contents
= agent_host
->GetWebContents())
85 return GetFaviconURLForContents(web_contents
);
89 static base::TimeTicks
GetLastActiveTimeForAgentHost(
90 scoped_refptr
<DevToolsAgentHost
> agent_host
) {
91 if (WebContents
* web_contents
= agent_host
->GetWebContents())
92 return web_contents
->GetLastActiveTime();
93 return base::TimeTicks();
96 bool AuthorizeSocketAccessWithDebugPermission(
97 const net::UnixDomainServerSocket::Credentials
& credentials
) {
98 JNIEnv
* env
= base::android::AttachCurrentThread();
99 return Java_DevToolsServer_checkDebugPermission(
100 env
, base::android::GetApplicationContext(),
101 credentials
.process_id
, credentials
.user_id
) ||
102 content::CanUserConnectToDevTools(credentials
);
105 class TargetBase
: public content::DevToolsTarget
{
107 // content::DevToolsTarget implementation:
108 virtual std::string
GetParentId() const OVERRIDE
{ return std::string(); }
110 virtual std::string
GetTitle() const OVERRIDE
{ return title_
; }
112 virtual std::string
GetDescription() const OVERRIDE
{ return std::string(); }
114 virtual GURL
GetURL() const OVERRIDE
{ return url_
; }
116 virtual GURL
GetFaviconURL() const OVERRIDE
{ return favicon_url_
; }
118 virtual base::TimeTicks
GetLastActivityTime() const OVERRIDE
{
119 return last_activity_time_
;
123 explicit TargetBase(WebContents
* web_contents
)
124 : title_(base::UTF16ToUTF8(web_contents
->GetTitle())),
125 url_(web_contents
->GetURL()),
126 favicon_url_(GetFaviconURLForContents(web_contents
)),
127 last_activity_time_(web_contents
->GetLastActiveTime()) {
130 explicit TargetBase(scoped_refptr
<DevToolsAgentHost
> agent_host
)
131 : title_(agent_host
->GetTitle()),
132 url_(agent_host
->GetURL()),
133 favicon_url_(GetFaviconURLForAgentHost(agent_host
)),
134 last_activity_time_(GetLastActiveTimeForAgentHost(agent_host
)) {
137 TargetBase(const std::string
& title
, const GURL
& url
)
143 const std::string title_
;
145 const GURL favicon_url_
;
146 const base::TimeTicks last_activity_time_
;
149 class TabTarget
: public TargetBase
{
151 static TabTarget
* CreateForWebContents(int tab_id
,
152 WebContents
* web_contents
) {
153 return new TabTarget(tab_id
, web_contents
);
156 static TabTarget
* CreateForUnloadedTab(int tab_id
,
157 const base::string16
& title
,
159 return new TabTarget(tab_id
, title
, url
);
162 // content::DevToolsTarget implementation:
163 virtual std::string
GetId() const OVERRIDE
{
164 return base::IntToString(tab_id_
);
167 virtual std::string
GetType() const OVERRIDE
{
168 return kTargetTypePage
;
171 virtual bool IsAttached() const OVERRIDE
{
174 if (!FindTab(&model
, &index
))
176 WebContents
* web_contents
= model
->GetWebContentsAt(index
);
179 return DevToolsAgentHost::IsDebuggerAttached(web_contents
);
182 virtual scoped_refptr
<DevToolsAgentHost
> GetAgentHost() const OVERRIDE
{
185 if (!FindTab(&model
, &index
))
187 WebContents
* web_contents
= model
->GetWebContentsAt(index
);
189 // The tab has been pushed out of memory, pull it back.
190 TabAndroid
* tab
= model
->GetTabAt(index
);
195 web_contents
= model
->GetWebContentsAt(index
);
199 return DevToolsAgentHost::GetOrCreateFor(web_contents
);
202 virtual bool Activate() const OVERRIDE
{
205 if (!FindTab(&model
, &index
))
207 model
->SetActiveIndex(index
);
211 virtual bool Close() const OVERRIDE
{
214 if (!FindTab(&model
, &index
))
216 model
->CloseTabAt(index
);
221 TabTarget(int tab_id
, WebContents
* web_contents
)
222 : TargetBase(web_contents
),
226 TabTarget(int tab_id
, const base::string16
& title
, const GURL
& url
)
227 : TargetBase(base::UTF16ToUTF8(title
), url
),
231 bool FindTab(TabModel
** model_result
, int* index_result
) const {
232 for (TabModelList::const_iterator iter
= TabModelList::begin();
233 iter
!= TabModelList::end(); ++iter
) {
234 TabModel
* model
= *iter
;
235 for (int i
= 0; i
< model
->GetTabCount(); ++i
) {
236 TabAndroid
* tab
= model
->GetTabAt(i
);
237 if (tab
&& tab
->GetAndroidId() == tab_id_
) {
238 *model_result
= model
;
250 class NonTabTarget
: public TargetBase
{
252 explicit NonTabTarget(scoped_refptr
<DevToolsAgentHost
> agent_host
)
253 : TargetBase(agent_host
),
254 agent_host_(agent_host
) {
257 // content::DevToolsTarget implementation:
258 virtual std::string
GetId() const OVERRIDE
{
259 return agent_host_
->GetId();
262 virtual std::string
GetType() const OVERRIDE
{
263 switch (agent_host_
->GetType()) {
264 case DevToolsAgentHost::TYPE_WEB_CONTENTS
:
265 if (TabModelList::begin() == TabModelList::end()) {
266 // If there are no tab models we must be running in ChromeShell.
267 // Return the 'page' target type for backwards compatibility.
268 return kTargetTypePage
;
271 case DevToolsAgentHost::TYPE_SERVICE_WORKER
:
272 return kTargetTypeServiceWorker
;
276 return kTargetTypeOther
;
279 virtual bool IsAttached() const OVERRIDE
{
280 return agent_host_
->IsAttached();
283 virtual scoped_refptr
<DevToolsAgentHost
> GetAgentHost() const OVERRIDE
{
287 virtual bool Activate() const OVERRIDE
{
288 return agent_host_
->Activate();
291 virtual bool Close() const OVERRIDE
{
292 return agent_host_
->Close();
296 scoped_refptr
<DevToolsAgentHost
> agent_host_
;
299 // Delegate implementation for the devtools http handler on android. A new
300 // instance of this gets created each time devtools is enabled.
301 class DevToolsServerDelegate
: public content::DevToolsHttpHandlerDelegate
{
303 explicit DevToolsServerDelegate(
304 const net::UnixDomainServerSocket::AuthCallback
& auth_callback
)
305 : last_tethering_socket_(0),
306 auth_callback_(auth_callback
) {
309 virtual std::string
GetDiscoveryPageHTML() OVERRIDE
{
310 // TopSites updates itself after a delay. Ask TopSites to update itself
311 // when we're about to show the remote debugging landing page.
312 content::BrowserThread::PostTask(
313 content::BrowserThread::UI
,
315 base::Bind(&DevToolsServerDelegate::PopulatePageThumbnails
));
316 return ResourceBundle::GetSharedInstance().GetRawDataResource(
317 IDR_DEVTOOLS_DISCOVERY_PAGE_HTML
).as_string();
320 virtual bool BundlesFrontendResources() OVERRIDE
{
324 virtual base::FilePath
GetDebugFrontendDir() OVERRIDE
{
325 return base::FilePath();
328 virtual std::string
GetPageThumbnailData(const GURL
& url
) OVERRIDE
{
330 ProfileManager::GetLastUsedProfile()->GetOriginalProfile();
331 history::TopSites
* top_sites
= profile
->GetTopSites();
333 scoped_refptr
<base::RefCountedMemory
> data
;
334 if (top_sites
->GetPageThumbnail(url
, false, &data
))
335 return std::string(data
->front_as
<char>(), data
->size());
340 virtual scoped_ptr
<content::DevToolsTarget
> CreateNewTarget(
341 const GURL
& url
) OVERRIDE
{
342 if (TabModelList::empty())
343 return scoped_ptr
<content::DevToolsTarget
>();
344 TabModel
* tab_model
= TabModelList::get(0);
346 return scoped_ptr
<content::DevToolsTarget
>();
347 WebContents
* web_contents
= tab_model
->CreateNewTabForDevTools(url
);
349 return scoped_ptr
<content::DevToolsTarget
>();
351 TabAndroid
* tab
= TabAndroid::FromWebContents(web_contents
);
353 return scoped_ptr
<content::DevToolsTarget
>();
355 return scoped_ptr
<content::DevToolsTarget
>(
356 TabTarget::CreateForWebContents(tab
->GetAndroidId(), web_contents
));
359 virtual void EnumerateTargets(TargetCallback callback
) OVERRIDE
{
362 // Enumerate existing tabs, including the ones with no WebContents.
363 std::set
<WebContents
*> tab_web_contents
;
364 for (TabModelList::const_iterator iter
= TabModelList::begin();
365 iter
!= TabModelList::end(); ++iter
) {
366 TabModel
* model
= *iter
;
367 for (int i
= 0; i
< model
->GetTabCount(); ++i
) {
368 TabAndroid
* tab
= model
->GetTabAt(i
);
372 WebContents
* web_contents
= model
->GetWebContentsAt(i
);
374 tab_web_contents
.insert(web_contents
);
375 targets
.push_back(TabTarget::CreateForWebContents(tab
->GetAndroidId(),
378 targets
.push_back(TabTarget::CreateForUnloadedTab(tab
->GetAndroidId(),
385 // Add targets for WebContents not associated with any tabs.
386 DevToolsAgentHost::List agents
=
387 DevToolsAgentHost::GetOrCreateAll();
388 for (DevToolsAgentHost::List::iterator it
= agents
.begin();
389 it
!= agents
.end(); ++it
) {
390 if (WebContents
* web_contents
= (*it
)->GetWebContents()) {
391 if (tab_web_contents
.find(web_contents
) != tab_web_contents
.end())
394 targets
.push_back(new NonTabTarget(*it
));
397 callback
.Run(targets
);
400 virtual scoped_ptr
<net::StreamListenSocket
> CreateSocketForTethering(
401 net::StreamListenSocket::Delegate
* delegate
,
402 std::string
* name
) OVERRIDE
{
403 *name
= base::StringPrintf(
404 kTetheringSocketName
, getpid(), ++last_tethering_socket_
);
405 return net::deprecated::UnixDomainListenSocket::
406 CreateAndListenWithAbstractNamespace(
411 .PassAs
<net::StreamListenSocket
>();
415 static void PopulatePageThumbnails() {
417 ProfileManager::GetLastUsedProfile()->GetOriginalProfile();
418 history::TopSites
* top_sites
= profile
->GetTopSites();
420 top_sites
->SyncWithHistory();
423 int last_tethering_socket_
;
424 const net::UnixDomainServerSocket::AuthCallback auth_callback_
;
426 DISALLOW_COPY_AND_ASSIGN(DevToolsServerDelegate
);
429 // Factory for UnixDomainServerSocket. It tries a fallback socket when
430 // original socket doesn't work.
431 class UnixDomainServerSocketFactory
432 : public content::DevToolsHttpHandler::ServerSocketFactory
{
434 UnixDomainServerSocketFactory(
435 const std::string
& socket_name
,
436 const net::UnixDomainServerSocket::AuthCallback
& auth_callback
)
437 : content::DevToolsHttpHandler::ServerSocketFactory(socket_name
, 0, 1),
438 auth_callback_(auth_callback
) {
442 // content::DevToolsHttpHandler::ServerSocketFactory.
443 virtual scoped_ptr
<net::ServerSocket
> Create() const OVERRIDE
{
444 return scoped_ptr
<net::ServerSocket
>(
445 new net::UnixDomainServerSocket(auth_callback_
,
446 true /* use_abstract_namespace */));
449 virtual scoped_ptr
<net::ServerSocket
> CreateAndListen() const OVERRIDE
{
450 scoped_ptr
<net::ServerSocket
> socket
= Create();
452 return scoped_ptr
<net::ServerSocket
>();
454 if (socket
->ListenWithAddressAndPort(address_
, port_
, backlog_
) == net::OK
)
455 return socket
.Pass();
457 // Try a fallback socket name.
458 const std::string
fallback_address(
459 base::StringPrintf("%s_%d", address_
.c_str(), getpid()));
460 if (socket
->ListenWithAddressAndPort(fallback_address
, port_
, backlog_
)
462 return socket
.Pass();
464 return scoped_ptr
<net::ServerSocket
>();
467 const net::UnixDomainServerSocket::AuthCallback auth_callback_
;
469 DISALLOW_COPY_AND_ASSIGN(UnixDomainServerSocketFactory
);
474 DevToolsServer::DevToolsServer(const std::string
& socket_name_prefix
)
475 : socket_name_(base::StringPrintf(kDevToolsChannelNameFormat
,
476 socket_name_prefix
.c_str())),
477 protocol_handler_(NULL
) {
478 // Override the socket name if one is specified on the command line.
479 const CommandLine
& command_line
= *CommandLine::ForCurrentProcess();
480 if (command_line
.HasSwitch(switches::kRemoteDebuggingSocketName
)) {
481 socket_name_
= command_line
.GetSwitchValueASCII(
482 switches::kRemoteDebuggingSocketName
);
486 DevToolsServer::~DevToolsServer() {
490 void DevToolsServer::Start(bool allow_debug_permission
) {
491 if (protocol_handler_
)
494 net::UnixDomainServerSocket::AuthCallback auth_callback
=
495 allow_debug_permission
?
496 base::Bind(&AuthorizeSocketAccessWithDebugPermission
) :
497 base::Bind(&content::CanUserConnectToDevTools
);
498 scoped_ptr
<content::DevToolsHttpHandler::ServerSocketFactory
> factory(
499 new UnixDomainServerSocketFactory(socket_name_
, auth_callback
));
500 protocol_handler_
= content::DevToolsHttpHandler::Start(
502 base::StringPrintf(kFrontEndURL
, content::GetWebKitRevision().c_str()),
503 new DevToolsServerDelegate(auth_callback
),
507 void DevToolsServer::Stop() {
508 if (!protocol_handler_
)
510 // Note that the call to Stop() below takes care of |protocol_handler_|
512 protocol_handler_
->Stop();
513 protocol_handler_
= NULL
;
516 bool DevToolsServer::IsStarted() const {
517 return protocol_handler_
;
520 bool RegisterDevToolsServer(JNIEnv
* env
) {
521 return RegisterNativesImpl(env
);
524 static jlong
InitRemoteDebugging(JNIEnv
* env
,
526 jstring socket_name_prefix
) {
527 DevToolsServer
* server
= new DevToolsServer(
528 base::android::ConvertJavaStringToUTF8(env
, socket_name_prefix
));
529 return reinterpret_cast<intptr_t>(server
);
532 static void DestroyRemoteDebugging(JNIEnv
* env
, jobject obj
, jlong server
) {
533 delete reinterpret_cast<DevToolsServer
*>(server
);
536 static jboolean
IsRemoteDebuggingEnabled(JNIEnv
* env
,
539 return reinterpret_cast<DevToolsServer
*>(server
)->IsStarted();
542 static void SetRemoteDebuggingEnabled(JNIEnv
* env
,
546 jboolean allow_debug_permission
) {
547 DevToolsServer
* devtools_server
= reinterpret_cast<DevToolsServer
*>(server
);
549 devtools_server
->Start(allow_debug_permission
);
551 devtools_server
->Stop();