Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / views / tabs / browser_tab_strip_controller.cc
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.
4
5 #include "chrome/browser/ui/views/tabs/browser_tab_strip_controller.h"
6
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"
49
50 using base::UserMetricsAction;
51 using content::WebContents;
52
53 namespace {
54
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;
62 }
63
64 TabStripLayoutType DetermineTabStripLayout(
65     PrefService* prefs,
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;
72   }
73   // For chromeos always allow entering stacked mode.
74 #if defined(USE_AURA)
75   if (host_desktop_type != chrome::HOST_DESKTOP_TYPE_ASH)
76     return TAB_STRIP_LAYOUT_SHRINK;
77 #else
78   if (ui::GetDisplayLayout() != ui::LAYOUT_TOUCH)
79     return TAB_STRIP_LAYOUT_SHRINK;
80 #endif
81   *adjust_layout = true;
82   switch (prefs->GetInteger(prefs::kTabStripLayoutType)) {
83     case TAB_STRIP_LAYOUT_STACKED:
84       return TAB_STRIP_LAYOUT_STACKED;
85     default:
86       return TAB_STRIP_LAYOUT_SHRINK;
87   }
88 }
89
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);
96
97   // Get the MIME type based on the filename.
98   std::string mime_type;
99   net::GetMimeTypeFromFile(full_path, &mime_type);
100
101   return mime_type;
102 }
103
104 }  // namespace
105
106 class BrowserTabStripController::TabContextMenuContents
107     : public ui::SimpleMenuModel::Delegate {
108  public:
109   TabContextMenuContents(Tab* tab,
110                          BrowserTabStripController* controller)
111       : tab_(tab),
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()));
118   }
119
120   virtual ~TabContextMenuContents() {
121     if (controller_)
122       controller_->tabstrip_->StopAllHighlighting();
123   }
124
125   void Cancel() {
126     controller_ = NULL;
127   }
128
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)
136       return;
137   }
138
139   // Overridden from ui::SimpleMenuModel::Delegate:
140   virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
141     return false;
142   }
143   virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
144     return controller_->IsCommandEnabledForTab(
145         static_cast<TabStripModel::ContextMenuCommand>(command_id),
146         tab_);
147   }
148   virtual bool GetAcceleratorForCommandId(
149       int command_id,
150       ui::Accelerator* accelerator) OVERRIDE {
151     int browser_cmd;
152     return TabStripModel::ContextMenuCommandToBrowserCommand(command_id,
153                                                              &browser_cmd) ?
154         controller_->tabstrip_->GetWidget()->GetAccelerator(browser_cmd,
155                                                             accelerator) :
156         false;
157   }
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_);
162   }
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),
169         tab_);
170   }
171
172   virtual void MenuClosed(ui::SimpleMenuModel* /*source*/) OVERRIDE {
173     if (controller_)
174       controller_->tabstrip_->StopAllHighlighting();
175   }
176
177  private:
178   scoped_ptr<TabMenuModel> model_;
179   scoped_ptr<views::MenuRunner> menu_runner_;
180
181   // The tab we're showing a menu for.
182   Tab* tab_;
183
184   // A pointer back to our hosting controller, for command state information.
185   BrowserTabStripController* controller_;
186
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_;
190
191   DISALLOW_COPY_AND_ASSIGN(TabContextMenuContents);
192 };
193
194 ////////////////////////////////////////////////////////////////////////////////
195 // BrowserTabStripController, public:
196
197 BrowserTabStripController::BrowserTabStripController(Browser* browser,
198                                                      TabStripModel* model)
199     : model_(model),
200       tabstrip_(NULL),
201       browser_(browser),
202       hover_tab_selector_(model),
203       weak_ptr_factory_(this) {
204   model_->AddObserver(this);
205
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)));
211 }
212
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();
219
220   model_->RemoveObserver(this);
221 }
222
223 void BrowserTabStripController::InitFromModel(TabStrip* tabstrip) {
224   tabstrip_ = tabstrip;
225
226   UpdateLayoutType();
227
228   // Walk the model, calling our insertion observer method for each item within
229   // it.
230   for (int i = 0; i < model_->count(); ++i)
231     AddTab(model_->GetWebContentsAt(i), i, model_->active_index() == i);
232 }
233
234 bool BrowserTabStripController::IsCommandEnabledForTab(
235     TabStripModel::ContextMenuCommand command_id,
236     Tab* tab) const {
237   int model_index = tabstrip_->GetModelIndexOfTab(tab);
238   return model_->ContainsIndex(model_index) ?
239       model_->IsContextMenuCommandEnabled(model_index, command_id) : false;
240 }
241
242 void BrowserTabStripController::ExecuteCommandForTab(
243     TabStripModel::ContextMenuCommand command_id,
244     Tab* tab) {
245   int model_index = tabstrip_->GetModelIndexOfTab(tab);
246   if (model_->ContainsIndex(model_index))
247     model_->ExecuteContextMenuCommand(model_index, command_id);
248 }
249
250 bool BrowserTabStripController::IsTabPinned(Tab* tab) const {
251   return IsTabPinned(tabstrip_->GetModelIndexOfTab(tab));
252 }
253
254 const ui::ListSelectionModel& BrowserTabStripController::GetSelectionModel() {
255   return model_->selection_model();
256 }
257
258 int BrowserTabStripController::GetCount() const {
259   return model_->count();
260 }
261
262 bool BrowserTabStripController::IsValidIndex(int index) const {
263   return model_->ContainsIndex(index);
264 }
265
266 bool BrowserTabStripController::IsActiveTab(int model_index) const {
267   return model_->active_index() == model_index;
268 }
269
270 int BrowserTabStripController::GetActiveIndex() const {
271   return model_->active_index();
272 }
273
274 bool BrowserTabStripController::IsTabSelected(int model_index) const {
275   return model_->IsTabSelected(model_index);
276 }
277
278 bool BrowserTabStripController::IsTabPinned(int model_index) const {
279   return model_->ContainsIndex(model_index) && model_->IsTabPinned(model_index);
280 }
281
282 bool BrowserTabStripController::IsNewTabPage(int model_index) const {
283   if (!model_->ContainsIndex(model_index))
284     return false;
285
286   const WebContents* contents = model_->GetWebContentsAt(model_index);
287   return contents && (contents->GetURL() == GURL(chrome::kChromeUINewTabURL) ||
288       chrome::IsInstantNTP(contents));
289 }
290
291 void BrowserTabStripController::SelectTab(int model_index) {
292   model_->ActivateTabAt(model_index, true);
293 }
294
295 void BrowserTabStripController::ExtendSelectionTo(int model_index) {
296   model_->ExtendSelectionTo(model_index);
297 }
298
299 void BrowserTabStripController::ToggleSelected(int model_index) {
300   model_->ToggleSelectionAt(model_index);
301 }
302
303 void BrowserTabStripController::AddSelectionFromAnchorTo(int model_index) {
304   model_->AddSelectionFromAnchorTo(model_index);
305 }
306
307 void BrowserTabStripController::CloseTab(int model_index,
308                                          CloseTabSource source) {
309   // Cancel any pending tab transition.
310   hover_tab_selector_.CancelTabTransition();
311
312   tabstrip_->PrepareForCloseAt(model_index, source);
313   model_->CloseWebContentsAt(model_index,
314                              TabStripModel::CLOSE_USER_GESTURE |
315                              TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
316 }
317
318 void BrowserTabStripController::ShowContextMenuForTab(
319     Tab* tab,
320     const gfx::Point& p,
321     ui::MenuSourceType source_type) {
322   context_menu_contents_.reset(new TabContextMenuContents(tab, this));
323   context_menu_contents_->RunMenuAt(p, source_type);
324 }
325
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));
335     }
336   }
337 }
338
339 int BrowserTabStripController::HasAvailableDragActions() const {
340   return model_->delegate()->GetDragActions();
341 }
342
343 void BrowserTabStripController::OnDropIndexUpdate(int index,
344                                                   bool drop_before) {
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);
349   } else {
350     hover_tab_selector_.CancelTabTransition();
351   }
352 }
353
354 void BrowserTabStripController::PerformDrop(bool drop_before,
355                                             int index,
356                                             const GURL& url) {
357   chrome::NavigateParams params(browser_, url, content::PAGE_TRANSITION_LINK);
358   params.tabstrip_index = index;
359
360   if (drop_before) {
361     content::RecordAction(UserMetricsAction("Tab_DropURLBetweenTabs"));
362     params.disposition = NEW_FOREGROUND_TAB;
363   } else {
364     content::RecordAction(UserMetricsAction("Tab_DropURLOnTab"));
365     params.disposition = CURRENT_TAB;
366     params.source_contents = model_->GetWebContentsAt(index);
367   }
368   params.window_action = chrome::NavigateParams::SHOW_WINDOW;
369   chrome::Navigate(&params);
370 }
371
372 bool BrowserTabStripController::IsCompatibleWith(TabStrip* other) const {
373   Profile* other_profile =
374       static_cast<BrowserTabStripController*>(other->controller())->profile();
375   return other_profile == profile();
376 }
377
378 void BrowserTabStripController::CreateNewTab() {
379   model_->delegate()->AddTabAt(GURL(), -1, true);
380 }
381
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);
391 }
392
393 bool BrowserTabStripController::IsIncognito() {
394   return browser_->profile()->IsOffTheRecord();
395 }
396
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())
403     return;
404
405   g_browser_process->local_state()->SetInteger(
406       prefs::kTabStripLayoutType,
407       static_cast<int>(tabstrip_->layout_type()));
408 }
409
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));
420   }
421 }
422
423 void BrowserTabStripController::OnStoppedDraggingTabs() {
424   immersive_reveal_lock_.reset();
425 }
426
427 void BrowserTabStripController::CheckFileSupported(const GURL& url) {
428   base::PostTaskAndReplyWithResult(
429       content::BrowserThread::GetBlockingPool(),
430       FROM_HERE,
431       base::Bind(&FindURLMimeType, url),
432       base::Bind(&BrowserTabStripController::OnFindURLMimeTypeCompleted,
433                  weak_ptr_factory_.GetWeakPtr(),
434                  url));
435 }
436
437 ////////////////////////////////////////////////////////////////////////////////
438 // BrowserTabStripController, TabStripModelObserver implementation:
439
440 void BrowserTabStripController::TabInsertedAt(WebContents* contents,
441                                               int model_index,
442                                               bool is_active) {
443   DCHECK(contents);
444   DCHECK(model_->ContainsIndex(model_index));
445   AddTab(contents, model_index, is_active);
446 }
447
448 void BrowserTabStripController::TabDetachedAt(WebContents* contents,
449                                               int model_index) {
450   // Cancel any pending tab transition.
451   hover_tab_selector_.CancelTabTransition();
452
453   tabstrip_->RemoveTabAt(model_index);
454 }
455
456 void BrowserTabStripController::TabSelectionChanged(
457     TabStripModel* tab_strip_model,
458     const ui::ListSelectionModel& old_model) {
459   tabstrip_->SetSelection(old_model, model_->selection_model());
460 }
461
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();
467
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);
472 }
473
474 void BrowserTabStripController::TabChangedAt(WebContents* contents,
475                                              int model_index,
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.
480     return;
481   }
482
483   SetTabDataAt(contents, model_index);
484 }
485
486 void BrowserTabStripController::TabReplacedAt(TabStripModel* tab_strip_model,
487                                               WebContents* old_contents,
488                                               WebContents* new_contents,
489                                               int model_index) {
490   SetTabDataAt(new_contents, model_index);
491 }
492
493 void BrowserTabStripController::TabPinnedStateChanged(WebContents* contents,
494                                                       int model_index) {
495   // Currently none of the renderers render pinned state differently.
496 }
497
498 void BrowserTabStripController::TabMiniStateChanged(WebContents* contents,
499                                                     int model_index) {
500   SetTabDataAt(contents, model_index);
501 }
502
503 void BrowserTabStripController::TabBlockedStateChanged(WebContents* contents,
504                                                        int model_index) {
505   SetTabDataAt(contents, model_index);
506 }
507
508 void BrowserTabStripController::SetTabRendererDataFromModel(
509     WebContents* contents,
510     int model_index,
511     TabRendererData* data,
512     TabStatus tab_status) {
513   FaviconTabHelper* favicon_tab_helper =
514       FaviconTabHelper::FromWebContents(contents);
515
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);
528 }
529
530 void BrowserTabStripController::SetTabDataAt(content::WebContents* web_contents,
531                                              int model_index) {
532   TabRendererData data;
533   SetTabRendererDataFromModel(web_contents, model_index, &data, EXISTING_TAB);
534   tabstrip_->SetTabData(model_index, data);
535 }
536
537 void BrowserTabStripController::StartHighlightTabsForCommand(
538     TabStripModel::ContextMenuCommand command_id,
539     Tab* tab) {
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);
549       }
550     }
551   }
552 }
553
554 void BrowserTabStripController::StopHighlightTabsForCommand(
555     TabStripModel::ContextMenuCommand command_id,
556     Tab* tab) {
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();
561   }
562 }
563
564 void BrowserTabStripController::AddTab(WebContents* contents,
565                                        int index,
566                                        bool is_active) {
567   // Cancel any pending tab transition.
568   hover_tab_selector_.CancelTabTransition();
569
570   TabRendererData data;
571   SetTabRendererDataFromModel(contents, index, &data, NEW_TAB);
572   tabstrip_->AddTabAt(index, data, is_active);
573 }
574
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);
581 }
582
583 void BrowserTabStripController::OnFindURLMimeTypeCompleted(
584     const GURL& url,
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(
592       url,
593       mime_type.empty() ||
594       net::IsSupportedMimeType(mime_type) ||
595       content::PluginService::GetInstance()->GetPluginInfo(
596           -1,                // process ID
597           MSG_ROUTING_NONE,  // routing ID
598           model_->profile()->GetResourceContext(),
599           url, GURL(), mime_type, false,
600           NULL, &plugin, NULL));
601 }