inspect_ui.cc revision bb1529ce867d8845a77ec7cdf3e3003ef1771a40
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/stringprintf.h"
16#include "base/strings/utf_string_conversions.h"
17#include "base/values.h"
18#include "chrome/browser/devtools/devtools_window.h"
19#include "chrome/browser/extensions/extension_service.h"
20#include "chrome/browser/profiles/profile.h"
21#include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
22#include "chrome/browser/ui/webui/theme_source.h"
23#include "chrome/common/pref_names.h"
24#include "chrome/common/url_constants.h"
25#include "content/public/browser/browser_thread.h"
26#include "content/public/browser/child_process_data.h"
27#include "content/public/browser/devtools_agent_host.h"
28#include "content/public/browser/devtools_client_host.h"
29#include "content/public/browser/devtools_manager.h"
30#include "content/public/browser/favicon_status.h"
31#include "content/public/browser/navigation_entry.h"
32#include "content/public/browser/notification_service.h"
33#include "content/public/browser/notification_source.h"
34#include "content/public/browser/notification_types.h"
35#include "content/public/browser/render_process_host.h"
36#include "content/public/browser/render_view_host.h"
37#include "content/public/browser/web_contents.h"
38#include "content/public/browser/web_ui.h"
39#include "content/public/browser/web_ui_data_source.h"
40#include "content/public/browser/web_ui_message_handler.h"
41#include "content/public/browser/worker_service.h"
42#include "content/public/browser/worker_service_observer.h"
43#include "content/public/common/process_type.h"
44#include "grit/browser_resources.h"
45#include "grit/generated_resources.h"
46#include "net/base/escape.h"
47#include "net/base/net_errors.h"
48#include "ui/base/resource/resource_bundle.h"
49
50using content::BrowserThread;
51using content::ChildProcessData;
52using content::DevToolsAgentHost;
53using content::DevToolsClientHost;
54using content::DevToolsManager;
55using content::RenderProcessHost;
56using content::RenderViewHost;
57using content::RenderViewHostDelegate;
58using content::RenderWidgetHost;
59using content::WebContents;
60using content::WebUIMessageHandler;
61using content::WorkerService;
62using content::WorkerServiceObserver;
63
64namespace {
65
66static const char kDataFile[] = "targets-data.json";
67static const char kAdbPages[] = "adb-pages";
68
69static const char kAppTargetType[] = "app";
70static const char kExtensionTargetType[]  = "extension";
71static const char kPageTargetType[]  = "page";
72static const char kWorkerTargetType[]  = "worker";
73static const char kAdbTargetType[]  = "adb_page";
74
75static const char kInitUICommand[]  = "init-ui";
76static const char kInspectCommand[]  = "inspect";
77static const char kTerminateCommand[]  = "terminate";
78static const char kReloadCommand[]  = "reload";
79static const char kOpenCommand[]  = "open";
80
81static const char kPortForwardingEnabledCommand[] =
82    "set-port-forwarding-enabled";
83static const char kPortForwardingConfigCommand[] = "set-port-forwarding-config";
84
85static const char kTargetTypeField[]  = "type";
86static const char kAttachedField[]  = "attached";
87static const char kProcessIdField[]  = "processId";
88static const char kRouteIdField[]  = "routeId";
89static const char kUrlField[]  = "url";
90static const char kNameField[]  = "name";
91static const char kFaviconUrlField[] = "faviconUrl";
92static const char kPidField[]  = "pid";
93static const char kAdbSerialField[] = "adbSerial";
94static const char kAdbModelField[] = "adbModel";
95static const char kAdbBrowserNameField[] = "adbBrowserName";
96static const char kAdbGlobalIdField[] = "adbGlobalId";
97static const char kAdbBrowsersField[] = "browsers";
98static const char kAdbPagesField[] = "pages";
99
100DictionaryValue* BuildTargetDescriptor(
101    const std::string& target_type,
102    bool attached,
103    const GURL& url,
104    const std::string& name,
105    const GURL& favicon_url,
106    int process_id,
107    int route_id,
108    base::ProcessHandle handle = base::kNullProcessHandle) {
109  DictionaryValue* target_data = new DictionaryValue();
110  target_data->SetString(kTargetTypeField, target_type);
111  target_data->SetBoolean(kAttachedField, attached);
112  target_data->SetInteger(kProcessIdField, process_id);
113  target_data->SetInteger(kRouteIdField, route_id);
114  target_data->SetString(kUrlField, url.spec());
115  target_data->SetString(kNameField, net::EscapeForHTML(name));
116  target_data->SetInteger(kPidField, base::GetProcId(handle));
117  target_data->SetString(kFaviconUrlField, favicon_url.spec());
118
119  return target_data;
120}
121
122bool HasClientHost(RenderViewHost* rvh) {
123  if (!DevToolsAgentHost::HasFor(rvh))
124    return false;
125
126  scoped_refptr<DevToolsAgentHost> agent(
127      DevToolsAgentHost::GetOrCreateFor(rvh));
128  return agent->IsAttached();
129}
130
131DictionaryValue* BuildTargetDescriptor(RenderViewHost* rvh, bool is_tab) {
132  WebContents* web_contents = WebContents::FromRenderViewHost(rvh);
133  std::string title;
134  std::string target_type = is_tab ? kPageTargetType : "";
135  GURL url;
136  GURL favicon_url;
137  if (web_contents) {
138    url = web_contents->GetURL();
139    title = UTF16ToUTF8(web_contents->GetTitle());
140    content::NavigationController& controller = web_contents->GetController();
141    content::NavigationEntry* entry = controller.GetActiveEntry();
142    if (entry != NULL && entry->GetURL().is_valid())
143      favicon_url = entry->GetFavicon().url;
144
145    Profile* profile = Profile::FromBrowserContext(
146        web_contents->GetBrowserContext());
147    if (profile) {
148      ExtensionService* extension_service = profile->GetExtensionService();
149      const extensions::Extension* extension = extension_service->
150          extensions()->GetByID(url.host());
151      if (extension) {
152        if (extension->is_hosted_app()
153            || extension->is_legacy_packaged_app()
154            || extension->is_platform_app())
155          target_type = kAppTargetType;
156        else
157          target_type = kExtensionTargetType;
158        title = extension->name();
159      }
160    }
161  }
162
163  return BuildTargetDescriptor(target_type,
164                               HasClientHost(rvh),
165                               url,
166                               title,
167                               favicon_url,
168                               rvh->GetProcess()->GetID(),
169                               rvh->GetRoutingID());
170}
171
172class InspectMessageHandler : public WebUIMessageHandler {
173 public:
174  explicit InspectMessageHandler(InspectUI* inspect_ui)
175      : inspect_ui_(inspect_ui) {}
176  virtual ~InspectMessageHandler() {}
177
178 private:
179  // WebUIMessageHandler implementation.
180  virtual void RegisterMessages() OVERRIDE;
181
182  void HandleInitUICommand(const ListValue* args);
183  void HandleInspectCommand(const ListValue* args);
184  void HandleTerminateCommand(const ListValue* args);
185  void HandleReloadCommand(const ListValue* args);
186  void HandleOpenCommand(const ListValue* args);
187  void HandlePortForwardingEnabledCommand(const ListValue* args);
188  void HandlePortForwardingConfigCommand(const ListValue* args);
189
190  static bool GetProcessAndRouteId(const ListValue* args,
191                                   int* process_id,
192                                   int* route_id);
193
194  static bool GetRemotePageId(const ListValue* args, std::string* page_id);
195
196  InspectUI* inspect_ui_;
197
198  DISALLOW_COPY_AND_ASSIGN(InspectMessageHandler);
199};
200
201void InspectMessageHandler::RegisterMessages() {
202  web_ui()->RegisterMessageCallback(kInitUICommand,
203      base::Bind(&InspectMessageHandler::HandleInitUICommand,
204                 base::Unretained(this)));
205  web_ui()->RegisterMessageCallback(kInspectCommand,
206      base::Bind(&InspectMessageHandler::HandleInspectCommand,
207                 base::Unretained(this)));
208  web_ui()->RegisterMessageCallback(kTerminateCommand,
209      base::Bind(&InspectMessageHandler::HandleTerminateCommand,
210                 base::Unretained(this)));
211  web_ui()->RegisterMessageCallback(kPortForwardingEnabledCommand,
212      base::Bind(&InspectMessageHandler::HandlePortForwardingEnabledCommand,
213                 base::Unretained(this)));
214  web_ui()->RegisterMessageCallback(kPortForwardingConfigCommand,
215      base::Bind(&InspectMessageHandler::HandlePortForwardingConfigCommand,
216                 base::Unretained(this)));
217  web_ui()->RegisterMessageCallback(kReloadCommand,
218      base::Bind(&InspectMessageHandler::HandleReloadCommand,
219                 base::Unretained(this)));
220  web_ui()->RegisterMessageCallback(kOpenCommand,
221      base::Bind(&InspectMessageHandler::HandleOpenCommand,
222                 base::Unretained(this)));
223}
224
225void InspectMessageHandler::HandleInitUICommand(const ListValue*) {
226  inspect_ui_->InitUI();
227}
228
229void InspectMessageHandler::HandleInspectCommand(const ListValue* args) {
230  Profile* profile = Profile::FromWebUI(web_ui());
231  if (!profile)
232    return;
233
234  std::string page_id;
235  if (GetRemotePageId(args, &page_id)) {
236    inspect_ui_->InspectRemotePage(page_id);
237    return;
238  }
239
240  int process_id;
241  int route_id;
242  if (!GetProcessAndRouteId(args, &process_id, &route_id) || process_id == 0
243      || route_id == 0) {
244    return;
245  }
246
247  RenderViewHost* rvh = RenderViewHost::FromID(process_id, route_id);
248  if (rvh) {
249    DevToolsWindow::OpenDevToolsWindow(rvh);
250    return;
251  }
252
253  scoped_refptr<DevToolsAgentHost> agent_host(
254      DevToolsAgentHost::GetForWorker(process_id, route_id));
255  if (!agent_host.get())
256    return;
257
258  DevToolsWindow::OpenDevToolsWindowForWorker(profile, agent_host.get());
259}
260
261static void TerminateWorker(int process_id, int route_id) {
262  WorkerService::GetInstance()->TerminateWorker(process_id, route_id);
263}
264
265void InspectMessageHandler::HandleTerminateCommand(const ListValue* args) {
266  std::string page_id;
267  if (GetRemotePageId(args, &page_id)) {
268    inspect_ui_->CloseRemotePage(page_id);
269    return;
270  }
271
272  int process_id;
273  int route_id;
274  if (!GetProcessAndRouteId(args, &process_id, &route_id))
275    return;
276
277  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
278      base::Bind(&TerminateWorker, process_id, route_id));
279}
280
281void InspectMessageHandler::HandleReloadCommand(const ListValue* args) {
282  std::string page_id;
283  if (GetRemotePageId(args, &page_id))
284    inspect_ui_->ReloadRemotePage(page_id);
285}
286
287void InspectMessageHandler::HandleOpenCommand(const ListValue* args) {
288  std::string browser_id;
289  std::string url;
290  if (args->GetSize() == 2 &&
291      args->GetString(0, &browser_id) &&
292      args->GetString(1, &url)) {
293    inspect_ui_->OpenRemotePage(browser_id, url);
294  }
295}
296
297bool InspectMessageHandler::GetProcessAndRouteId(const ListValue* args,
298                                                 int* process_id,
299                                                 int* route_id) {
300  const DictionaryValue* data;
301  if (args->GetSize() == 1 && args->GetDictionary(0, &data) &&
302      data->GetInteger(kProcessIdField, process_id) &&
303      data->GetInteger(kRouteIdField, route_id)) {
304    return true;
305  }
306  return false;
307}
308
309void InspectMessageHandler::HandlePortForwardingEnabledCommand(
310    const ListValue* args) {
311  Profile* profile = Profile::FromWebUI(web_ui());
312  if (!profile)
313    return;
314
315  bool enabled;
316  if (args->GetSize() == 1 && args->GetBoolean(0, &enabled)) {
317    profile->GetPrefs()->SetBoolean(
318        prefs::kDevToolsPortForwardingEnabled, enabled);
319  }
320}
321
322void InspectMessageHandler::HandlePortForwardingConfigCommand(
323    const ListValue* args) {
324  Profile* profile = Profile::FromWebUI(web_ui());
325  if (!profile)
326    return;
327
328  const DictionaryValue* dict_src;
329  if (args->GetSize() == 1 && args->GetDictionary(0, &dict_src))
330    profile->GetPrefs()->Set(prefs::kDevToolsPortForwardingConfig, *dict_src);
331}
332
333bool InspectMessageHandler::GetRemotePageId(const ListValue* args,
334                                            std::string* page_id) {
335  const DictionaryValue* data;
336  if (args->GetSize() == 1 && args->GetDictionary(0, &data) &&
337      data->GetString(kAdbGlobalIdField, page_id)) {
338    return true;
339  }
340  return false;
341}
342
343}  // namespace
344
345class InspectUI::WorkerCreationDestructionListener
346    : public WorkerServiceObserver,
347      public base::RefCountedThreadSafe<WorkerCreationDestructionListener> {
348 public:
349  WorkerCreationDestructionListener()
350      : discovery_ui_(NULL) {}
351
352  void Init(InspectUI* workers_ui) {
353    DCHECK(workers_ui);
354    DCHECK(!discovery_ui_);
355    discovery_ui_ = workers_ui;
356    BrowserThread::PostTask(
357        BrowserThread::IO, FROM_HERE,
358        base::Bind(&WorkerCreationDestructionListener::RegisterObserver,
359                   this));
360  }
361
362  void InspectUIDestroyed() {
363    DCHECK(discovery_ui_);
364    discovery_ui_ = NULL;
365    BrowserThread::PostTask(
366        BrowserThread::IO, FROM_HERE,
367        base::Bind(&WorkerCreationDestructionListener::UnregisterObserver,
368                   this));
369  }
370
371  void InitUI() {
372    BrowserThread::PostTask(
373        BrowserThread::IO, FROM_HERE,
374        base::Bind(&WorkerCreationDestructionListener::CollectWorkersData,
375                   this));
376  }
377
378 private:
379  friend class base::RefCountedThreadSafe<WorkerCreationDestructionListener>;
380  virtual ~WorkerCreationDestructionListener() {}
381
382  virtual void WorkerCreated(
383      const GURL& url,
384      const string16& name,
385      int process_id,
386      int route_id) OVERRIDE {
387    CollectWorkersData();
388  }
389
390  virtual void WorkerDestroyed(int process_id, int route_id) OVERRIDE {
391    CollectWorkersData();
392  }
393
394  void CollectWorkersData() {
395    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
396    scoped_ptr<ListValue> target_list(new ListValue());
397    std::vector<WorkerService::WorkerInfo> worker_info =
398        WorkerService::GetInstance()->GetWorkers();
399    for (size_t i = 0; i < worker_info.size(); ++i) {
400      target_list->Append(BuildTargetDescriptor(
401          kWorkerTargetType,
402          false,
403          worker_info[i].url,
404          UTF16ToUTF8(worker_info[i].name),
405          GURL(),
406          worker_info[i].process_id,
407          worker_info[i].route_id,
408          worker_info[i].handle));
409    }
410
411    BrowserThread::PostTask(
412        BrowserThread::UI, FROM_HERE,
413        base::Bind(&WorkerCreationDestructionListener::PopulateWorkersList,
414                   this, base::Owned(target_list.release())));
415  }
416
417  void RegisterObserver() {
418    WorkerService::GetInstance()->AddObserver(this);
419  }
420
421  void UnregisterObserver() {
422    WorkerService::GetInstance()->RemoveObserver(this);
423  }
424
425  void PopulateWorkersList(ListValue* target_list) {
426    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
427    if (discovery_ui_) {
428      discovery_ui_->web_ui()->CallJavascriptFunction(
429          "populateWorkersList", *target_list);
430    }
431  }
432
433  InspectUI* discovery_ui_;
434};
435
436InspectUI::InspectUI(content::WebUI* web_ui)
437    : WebUIController(web_ui) {
438  web_ui->AddMessageHandler(new InspectMessageHandler(this));
439  Profile* profile = Profile::FromWebUI(web_ui);
440  content::WebUIDataSource::Add(profile, CreateInspectUIHTMLSource());
441
442  // Set up the chrome://theme/ source.
443  ThemeSource* theme = new ThemeSource(profile);
444  content::URLDataSource::Add(profile, theme);
445}
446
447InspectUI::~InspectUI() {
448  StopListeningNotifications();
449}
450
451void InspectUI::InitUI() {
452  StartListeningNotifications();
453  PopulateLists();
454  UpdatePortForwardingEnabled();
455  UpdatePortForwardingConfig();
456  observer_->InitUI();
457}
458
459void InspectUI::InspectRemotePage(const std::string& id) {
460  RemotePages::iterator it = remote_pages_.find(id);
461  if (it != remote_pages_.end()) {
462    Profile* profile = Profile::FromWebUI(web_ui());
463    it->second->Inspect(profile);
464  }
465}
466
467void InspectUI::ReloadRemotePage(const std::string& id) {
468  RemotePages::iterator it = remote_pages_.find(id);
469  if (it != remote_pages_.end())
470    it->second->Reload();
471}
472
473void InspectUI::CloseRemotePage(const std::string& id) {
474  RemotePages::iterator it = remote_pages_.find(id);
475  if (it != remote_pages_.end())
476    it->second->Close();
477}
478
479void InspectUI::OpenRemotePage(const std::string& browser_id,
480                               const std::string& url) {
481  GURL gurl(url);
482  if (!gurl.is_valid()) {
483    gurl = GURL("http://" + url);
484    if (!gurl.is_valid())
485      return;
486  }
487  RemoteBrowsers::iterator it = remote_browsers_.find(browser_id);
488  if (it != remote_browsers_.end())
489    it->second->Open(gurl.spec());
490}
491
492void InspectUI::PopulateLists() {
493  std::set<RenderViewHost*> tab_rvhs;
494  for (TabContentsIterator it; !it.done(); it.Next())
495    tab_rvhs.insert(it->GetRenderViewHost());
496
497  scoped_ptr<ListValue> target_list(new ListValue());
498
499  std::vector<RenderViewHost*> rvh_vector =
500      DevToolsAgentHost::GetValidRenderViewHosts();
501
502  for (std::vector<RenderViewHost*>::iterator it(rvh_vector.begin());
503       it != rvh_vector.end(); it++) {
504    bool is_tab = tab_rvhs.find(*it) != tab_rvhs.end();
505    target_list->Append(BuildTargetDescriptor(*it, is_tab));
506  }
507  web_ui()->CallJavascriptFunction("populateLists", *target_list.get());
508}
509
510void InspectUI::Observe(int type,
511    const content::NotificationSource& source,
512    const content::NotificationDetails& details) {
513  if (source != content::Source<WebContents>(web_ui()->GetWebContents()))
514    PopulateLists();
515  else if (type == content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED)
516    StopListeningNotifications();
517}
518
519void InspectUI::StartListeningNotifications() {
520  if (observer_)
521    return;
522
523  observer_ = new WorkerCreationDestructionListener();
524  observer_->Init(this);
525
526  Profile* profile = Profile::FromWebUI(web_ui());
527  DevToolsAdbBridge* adb_bridge =
528      DevToolsAdbBridge::Factory::GetForProfile(profile);
529  if (adb_bridge)
530    adb_bridge->AddListener(this);
531
532  notification_registrar_.Add(this,
533                              content::NOTIFICATION_WEB_CONTENTS_CONNECTED,
534                              content::NotificationService::AllSources());
535  notification_registrar_.Add(this,
536                              content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
537                              content::NotificationService::AllSources());
538  notification_registrar_.Add(this,
539                              content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
540                              content::NotificationService::AllSources());
541
542  pref_change_registrar_.Init(profile->GetPrefs());
543  pref_change_registrar_.Add(prefs::kDevToolsPortForwardingEnabled,
544      base::Bind(&InspectUI::UpdatePortForwardingEnabled,
545                 base::Unretained(this)));
546  pref_change_registrar_.Add(prefs::kDevToolsPortForwardingConfig,
547      base::Bind(&InspectUI::UpdatePortForwardingConfig,
548                 base::Unretained(this)));
549}
550
551void InspectUI::StopListeningNotifications()
552{
553  if (!observer_.get())
554    return;
555  Profile* profile = Profile::FromWebUI(web_ui());
556  DevToolsAdbBridge* adb_bridge =
557      DevToolsAdbBridge::Factory::GetForProfile(profile);
558  if (adb_bridge)
559    adb_bridge->RemoveListener(this);
560  observer_->InspectUIDestroyed();
561  observer_ = NULL;
562  notification_registrar_.RemoveAll();
563  pref_change_registrar_.RemoveAll();
564}
565
566content::WebUIDataSource* InspectUI::CreateInspectUIHTMLSource() {
567  content::WebUIDataSource* source =
568      content::WebUIDataSource::Create(chrome::kChromeUIInspectHost);
569  source->AddResourcePath("inspect.css", IDR_INSPECT_CSS);
570  source->AddResourcePath("inspect.js", IDR_INSPECT_JS);
571  source->SetDefaultResource(IDR_INSPECT_HTML);
572  return source;
573}
574
575void InspectUI::RemoteDevicesChanged(
576    DevToolsAdbBridge::RemoteDevices* devices) {
577  remote_browsers_.clear();
578  remote_pages_.clear();
579  ListValue device_list;
580  for (DevToolsAdbBridge::RemoteDevices::iterator dit = devices->begin();
581       dit != devices->end(); ++dit) {
582    DevToolsAdbBridge::RemoteDevice* device = dit->get();
583    DictionaryValue* device_data = new DictionaryValue();
584    device_data->SetString(kAdbModelField, device->model());
585    device_data->SetString(kAdbSerialField, device->serial());
586    std::string device_id = base::StringPrintf(
587        "device:%s",
588        device->serial().c_str());
589    device_data->SetString(kAdbGlobalIdField, device_id);
590    ListValue* browser_list = new ListValue();
591    device_data->Set(kAdbBrowsersField, browser_list);
592
593    DevToolsAdbBridge::RemoteBrowsers& browsers = device->browsers();
594    for (DevToolsAdbBridge::RemoteBrowsers::iterator bit =
595        browsers.begin(); bit != browsers.end(); ++bit) {
596      DevToolsAdbBridge::RemoteBrowser* browser = bit->get();
597      DictionaryValue* browser_data = new DictionaryValue();
598      browser_data->SetString(kAdbBrowserNameField, browser->name());
599      std::string browser_id = base::StringPrintf(
600          "browser:%s:%s:%s",
601          device->serial().c_str(),
602          browser->socket().c_str(),
603          browser->name().c_str());
604      browser_data->SetString(kAdbGlobalIdField, browser_id);
605      remote_browsers_[browser_id] = browser;
606      ListValue* page_list = new ListValue();
607      browser_data->Set(kAdbPagesField, page_list);
608
609      DevToolsAdbBridge::RemotePages& pages = browser->pages();
610      for (DevToolsAdbBridge::RemotePages::iterator it =
611          pages.begin(); it != pages.end(); ++it) {
612        DevToolsAdbBridge::RemotePage* page =  it->get();
613        DictionaryValue* page_data = BuildTargetDescriptor(
614            kAdbTargetType, page->attached(),
615            GURL(page->url()), page->title(), GURL(page->favicon_url()),
616            0, 0);
617        std::string page_id = base::StringPrintf("page:%s:%s:%s",
618            device->serial().c_str(),
619            browser->socket().c_str(),
620            page->id().c_str());
621        page_data->SetString(kAdbGlobalIdField, page_id);
622        remote_pages_[page_id] = page;
623        page_list->Append(page_data);
624      }
625      browser_list->Append(browser_data);
626    }
627    device_list.Append(device_data);
628  }
629  web_ui()->CallJavascriptFunction("populateDeviceLists", device_list);
630}
631
632void InspectUI::UpdatePortForwardingEnabled() {
633  Profile* profile = Profile::FromWebUI(web_ui());
634  const base::Value* value = profile->GetPrefs()->FindPreference(
635      prefs::kDevToolsPortForwardingEnabled)->GetValue();
636  web_ui()->CallJavascriptFunction("updatePortForwardingEnabled", *value);
637
638}
639
640void InspectUI::UpdatePortForwardingConfig() {
641  Profile* profile = Profile::FromWebUI(web_ui());
642  const base::Value* value = profile->GetPrefs()->FindPreference(
643      prefs::kDevToolsPortForwardingConfig)->GetValue();
644  web_ui()->CallJavascriptFunction("updatePortForwardingConfig", *value);
645}
646