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