Home | History | Annotate | Download | only in tabs
      1 // Copyright (c) 2011 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 "chrome/browser/extensions/extension_tab_helper.h"
     10 #include "chrome/browser/metrics/user_metrics.h"
     11 #include "chrome/browser/profiles/profile.h"
     12 #include "chrome/browser/tabs/tab_strip_model.h"
     13 #include "chrome/browser/ui/browser.h"
     14 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
     15 #include "chrome/browser/ui/tabs/tab_menu_model.h"
     16 #include "chrome/browser/ui/views/tabs/base_tab_strip.h"
     17 #include "chrome/browser/ui/views/tabs/tab_renderer_data.h"
     18 #include "chrome/common/url_constants.h"
     19 #include "content/browser/renderer_host/render_view_host.h"
     20 #include "content/browser/tab_contents/tab_contents.h"
     21 #include "content/common/notification_service.h"
     22 #include "views/controls/menu/menu_2.h"
     23 #include "views/widget/widget.h"
     24 
     25 static TabRendererData::NetworkState TabContentsNetworkState(
     26     TabContents* contents) {
     27   if (!contents || !contents->is_loading())
     28     return TabRendererData::NETWORK_STATE_NONE;
     29   if (contents->waiting_for_response())
     30     return TabRendererData::NETWORK_STATE_WAITING;
     31   return TabRendererData::NETWORK_STATE_LOADING;
     32 }
     33 
     34 class BrowserTabStripController::TabContextMenuContents
     35     : public ui::SimpleMenuModel::Delegate {
     36  public:
     37   TabContextMenuContents(BaseTab* tab,
     38                          BrowserTabStripController* controller)
     39       : ALLOW_THIS_IN_INITIALIZER_LIST(
     40           model_(this,
     41                  controller->model_,
     42                  controller->tabstrip_->GetModelIndexOfBaseTab(tab))),
     43         tab_(tab),
     44         controller_(controller),
     45         last_command_(TabStripModel::CommandFirst) {
     46     Build();
     47   }
     48   virtual ~TabContextMenuContents() {
     49     menu_->CancelMenu();
     50     if (controller_)
     51       controller_->tabstrip_->StopAllHighlighting();
     52   }
     53 
     54   void Cancel() {
     55     controller_ = NULL;
     56   }
     57 
     58   void RunMenuAt(const gfx::Point& point) {
     59     menu_->RunMenuAt(point, views::Menu2::ALIGN_TOPLEFT);
     60     // We could be gone now. Assume |this| is junk!
     61   }
     62 
     63   // Overridden from ui::SimpleMenuModel::Delegate:
     64   virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
     65     return controller_->IsCommandCheckedForTab(
     66         static_cast<TabStripModel::ContextMenuCommand>(command_id),
     67         tab_);
     68   }
     69   virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
     70     return controller_->IsCommandEnabledForTab(
     71         static_cast<TabStripModel::ContextMenuCommand>(command_id),
     72         tab_);
     73   }
     74   virtual bool GetAcceleratorForCommandId(
     75       int command_id,
     76       ui::Accelerator* accelerator) OVERRIDE {
     77     int browser_cmd;
     78     return TabStripModel::ContextMenuCommandToBrowserCommand(command_id,
     79                                                              &browser_cmd) ?
     80         controller_->tabstrip_->GetWidget()->GetAccelerator(browser_cmd,
     81                                                             accelerator) :
     82         false;
     83   }
     84   virtual void CommandIdHighlighted(int command_id) OVERRIDE {
     85     controller_->StopHighlightTabsForCommand(last_command_, tab_);
     86     last_command_ = static_cast<TabStripModel::ContextMenuCommand>(command_id);
     87     controller_->StartHighlightTabsForCommand(last_command_, tab_);
     88   }
     89   virtual void ExecuteCommand(int command_id) OVERRIDE {
     90     // Executing the command destroys |this|, and can also end up destroying
     91     // |controller_| (e.g. for |CommandUseVerticalTabs|). So stop the highlights
     92     // before executing the command.
     93     controller_->tabstrip_->StopAllHighlighting();
     94     controller_->ExecuteCommandForTab(
     95         static_cast<TabStripModel::ContextMenuCommand>(command_id),
     96         tab_);
     97   }
     98 
     99   virtual void MenuClosed() OVERRIDE {
    100     if (controller_)
    101       controller_->tabstrip_->StopAllHighlighting();
    102   }
    103 
    104  private:
    105   void Build() {
    106     menu_.reset(new views::Menu2(&model_));
    107   }
    108 
    109   TabMenuModel model_;
    110   scoped_ptr<views::Menu2> menu_;
    111 
    112   // The tab we're showing a menu for.
    113   BaseTab* tab_;
    114 
    115   // A pointer back to our hosting controller, for command state information.
    116   BrowserTabStripController* controller_;
    117 
    118   // The last command that was selected, so that we can start/stop highlighting
    119   // appropriately as the user moves through the menu.
    120   TabStripModel::ContextMenuCommand last_command_;
    121 
    122   DISALLOW_COPY_AND_ASSIGN(TabContextMenuContents);
    123 };
    124 
    125 ////////////////////////////////////////////////////////////////////////////////
    126 // BrowserTabStripController, public:
    127 
    128 BrowserTabStripController::BrowserTabStripController(Browser* browser,
    129                                                      TabStripModel* model)
    130     : model_(model),
    131       tabstrip_(NULL),
    132       browser_(browser) {
    133   model_->AddObserver(this);
    134 
    135   notification_registrar_.Add(this,
    136       NotificationType::TAB_CLOSEABLE_STATE_CHANGED,
    137       NotificationService::AllSources());
    138 }
    139 
    140 BrowserTabStripController::~BrowserTabStripController() {
    141   // When we get here the TabStrip is being deleted. We need to explicitly
    142   // cancel the menu, otherwise it may try to invoke something on the tabstrip
    143   // from it's destructor.
    144   if (context_menu_contents_.get())
    145     context_menu_contents_->Cancel();
    146 
    147   model_->RemoveObserver(this);
    148 }
    149 
    150 void BrowserTabStripController::InitFromModel(BaseTabStrip* tabstrip) {
    151   tabstrip_ = tabstrip;
    152   // Walk the model, calling our insertion observer method for each item within
    153   // it.
    154   for (int i = 0; i < model_->count(); ++i)
    155     TabInsertedAt(model_->GetTabContentsAt(i), i, model_->active_index() == i);
    156 }
    157 
    158 bool BrowserTabStripController::IsCommandEnabledForTab(
    159     TabStripModel::ContextMenuCommand command_id,
    160     BaseTab* tab) const {
    161   int model_index = tabstrip_->GetModelIndexOfBaseTab(tab);
    162   return model_->ContainsIndex(model_index) ?
    163       model_->IsContextMenuCommandEnabled(model_index, command_id) : false;
    164 }
    165 
    166 bool BrowserTabStripController::IsCommandCheckedForTab(
    167     TabStripModel::ContextMenuCommand command_id,
    168     BaseTab* tab) const {
    169   int model_index = tabstrip_->GetModelIndexOfBaseTab(tab);
    170   return model_->ContainsIndex(model_index) ?
    171       model_->IsContextMenuCommandChecked(model_index, command_id) : false;
    172 }
    173 
    174 void BrowserTabStripController::ExecuteCommandForTab(
    175     TabStripModel::ContextMenuCommand command_id,
    176     BaseTab* tab) {
    177   int model_index = tabstrip_->GetModelIndexOfBaseTab(tab);
    178   if (model_->ContainsIndex(model_index))
    179     model_->ExecuteContextMenuCommand(model_index, command_id);
    180 }
    181 
    182 bool BrowserTabStripController::IsTabPinned(BaseTab* tab) const {
    183   return IsTabPinned(tabstrip_->GetModelIndexOfBaseTab(tab));
    184 }
    185 
    186 int BrowserTabStripController::GetCount() const {
    187   return model_->count();
    188 }
    189 
    190 bool BrowserTabStripController::IsValidIndex(int index) const {
    191   return model_->ContainsIndex(index);
    192 }
    193 
    194 bool BrowserTabStripController::IsActiveTab(int model_index) const {
    195   return model_->active_index() == model_index;
    196 }
    197 
    198 bool BrowserTabStripController::IsTabSelected(int model_index) const {
    199   return model_->IsTabSelected(model_index);
    200 }
    201 
    202 bool BrowserTabStripController::IsTabPinned(int model_index) const {
    203   return model_->ContainsIndex(model_index) && model_->IsTabPinned(model_index);
    204 }
    205 
    206 bool BrowserTabStripController::IsTabCloseable(int model_index) const {
    207   return !model_->ContainsIndex(model_index) ||
    208       model_->delegate()->CanCloseTab();
    209 }
    210 
    211 bool BrowserTabStripController::IsNewTabPage(int model_index) const {
    212   return model_->ContainsIndex(model_index) &&
    213       model_->GetTabContentsAt(model_index)->tab_contents()->GetURL() ==
    214       GURL(chrome::kChromeUINewTabURL);
    215 }
    216 
    217 void BrowserTabStripController::SelectTab(int model_index) {
    218   model_->ActivateTabAt(model_index, true);
    219 }
    220 
    221 void BrowserTabStripController::ExtendSelectionTo(int model_index) {
    222   model_->ExtendSelectionTo(model_index);
    223 }
    224 
    225 void BrowserTabStripController::ToggleSelected(int model_index) {
    226   model_->ToggleSelectionAt(model_index);
    227 }
    228 
    229 void BrowserTabStripController::AddSelectionFromAnchorTo(int model_index) {
    230   model_->AddSelectionFromAnchorTo(model_index);
    231 }
    232 
    233 void BrowserTabStripController::CloseTab(int model_index) {
    234   tabstrip_->PrepareForCloseAt(model_index);
    235   model_->CloseTabContentsAt(model_index,
    236                              TabStripModel::CLOSE_USER_GESTURE |
    237                              TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
    238 }
    239 
    240 void BrowserTabStripController::ShowContextMenuForTab(BaseTab* tab,
    241                                                       const gfx::Point& p) {
    242   context_menu_contents_.reset(new TabContextMenuContents(tab, this));
    243   context_menu_contents_->RunMenuAt(p);
    244 }
    245 
    246 void BrowserTabStripController::UpdateLoadingAnimations() {
    247   // Don't use the model count here as it's possible for this to be invoked
    248   // before we've applied an update from the model (Browser::TabInsertedAt may
    249   // be processed before us and invokes this).
    250   for (int tab_index = 0, tab_count = tabstrip_->tab_count();
    251        tab_index < tab_count; ++tab_index) {
    252     BaseTab* tab = tabstrip_->base_tab_at_tab_index(tab_index);
    253     int model_index = tabstrip_->GetModelIndexOfBaseTab(tab);
    254     if (model_->ContainsIndex(model_index)) {
    255       TabContentsWrapper* contents = model_->GetTabContentsAt(model_index);
    256       tab->UpdateLoadingAnimation(
    257           TabContentsNetworkState(contents->tab_contents()));
    258     }
    259   }
    260 }
    261 
    262 int BrowserTabStripController::HasAvailableDragActions() const {
    263   return model_->delegate()->GetDragActions();
    264 }
    265 
    266 void BrowserTabStripController::PerformDrop(bool drop_before,
    267                                             int index,
    268                                             const GURL& url) {
    269   browser::NavigateParams params(browser_, url, PageTransition::LINK);
    270   params.tabstrip_index = index;
    271 
    272   if (drop_before) {
    273     UserMetrics::RecordAction(UserMetricsAction("Tab_DropURLBetweenTabs"),
    274                               model_->profile());
    275     params.disposition = NEW_FOREGROUND_TAB;
    276   } else {
    277     UserMetrics::RecordAction(UserMetricsAction("Tab_DropURLOnTab"),
    278                               model_->profile());
    279     params.disposition = CURRENT_TAB;
    280     params.source_contents = model_->GetTabContentsAt(index);
    281   }
    282 
    283   browser::Navigate(&params);
    284 }
    285 
    286 bool BrowserTabStripController::IsCompatibleWith(BaseTabStrip* other) const {
    287   Profile* other_profile =
    288       static_cast<BrowserTabStripController*>(other->controller())->profile();
    289   return other_profile == profile();
    290 }
    291 
    292 void BrowserTabStripController::CreateNewTab() {
    293   UserMetrics::RecordAction(UserMetricsAction("NewTab_Button"),
    294                             model_->profile());
    295 
    296   model_->delegate()->AddBlankTab(true);
    297 }
    298 
    299 ////////////////////////////////////////////////////////////////////////////////
    300 // BrowserTabStripController, TabStripModelObserver implementation:
    301 
    302 void BrowserTabStripController::TabInsertedAt(TabContentsWrapper* contents,
    303                                               int model_index,
    304                                               bool active) {
    305   DCHECK(contents);
    306   DCHECK(model_index == TabStripModel::kNoTab ||
    307          model_->ContainsIndex(model_index));
    308 
    309   TabRendererData data;
    310   SetTabRendererDataFromModel(contents->tab_contents(), model_index, &data);
    311   tabstrip_->AddTabAt(model_index, data);
    312 }
    313 
    314 void BrowserTabStripController::TabDetachedAt(TabContentsWrapper* contents,
    315                                               int model_index) {
    316   tabstrip_->RemoveTabAt(model_index);
    317 }
    318 
    319 void BrowserTabStripController::TabSelectedAt(TabContentsWrapper* old_contents,
    320                                               TabContentsWrapper* contents,
    321                                               int model_index,
    322                                               bool user_gesture) {
    323   tabstrip_->SelectTabAt(model_->GetIndexOfTabContents(old_contents),
    324                          model_index);
    325 }
    326 
    327 void BrowserTabStripController::TabMoved(TabContentsWrapper* contents,
    328                                          int from_model_index,
    329                                          int to_model_index) {
    330   // Update the data first as the pinned state may have changed.
    331   TabRendererData data;
    332   SetTabRendererDataFromModel(contents->tab_contents(), to_model_index, &data);
    333   tabstrip_->SetTabData(from_model_index, data);
    334 
    335   tabstrip_->MoveTab(from_model_index, to_model_index);
    336 }
    337 
    338 void BrowserTabStripController::TabChangedAt(TabContentsWrapper* contents,
    339                                              int model_index,
    340                                              TabChangeType change_type) {
    341   if (change_type == TITLE_NOT_LOADING) {
    342     tabstrip_->TabTitleChangedNotLoading(model_index);
    343     // We'll receive another notification of the change asynchronously.
    344     return;
    345   }
    346 
    347   SetTabDataAt(contents, model_index);
    348 }
    349 
    350 void BrowserTabStripController::TabReplacedAt(TabStripModel* tab_strip_model,
    351                                               TabContentsWrapper* old_contents,
    352                                               TabContentsWrapper* new_contents,
    353                                               int model_index) {
    354   SetTabDataAt(new_contents, model_index);
    355 }
    356 
    357 void BrowserTabStripController::TabPinnedStateChanged(
    358     TabContentsWrapper* contents,
    359     int model_index) {
    360   // Currently none of the renderers render pinned state differently.
    361 }
    362 
    363 void BrowserTabStripController::TabMiniStateChanged(
    364     TabContentsWrapper* contents,
    365     int model_index) {
    366   SetTabDataAt(contents, model_index);
    367 }
    368 
    369 void BrowserTabStripController::TabBlockedStateChanged(
    370     TabContentsWrapper* contents,
    371     int model_index) {
    372   SetTabDataAt(contents, model_index);
    373 }
    374 
    375 void BrowserTabStripController::SetTabDataAt(
    376     TabContentsWrapper* contents,
    377     int model_index) {
    378   TabRendererData data;
    379   SetTabRendererDataFromModel(contents->tab_contents(), model_index, &data);
    380   tabstrip_->SetTabData(model_index, data);
    381 }
    382 
    383 void BrowserTabStripController::SetTabRendererDataFromModel(
    384     TabContents* contents,
    385     int model_index,
    386     TabRendererData* data) {
    387   SkBitmap* app_icon = NULL;
    388   TabContentsWrapper* wrapper =
    389       TabContentsWrapper::GetCurrentWrapperForContents(contents);
    390 
    391   // Extension App icons are slightly larger than favicons, so only allow
    392   // them if permitted by the model.
    393   if (model_->delegate()->LargeIconsPermitted())
    394     app_icon = wrapper->extension_tab_helper()->GetExtensionAppIcon();
    395 
    396   if (app_icon)
    397     data->favicon = *app_icon;
    398   else
    399     data->favicon = contents->GetFavicon();
    400   data->network_state = TabContentsNetworkState(contents);
    401   data->title = contents->GetTitle();
    402   data->url = contents->GetURL();
    403   data->loading = contents->is_loading();
    404   data->crashed_status = contents->crashed_status();
    405   data->incognito = contents->profile()->IsOffTheRecord();
    406   data->show_icon = contents->ShouldDisplayFavicon();
    407   data->mini = model_->IsMiniTab(model_index);
    408   data->blocked = model_->IsTabBlocked(model_index);
    409   data->app = wrapper->extension_tab_helper()->is_app();
    410 }
    411 
    412 void BrowserTabStripController::StartHighlightTabsForCommand(
    413     TabStripModel::ContextMenuCommand command_id,
    414     BaseTab* tab) {
    415   if (command_id == TabStripModel::CommandCloseOtherTabs ||
    416       command_id == TabStripModel::CommandCloseTabsToRight) {
    417     int model_index = tabstrip_->GetModelIndexOfBaseTab(tab);
    418     if (IsValidIndex(model_index)) {
    419       std::vector<int> indices =
    420           model_->GetIndicesClosedByCommand(model_index, command_id);
    421       for (std::vector<int>::const_iterator i = indices.begin();
    422            i != indices.end(); ++i) {
    423         tabstrip_->StartHighlight(*i);
    424       }
    425     }
    426   }
    427 }
    428 
    429 void BrowserTabStripController::StopHighlightTabsForCommand(
    430     TabStripModel::ContextMenuCommand command_id,
    431     BaseTab* tab) {
    432   if (command_id == TabStripModel::CommandCloseTabsToRight ||
    433       command_id == TabStripModel::CommandCloseOtherTabs) {
    434     // Just tell all Tabs to stop pulsing - it's safe.
    435     tabstrip_->StopAllHighlighting();
    436   }
    437 }
    438 
    439 ////////////////////////////////////////////////////////////////////////////////
    440 // BrowserTabStripController, NotificationObserver implementation:
    441 
    442 void BrowserTabStripController::Observe(NotificationType type,
    443     const NotificationSource& source, const NotificationDetails& details) {
    444   DCHECK(type.value == NotificationType::TAB_CLOSEABLE_STATE_CHANGED);
    445   // Note that this notification may be fired during a model mutation and
    446   // possibly before the tabstrip has processed the change.
    447   // Here, we just re-layout each existing tab to reflect the change in its
    448   // closeable state, and then schedule paint for entire tabstrip.
    449   for (int i = 0; i < tabstrip_->tab_count(); ++i) {
    450     tabstrip_->base_tab_at_tab_index(i)->Layout();
    451   }
    452   tabstrip_->SchedulePaint();
    453 }
    454