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