browser_tab_strip_controller.cc revision 58537e28ecd584eab876aee8be7156509866d23a
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 39using content::UserMetricsAction; 40using content::WebContents; 41 42namespace { 43 44TabRendererData::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 53TabStripLayoutType 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 76class 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 167BrowserTabStripController::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 182BrowserTabStripController::~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 192void 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 203bool 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 211void 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 219bool BrowserTabStripController::IsTabPinned(Tab* tab) const { 220 return IsTabPinned(tabstrip_->GetModelIndexOfTab(tab)); 221} 222 223const ui::ListSelectionModel& BrowserTabStripController::GetSelectionModel() { 224 return model_->selection_model(); 225} 226 227int BrowserTabStripController::GetCount() const { 228 return model_->count(); 229} 230 231bool BrowserTabStripController::IsValidIndex(int index) const { 232 return model_->ContainsIndex(index); 233} 234 235bool BrowserTabStripController::IsActiveTab(int model_index) const { 236 return model_->active_index() == model_index; 237} 238 239int BrowserTabStripController::GetActiveIndex() const { 240 return model_->active_index(); 241} 242 243bool BrowserTabStripController::IsTabSelected(int model_index) const { 244 return model_->IsTabSelected(model_index); 245} 246 247bool BrowserTabStripController::IsTabPinned(int model_index) const { 248 return model_->ContainsIndex(model_index) && model_->IsTabPinned(model_index); 249} 250 251bool 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 260void BrowserTabStripController::SelectTab(int model_index) { 261 model_->ActivateTabAt(model_index, true); 262} 263 264void BrowserTabStripController::ExtendSelectionTo(int model_index) { 265 model_->ExtendSelectionTo(model_index); 266} 267 268void BrowserTabStripController::ToggleSelected(int model_index) { 269 model_->ToggleSelectionAt(model_index); 270} 271 272void BrowserTabStripController::AddSelectionFromAnchorTo(int model_index) { 273 model_->AddSelectionFromAnchorTo(model_index); 274} 275 276void 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 287void 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 295void 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 308int BrowserTabStripController::HasAvailableDragActions() const { 309 return model_->delegate()->GetDragActions(); 310} 311 312void 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 323void 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(¶ms); 339} 340 341bool BrowserTabStripController::IsCompatibleWith(TabStrip* other) const { 342 Profile* other_profile = 343 static_cast<BrowserTabStripController*>(other->controller())->profile(); 344 return other_profile == profile(); 345} 346 347void BrowserTabStripController::CreateNewTab() { 348 model_->delegate()->AddBlankTabAt(-1, true); 349} 350 351bool BrowserTabStripController::IsIncognito() { 352 return browser_->profile()->IsOffTheRecord(); 353} 354 355void 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 367void 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 380void BrowserTabStripController::OnStoppedDraggingTabs() { 381 immersive_reveal_lock_.reset(); 382} 383 384//////////////////////////////////////////////////////////////////////////////// 385// BrowserTabStripController, TabStripModelObserver implementation: 386 387void 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 395void 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 403void BrowserTabStripController::TabSelectionChanged( 404 TabStripModel* tab_strip_model, 405 const ui::ListSelectionModel& old_model) { 406 tabstrip_->SetSelection(old_model, model_->selection_model()); 407} 408 409void 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 421void 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 433void 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 440void BrowserTabStripController::TabPinnedStateChanged(WebContents* contents, 441 int model_index) { 442 // Currently none of the renderers render pinned state differently. 443} 444 445void BrowserTabStripController::TabMiniStateChanged(WebContents* contents, 446 int model_index) { 447 SetTabDataAt(contents, model_index); 448} 449 450void BrowserTabStripController::TabBlockedStateChanged(WebContents* contents, 451 int model_index) { 452 SetTabDataAt(contents, model_index); 453} 454 455void 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 487void 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 494void 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 511void 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 521void 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 532void 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