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