1// Copyright 2013 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/devtools/devtools_targets_ui.h"
6
7#include "base/memory/weak_ptr.h"
8#include "base/stl_util.h"
9#include "base/strings/stringprintf.h"
10#include "base/values.h"
11#include "base/version.h"
12#include "chrome/browser/devtools/device/devtools_android_bridge.h"
13#include "chrome/browser/devtools/devtools_target_impl.h"
14#include "chrome/common/chrome_version_info.h"
15#include "content/public/browser/browser_child_process_observer.h"
16#include "content/public/browser/browser_thread.h"
17#include "content/public/browser/child_process_data.h"
18#include "content/public/browser/notification_observer.h"
19#include "content/public/browser/notification_registrar.h"
20#include "content/public/browser/notification_service.h"
21#include "content/public/browser/notification_source.h"
22#include "content/public/browser/notification_types.h"
23#include "content/public/browser/worker_service.h"
24#include "content/public/browser/worker_service_observer.h"
25#include "content/public/common/process_type.h"
26#include "net/base/escape.h"
27
28using content::BrowserThread;
29
30namespace {
31
32const char kTargetSourceField[]  = "source";
33const char kTargetSourceLocal[]  = "local";
34const char kTargetSourceRemote[]  = "remote";
35
36const char kTargetIdField[]  = "id";
37const char kTargetTypeField[]  = "type";
38const char kAttachedField[]  = "attached";
39const char kUrlField[]  = "url";
40const char kNameField[]  = "name";
41const char kFaviconUrlField[] = "faviconUrl";
42const char kDescriptionField[] = "description";
43
44const char kGuestList[] = "guests";
45
46const char kAdbModelField[] = "adbModel";
47const char kAdbConnectedField[] = "adbConnected";
48const char kAdbSerialField[] = "adbSerial";
49const char kAdbBrowsersList[] = "browsers";
50const char kAdbDeviceIdFormat[] = "device:%s";
51
52const char kAdbBrowserNameField[] = "adbBrowserName";
53const char kAdbBrowserVersionField[] = "adbBrowserVersion";
54const char kAdbBrowserChromeVersionField[] = "adbBrowserChromeVersion";
55const char kCompatibleVersion[] = "compatibleVersion";
56const char kAdbPagesList[] = "pages";
57
58const char kAdbScreenWidthField[] = "adbScreenWidth";
59const char kAdbScreenHeightField[] = "adbScreenHeight";
60const char kAdbAttachedForeignField[]  = "adbAttachedForeign";
61
62// CancelableTimer ------------------------------------------------------------
63
64class CancelableTimer {
65 public:
66  CancelableTimer(base::Closure callback, base::TimeDelta delay)
67      : callback_(callback),
68        weak_factory_(this) {
69    base::MessageLoop::current()->PostDelayedTask(
70        FROM_HERE,
71        base::Bind(&CancelableTimer::Fire, weak_factory_.GetWeakPtr()),
72        delay);
73  }
74
75 private:
76  void Fire() { callback_.Run(); }
77
78  base::Closure callback_;
79  base::WeakPtrFactory<CancelableTimer> weak_factory_;
80};
81
82// WorkerObserver -------------------------------------------------------------
83
84class WorkerObserver
85    : public content::WorkerServiceObserver,
86      public base::RefCountedThreadSafe<WorkerObserver> {
87 public:
88  WorkerObserver() {}
89
90  void Start(base::Closure callback) {
91    DCHECK(callback_.is_null());
92    DCHECK(!callback.is_null());
93    callback_ = callback;
94    BrowserThread::PostTask(
95        BrowserThread::IO, FROM_HERE,
96        base::Bind(&WorkerObserver::StartOnIOThread, this));
97  }
98
99  void Stop() {
100    DCHECK(!callback_.is_null());
101    callback_ = base::Closure();
102    BrowserThread::PostTask(
103        BrowserThread::IO, FROM_HERE,
104        base::Bind(&WorkerObserver::StopOnIOThread, this));
105  }
106
107 private:
108  friend class base::RefCountedThreadSafe<WorkerObserver>;
109  virtual ~WorkerObserver() {}
110
111  // content::WorkerServiceObserver overrides:
112  virtual void WorkerCreated(
113      const GURL& url,
114      const base::string16& name,
115      int process_id,
116      int route_id) OVERRIDE {
117    NotifyOnIOThread();
118  }
119
120  virtual void WorkerDestroyed(int process_id, int route_id) OVERRIDE {
121    NotifyOnIOThread();
122  }
123
124  void StartOnIOThread() {
125    content::WorkerService::GetInstance()->AddObserver(this);
126  }
127
128  void StopOnIOThread() {
129    content::WorkerService::GetInstance()->RemoveObserver(this);
130  }
131
132  void NotifyOnIOThread() {
133    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
134    BrowserThread::PostTask(
135        BrowserThread::UI, FROM_HERE,
136        base::Bind(&WorkerObserver::NotifyOnUIThread, this));
137  }
138
139  void NotifyOnUIThread() {
140    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
141    if (callback_.is_null())
142      return;
143    callback_.Run();
144  }
145
146  // Accessed on UI thread.
147  base::Closure callback_;
148};
149
150// LocalTargetsUIHandler ---------------------------------------------
151
152class LocalTargetsUIHandler
153    : public DevToolsTargetsUIHandler,
154      public content::NotificationObserver {
155 public:
156  explicit LocalTargetsUIHandler(const Callback& callback);
157  virtual ~LocalTargetsUIHandler();
158
159  // DevToolsTargetsUIHandler overrides.
160  virtual void ForceUpdate() OVERRIDE;
161
162private:
163  // content::NotificationObserver overrides.
164  virtual void Observe(int type,
165                       const content::NotificationSource& source,
166                       const content::NotificationDetails& details) OVERRIDE;
167
168  void ScheduleUpdate();
169  void UpdateTargets();
170  void SendTargets(const DevToolsTargetImpl::List& targets);
171
172  content::NotificationRegistrar notification_registrar_;
173  scoped_ptr<CancelableTimer> timer_;
174  scoped_refptr<WorkerObserver> observer_;
175  base::WeakPtrFactory<LocalTargetsUIHandler> weak_factory_;
176};
177
178LocalTargetsUIHandler::LocalTargetsUIHandler(
179    const Callback& callback)
180    : DevToolsTargetsUIHandler(kTargetSourceLocal, callback),
181      observer_(new WorkerObserver()),
182      weak_factory_(this) {
183  notification_registrar_.Add(this,
184                              content::NOTIFICATION_WEB_CONTENTS_CONNECTED,
185                              content::NotificationService::AllSources());
186  notification_registrar_.Add(this,
187                              content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
188                              content::NotificationService::AllSources());
189  notification_registrar_.Add(this,
190                              content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
191                              content::NotificationService::AllSources());
192  observer_->Start(base::Bind(&LocalTargetsUIHandler::ScheduleUpdate,
193                              base::Unretained(this)));
194  UpdateTargets();
195}
196
197LocalTargetsUIHandler::~LocalTargetsUIHandler() {
198  notification_registrar_.RemoveAll();
199  observer_->Stop();
200}
201
202void LocalTargetsUIHandler::Observe(
203    int type,
204    const content::NotificationSource& source,
205    const content::NotificationDetails& details) {
206  ScheduleUpdate();
207}
208
209void LocalTargetsUIHandler::ForceUpdate() {
210  ScheduleUpdate();
211}
212
213void LocalTargetsUIHandler::ScheduleUpdate() {
214  const int kUpdateDelay = 100;
215  timer_.reset(
216      new CancelableTimer(
217          base::Bind(&LocalTargetsUIHandler::UpdateTargets,
218                     base::Unretained(this)),
219          base::TimeDelta::FromMilliseconds(kUpdateDelay)));
220}
221
222void LocalTargetsUIHandler::UpdateTargets() {
223  DevToolsTargetImpl::EnumerateAllTargets(base::Bind(
224      &LocalTargetsUIHandler::SendTargets,
225      weak_factory_.GetWeakPtr()));
226}
227
228void LocalTargetsUIHandler::SendTargets(
229    const DevToolsTargetImpl::List& targets) {
230  base::ListValue list_value;
231  std::map<std::string, base::DictionaryValue*> id_to_descriptor;
232
233  STLDeleteValues(&targets_);
234  for (DevToolsTargetImpl::List::const_iterator it = targets.begin();
235      it != targets.end(); ++it) {
236    DevToolsTargetImpl* target = *it;
237    targets_[target->GetId()] = target;
238    id_to_descriptor[target->GetId()] = Serialize(*target);
239  }
240
241  for (TargetMap::iterator it(targets_.begin()); it != targets_.end(); ++it) {
242    DevToolsTargetImpl* target = it->second;
243    base::DictionaryValue* descriptor = id_to_descriptor[target->GetId()];
244    std::string parent_id = target->GetParentId();
245    if (parent_id.empty() || id_to_descriptor.count(parent_id) == 0) {
246      list_value.Append(descriptor);
247    } else {
248      base::DictionaryValue* parent = id_to_descriptor[parent_id];
249      base::ListValue* guests = NULL;
250      if (!parent->GetList(kGuestList, &guests)) {
251        guests = new base::ListValue();
252        parent->Set(kGuestList, guests);
253      }
254      guests->Append(descriptor);
255    }
256  }
257
258  SendSerializedTargets(list_value);
259}
260
261// AdbTargetsUIHandler --------------------------------------------------------
262
263class AdbTargetsUIHandler
264    : public DevToolsTargetsUIHandler,
265      public DevToolsAndroidBridge::DeviceListListener {
266 public:
267  AdbTargetsUIHandler(const Callback& callback, Profile* profile);
268  virtual ~AdbTargetsUIHandler();
269
270  virtual void Open(const std::string& browser_id,
271                    const std::string& url,
272                    const DevToolsTargetsUIHandler::TargetCallback&) OVERRIDE;
273
274  virtual scoped_refptr<content::DevToolsAgentHost> GetBrowserAgentHost(
275      const std::string& browser_id) OVERRIDE;
276
277 private:
278  // DevToolsAndroidBridge::Listener overrides.
279  virtual void DeviceListChanged(
280      const DevToolsAndroidBridge::RemoteDevices& devices) OVERRIDE;
281
282  Profile* profile_;
283
284  typedef std::map<std::string,
285      scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> > RemoteBrowsers;
286  RemoteBrowsers remote_browsers_;
287};
288
289AdbTargetsUIHandler::AdbTargetsUIHandler(const Callback& callback,
290                                         Profile* profile)
291    : DevToolsTargetsUIHandler(kTargetSourceRemote, callback),
292      profile_(profile) {
293  DevToolsAndroidBridge* android_bridge =
294      DevToolsAndroidBridge::Factory::GetForProfile(profile_);
295  if (android_bridge)
296    android_bridge->AddDeviceListListener(this);
297}
298
299AdbTargetsUIHandler::~AdbTargetsUIHandler() {
300  DevToolsAndroidBridge* android_bridge =
301      DevToolsAndroidBridge::Factory::GetForProfile(profile_);
302  if (android_bridge)
303    android_bridge->RemoveDeviceListListener(this);
304}
305
306static void CallOnTarget(
307    const DevToolsTargetsUIHandler::TargetCallback& callback,
308    DevToolsAndroidBridge::RemotePage* page) {
309  scoped_ptr<DevToolsAndroidBridge::RemotePage> my_page(page);
310  callback.Run(my_page ? my_page->GetTarget() : NULL);
311}
312
313void AdbTargetsUIHandler::Open(
314    const std::string& browser_id,
315    const std::string& url,
316    const DevToolsTargetsUIHandler::TargetCallback& callback) {
317  RemoteBrowsers::iterator it = remote_browsers_.find(browser_id);
318  if (it !=  remote_browsers_.end())
319    it->second->Open(url, base::Bind(&CallOnTarget, callback));
320}
321
322scoped_refptr<content::DevToolsAgentHost>
323AdbTargetsUIHandler::GetBrowserAgentHost(
324    const std::string& browser_id) {
325  RemoteBrowsers::iterator it = remote_browsers_.find(browser_id);
326  return it != remote_browsers_.end() ? it->second->GetAgentHost() : NULL;
327}
328
329void AdbTargetsUIHandler::DeviceListChanged(
330    const DevToolsAndroidBridge::RemoteDevices& devices) {
331  remote_browsers_.clear();
332  STLDeleteValues(&targets_);
333
334  base::ListValue device_list;
335  for (DevToolsAndroidBridge::RemoteDevices::const_iterator dit =
336      devices.begin(); dit != devices.end(); ++dit) {
337    DevToolsAndroidBridge::RemoteDevice* device = dit->get();
338    base::DictionaryValue* device_data = new base::DictionaryValue();
339    device_data->SetString(kAdbModelField, device->model());
340    device_data->SetString(kAdbSerialField, device->serial());
341    device_data->SetBoolean(kAdbConnectedField, device->is_connected());
342    std::string device_id = base::StringPrintf(
343        kAdbDeviceIdFormat,
344        device->serial().c_str());
345    device_data->SetString(kTargetIdField, device_id);
346    base::ListValue* browser_list = new base::ListValue();
347    device_data->Set(kAdbBrowsersList, browser_list);
348
349    DevToolsAndroidBridge::RemoteBrowsers& browsers = device->browsers();
350    for (DevToolsAndroidBridge::RemoteBrowsers::iterator bit =
351        browsers.begin(); bit != browsers.end(); ++bit) {
352      DevToolsAndroidBridge::RemoteBrowser* browser = bit->get();
353      base::DictionaryValue* browser_data = new base::DictionaryValue();
354      browser_data->SetString(kAdbBrowserNameField, browser->display_name());
355      browser_data->SetString(kAdbBrowserVersionField, browser->version());
356      DevToolsAndroidBridge::RemoteBrowser::ParsedVersion parsed =
357          browser->GetParsedVersion();
358      browser_data->SetInteger(
359          kAdbBrowserChromeVersionField,
360          browser->IsChrome() && !parsed.empty() ? parsed[0] : 0);
361      std::string browser_id = base::StringPrintf(
362          "browser:%s:%s:%s:%s",
363          device->serial().c_str(), // Ensure uniqueness across devices.
364          browser->display_name().c_str(),  // Sort by display name.
365          browser->version().c_str(),  // Then by version.
366          browser->socket().c_str());  // Ensure uniqueness on the device.
367      browser_data->SetString(kTargetIdField, browser_id);
368      browser_data->SetString(kTargetSourceField, source_id());
369
370      base::Version remote_version;
371      remote_version = base::Version(browser->version());
372
373      chrome::VersionInfo version_info;
374      base::Version local_version(version_info.Version());
375
376      browser_data->SetBoolean(kCompatibleVersion,
377          (!remote_version.IsValid()) || (!local_version.IsValid()) ||
378          remote_version.components()[0] <= local_version.components()[0]);
379
380      base::ListValue* page_list = new base::ListValue();
381      remote_browsers_[browser_id] = browser;
382            browser_data->Set(kAdbPagesList, page_list);
383      std::vector<DevToolsAndroidBridge::RemotePage*> pages =
384          browser->CreatePages();
385      for (std::vector<DevToolsAndroidBridge::RemotePage*>::iterator it =
386          pages.begin(); it != pages.end(); ++it) {
387        DevToolsAndroidBridge::RemotePage* page =  *it;
388        DevToolsTargetImpl* target = page->GetTarget();
389        base::DictionaryValue* target_data = Serialize(*target);
390        target_data->SetBoolean(
391            kAdbAttachedForeignField,
392            target->IsAttached() &&
393                !DevToolsAndroidBridge::HasDevToolsWindow(target->GetId()));
394        // Pass the screen size in the target object to make sure that
395        // the caching logic does not prevent the target item from updating
396        // when the screen size changes.
397        gfx::Size screen_size = device->screen_size();
398        target_data->SetInteger(kAdbScreenWidthField, screen_size.width());
399        target_data->SetInteger(kAdbScreenHeightField, screen_size.height());
400        targets_[target->GetId()] = target;
401        page_list->Append(target_data);
402      }
403      browser_list->Append(browser_data);
404    }
405
406    device_list.Append(device_data);
407  }
408  SendSerializedTargets(device_list);
409}
410
411} // namespace
412
413// DevToolsTargetsUIHandler ---------------------------------------------------
414
415DevToolsTargetsUIHandler::DevToolsTargetsUIHandler(
416    const std::string& source_id,
417    const Callback& callback)
418    : source_id_(source_id),
419      callback_(callback) {
420}
421
422DevToolsTargetsUIHandler::~DevToolsTargetsUIHandler() {
423  STLDeleteValues(&targets_);
424}
425
426// static
427scoped_ptr<DevToolsTargetsUIHandler>
428DevToolsTargetsUIHandler::CreateForLocal(
429    const DevToolsTargetsUIHandler::Callback& callback) {
430  return scoped_ptr<DevToolsTargetsUIHandler>(
431      new LocalTargetsUIHandler(callback));
432}
433
434// static
435scoped_ptr<DevToolsTargetsUIHandler>
436DevToolsTargetsUIHandler::CreateForAdb(
437    const DevToolsTargetsUIHandler::Callback& callback, Profile* profile) {
438  return scoped_ptr<DevToolsTargetsUIHandler>(
439      new AdbTargetsUIHandler(callback, profile));
440}
441
442DevToolsTargetImpl* DevToolsTargetsUIHandler::GetTarget(
443    const std::string& target_id) {
444  TargetMap::iterator it = targets_.find(target_id);
445  if (it != targets_.end())
446    return it->second;
447  return NULL;
448}
449
450void DevToolsTargetsUIHandler::Open(const std::string& browser_id,
451                                    const std::string& url,
452                                    const TargetCallback& callback) {
453  callback.Run(NULL);
454}
455
456scoped_refptr<content::DevToolsAgentHost>
457DevToolsTargetsUIHandler::GetBrowserAgentHost(const std::string& browser_id) {
458  return NULL;
459}
460
461base::DictionaryValue* DevToolsTargetsUIHandler::Serialize(
462    const DevToolsTargetImpl& target) {
463  base::DictionaryValue* target_data = new base::DictionaryValue();
464  target_data->SetString(kTargetSourceField, source_id_);
465  target_data->SetString(kTargetIdField, target.GetId());
466  target_data->SetString(kTargetTypeField, target.GetType());
467  target_data->SetBoolean(kAttachedField, target.IsAttached());
468  target_data->SetString(kUrlField, target.GetURL().spec());
469  target_data->SetString(kNameField, net::EscapeForHTML(target.GetTitle()));
470  target_data->SetString(kFaviconUrlField, target.GetFaviconURL().spec());
471  target_data->SetString(kDescriptionField, target.GetDescription());
472  return target_data;
473}
474
475void DevToolsTargetsUIHandler::SendSerializedTargets(
476    const base::ListValue& list) {
477  callback_.Run(source_id_, list);
478}
479
480void DevToolsTargetsUIHandler::ForceUpdate() {
481}
482
483// PortForwardingStatusSerializer ---------------------------------------------
484
485PortForwardingStatusSerializer::PortForwardingStatusSerializer(
486    const Callback& callback, Profile* profile)
487      : callback_(callback),
488        profile_(profile) {
489  DevToolsAndroidBridge* android_bridge =
490      DevToolsAndroidBridge::Factory::GetForProfile(profile_);
491  if (android_bridge)
492    android_bridge->AddPortForwardingListener(this);
493}
494
495PortForwardingStatusSerializer::~PortForwardingStatusSerializer() {
496  DevToolsAndroidBridge* android_bridge =
497      DevToolsAndroidBridge::Factory::GetForProfile(profile_);
498  if (android_bridge)
499    android_bridge->RemovePortForwardingListener(this);
500}
501
502void PortForwardingStatusSerializer::PortStatusChanged(
503    const DevicesStatus& status) {
504  base::DictionaryValue result;
505  for (DevicesStatus::const_iterator sit = status.begin();
506      sit != status.end(); ++sit) {
507    base::DictionaryValue* device_status_dict = new base::DictionaryValue();
508    const PortStatusMap& device_status_map = sit->second;
509    for (PortStatusMap::const_iterator it = device_status_map.begin();
510         it != device_status_map.end(); ++it) {
511      device_status_dict->SetInteger(
512          base::StringPrintf("%d", it->first), it->second);
513    }
514
515    std::string device_id = base::StringPrintf(
516        kAdbDeviceIdFormat,
517        sit->first.c_str());
518    result.Set(device_id, device_status_dict);
519  }
520  callback_.Run(result);
521}
522