inspect_ui.cc revision 868fa2fe829687343ffae624259930155e16dbd8
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  explicit WorkerCreationDestructionListener(InspectUI* workers_ui)
308      : discovery_ui_(workers_ui) {
309    BrowserThread::PostTask(
310        BrowserThread::IO, FROM_HERE,
311        base::Bind(&WorkerCreationDestructionListener::RegisterObserver,
312                   this));
313  }
314
315  void InspectUIDestroyed() {
316    discovery_ui_ = NULL;
317    BrowserThread::PostTask(
318        BrowserThread::IO, FROM_HERE,
319        base::Bind(&WorkerCreationDestructionListener::UnregisterObserver,
320                   this));
321  }
322
323 private:
324  friend class base::RefCountedThreadSafe<WorkerCreationDestructionListener>;
325  virtual ~WorkerCreationDestructionListener() {}
326
327  virtual void WorkerCreated(
328      const GURL& url,
329      const string16& name,
330      int process_id,
331      int route_id) OVERRIDE {
332    BrowserThread::PostTask(
333        BrowserThread::UI, FROM_HERE,
334        base::Bind(&WorkerCreationDestructionListener::NotifyItemsChanged,
335                   this));
336  }
337
338  virtual void WorkerDestroyed(int process_id, int route_id) OVERRIDE {
339    BrowserThread::PostTask(
340        BrowserThread::UI, FROM_HERE,
341        base::Bind(&WorkerCreationDestructionListener::NotifyItemsChanged,
342                   this));
343  }
344
345  void NotifyItemsChanged() {
346    if (discovery_ui_)
347      discovery_ui_->RefreshUI();
348  }
349
350  void RegisterObserver() {
351    WorkerService::GetInstance()->AddObserver(this);
352  }
353
354  void UnregisterObserver() {
355    WorkerService::GetInstance()->RemoveObserver(this);
356  }
357
358  InspectUI* discovery_ui_;
359};
360
361InspectUI::InspectUI(content::WebUI* web_ui)
362    : WebUIController(web_ui),
363      observer_(new WorkerCreationDestructionListener(this)),
364      weak_factory_(this) {
365  Profile* profile = Profile::FromWebUI(web_ui);
366  adb_bridge_.reset(new DevToolsAdbBridge(profile));
367  web_ui->AddMessageHandler(new InspectMessageHandler(adb_bridge_.get()));
368  content::WebUIDataSource::Add(profile, CreateInspectUIHTMLSource());
369
370  registrar_.Add(this,
371                 content::NOTIFICATION_WEB_CONTENTS_CONNECTED,
372                 content::NotificationService::AllSources());
373  registrar_.Add(this,
374                 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
375                 content::NotificationService::AllSources());
376  registrar_.Add(this,
377                 content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
378                 content::NotificationService::AllSources());
379}
380
381InspectUI::~InspectUI() {
382  StopListeningNotifications();
383}
384
385void InspectUI::RefreshUI() {
386  web_ui()->CallJavascriptFunction("populateLists");
387}
388
389// static
390bool InspectUI::WeakHandleRequestCallback(
391    const base::WeakPtr<InspectUI>& inspect_ui,
392    const std::string& path,
393    const content::WebUIDataSource::GotDataCallback& callback) {
394  if (!inspect_ui.get())
395    return false;
396  return inspect_ui->HandleRequestCallback(path, callback);
397}
398
399void InspectUI::Observe(int type,
400    const content::NotificationSource& source,
401    const content::NotificationDetails& details) {
402  if (source != content::Source<WebContents>(web_ui()->GetWebContents()))
403    RefreshUI();
404  else if (type == content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED)
405    StopListeningNotifications();
406}
407
408void InspectUI::StopListeningNotifications()
409{
410  if (!observer_.get())
411    return;
412  adb_bridge_.reset();
413  observer_->InspectUIDestroyed();
414  observer_ = NULL;
415  registrar_.RemoveAll();
416}
417
418content::WebUIDataSource* InspectUI::CreateInspectUIHTMLSource() {
419  content::WebUIDataSource* source =
420      content::WebUIDataSource::Create(chrome::kChromeUIInspectHost);
421  source->AddResourcePath("inspect.css", IDR_INSPECT_CSS);
422  source->AddResourcePath("inspect.js", IDR_INSPECT_JS);
423  source->SetDefaultResource(IDR_INSPECT_HTML);
424  source->SetRequestFilter(base::Bind(&InspectUI::WeakHandleRequestCallback,
425                                      weak_factory_.GetWeakPtr()));
426  return source;
427}
428
429bool InspectUI::HandleRequestCallback(
430    const std::string& path,
431    const content::WebUIDataSource::GotDataCallback& callback) {
432  if (path == kDataFile)
433    return HandleDataRequestCallback(path, callback);
434  if (path.find(kAdbPages) == 0)
435    return HandleAdbPagesCallback(path, callback);
436  return false;
437}
438
439bool InspectUI::HandleAdbPagesCallback(
440    const std::string& path,
441    const content::WebUIDataSource::GotDataCallback& callback) {
442  adb_bridge_->Pages(base::Bind(&InspectUI::OnAdbPages,
443                                weak_factory_.GetWeakPtr(),
444                                callback));
445  return true;
446}
447
448void InspectUI::OnAdbPages(
449    const content::WebUIDataSource::GotDataCallback& callback,
450    int result,
451    DevToolsAdbBridge::RemotePages* pages) {
452  if (result != net::OK)
453    return;
454  ListValue targets;
455  scoped_ptr<DevToolsAdbBridge::RemotePages> my_pages(pages);
456  for (DevToolsAdbBridge::RemotePages::iterator it = my_pages->begin();
457       it != my_pages->end(); ++it) {
458    DevToolsAdbBridge::RemotePage* page = it->get();
459    DictionaryValue* target_data = BuildTargetDescriptor(kAdbTargetType,
460        false, GURL(page->url()), page->title(), GURL(page->favicon_url()), 0,
461        0);
462    target_data->SetString(kAdbSerialField, page->serial());
463    target_data->SetString(kAdbModelField, page->model());
464    target_data->SetString(kAdbPackageField, page->package());
465    target_data->SetString(kAdbSocketField, page->socket());
466    target_data->SetString(kAdbDebugUrlField, page->debug_url());
467    target_data->SetString(kAdbFrontendUrlField, page->frontend_url());
468    targets.Append(target_data);
469  }
470
471  std::string json_string;
472  base::JSONWriter::Write(&targets, &json_string);
473  callback.Run(base::RefCountedString::TakeString(&json_string));
474}
475