text: // Copyright (c) 2011 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/sidebar/sidebar_manager.h"
  
  #include <vector>
  
  #include "base/command_line.h"
  #include "chrome/browser/browser_process.h"
  #include "chrome/browser/extensions/extension_sidebar_api.h"
  #include "chrome/browser/profiles/profile.h"
  #include "chrome/browser/sidebar/sidebar_container.h"
  #include "chrome/common/chrome_switches.h"
  #include "content/browser/tab_contents/tab_contents.h"
  #include "content/common/notification_service.h"
  #include "googleurl/src/gurl.h"
  
  struct SidebarManager::SidebarStateForTab {
    // Sidebars linked to this tab.
    ContentIdToSidebarHostMap content_id_to_sidebar_host;
    // Content id of the currently active (expanded and visible) sidebar.
    std::string active_content_id;
  };
  
  // static
  SidebarManager* SidebarManager::GetInstance() {
    return g_browser_process->sidebar_manager();
  }
  
  // static
  bool SidebarManager::IsSidebarAllowed() {
    return CommandLine::ForCurrentProcess()->HasSwitch(
        switches::kEnableExperimentalExtensionApis);
  }
  
  SidebarManager::SidebarManager() {
  }
  
  SidebarContainer* SidebarManager::GetActiveSidebarContainerFor(
      TabContents* tab) {
    TabToSidebarHostMap::iterator it = tab_to_sidebar_host_.find(tab);
    if (it == tab_to_sidebar_host_.end())
      return NULL;
    if (it->second.active_content_id.empty())
      return NULL;
    ContentIdToSidebarHostMap::iterator host_it =
        it->second.content_id_to_sidebar_host.find(it->second.active_content_id);
    DCHECK(host_it != it->second.content_id_to_sidebar_host.end());
    return host_it->second;
  }
  
  SidebarContainer* SidebarManager::GetSidebarContainerFor(
      TabContents* tab, const std::string& content_id) {
    DCHECK(!content_id.empty());
    TabToSidebarHostMap::iterator it = tab_to_sidebar_host_.find(tab);
    if (it == tab_to_sidebar_host_.end())
      return NULL;
    ContentIdToSidebarHostMap::iterator host_it =
        it->second.content_id_to_sidebar_host.find(content_id);
    if (host_it == it->second.content_id_to_sidebar_host.end())
      return NULL;
    return host_it->second;
  }
  
  TabContents* SidebarManager::GetSidebarTabContents(
      TabContents* tab, const std::string& content_id) {
    DCHECK(!content_id.empty());
    SidebarContainer* sidebar_host = GetSidebarContainerFor(tab, content_id);
    if (!sidebar_host)
      return NULL;
    return sidebar_host->sidebar_contents();
  }
  
  void SidebarManager::NotifyStateChanges(
      TabContents* was_active_sidebar_contents,
      TabContents* active_sidebar_contents) {
    if (was_active_sidebar_contents == active_sidebar_contents)
      return;
  
    SidebarContainer* was_active_host =
        was_active_sidebar_contents == NULL ? NULL :
            FindSidebarContainerFor(was_active_sidebar_contents);
    SidebarContainer* active_host =
        active_sidebar_contents == NULL ? NULL :
            FindSidebarContainerFor(active_sidebar_contents);
  
    if (was_active_host != NULL) {
      ExtensionSidebarEventRouter::OnStateChanged(
          was_active_sidebar_contents->profile(),
          was_active_host->tab_contents(), was_active_host->content_id(),
          extension_sidebar_constants::kShownState);
    }
  
    if (active_host != NULL) {
      ExtensionSidebarEventRouter::OnStateChanged(
          active_sidebar_contents->profile(),
          active_host->tab_contents(), active_host->content_id(),
          extension_sidebar_constants::kActiveState);
    }
  }
  
  void SidebarManager::ShowSidebar(TabContents* tab,
                                   const std::string& content_id) {
    DCHECK(!content_id.empty());
    SidebarContainer* host = GetSidebarContainerFor(tab, content_id);
    if (!host) {
      host = new SidebarContainer(tab, content_id, this);
      RegisterSidebarContainerFor(tab, host);
      // It might trigger UpdateSidebar notification, so load them after
      // the registration.
      host->LoadDefaults();
    }
  
    host->Show();
  
    ExtensionSidebarEventRouter::OnStateChanged(
        tab->profile(), tab, content_id,
        extension_sidebar_constants::kShownState);
  }
  
  void SidebarManager::ExpandSidebar(TabContents* tab,
                                     const std::string& content_id) {
    DCHECK(!content_id.empty());
    TabToSidebarHostMap::iterator it = tab_to_sidebar_host_.find(tab);
    if (it == tab_to_sidebar_host_.end())
      return;
    // If it's already active, bail out.
    if (it->second.active_content_id == content_id)
      return;
  
    SidebarContainer* host = GetSidebarContainerFor(tab, content_id);
    DCHECK(host);
    if (!host)
      return;
    it->second.active_content_id = content_id;
  
    host->Expand();
  }
  
  void SidebarManager::CollapseSidebar(TabContents* tab,
                                       const std::string& content_id) {
    DCHECK(!content_id.empty());
    TabToSidebarHostMap::iterator it = tab_to_sidebar_host_.find(tab);
    if (it == tab_to_sidebar_host_.end())
      return;
    // If it's not the one active now, bail out.
    if (it->second.active_content_id != content_id)
      return;
  
    SidebarContainer* host = GetSidebarContainerFor(tab, content_id);
    DCHECK(host);
    if (!host)
      return;
    it->second.active_content_id.clear();
  
    host->Collapse();
  }
  
  void SidebarManager::HideSidebar(TabContents* tab,
                                   const std::string& content_id) {
    DCHECK(!content_id.empty());
    TabToSidebarHostMap::iterator it = tab_to_sidebar_host_.find(tab);
    if (it == tab_to_sidebar_host_.end())
      return;
    if (it->second.active_content_id == content_id)
      it->second.active_content_id.clear();
  
    SidebarContainer* host = GetSidebarContainerFor(tab, content_id);
    DCHECK(host);
  
    UnregisterSidebarContainerFor(tab, content_id);
  
    ExtensionSidebarEventRouter::OnStateChanged(
        tab->profile(), tab, content_id,
        extension_sidebar_constants::kHiddenState);
  }
  
  void SidebarManager::NavigateSidebar(TabContents* tab,
                                       const std::string& content_id,
                                       const GURL& url) {
    DCHECK(!content_id.empty());
    SidebarContainer* host = GetSidebarContainerFor(tab, content_id);
    if (!host)
      return;
  
    host->Navigate(url);
  }
  
  void SidebarManager::SetSidebarBadgeText(
      TabContents* tab, const std::string& content_id,
      const string16& badge_text) {
    SidebarContainer* host = GetSidebarContainerFor(tab, content_id);
    if (!host)
      return;
    host->SetBadgeText(badge_text);
  }
  
  void SidebarManager::SetSidebarIcon(
      TabContents* tab, const std::string& content_id,
      const SkBitmap& bitmap) {
    SidebarContainer* host = GetSidebarContainerFor(tab, content_id);
    if (!host)
      return;
    host->SetIcon(bitmap);
  }
  
  void SidebarManager::SetSidebarTitle(
      TabContents* tab, const std::string& content_id,
      const string16& title) {
    SidebarContainer* host = GetSidebarContainerFor(tab, content_id);
    if (!host)
      return;
    host->SetTitle(title);
  }
  
  SidebarManager::~SidebarManager() {
    DCHECK(tab_to_sidebar_host_.empty());
    DCHECK(sidebar_host_to_tab_.empty());
  }
  
  void SidebarManager::Observe(NotificationType type,
                               const NotificationSource& source,
                               const NotificationDetails& details) {
    if (type == NotificationType::TAB_CONTENTS_DESTROYED) {
      HideAllSidebars(Source<TabContents>(source).ptr());
    } else {
      NOTREACHED() << "Got a notification we didn't register for!";
    }
  }
  
  void SidebarManager::UpdateSidebar(SidebarContainer* host) {
    NotificationService::current()->Notify(
        NotificationType::SIDEBAR_CHANGED,
        Source<SidebarManager>(this),
        Details<SidebarContainer>(host));
  }
  
  void SidebarManager::HideAllSidebars(TabContents* tab) {
    TabToSidebarHostMap::iterator tab_it = tab_to_sidebar_host_.find(tab);
    if (tab_it == tab_to_sidebar_host_.end())
      return;
    const ContentIdToSidebarHostMap& hosts =
        tab_it->second.content_id_to_sidebar_host;
  
    std::vector<std::string> content_ids;
    for (ContentIdToSidebarHostMap::const_iterator it = hosts.begin();
         it != hosts.end(); ++it) {
      content_ids.push_back(it->first);
    }
  
    for (std::vector<std::string>::iterator it = content_ids.begin();
         it != content_ids.end(); ++it) {
      HideSidebar(tab, *it);
    }
  }
  
  SidebarContainer* SidebarManager::FindSidebarContainerFor(
      TabContents* sidebar_contents) {
    for (SidebarHostToTabMap::iterator it = sidebar_host_to_tab_.begin();
         it != sidebar_host_to_tab_.end();
         ++it) {
      if (sidebar_contents == it->first->sidebar_contents())
        return it->first;
    }
    return NULL;
  }
  
  void SidebarManager::RegisterSidebarContainerFor(
      TabContents* tab, SidebarContainer* sidebar_host) {
    DCHECK(!GetSidebarContainerFor(tab, sidebar_host->content_id()));
  
    // If it's a first sidebar for this tab, register destroy notification.
    if (tab_to_sidebar_host_.find(tab) == tab_to_sidebar_host_.end()) {
      registrar_.Add(this,
                     NotificationType::TAB_CONTENTS_DESTROYED,
                     Source<TabContents>(tab));
    }
  
    BindSidebarHost(tab, sidebar_host);
  }
  
  void SidebarManager::UnregisterSidebarContainerFor(
        TabContents* tab, const std::string& content_id) {
    SidebarContainer* host = GetSidebarContainerFor(tab, content_id);
    DCHECK(host);
    if (!host)
      return;
  
    UnbindSidebarHost(tab, host);
  
    // If there's no more sidebars linked to this tab, unsubscribe.
    if (tab_to_sidebar_host_.find(tab) == tab_to_sidebar_host_.end()) {
      registrar_.Remove(this,
                        NotificationType::TAB_CONTENTS_DESTROYED,
                        Source<TabContents>(tab));
    }
  
    // Issue tab closing event post unbound.
    host->SidebarClosing();
    // Destroy sidebar container.
    delete host;
  }
  
  void SidebarManager::BindSidebarHost(TabContents* tab,
                                       SidebarContainer* sidebar_host) {
    const std::string& content_id = sidebar_host->content_id();
  
    DCHECK(GetSidebarContainerFor(tab, content_id) == NULL);
    DCHECK(sidebar_host_to_tab_.find(sidebar_host) ==
           sidebar_host_to_tab_.end());
  
    tab_to_sidebar_host_[tab].content_id_to_sidebar_host[content_id] =
        sidebar_host;
    sidebar_host_to_tab_[sidebar_host] = tab;
  }
  
  void SidebarManager::UnbindSidebarHost(TabContents* tab,
                                         SidebarContainer* sidebar_host) {
    const std::string& content_id = sidebar_host->content_id();
  
    DCHECK(GetSidebarContainerFor(tab, content_id) == sidebar_host);
    DCHECK(sidebar_host_to_tab_.find(sidebar_host)->second == tab);
    DCHECK(tab_to_sidebar_host_[tab].active_content_id != content_id);
  
    tab_to_sidebar_host_[tab].content_id_to_sidebar_host.erase(content_id);
    if (tab_to_sidebar_host_[tab].content_id_to_sidebar_host.empty())
      tab_to_sidebar_host_.erase(tab);
    sidebar_host_to_tab_.erase(sidebar_host);
  }
  