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 "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"
50 using base::UserMetricsAction
;
51 using content::WebContents
;
55 TabRendererData::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
;
64 TabStripLayoutType
DetermineTabStripLayout(
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
;
73 // For chromeos always allow entering stacked mode.
75 if (host_desktop_type
!= chrome::HOST_DESKTOP_TYPE_ASH
)
76 return TAB_STRIP_LAYOUT_SHRINK
;
78 if (ui::GetDisplayLayout() != ui::LAYOUT_TOUCH
)
79 return TAB_STRIP_LAYOUT_SHRINK
;
81 *adjust_layout
= true;
82 switch (prefs
->GetInteger(prefs::kTabStripLayoutType
)) {
83 case TAB_STRIP_LAYOUT_STACKED
:
84 return TAB_STRIP_LAYOUT_STACKED
;
86 return TAB_STRIP_LAYOUT_SHRINK
;
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.
92 std::string
FindURLMimeType(const GURL
& url
) {
93 DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
94 base::FilePath full_path
;
95 net::FileURLToFilePath(url
, &full_path
);
97 // Get the MIME type based on the filename.
98 std::string mime_type
;
99 net::GetMimeTypeFromFile(full_path
, &mime_type
);
106 class BrowserTabStripController::TabContextMenuContents
107 : public ui::SimpleMenuModel::Delegate
{
109 TabContextMenuContents(Tab
* tab
,
110 BrowserTabStripController
* controller
)
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()));
120 virtual ~TabContextMenuContents() {
122 controller_
->tabstrip_
->StopAllHighlighting();
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
)
139 // Overridden from ui::SimpleMenuModel::Delegate:
140 virtual bool IsCommandIdChecked(int command_id
) const OVERRIDE
{
143 virtual bool IsCommandIdEnabled(int command_id
) const OVERRIDE
{
144 return controller_
->IsCommandEnabledForTab(
145 static_cast<TabStripModel::ContextMenuCommand
>(command_id
),
148 virtual bool GetAcceleratorForCommandId(
150 ui::Accelerator
* accelerator
) OVERRIDE
{
152 return TabStripModel::ContextMenuCommandToBrowserCommand(command_id
,
154 controller_
->tabstrip_
->GetWidget()->GetAccelerator(browser_cmd
,
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_
);
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
),
172 virtual void MenuClosed(ui::SimpleMenuModel
* /*source*/) OVERRIDE
{
174 controller_
->tabstrip_
->StopAllHighlighting();
178 scoped_ptr
<TabMenuModel
> model_
;
179 scoped_ptr
<views::MenuRunner
> menu_runner_
;
181 // The tab we're showing a menu for.
184 // A pointer back to our hosting controller, for command state information.
185 BrowserTabStripController
* controller_
;
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_
;
191 DISALLOW_COPY_AND_ASSIGN(TabContextMenuContents
);
194 ////////////////////////////////////////////////////////////////////////////////
195 // BrowserTabStripController, public:
197 BrowserTabStripController::BrowserTabStripController(Browser
* browser
,
198 TabStripModel
* model
)
202 hover_tab_selector_(model
),
203 weak_ptr_factory_(this) {
204 model_
->AddObserver(this);
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)));
213 BrowserTabStripController::~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();
220 model_
->RemoveObserver(this);
223 void BrowserTabStripController::InitFromModel(TabStrip
* tabstrip
) {
224 tabstrip_
= tabstrip
;
228 // Walk the model, calling our insertion observer method for each item within
230 for (int i
= 0; i
< model_
->count(); ++i
)
231 AddTab(model_
->GetWebContentsAt(i
), i
, model_
->active_index() == i
);
234 bool BrowserTabStripController::IsCommandEnabledForTab(
235 TabStripModel::ContextMenuCommand command_id
,
237 int model_index
= tabstrip_
->GetModelIndexOfTab(tab
);
238 return model_
->ContainsIndex(model_index
) ?
239 model_
->IsContextMenuCommandEnabled(model_index
, command_id
) : false;
242 void BrowserTabStripController::ExecuteCommandForTab(
243 TabStripModel::ContextMenuCommand command_id
,
245 int model_index
= tabstrip_
->GetModelIndexOfTab(tab
);
246 if (model_
->ContainsIndex(model_index
))
247 model_
->ExecuteContextMenuCommand(model_index
, command_id
);
250 bool BrowserTabStripController::IsTabPinned(Tab
* tab
) const {
251 return IsTabPinned(tabstrip_
->GetModelIndexOfTab(tab
));
254 const ui::ListSelectionModel
& BrowserTabStripController::GetSelectionModel() {
255 return model_
->selection_model();
258 int BrowserTabStripController::GetCount() const {
259 return model_
->count();
262 bool BrowserTabStripController::IsValidIndex(int index
) const {
263 return model_
->ContainsIndex(index
);
266 bool BrowserTabStripController::IsActiveTab(int model_index
) const {
267 return model_
->active_index() == model_index
;
270 int BrowserTabStripController::GetActiveIndex() const {
271 return model_
->active_index();
274 bool BrowserTabStripController::IsTabSelected(int model_index
) const {
275 return model_
->IsTabSelected(model_index
);
278 bool BrowserTabStripController::IsTabPinned(int model_index
) const {
279 return model_
->ContainsIndex(model_index
) && model_
->IsTabPinned(model_index
);
282 bool BrowserTabStripController::IsNewTabPage(int model_index
) const {
283 if (!model_
->ContainsIndex(model_index
))
286 const WebContents
* contents
= model_
->GetWebContentsAt(model_index
);
287 return contents
&& (contents
->GetURL() == GURL(chrome::kChromeUINewTabURL
) ||
288 chrome::IsInstantNTP(contents
));
291 void BrowserTabStripController::SelectTab(int model_index
) {
292 model_
->ActivateTabAt(model_index
, true);
295 void BrowserTabStripController::ExtendSelectionTo(int model_index
) {
296 model_
->ExtendSelectionTo(model_index
);
299 void BrowserTabStripController::ToggleSelected(int model_index
) {
300 model_
->ToggleSelectionAt(model_index
);
303 void BrowserTabStripController::AddSelectionFromAnchorTo(int model_index
) {
304 model_
->AddSelectionFromAnchorTo(model_index
);
307 void BrowserTabStripController::CloseTab(int model_index
,
308 CloseTabSource source
) {
309 // Cancel any pending tab transition.
310 hover_tab_selector_
.CancelTabTransition();
312 tabstrip_
->PrepareForCloseAt(model_index
, source
);
313 model_
->CloseWebContentsAt(model_index
,
314 TabStripModel::CLOSE_USER_GESTURE
|
315 TabStripModel::CLOSE_CREATE_HISTORICAL_TAB
);
318 void BrowserTabStripController::ShowContextMenuForTab(
321 ui::MenuSourceType source_type
) {
322 context_menu_contents_
.reset(new TabContextMenuContents(tab
, this));
323 context_menu_contents_
->RunMenuAt(p
, source_type
);
326 void 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
));
339 int BrowserTabStripController::HasAvailableDragActions() const {
340 return model_
->delegate()->GetDragActions();
343 void BrowserTabStripController::OnDropIndexUpdate(int index
,
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
);
350 hover_tab_selector_
.CancelTabTransition();
354 void BrowserTabStripController::PerformDrop(bool drop_before
,
357 chrome::NavigateParams
params(browser_
, url
, content::PAGE_TRANSITION_LINK
);
358 params
.tabstrip_index
= index
;
361 content::RecordAction(UserMetricsAction("Tab_DropURLBetweenTabs"));
362 params
.disposition
= NEW_FOREGROUND_TAB
;
364 content::RecordAction(UserMetricsAction("Tab_DropURLOnTab"));
365 params
.disposition
= CURRENT_TAB
;
366 params
.source_contents
= model_
->GetWebContentsAt(index
);
368 params
.window_action
= chrome::NavigateParams::SHOW_WINDOW
;
369 chrome::Navigate(¶ms
);
372 bool BrowserTabStripController::IsCompatibleWith(TabStrip
* other
) const {
373 Profile
* other_profile
=
374 static_cast<BrowserTabStripController
*>(other
->controller())->profile();
375 return other_profile
== profile();
378 void BrowserTabStripController::CreateNewTab() {
379 model_
->delegate()->AddTabAt(GURL(), -1, true);
382 void 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);
393 bool BrowserTabStripController::IsIncognito() {
394 return browser_
->profile()->IsOffTheRecord();
397 void 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())
405 g_browser_process
->local_state()->SetInteger(
406 prefs::kTabStripLayoutType
,
407 static_cast<int>(tabstrip_
->layout_type()));
410 void 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
));
423 void BrowserTabStripController::OnStoppedDraggingTabs() {
424 immersive_reveal_lock_
.reset();
427 void BrowserTabStripController::CheckFileSupported(const GURL
& url
) {
428 base::PostTaskAndReplyWithResult(
429 content::BrowserThread::GetBlockingPool(),
431 base::Bind(&FindURLMimeType
, url
),
432 base::Bind(&BrowserTabStripController::OnFindURLMimeTypeCompleted
,
433 weak_ptr_factory_
.GetWeakPtr(),
437 ////////////////////////////////////////////////////////////////////////////////
438 // BrowserTabStripController, TabStripModelObserver implementation:
440 void BrowserTabStripController::TabInsertedAt(WebContents
* contents
,
444 DCHECK(model_
->ContainsIndex(model_index
));
445 AddTab(contents
, model_index
, is_active
);
448 void BrowserTabStripController::TabDetachedAt(WebContents
* contents
,
450 // Cancel any pending tab transition.
451 hover_tab_selector_
.CancelTabTransition();
453 tabstrip_
->RemoveTabAt(model_index
);
456 void BrowserTabStripController::TabSelectionChanged(
457 TabStripModel
* tab_strip_model
,
458 const ui::ListSelectionModel
& old_model
) {
459 tabstrip_
->SetSelection(old_model
, model_
->selection_model());
462 void 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();
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
);
474 void BrowserTabStripController::TabChangedAt(WebContents
* contents
,
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.
483 SetTabDataAt(contents
, model_index
);
486 void BrowserTabStripController::TabReplacedAt(TabStripModel
* tab_strip_model
,
487 WebContents
* old_contents
,
488 WebContents
* new_contents
,
490 SetTabDataAt(new_contents
, model_index
);
493 void BrowserTabStripController::TabPinnedStateChanged(WebContents
* contents
,
495 // Currently none of the renderers render pinned state differently.
498 void BrowserTabStripController::TabMiniStateChanged(WebContents
* contents
,
500 SetTabDataAt(contents
, model_index
);
503 void BrowserTabStripController::TabBlockedStateChanged(WebContents
* contents
,
505 SetTabDataAt(contents
, model_index
);
508 void BrowserTabStripController::SetTabRendererDataFromModel(
509 WebContents
* contents
,
511 TabRendererData
* data
,
512 TabStatus tab_status
) {
513 FaviconTabHelper
* favicon_tab_helper
=
514 FaviconTabHelper::FromWebContents(contents
);
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
->show_icon
= favicon_tab_helper
->ShouldDisplayFavicon();
524 data
->mini
= model_
->IsMiniTab(model_index
);
525 data
->blocked
= model_
->IsTabBlocked(model_index
);
526 data
->app
= extensions::TabHelper::FromWebContents(contents
)->is_app();
527 data
->media_state
= chrome::GetTabMediaStateForContents(contents
);
530 void BrowserTabStripController::SetTabDataAt(content::WebContents
* web_contents
,
532 TabRendererData data
;
533 SetTabRendererDataFromModel(web_contents
, model_index
, &data
, EXISTING_TAB
);
534 tabstrip_
->SetTabData(model_index
, data
);
537 void BrowserTabStripController::StartHighlightTabsForCommand(
538 TabStripModel::ContextMenuCommand command_id
,
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
);
554 void BrowserTabStripController::StopHighlightTabsForCommand(
555 TabStripModel::ContextMenuCommand command_id
,
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();
564 void BrowserTabStripController::AddTab(WebContents
* contents
,
567 // Cancel any pending tab transition.
568 hover_tab_selector_
.CancelTabTransition();
570 TabRendererData data
;
571 SetTabRendererDataFromModel(contents
, index
, &data
, NEW_TAB
);
572 tabstrip_
->AddTabAt(index
, data
, is_active
);
575 void 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
);
583 void BrowserTabStripController::OnFindURLMimeTypeCompleted(
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(
594 net::IsSupportedMimeType(mime_type
) ||
595 content::PluginService::GetInstance()->GetPluginInfo(
597 MSG_ROUTING_NONE
, // routing ID
598 model_
->profile()->GetResourceContext(),
599 url
, GURL(), mime_type
, false,
600 NULL
, &plugin
, NULL
));