Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / views / tabs / browser_tab_strip_controller.cc
blob666104484d86a5710bc5545ba8b21e04a3450f41
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_match.h"
15 #include "chrome/browser/browser_process.h"
16 #include "chrome/browser/chrome_notification_types.h"
17 #include "chrome/browser/extensions/tab_helper.h"
18 #include "chrome/browser/favicon/favicon_tab_helper.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/search/search.h"
21 #include "chrome/browser/ui/browser.h"
22 #include "chrome/browser/ui/browser_tabstrip.h"
23 #include "chrome/browser/ui/tabs/tab_menu_model.h"
24 #include "chrome/browser/ui/tabs/tab_strip_model.h"
25 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
26 #include "chrome/browser/ui/tabs/tab_utils.h"
27 #include "chrome/browser/ui/views/frame/browser_view.h"
28 #include "chrome/browser/ui/views/tabs/tab.h"
29 #include "chrome/browser/ui/views/tabs/tab_renderer_data.h"
30 #include "chrome/browser/ui/views/tabs/tab_strip.h"
31 #include "chrome/common/chrome_switches.h"
32 #include "chrome/common/pref_names.h"
33 #include "chrome/common/url_constants.h"
34 #include "content/public/browser/browser_thread.h"
35 #include "content/public/browser/notification_service.h"
36 #include "content/public/browser/plugin_service.h"
37 #include "content/public/browser/user_metrics.h"
38 #include "content/public/browser/web_contents.h"
39 #include "content/public/common/webplugininfo.h"
40 #include "ipc/ipc_message.h"
41 #include "net/base/net_util.h"
42 #include "ui/base/layout.h"
43 #include "ui/base/models/list_selection_model.h"
44 #include "ui/gfx/image/image.h"
45 #include "ui/views/controls/menu/menu_item_view.h"
46 #include "ui/views/controls/menu/menu_runner.h"
47 #include "ui/views/widget/widget.h"
49 using base::UserMetricsAction;
50 using content::WebContents;
52 namespace {
54 TabRendererData::NetworkState TabContentsNetworkState(
55 WebContents* contents) {
56 if (!contents || !contents->IsLoading())
57 return TabRendererData::NETWORK_STATE_NONE;
58 if (contents->IsWaitingForResponse())
59 return TabRendererData::NETWORK_STATE_WAITING;
60 return TabRendererData::NETWORK_STATE_LOADING;
63 TabStripLayoutType DetermineTabStripLayout(
64 PrefService* prefs,
65 chrome::HostDesktopType host_desktop_type,
66 bool* adjust_layout) {
67 *adjust_layout = false;
68 if (CommandLine::ForCurrentProcess()->HasSwitch(
69 switches::kEnableStackedTabStrip)) {
70 return TAB_STRIP_LAYOUT_STACKED;
72 // For chromeos always allow entering stacked mode.
73 #if defined(USE_AURA)
74 if (host_desktop_type != chrome::HOST_DESKTOP_TYPE_ASH)
75 return TAB_STRIP_LAYOUT_SHRINK;
76 #else
77 if (ui::GetDisplayLayout() != ui::LAYOUT_TOUCH)
78 return TAB_STRIP_LAYOUT_SHRINK;
79 #endif
80 *adjust_layout = true;
81 switch (prefs->GetInteger(prefs::kTabStripLayoutType)) {
82 case TAB_STRIP_LAYOUT_STACKED:
83 return TAB_STRIP_LAYOUT_STACKED;
84 default:
85 return TAB_STRIP_LAYOUT_SHRINK;
89 // Get the MIME type of the file pointed to by the url, based on the file's
90 // extension. Must be called on a thread that allows IO.
91 std::string FindURLMimeType(const GURL& url) {
92 DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
93 base::FilePath full_path;
94 net::FileURLToFilePath(url, &full_path);
96 // Get the MIME type based on the filename.
97 std::string mime_type;
98 net::GetMimeTypeFromFile(full_path, &mime_type);
100 return mime_type;
103 } // namespace
105 class BrowserTabStripController::TabContextMenuContents
106 : public ui::SimpleMenuModel::Delegate {
107 public:
108 TabContextMenuContents(Tab* tab,
109 BrowserTabStripController* controller)
110 : tab_(tab),
111 controller_(controller),
112 last_command_(TabStripModel::CommandFirst) {
113 model_.reset(new TabMenuModel(
114 this, controller->model_,
115 controller->tabstrip_->GetModelIndexOfTab(tab)));
116 menu_runner_.reset(new views::MenuRunner(model_.get()));
119 virtual ~TabContextMenuContents() {
120 if (controller_)
121 controller_->tabstrip_->StopAllHighlighting();
124 void Cancel() {
125 controller_ = NULL;
128 void RunMenuAt(const gfx::Point& point, ui::MenuSourceType source_type) {
129 if (menu_runner_->RunMenuAt(
130 tab_->GetWidget(), NULL, gfx::Rect(point, gfx::Size()),
131 views::MenuItemView::TOPLEFT, source_type,
132 views::MenuRunner::HAS_MNEMONICS |
133 views::MenuRunner::CONTEXT_MENU) ==
134 views::MenuRunner::MENU_DELETED)
135 return;
138 // Overridden from ui::SimpleMenuModel::Delegate:
139 virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
140 return false;
142 virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
143 return controller_->IsCommandEnabledForTab(
144 static_cast<TabStripModel::ContextMenuCommand>(command_id),
145 tab_);
147 virtual bool GetAcceleratorForCommandId(
148 int command_id,
149 ui::Accelerator* accelerator) OVERRIDE {
150 int browser_cmd;
151 return TabStripModel::ContextMenuCommandToBrowserCommand(command_id,
152 &browser_cmd) ?
153 controller_->tabstrip_->GetWidget()->GetAccelerator(browser_cmd,
154 accelerator) :
155 false;
157 virtual void CommandIdHighlighted(int command_id) OVERRIDE {
158 controller_->StopHighlightTabsForCommand(last_command_, tab_);
159 last_command_ = static_cast<TabStripModel::ContextMenuCommand>(command_id);
160 controller_->StartHighlightTabsForCommand(last_command_, tab_);
162 virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE {
163 // Executing the command destroys |this|, and can also end up destroying
164 // |controller_|. So stop the highlights before executing the command.
165 controller_->tabstrip_->StopAllHighlighting();
166 controller_->ExecuteCommandForTab(
167 static_cast<TabStripModel::ContextMenuCommand>(command_id),
168 tab_);
171 virtual void MenuClosed(ui::SimpleMenuModel* /*source*/) OVERRIDE {
172 if (controller_)
173 controller_->tabstrip_->StopAllHighlighting();
176 private:
177 scoped_ptr<TabMenuModel> model_;
178 scoped_ptr<views::MenuRunner> menu_runner_;
180 // The tab we're showing a menu for.
181 Tab* tab_;
183 // A pointer back to our hosting controller, for command state information.
184 BrowserTabStripController* controller_;
186 // The last command that was selected, so that we can start/stop highlighting
187 // appropriately as the user moves through the menu.
188 TabStripModel::ContextMenuCommand last_command_;
190 DISALLOW_COPY_AND_ASSIGN(TabContextMenuContents);
193 ////////////////////////////////////////////////////////////////////////////////
194 // BrowserTabStripController, public:
196 BrowserTabStripController::BrowserTabStripController(Browser* browser,
197 TabStripModel* model)
198 : model_(model),
199 tabstrip_(NULL),
200 browser_(browser),
201 hover_tab_selector_(model),
202 weak_ptr_factory_(this) {
203 model_->AddObserver(this);
205 local_pref_registrar_.Init(g_browser_process->local_state());
206 local_pref_registrar_.Add(
207 prefs::kTabStripLayoutType,
208 base::Bind(&BrowserTabStripController::UpdateLayoutType,
209 base::Unretained(this)));
212 BrowserTabStripController::~BrowserTabStripController() {
213 // When we get here the TabStrip is being deleted. We need to explicitly
214 // cancel the menu, otherwise it may try to invoke something on the tabstrip
215 // from its destructor.
216 if (context_menu_contents_.get())
217 context_menu_contents_->Cancel();
219 model_->RemoveObserver(this);
222 void BrowserTabStripController::InitFromModel(TabStrip* tabstrip) {
223 tabstrip_ = tabstrip;
225 UpdateLayoutType();
227 // Walk the model, calling our insertion observer method for each item within
228 // it.
229 for (int i = 0; i < model_->count(); ++i)
230 AddTab(model_->GetWebContentsAt(i), i, model_->active_index() == i);
233 bool BrowserTabStripController::IsCommandEnabledForTab(
234 TabStripModel::ContextMenuCommand command_id,
235 Tab* tab) const {
236 int model_index = tabstrip_->GetModelIndexOfTab(tab);
237 return model_->ContainsIndex(model_index) ?
238 model_->IsContextMenuCommandEnabled(model_index, command_id) : false;
241 void BrowserTabStripController::ExecuteCommandForTab(
242 TabStripModel::ContextMenuCommand command_id,
243 Tab* tab) {
244 int model_index = tabstrip_->GetModelIndexOfTab(tab);
245 if (model_->ContainsIndex(model_index))
246 model_->ExecuteContextMenuCommand(model_index, command_id);
249 bool BrowserTabStripController::IsTabPinned(Tab* tab) const {
250 return IsTabPinned(tabstrip_->GetModelIndexOfTab(tab));
253 const ui::ListSelectionModel& BrowserTabStripController::GetSelectionModel() {
254 return model_->selection_model();
257 int BrowserTabStripController::GetCount() const {
258 return model_->count();
261 bool BrowserTabStripController::IsValidIndex(int index) const {
262 return model_->ContainsIndex(index);
265 bool BrowserTabStripController::IsActiveTab(int model_index) const {
266 return model_->active_index() == model_index;
269 int BrowserTabStripController::GetActiveIndex() const {
270 return model_->active_index();
273 bool BrowserTabStripController::IsTabSelected(int model_index) const {
274 return model_->IsTabSelected(model_index);
277 bool BrowserTabStripController::IsTabPinned(int model_index) const {
278 return model_->ContainsIndex(model_index) && model_->IsTabPinned(model_index);
281 bool BrowserTabStripController::IsNewTabPage(int model_index) const {
282 if (!model_->ContainsIndex(model_index))
283 return false;
285 const WebContents* contents = model_->GetWebContentsAt(model_index);
286 return contents && (contents->GetURL() == GURL(chrome::kChromeUINewTabURL) ||
287 chrome::IsInstantNTP(contents));
290 void BrowserTabStripController::SelectTab(int model_index) {
291 model_->ActivateTabAt(model_index, true);
294 void BrowserTabStripController::ExtendSelectionTo(int model_index) {
295 model_->ExtendSelectionTo(model_index);
298 void BrowserTabStripController::ToggleSelected(int model_index) {
299 model_->ToggleSelectionAt(model_index);
302 void BrowserTabStripController::AddSelectionFromAnchorTo(int model_index) {
303 model_->AddSelectionFromAnchorTo(model_index);
306 void BrowserTabStripController::CloseTab(int model_index,
307 CloseTabSource source) {
308 // Cancel any pending tab transition.
309 hover_tab_selector_.CancelTabTransition();
311 tabstrip_->PrepareForCloseAt(model_index, source);
312 model_->CloseWebContentsAt(model_index,
313 TabStripModel::CLOSE_USER_GESTURE |
314 TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
317 void BrowserTabStripController::ShowContextMenuForTab(
318 Tab* tab,
319 const gfx::Point& p,
320 ui::MenuSourceType source_type) {
321 context_menu_contents_.reset(new TabContextMenuContents(tab, this));
322 context_menu_contents_->RunMenuAt(p, source_type);
325 void BrowserTabStripController::UpdateLoadingAnimations() {
326 // Don't use the model count here as it's possible for this to be invoked
327 // before we've applied an update from the model (Browser::TabInsertedAt may
328 // be processed before us and invokes this).
329 for (int i = 0, tab_count = tabstrip_->tab_count(); i < tab_count; ++i) {
330 if (model_->ContainsIndex(i)) {
331 Tab* tab = tabstrip_->tab_at(i);
332 WebContents* contents = model_->GetWebContentsAt(i);
333 tab->UpdateLoadingAnimation(TabContentsNetworkState(contents));
338 int BrowserTabStripController::HasAvailableDragActions() const {
339 return model_->delegate()->GetDragActions();
342 void BrowserTabStripController::OnDropIndexUpdate(int index,
343 bool drop_before) {
344 // Perform a delayed tab transition if hovering directly over a tab.
345 // Otherwise, cancel the pending one.
346 if (index != -1 && !drop_before) {
347 hover_tab_selector_.StartTabTransition(index);
348 } else {
349 hover_tab_selector_.CancelTabTransition();
353 void BrowserTabStripController::PerformDrop(bool drop_before,
354 int index,
355 const GURL& url) {
356 chrome::NavigateParams params(browser_, url, content::PAGE_TRANSITION_LINK);
357 params.tabstrip_index = index;
359 if (drop_before) {
360 content::RecordAction(UserMetricsAction("Tab_DropURLBetweenTabs"));
361 params.disposition = NEW_FOREGROUND_TAB;
362 } else {
363 content::RecordAction(UserMetricsAction("Tab_DropURLOnTab"));
364 params.disposition = CURRENT_TAB;
365 params.source_contents = model_->GetWebContentsAt(index);
367 params.window_action = chrome::NavigateParams::SHOW_WINDOW;
368 chrome::Navigate(&params);
371 bool BrowserTabStripController::IsCompatibleWith(TabStrip* other) const {
372 Profile* other_profile =
373 static_cast<BrowserTabStripController*>(other->controller())->profile();
374 return other_profile == profile();
377 void BrowserTabStripController::CreateNewTab() {
378 model_->delegate()->AddTabAt(GURL(), -1, true);
381 void BrowserTabStripController::CreateNewTabWithLocation(
382 const base::string16& location) {
383 // Use autocomplete to clean up the text, going so far as to turn it into
384 // a search query if necessary.
385 AutocompleteMatch match;
386 AutocompleteClassifierFactory::GetForProfile(profile())->Classify(
387 location, false, false, &match, NULL);
388 if (match.destination_url.is_valid())
389 model_->delegate()->AddTabAt(match.destination_url, -1, true);
392 bool BrowserTabStripController::IsIncognito() {
393 return browser_->profile()->IsOffTheRecord();
396 void BrowserTabStripController::LayoutTypeMaybeChanged() {
397 bool adjust_layout = false;
398 TabStripLayoutType layout_type =
399 DetermineTabStripLayout(g_browser_process->local_state(),
400 browser_->host_desktop_type(), &adjust_layout);
401 if (!adjust_layout || layout_type == tabstrip_->layout_type())
402 return;
404 g_browser_process->local_state()->SetInteger(
405 prefs::kTabStripLayoutType,
406 static_cast<int>(tabstrip_->layout_type()));
409 void BrowserTabStripController::OnStartedDraggingTabs() {
410 BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser_);
411 if (browser_view && !immersive_reveal_lock_.get()) {
412 // The top-of-window views should be revealed while the user is dragging
413 // tabs in immersive fullscreen. The top-of-window views may not be already
414 // revealed if the user is attempting to attach a tab to a tabstrip
415 // belonging to an immersive fullscreen window.
416 immersive_reveal_lock_.reset(
417 browser_view->immersive_mode_controller()->GetRevealedLock(
418 ImmersiveModeController::ANIMATE_REVEAL_NO));
422 void BrowserTabStripController::OnStoppedDraggingTabs() {
423 immersive_reveal_lock_.reset();
426 void BrowserTabStripController::CheckFileSupported(const GURL& url) {
427 base::PostTaskAndReplyWithResult(
428 content::BrowserThread::GetBlockingPool(),
429 FROM_HERE,
430 base::Bind(&FindURLMimeType, url),
431 base::Bind(&BrowserTabStripController::OnFindURLMimeTypeCompleted,
432 weak_ptr_factory_.GetWeakPtr(),
433 url));
436 ////////////////////////////////////////////////////////////////////////////////
437 // BrowserTabStripController, TabStripModelObserver implementation:
439 void BrowserTabStripController::TabInsertedAt(WebContents* contents,
440 int model_index,
441 bool is_active) {
442 DCHECK(contents);
443 DCHECK(model_->ContainsIndex(model_index));
444 AddTab(contents, model_index, is_active);
447 void BrowserTabStripController::TabDetachedAt(WebContents* contents,
448 int model_index) {
449 // Cancel any pending tab transition.
450 hover_tab_selector_.CancelTabTransition();
452 tabstrip_->RemoveTabAt(model_index);
455 void BrowserTabStripController::TabSelectionChanged(
456 TabStripModel* tab_strip_model,
457 const ui::ListSelectionModel& old_model) {
458 tabstrip_->SetSelection(old_model, model_->selection_model());
461 void BrowserTabStripController::TabMoved(WebContents* contents,
462 int from_model_index,
463 int to_model_index) {
464 // Cancel any pending tab transition.
465 hover_tab_selector_.CancelTabTransition();
467 // Pass in the TabRendererData as the pinned state may have changed.
468 TabRendererData data;
469 SetTabRendererDataFromModel(contents, to_model_index, &data, EXISTING_TAB);
470 tabstrip_->MoveTab(from_model_index, to_model_index, data);
473 void BrowserTabStripController::TabChangedAt(WebContents* contents,
474 int model_index,
475 TabChangeType change_type) {
476 if (change_type == TITLE_NOT_LOADING) {
477 tabstrip_->TabTitleChangedNotLoading(model_index);
478 // We'll receive another notification of the change asynchronously.
479 return;
482 SetTabDataAt(contents, model_index);
485 void BrowserTabStripController::TabReplacedAt(TabStripModel* tab_strip_model,
486 WebContents* old_contents,
487 WebContents* new_contents,
488 int model_index) {
489 SetTabDataAt(new_contents, model_index);
492 void BrowserTabStripController::TabPinnedStateChanged(WebContents* contents,
493 int model_index) {
494 // Currently none of the renderers render pinned state differently.
497 void BrowserTabStripController::TabMiniStateChanged(WebContents* contents,
498 int model_index) {
499 SetTabDataAt(contents, model_index);
502 void BrowserTabStripController::TabBlockedStateChanged(WebContents* contents,
503 int model_index) {
504 SetTabDataAt(contents, model_index);
507 void BrowserTabStripController::SetTabRendererDataFromModel(
508 WebContents* contents,
509 int model_index,
510 TabRendererData* data,
511 TabStatus tab_status) {
512 FaviconTabHelper* favicon_tab_helper =
513 FaviconTabHelper::FromWebContents(contents);
515 data->favicon = favicon_tab_helper->GetFavicon().AsImageSkia();
516 data->network_state = TabContentsNetworkState(contents);
517 data->title = contents->GetTitle();
518 data->url = contents->GetURL();
519 data->loading = contents->IsLoading();
520 data->crashed_status = contents->GetCrashedStatus();
521 data->incognito = contents->GetBrowserContext()->IsOffTheRecord();
522 data->show_icon = favicon_tab_helper->ShouldDisplayFavicon();
523 data->mini = model_->IsMiniTab(model_index);
524 data->blocked = model_->IsTabBlocked(model_index);
525 data->app = extensions::TabHelper::FromWebContents(contents)->is_app();
526 data->media_state = chrome::GetTabMediaStateForContents(contents);
529 void BrowserTabStripController::SetTabDataAt(content::WebContents* web_contents,
530 int model_index) {
531 TabRendererData data;
532 SetTabRendererDataFromModel(web_contents, model_index, &data, EXISTING_TAB);
533 tabstrip_->SetTabData(model_index, data);
536 void BrowserTabStripController::StartHighlightTabsForCommand(
537 TabStripModel::ContextMenuCommand command_id,
538 Tab* tab) {
539 if (command_id == TabStripModel::CommandCloseOtherTabs ||
540 command_id == TabStripModel::CommandCloseTabsToRight) {
541 int model_index = tabstrip_->GetModelIndexOfTab(tab);
542 if (IsValidIndex(model_index)) {
543 std::vector<int> indices =
544 model_->GetIndicesClosedByCommand(model_index, command_id);
545 for (std::vector<int>::const_iterator i(indices.begin());
546 i != indices.end(); ++i) {
547 tabstrip_->StartHighlight(*i);
553 void BrowserTabStripController::StopHighlightTabsForCommand(
554 TabStripModel::ContextMenuCommand command_id,
555 Tab* tab) {
556 if (command_id == TabStripModel::CommandCloseTabsToRight ||
557 command_id == TabStripModel::CommandCloseOtherTabs) {
558 // Just tell all Tabs to stop pulsing - it's safe.
559 tabstrip_->StopAllHighlighting();
563 void BrowserTabStripController::AddTab(WebContents* contents,
564 int index,
565 bool is_active) {
566 // Cancel any pending tab transition.
567 hover_tab_selector_.CancelTabTransition();
569 TabRendererData data;
570 SetTabRendererDataFromModel(contents, index, &data, NEW_TAB);
571 tabstrip_->AddTabAt(index, data, is_active);
574 void BrowserTabStripController::UpdateLayoutType() {
575 bool adjust_layout = false;
576 TabStripLayoutType layout_type =
577 DetermineTabStripLayout(g_browser_process->local_state(),
578 browser_->host_desktop_type(), &adjust_layout);
579 tabstrip_->SetLayoutType(layout_type, adjust_layout);
582 void BrowserTabStripController::OnFindURLMimeTypeCompleted(
583 const GURL& url,
584 const std::string& mime_type) {
585 // Check whether the mime type, if given, is known to be supported or whether
586 // there is a plugin that supports the mime type (e.g. PDF).
587 // TODO(bauerb): This possibly uses stale information, but it's guaranteed not
588 // to do disk access.
589 content::WebPluginInfo plugin;
590 tabstrip_->FileSupported(
591 url,
592 mime_type.empty() ||
593 net::IsSupportedMimeType(mime_type) ||
594 content::PluginService::GetInstance()->GetPluginInfo(
595 -1, // process ID
596 MSG_ROUTING_NONE, // routing ID
597 model_->profile()->GetResourceContext(),
598 url, GURL(), mime_type, false,
599 NULL, &plugin, NULL));