Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / views / tabs / browser_tab_strip_controller.cc
blobd0ec6160802afa67493bebaf4322dba2860af694
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/prefs/pref_service.h"
9 #include "base/task_runner_util.h"
10 #include "base/threading/sequenced_worker_pool.h"
11 #include "chrome/browser/autocomplete/autocomplete_classifier_factory.h"
12 #include "chrome/browser/browser_process.h"
13 #include "chrome/browser/chrome_notification_types.h"
14 #include "chrome/browser/extensions/tab_helper.h"
15 #include "chrome/browser/favicon/favicon_utils.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/search/search.h"
18 #include "chrome/browser/ui/browser.h"
19 #include "chrome/browser/ui/browser_tabstrip.h"
20 #include "chrome/browser/ui/tabs/tab_menu_model.h"
21 #include "chrome/browser/ui/tabs/tab_strip_model.h"
22 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
23 #include "chrome/browser/ui/tabs/tab_utils.h"
24 #include "chrome/browser/ui/views/frame/browser_view.h"
25 #include "chrome/browser/ui/views/tabs/tab.h"
26 #include "chrome/browser/ui/views/tabs/tab_renderer_data.h"
27 #include "chrome/browser/ui/views/tabs/tab_strip.h"
28 #include "chrome/common/pref_names.h"
29 #include "chrome/common/url_constants.h"
30 #include "components/favicon/content/content_favicon_driver.h"
31 #include "components/metrics/proto/omnibox_event.pb.h"
32 #include "components/mime_util/mime_util.h"
33 #include "components/omnibox/browser/autocomplete_classifier.h"
34 #include "components/omnibox/browser/autocomplete_match.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->IsLoadingToDifferentDocument())
56 return TabRendererData::NETWORK_STATE_NONE;
57 if (contents->IsWaitingForResponse())
58 return TabRendererData::NETWORK_STATE_WAITING;
59 return TabRendererData::NETWORK_STATE_LOADING;
62 bool DetermineTabStripLayoutStacked(
63 PrefService* prefs,
64 chrome::HostDesktopType host_desktop_type,
65 bool* adjust_layout) {
66 *adjust_layout = false;
67 // For ash, always allow entering stacked mode.
68 if (host_desktop_type != chrome::HOST_DESKTOP_TYPE_ASH)
69 return false;
70 *adjust_layout = true;
71 return prefs->GetBoolean(prefs::kTabStripStackedLayout);
74 // Get the MIME type of the file pointed to by the url, based on the file's
75 // extension. Must be called on a thread that allows IO.
76 std::string FindURLMimeType(const GURL& url) {
77 DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
78 base::FilePath full_path;
79 net::FileURLToFilePath(url, &full_path);
81 // Get the MIME type based on the filename.
82 std::string mime_type;
83 net::GetMimeTypeFromFile(full_path, &mime_type);
85 return mime_type;
88 } // namespace
90 class BrowserTabStripController::TabContextMenuContents
91 : public ui::SimpleMenuModel::Delegate {
92 public:
93 TabContextMenuContents(Tab* tab,
94 BrowserTabStripController* controller)
95 : tab_(tab),
96 controller_(controller),
97 last_command_(TabStripModel::CommandFirst) {
98 model_.reset(new TabMenuModel(
99 this, controller->model_,
100 controller->tabstrip_->GetModelIndexOfTab(tab)));
101 menu_runner_.reset(new views::MenuRunner(
102 model_.get(),
103 views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU));
106 ~TabContextMenuContents() override {
107 if (controller_)
108 controller_->tabstrip_->StopAllHighlighting();
111 void Cancel() {
112 controller_ = NULL;
115 void RunMenuAt(const gfx::Point& point, ui::MenuSourceType source_type) {
116 if (menu_runner_->RunMenuAt(tab_->GetWidget(),
117 NULL,
118 gfx::Rect(point, gfx::Size()),
119 views::MENU_ANCHOR_TOPLEFT,
120 source_type) ==
121 views::MenuRunner::MENU_DELETED) {
122 return;
126 // Overridden from ui::SimpleMenuModel::Delegate:
127 bool IsCommandIdChecked(int command_id) const override { return false; }
128 bool IsCommandIdEnabled(int command_id) const override {
129 return controller_->IsCommandEnabledForTab(
130 static_cast<TabStripModel::ContextMenuCommand>(command_id),
131 tab_);
133 bool GetAcceleratorForCommandId(int command_id,
134 ui::Accelerator* accelerator) override {
135 int browser_cmd;
136 return TabStripModel::ContextMenuCommandToBrowserCommand(command_id,
137 &browser_cmd) ?
138 controller_->tabstrip_->GetWidget()->GetAccelerator(browser_cmd,
139 accelerator) :
140 false;
142 void CommandIdHighlighted(int command_id) override {
143 controller_->StopHighlightTabsForCommand(last_command_, tab_);
144 last_command_ = static_cast<TabStripModel::ContextMenuCommand>(command_id);
145 controller_->StartHighlightTabsForCommand(last_command_, tab_);
147 void ExecuteCommand(int command_id, int event_flags) override {
148 // Executing the command destroys |this|, and can also end up destroying
149 // |controller_|. So stop the highlights before executing the command.
150 controller_->tabstrip_->StopAllHighlighting();
151 controller_->ExecuteCommandForTab(
152 static_cast<TabStripModel::ContextMenuCommand>(command_id),
153 tab_);
156 void MenuClosed(ui::SimpleMenuModel* /*source*/) override {
157 if (controller_)
158 controller_->tabstrip_->StopAllHighlighting();
161 private:
162 scoped_ptr<TabMenuModel> model_;
163 scoped_ptr<views::MenuRunner> menu_runner_;
165 // The tab we're showing a menu for.
166 Tab* tab_;
168 // A pointer back to our hosting controller, for command state information.
169 BrowserTabStripController* controller_;
171 // The last command that was selected, so that we can start/stop highlighting
172 // appropriately as the user moves through the menu.
173 TabStripModel::ContextMenuCommand last_command_;
175 DISALLOW_COPY_AND_ASSIGN(TabContextMenuContents);
178 ////////////////////////////////////////////////////////////////////////////////
179 // BrowserTabStripController, public:
181 BrowserTabStripController::BrowserTabStripController(Browser* browser,
182 TabStripModel* model)
183 : model_(model),
184 tabstrip_(NULL),
185 browser_(browser),
186 hover_tab_selector_(model),
187 weak_ptr_factory_(this) {
188 model_->AddObserver(this);
190 local_pref_registrar_.Init(g_browser_process->local_state());
191 local_pref_registrar_.Add(
192 prefs::kTabStripStackedLayout,
193 base::Bind(&BrowserTabStripController::UpdateStackedLayout,
194 base::Unretained(this)));
197 BrowserTabStripController::~BrowserTabStripController() {
198 // When we get here the TabStrip is being deleted. We need to explicitly
199 // cancel the menu, otherwise it may try to invoke something on the tabstrip
200 // from its destructor.
201 if (context_menu_contents_.get())
202 context_menu_contents_->Cancel();
204 model_->RemoveObserver(this);
207 void BrowserTabStripController::InitFromModel(TabStrip* tabstrip) {
208 tabstrip_ = tabstrip;
210 UpdateStackedLayout();
212 // Walk the model, calling our insertion observer method for each item within
213 // it.
214 for (int i = 0; i < model_->count(); ++i)
215 AddTab(model_->GetWebContentsAt(i), i, model_->active_index() == i);
218 bool BrowserTabStripController::IsCommandEnabledForTab(
219 TabStripModel::ContextMenuCommand command_id,
220 Tab* tab) const {
221 int model_index = tabstrip_->GetModelIndexOfTab(tab);
222 return model_->ContainsIndex(model_index) ?
223 model_->IsContextMenuCommandEnabled(model_index, command_id) : false;
226 void BrowserTabStripController::ExecuteCommandForTab(
227 TabStripModel::ContextMenuCommand command_id,
228 Tab* tab) {
229 int model_index = tabstrip_->GetModelIndexOfTab(tab);
230 if (model_->ContainsIndex(model_index))
231 model_->ExecuteContextMenuCommand(model_index, command_id);
234 bool BrowserTabStripController::IsTabPinned(Tab* tab) const {
235 return IsTabPinned(tabstrip_->GetModelIndexOfTab(tab));
238 const ui::ListSelectionModel& BrowserTabStripController::GetSelectionModel() {
239 return model_->selection_model();
242 int BrowserTabStripController::GetCount() const {
243 return model_->count();
246 bool BrowserTabStripController::IsValidIndex(int index) const {
247 return model_->ContainsIndex(index);
250 bool BrowserTabStripController::IsActiveTab(int model_index) const {
251 return model_->active_index() == model_index;
254 int BrowserTabStripController::GetActiveIndex() const {
255 return model_->active_index();
258 bool BrowserTabStripController::IsTabSelected(int model_index) const {
259 return model_->IsTabSelected(model_index);
262 bool BrowserTabStripController::IsTabPinned(int model_index) const {
263 return model_->ContainsIndex(model_index) && model_->IsTabPinned(model_index);
266 bool BrowserTabStripController::IsNewTabPage(int model_index) const {
267 if (!model_->ContainsIndex(model_index))
268 return false;
270 const WebContents* contents = model_->GetWebContentsAt(model_index);
271 return contents && (contents->GetURL() == GURL(chrome::kChromeUINewTabURL) ||
272 search::IsInstantNTP(contents));
275 void BrowserTabStripController::SelectTab(int model_index) {
276 model_->ActivateTabAt(model_index, true);
279 void BrowserTabStripController::ExtendSelectionTo(int model_index) {
280 model_->ExtendSelectionTo(model_index);
283 void BrowserTabStripController::ToggleSelected(int model_index) {
284 model_->ToggleSelectionAt(model_index);
287 void BrowserTabStripController::AddSelectionFromAnchorTo(int model_index) {
288 model_->AddSelectionFromAnchorTo(model_index);
291 void BrowserTabStripController::CloseTab(int model_index,
292 CloseTabSource source) {
293 // Cancel any pending tab transition.
294 hover_tab_selector_.CancelTabTransition();
296 tabstrip_->PrepareForCloseAt(model_index, source);
297 model_->CloseWebContentsAt(model_index,
298 TabStripModel::CLOSE_USER_GESTURE |
299 TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
302 void BrowserTabStripController::ToggleTabAudioMute(int model_index) {
303 content::WebContents* const contents = model_->GetWebContentsAt(model_index);
304 chrome::SetTabAudioMuted(contents, !contents->IsAudioMuted(),
305 TAB_MUTED_REASON_AUDIO_INDICATOR, std::string());
308 void BrowserTabStripController::ShowContextMenuForTab(
309 Tab* tab,
310 const gfx::Point& p,
311 ui::MenuSourceType source_type) {
312 context_menu_contents_.reset(new TabContextMenuContents(tab, this));
313 context_menu_contents_->RunMenuAt(p, source_type);
316 void BrowserTabStripController::UpdateLoadingAnimations() {
317 // Don't use the model count here as it's possible for this to be invoked
318 // before we've applied an update from the model (Browser::TabInsertedAt may
319 // be processed before us and invokes this).
320 for (int i = 0, tab_count = tabstrip_->tab_count(); i < tab_count; ++i) {
321 if (model_->ContainsIndex(i)) {
322 Tab* tab = tabstrip_->tab_at(i);
323 WebContents* contents = model_->GetWebContentsAt(i);
324 tab->UpdateLoadingAnimation(TabContentsNetworkState(contents));
329 int BrowserTabStripController::HasAvailableDragActions() const {
330 return model_->delegate()->GetDragActions();
333 void BrowserTabStripController::OnDropIndexUpdate(int index,
334 bool drop_before) {
335 // Perform a delayed tab transition if hovering directly over a tab.
336 // Otherwise, cancel the pending one.
337 if (index != -1 && !drop_before) {
338 hover_tab_selector_.StartTabTransition(index);
339 } else {
340 hover_tab_selector_.CancelTabTransition();
344 void BrowserTabStripController::PerformDrop(bool drop_before,
345 int index,
346 const GURL& url) {
347 chrome::NavigateParams params(browser_, url, ui::PAGE_TRANSITION_LINK);
348 params.tabstrip_index = index;
350 if (drop_before) {
351 content::RecordAction(UserMetricsAction("Tab_DropURLBetweenTabs"));
352 params.disposition = NEW_FOREGROUND_TAB;
353 } else {
354 content::RecordAction(UserMetricsAction("Tab_DropURLOnTab"));
355 params.disposition = CURRENT_TAB;
356 params.source_contents = model_->GetWebContentsAt(index);
358 params.window_action = chrome::NavigateParams::SHOW_WINDOW;
359 chrome::Navigate(&params);
362 bool BrowserTabStripController::IsCompatibleWith(TabStrip* other) const {
363 Profile* other_profile =
364 static_cast<BrowserTabStripController*>(other->controller())->profile();
365 return other_profile == profile();
368 void BrowserTabStripController::CreateNewTab() {
369 model_->delegate()->AddTabAt(GURL(), -1, true);
372 void BrowserTabStripController::CreateNewTabWithLocation(
373 const base::string16& location) {
374 // Use autocomplete to clean up the text, going so far as to turn it into
375 // a search query if necessary.
376 AutocompleteMatch match;
377 AutocompleteClassifierFactory::GetForProfile(profile())->Classify(
378 location, false, false, metrics::OmniboxEventProto::BLANK, &match, NULL);
379 if (match.destination_url.is_valid())
380 model_->delegate()->AddTabAt(match.destination_url, -1, true);
383 bool BrowserTabStripController::IsIncognito() {
384 return browser_->profile()->IsOffTheRecord();
387 void BrowserTabStripController::StackedLayoutMaybeChanged() {
388 bool adjust_layout = false;
389 bool stacked_layout =
390 DetermineTabStripLayoutStacked(g_browser_process->local_state(),
391 browser_->host_desktop_type(),
392 &adjust_layout);
393 if (!adjust_layout || stacked_layout == tabstrip_->stacked_layout())
394 return;
396 g_browser_process->local_state()->SetBoolean(prefs::kTabStripStackedLayout,
397 tabstrip_->stacked_layout());
400 void BrowserTabStripController::OnStartedDraggingTabs() {
401 BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser_);
402 if (browser_view && !immersive_reveal_lock_.get()) {
403 // The top-of-window views should be revealed while the user is dragging
404 // tabs in immersive fullscreen. The top-of-window views may not be already
405 // revealed if the user is attempting to attach a tab to a tabstrip
406 // belonging to an immersive fullscreen window.
407 immersive_reveal_lock_.reset(
408 browser_view->immersive_mode_controller()->GetRevealedLock(
409 ImmersiveModeController::ANIMATE_REVEAL_NO));
413 void BrowserTabStripController::OnStoppedDraggingTabs() {
414 immersive_reveal_lock_.reset();
417 void BrowserTabStripController::CheckFileSupported(const GURL& url) {
418 base::PostTaskAndReplyWithResult(
419 content::BrowserThread::GetBlockingPool(),
420 FROM_HERE,
421 base::Bind(&FindURLMimeType, url),
422 base::Bind(&BrowserTabStripController::OnFindURLMimeTypeCompleted,
423 weak_ptr_factory_.GetWeakPtr(),
424 url));
427 ////////////////////////////////////////////////////////////////////////////////
428 // BrowserTabStripController, TabStripModelObserver implementation:
430 void BrowserTabStripController::TabInsertedAt(WebContents* contents,
431 int model_index,
432 bool is_active) {
433 DCHECK(contents);
434 DCHECK(model_->ContainsIndex(model_index));
435 AddTab(contents, model_index, is_active);
438 void BrowserTabStripController::TabDetachedAt(WebContents* contents,
439 int model_index) {
440 // Cancel any pending tab transition.
441 hover_tab_selector_.CancelTabTransition();
443 tabstrip_->RemoveTabAt(model_index);
446 void BrowserTabStripController::TabSelectionChanged(
447 TabStripModel* tab_strip_model,
448 const ui::ListSelectionModel& old_model) {
449 tabstrip_->SetSelection(old_model, model_->selection_model());
452 void BrowserTabStripController::TabMoved(WebContents* contents,
453 int from_model_index,
454 int to_model_index) {
455 // Cancel any pending tab transition.
456 hover_tab_selector_.CancelTabTransition();
458 // Pass in the TabRendererData as the pinned state may have changed.
459 TabRendererData data;
460 SetTabRendererDataFromModel(contents, to_model_index, &data, EXISTING_TAB);
461 tabstrip_->MoveTab(from_model_index, to_model_index, data);
464 void BrowserTabStripController::TabChangedAt(WebContents* contents,
465 int model_index,
466 TabChangeType change_type) {
467 if (change_type == TITLE_NOT_LOADING) {
468 tabstrip_->TabTitleChangedNotLoading(model_index);
469 // We'll receive another notification of the change asynchronously.
470 return;
473 SetTabDataAt(contents, model_index);
476 void BrowserTabStripController::TabReplacedAt(TabStripModel* tab_strip_model,
477 WebContents* old_contents,
478 WebContents* new_contents,
479 int model_index) {
480 SetTabDataAt(new_contents, model_index);
483 void BrowserTabStripController::TabPinnedStateChanged(WebContents* contents,
484 int model_index) {
485 SetTabDataAt(contents, model_index);
488 void BrowserTabStripController::TabBlockedStateChanged(WebContents* contents,
489 int model_index) {
490 SetTabDataAt(contents, model_index);
493 void BrowserTabStripController::SetTabRendererDataFromModel(
494 WebContents* contents,
495 int model_index,
496 TabRendererData* data,
497 TabStatus tab_status) {
498 favicon::FaviconDriver* favicon_driver =
499 favicon::ContentFaviconDriver::FromWebContents(contents);
501 data->favicon = favicon_driver->GetFavicon().AsImageSkia();
502 data->network_state = TabContentsNetworkState(contents);
503 data->title = contents->GetTitle();
504 data->url = contents->GetURL();
505 data->loading = contents->IsLoading();
506 data->crashed_status = contents->GetCrashedStatus();
507 data->incognito = contents->GetBrowserContext()->IsOffTheRecord();
508 data->pinned = model_->IsTabPinned(model_index);
509 data->show_icon = data->pinned || favicon::ShouldDisplayFavicon(contents);
510 data->blocked = model_->IsTabBlocked(model_index);
511 data->app = extensions::TabHelper::FromWebContents(contents)->is_app();
512 data->media_state = chrome::GetTabMediaStateForContents(contents);
515 void BrowserTabStripController::SetTabDataAt(content::WebContents* web_contents,
516 int model_index) {
517 TabRendererData data;
518 SetTabRendererDataFromModel(web_contents, model_index, &data, EXISTING_TAB);
519 tabstrip_->SetTabData(model_index, data);
522 void BrowserTabStripController::StartHighlightTabsForCommand(
523 TabStripModel::ContextMenuCommand command_id,
524 Tab* tab) {
525 if (command_id == TabStripModel::CommandCloseOtherTabs ||
526 command_id == TabStripModel::CommandCloseTabsToRight) {
527 int model_index = tabstrip_->GetModelIndexOfTab(tab);
528 if (IsValidIndex(model_index)) {
529 std::vector<int> indices =
530 model_->GetIndicesClosedByCommand(model_index, command_id);
531 for (std::vector<int>::const_iterator i(indices.begin());
532 i != indices.end(); ++i) {
533 tabstrip_->StartHighlight(*i);
539 void BrowserTabStripController::StopHighlightTabsForCommand(
540 TabStripModel::ContextMenuCommand command_id,
541 Tab* tab) {
542 if (command_id == TabStripModel::CommandCloseTabsToRight ||
543 command_id == TabStripModel::CommandCloseOtherTabs) {
544 // Just tell all Tabs to stop pulsing - it's safe.
545 tabstrip_->StopAllHighlighting();
549 void BrowserTabStripController::AddTab(WebContents* contents,
550 int index,
551 bool is_active) {
552 // Cancel any pending tab transition.
553 hover_tab_selector_.CancelTabTransition();
555 TabRendererData data;
556 SetTabRendererDataFromModel(contents, index, &data, NEW_TAB);
557 tabstrip_->AddTabAt(index, data, is_active);
560 void BrowserTabStripController::UpdateStackedLayout() {
561 bool adjust_layout = false;
562 bool stacked_layout =
563 DetermineTabStripLayoutStacked(g_browser_process->local_state(),
564 browser_->host_desktop_type(),
565 &adjust_layout);
566 tabstrip_->set_adjust_layout(adjust_layout);
567 tabstrip_->SetStackedLayout(stacked_layout);
570 void BrowserTabStripController::OnFindURLMimeTypeCompleted(
571 const GURL& url,
572 const std::string& mime_type) {
573 // Check whether the mime type, if given, is known to be supported or whether
574 // there is a plugin that supports the mime type (e.g. PDF).
575 // TODO(bauerb): This possibly uses stale information, but it's guaranteed not
576 // to do disk access.
577 content::WebPluginInfo plugin;
578 tabstrip_->FileSupported(
579 url,
580 mime_type.empty() || mime_util::IsSupportedMimeType(mime_type) ||
581 content::PluginService::GetInstance()->GetPluginInfo(
582 -1, // process ID
583 MSG_ROUTING_NONE, // routing ID
584 model_->profile()->GetResourceContext(), url, GURL(), mime_type,
585 false, NULL, &plugin, NULL));