text: // Copyright 2014 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "chrome/browser/devtools/devtools_ui_bindings.h"
  
  #include "base/command_line.h"
  #include "base/json/json_reader.h"
  #include "base/json/json_writer.h"
  #include "base/strings/string_number_conversions.h"
  #include "base/strings/string_util.h"
  #include "base/strings/stringprintf.h"
  #include "base/strings/utf_string_conversions.h"
  #include "base/values.h"
  #include "chrome/browser/chrome_notification_types.h"
  #include "chrome/browser/chrome_page_zoom.h"
  #include "chrome/browser/devtools/devtools_target_impl.h"
  #include "chrome/browser/extensions/chrome_extension_web_contents_observer.h"
  #include "chrome/browser/extensions/extension_service.h"
  #include "chrome/browser/infobars/infobar_service.h"
  #include "chrome/browser/profiles/profile.h"
  #include "chrome/browser/themes/theme_properties.h"
  #include "chrome/browser/themes/theme_service.h"
  #include "chrome/browser/themes/theme_service_factory.h"
  #include "chrome/browser/ui/browser.h"
  #include "chrome/browser/ui/browser_iterator.h"
  #include "chrome/browser/ui/browser_list.h"
  #include "chrome/browser/ui/browser_window.h"
  #include "chrome/browser/ui/tabs/tab_strip_model.h"
  #include "chrome/common/chrome_switches.h"
  #include "chrome/common/extensions/manifest_url_handler.h"
  #include "chrome/common/url_constants.h"
  #include "chrome/grit/generated_resources.h"
  #include "components/infobars/core/confirm_infobar_delegate.h"
  #include "components/infobars/core/infobar.h"
  #include "content/public/browser/favicon_status.h"
  #include "content/public/browser/invalidate_type.h"
  #include "content/public/browser/navigation_controller.h"
  #include "content/public/browser/navigation_entry.h"
  #include "content/public/browser/notification_source.h"
  #include "content/public/browser/render_frame_host.h"
  #include "content/public/browser/render_view_host.h"
  #include "content/public/browser/user_metrics.h"
  #include "content/public/browser/web_contents.h"
  #include "content/public/browser/web_contents_observer.h"
  #include "content/public/common/page_transition_types.h"
  #include "content/public/common/renderer_preferences.h"
  #include "content/public/common/url_constants.h"
  #include "extensions/browser/extension_system.h"
  #include "extensions/common/extension_set.h"
  #include "extensions/common/permissions/permissions_data.h"
  #include "ui/base/l10n/l10n_util.h"
  
  using base::DictionaryValue;
  using content::BrowserThread;
  
  namespace {
  
  static const char kFrontendHostId[] = "id";
  static const char kFrontendHostMethod[] = "method";
  static const char kFrontendHostParams[] = "params";
  static const char kTitleFormat[] = "Developer Tools - %s";
  
  typedef std::vector<DevToolsUIBindings*> DevToolsUIBindingsList;
  base::LazyInstance<DevToolsUIBindingsList>::Leaky g_instances =
      LAZY_INSTANCE_INITIALIZER;
  
  std::string SkColorToRGBAString(SkColor color) {
    // We avoid StringPrintf because it will use locale specific formatters for
    // the double (e.g. ',' instead of '.' in German).
    return "rgba(" + base::IntToString(SkColorGetR(color)) + "," +
        base::IntToString(SkColorGetG(color)) + "," +
        base::IntToString(SkColorGetB(color)) + "," +
        base::DoubleToString(SkColorGetA(color) / 255.0) + ")";
  }
  
  base::DictionaryValue* CreateFileSystemValue(
      DevToolsFileHelper::FileSystem file_system) {
    base::DictionaryValue* file_system_value = new base::DictionaryValue();
    file_system_value->SetString("fileSystemName", file_system.file_system_name);
    file_system_value->SetString("rootURL", file_system.root_url);
    file_system_value->SetString("fileSystemPath", file_system.file_system_path);
    return file_system_value;
  }
  
  Browser* FindBrowser(content::WebContents* web_contents) {
    for (chrome::BrowserIterator it; !it.done(); it.Next()) {
      int tab_index = it->tab_strip_model()->GetIndexOfWebContents(
          web_contents);
      if (tab_index != TabStripModel::kNoTab)
        return *it;
    }
    return NULL;
  }
  
  // DevToolsConfirmInfoBarDelegate ---------------------------------------------
  
  typedef base::Callback<void(bool)> InfoBarCallback;
  
  class DevToolsConfirmInfoBarDelegate : public ConfirmInfoBarDelegate {
   public:
    // If |infobar_service| is NULL, runs |callback| with a single argument with
    // value "false".  Otherwise, creates a dev tools confirm infobar and delegate
    // and adds the infobar to |infobar_service|.
    static void Create(InfoBarService* infobar_service,
                       const InfoBarCallback& callback,
                       const base::string16& message);
  
   private:
    DevToolsConfirmInfoBarDelegate(
        const InfoBarCallback& callback,
        const base::string16& message);
    virtual ~DevToolsConfirmInfoBarDelegate();
  
    virtual base::string16 GetMessageText() const OVERRIDE;
    virtual base::string16 GetButtonLabel(InfoBarButton button) const OVERRIDE;
    virtual bool Accept() OVERRIDE;
    virtual bool Cancel() OVERRIDE;
  
    InfoBarCallback callback_;
    const base::string16 message_;
  
    DISALLOW_COPY_AND_ASSIGN(DevToolsConfirmInfoBarDelegate);
  };
  
  void DevToolsConfirmInfoBarDelegate::Create(
      InfoBarService* infobar_service,
      const InfoBarCallback& callback,
      const base::string16& message) {
    if (!infobar_service) {
      callback.Run(false);
      return;
    }
  
    infobar_service->AddInfoBar(ConfirmInfoBarDelegate::CreateInfoBar(
        scoped_ptr<ConfirmInfoBarDelegate>(
            new DevToolsConfirmInfoBarDelegate(callback, message))));
  }
  
  DevToolsConfirmInfoBarDelegate::DevToolsConfirmInfoBarDelegate(
      const InfoBarCallback& callback,
      const base::string16& message)
      : ConfirmInfoBarDelegate(),
        callback_(callback),
        message_(message) {
  }
  
  DevToolsConfirmInfoBarDelegate::~DevToolsConfirmInfoBarDelegate() {
    if (!callback_.is_null())
      callback_.Run(false);
  }
  
  base::string16 DevToolsConfirmInfoBarDelegate::GetMessageText() const {
    return message_;
  }
  
  base::string16 DevToolsConfirmInfoBarDelegate::GetButtonLabel(
      InfoBarButton button) const {
    return l10n_util::GetStringUTF16((button == BUTTON_OK) ?
        IDS_DEV_TOOLS_CONFIRM_ALLOW_BUTTON : IDS_DEV_TOOLS_CONFIRM_DENY_BUTTON);
  }
  
  bool DevToolsConfirmInfoBarDelegate::Accept() {
    callback_.Run(true);
    callback_.Reset();
    return true;
  }
  
  bool DevToolsConfirmInfoBarDelegate::Cancel() {
    callback_.Run(false);
    callback_.Reset();
    return true;
  }
  
  // DevToolsUIDefaultDelegate --------------------------------------------------
  
  class DefaultBindingsDelegate : public DevToolsUIBindings::Delegate {
   public:
    explicit DefaultBindingsDelegate(content::WebContents* web_contents)
        : web_contents_(web_contents) {}
  
   private:
    virtual ~DefaultBindingsDelegate() {}
  
    virtual void ActivateWindow() OVERRIDE;
    virtual void CloseWindow() OVERRIDE {}
    virtual void SetInspectedPageBounds(const gfx::Rect& rect) OVERRIDE {}
    virtual void InspectElementCompleted() OVERRIDE {}
    virtual void MoveWindow(int x, int y) OVERRIDE {}
    virtual void SetIsDocked(bool is_docked) OVERRIDE {}
    virtual void OpenInNewTab(const std::string& url) OVERRIDE;
    virtual void SetWhitelistedShortcuts(const std::string& message) OVERRIDE {}
  
    virtual void InspectedContentsClosing() OVERRIDE;
    virtual void OnLoadCompleted() OVERRIDE {}
    virtual InfoBarService* GetInfoBarService() OVERRIDE;
    virtual void RenderProcessGone() OVERRIDE {}
  
    content::WebContents* web_contents_;
    DISALLOW_COPY_AND_ASSIGN(DefaultBindingsDelegate);
  };
  
  void DefaultBindingsDelegate::ActivateWindow() {
    web_contents_->GetDelegate()->ActivateContents(web_contents_);
    web_contents_->Focus();
  }
  
  void DefaultBindingsDelegate::OpenInNewTab(const std::string& url) {
    content::OpenURLParams params(
        GURL(url), content::Referrer(), NEW_FOREGROUND_TAB,
        content::PAGE_TRANSITION_LINK, false);
    Browser* browser = FindBrowser(web_contents_);
    browser->OpenURL(params);
  }
  
  void DefaultBindingsDelegate::InspectedContentsClosing() {
    web_contents_->GetRenderViewHost()->ClosePage();
  }
  
  InfoBarService* DefaultBindingsDelegate::GetInfoBarService() {
    return InfoBarService::FromWebContents(web_contents_);
  }
  
  }  // namespace
  
  // DevToolsUIBindings::FrontendWebContentsObserver ----------------------------
  
  class DevToolsUIBindings::FrontendWebContentsObserver
      : public content::WebContentsObserver {
   public:
    explicit FrontendWebContentsObserver(DevToolsUIBindings* ui_bindings);
    virtual ~FrontendWebContentsObserver();
  
   private:
    // contents::WebContentsObserver:
    virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE;
    virtual void AboutToNavigateRenderView(
        content::RenderViewHost* render_view_host) OVERRIDE;
    virtual void DocumentOnLoadCompletedInMainFrame() OVERRIDE;
  
    DevToolsUIBindings* devtools_bindings_;
    DISALLOW_COPY_AND_ASSIGN(FrontendWebContentsObserver);
  };
  
  DevToolsUIBindings::FrontendWebContentsObserver::FrontendWebContentsObserver(
      DevToolsUIBindings* devtools_ui_bindings)
      : WebContentsObserver(devtools_ui_bindings->web_contents()),
        devtools_bindings_(devtools_ui_bindings) {
  }
  
  DevToolsUIBindings::FrontendWebContentsObserver::
      ~FrontendWebContentsObserver() {
  }
  
  void DevToolsUIBindings::FrontendWebContentsObserver::RenderProcessGone(
      base::TerminationStatus status) {
    switch (status) {
      case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
      case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
      case base::TERMINATION_STATUS_PROCESS_CRASHED:
        if (devtools_bindings_->agent_host_)
          devtools_bindings_->Detach();
        break;
      default:
        break;
    }
    devtools_bindings_->delegate_->RenderProcessGone();
  }
  
  void DevToolsUIBindings::FrontendWebContentsObserver::AboutToNavigateRenderView(
      content::RenderViewHost* render_view_host) {
    devtools_bindings_->frontend_host_.reset(
        content::DevToolsFrontendHost::Create(
            render_view_host, devtools_bindings_));
  }
  
  void DevToolsUIBindings::FrontendWebContentsObserver::
      DocumentOnLoadCompletedInMainFrame() {
    devtools_bindings_->DocumentOnLoadCompletedInMainFrame();
  }
  
  // DevToolsUIBindings ---------------------------------------------------------
  
  DevToolsUIBindings* DevToolsUIBindings::ForWebContents(
       content::WebContents* web_contents) {
   if (g_instances == NULL)
     return NULL;
   DevToolsUIBindingsList* instances = g_instances.Pointer();
   for (DevToolsUIBindingsList::iterator it(instances->begin());
        it != instances->end(); ++it) {
     if ((*it)->web_contents() == web_contents)
       return *it;
   }
   return NULL;
  }
  
  // static
  GURL DevToolsUIBindings::ApplyThemeToURL(Profile* profile,
                                           const GURL& base_url) {
    std::string frontend_url = base_url.spec();
    ThemeService* tp = ThemeServiceFactory::GetForProfile(profile);
    DCHECK(tp);
    std::string url_string(
        frontend_url +
        ((frontend_url.find("?") == std::string::npos) ? "?" : "&") +
        "dockSide=undocked" + // TODO(dgozman): remove this support in M38.
        "&toolbarColor=" +
        SkColorToRGBAString(tp->GetColor(ThemeProperties::COLOR_TOOLBAR)) +
        "&textColor=" +
        SkColorToRGBAString(tp->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT)));
    if (CommandLine::ForCurrentProcess()->HasSwitch(
        switches::kEnableDevToolsExperiments))
      url_string += "&experiments=true";
  #if defined(DEBUG_DEVTOOLS)
    url_string += "&debugFrontend=true";
  #endif  // defined(DEBUG_DEVTOOLS)
    return GURL(url_string);
  }
  
  DevToolsUIBindings::DevToolsUIBindings(content::WebContents* web_contents)
      : profile_(Profile::FromBrowserContext(web_contents->GetBrowserContext())),
        web_contents_(web_contents),
        delegate_(new DefaultBindingsDelegate(web_contents_)),
        device_count_updates_enabled_(false),
        devices_updates_enabled_(false),
        weak_factory_(this) {
    g_instances.Get().push_back(this);
    frontend_contents_observer_.reset(new FrontendWebContentsObserver(this));
    web_contents_->GetMutableRendererPrefs()->can_accept_load_drops = false;
  
    file_helper_.reset(new DevToolsFileHelper(web_contents_, profile_));
    file_system_indexer_ = new DevToolsFileSystemIndexer();
    extensions::ChromeExtensionWebContentsObserver::CreateForWebContents(
        web_contents_);
  
    // Wipe out page icon so that the default application icon is used.
    content::NavigationEntry* entry =
        web_contents_->GetController().GetActiveEntry();
    entry->GetFavicon().image = gfx::Image();
    entry->GetFavicon().valid = true;
  
    // Register on-load actions.
    registrar_.Add(
        this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
        content::Source<ThemeService>(
            ThemeServiceFactory::GetForProfile(profile_)));
  
    embedder_message_dispatcher_.reset(
        DevToolsEmbedderMessageDispatcher::createForDevToolsFrontend(this));
  
    frontend_host_.reset(
        content::DevToolsFrontendHost::Create(
            web_contents_->GetRenderViewHost(), this));
  }
  
  DevToolsUIBindings::~DevToolsUIBindings() {
    if (agent_host_)
      agent_host_->DetachClient();
  
    for (IndexingJobsMap::const_iterator jobs_it(indexing_jobs_.begin());
         jobs_it != indexing_jobs_.end(); ++jobs_it) {
      jobs_it->second->Stop();
    }
    indexing_jobs_.clear();
    SetDeviceCountUpdatesEnabled(false);
    SetDevicesUpdatesEnabled(false);
  
    // Remove self from global list.
    DevToolsUIBindingsList* instances = g_instances.Pointer();
    DevToolsUIBindingsList::iterator it(
        std::find(instances->begin(), instances->end(), this));
    DCHECK(it != instances->end());
    instances->erase(it);
  }
  
  // content::NotificationObserver overrides ------------------------------------
  void DevToolsUIBindings::Observe(int type,
                                   const content::NotificationSource& source,
                                   const content::NotificationDetails& details) {
    DCHECK_EQ(chrome::NOTIFICATION_BROWSER_THEME_CHANGED, type);
    UpdateTheme();
  }
  
  // content::DevToolsFrontendHost::Delegate implementation ---------------------
  void DevToolsUIBindings::HandleMessageFromDevToolsFrontend(
      const std::string& message) {
    std::string method;
    base::ListValue empty_params;
    base::ListValue* params = &empty_params;
  
    base::DictionaryValue* dict = NULL;
    scoped_ptr<base::Value> parsed_message(base::JSONReader::Read(message));
    if (!parsed_message ||
        !parsed_message->GetAsDictionary(&dict) ||
        !dict->GetString(kFrontendHostMethod, &method) ||
        (dict->HasKey(kFrontendHostParams) &&
            !dict->GetList(kFrontendHostParams, &params))) {
      LOG(ERROR) << "Invalid message was sent to embedder: " << message;
      return;
    }
  
    int id = 0;
    dict->GetInteger(kFrontendHostId, &id);
  
    std::string error;
    embedder_message_dispatcher_->Dispatch(method, params, &error);
    if (id) {
      base::FundamentalValue id_value(id);
      base::StringValue error_value(error);
      CallClientFunction("InspectorFrontendAPI.embedderMessageAck",
                         &id_value, &error_value, NULL);
    }
  }
  
  void DevToolsUIBindings::HandleMessageFromDevToolsFrontendToBackend(
      const std::string& message) {
    if (agent_host_)
      agent_host_->DispatchProtocolMessage(message);
  }
  
  // content::DevToolsAgentHostClient implementation --------------------------
  void DevToolsUIBindings::DispatchProtocolMessage(
      content::DevToolsAgentHost* agent_host, const std::string& message) {
    DCHECK(agent_host == agent_host_.get());
    base::StringValue message_value(message);
    CallClientFunction("InspectorFrontendAPI.dispatchMessage",
                       &message_value, NULL, NULL);
  }
  
  void DevToolsUIBindings::AgentHostClosed(
      content::DevToolsAgentHost* agent_host,
      bool replaced_with_another_client) {
    DCHECK(agent_host == agent_host_.get());
    agent_host_ = NULL;
    delegate_->InspectedContentsClosing();
  }
  
  // DevToolsEmbedderMessageDispatcher::Delegate implementation -----------------
  void DevToolsUIBindings::ActivateWindow() {
    delegate_->ActivateWindow();
  }
  
  void DevToolsUIBindings::CloseWindow() {
    delegate_->CloseWindow();
  }
  
  void DevToolsUIBindings::SetInspectedPageBounds(const gfx::Rect& rect) {
    delegate_->SetInspectedPageBounds(rect);
  }
  
  void DevToolsUIBindings::MoveWindow(int x, int y) {
    delegate_->MoveWindow(x, y);
  }
  
  void DevToolsUIBindings::SetIsDocked(bool dock_requested) {
    delegate_->SetIsDocked(dock_requested);
  }
  
  void DevToolsUIBindings::InspectElementCompleted() {
    delegate_->InspectElementCompleted();
  }
  
  void DevToolsUIBindings::InspectedURLChanged(const std::string& url) {
    content::NavigationController& controller = web_contents()->GetController();
    content::NavigationEntry* entry = controller.GetActiveEntry();
    // DevTools UI is not localized.
    entry->SetTitle(
        base::UTF8ToUTF16(base::StringPrintf(kTitleFormat, url.c_str())));
    web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TITLE);
  }
  
  void DevToolsUIBindings::OpenInNewTab(const std::string& url) {
    delegate_->OpenInNewTab(url);
  }
  
  void DevToolsUIBindings::SaveToFile(const std::string& url,
                                      const std::string& content,
                                      bool save_as) {
    file_helper_->Save(url, content, save_as,
                       base::Bind(&DevToolsUIBindings::FileSavedAs,
                                  weak_factory_.GetWeakPtr(), url),
                       base::Bind(&DevToolsUIBindings::CanceledFileSaveAs,
                                  weak_factory_.GetWeakPtr(), url));
  }
  
  void DevToolsUIBindings::AppendToFile(const std::string& url,
                                        const std::string& content) {
    file_helper_->Append(url, content,
                         base::Bind(&DevToolsUIBindings::AppendedTo,
                                    weak_factory_.GetWeakPtr(), url));
  }
  
  void DevToolsUIBindings::RequestFileSystems() {
    CHECK(web_contents_->GetURL().SchemeIs(content::kChromeDevToolsScheme));
    file_helper_->RequestFileSystems(base::Bind(
        &DevToolsUIBindings::FileSystemsLoaded, weak_factory_.GetWeakPtr()));
  }
  
  void DevToolsUIBindings::AddFileSystem() {
    CHECK(web_contents_->GetURL().SchemeIs(content::kChromeDevToolsScheme));
    file_helper_->AddFileSystem(
        base::Bind(&DevToolsUIBindings::FileSystemAdded,
                   weak_factory_.GetWeakPtr()),
        base::Bind(&DevToolsUIBindings::ShowDevToolsConfirmInfoBar,
                   weak_factory_.GetWeakPtr()));
  }
  
  void DevToolsUIBindings::RemoveFileSystem(
      const std::string& file_system_path) {
    CHECK(web_contents_->GetURL().SchemeIs(content::kChromeDevToolsScheme));
    file_helper_->RemoveFileSystem(file_system_path);
    base::StringValue file_system_path_value(file_system_path);
    CallClientFunction("InspectorFrontendAPI.fileSystemRemoved",
                       &file_system_path_value, NULL, NULL);
  }
  
  void DevToolsUIBindings::UpgradeDraggedFileSystemPermissions(
      const std::string& file_system_url) {
    CHECK(web_contents_->GetURL().SchemeIs(content::kChromeDevToolsScheme));
    file_helper_->UpgradeDraggedFileSystemPermissions(
        file_system_url,
        base::Bind(&DevToolsUIBindings::FileSystemAdded,
                   weak_factory_.GetWeakPtr()),
        base::Bind(&DevToolsUIBindings::ShowDevToolsConfirmInfoBar,
                   weak_factory_.GetWeakPtr()));
  }
  
  void DevToolsUIBindings::IndexPath(int request_id,
                                     const std::string& file_system_path) {
    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    CHECK(web_contents_->GetURL().SchemeIs(content::kChromeDevToolsScheme));
    if (!file_helper_->IsFileSystemAdded(file_system_path)) {
      IndexingDone(request_id, file_system_path);
      return;
    }
    indexing_jobs_[request_id] =
        scoped_refptr<DevToolsFileSystemIndexer::FileSystemIndexingJob>(
            file_system_indexer_->IndexPath(
                file_system_path,
                Bind(&DevToolsUIBindings::IndexingTotalWorkCalculated,
                     weak_factory_.GetWeakPtr(),
                     request_id,
                     file_system_path),
                Bind(&DevToolsUIBindings::IndexingWorked,
                     weak_factory_.GetWeakPtr(),
                     request_id,
                     file_system_path),
                Bind(&DevToolsUIBindings::IndexingDone,
                     weak_factory_.GetWeakPtr(),
                     request_id,
                     file_system_path)));
  }
  
  void DevToolsUIBindings::StopIndexing(int request_id) {
    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    IndexingJobsMap::iterator it = indexing_jobs_.find(request_id);
    if (it == indexing_jobs_.end())
      return;
    it->second->Stop();
    indexing_jobs_.erase(it);
  }
  
  void DevToolsUIBindings::SearchInPath(int request_id,
                                        const std::string& file_system_path,
                                        const std::string& query) {
    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    CHECK(web_contents_->GetURL().SchemeIs(content::kChromeDevToolsScheme));
    if (!file_helper_->IsFileSystemAdded(file_system_path)) {
      SearchCompleted(request_id, file_system_path, std::vector<std::string>());
      return;
    }
    file_system_indexer_->SearchInPath(file_system_path,
                                       query,
                                       Bind(&DevToolsUIBindings::SearchCompleted,
                                            weak_factory_.GetWeakPtr(),
                                            request_id,
                                            file_system_path));
  }
  
  void DevToolsUIBindings::SetWhitelistedShortcuts(
      const std::string& message) {
    delegate_->SetWhitelistedShortcuts(message);
  }
  
  void DevToolsUIBindings::ZoomIn() {
    chrome_page_zoom::Zoom(web_contents(), content::PAGE_ZOOM_IN);
  }
  
  void DevToolsUIBindings::ZoomOut() {
    chrome_page_zoom::Zoom(web_contents(), content::PAGE_ZOOM_OUT);
  }
  
  void DevToolsUIBindings::ResetZoom() {
    chrome_page_zoom::Zoom(web_contents(), content::PAGE_ZOOM_RESET);
  }
  
  static void InspectTarget(Profile* profile, DevToolsTargetImpl* target) {
    if (target)
      target->Inspect(profile);
  }
  
  void DevToolsUIBindings::OpenUrlOnRemoteDeviceAndInspect(
      const std::string& browser_id,
      const std::string& url) {
    if (remote_targets_handler_) {
      remote_targets_handler_->Open(browser_id, url,
          base::Bind(&InspectTarget, profile_));
    }
  }
  
  void DevToolsUIBindings::SetDeviceCountUpdatesEnabled(bool enabled) {
    if (device_count_updates_enabled_ == enabled)
      return;
    DevToolsAndroidBridge* adb_bridge =
        DevToolsAndroidBridge::Factory::GetForProfile(profile_);
    if (!adb_bridge)
      return;
  
    device_count_updates_enabled_ = enabled;
    if (enabled)
      adb_bridge->AddDeviceCountListener(this);
    else
      adb_bridge->RemoveDeviceCountListener(this);
  }
  
  void DevToolsUIBindings::SetDevicesUpdatesEnabled(bool enabled) {
    if (devices_updates_enabled_ == enabled)
      return;
    devices_updates_enabled_ = enabled;
    if (enabled) {
      remote_targets_handler_ = DevToolsTargetsUIHandler::CreateForAdb(
          base::Bind(&DevToolsUIBindings::DevicesUpdated,
                     base::Unretained(this)),
          profile_);
    } else {
      remote_targets_handler_.reset();
    }
  }
  
  void DevToolsUIBindings::SendMessageToBrowser(const std::string& message) {
    if (agent_host_)
      agent_host_->DispatchProtocolMessage(message);
  }
  
  void DevToolsUIBindings::DeviceCountChanged(int count) {
    base::FundamentalValue value(count);
    CallClientFunction("InspectorFrontendAPI.deviceCountUpdated", &value, NULL,
                       NULL);
  }
  
  void DevToolsUIBindings::DevicesUpdated(
      const std::string& source,
      const base::ListValue& targets) {
    CallClientFunction("InspectorFrontendAPI.devicesUpdated", &targets, NULL,
                       NULL);
  }
  
  void DevToolsUIBindings::FileSavedAs(const std::string& url) {
    base::StringValue url_value(url);
    CallClientFunction("InspectorFrontendAPI.savedURL", &url_value, NULL, NULL);
  }
  
  void DevToolsUIBindings::CanceledFileSaveAs(const std::string& url) {
    base::StringValue url_value(url);
    CallClientFunction("InspectorFrontendAPI.canceledSaveURL",
                       &url_value, NULL, NULL);
  }
  
  void DevToolsUIBindings::AppendedTo(const std::string& url) {
    base::StringValue url_value(url);
    CallClientFunction("InspectorFrontendAPI.appendedToURL", &url_value, NULL,
                       NULL);
  }
  
  void DevToolsUIBindings::FileSystemsLoaded(
      const std::vector<DevToolsFileHelper::FileSystem>& file_systems) {
    base::ListValue file_systems_value;
    for (size_t i = 0; i < file_systems.size(); ++i)
      file_systems_value.Append(CreateFileSystemValue(file_systems[i]));
    CallClientFunction("InspectorFrontendAPI.fileSystemsLoaded",
                       &file_systems_value, NULL, NULL);
  }
  
  void DevToolsUIBindings::FileSystemAdded(
      const DevToolsFileHelper::FileSystem& file_system) {
    scoped_ptr<base::StringValue> error_string_value(
        new base::StringValue(std::string()));
    scoped_ptr<base::DictionaryValue> file_system_value;
    if (!file_system.file_system_path.empty())
      file_system_value.reset(CreateFileSystemValue(file_system));
    CallClientFunction("InspectorFrontendAPI.fileSystemAdded",
                       error_string_value.get(), file_system_value.get(), NULL);
  }
  
  void DevToolsUIBindings::IndexingTotalWorkCalculated(
      int request_id,
      const std::string& file_system_path,
      int total_work) {
    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    base::FundamentalValue request_id_value(request_id);
    base::StringValue file_system_path_value(file_system_path);
    base::FundamentalValue total_work_value(total_work);
    CallClientFunction("InspectorFrontendAPI.indexingTotalWorkCalculated",
                       &request_id_value, &file_system_path_value,
                       &total_work_value);
  }
  
  void DevToolsUIBindings::IndexingWorked(int request_id,
                                          const std::string& file_system_path,
                                          int worked) {
    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    base::FundamentalValue request_id_value(request_id);
    base::StringValue file_system_path_value(file_system_path);
    base::FundamentalValue worked_value(worked);
    CallClientFunction("InspectorFrontendAPI.indexingWorked", &request_id_value,
                       &file_system_path_value, &worked_value);
  }
  
  void DevToolsUIBindings::IndexingDone(int request_id,
                                        const std::string& file_system_path) {
    indexing_jobs_.erase(request_id);
    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    base::FundamentalValue request_id_value(request_id);
    base::StringValue file_system_path_value(file_system_path);
    CallClientFunction("InspectorFrontendAPI.indexingDone", &request_id_value,
                       &file_system_path_value, NULL);
  }
  
  void DevToolsUIBindings::SearchCompleted(
      int request_id,
      const std::string& file_system_path,
      const std::vector<std::string>& file_paths) {
    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    base::ListValue file_paths_value;
    for (std::vector<std::string>::const_iterator it(file_paths.begin());
         it != file_paths.end(); ++it) {
      file_paths_value.AppendString(*it);
    }
    base::FundamentalValue request_id_value(request_id);
    base::StringValue file_system_path_value(file_system_path);
    CallClientFunction("InspectorFrontendAPI.searchCompleted", &request_id_value,
                       &file_system_path_value, &file_paths_value);
  }
  
  void DevToolsUIBindings::ShowDevToolsConfirmInfoBar(
      const base::string16& message,
      const InfoBarCallback& callback) {
    DevToolsConfirmInfoBarDelegate::Create(delegate_->GetInfoBarService(),
        callback, message);
  }
  
  void DevToolsUIBindings::UpdateTheme() {
    ThemeService* tp = ThemeServiceFactory::GetForProfile(profile_);
    DCHECK(tp);
  
    std::string command("InspectorFrontendAPI.setToolbarColors(\"" +
        SkColorToRGBAString(tp->GetColor(ThemeProperties::COLOR_TOOLBAR)) +
        "\", \"" +
        SkColorToRGBAString(tp->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT)) +
        "\")");
    web_contents_->GetMainFrame()->ExecuteJavaScript(base::ASCIIToUTF16(command));
  }
  
  void DevToolsUIBindings::AddDevToolsExtensionsToClient() {
    const ExtensionService* extension_service = extensions::ExtensionSystem::Get(
        profile_->GetOriginalProfile())->extension_service();
    if (!extension_service)
      return;
    const extensions::ExtensionSet* extensions = extension_service->extensions();
  
    base::ListValue results;
    for (extensions::ExtensionSet::const_iterator extension(extensions->begin());
         extension != extensions->end(); ++extension) {
      if (extensions::ManifestURL::GetDevToolsPage(extension->get()).is_empty())
        continue;
      base::DictionaryValue* extension_info = new base::DictionaryValue();
      extension_info->Set(
          "startPage",
          new base::StringValue(
              extensions::ManifestURL::GetDevToolsPage(
                  extension->get()).spec()));
      extension_info->Set("name", new base::StringValue((*extension)->name()));
      extension_info->Set("exposeExperimentalAPIs",
                          new base::FundamentalValue(
                              (*extension)->permissions_data()->HasAPIPermission(
                                  extensions::APIPermission::kExperimental)));
      results.Append(extension_info);
    }
    CallClientFunction("WebInspector.addExtensions", &results, NULL, NULL);
  }
  
  void DevToolsUIBindings::SetDelegate(Delegate* delegate) {
    delegate_.reset(delegate);
  }
  
  void DevToolsUIBindings::AttachTo(content::DevToolsAgentHost* agent_host) {
    if (agent_host_)
      Detach();
    agent_host_ = agent_host;
    agent_host_->AttachClient(this);
  }
  
  void DevToolsUIBindings::Reattach() {
    DCHECK(agent_host_);
    agent_host_->DetachClient();
    agent_host_->AttachClient(this);
  }
  
  void DevToolsUIBindings::Detach() {
    if (agent_host_)
      agent_host_->DetachClient();
    agent_host_ = NULL;
  }
  
  bool DevToolsUIBindings::IsAttachedTo(content::DevToolsAgentHost* agent_host) {
    return agent_host_ == agent_host;
  }
  
  void DevToolsUIBindings::CallClientFunction(const std::string& function_name,
                                              const base::Value* arg1,
                                              const base::Value* arg2,
                                              const base::Value* arg3) {
    std::string params;
    if (arg1) {
      std::string json;
      base::JSONWriter::Write(arg1, &json);
      params.append(json);
      if (arg2) {
        base::JSONWriter::Write(arg2, &json);
        params.append(", " + json);
        if (arg3) {
          base::JSONWriter::Write(arg3, &json);
          params.append(", " + json);
        }
      }
    }
  
    base::string16 javascript = base::UTF8ToUTF16(
        function_name + "(" + params + ");");
    web_contents_->GetMainFrame()->ExecuteJavaScript(javascript);
  }
  
  void DevToolsUIBindings::DocumentOnLoadCompletedInMainFrame() {
    // Call delegate first - it seeds importants bit of information.
    delegate_->OnLoadCompleted();
  
    UpdateTheme();
    AddDevToolsExtensionsToClient();
  }
  