root/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. TabContentsNetworkState
  2. DetermineTabStripLayout
  3. FindURLMimeType
  4. last_command_
  5. TabContextMenuContents
  6. Cancel
  7. RunMenuAt
  8. IsCommandIdChecked
  9. IsCommandIdEnabled
  10. GetAcceleratorForCommandId
  11. CommandIdHighlighted
  12. ExecuteCommand
  13. MenuClosed
  14. weak_ptr_factory_
  15. InitFromModel
  16. IsCommandEnabledForTab
  17. ExecuteCommandForTab
  18. IsTabPinned
  19. GetSelectionModel
  20. GetCount
  21. IsValidIndex
  22. IsActiveTab
  23. GetActiveIndex
  24. IsTabSelected
  25. IsTabPinned
  26. IsNewTabPage
  27. SelectTab
  28. ExtendSelectionTo
  29. ToggleSelected
  30. AddSelectionFromAnchorTo
  31. CloseTab
  32. ShowContextMenuForTab
  33. UpdateLoadingAnimations
  34. HasAvailableDragActions
  35. OnDropIndexUpdate
  36. PerformDrop
  37. IsCompatibleWith
  38. CreateNewTab
  39. CreateNewTabWithLocation
  40. IsIncognito
  41. LayoutTypeMaybeChanged
  42. OnStartedDraggingTabs
  43. OnStoppedDraggingTabs
  44. CheckFileSupported
  45. TabInsertedAt
  46. TabDetachedAt
  47. TabSelectionChanged
  48. TabMoved
  49. TabChangedAt
  50. TabReplacedAt
  51. TabPinnedStateChanged
  52. TabMiniStateChanged
  53. TabBlockedStateChanged
  54. SetTabRendererDataFromModel
  55. SetTabDataAt
  56. StartHighlightTabsForCommand
  57. StopHighlightTabsForCommand
  58. AddTab
  59. UpdateLayoutType
  60. OnFindURLMimeTypeCompleted

// Copyright (c) 2012 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/ui/views/tabs/browser_tab_strip_controller.h"

#include "base/auto_reset.h"
#include "base/command_line.h"
#include "base/prefs/pref_service.h"
#include "base/task_runner_util.h"
#include "base/threading/sequenced_worker_pool.h"
#include "chrome/browser/autocomplete/autocomplete_classifier.h"
#include "chrome/browser/autocomplete/autocomplete_classifier_factory.h"
#include "chrome/browser/autocomplete/autocomplete_input.h"
#include "chrome/browser/autocomplete/autocomplete_match.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/tab_helper.h"
#include "chrome/browser/favicon/favicon_tab_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search/search.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/tabs/tab_menu_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
#include "chrome/browser/ui/tabs/tab_utils.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/tabs/tab.h"
#include "chrome/browser/ui/views/tabs/tab_renderer_data.h"
#include "chrome/browser/ui/views/tabs/tab_strip.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/plugin_service.h"
#include "content/public/browser/user_metrics.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/webplugininfo.h"
#include "ipc/ipc_message.h"
#include "net/base/net_util.h"
#include "ui/base/layout.h"
#include "ui/base/models/list_selection_model.h"
#include "ui/gfx/image/image.h"
#include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/widget/widget.h"

using base::UserMetricsAction;
using content::WebContents;

namespace {

TabRendererData::NetworkState TabContentsNetworkState(
    WebContents* contents) {
  if (!contents || !contents->IsLoading())
    return TabRendererData::NETWORK_STATE_NONE;
  if (contents->IsWaitingForResponse())
    return TabRendererData::NETWORK_STATE_WAITING;
  return TabRendererData::NETWORK_STATE_LOADING;
}

TabStripLayoutType DetermineTabStripLayout(
    PrefService* prefs,
    chrome::HostDesktopType host_desktop_type,
    bool* adjust_layout) {
  *adjust_layout = false;
  if (CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kEnableStackedTabStrip)) {
    return TAB_STRIP_LAYOUT_STACKED;
  }
  // For chromeos always allow entering stacked mode.
#if defined(USE_AURA)
  if (host_desktop_type != chrome::HOST_DESKTOP_TYPE_ASH)
    return TAB_STRIP_LAYOUT_SHRINK;
#else
  if (ui::GetDisplayLayout() != ui::LAYOUT_TOUCH)
    return TAB_STRIP_LAYOUT_SHRINK;
#endif
  *adjust_layout = true;
  switch (prefs->GetInteger(prefs::kTabStripLayoutType)) {
    case TAB_STRIP_LAYOUT_STACKED:
      return TAB_STRIP_LAYOUT_STACKED;
    default:
      return TAB_STRIP_LAYOUT_SHRINK;
  }
}

// Get the MIME type of the file pointed to by the url, based on the file's
// extension. Must be called on a thread that allows IO.
std::string FindURLMimeType(const GURL& url) {
  DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
  base::FilePath full_path;
  net::FileURLToFilePath(url, &full_path);

  // Get the MIME type based on the filename.
  std::string mime_type;
  net::GetMimeTypeFromFile(full_path, &mime_type);

  return mime_type;
}

}  // namespace

class BrowserTabStripController::TabContextMenuContents
    : public ui::SimpleMenuModel::Delegate {
 public:
  TabContextMenuContents(Tab* tab,
                         BrowserTabStripController* controller)
      : tab_(tab),
        controller_(controller),
        last_command_(TabStripModel::CommandFirst) {
    model_.reset(new TabMenuModel(
        this, controller->model_,
        controller->tabstrip_->GetModelIndexOfTab(tab)));
    menu_runner_.reset(new views::MenuRunner(model_.get()));
  }

  virtual ~TabContextMenuContents() {
    if (controller_)
      controller_->tabstrip_->StopAllHighlighting();
  }

  void Cancel() {
    controller_ = NULL;
  }

  void RunMenuAt(const gfx::Point& point, ui::MenuSourceType source_type) {
    if (menu_runner_->RunMenuAt(
            tab_->GetWidget(), NULL, gfx::Rect(point, gfx::Size()),
            views::MenuItemView::TOPLEFT, source_type,
            views::MenuRunner::HAS_MNEMONICS |
            views::MenuRunner::CONTEXT_MENU) ==
        views::MenuRunner::MENU_DELETED)
      return;
  }

  // Overridden from ui::SimpleMenuModel::Delegate:
  virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
    return false;
  }
  virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
    return controller_->IsCommandEnabledForTab(
        static_cast<TabStripModel::ContextMenuCommand>(command_id),
        tab_);
  }
  virtual bool GetAcceleratorForCommandId(
      int command_id,
      ui::Accelerator* accelerator) OVERRIDE {
    int browser_cmd;
    return TabStripModel::ContextMenuCommandToBrowserCommand(command_id,
                                                             &browser_cmd) ?
        controller_->tabstrip_->GetWidget()->GetAccelerator(browser_cmd,
                                                            accelerator) :
        false;
  }
  virtual void CommandIdHighlighted(int command_id) OVERRIDE {
    controller_->StopHighlightTabsForCommand(last_command_, tab_);
    last_command_ = static_cast<TabStripModel::ContextMenuCommand>(command_id);
    controller_->StartHighlightTabsForCommand(last_command_, tab_);
  }
  virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE {
    // Executing the command destroys |this|, and can also end up destroying
    // |controller_|. So stop the highlights before executing the command.
    controller_->tabstrip_->StopAllHighlighting();
    controller_->ExecuteCommandForTab(
        static_cast<TabStripModel::ContextMenuCommand>(command_id),
        tab_);
  }

  virtual void MenuClosed(ui::SimpleMenuModel* /*source*/) OVERRIDE {
    if (controller_)
      controller_->tabstrip_->StopAllHighlighting();
  }

 private:
  scoped_ptr<TabMenuModel> model_;
  scoped_ptr<views::MenuRunner> menu_runner_;

  // The tab we're showing a menu for.
  Tab* tab_;

  // A pointer back to our hosting controller, for command state information.
  BrowserTabStripController* controller_;

  // The last command that was selected, so that we can start/stop highlighting
  // appropriately as the user moves through the menu.
  TabStripModel::ContextMenuCommand last_command_;

  DISALLOW_COPY_AND_ASSIGN(TabContextMenuContents);
};

////////////////////////////////////////////////////////////////////////////////
// BrowserTabStripController, public:

BrowserTabStripController::BrowserTabStripController(Browser* browser,
                                                     TabStripModel* model)
    : model_(model),
      tabstrip_(NULL),
      browser_(browser),
      hover_tab_selector_(model),
      weak_ptr_factory_(this) {
  model_->AddObserver(this);

  local_pref_registrar_.Init(g_browser_process->local_state());
  local_pref_registrar_.Add(
      prefs::kTabStripLayoutType,
      base::Bind(&BrowserTabStripController::UpdateLayoutType,
                 base::Unretained(this)));
}

BrowserTabStripController::~BrowserTabStripController() {
  // When we get here the TabStrip is being deleted. We need to explicitly
  // cancel the menu, otherwise it may try to invoke something on the tabstrip
  // from its destructor.
  if (context_menu_contents_.get())
    context_menu_contents_->Cancel();

  model_->RemoveObserver(this);
}

void BrowserTabStripController::InitFromModel(TabStrip* tabstrip) {
  tabstrip_ = tabstrip;

  UpdateLayoutType();

  // Walk the model, calling our insertion observer method for each item within
  // it.
  for (int i = 0; i < model_->count(); ++i)
    AddTab(model_->GetWebContentsAt(i), i, model_->active_index() == i);
}

bool BrowserTabStripController::IsCommandEnabledForTab(
    TabStripModel::ContextMenuCommand command_id,
    Tab* tab) const {
  int model_index = tabstrip_->GetModelIndexOfTab(tab);
  return model_->ContainsIndex(model_index) ?
      model_->IsContextMenuCommandEnabled(model_index, command_id) : false;
}

void BrowserTabStripController::ExecuteCommandForTab(
    TabStripModel::ContextMenuCommand command_id,
    Tab* tab) {
  int model_index = tabstrip_->GetModelIndexOfTab(tab);
  if (model_->ContainsIndex(model_index))
    model_->ExecuteContextMenuCommand(model_index, command_id);
}

bool BrowserTabStripController::IsTabPinned(Tab* tab) const {
  return IsTabPinned(tabstrip_->GetModelIndexOfTab(tab));
}

const ui::ListSelectionModel& BrowserTabStripController::GetSelectionModel() {
  return model_->selection_model();
}

int BrowserTabStripController::GetCount() const {
  return model_->count();
}

bool BrowserTabStripController::IsValidIndex(int index) const {
  return model_->ContainsIndex(index);
}

bool BrowserTabStripController::IsActiveTab(int model_index) const {
  return model_->active_index() == model_index;
}

int BrowserTabStripController::GetActiveIndex() const {
  return model_->active_index();
}

bool BrowserTabStripController::IsTabSelected(int model_index) const {
  return model_->IsTabSelected(model_index);
}

bool BrowserTabStripController::IsTabPinned(int model_index) const {
  return model_->ContainsIndex(model_index) && model_->IsTabPinned(model_index);
}

bool BrowserTabStripController::IsNewTabPage(int model_index) const {
  if (!model_->ContainsIndex(model_index))
    return false;

  const WebContents* contents = model_->GetWebContentsAt(model_index);
  return contents && (contents->GetURL() == GURL(chrome::kChromeUINewTabURL) ||
      chrome::IsInstantNTP(contents));
}

void BrowserTabStripController::SelectTab(int model_index) {
  model_->ActivateTabAt(model_index, true);
}

void BrowserTabStripController::ExtendSelectionTo(int model_index) {
  model_->ExtendSelectionTo(model_index);
}

void BrowserTabStripController::ToggleSelected(int model_index) {
  model_->ToggleSelectionAt(model_index);
}

void BrowserTabStripController::AddSelectionFromAnchorTo(int model_index) {
  model_->AddSelectionFromAnchorTo(model_index);
}

void BrowserTabStripController::CloseTab(int model_index,
                                         CloseTabSource source) {
  // Cancel any pending tab transition.
  hover_tab_selector_.CancelTabTransition();

  tabstrip_->PrepareForCloseAt(model_index, source);
  model_->CloseWebContentsAt(model_index,
                             TabStripModel::CLOSE_USER_GESTURE |
                             TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
}

void BrowserTabStripController::ShowContextMenuForTab(
    Tab* tab,
    const gfx::Point& p,
    ui::MenuSourceType source_type) {
  context_menu_contents_.reset(new TabContextMenuContents(tab, this));
  context_menu_contents_->RunMenuAt(p, source_type);
}

void BrowserTabStripController::UpdateLoadingAnimations() {
  // Don't use the model count here as it's possible for this to be invoked
  // before we've applied an update from the model (Browser::TabInsertedAt may
  // be processed before us and invokes this).
  for (int i = 0, tab_count = tabstrip_->tab_count(); i < tab_count; ++i) {
    if (model_->ContainsIndex(i)) {
      Tab* tab = tabstrip_->tab_at(i);
      WebContents* contents = model_->GetWebContentsAt(i);
      tab->UpdateLoadingAnimation(TabContentsNetworkState(contents));
    }
  }
}

int BrowserTabStripController::HasAvailableDragActions() const {
  return model_->delegate()->GetDragActions();
}

void BrowserTabStripController::OnDropIndexUpdate(int index,
                                                  bool drop_before) {
  // Perform a delayed tab transition if hovering directly over a tab.
  // Otherwise, cancel the pending one.
  if (index != -1 && !drop_before) {
    hover_tab_selector_.StartTabTransition(index);
  } else {
    hover_tab_selector_.CancelTabTransition();
  }
}

void BrowserTabStripController::PerformDrop(bool drop_before,
                                            int index,
                                            const GURL& url) {
  chrome::NavigateParams params(browser_, url, content::PAGE_TRANSITION_LINK);
  params.tabstrip_index = index;

  if (drop_before) {
    content::RecordAction(UserMetricsAction("Tab_DropURLBetweenTabs"));
    params.disposition = NEW_FOREGROUND_TAB;
  } else {
    content::RecordAction(UserMetricsAction("Tab_DropURLOnTab"));
    params.disposition = CURRENT_TAB;
    params.source_contents = model_->GetWebContentsAt(index);
  }
  params.window_action = chrome::NavigateParams::SHOW_WINDOW;
  chrome::Navigate(&params);
}

bool BrowserTabStripController::IsCompatibleWith(TabStrip* other) const {
  Profile* other_profile =
      static_cast<BrowserTabStripController*>(other->controller())->profile();
  return other_profile == profile();
}

void BrowserTabStripController::CreateNewTab() {
  model_->delegate()->AddTabAt(GURL(), -1, true);
}

void BrowserTabStripController::CreateNewTabWithLocation(
    const base::string16& location) {
  // Use autocomplete to clean up the text, going so far as to turn it into
  // a search query if necessary.
  AutocompleteMatch match;
  AutocompleteClassifierFactory::GetForProfile(profile())->Classify(
      location, false, false, AutocompleteInput::BLANK, &match, NULL);
  if (match.destination_url.is_valid())
    model_->delegate()->AddTabAt(match.destination_url, -1, true);
}

bool BrowserTabStripController::IsIncognito() {
  return browser_->profile()->IsOffTheRecord();
}

void BrowserTabStripController::LayoutTypeMaybeChanged() {
  bool adjust_layout = false;
  TabStripLayoutType layout_type =
      DetermineTabStripLayout(g_browser_process->local_state(),
                              browser_->host_desktop_type(), &adjust_layout);
  if (!adjust_layout || layout_type == tabstrip_->layout_type())
    return;

  g_browser_process->local_state()->SetInteger(
      prefs::kTabStripLayoutType,
      static_cast<int>(tabstrip_->layout_type()));
}

void BrowserTabStripController::OnStartedDraggingTabs() {
  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser_);
  if (browser_view && !immersive_reveal_lock_.get()) {
    // The top-of-window views should be revealed while the user is dragging
    // tabs in immersive fullscreen. The top-of-window views may not be already
    // revealed if the user is attempting to attach a tab to a tabstrip
    // belonging to an immersive fullscreen window.
    immersive_reveal_lock_.reset(
        browser_view->immersive_mode_controller()->GetRevealedLock(
            ImmersiveModeController::ANIMATE_REVEAL_NO));
  }
}

void BrowserTabStripController::OnStoppedDraggingTabs() {
  immersive_reveal_lock_.reset();
}

void BrowserTabStripController::CheckFileSupported(const GURL& url) {
  base::PostTaskAndReplyWithResult(
      content::BrowserThread::GetBlockingPool(),
      FROM_HERE,
      base::Bind(&FindURLMimeType, url),
      base::Bind(&BrowserTabStripController::OnFindURLMimeTypeCompleted,
                 weak_ptr_factory_.GetWeakPtr(),
                 url));
}

////////////////////////////////////////////////////////////////////////////////
// BrowserTabStripController, TabStripModelObserver implementation:

void BrowserTabStripController::TabInsertedAt(WebContents* contents,
                                              int model_index,
                                              bool is_active) {
  DCHECK(contents);
  DCHECK(model_->ContainsIndex(model_index));
  AddTab(contents, model_index, is_active);
}

void BrowserTabStripController::TabDetachedAt(WebContents* contents,
                                              int model_index) {
  // Cancel any pending tab transition.
  hover_tab_selector_.CancelTabTransition();

  tabstrip_->RemoveTabAt(model_index);
}

void BrowserTabStripController::TabSelectionChanged(
    TabStripModel* tab_strip_model,
    const ui::ListSelectionModel& old_model) {
  tabstrip_->SetSelection(old_model, model_->selection_model());
}

void BrowserTabStripController::TabMoved(WebContents* contents,
                                         int from_model_index,
                                         int to_model_index) {
  // Cancel any pending tab transition.
  hover_tab_selector_.CancelTabTransition();

  // Pass in the TabRendererData as the pinned state may have changed.
  TabRendererData data;
  SetTabRendererDataFromModel(contents, to_model_index, &data, EXISTING_TAB);
  tabstrip_->MoveTab(from_model_index, to_model_index, data);
}

void BrowserTabStripController::TabChangedAt(WebContents* contents,
                                             int model_index,
                                             TabChangeType change_type) {
  if (change_type == TITLE_NOT_LOADING) {
    tabstrip_->TabTitleChangedNotLoading(model_index);
    // We'll receive another notification of the change asynchronously.
    return;
  }

  SetTabDataAt(contents, model_index);
}

void BrowserTabStripController::TabReplacedAt(TabStripModel* tab_strip_model,
                                              WebContents* old_contents,
                                              WebContents* new_contents,
                                              int model_index) {
  SetTabDataAt(new_contents, model_index);
}

void BrowserTabStripController::TabPinnedStateChanged(WebContents* contents,
                                                      int model_index) {
  // Currently none of the renderers render pinned state differently.
}

void BrowserTabStripController::TabMiniStateChanged(WebContents* contents,
                                                    int model_index) {
  SetTabDataAt(contents, model_index);
}

void BrowserTabStripController::TabBlockedStateChanged(WebContents* contents,
                                                       int model_index) {
  SetTabDataAt(contents, model_index);
}

void BrowserTabStripController::SetTabRendererDataFromModel(
    WebContents* contents,
    int model_index,
    TabRendererData* data,
    TabStatus tab_status) {
  FaviconTabHelper* favicon_tab_helper =
      FaviconTabHelper::FromWebContents(contents);

  data->favicon = favicon_tab_helper->GetFavicon().AsImageSkia();
  data->network_state = TabContentsNetworkState(contents);
  data->title = contents->GetTitle();
  data->url = contents->GetURL();
  data->loading = contents->IsLoading();
  data->crashed_status = contents->GetCrashedStatus();
  data->incognito = contents->GetBrowserContext()->IsOffTheRecord();
  data->mini = model_->IsMiniTab(model_index);
  data->show_icon = data->mini || favicon_tab_helper->ShouldDisplayFavicon();
  data->blocked = model_->IsTabBlocked(model_index);
  data->app = extensions::TabHelper::FromWebContents(contents)->is_app();
  data->media_state = chrome::GetTabMediaStateForContents(contents);
}

void BrowserTabStripController::SetTabDataAt(content::WebContents* web_contents,
                                             int model_index) {
  TabRendererData data;
  SetTabRendererDataFromModel(web_contents, model_index, &data, EXISTING_TAB);
  tabstrip_->SetTabData(model_index, data);
}

void BrowserTabStripController::StartHighlightTabsForCommand(
    TabStripModel::ContextMenuCommand command_id,
    Tab* tab) {
  if (command_id == TabStripModel::CommandCloseOtherTabs ||
      command_id == TabStripModel::CommandCloseTabsToRight) {
    int model_index = tabstrip_->GetModelIndexOfTab(tab);
    if (IsValidIndex(model_index)) {
      std::vector<int> indices =
          model_->GetIndicesClosedByCommand(model_index, command_id);
      for (std::vector<int>::const_iterator i(indices.begin());
           i != indices.end(); ++i) {
        tabstrip_->StartHighlight(*i);
      }
    }
  }
}

void BrowserTabStripController::StopHighlightTabsForCommand(
    TabStripModel::ContextMenuCommand command_id,
    Tab* tab) {
  if (command_id == TabStripModel::CommandCloseTabsToRight ||
      command_id == TabStripModel::CommandCloseOtherTabs) {
    // Just tell all Tabs to stop pulsing - it's safe.
    tabstrip_->StopAllHighlighting();
  }
}

void BrowserTabStripController::AddTab(WebContents* contents,
                                       int index,
                                       bool is_active) {
  // Cancel any pending tab transition.
  hover_tab_selector_.CancelTabTransition();

  TabRendererData data;
  SetTabRendererDataFromModel(contents, index, &data, NEW_TAB);
  tabstrip_->AddTabAt(index, data, is_active);
}

void BrowserTabStripController::UpdateLayoutType() {
  bool adjust_layout = false;
  TabStripLayoutType layout_type =
      DetermineTabStripLayout(g_browser_process->local_state(),
                              browser_->host_desktop_type(), &adjust_layout);
  tabstrip_->SetLayoutType(layout_type, adjust_layout);
}

void BrowserTabStripController::OnFindURLMimeTypeCompleted(
    const GURL& url,
    const std::string& mime_type) {
  // Check whether the mime type, if given, is known to be supported or whether
  // there is a plugin that supports the mime type (e.g. PDF).
  // TODO(bauerb): This possibly uses stale information, but it's guaranteed not
  // to do disk access.
  content::WebPluginInfo plugin;
  tabstrip_->FileSupported(
      url,
      mime_type.empty() ||
      net::IsSupportedMimeType(mime_type) ||
      content::PluginService::GetInstance()->GetPluginInfo(
          -1,                // process ID
          MSG_ROUTING_NONE,  // routing ID
          model_->profile()->GetResourceContext(),
          url, GURL(), mime_type, false,
          NULL, &plugin, NULL));
}

/* [<][>][^][v][top][bottom][index][help] */