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