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