Upstream version 10.39.225.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/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.h"
12 #include "chrome/browser/autocomplete/autocomplete_classifier_factory.h"
13 #include "chrome/browser/browser_process.h"
14 #include "chrome/browser/chrome_notification_types.h"
15 #include "chrome/browser/extensions/tab_helper.h"
16 #include "chrome/browser/favicon/favicon_tab_helper.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/search/search.h"
19 #include "chrome/browser/ui/browser.h"
20 #include "chrome/browser/ui/browser_tabstrip.h"
21 #include "chrome/browser/ui/tabs/tab_menu_model.h"
22 #include "chrome/browser/ui/tabs/tab_strip_model.h"
23 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
24 #include "chrome/browser/ui/tabs/tab_utils.h"
25 #include "chrome/browser/ui/views/frame/browser_view.h"
26 #include "chrome/browser/ui/views/tabs/tab.h"
27 #include "chrome/browser/ui/views/tabs/tab_renderer_data.h"
28 #include "chrome/browser/ui/views/tabs/tab_strip.h"
29 #include "chrome/common/pref_names.h"
30 #include "chrome/common/url_constants.h"
31 #include "components/metrics/proto/omnibox_event.pb.h"
32 #include "components/omnibox/autocomplete_match.h"
33 #include "content/public/browser/browser_thread.h"
34 #include "content/public/browser/notification_service.h"
35 #include "content/public/browser/plugin_service.h"
36 #include "content/public/browser/user_metrics.h"
37 #include "content/public/browser/web_contents.h"
38 #include "content/public/common/webplugininfo.h"
39 #include "ipc/ipc_message.h"
40 #include "net/base/filename_util.h"
41 #include "ui/base/models/list_selection_model.h"
42 #include "ui/gfx/image/image.h"
43 #include "ui/views/controls/menu/menu_runner.h"
44 #include "ui/views/widget/widget.h"
45
46 using base::UserMetricsAction;
47 using content::WebContents;
48
49 namespace {
50
51 TabRendererData::NetworkState TabContentsNetworkState(
52     WebContents* contents) {
53   if (!contents || !contents->IsLoadingToDifferentDocument())
54     return TabRendererData::NETWORK_STATE_NONE;
55   if (contents->IsWaitingForResponse())
56     return TabRendererData::NETWORK_STATE_WAITING;
57   return TabRendererData::NETWORK_STATE_LOADING;
58 }
59
60 bool DetermineTabStripLayoutStacked(
61     PrefService* prefs,
62     chrome::HostDesktopType host_desktop_type,
63     bool* adjust_layout) {
64   *adjust_layout = false;
65   // For ash, always allow entering stacked mode.
66   if (host_desktop_type != chrome::HOST_DESKTOP_TYPE_ASH)
67     return false;
68   *adjust_layout = true;
69   return prefs->GetBoolean(prefs::kTabStripStackedLayout);
70 }
71
72 // Get the MIME type of the file pointed to by the url, based on the file's
73 // extension. Must be called on a thread that allows IO.
74 std::string FindURLMimeType(const GURL& url) {
75   DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
76   base::FilePath full_path;
77   net::FileURLToFilePath(url, &full_path);
78
79   // Get the MIME type based on the filename.
80   std::string mime_type;
81   net::GetMimeTypeFromFile(full_path, &mime_type);
82
83   return mime_type;
84 }
85
86 }  // namespace
87
88 class BrowserTabStripController::TabContextMenuContents
89     : public ui::SimpleMenuModel::Delegate {
90  public:
91   TabContextMenuContents(Tab* tab,
92                          BrowserTabStripController* controller)
93       : tab_(tab),
94         controller_(controller),
95         last_command_(TabStripModel::CommandFirst) {
96     model_.reset(new TabMenuModel(
97         this, controller->model_,
98         controller->tabstrip_->GetModelIndexOfTab(tab)));
99     menu_runner_.reset(new views::MenuRunner(
100         model_.get(),
101         views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU));
102   }
103
104   virtual ~TabContextMenuContents() {
105     if (controller_)
106       controller_->tabstrip_->StopAllHighlighting();
107   }
108
109   void Cancel() {
110     controller_ = NULL;
111   }
112
113   void RunMenuAt(const gfx::Point& point, ui::MenuSourceType source_type) {
114     if (menu_runner_->RunMenuAt(tab_->GetWidget(),
115                                 NULL,
116                                 gfx::Rect(point, gfx::Size()),
117                                 views::MENU_ANCHOR_TOPLEFT,
118                                 source_type) ==
119         views::MenuRunner::MENU_DELETED) {
120       return;
121     }
122   }
123
124   // Overridden from ui::SimpleMenuModel::Delegate:
125   virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
126     return false;
127   }
128   virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
129     return controller_->IsCommandEnabledForTab(
130         static_cast<TabStripModel::ContextMenuCommand>(command_id),
131         tab_);
132   }
133   virtual bool GetAcceleratorForCommandId(
134       int command_id,
135       ui::Accelerator* accelerator) OVERRIDE {
136     int browser_cmd;
137     return TabStripModel::ContextMenuCommandToBrowserCommand(command_id,
138                                                              &browser_cmd) ?
139         controller_->tabstrip_->GetWidget()->GetAccelerator(browser_cmd,
140                                                             accelerator) :
141         false;
142   }
143   virtual void CommandIdHighlighted(int command_id) OVERRIDE {
144     controller_->StopHighlightTabsForCommand(last_command_, tab_);
145     last_command_ = static_cast<TabStripModel::ContextMenuCommand>(command_id);
146     controller_->StartHighlightTabsForCommand(last_command_, tab_);
147   }
148   virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE {
149     // Executing the command destroys |this|, and can also end up destroying
150     // |controller_|. So stop the highlights before executing the command.
151     controller_->tabstrip_->StopAllHighlighting();
152     controller_->ExecuteCommandForTab(
153         static_cast<TabStripModel::ContextMenuCommand>(command_id),
154         tab_);
155   }
156
157   virtual void MenuClosed(ui::SimpleMenuModel* /*source*/) OVERRIDE {
158     if (controller_)
159       controller_->tabstrip_->StopAllHighlighting();
160   }
161
162  private:
163   scoped_ptr<TabMenuModel> model_;
164   scoped_ptr<views::MenuRunner> menu_runner_;
165
166   // The tab we're showing a menu for.
167   Tab* tab_;
168
169   // A pointer back to our hosting controller, for command state information.
170   BrowserTabStripController* controller_;
171
172   // The last command that was selected, so that we can start/stop highlighting
173   // appropriately as the user moves through the menu.
174   TabStripModel::ContextMenuCommand last_command_;
175
176   DISALLOW_COPY_AND_ASSIGN(TabContextMenuContents);
177 };
178
179 ////////////////////////////////////////////////////////////////////////////////
180 // BrowserTabStripController, public:
181
182 BrowserTabStripController::BrowserTabStripController(Browser* browser,
183                                                      TabStripModel* model)
184     : model_(model),
185       tabstrip_(NULL),
186       browser_(browser),
187       hover_tab_selector_(model),
188       weak_ptr_factory_(this) {
189   model_->AddObserver(this);
190
191   local_pref_registrar_.Init(g_browser_process->local_state());
192   local_pref_registrar_.Add(
193       prefs::kTabStripStackedLayout,
194       base::Bind(&BrowserTabStripController::UpdateStackedLayout,
195                  base::Unretained(this)));
196 }
197
198 BrowserTabStripController::~BrowserTabStripController() {
199   // When we get here the TabStrip is being deleted. We need to explicitly
200   // cancel the menu, otherwise it may try to invoke something on the tabstrip
201   // from its destructor.
202   if (context_menu_contents_.get())
203     context_menu_contents_->Cancel();
204
205   model_->RemoveObserver(this);
206 }
207
208 void BrowserTabStripController::InitFromModel(TabStrip* tabstrip) {
209   tabstrip_ = tabstrip;
210
211   UpdateStackedLayout();
212
213   // Walk the model, calling our insertion observer method for each item within
214   // it.
215   for (int i = 0; i < model_->count(); ++i)
216     AddTab(model_->GetWebContentsAt(i), i, model_->active_index() == i);
217 }
218
219 bool BrowserTabStripController::IsCommandEnabledForTab(
220     TabStripModel::ContextMenuCommand command_id,
221     Tab* tab) const {
222   int model_index = tabstrip_->GetModelIndexOfTab(tab);
223   return model_->ContainsIndex(model_index) ?
224       model_->IsContextMenuCommandEnabled(model_index, command_id) : false;
225 }
226
227 void BrowserTabStripController::ExecuteCommandForTab(
228     TabStripModel::ContextMenuCommand command_id,
229     Tab* tab) {
230   int model_index = tabstrip_->GetModelIndexOfTab(tab);
231   if (model_->ContainsIndex(model_index))
232     model_->ExecuteContextMenuCommand(model_index, command_id);
233 }
234
235 bool BrowserTabStripController::IsTabPinned(Tab* tab) const {
236   return IsTabPinned(tabstrip_->GetModelIndexOfTab(tab));
237 }
238
239 const ui::ListSelectionModel& BrowserTabStripController::GetSelectionModel() {
240   return model_->selection_model();
241 }
242
243 int BrowserTabStripController::GetCount() const {
244   return model_->count();
245 }
246
247 bool BrowserTabStripController::IsValidIndex(int index) const {
248   return model_->ContainsIndex(index);
249 }
250
251 bool BrowserTabStripController::IsActiveTab(int model_index) const {
252   return model_->active_index() == model_index;
253 }
254
255 int BrowserTabStripController::GetActiveIndex() const {
256   return model_->active_index();
257 }
258
259 bool BrowserTabStripController::IsTabSelected(int model_index) const {
260   return model_->IsTabSelected(model_index);
261 }
262
263 bool BrowserTabStripController::IsTabPinned(int model_index) const {
264   return model_->ContainsIndex(model_index) && model_->IsTabPinned(model_index);
265 }
266
267 bool BrowserTabStripController::IsNewTabPage(int model_index) const {
268   if (!model_->ContainsIndex(model_index))
269     return false;
270
271   const WebContents* contents = model_->GetWebContentsAt(model_index);
272   return contents && (contents->GetURL() == GURL(chrome::kChromeUINewTabURL) ||
273       chrome::IsInstantNTP(contents));
274 }
275
276 void BrowserTabStripController::SelectTab(int model_index) {
277   model_->ActivateTabAt(model_index, true);
278 }
279
280 void BrowserTabStripController::ExtendSelectionTo(int model_index) {
281   model_->ExtendSelectionTo(model_index);
282 }
283
284 void BrowserTabStripController::ToggleSelected(int model_index) {
285   model_->ToggleSelectionAt(model_index);
286 }
287
288 void BrowserTabStripController::AddSelectionFromAnchorTo(int model_index) {
289   model_->AddSelectionFromAnchorTo(model_index);
290 }
291
292 void BrowserTabStripController::CloseTab(int model_index,
293                                          CloseTabSource source) {
294   // Cancel any pending tab transition.
295   hover_tab_selector_.CancelTabTransition();
296
297   tabstrip_->PrepareForCloseAt(model_index, source);
298   model_->CloseWebContentsAt(model_index,
299                              TabStripModel::CLOSE_USER_GESTURE |
300                              TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
301 }
302
303 void BrowserTabStripController::ToggleTabAudioMute(int model_index) {
304   content::WebContents* const contents = model_->GetWebContentsAt(model_index);
305   chrome::SetTabAudioMuted(contents, !chrome::IsTabAudioMuted(contents));
306 }
307
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);
314 }
315
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));
325     }
326   }
327 }
328
329 int BrowserTabStripController::HasAvailableDragActions() const {
330   return model_->delegate()->GetDragActions();
331 }
332
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();
341   }
342 }
343
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;
349
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);
357   }
358   params.window_action = chrome::NavigateParams::SHOW_WINDOW;
359   chrome::Navigate(&params);
360 }
361
362 bool BrowserTabStripController::IsCompatibleWith(TabStrip* other) const {
363   Profile* other_profile =
364       static_cast<BrowserTabStripController*>(other->controller())->profile();
365   return other_profile == profile();
366 }
367
368 void BrowserTabStripController::CreateNewTab() {
369   model_->delegate()->AddTabAt(GURL(), -1, true);
370 }
371
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);
381 }
382
383 bool BrowserTabStripController::IsIncognito() {
384   return browser_->profile()->IsOffTheRecord();
385 }
386
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;
395
396   g_browser_process->local_state()->SetBoolean(prefs::kTabStripStackedLayout,
397                                                tabstrip_->stacked_layout());
398 }
399
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));
410   }
411 }
412
413 void BrowserTabStripController::OnStoppedDraggingTabs() {
414   immersive_reveal_lock_.reset();
415 }
416
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));
425 }
426
427 ////////////////////////////////////////////////////////////////////////////////
428 // BrowserTabStripController, TabStripModelObserver implementation:
429
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);
436 }
437
438 void BrowserTabStripController::TabDetachedAt(WebContents* contents,
439                                               int model_index) {
440   // Cancel any pending tab transition.
441   hover_tab_selector_.CancelTabTransition();
442
443   tabstrip_->RemoveTabAt(model_index);
444 }
445
446 void BrowserTabStripController::TabSelectionChanged(
447     TabStripModel* tab_strip_model,
448     const ui::ListSelectionModel& old_model) {
449   tabstrip_->SetSelection(old_model, model_->selection_model());
450 }
451
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();
457
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);
462 }
463
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;
471   }
472
473   SetTabDataAt(contents, model_index);
474 }
475
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);
481 }
482
483 void BrowserTabStripController::TabPinnedStateChanged(WebContents* contents,
484                                                       int model_index) {
485   // Currently none of the renderers render pinned state differently.
486 }
487
488 void BrowserTabStripController::TabMiniStateChanged(WebContents* contents,
489                                                     int model_index) {
490   SetTabDataAt(contents, model_index);
491 }
492
493 void BrowserTabStripController::TabBlockedStateChanged(WebContents* contents,
494                                                        int model_index) {
495   SetTabDataAt(contents, model_index);
496 }
497
498 void BrowserTabStripController::SetTabRendererDataFromModel(
499     WebContents* contents,
500     int model_index,
501     TabRendererData* data,
502     TabStatus tab_status) {
503   FaviconTabHelper* favicon_tab_helper =
504       FaviconTabHelper::FromWebContents(contents);
505
506   data->favicon = favicon_tab_helper->GetFavicon().AsImageSkia();
507   data->network_state = TabContentsNetworkState(contents);
508   data->title = contents->GetTitle();
509   data->url = contents->GetURL();
510   data->loading = contents->IsLoading();
511   data->crashed_status = contents->GetCrashedStatus();
512   data->incognito = contents->GetBrowserContext()->IsOffTheRecord();
513   data->mini = model_->IsMiniTab(model_index);
514   data->show_icon = data->mini || favicon_tab_helper->ShouldDisplayFavicon();
515   data->blocked = model_->IsTabBlocked(model_index);
516   data->app = extensions::TabHelper::FromWebContents(contents)->is_app();
517   data->media_state = chrome::GetTabMediaStateForContents(contents);
518 }
519
520 void BrowserTabStripController::SetTabDataAt(content::WebContents* web_contents,
521                                              int model_index) {
522   TabRendererData data;
523   SetTabRendererDataFromModel(web_contents, model_index, &data, EXISTING_TAB);
524   tabstrip_->SetTabData(model_index, data);
525 }
526
527 void BrowserTabStripController::StartHighlightTabsForCommand(
528     TabStripModel::ContextMenuCommand command_id,
529     Tab* tab) {
530   if (command_id == TabStripModel::CommandCloseOtherTabs ||
531       command_id == TabStripModel::CommandCloseTabsToRight) {
532     int model_index = tabstrip_->GetModelIndexOfTab(tab);
533     if (IsValidIndex(model_index)) {
534       std::vector<int> indices =
535           model_->GetIndicesClosedByCommand(model_index, command_id);
536       for (std::vector<int>::const_iterator i(indices.begin());
537            i != indices.end(); ++i) {
538         tabstrip_->StartHighlight(*i);
539       }
540     }
541   }
542 }
543
544 void BrowserTabStripController::StopHighlightTabsForCommand(
545     TabStripModel::ContextMenuCommand command_id,
546     Tab* tab) {
547   if (command_id == TabStripModel::CommandCloseTabsToRight ||
548       command_id == TabStripModel::CommandCloseOtherTabs) {
549     // Just tell all Tabs to stop pulsing - it's safe.
550     tabstrip_->StopAllHighlighting();
551   }
552 }
553
554 void BrowserTabStripController::AddTab(WebContents* contents,
555                                        int index,
556                                        bool is_active) {
557   // Cancel any pending tab transition.
558   hover_tab_selector_.CancelTabTransition();
559
560   TabRendererData data;
561   SetTabRendererDataFromModel(contents, index, &data, NEW_TAB);
562   tabstrip_->AddTabAt(index, data, is_active);
563 }
564
565 void BrowserTabStripController::UpdateStackedLayout() {
566   bool adjust_layout = false;
567   bool stacked_layout =
568       DetermineTabStripLayoutStacked(g_browser_process->local_state(),
569                                      browser_->host_desktop_type(),
570                                      &adjust_layout);
571   tabstrip_->set_adjust_layout(adjust_layout);
572   tabstrip_->SetStackedLayout(stacked_layout);
573 }
574
575 void BrowserTabStripController::OnFindURLMimeTypeCompleted(
576     const GURL& url,
577     const std::string& mime_type) {
578   // Check whether the mime type, if given, is known to be supported or whether
579   // there is a plugin that supports the mime type (e.g. PDF).
580   // TODO(bauerb): This possibly uses stale information, but it's guaranteed not
581   // to do disk access.
582   content::WebPluginInfo plugin;
583   tabstrip_->FileSupported(
584       url,
585       mime_type.empty() ||
586       net::IsSupportedMimeType(mime_type) ||
587       content::PluginService::GetInstance()->GetPluginInfo(
588           -1,                // process ID
589           MSG_ROUTING_NONE,  // routing ID
590           model_->profile()->GetResourceContext(),
591           url, GURL(), mime_type, false,
592           NULL, &plugin, NULL));
593 }