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