inspect_ui.cc revision 03b57e008b61dfcb1fbad3aea950ae0e001748b0
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 "base/prefs/pref_service.h"
8#include "base/stl_util.h"
9#include "chrome/browser/devtools/devtools_target_impl.h"
10#include "chrome/browser/devtools/devtools_targets_ui.h"
11#include "chrome/browser/devtools/devtools_ui_bindings.h"
12#include "chrome/browser/profiles/profile.h"
13#include "chrome/browser/ui/browser_navigator.h"
14#include "chrome/browser/ui/singleton_tabs.h"
15#include "chrome/browser/ui/webui/theme_source.h"
16#include "chrome/common/pref_names.h"
17#include "chrome/common/url_constants.h"
18#include "content/public/browser/devtools_agent_host.h"
19#include "content/public/browser/navigation_entry.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/user_metrics.h"
24#include "content/public/browser/web_contents.h"
25#include "content/public/browser/web_contents_delegate.h"
26#include "content/public/browser/web_contents_observer.h"
27#include "content/public/browser/web_ui.h"
28#include "content/public/browser/web_ui_data_source.h"
29#include "content/public/browser/web_ui_message_handler.h"
30#include "grit/browser_resources.h"
31
32using content::WebContents;
33using content::WebUIMessageHandler;
34
35namespace {
36
37const char kInitUICommand[]  = "init-ui";
38const char kInspectCommand[]  = "inspect";
39const char kActivateCommand[]  = "activate";
40const char kCloseCommand[]  = "close";
41const char kReloadCommand[]  = "reload";
42const char kOpenCommand[]  = "open";
43const char kInspectBrowser[] = "inspect-browser";
44const char kLocalHost[] = "localhost";
45
46const char kDiscoverUsbDevicesEnabledCommand[] =
47    "set-discover-usb-devices-enabled";
48const char kPortForwardingEnabledCommand[] =
49    "set-port-forwarding-enabled";
50const char kPortForwardingConfigCommand[] = "set-port-forwarding-config";
51
52const char kPortForwardingDefaultPort[] = "8080";
53const char kPortForwardingDefaultLocation[] = "localhost:8080";
54
55// InspectMessageHandler --------------------------------------------
56
57class InspectMessageHandler : public WebUIMessageHandler {
58 public:
59  explicit InspectMessageHandler(InspectUI* inspect_ui)
60      : inspect_ui_(inspect_ui) {}
61  virtual ~InspectMessageHandler() {}
62
63 private:
64  // WebUIMessageHandler implementation.
65  virtual void RegisterMessages() OVERRIDE;
66
67  void HandleInitUICommand(const base::ListValue* args);
68  void HandleInspectCommand(const base::ListValue* args);
69  void HandleActivateCommand(const base::ListValue* args);
70  void HandleCloseCommand(const base::ListValue* args);
71  void HandleReloadCommand(const base::ListValue* args);
72  void HandleOpenCommand(const base::ListValue* args);
73  void HandleInspectBrowserCommand(const base::ListValue* args);
74  void HandleBooleanPrefChanged(const char* pref_name,
75                                const base::ListValue* args);
76  void HandlePortForwardingConfigCommand(const base::ListValue* args);
77
78  InspectUI* inspect_ui_;
79
80  DISALLOW_COPY_AND_ASSIGN(InspectMessageHandler);
81};
82
83void InspectMessageHandler::RegisterMessages() {
84  web_ui()->RegisterMessageCallback(kInitUICommand,
85      base::Bind(&InspectMessageHandler::HandleInitUICommand,
86                 base::Unretained(this)));
87  web_ui()->RegisterMessageCallback(kInspectCommand,
88      base::Bind(&InspectMessageHandler::HandleInspectCommand,
89                 base::Unretained(this)));
90  web_ui()->RegisterMessageCallback(kActivateCommand,
91      base::Bind(&InspectMessageHandler::HandleActivateCommand,
92                 base::Unretained(this)));
93  web_ui()->RegisterMessageCallback(kCloseCommand,
94      base::Bind(&InspectMessageHandler::HandleCloseCommand,
95                 base::Unretained(this)));
96  web_ui()->RegisterMessageCallback(kDiscoverUsbDevicesEnabledCommand,
97      base::Bind(&InspectMessageHandler::HandleBooleanPrefChanged,
98                  base::Unretained(this),
99                  &prefs::kDevToolsDiscoverUsbDevicesEnabled[0]));
100  web_ui()->RegisterMessageCallback(kPortForwardingEnabledCommand,
101      base::Bind(&InspectMessageHandler::HandleBooleanPrefChanged,
102                 base::Unretained(this),
103                 &prefs::kDevToolsPortForwardingEnabled[0]));
104  web_ui()->RegisterMessageCallback(kPortForwardingConfigCommand,
105      base::Bind(&InspectMessageHandler::HandlePortForwardingConfigCommand,
106                 base::Unretained(this)));
107  web_ui()->RegisterMessageCallback(kReloadCommand,
108      base::Bind(&InspectMessageHandler::HandleReloadCommand,
109                 base::Unretained(this)));
110  web_ui()->RegisterMessageCallback(kOpenCommand,
111      base::Bind(&InspectMessageHandler::HandleOpenCommand,
112                 base::Unretained(this)));
113  web_ui()->RegisterMessageCallback(kInspectBrowser,
114      base::Bind(&InspectMessageHandler::HandleInspectBrowserCommand,
115                 base::Unretained(this)));
116}
117
118void InspectMessageHandler::HandleInitUICommand(const base::ListValue*) {
119  inspect_ui_->InitUI();
120}
121
122static bool ParseStringArgs(const base::ListValue* args,
123                            std::string* arg0,
124                            std::string* arg1,
125                            std::string* arg2 = 0) {
126  int arg_size = args->GetSize();
127  return (!arg0 || (arg_size > 0 && args->GetString(0, arg0))) &&
128         (!arg1 || (arg_size > 1 && args->GetString(1, arg1))) &&
129         (!arg2 || (arg_size > 2 && args->GetString(2, arg2)));
130}
131
132void InspectMessageHandler::HandleInspectCommand(const base::ListValue* args) {
133  std::string source;
134  std::string id;
135  if (ParseStringArgs(args, &source, &id))
136    inspect_ui_->Inspect(source, id);
137}
138
139void InspectMessageHandler::HandleActivateCommand(const base::ListValue* args) {
140  std::string source;
141  std::string id;
142  if (ParseStringArgs(args, &source, &id))
143    inspect_ui_->Activate(source, id);
144}
145
146void InspectMessageHandler::HandleCloseCommand(const base::ListValue* args) {
147  std::string source;
148  std::string id;
149  if (ParseStringArgs(args, &source, &id))
150    inspect_ui_->Close(source, id);
151}
152
153void InspectMessageHandler::HandleReloadCommand(const base::ListValue* args) {
154  std::string source;
155  std::string id;
156  if (ParseStringArgs(args, &source, &id))
157    inspect_ui_->Reload(source, id);
158}
159
160void InspectMessageHandler::HandleOpenCommand(const base::ListValue* args) {
161  std::string source_id;
162  std::string browser_id;
163  std::string url;
164  if (ParseStringArgs(args, &source_id, &browser_id, &url))
165    inspect_ui_->Open(source_id, browser_id, url);
166}
167
168void InspectMessageHandler::HandleInspectBrowserCommand(
169    const base::ListValue* args) {
170  std::string source_id;
171  std::string browser_id;
172  std::string front_end;
173  if (ParseStringArgs(args, &source_id, &browser_id, &front_end)) {
174    inspect_ui_->InspectBrowserWithCustomFrontend(
175        source_id, browser_id, GURL(front_end));
176  }
177}
178
179void InspectMessageHandler::HandleBooleanPrefChanged(
180    const char* pref_name,
181    const base::ListValue* args) {
182  Profile* profile = Profile::FromWebUI(web_ui());
183  if (!profile)
184    return;
185
186  bool enabled;
187  if (args->GetSize() == 1 && args->GetBoolean(0, &enabled))
188    profile->GetPrefs()->SetBoolean(pref_name, enabled);
189}
190
191void InspectMessageHandler::HandlePortForwardingConfigCommand(
192    const base::ListValue* args) {
193  Profile* profile = Profile::FromWebUI(web_ui());
194  if (!profile)
195    return;
196
197  const base::DictionaryValue* dict_src;
198  if (args->GetSize() == 1 && args->GetDictionary(0, &dict_src))
199    profile->GetPrefs()->Set(prefs::kDevToolsPortForwardingConfig, *dict_src);
200}
201
202// DevToolsUIBindingsEnabler ----------------------------------------
203
204class DevToolsUIBindingsEnabler
205    : public content::WebContentsObserver {
206 public:
207  DevToolsUIBindingsEnabler(WebContents* web_contents,
208                            const GURL& url);
209  virtual ~DevToolsUIBindingsEnabler() {}
210
211  DevToolsUIBindings* GetBindings();
212
213 private:
214  // contents::WebContentsObserver overrides.
215  virtual void WebContentsDestroyed() OVERRIDE;
216  virtual void AboutToNavigateRenderView(
217      content::RenderViewHost* render_view_host) OVERRIDE;
218
219  DevToolsUIBindings bindings_;
220  GURL url_;
221  DISALLOW_COPY_AND_ASSIGN(DevToolsUIBindingsEnabler);
222};
223
224DevToolsUIBindingsEnabler::DevToolsUIBindingsEnabler(
225    WebContents* web_contents,
226    const GURL& url)
227    : WebContentsObserver(web_contents),
228      bindings_(web_contents),
229      url_(url) {
230}
231
232DevToolsUIBindings* DevToolsUIBindingsEnabler::GetBindings() {
233  return &bindings_;
234}
235
236void DevToolsUIBindingsEnabler::WebContentsDestroyed() {
237  delete this;
238}
239
240void DevToolsUIBindingsEnabler::AboutToNavigateRenderView(
241    content::RenderViewHost* render_view_host) {
242   content::NavigationEntry* entry =
243       web_contents()->GetController().GetActiveEntry();
244   if (url_ != entry->GetURL())
245     delete this;
246}
247
248}  // namespace
249
250// InspectUI --------------------------------------------------------
251
252InspectUI::InspectUI(content::WebUI* web_ui)
253    : WebUIController(web_ui) {
254  web_ui->AddMessageHandler(new InspectMessageHandler(this));
255  Profile* profile = Profile::FromWebUI(web_ui);
256  content::WebUIDataSource::Add(profile, CreateInspectUIHTMLSource());
257
258  // Set up the chrome://theme/ source.
259  ThemeSource* theme = new ThemeSource(profile);
260  content::URLDataSource::Add(profile, theme);
261}
262
263InspectUI::~InspectUI() {
264  StopListeningNotifications();
265}
266
267void InspectUI::InitUI() {
268  SetPortForwardingDefaults();
269  StartListeningNotifications();
270  UpdateDiscoverUsbDevicesEnabled();
271  UpdatePortForwardingEnabled();
272  UpdatePortForwardingConfig();
273}
274
275void InspectUI::Inspect(const std::string& source_id,
276                        const std::string& target_id) {
277  DevToolsTargetImpl* target = FindTarget(source_id, target_id);
278  if (target)
279    target->Inspect(Profile::FromWebUI(web_ui()));
280}
281
282void InspectUI::Activate(const std::string& source_id,
283                         const std::string& target_id) {
284  DevToolsTargetImpl* target = FindTarget(source_id, target_id);
285  if (target)
286    target->Activate();
287}
288
289void InspectUI::Close(const std::string& source_id,
290                      const std::string& target_id) {
291  DevToolsTargetImpl* target = FindTarget(source_id, target_id);
292  if (target)
293    target->Close();
294}
295
296void InspectUI::Reload(const std::string& source_id,
297                       const std::string& target_id) {
298  DevToolsTargetImpl* target = FindTarget(source_id, target_id);
299  if (target)
300    target->Reload();
301}
302
303static void NoOp(DevToolsTargetImpl*) {}
304
305void InspectUI::Open(const std::string& source_id,
306                     const std::string& browser_id,
307                     const std::string& url) {
308  DevToolsTargetsUIHandler* handler = FindTargetHandler(source_id);
309  if (handler)
310    handler->Open(browser_id, url, base::Bind(&NoOp));
311}
312
313void InspectUI::InspectBrowserWithCustomFrontend(
314    const std::string& source_id,
315    const std::string& browser_id,
316    const GURL& frontend_url) {
317  if (!frontend_url.SchemeIs(content::kChromeUIScheme) &&
318      !frontend_url.SchemeIs(content::kChromeDevToolsScheme) &&
319      frontend_url.host() != kLocalHost) {
320    return;
321  }
322
323  DevToolsTargetsUIHandler* handler = FindTargetHandler(source_id);
324  if (!handler)
325    return;
326
327  // Fetch agent host from remote browser.
328  scoped_refptr<content::DevToolsAgentHost> agent_host =
329      handler->GetBrowserAgentHost(browser_id);
330  if (agent_host->IsAttached())
331    return;
332
333  // Create web contents for the front-end.
334  WebContents* inspect_ui = web_ui()->GetWebContents();
335  WebContents* front_end = inspect_ui->GetDelegate()->OpenURLFromTab(
336      inspect_ui,
337      content::OpenURLParams(frontend_url,
338                             content::Referrer(),
339                             NEW_FOREGROUND_TAB,
340                             content::PAGE_TRANSITION_AUTO_TOPLEVEL,
341                             false));
342
343  // Install devtools bindings.
344  DevToolsUIBindingsEnabler* bindings_enabler =
345      new DevToolsUIBindingsEnabler(front_end, frontend_url);
346  bindings_enabler->GetBindings()->AttachTo(agent_host.get());
347}
348
349void InspectUI::InspectDevices(Browser* browser) {
350  content::RecordAction(base::UserMetricsAction("InspectDevices"));
351  chrome::NavigateParams params(chrome::GetSingletonTabNavigateParams(
352      browser, GURL(chrome::kChromeUIInspectURL)));
353  params.path_behavior = chrome::NavigateParams::IGNORE_AND_NAVIGATE;
354  ShowSingletonTabOverwritingNTP(browser, params);
355}
356
357void InspectUI::Observe(int type,
358    const content::NotificationSource& source,
359    const content::NotificationDetails& details) {
360  if (source == content::Source<WebContents>(web_ui()->GetWebContents()))
361    StopListeningNotifications();
362}
363
364void InspectUI::StartListeningNotifications() {
365  if (!target_handlers_.empty())  // Possible when reloading the page.
366    StopListeningNotifications();
367
368  Profile* profile = Profile::FromWebUI(web_ui());
369
370  DevToolsTargetsUIHandler::Callback callback =
371      base::Bind(&InspectUI::PopulateTargets, base::Unretained(this));
372
373  AddTargetUIHandler(
374      DevToolsTargetsUIHandler::CreateForRenderers(callback));
375  AddTargetUIHandler(
376      DevToolsTargetsUIHandler::CreateForWorkers(callback));
377  if (profile->IsOffTheRecord()) {
378    ShowIncognitoWarning();
379  } else {
380    AddTargetUIHandler(
381        DevToolsTargetsUIHandler::CreateForAdb(callback, profile));
382  }
383
384  port_status_serializer_.reset(
385      new PortForwardingStatusSerializer(
386          base::Bind(&InspectUI::PopulatePortStatus, base::Unretained(this)),
387          profile));
388
389  notification_registrar_.Add(this,
390                              content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
391                              content::NotificationService::AllSources());
392
393  pref_change_registrar_.Init(profile->GetPrefs());
394  pref_change_registrar_.Add(prefs::kDevToolsDiscoverUsbDevicesEnabled,
395      base::Bind(&InspectUI::UpdateDiscoverUsbDevicesEnabled,
396                 base::Unretained(this)));
397  pref_change_registrar_.Add(prefs::kDevToolsPortForwardingEnabled,
398      base::Bind(&InspectUI::UpdatePortForwardingEnabled,
399                 base::Unretained(this)));
400  pref_change_registrar_.Add(prefs::kDevToolsPortForwardingConfig,
401      base::Bind(&InspectUI::UpdatePortForwardingConfig,
402                 base::Unretained(this)));
403}
404
405void InspectUI::StopListeningNotifications() {
406  if (target_handlers_.empty())
407    return;
408
409  STLDeleteValues(&target_handlers_);
410
411  port_status_serializer_.reset();
412
413  notification_registrar_.RemoveAll();
414  pref_change_registrar_.RemoveAll();
415}
416
417content::WebUIDataSource* InspectUI::CreateInspectUIHTMLSource() {
418  content::WebUIDataSource* source =
419      content::WebUIDataSource::Create(chrome::kChromeUIInspectHost);
420  source->AddResourcePath("inspect.css", IDR_INSPECT_CSS);
421  source->AddResourcePath("inspect.js", IDR_INSPECT_JS);
422  source->SetDefaultResource(IDR_INSPECT_HTML);
423  source->OverrideContentSecurityPolicyFrameSrc(
424      "frame-src chrome://serviceworker-internals;");
425  serviceworker_webui_.reset(web_ui()->GetWebContents()->CreateWebUI(
426      GURL(content::kChromeUIServiceWorkerInternalsURL)));
427  serviceworker_webui_->OverrideJavaScriptFrame(
428      content::kChromeUIServiceWorkerInternalsHost);
429  return source;
430}
431
432void InspectUI::RenderViewCreated(content::RenderViewHost* render_view_host) {
433  serviceworker_webui_->GetController()->RenderViewCreated(render_view_host);
434}
435
436void InspectUI::RenderViewReused(content::RenderViewHost* render_view_host) {
437  serviceworker_webui_->GetController()->RenderViewReused(render_view_host);
438}
439
440bool InspectUI::OverrideHandleWebUIMessage(const GURL& source_url,
441                                           const std::string& message,
442                                           const base::ListValue& args) {
443  if (source_url.SchemeIs(content::kChromeUIScheme) &&
444      source_url.host() == content::kChromeUIServiceWorkerInternalsHost) {
445    serviceworker_webui_->ProcessWebUIMessage(source_url, message, args);
446    return true;
447  }
448  return false;
449}
450
451void InspectUI::UpdateDiscoverUsbDevicesEnabled() {
452  web_ui()->CallJavascriptFunction(
453      "updateDiscoverUsbDevicesEnabled",
454      *GetPrefValue(prefs::kDevToolsDiscoverUsbDevicesEnabled));
455}
456
457void InspectUI::UpdatePortForwardingEnabled() {
458  web_ui()->CallJavascriptFunction(
459      "updatePortForwardingEnabled",
460      *GetPrefValue(prefs::kDevToolsPortForwardingEnabled));
461}
462
463void InspectUI::UpdatePortForwardingConfig() {
464  web_ui()->CallJavascriptFunction(
465      "updatePortForwardingConfig",
466      *GetPrefValue(prefs::kDevToolsPortForwardingConfig));
467}
468
469void InspectUI::SetPortForwardingDefaults() {
470  Profile* profile = Profile::FromWebUI(web_ui());
471  PrefService* prefs = profile->GetPrefs();
472
473  bool default_set;
474  if (!GetPrefValue(prefs::kDevToolsPortForwardingDefaultSet)->
475      GetAsBoolean(&default_set) || default_set) {
476    return;
477  }
478
479  // This is the first chrome://inspect invocation on a fresh profile or after
480  // upgrade from a version that did not have kDevToolsPortForwardingDefaultSet.
481  prefs->SetBoolean(prefs::kDevToolsPortForwardingDefaultSet, true);
482
483  bool enabled;
484  const base::DictionaryValue* config;
485  if (!GetPrefValue(prefs::kDevToolsPortForwardingEnabled)->
486        GetAsBoolean(&enabled) ||
487      !GetPrefValue(prefs::kDevToolsPortForwardingConfig)->
488        GetAsDictionary(&config)) {
489    return;
490  }
491
492  // Do nothing if user already took explicit action.
493  if (enabled || config->size() != 0)
494    return;
495
496  base::DictionaryValue default_config;
497  default_config.SetString(
498      kPortForwardingDefaultPort, kPortForwardingDefaultLocation);
499  prefs->Set(prefs::kDevToolsPortForwardingConfig, default_config);
500}
501
502const base::Value* InspectUI::GetPrefValue(const char* name) {
503  Profile* profile = Profile::FromWebUI(web_ui());
504  return profile->GetPrefs()->FindPreference(name)->GetValue();
505}
506
507void InspectUI::AddTargetUIHandler(
508    scoped_ptr<DevToolsTargetsUIHandler> handler) {
509  DevToolsTargetsUIHandler* handler_ptr = handler.release();
510  target_handlers_[handler_ptr->source_id()] = handler_ptr;
511}
512
513DevToolsTargetsUIHandler* InspectUI::FindTargetHandler(
514    const std::string& source_id) {
515  TargetHandlerMap::iterator it = target_handlers_.find(source_id);
516     return it != target_handlers_.end() ? it->second : NULL;
517}
518
519DevToolsTargetImpl* InspectUI::FindTarget(
520    const std::string& source_id, const std::string& target_id) {
521  TargetHandlerMap::iterator it = target_handlers_.find(source_id);
522  return it != target_handlers_.end() ?
523         it->second->GetTarget(target_id) : NULL;
524}
525
526void InspectUI::PopulateTargets(const std::string& source,
527                                const base::ListValue& targets) {
528  web_ui()->CallJavascriptFunction("populateTargets",
529                                   base::StringValue(source),
530                                   targets);
531}
532
533void InspectUI::PopulatePortStatus(const base::Value& status) {
534  web_ui()->CallJavascriptFunction("populatePortStatus", status);
535}
536
537void InspectUI::ShowIncognitoWarning() {
538  web_ui()->CallJavascriptFunction("showIncognitoWarning");
539}
540