Move Webstore URL concepts to //extensions and out
[chromium-blink-merge.git] / chrome / browser / android / dev_tools_server.cc
blobe41b65f943af41f471c4532c0a51ea29e65eb2a8
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"
7 #include <pwd.h>
8 #include <cstring>
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;
53 namespace {
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;
79 return GURL();
82 static GURL GetFaviconURLForAgentHost(
83 scoped_refptr<DevToolsAgentHost> agent_host) {
84 if (WebContents* web_contents = agent_host->GetWebContents())
85 return GetFaviconURLForContents(web_contents);
86 return GURL();
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 {
106 public:
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_;
122 protected:
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)
138 : title_(title),
139 url_(url) {
142 private:
143 const std::string title_;
144 const GURL url_;
145 const GURL favicon_url_;
146 const base::TimeTicks last_activity_time_;
149 class TabTarget : public TargetBase {
150 public:
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,
158 const GURL& url) {
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 {
172 TabModel* model;
173 int index;
174 if (!FindTab(&model, &index))
175 return false;
176 WebContents* web_contents = model->GetWebContentsAt(index);
177 if (!web_contents)
178 return false;
179 return DevToolsAgentHost::IsDebuggerAttached(web_contents);
182 virtual scoped_refptr<DevToolsAgentHost> GetAgentHost() const OVERRIDE {
183 TabModel* model;
184 int index;
185 if (!FindTab(&model, &index))
186 return NULL;
187 WebContents* web_contents = model->GetWebContentsAt(index);
188 if (!web_contents) {
189 // The tab has been pushed out of memory, pull it back.
190 TabAndroid* tab = model->GetTabAt(index);
191 if (!tab)
192 return NULL;
194 tab->LoadIfNeeded();
195 web_contents = model->GetWebContentsAt(index);
196 if (!web_contents)
197 return NULL;
199 return DevToolsAgentHost::GetOrCreateFor(web_contents);
202 virtual bool Activate() const OVERRIDE {
203 TabModel* model;
204 int index;
205 if (!FindTab(&model, &index))
206 return false;
207 model->SetActiveIndex(index);
208 return true;
211 virtual bool Close() const OVERRIDE {
212 TabModel* model;
213 int index;
214 if (!FindTab(&model, &index))
215 return false;
216 model->CloseTabAt(index);
217 return true;
220 private:
221 TabTarget(int tab_id, WebContents* web_contents)
222 : TargetBase(web_contents),
223 tab_id_(tab_id) {
226 TabTarget(int tab_id, const base::string16& title, const GURL& url)
227 : TargetBase(base::UTF16ToUTF8(title), url),
228 tab_id_(tab_id) {
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;
239 *index_result = i;
240 return true;
244 return false;
247 const int tab_id_;
250 class NonTabTarget : public TargetBase {
251 public:
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;
270 break;
271 case DevToolsAgentHost::TYPE_SERVICE_WORKER:
272 return kTargetTypeServiceWorker;
273 default:
274 break;
276 return kTargetTypeOther;
279 virtual bool IsAttached() const OVERRIDE {
280 return agent_host_->IsAttached();
283 virtual scoped_refptr<DevToolsAgentHost> GetAgentHost() const OVERRIDE {
284 return agent_host_;
287 virtual bool Activate() const OVERRIDE {
288 return agent_host_->Activate();
291 virtual bool Close() const OVERRIDE {
292 return agent_host_->Close();
295 private:
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 {
302 public:
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,
314 FROM_HERE,
315 base::Bind(&DevToolsServerDelegate::PopulatePageThumbnails));
316 return ResourceBundle::GetSharedInstance().GetRawDataResource(
317 IDR_DEVTOOLS_DISCOVERY_PAGE_HTML).as_string();
320 virtual bool BundlesFrontendResources() OVERRIDE {
321 return false;
324 virtual base::FilePath GetDebugFrontendDir() OVERRIDE {
325 return base::FilePath();
328 virtual std::string GetPageThumbnailData(const GURL& url) OVERRIDE {
329 Profile* profile =
330 ProfileManager::GetLastUsedProfile()->GetOriginalProfile();
331 history::TopSites* top_sites = profile->GetTopSites();
332 if (top_sites) {
333 scoped_refptr<base::RefCountedMemory> data;
334 if (top_sites->GetPageThumbnail(url, false, &data))
335 return std::string(data->front_as<char>(), data->size());
337 return "";
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);
345 if (!tab_model)
346 return scoped_ptr<content::DevToolsTarget>();
347 WebContents* web_contents = tab_model->CreateNewTabForDevTools(url);
348 if (!web_contents)
349 return scoped_ptr<content::DevToolsTarget>();
351 TabAndroid* tab = TabAndroid::FromWebContents(web_contents);
352 if (!tab)
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 {
360 TargetList targets;
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);
369 if (!tab)
370 continue;
372 WebContents* web_contents = model->GetWebContentsAt(i);
373 if (web_contents) {
374 tab_web_contents.insert(web_contents);
375 targets.push_back(TabTarget::CreateForWebContents(tab->GetAndroidId(),
376 web_contents));
377 } else {
378 targets.push_back(TabTarget::CreateForUnloadedTab(tab->GetAndroidId(),
379 tab->GetTitle(),
380 tab->GetURL()));
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())
392 continue;
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(
407 *name,
409 delegate,
410 auth_callback_)
411 .PassAs<net::StreamListenSocket>();
414 private:
415 static void PopulatePageThumbnails() {
416 Profile* profile =
417 ProfileManager::GetLastUsedProfile()->GetOriginalProfile();
418 history::TopSites* top_sites = profile->GetTopSites();
419 if (top_sites)
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 {
433 public:
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) {
441 private:
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();
451 if (!socket)
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_)
461 == net::OK)
462 return socket.Pass();
464 return scoped_ptr<net::ServerSocket>();
467 const net::UnixDomainServerSocket::AuthCallback auth_callback_;
469 DISALLOW_COPY_AND_ASSIGN(UnixDomainServerSocketFactory);
472 } // namespace
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() {
487 Stop();
490 void DevToolsServer::Start(bool allow_debug_permission) {
491 if (protocol_handler_)
492 return;
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(
501 factory.Pass(),
502 base::StringPrintf(kFrontEndURL, content::GetWebKitRevision().c_str()),
503 new DevToolsServerDelegate(auth_callback),
504 base::FilePath());
507 void DevToolsServer::Stop() {
508 if (!protocol_handler_)
509 return;
510 // Note that the call to Stop() below takes care of |protocol_handler_|
511 // deletion.
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,
525 jobject obj,
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,
537 jobject obj,
538 jlong server) {
539 return reinterpret_cast<DevToolsServer*>(server)->IsStarted();
542 static void SetRemoteDebuggingEnabled(JNIEnv* env,
543 jobject obj,
544 jlong server,
545 jboolean enabled,
546 jboolean allow_debug_permission) {
547 DevToolsServer* devtools_server = reinterpret_cast<DevToolsServer*>(server);
548 if (enabled) {
549 devtools_server->Start(allow_debug_permission);
550 } else {
551 devtools_server->Stop();