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(¶ms); 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