Home | History | Annotate | Download | only in tabs
      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/views/tabs/browser_tab_strip_controller.h"
      6 
      7 #include "base/auto_reset.h"
      8 #include "base/command_line.h"
      9 #include "base/prefs/pref_service.h"
     10 #include "chrome/browser/browser_process.h"
     11 #include "chrome/browser/chrome_notification_types.h"
     12 #include "chrome/browser/extensions/tab_helper.h"
     13 #include "chrome/browser/favicon/favicon_tab_helper.h"
     14 #include "chrome/browser/profiles/profile.h"
     15 #include "chrome/browser/search/search.h"
     16 #include "chrome/browser/ui/browser.h"
     17 #include "chrome/browser/ui/browser_tabstrip.h"
     18 #include "chrome/browser/ui/tabs/tab_menu_model.h"
     19 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     20 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
     21 #include "chrome/browser/ui/tabs/tab_utils.h"
     22 #include "chrome/browser/ui/views/frame/browser_view.h"
     23 #include "chrome/browser/ui/views/tabs/tab.h"
     24 #include "chrome/browser/ui/views/tabs/tab_renderer_data.h"
     25 #include "chrome/browser/ui/views/tabs/tab_strip.h"
     26 #include "chrome/common/chrome_switches.h"
     27 #include "chrome/common/pref_names.h"
     28 #include "chrome/common/url_constants.h"
     29 #include "content/public/browser/notification_service.h"
     30 #include "content/public/browser/user_metrics.h"
     31 #include "content/public/browser/web_contents.h"
     32 #include "ui/base/layout.h"
     33 #include "ui/base/models/list_selection_model.h"
     34 #include "ui/gfx/image/image.h"
     35 #include "ui/views/controls/menu/menu_item_view.h"
     36 #include "ui/views/controls/menu/menu_runner.h"
     37 #include "ui/views/widget/widget.h"
     38 
     39 using content::UserMetricsAction;
     40 using content::WebContents;
     41 
     42 namespace {
     43 
     44 TabRendererData::NetworkState TabContentsNetworkState(
     45     WebContents* contents) {
     46   if (!contents || !contents->IsLoading())
     47     return TabRendererData::NETWORK_STATE_NONE;
     48   if (contents->IsWaitingForResponse())
     49     return TabRendererData::NETWORK_STATE_WAITING;
     50   return TabRendererData::NETWORK_STATE_LOADING;
     51 }
     52 
     53 TabStripLayoutType DetermineTabStripLayout(PrefService* prefs,
     54                                            bool* adjust_layout) {
     55   *adjust_layout = false;
     56   if (CommandLine::ForCurrentProcess()->HasSwitch(
     57           switches::kEnableStackedTabStrip)) {
     58     return TAB_STRIP_LAYOUT_STACKED;
     59   }
     60   // For chromeos always allow entering stacked mode.
     61 #if !defined(OS_CHROMEOS)
     62   if (ui::GetDisplayLayout() != ui::LAYOUT_TOUCH)
     63     return TAB_STRIP_LAYOUT_SHRINK;
     64 #endif
     65   *adjust_layout = true;
     66   switch (prefs->GetInteger(prefs::kTabStripLayoutType)) {
     67     case TAB_STRIP_LAYOUT_STACKED:
     68       return TAB_STRIP_LAYOUT_STACKED;
     69     default:
     70       return TAB_STRIP_LAYOUT_SHRINK;
     71   }
     72 }
     73 
     74 }  // namespace
     75 
     76 class BrowserTabStripController::TabContextMenuContents
     77     : public ui::SimpleMenuModel::Delegate {
     78  public:
     79   TabContextMenuContents(Tab* tab,
     80                          BrowserTabStripController* controller)
     81       : tab_(tab),
     82         controller_(controller),
     83         last_command_(TabStripModel::CommandFirst) {
     84     model_.reset(new TabMenuModel(
     85         this, controller->model_,
     86         controller->tabstrip_->GetModelIndexOfTab(tab)));
     87     menu_runner_.reset(new views::MenuRunner(model_.get()));
     88   }
     89 
     90   virtual ~TabContextMenuContents() {
     91     if (controller_)
     92       controller_->tabstrip_->StopAllHighlighting();
     93   }
     94 
     95   void Cancel() {
     96     controller_ = NULL;
     97   }
     98 
     99   void RunMenuAt(const gfx::Point& point, ui::MenuSourceType source_type) {
    100     if (menu_runner_->RunMenuAt(
    101             tab_->GetWidget(), NULL, gfx::Rect(point, gfx::Size()),
    102             views::MenuItemView::TOPLEFT, source_type,
    103             views::MenuRunner::HAS_MNEMONICS |
    104             views::MenuRunner::CONTEXT_MENU) ==
    105         views::MenuRunner::MENU_DELETED)
    106       return;
    107   }
    108 
    109   // Overridden from ui::SimpleMenuModel::Delegate:
    110   virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
    111     return false;
    112   }
    113   virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
    114     return controller_->IsCommandEnabledForTab(
    115         static_cast<TabStripModel::ContextMenuCommand>(command_id),
    116         tab_);
    117   }
    118   virtual bool GetAcceleratorForCommandId(
    119       int command_id,
    120       ui::Accelerator* accelerator) OVERRIDE {
    121     int browser_cmd;
    122     return TabStripModel::ContextMenuCommandToBrowserCommand(command_id,
    123                                                              &browser_cmd) ?
    124         controller_->tabstrip_->GetWidget()->GetAccelerator(browser_cmd,
    125                                                             accelerator) :
    126         false;
    127   }
    128   virtual void CommandIdHighlighted(int command_id) OVERRIDE {
    129     controller_->StopHighlightTabsForCommand(last_command_, tab_);
    130     last_command_ = static_cast<TabStripModel::ContextMenuCommand>(command_id);
    131     controller_->StartHighlightTabsForCommand(last_command_, tab_);
    132   }
    133   virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE {
    134     // Executing the command destroys |this|, and can also end up destroying
    135     // |controller_|. So stop the highlights before executing the command.
    136     controller_->tabstrip_->StopAllHighlighting();
    137     controller_->ExecuteCommandForTab(
    138         static_cast<TabStripModel::ContextMenuCommand>(command_id),
    139         tab_);
    140   }
    141 
    142   virtual void MenuClosed(ui::SimpleMenuModel* /*source*/) OVERRIDE {
    143     if (controller_)
    144       controller_->tabstrip_->StopAllHighlighting();
    145   }
    146 
    147  private:
    148   scoped_ptr<TabMenuModel> model_;
    149   scoped_ptr<views::MenuRunner> menu_runner_;
    150 
    151   // The tab we're showing a menu for.
    152   Tab* tab_;
    153 
    154   // A pointer back to our hosting controller, for command state information.
    155   BrowserTabStripController* controller_;
    156 
    157   // The last command that was selected, so that we can start/stop highlighting
    158   // appropriately as the user moves through the menu.
    159   TabStripModel::ContextMenuCommand last_command_;
    160 
    161   DISALLOW_COPY_AND_ASSIGN(TabContextMenuContents);
    162 };
    163 
    164 ////////////////////////////////////////////////////////////////////////////////
    165 // BrowserTabStripController, public:
    166 
    167 BrowserTabStripController::BrowserTabStripController(Browser* browser,
    168                                                      TabStripModel* model)
    169     : model_(model),
    170       tabstrip_(NULL),
    171       browser_(browser),
    172       hover_tab_selector_(model) {
    173   model_->AddObserver(this);
    174 
    175   local_pref_registrar_.Init(g_browser_process->local_state());
    176   local_pref_registrar_.Add(
    177       prefs::kTabStripLayoutType,
    178       base::Bind(&BrowserTabStripController::UpdateLayoutType,
    179                  base::Unretained(this)));
    180 }
    181 
    182 BrowserTabStripController::~BrowserTabStripController() {
    183   // When we get here the TabStrip is being deleted. We need to explicitly
    184   // cancel the menu, otherwise it may try to invoke something on the tabstrip
    185   // from its destructor.
    186   if (context_menu_contents_.get())
    187     context_menu_contents_->Cancel();
    188 
    189   model_->RemoveObserver(this);
    190 }
    191 
    192 void BrowserTabStripController::InitFromModel(TabStrip* tabstrip) {
    193   tabstrip_ = tabstrip;
    194 
    195   UpdateLayoutType();
    196 
    197   // Walk the model, calling our insertion observer method for each item within
    198   // it.
    199   for (int i = 0; i < model_->count(); ++i)
    200     AddTab(model_->GetWebContentsAt(i), i, model_->active_index() == i);
    201 }
    202 
    203 bool BrowserTabStripController::IsCommandEnabledForTab(
    204     TabStripModel::ContextMenuCommand command_id,
    205     Tab* tab) const {
    206   int model_index = tabstrip_->GetModelIndexOfTab(tab);
    207   return model_->ContainsIndex(model_index) ?
    208       model_->IsContextMenuCommandEnabled(model_index, command_id) : false;
    209 }
    210 
    211 void BrowserTabStripController::ExecuteCommandForTab(
    212     TabStripModel::ContextMenuCommand command_id,
    213     Tab* tab) {
    214   int model_index = tabstrip_->GetModelIndexOfTab(tab);
    215   if (model_->ContainsIndex(model_index))
    216     model_->ExecuteContextMenuCommand(model_index, command_id);
    217 }
    218 
    219 bool BrowserTabStripController::IsTabPinned(Tab* tab) const {
    220   return IsTabPinned(tabstrip_->GetModelIndexOfTab(tab));
    221 }
    222 
    223 const ui::ListSelectionModel& BrowserTabStripController::GetSelectionModel() {
    224   return model_->selection_model();
    225 }
    226 
    227 int BrowserTabStripController::GetCount() const {
    228   return model_->count();
    229 }
    230 
    231 bool BrowserTabStripController::IsValidIndex(int index) const {
    232   return model_->ContainsIndex(index);
    233 }
    234 
    235 bool BrowserTabStripController::IsActiveTab(int model_index) const {
    236   return model_->active_index() == model_index;
    237 }
    238 
    239 int BrowserTabStripController::GetActiveIndex() const {
    240   return model_->active_index();
    241 }
    242 
    243 bool BrowserTabStripController::IsTabSelected(int model_index) const {
    244   return model_->IsTabSelected(model_index);
    245 }
    246 
    247 bool BrowserTabStripController::IsTabPinned(int model_index) const {
    248   return model_->ContainsIndex(model_index) && model_->IsTabPinned(model_index);
    249 }
    250 
    251 bool BrowserTabStripController::IsNewTabPage(int model_index) const {
    252   if (!model_->ContainsIndex(model_index))
    253     return false;
    254 
    255   const WebContents* contents = model_->GetWebContentsAt(model_index);
    256   return contents && (contents->GetURL() == GURL(chrome::kChromeUINewTabURL) ||
    257       chrome::IsInstantNTP(contents));
    258 }
    259 
    260 void BrowserTabStripController::SelectTab(int model_index) {
    261   model_->ActivateTabAt(model_index, true);
    262 }
    263 
    264 void BrowserTabStripController::ExtendSelectionTo(int model_index) {
    265   model_->ExtendSelectionTo(model_index);
    266 }
    267 
    268 void BrowserTabStripController::ToggleSelected(int model_index) {
    269   model_->ToggleSelectionAt(model_index);
    270 }
    271 
    272 void BrowserTabStripController::AddSelectionFromAnchorTo(int model_index) {
    273   model_->AddSelectionFromAnchorTo(model_index);
    274 }
    275 
    276 void BrowserTabStripController::CloseTab(int model_index,
    277                                          CloseTabSource source) {
    278   // Cancel any pending tab transition.
    279   hover_tab_selector_.CancelTabTransition();
    280 
    281   tabstrip_->PrepareForCloseAt(model_index, source);
    282   model_->CloseWebContentsAt(model_index,
    283                              TabStripModel::CLOSE_USER_GESTURE |
    284                              TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
    285 }
    286 
    287 void BrowserTabStripController::ShowContextMenuForTab(
    288     Tab* tab,
    289     const gfx::Point& p,
    290     ui::MenuSourceType source_type) {
    291   context_menu_contents_.reset(new TabContextMenuContents(tab, this));
    292   context_menu_contents_->RunMenuAt(p, source_type);
    293 }
    294 
    295 void BrowserTabStripController::UpdateLoadingAnimations() {
    296   // Don't use the model count here as it's possible for this to be invoked
    297   // before we've applied an update from the model (Browser::TabInsertedAt may
    298   // be processed before us and invokes this).
    299   for (int i = 0, tab_count = tabstrip_->tab_count(); i < tab_count; ++i) {
    300     if (model_->ContainsIndex(i)) {
    301       Tab* tab = tabstrip_->tab_at(i);
    302       WebContents* contents = model_->GetWebContentsAt(i);
    303       tab->UpdateLoadingAnimation(TabContentsNetworkState(contents));
    304     }
    305   }
    306 }
    307 
    308 int BrowserTabStripController::HasAvailableDragActions() const {
    309   return model_->delegate()->GetDragActions();
    310 }
    311 
    312 void BrowserTabStripController::OnDropIndexUpdate(int index,
    313                                                   bool drop_before) {
    314   // Perform a delayed tab transition if hovering directly over a tab.
    315   // Otherwise, cancel the pending one.
    316   if (index != -1 && !drop_before) {
    317     hover_tab_selector_.StartTabTransition(index);
    318   } else {
    319     hover_tab_selector_.CancelTabTransition();
    320   }
    321 }
    322 
    323 void BrowserTabStripController::PerformDrop(bool drop_before,
    324                                             int index,
    325                                             const GURL& url) {
    326   chrome::NavigateParams params(browser_, url, content::PAGE_TRANSITION_LINK);
    327   params.tabstrip_index = index;
    328 
    329   if (drop_before) {
    330     content::RecordAction(UserMetricsAction("Tab_DropURLBetweenTabs"));
    331     params.disposition = NEW_FOREGROUND_TAB;
    332   } else {
    333     content::RecordAction(UserMetricsAction("Tab_DropURLOnTab"));
    334     params.disposition = CURRENT_TAB;
    335     params.source_contents = model_->GetWebContentsAt(index);
    336   }
    337   params.window_action = chrome::NavigateParams::SHOW_WINDOW;
    338   chrome::Navigate(&params);
    339 }
    340 
    341 bool BrowserTabStripController::IsCompatibleWith(TabStrip* other) const {
    342   Profile* other_profile =
    343       static_cast<BrowserTabStripController*>(other->controller())->profile();
    344   return other_profile == profile();
    345 }
    346 
    347 void BrowserTabStripController::CreateNewTab() {
    348   model_->delegate()->AddBlankTabAt(-1, true);
    349 }
    350 
    351 bool BrowserTabStripController::IsIncognito() {
    352   return browser_->profile()->IsOffTheRecord();
    353 }
    354 
    355 void BrowserTabStripController::LayoutTypeMaybeChanged() {
    356   bool adjust_layout = false;
    357   TabStripLayoutType layout_type =
    358       DetermineTabStripLayout(g_browser_process->local_state(), &adjust_layout);
    359   if (!adjust_layout || layout_type == tabstrip_->layout_type())
    360     return;
    361 
    362   g_browser_process->local_state()->SetInteger(
    363       prefs::kTabStripLayoutType,
    364       static_cast<int>(tabstrip_->layout_type()));
    365 }
    366 
    367 void BrowserTabStripController::OnStartedDraggingTabs() {
    368   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser_);
    369   if (browser_view && !immersive_reveal_lock_.get()) {
    370     // The top-of-window views should be revealed while the user is dragging
    371     // tabs in immersive fullscreen. The top-of-window views may not be already
    372     // revealed if the user is attempting to attach a tab to a tabstrip
    373     // belonging to an immersive fullscreen window.
    374     immersive_reveal_lock_.reset(
    375         browser_view->immersive_mode_controller()->GetRevealedLock(
    376             ImmersiveModeController::ANIMATE_REVEAL_NO));
    377   }
    378 }
    379 
    380 void BrowserTabStripController::OnStoppedDraggingTabs() {
    381   immersive_reveal_lock_.reset();
    382 }
    383 
    384 ////////////////////////////////////////////////////////////////////////////////
    385 // BrowserTabStripController, TabStripModelObserver implementation:
    386 
    387 void BrowserTabStripController::TabInsertedAt(WebContents* contents,
    388                                               int model_index,
    389                                               bool is_active) {
    390   DCHECK(contents);
    391   DCHECK(model_->ContainsIndex(model_index));
    392   AddTab(contents, model_index, is_active);
    393 }
    394 
    395 void BrowserTabStripController::TabDetachedAt(WebContents* contents,
    396                                               int model_index) {
    397   // Cancel any pending tab transition.
    398   hover_tab_selector_.CancelTabTransition();
    399 
    400   tabstrip_->RemoveTabAt(model_index);
    401 }
    402 
    403 void BrowserTabStripController::TabSelectionChanged(
    404     TabStripModel* tab_strip_model,
    405     const ui::ListSelectionModel& old_model) {
    406   tabstrip_->SetSelection(old_model, model_->selection_model());
    407 }
    408 
    409 void BrowserTabStripController::TabMoved(WebContents* contents,
    410                                          int from_model_index,
    411                                          int to_model_index) {
    412   // Cancel any pending tab transition.
    413   hover_tab_selector_.CancelTabTransition();
    414 
    415   // Pass in the TabRendererData as the pinned state may have changed.
    416   TabRendererData data;
    417   SetTabRendererDataFromModel(contents, to_model_index, &data, EXISTING_TAB);
    418   tabstrip_->MoveTab(from_model_index, to_model_index, data);
    419 }
    420 
    421 void BrowserTabStripController::TabChangedAt(WebContents* contents,
    422                                              int model_index,
    423                                              TabChangeType change_type) {
    424   if (change_type == TITLE_NOT_LOADING) {
    425     tabstrip_->TabTitleChangedNotLoading(model_index);
    426     // We'll receive another notification of the change asynchronously.
    427     return;
    428   }
    429 
    430   SetTabDataAt(contents, model_index);
    431 }
    432 
    433 void BrowserTabStripController::TabReplacedAt(TabStripModel* tab_strip_model,
    434                                               WebContents* old_contents,
    435                                               WebContents* new_contents,
    436                                               int model_index) {
    437   SetTabDataAt(new_contents, model_index);
    438 }
    439 
    440 void BrowserTabStripController::TabPinnedStateChanged(WebContents* contents,
    441                                                       int model_index) {
    442   // Currently none of the renderers render pinned state differently.
    443 }
    444 
    445 void BrowserTabStripController::TabMiniStateChanged(WebContents* contents,
    446                                                     int model_index) {
    447   SetTabDataAt(contents, model_index);
    448 }
    449 
    450 void BrowserTabStripController::TabBlockedStateChanged(WebContents* contents,
    451                                                        int model_index) {
    452   SetTabDataAt(contents, model_index);
    453 }
    454 
    455 void BrowserTabStripController::SetTabRendererDataFromModel(
    456     WebContents* contents,
    457     int model_index,
    458     TabRendererData* data,
    459     TabStatus tab_status) {
    460   FaviconTabHelper* favicon_tab_helper =
    461       FaviconTabHelper::FromWebContents(contents);
    462 
    463   data->favicon = favicon_tab_helper->GetFavicon().AsImageSkia();
    464   data->network_state = TabContentsNetworkState(contents);
    465   data->title = contents->GetTitle();
    466   data->url = contents->GetURL();
    467   data->loading = contents->IsLoading();
    468   data->crashed_status = contents->GetCrashedStatus();
    469   data->incognito = contents->GetBrowserContext()->IsOffTheRecord();
    470   data->show_icon = favicon_tab_helper->ShouldDisplayFavicon();
    471   data->mini = model_->IsMiniTab(model_index);
    472   data->blocked = model_->IsTabBlocked(model_index);
    473   data->app = extensions::TabHelper::FromWebContents(contents)->is_app();
    474   if (chrome::ShouldShowProjectingIndicator(contents))
    475     data->capture_state = TabRendererData::CAPTURE_STATE_PROJECTING;
    476   else if (chrome::ShouldShowRecordingIndicator(contents))
    477     data->capture_state = TabRendererData::CAPTURE_STATE_RECORDING;
    478   else
    479     data->capture_state = TabRendererData::CAPTURE_STATE_NONE;
    480 
    481   if (chrome::IsPlayingAudio(contents))
    482     data->audio_state = TabRendererData::AUDIO_STATE_PLAYING;
    483   else
    484     data->audio_state = TabRendererData::AUDIO_STATE_NONE;
    485 }
    486 
    487 void BrowserTabStripController::SetTabDataAt(content::WebContents* web_contents,
    488                                              int model_index) {
    489   TabRendererData data;
    490   SetTabRendererDataFromModel(web_contents, model_index, &data, EXISTING_TAB);
    491   tabstrip_->SetTabData(model_index, data);
    492 }
    493 
    494 void BrowserTabStripController::StartHighlightTabsForCommand(
    495     TabStripModel::ContextMenuCommand command_id,
    496     Tab* tab) {
    497   if (command_id == TabStripModel::CommandCloseOtherTabs ||
    498       command_id == TabStripModel::CommandCloseTabsToRight) {
    499     int model_index = tabstrip_->GetModelIndexOfTab(tab);
    500     if (IsValidIndex(model_index)) {
    501       std::vector<int> indices =
    502           model_->GetIndicesClosedByCommand(model_index, command_id);
    503       for (std::vector<int>::const_iterator i(indices.begin());
    504            i != indices.end(); ++i) {
    505         tabstrip_->StartHighlight(*i);
    506       }
    507     }
    508   }
    509 }
    510 
    511 void BrowserTabStripController::StopHighlightTabsForCommand(
    512     TabStripModel::ContextMenuCommand command_id,
    513     Tab* tab) {
    514   if (command_id == TabStripModel::CommandCloseTabsToRight ||
    515       command_id == TabStripModel::CommandCloseOtherTabs) {
    516     // Just tell all Tabs to stop pulsing - it's safe.
    517     tabstrip_->StopAllHighlighting();
    518   }
    519 }
    520 
    521 void BrowserTabStripController::AddTab(WebContents* contents,
    522                                        int index,
    523                                        bool is_active) {
    524   // Cancel any pending tab transition.
    525   hover_tab_selector_.CancelTabTransition();
    526 
    527   TabRendererData data;
    528   SetTabRendererDataFromModel(contents, index, &data, NEW_TAB);
    529   tabstrip_->AddTabAt(index, data, is_active);
    530 }
    531 
    532 void BrowserTabStripController::UpdateLayoutType() {
    533   bool adjust_layout = false;
    534   TabStripLayoutType layout_type =
    535       DetermineTabStripLayout(g_browser_process->local_state(), &adjust_layout);
    536   tabstrip_->SetLayoutType(layout_type, adjust_layout);
    537 }
    538