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.
5 #include "chrome/browser/ui/views/tabs/browser_tab_strip_controller.h"
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"
39 using content::UserMetricsAction;
40 using content::WebContents;
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;
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;
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;
65 *adjust_layout = true;
66 switch (prefs->GetInteger(prefs::kTabStripLayoutType)) {
67 case TAB_STRIP_LAYOUT_STACKED:
68 return TAB_STRIP_LAYOUT_STACKED;
70 return TAB_STRIP_LAYOUT_SHRINK;
76 class BrowserTabStripController::TabContextMenuContents
77 : public ui::SimpleMenuModel::Delegate {
79 TabContextMenuContents(Tab* tab,
80 BrowserTabStripController* controller)
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()));
90 virtual ~TabContextMenuContents() {
92 controller_->tabstrip_->StopAllHighlighting();
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)
109 // Overridden from ui::SimpleMenuModel::Delegate:
110 virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
113 virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
114 return controller_->IsCommandEnabledForTab(
115 static_cast<TabStripModel::ContextMenuCommand>(command_id),
118 virtual bool GetAcceleratorForCommandId(
120 ui::Accelerator* accelerator) OVERRIDE {
122 return TabStripModel::ContextMenuCommandToBrowserCommand(command_id,
124 controller_->tabstrip_->GetWidget()->GetAccelerator(browser_cmd,
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_);
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),
142 virtual void MenuClosed(ui::SimpleMenuModel* /*source*/) OVERRIDE {
144 controller_->tabstrip_->StopAllHighlighting();
148 scoped_ptr<TabMenuModel> model_;
149 scoped_ptr<views::MenuRunner> menu_runner_;
151 // The tab we're showing a menu for.
154 // A pointer back to our hosting controller, for command state information.
155 BrowserTabStripController* controller_;
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_;
161 DISALLOW_COPY_AND_ASSIGN(TabContextMenuContents);
164 ////////////////////////////////////////////////////////////////////////////////
165 // BrowserTabStripController, public:
167 BrowserTabStripController::BrowserTabStripController(Browser* browser,
168 TabStripModel* model)
172 hover_tab_selector_(model) {
173 model_->AddObserver(this);
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)));
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();
189 model_->RemoveObserver(this);
192 void BrowserTabStripController::InitFromModel(TabStrip* tabstrip) {
193 tabstrip_ = tabstrip;
197 // Walk the model, calling our insertion observer method for each item within
199 for (int i = 0; i < model_->count(); ++i)
200 AddTab(model_->GetWebContentsAt(i), i, model_->active_index() == i);
203 bool BrowserTabStripController::IsCommandEnabledForTab(
204 TabStripModel::ContextMenuCommand command_id,
206 int model_index = tabstrip_->GetModelIndexOfTab(tab);
207 return model_->ContainsIndex(model_index) ?
208 model_->IsContextMenuCommandEnabled(model_index, command_id) : false;
211 void BrowserTabStripController::ExecuteCommandForTab(
212 TabStripModel::ContextMenuCommand command_id,
214 int model_index = tabstrip_->GetModelIndexOfTab(tab);
215 if (model_->ContainsIndex(model_index))
216 model_->ExecuteContextMenuCommand(model_index, command_id);
219 bool BrowserTabStripController::IsTabPinned(Tab* tab) const {
220 return IsTabPinned(tabstrip_->GetModelIndexOfTab(tab));
223 const ui::ListSelectionModel& BrowserTabStripController::GetSelectionModel() {
224 return model_->selection_model();
227 int BrowserTabStripController::GetCount() const {
228 return model_->count();
231 bool BrowserTabStripController::IsValidIndex(int index) const {
232 return model_->ContainsIndex(index);
235 bool BrowserTabStripController::IsActiveTab(int model_index) const {
236 return model_->active_index() == model_index;
239 int BrowserTabStripController::GetActiveIndex() const {
240 return model_->active_index();
243 bool BrowserTabStripController::IsTabSelected(int model_index) const {
244 return model_->IsTabSelected(model_index);
247 bool BrowserTabStripController::IsTabPinned(int model_index) const {
248 return model_->ContainsIndex(model_index) && model_->IsTabPinned(model_index);
251 bool BrowserTabStripController::IsNewTabPage(int model_index) const {
252 if (!model_->ContainsIndex(model_index))
255 const WebContents* contents = model_->GetWebContentsAt(model_index);
256 return contents && (contents->GetURL() == GURL(chrome::kChromeUINewTabURL) ||
257 chrome::IsInstantNTP(contents));
260 void BrowserTabStripController::SelectTab(int model_index) {
261 model_->ActivateTabAt(model_index, true);
264 void BrowserTabStripController::ExtendSelectionTo(int model_index) {
265 model_->ExtendSelectionTo(model_index);
268 void BrowserTabStripController::ToggleSelected(int model_index) {
269 model_->ToggleSelectionAt(model_index);
272 void BrowserTabStripController::AddSelectionFromAnchorTo(int model_index) {
273 model_->AddSelectionFromAnchorTo(model_index);
276 void BrowserTabStripController::CloseTab(int model_index,
277 CloseTabSource source) {
278 // Cancel any pending tab transition.
279 hover_tab_selector_.CancelTabTransition();
281 tabstrip_->PrepareForCloseAt(model_index, source);
282 model_->CloseWebContentsAt(model_index,
283 TabStripModel::CLOSE_USER_GESTURE |
284 TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
287 void BrowserTabStripController::ShowContextMenuForTab(
290 ui::MenuSourceType source_type) {
291 context_menu_contents_.reset(new TabContextMenuContents(tab, this));
292 context_menu_contents_->RunMenuAt(p, source_type);
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));
308 int BrowserTabStripController::HasAvailableDragActions() const {
309 return model_->delegate()->GetDragActions();
312 void BrowserTabStripController::OnDropIndexUpdate(int index,
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);
319 hover_tab_selector_.CancelTabTransition();
323 void BrowserTabStripController::PerformDrop(bool drop_before,
326 chrome::NavigateParams params(browser_, url, content::PAGE_TRANSITION_LINK);
327 params.tabstrip_index = index;
330 content::RecordAction(UserMetricsAction("Tab_DropURLBetweenTabs"));
331 params.disposition = NEW_FOREGROUND_TAB;
333 content::RecordAction(UserMetricsAction("Tab_DropURLOnTab"));
334 params.disposition = CURRENT_TAB;
335 params.source_contents = model_->GetWebContentsAt(index);
337 params.window_action = chrome::NavigateParams::SHOW_WINDOW;
338 chrome::Navigate(¶ms);
341 bool BrowserTabStripController::IsCompatibleWith(TabStrip* other) const {
342 Profile* other_profile =
343 static_cast<BrowserTabStripController*>(other->controller())->profile();
344 return other_profile == profile();
347 void BrowserTabStripController::CreateNewTab() {
348 model_->delegate()->AddBlankTabAt(-1, true);
351 bool BrowserTabStripController::IsIncognito() {
352 return browser_->profile()->IsOffTheRecord();
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())
362 g_browser_process->local_state()->SetInteger(
363 prefs::kTabStripLayoutType,
364 static_cast<int>(tabstrip_->layout_type()));
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));
380 void BrowserTabStripController::OnStoppedDraggingTabs() {
381 immersive_reveal_lock_.reset();
384 ////////////////////////////////////////////////////////////////////////////////
385 // BrowserTabStripController, TabStripModelObserver implementation:
387 void BrowserTabStripController::TabInsertedAt(WebContents* contents,
391 DCHECK(model_->ContainsIndex(model_index));
392 AddTab(contents, model_index, is_active);
395 void BrowserTabStripController::TabDetachedAt(WebContents* contents,
397 // Cancel any pending tab transition.
398 hover_tab_selector_.CancelTabTransition();
400 tabstrip_->RemoveTabAt(model_index);
403 void BrowserTabStripController::TabSelectionChanged(
404 TabStripModel* tab_strip_model,
405 const ui::ListSelectionModel& old_model) {
406 tabstrip_->SetSelection(old_model, model_->selection_model());
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();
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);
421 void BrowserTabStripController::TabChangedAt(WebContents* contents,
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.
430 SetTabDataAt(contents, model_index);
433 void BrowserTabStripController::TabReplacedAt(TabStripModel* tab_strip_model,
434 WebContents* old_contents,
435 WebContents* new_contents,
437 SetTabDataAt(new_contents, model_index);
440 void BrowserTabStripController::TabPinnedStateChanged(WebContents* contents,
442 // Currently none of the renderers render pinned state differently.
445 void BrowserTabStripController::TabMiniStateChanged(WebContents* contents,
447 SetTabDataAt(contents, model_index);
450 void BrowserTabStripController::TabBlockedStateChanged(WebContents* contents,
452 SetTabDataAt(contents, model_index);
455 void BrowserTabStripController::SetTabRendererDataFromModel(
456 WebContents* contents,
458 TabRendererData* data,
459 TabStatus tab_status) {
460 FaviconTabHelper* favicon_tab_helper =
461 FaviconTabHelper::FromWebContents(contents);
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 data->media_state = chrome::GetTabMediaStateForContents(contents);
477 void BrowserTabStripController::SetTabDataAt(content::WebContents* web_contents,
479 TabRendererData data;
480 SetTabRendererDataFromModel(web_contents, model_index, &data, EXISTING_TAB);
481 tabstrip_->SetTabData(model_index, data);
484 void BrowserTabStripController::StartHighlightTabsForCommand(
485 TabStripModel::ContextMenuCommand command_id,
487 if (command_id == TabStripModel::CommandCloseOtherTabs ||
488 command_id == TabStripModel::CommandCloseTabsToRight) {
489 int model_index = tabstrip_->GetModelIndexOfTab(tab);
490 if (IsValidIndex(model_index)) {
491 std::vector<int> indices =
492 model_->GetIndicesClosedByCommand(model_index, command_id);
493 for (std::vector<int>::const_iterator i(indices.begin());
494 i != indices.end(); ++i) {
495 tabstrip_->StartHighlight(*i);
501 void BrowserTabStripController::StopHighlightTabsForCommand(
502 TabStripModel::ContextMenuCommand command_id,
504 if (command_id == TabStripModel::CommandCloseTabsToRight ||
505 command_id == TabStripModel::CommandCloseOtherTabs) {
506 // Just tell all Tabs to stop pulsing - it's safe.
507 tabstrip_->StopAllHighlighting();
511 void BrowserTabStripController::AddTab(WebContents* contents,
514 // Cancel any pending tab transition.
515 hover_tab_selector_.CancelTabTransition();
517 TabRendererData data;
518 SetTabRendererDataFromModel(contents, index, &data, NEW_TAB);
519 tabstrip_->AddTabAt(index, data, is_active);
522 void BrowserTabStripController::UpdateLayoutType() {
523 bool adjust_layout = false;
524 TabStripLayoutType layout_type =
525 DetermineTabStripLayout(g_browser_process->local_state(), &adjust_layout);
526 tabstrip_->SetLayoutType(layout_type, adjust_layout);