inspect_ui.cc revision a3f7b4e666c476898878fa745f637129375cd889
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/ui/webui/inspect_ui.h"
6
7#include <set>
8
9#include "base/bind.h"
10#include "base/bind_helpers.h"
11#include "base/json/json_writer.h"
12#include "base/memory/ref_counted_memory.h"
13#include "base/strings/string_number_conversions.h"
14#include "base/strings/string_util.h"
15#include "base/strings/utf_string_conversions.h"
16#include "base/values.h"
17#include "chrome/browser/devtools/devtools_window.h"
18#include "chrome/browser/extensions/extension_service.h"
19#include "chrome/browser/profiles/profile.h"
20#include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
21#include "chrome/common/url_constants.h"
22#include "content/public/browser/browser_thread.h"
23#include "content/public/browser/child_process_data.h"
24#include "content/public/browser/devtools_agent_host.h"
25#include "content/public/browser/devtools_client_host.h"
26#include "content/public/browser/devtools_manager.h"
27#include "content/public/browser/favicon_status.h"
28#include "content/public/browser/navigation_entry.h"
29#include "content/public/browser/notification_service.h"
30#include "content/public/browser/notification_source.h"
31#include "content/public/browser/notification_types.h"
32#include "content/public/browser/render_process_host.h"
33#include "content/public/browser/render_view_host.h"
34#include "content/public/browser/web_contents.h"
35#include "content/public/browser/web_ui.h"
36#include "content/public/browser/web_ui_data_source.h"
37#include "content/public/browser/web_ui_message_handler.h"
38#include "content/public/browser/worker_service.h"
39#include "content/public/browser/worker_service_observer.h"
40#include "content/public/common/process_type.h"
41#include "grit/browser_resources.h"
42#include "grit/generated_resources.h"
43#include "net/base/escape.h"
44#include "net/base/net_errors.h"
45#include "ui/base/resource/resource_bundle.h"
46
47using content::BrowserThread;
48using content::ChildProcessData;
49using content::DevToolsAgentHost;
50using content::DevToolsClientHost;
51using content::DevToolsManager;
52using content::RenderProcessHost;
53using content::RenderViewHost;
54using content::RenderViewHostDelegate;
55using content::RenderWidgetHost;
56using content::WebContents;
57using content::WebUIMessageHandler;
58using content::WorkerService;
59using content::WorkerServiceObserver;
60
61namespace {
62
63static const char kDataFile[] = "targets-data.json";
64static const char kAdbPages[] = "adb-pages";
65
66static const char kAppTargetType[] = "app";
67static const char kExtensionTargetType[]  = "extension";
68static const char kPageTargetType[]  = "page";
69static const char kWorkerTargetType[]  = "worker";
70static const char kAdbTargetType[]  = "adb_page";
71
72static const char kInspectCommand[]  = "inspect";
73static const char kTerminateCommand[]  = "terminate";
74
75static const char kTargetTypeField[]  = "type";
76static const char kAttachedField[]  = "attached";
77static const char kProcessIdField[]  = "processId";
78static const char kRouteIdField[]  = "routeId";
79static const char kUrlField[]  = "url";
80static const char kNameField[]  = "name";
81static const char kFaviconUrlField[] = "faviconUrl";
82static const char kPidField[]  = "pid";
83static const char kAdbSerialField[] = "adbSerial";
84static const char kAdbModelField[] = "adbModel";
85static const char kAdbPackageField[] = "adbPackage";
86static const char kAdbSocketField[] = "adbSocket";
87static const char kAdbDebugUrlField[] = "adbDebugUrl";
88static const char kAdbFrontendUrlField[] = "adbFrontendUrl";
89
90DictionaryValue* BuildTargetDescriptor(
91    const std::string& target_type,
92    bool attached,
93    const GURL& url,
94    const std::string& name,
95    const GURL& favicon_url,
96    int process_id,
97    int route_id,
98    base::ProcessHandle handle = base::kNullProcessHandle) {
99  DictionaryValue* target_data = new DictionaryValue();
100  target_data->SetString(kTargetTypeField, target_type);
101  target_data->SetBoolean(kAttachedField, attached);
102  target_data->SetInteger(kProcessIdField, process_id);
103  target_data->SetInteger(kRouteIdField, route_id);
104  target_data->SetString(kUrlField, url.spec());
105  target_data->SetString(kNameField, net::EscapeForHTML(name));
106  target_data->SetInteger(kPidField, base::GetProcId(handle));
107  target_data->SetString(kFaviconUrlField, favicon_url.spec());
108
109  return target_data;
110}
111
112bool HasClientHost(RenderViewHost* rvh) {
113  if (!DevToolsAgentHost::HasFor(rvh))
114    return false;
115
116  scoped_refptr<DevToolsAgentHost> agent(
117      DevToolsAgentHost::GetOrCreateFor(rvh));
118  return agent->IsAttached();
119}
120
121DictionaryValue* BuildTargetDescriptor(RenderViewHost* rvh, bool is_tab) {
122  WebContents* web_contents = WebContents::FromRenderViewHost(rvh);
123  std::string title;
124  std::string target_type = is_tab ? kPageTargetType : "";
125  GURL url;
126  GURL favicon_url;
127  if (web_contents) {
128    url = web_contents->GetURL();
129    title = UTF16ToUTF8(web_contents->GetTitle());
130    content::NavigationController& controller = web_contents->GetController();
131    content::NavigationEntry* entry = controller.GetActiveEntry();
132    if (entry != NULL && entry->GetURL().is_valid())
133      favicon_url = entry->GetFavicon().url;
134
135    Profile* profile = Profile::FromBrowserContext(
136        web_contents->GetBrowserContext());
137    if (profile) {
138      ExtensionService* extension_service = profile->GetExtensionService();
139      const extensions::Extension* extension = extension_service->
140          extensions()->GetByID(url.host());
141      if (extension) {
142        if (extension->is_hosted_app()
143            || extension->is_legacy_packaged_app()
144            || extension->is_platform_app())
145          target_type = kAppTargetType;
146        else
147          target_type = kExtensionTargetType;
148        title = extension->name();
149      }
150    }
151  }
152
153  return BuildTargetDescriptor(target_type,
154                               HasClientHost(rvh),
155                               url,
156                               title,
157                               favicon_url,
158                               rvh->GetProcess()->GetID(),
159                               rvh->GetRoutingID());
160}
161
162// Appends the inspectable workers to the list of RenderViews, and sends the
163// response back to the webui system.
164void SendDescriptors(
165    ListValue* rvh_list,
166    const content::WebUIDataSource::GotDataCallback& callback) {
167  std::vector<WorkerService::WorkerInfo> worker_info =
168      WorkerService::GetInstance()->GetWorkers();
169  for (size_t i = 0; i < worker_info.size(); ++i) {
170    rvh_list->Append(BuildTargetDescriptor(
171        kWorkerTargetType,
172        false,
173        worker_info[i].url,
174        UTF16ToUTF8(worker_info[i].name),
175        GURL(),
176        worker_info[i].process_id,
177        worker_info[i].route_id,
178        worker_info[i].handle));
179  }
180
181  std::string json_string;
182  base::JSONWriter::Write(rvh_list, &json_string);
183  callback.Run(base::RefCountedString::TakeString(&json_string));
184}
185
186bool HandleDataRequestCallback(
187    const std::string& path,
188    const content::WebUIDataSource::GotDataCallback& callback) {
189  std::set<RenderViewHost*> tab_rvhs;
190  for (TabContentsIterator it; !it.done(); it.Next())
191    tab_rvhs.insert(it->GetRenderViewHost());
192
193  scoped_ptr<ListValue> rvh_list(new ListValue());
194
195  std::vector<RenderViewHost*> rvh_vector =
196      DevToolsAgentHost::GetValidRenderViewHosts();
197
198  for (std::vector<RenderViewHost*>::iterator it(rvh_vector.begin());
199       it != rvh_vector.end(); it++) {
200    bool is_tab = tab_rvhs.find(*it) != tab_rvhs.end();
201    rvh_list->Append(BuildTargetDescriptor(*it, is_tab));
202  }
203
204  BrowserThread::PostTask(
205      BrowserThread::IO,
206      FROM_HERE,
207      base::Bind(&SendDescriptors, base::Owned(rvh_list.release()), callback));
208  return true;
209}
210
211class InspectMessageHandler : public WebUIMessageHandler {
212 public:
213  explicit InspectMessageHandler(DevToolsAdbBridge* adb_bridge)
214      : adb_bridge_(adb_bridge) {}
215  virtual ~InspectMessageHandler() {}
216
217 private:
218  // WebUIMessageHandler implementation.
219  virtual void RegisterMessages() OVERRIDE;
220
221  // Callback for "openDevTools" message.
222  void HandleInspectCommand(const ListValue* args);
223  void HandleTerminateCommand(const ListValue* args);
224
225  bool GetProcessAndRouteId(const ListValue* args,
226                            int* process_id,
227                            int* route_id);
228
229  DevToolsAdbBridge* adb_bridge_;
230
231  DISALLOW_COPY_AND_ASSIGN(InspectMessageHandler);
232};
233
234void InspectMessageHandler::RegisterMessages() {
235  web_ui()->RegisterMessageCallback(kInspectCommand,
236      base::Bind(&InspectMessageHandler::HandleInspectCommand,
237                 base::Unretained(this)));
238  web_ui()->RegisterMessageCallback(kTerminateCommand,
239      base::Bind(&InspectMessageHandler::HandleTerminateCommand,
240                 base::Unretained(this)));
241}
242
243void InspectMessageHandler::HandleInspectCommand(const ListValue* args) {
244  int process_id;
245  int route_id;
246  if (!GetProcessAndRouteId(args, &process_id, &route_id) || process_id == 0
247      || route_id == 0) {
248    // Check for ADB serial
249    const DictionaryValue* data;
250    std::string serial;
251    std::string socket;
252    std::string debug_url;
253    std::string frontend_url;
254    if (args->GetSize() == 1 && args->GetDictionary(0, &data) &&
255        data->GetString(kAdbSerialField, &serial) &&
256        data->GetString(kAdbSocketField, &socket) &&
257        data->GetString(kAdbDebugUrlField, &debug_url) &&
258        data->GetString(kAdbFrontendUrlField, &frontend_url)) {
259      adb_bridge_->Attach(serial, socket, debug_url, frontend_url);
260    }
261    return;
262  }
263
264  RenderViewHost* rvh = RenderViewHost::FromID(process_id, route_id);
265  if (rvh) {
266    DevToolsWindow::OpenDevToolsWindow(rvh);
267    return;
268  }
269
270  Profile* profile = Profile::FromWebUI(web_ui());
271  if (!profile)
272    return;
273  scoped_refptr<DevToolsAgentHost> agent_host(
274      DevToolsAgentHost::GetForWorker(process_id, route_id));
275  if (!agent_host.get())
276    return;
277
278  DevToolsWindow::OpenDevToolsWindowForWorker(profile, agent_host.get());
279}
280
281static void TerminateWorker(int process_id, int route_id) {
282  WorkerService::GetInstance()->TerminateWorker(process_id, route_id);
283}
284
285void InspectMessageHandler::HandleTerminateCommand(const ListValue* args) {
286  int process_id;
287  int route_id;
288  if (!GetProcessAndRouteId(args, &process_id, &route_id))
289    return;
290
291  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
292      base::Bind(&TerminateWorker, process_id, route_id));
293}
294
295bool InspectMessageHandler::GetProcessAndRouteId(const ListValue* args,
296                                                 int* process_id,
297                                                 int* route_id) {
298  const DictionaryValue* data;
299  if (args->GetSize() == 1 && args->GetDictionary(0, &data) &&
300      data->GetInteger(kProcessIdField, process_id) &&
301      data->GetInteger(kRouteIdField, route_id)) {
302    return true;
303  }
304  return false;
305}
306
307}  // namespace
308
309class InspectUI::WorkerCreationDestructionListener
310    : public WorkerServiceObserver,
311      public base::RefCountedThreadSafe<WorkerCreationDestructionListener> {
312 public:
313  WorkerCreationDestructionListener()
314      : discovery_ui_(NULL) {}
315
316  void Init(InspectUI* workers_ui) {
317    DCHECK(workers_ui);
318    DCHECK(!discovery_ui_);
319    discovery_ui_ = workers_ui;
320    BrowserThread::PostTask(
321        BrowserThread::IO, FROM_HERE,
322        base::Bind(&WorkerCreationDestructionListener::RegisterObserver,
323                   this));
324  }
325
326  void InspectUIDestroyed() {
327    DCHECK(discovery_ui_);
328    discovery_ui_ = NULL;
329    BrowserThread::PostTask(
330        BrowserThread::IO, FROM_HERE,
331        base::Bind(&WorkerCreationDestructionListener::UnregisterObserver,
332                   this));
333  }
334
335 private:
336  friend class base::RefCountedThreadSafe<WorkerCreationDestructionListener>;
337  virtual ~WorkerCreationDestructionListener() {}
338
339  virtual void WorkerCreated(
340      const GURL& url,
341      const string16& name,
342      int process_id,
343      int route_id) OVERRIDE {
344    BrowserThread::PostTask(
345        BrowserThread::UI, FROM_HERE,
346        base::Bind(&WorkerCreationDestructionListener::NotifyItemsChanged,
347                   this));
348  }
349
350  virtual void WorkerDestroyed(int process_id, int route_id) OVERRIDE {
351    BrowserThread::PostTask(
352        BrowserThread::UI, FROM_HERE,
353        base::Bind(&WorkerCreationDestructionListener::NotifyItemsChanged,
354                   this));
355  }
356
357  void NotifyItemsChanged() {
358    if (discovery_ui_)
359      discovery_ui_->RefreshUI();
360  }
361
362  void RegisterObserver() {
363    WorkerService::GetInstance()->AddObserver(this);
364  }
365
366  void UnregisterObserver() {
367    WorkerService::GetInstance()->RemoveObserver(this);
368  }
369
370  InspectUI* discovery_ui_;
371};
372
373InspectUI::InspectUI(content::WebUI* web_ui)
374    : WebUIController(web_ui),
375      observer_(new WorkerCreationDestructionListener()),
376      weak_factory_(this) {
377  observer_->Init(this);
378  Profile* profile = Profile::FromWebUI(web_ui);
379  adb_bridge_ = DevToolsAdbBridge::Factory::GetForProfile(profile);
380  adb_bridge_->AddListener(this);
381  web_ui->AddMessageHandler(new InspectMessageHandler(adb_bridge_));
382  content::WebUIDataSource::Add(profile, CreateInspectUIHTMLSource());
383
384  registrar_.Add(this,
385                 content::NOTIFICATION_WEB_CONTENTS_CONNECTED,
386                 content::NotificationService::AllSources());
387  registrar_.Add(this,
388                 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
389                 content::NotificationService::AllSources());
390  registrar_.Add(this,
391                 content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
392                 content::NotificationService::AllSources());
393}
394
395InspectUI::~InspectUI() {
396  StopListeningNotifications();
397}
398
399void InspectUI::RefreshUI() {
400  web_ui()->CallJavascriptFunction("populateLists");
401}
402
403// static
404bool InspectUI::WeakHandleRequestCallback(
405    const base::WeakPtr<InspectUI>& inspect_ui,
406    const std::string& path,
407    const content::WebUIDataSource::GotDataCallback& callback) {
408  if (!inspect_ui.get())
409    return false;
410  return inspect_ui->HandleRequestCallback(path, callback);
411}
412
413void InspectUI::Observe(int type,
414    const content::NotificationSource& source,
415    const content::NotificationDetails& details) {
416  if (source != content::Source<WebContents>(web_ui()->GetWebContents()))
417    RefreshUI();
418  else if (type == content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED)
419    StopListeningNotifications();
420}
421
422void InspectUI::StopListeningNotifications()
423{
424  if (!observer_.get())
425    return;
426  adb_bridge_->RemoveListener(this);
427  adb_bridge_ = NULL;
428  observer_->InspectUIDestroyed();
429  observer_ = NULL;
430  registrar_.RemoveAll();
431}
432
433content::WebUIDataSource* InspectUI::CreateInspectUIHTMLSource() {
434  content::WebUIDataSource* source =
435      content::WebUIDataSource::Create(chrome::kChromeUIInspectHost);
436  source->AddResourcePath("inspect.css", IDR_INSPECT_CSS);
437  source->AddResourcePath("inspect.js", IDR_INSPECT_JS);
438  source->SetDefaultResource(IDR_INSPECT_HTML);
439  source->SetRequestFilter(base::Bind(&InspectUI::WeakHandleRequestCallback,
440                                      weak_factory_.GetWeakPtr()));
441  return source;
442}
443
444bool InspectUI::HandleRequestCallback(
445    const std::string& path,
446    const content::WebUIDataSource::GotDataCallback& callback) {
447  if (path == kDataFile)
448    return HandleDataRequestCallback(path, callback);
449  return false;
450}
451
452void InspectUI::RemotePagesChanged(DevToolsAdbBridge::RemotePages* pages) {
453  ListValue targets;
454  for (DevToolsAdbBridge::RemotePages::iterator it = pages->begin();
455       it != pages->end(); ++it) {
456    DevToolsAdbBridge::RemotePage* page = it->get();
457    DictionaryValue* target_data = BuildTargetDescriptor(kAdbTargetType,
458        false, GURL(page->url()), page->title(), GURL(page->favicon_url()), 0,
459        0);
460    target_data->SetString(kAdbSerialField, page->serial());
461    target_data->SetString(kAdbModelField, page->model());
462    target_data->SetString(kAdbPackageField, page->package());
463    target_data->SetString(kAdbSocketField, page->socket());
464    target_data->SetString(kAdbDebugUrlField, page->debug_url());
465    target_data->SetString(kAdbFrontendUrlField, page->frontend_url());
466    targets.Append(target_data);
467  }
468  web_ui()->CallJavascriptFunction("populateDeviceLists", targets);
469}
470