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