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.
4
5#include "chrome/browser/android/dev_tools_server.h"
6
7#include <pwd.h>
8#include <cstring>
9
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/logging.h"
17#include "base/strings/string_number_conversions.h"
18#include "base/strings/stringprintf.h"
19#include "base/strings/utf_string_conversions.h"
20#include "chrome/browser/android/tab_android.h"
21#include "chrome/browser/browser_process.h"
22#include "chrome/browser/devtools/devtools_adb_bridge.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 "grit/devtools_discovery_page_resources.h"
41#include "jni/DevToolsServer_jni.h"
42#include "net/socket/unix_domain_socket_posix.h"
43#include "net/url_request/url_request_context_getter.h"
44#include "ui/base/resource/resource_bundle.h"
45#include "webkit/common/user_agent/user_agent_util.h"
46
47using content::DevToolsAgentHost;
48using content::RenderViewHost;
49using content::WebContents;
50
51namespace {
52
53const char kFrontEndURL[] =
54    "http://chrome-devtools-frontend.appspot.com/serve_rev/%s/devtools.html";
55const char kDefaultSocketNamePrefix[] = "chrome";
56const char kTetheringSocketName[] = "chrome_devtools_tethering_%d_%d";
57
58const char kTargetTypePage[] = "page";
59const char kTargetTypeOther[] = "other";
60
61static GURL GetFaviconURL(WebContents* web_contents) {
62  content::NavigationController& controller = web_contents->GetController();
63  content::NavigationEntry* entry = controller.GetActiveEntry();
64  if (entry != NULL && entry->GetURL().is_valid())
65    return entry->GetFavicon().url;
66  return GURL();
67}
68
69class TargetBase : public content::DevToolsTarget {
70 public:
71  // content::DevToolsTarget implementation:
72  virtual std::string GetTitle() const OVERRIDE { return title_; }
73
74  virtual std::string GetDescription() const OVERRIDE { return std::string(); }
75
76  virtual GURL GetUrl() const OVERRIDE { return url_; }
77
78  virtual GURL GetFaviconUrl() const OVERRIDE { return favicon_url_; }
79
80  virtual base::TimeTicks GetLastActivityTime() const OVERRIDE {
81    return last_activity_time_;
82  }
83
84 protected:
85  explicit TargetBase(WebContents* web_contents)
86      : title_(UTF16ToUTF8(web_contents->GetTitle())),
87        url_(web_contents->GetURL()),
88        favicon_url_(GetFaviconURL(web_contents)),
89        last_activity_time_(web_contents->GetLastSelectedTime()) {
90  }
91
92  TargetBase(const base::string16& title, const GURL& url)
93      : title_(UTF16ToUTF8(title)),
94        url_(url)
95  {}
96
97 private:
98  const std::string title_;
99  const GURL url_;
100  const GURL favicon_url_;
101  const base::TimeTicks last_activity_time_;
102};
103
104class TabTarget : public TargetBase {
105 public:
106  static TabTarget* CreateForWebContents(int tab_id,
107                                         WebContents* web_contents) {
108    return new TabTarget(tab_id, web_contents);
109  }
110
111  static TabTarget* CreateForUnloadedTab(int tab_id,
112                                         const base::string16& title,
113                                         const GURL& url) {
114    return new TabTarget(tab_id, title, url);
115  }
116
117  // content::DevToolsTarget implementation:
118  virtual std::string GetId() const OVERRIDE {
119    return base::IntToString(tab_id_);
120  }
121
122  virtual std::string GetType() const OVERRIDE { return kTargetTypePage; }
123
124  virtual bool IsAttached() const OVERRIDE {
125    TabModel* model;
126    int index;
127    if (!FindTab(&model, &index))
128      return false;
129    WebContents* web_contents = model->GetWebContentsAt(index);
130    if (!web_contents)
131      return false;
132    return DevToolsAgentHost::IsDebuggerAttached(web_contents);
133  }
134
135  virtual scoped_refptr<DevToolsAgentHost> GetAgentHost() const OVERRIDE {
136    TabModel* model;
137    int index;
138    if (!FindTab(&model, &index))
139      return NULL;
140    WebContents* web_contents = model->GetWebContentsAt(index);
141    if (!web_contents) {
142      // The tab has been pushed out of memory, pull it back.
143      TabAndroid* tab = model->GetTabAt(index);
144      tab->RestoreIfNeeded();
145      web_contents = model->GetWebContentsAt(index);
146      if (!web_contents)
147        return NULL;
148    }
149    RenderViewHost* rvh = web_contents->GetRenderViewHost();
150    return rvh ? DevToolsAgentHost::GetOrCreateFor(rvh) : NULL;
151  }
152
153  virtual bool Activate() const OVERRIDE {
154    TabModel* model;
155    int index;
156    if (!FindTab(&model, &index))
157      return false;
158    model->SetActiveIndex(index);
159    return true;
160  }
161
162  virtual bool Close() const OVERRIDE {
163    TabModel* model;
164    int index;
165    if (!FindTab(&model, &index))
166      return false;
167    model->CloseTabAt(index);
168    return true;
169  }
170
171 private:
172  TabTarget(int tab_id, WebContents* web_contents)
173      : TargetBase(web_contents),
174        tab_id_(tab_id) {
175  }
176
177  TabTarget(int tab_id, const base::string16& title, const GURL& url)
178      : TargetBase(title, url),
179        tab_id_(tab_id) {
180  }
181
182  bool FindTab(TabModel** model_result, int* index_result) const {
183    for (TabModelList::const_iterator iter = TabModelList::begin();
184        iter != TabModelList::end(); ++iter) {
185      TabModel* model = *iter;
186      for (int i = 0; i < model->GetTabCount(); ++i) {
187        TabAndroid* tab = model->GetTabAt(i);
188        if (tab->GetAndroidId() == tab_id_) {
189          *model_result = model;
190          *index_result = i;
191          return true;
192        }
193      }
194    }
195    return false;
196  }
197
198  const int tab_id_;
199};
200
201class NonTabTarget : public TargetBase {
202 public:
203  explicit NonTabTarget(WebContents* web_contents)
204      : TargetBase(web_contents),
205        agent_host_(DevToolsAgentHost::GetOrCreateFor(
206            web_contents->GetRenderViewHost())) {
207  }
208
209  // content::DevToolsTarget implementation:
210  virtual std::string GetId() const OVERRIDE {
211    return agent_host_->GetId();
212  }
213
214  virtual std::string GetType() const OVERRIDE {
215    if (TabModelList::begin() == TabModelList::end()) {
216      // If there are no tab models we must be running in ChromiumTestShell.
217      // Return the 'page' target type for backwards compatibility.
218      return kTargetTypePage;
219    }
220    return kTargetTypeOther;
221  }
222
223  virtual bool IsAttached() const OVERRIDE {
224    return agent_host_->IsAttached();
225  }
226
227  virtual scoped_refptr<DevToolsAgentHost> GetAgentHost() const OVERRIDE {
228    return agent_host_;
229  }
230
231  virtual bool Activate() const OVERRIDE {
232    RenderViewHost* rvh = agent_host_->GetRenderViewHost();
233    if (!rvh)
234      return false;
235    WebContents* web_contents = WebContents::FromRenderViewHost(rvh);
236    if (!web_contents)
237      return false;
238    web_contents->GetDelegate()->ActivateContents(web_contents);
239    return true;
240  }
241
242  virtual bool Close() const OVERRIDE {
243    RenderViewHost* rvh = agent_host_->GetRenderViewHost();
244    if (!rvh)
245      return false;
246    rvh->ClosePage();
247    return true;
248  }
249
250 private:
251  scoped_refptr<DevToolsAgentHost> agent_host_;
252};
253
254// Delegate implementation for the devtools http handler on android. A new
255// instance of this gets created each time devtools is enabled.
256class DevToolsServerDelegate : public content::DevToolsHttpHandlerDelegate {
257 public:
258  DevToolsServerDelegate()
259      : last_tethering_socket_(0) {
260  }
261
262  virtual std::string GetDiscoveryPageHTML() OVERRIDE {
263    // TopSites updates itself after a delay. Ask TopSites to update itself
264    // when we're about to show the remote debugging landing page.
265    content::BrowserThread::PostTask(
266        content::BrowserThread::UI,
267        FROM_HERE,
268        base::Bind(&DevToolsServerDelegate::PopulatePageThumbnails));
269    return ResourceBundle::GetSharedInstance().GetRawDataResource(
270        IDR_DEVTOOLS_DISCOVERY_PAGE_HTML).as_string();
271  }
272
273  virtual bool BundlesFrontendResources() OVERRIDE {
274    return false;
275  }
276
277  virtual base::FilePath GetDebugFrontendDir() OVERRIDE {
278    return base::FilePath();
279  }
280
281  virtual std::string GetPageThumbnailData(const GURL& url) OVERRIDE {
282    Profile* profile =
283        ProfileManager::GetLastUsedProfile()->GetOriginalProfile();
284    history::TopSites* top_sites = profile->GetTopSites();
285    if (top_sites) {
286      scoped_refptr<base::RefCountedMemory> data;
287      if (top_sites->GetPageThumbnail(url, false, &data))
288        return std::string(reinterpret_cast<const char*>(data->front()),
289                           data->size());
290    }
291    return "";
292  }
293
294  virtual scoped_ptr<content::DevToolsTarget> CreateNewTarget(
295      const GURL& url) OVERRIDE {
296    Profile* profile =
297        g_browser_process->profile_manager()->GetDefaultProfile();
298    TabModel* tab_model = TabModelList::GetTabModelWithProfile(profile);
299    if (!tab_model)
300      return scoped_ptr<content::DevToolsTarget>();
301    WebContents* web_contents = tab_model->CreateNewTabForDevTools(url);
302    if (!web_contents)
303      return scoped_ptr<content::DevToolsTarget>();
304
305    for (int i = 0; i < tab_model->GetTabCount(); ++i) {
306      if (web_contents != tab_model->GetWebContentsAt(i))
307        continue;
308      TabAndroid* tab = tab_model->GetTabAt(i);
309      return scoped_ptr<content::DevToolsTarget>(
310          TabTarget::CreateForWebContents(tab->GetAndroidId(), web_contents));
311    }
312
313    // Newly created tab not found, return no target.
314    return scoped_ptr<content::DevToolsTarget>();
315  }
316
317  virtual void EnumerateTargets(TargetCallback callback) OVERRIDE {
318    TargetList targets;
319
320    // Enumerate existing tabs, including the ones with no WebContents.
321    std::set<WebContents*> tab_web_contents;
322    for (TabModelList::const_iterator iter = TabModelList::begin();
323        iter != TabModelList::end(); ++iter) {
324      TabModel* model = *iter;
325      for (int i = 0; i < model->GetTabCount(); ++i) {
326        TabAndroid* tab = model->GetTabAt(i);
327        WebContents* web_contents = model->GetWebContentsAt(i);
328        if (web_contents) {
329          tab_web_contents.insert(web_contents);
330          targets.push_back(TabTarget::CreateForWebContents(tab->GetAndroidId(),
331                                                            web_contents));
332        } else {
333          targets.push_back(TabTarget::CreateForUnloadedTab(tab->GetAndroidId(),
334                                                            tab->GetTitle(),
335                                                            tab->GetURL()));
336        }
337      }
338    }
339
340    // Add targets for WebContents not associated with any tabs.
341    std::vector<RenderViewHost*> rvh_list =
342        DevToolsAgentHost::GetValidRenderViewHosts();
343    for (std::vector<RenderViewHost*>::iterator it = rvh_list.begin();
344         it != rvh_list.end(); ++it) {
345      WebContents* web_contents = WebContents::FromRenderViewHost(*it);
346      if (!web_contents)
347        continue;
348      if (tab_web_contents.find(web_contents) != tab_web_contents.end())
349        continue;
350      targets.push_back(new NonTabTarget(web_contents));
351    }
352
353    callback.Run(targets);
354  }
355
356  virtual scoped_ptr<net::StreamListenSocket> CreateSocketForTethering(
357      net::StreamListenSocket::Delegate* delegate,
358      std::string* name) OVERRIDE {
359    *name = base::StringPrintf(
360        kTetheringSocketName, getpid(), ++last_tethering_socket_);
361    return net::UnixDomainSocket::CreateAndListenWithAbstractNamespace(
362               *name,
363               "",
364               delegate,
365               base::Bind(&content::CanUserConnectToDevTools))
366           .PassAs<net::StreamListenSocket>();
367  }
368
369 private:
370  static void PopulatePageThumbnails() {
371    Profile* profile =
372        ProfileManager::GetLastUsedProfile()->GetOriginalProfile();
373    history::TopSites* top_sites = profile->GetTopSites();
374    if (top_sites)
375      top_sites->SyncWithHistory();
376  }
377
378  int last_tethering_socket_;
379
380  DISALLOW_COPY_AND_ASSIGN(DevToolsServerDelegate);
381};
382
383}  // namespace
384
385DevToolsServer::DevToolsServer()
386    : socket_name_(base::StringPrintf(kDevToolsChannelNameFormat,
387                                      kDefaultSocketNamePrefix)),
388      protocol_handler_(NULL) {
389  // Override the default socket name if one is specified on the command line.
390  const CommandLine& command_line = *CommandLine::ForCurrentProcess();
391  if (command_line.HasSwitch(switches::kRemoteDebuggingSocketName)) {
392    socket_name_ = command_line.GetSwitchValueASCII(
393        switches::kRemoteDebuggingSocketName);
394  }
395}
396
397DevToolsServer::DevToolsServer(const std::string& socket_name_prefix)
398    : socket_name_(base::StringPrintf(kDevToolsChannelNameFormat,
399                                      socket_name_prefix.c_str())),
400      protocol_handler_(NULL) {
401  // Override the socket name if one is specified on the command line.
402  const CommandLine& command_line = *CommandLine::ForCurrentProcess();
403  if (command_line.HasSwitch(switches::kRemoteDebuggingSocketName)) {
404    socket_name_ = command_line.GetSwitchValueASCII(
405        switches::kRemoteDebuggingSocketName);
406  }
407}
408
409DevToolsServer::~DevToolsServer() {
410  Stop();
411}
412
413void DevToolsServer::Start() {
414  if (protocol_handler_)
415    return;
416
417  protocol_handler_ = content::DevToolsHttpHandler::Start(
418      new net::UnixDomainSocketWithAbstractNamespaceFactory(
419          socket_name_,
420          base::StringPrintf("%s_%d", socket_name_.c_str(), getpid()),
421          base::Bind(&content::CanUserConnectToDevTools)),
422      base::StringPrintf(kFrontEndURL,
423                         webkit_glue::GetWebKitRevision().c_str()),
424      new DevToolsServerDelegate());
425}
426
427void DevToolsServer::Stop() {
428  if (!protocol_handler_)
429    return;
430  // Note that the call to Stop() below takes care of |protocol_handler_|
431  // deletion.
432  protocol_handler_->Stop();
433  protocol_handler_ = NULL;
434}
435
436bool DevToolsServer::IsStarted() const {
437  return protocol_handler_;
438}
439
440bool RegisterDevToolsServer(JNIEnv* env) {
441  return RegisterNativesImpl(env);
442}
443
444static jint InitRemoteDebugging(JNIEnv* env,
445                                jobject obj,
446                                jstring socket_name_prefix) {
447  DevToolsServer* server = new DevToolsServer(
448      base::android::ConvertJavaStringToUTF8(env, socket_name_prefix));
449  return reinterpret_cast<jint>(server);
450}
451
452static void DestroyRemoteDebugging(JNIEnv* env, jobject obj, jint server) {
453  delete reinterpret_cast<DevToolsServer*>(server);
454}
455
456static jboolean IsRemoteDebuggingEnabled(JNIEnv* env,
457                                         jobject obj,
458                                         jint server) {
459  return reinterpret_cast<DevToolsServer*>(server)->IsStarted();
460}
461
462static void SetRemoteDebuggingEnabled(JNIEnv* env,
463                                      jobject obj,
464                                      jint server,
465                                      jboolean enabled) {
466  DevToolsServer* devtools_server = reinterpret_cast<DevToolsServer*>(server);
467  if (enabled) {
468    devtools_server->Start();
469  } else {
470    devtools_server->Stop();
471  }
472}
473