inspect_ui.cc revision 5f1c94371a64b3196d4be9466099bb892df9b88e
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(url::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);
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  if (profile->IsOffTheRecord()) {
330    ShowIncognitoWarning();
331  } else {
332    AddTargetUIHandler(
333        DevToolsTargetsUIHandler::CreateForAdb(callback, profile));
334  }
335
336  port_status_serializer_.reset(
337      new PortForwardingStatusSerializer(
338          base::Bind(&InspectUI::PopulatePortStatus, base::Unretained(this)),
339          profile));
340
341  notification_registrar_.Add(this,
342                              content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
343                              content::NotificationService::AllSources());
344
345  pref_change_registrar_.Init(profile->GetPrefs());
346  pref_change_registrar_.Add(prefs::kDevToolsDiscoverUsbDevicesEnabled,
347      base::Bind(&InspectUI::UpdateDiscoverUsbDevicesEnabled,
348                 base::Unretained(this)));
349  pref_change_registrar_.Add(prefs::kDevToolsPortForwardingEnabled,
350      base::Bind(&InspectUI::UpdatePortForwardingEnabled,
351                 base::Unretained(this)));
352  pref_change_registrar_.Add(prefs::kDevToolsPortForwardingConfig,
353      base::Bind(&InspectUI::UpdatePortForwardingConfig,
354                 base::Unretained(this)));
355}
356
357void InspectUI::StopListeningNotifications() {
358  if (target_handlers_.empty())
359    return;
360
361  STLDeleteValues(&target_handlers_);
362
363  port_status_serializer_.reset();
364
365  notification_registrar_.RemoveAll();
366  pref_change_registrar_.RemoveAll();
367}
368
369content::WebUIDataSource* InspectUI::CreateInspectUIHTMLSource() {
370  content::WebUIDataSource* source =
371      content::WebUIDataSource::Create(chrome::kChromeUIInspectHost);
372  source->AddResourcePath("inspect.css", IDR_INSPECT_CSS);
373  source->AddResourcePath("inspect.js", IDR_INSPECT_JS);
374  source->SetDefaultResource(IDR_INSPECT_HTML);
375  source->OverrideContentSecurityPolicyFrameSrc(
376      "frame-src chrome://serviceworker-internals;");
377  serviceworker_webui_.reset(web_ui()->GetWebContents()->CreateWebUI(
378      GURL(content::kChromeUIServiceWorkerInternalsURL)));
379  serviceworker_webui_->OverrideJavaScriptFrame(
380      content::kChromeUIServiceWorkerInternalsHost);
381  return source;
382}
383
384void InspectUI::RenderViewCreated(content::RenderViewHost* render_view_host) {
385  serviceworker_webui_->GetController()->RenderViewCreated(render_view_host);
386}
387
388void InspectUI::RenderViewReused(content::RenderViewHost* render_view_host) {
389  serviceworker_webui_->GetController()->RenderViewReused(render_view_host);
390}
391
392bool InspectUI::OverrideHandleWebUIMessage(const GURL& source_url,
393                                           const std::string& message,
394                                           const base::ListValue& args) {
395  if (source_url.SchemeIs(content::kChromeUIScheme) &&
396      source_url.host() == content::kChromeUIServiceWorkerInternalsHost) {
397    serviceworker_webui_->ProcessWebUIMessage(source_url, message, args);
398    return true;
399  }
400  return false;
401}
402
403void InspectUI::UpdateDiscoverUsbDevicesEnabled() {
404  web_ui()->CallJavascriptFunction(
405      "updateDiscoverUsbDevicesEnabled",
406      *GetPrefValue(prefs::kDevToolsDiscoverUsbDevicesEnabled));
407}
408
409void InspectUI::UpdatePortForwardingEnabled() {
410  web_ui()->CallJavascriptFunction(
411      "updatePortForwardingEnabled",
412      *GetPrefValue(prefs::kDevToolsPortForwardingEnabled));
413}
414
415void InspectUI::UpdatePortForwardingConfig() {
416  web_ui()->CallJavascriptFunction(
417      "updatePortForwardingConfig",
418      *GetPrefValue(prefs::kDevToolsPortForwardingConfig));
419}
420
421void InspectUI::SetPortForwardingDefaults() {
422  Profile* profile = Profile::FromWebUI(web_ui());
423  PrefService* prefs = profile->GetPrefs();
424
425  bool default_set;
426  if (!GetPrefValue(prefs::kDevToolsPortForwardingDefaultSet)->
427      GetAsBoolean(&default_set) || default_set) {
428    return;
429  }
430
431  // This is the first chrome://inspect invocation on a fresh profile or after
432  // upgrade from a version that did not have kDevToolsPortForwardingDefaultSet.
433  prefs->SetBoolean(prefs::kDevToolsPortForwardingDefaultSet, true);
434
435  bool enabled;
436  const base::DictionaryValue* config;
437  if (!GetPrefValue(prefs::kDevToolsPortForwardingEnabled)->
438        GetAsBoolean(&enabled) ||
439      !GetPrefValue(prefs::kDevToolsPortForwardingConfig)->
440        GetAsDictionary(&config)) {
441    return;
442  }
443
444  // Do nothing if user already took explicit action.
445  if (enabled || config->size() != 0)
446    return;
447
448  base::DictionaryValue default_config;
449  default_config.SetString(
450      kPortForwardingDefaultPort, kPortForwardingDefaultLocation);
451  prefs->Set(prefs::kDevToolsPortForwardingConfig, default_config);
452}
453
454const base::Value* InspectUI::GetPrefValue(const char* name) {
455  Profile* profile = Profile::FromWebUI(web_ui());
456  return profile->GetPrefs()->FindPreference(name)->GetValue();
457}
458
459void InspectUI::AddTargetUIHandler(
460    scoped_ptr<DevToolsTargetsUIHandler> handler) {
461  DevToolsTargetsUIHandler* handler_ptr = handler.release();
462  target_handlers_[handler_ptr->source_id()] = handler_ptr;
463}
464
465DevToolsTargetsUIHandler* InspectUI::FindTargetHandler(
466    const std::string& source_id) {
467  TargetHandlerMap::iterator it = target_handlers_.find(source_id);
468     return it != target_handlers_.end() ? it->second : NULL;
469}
470
471DevToolsTargetImpl* InspectUI::FindTarget(
472    const std::string& source_id, const std::string& target_id) {
473  TargetHandlerMap::iterator it = target_handlers_.find(source_id);
474  return it != target_handlers_.end() ?
475         it->second->GetTarget(target_id) : NULL;
476}
477
478void InspectUI::PopulateTargets(const std::string& source,
479                                const base::ListValue& targets) {
480  web_ui()->CallJavascriptFunction("populateTargets",
481                                   base::StringValue(source),
482                                   targets);
483}
484
485void InspectUI::PopulatePortStatus(const base::Value& status) {
486  web_ui()->CallJavascriptFunction("populatePortStatus", status);
487}
488
489void InspectUI::ShowIncognitoWarning() {
490  web_ui()->CallJavascriptFunction("showIncognitoWarning");
491}
492