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