Upstream version 7.36.149.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/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"
47
48 using base::UserMetricsAction;
49 using content::WebContents;
50
51 namespace {
52
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;
60 }
61
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;
70   }
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;
80   }
81 }
82
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);
89
90   // Get the MIME type based on the filename.
91   std::string mime_type;
92   net::GetMimeTypeFromFile(full_path, &mime_type);
93
94   return mime_type;
95 }
96
97 }  // namespace
98
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()));
111   }
112
113   virtual ~TabContextMenuContents() {
114     if (controller_)
115       controller_->tabstrip_->StopAllHighlighting();
116   }
117
118   void Cancel() {
119     controller_ = NULL;
120   }
121
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;
132     }
133   }
134
135   // Overridden from ui::SimpleMenuModel::Delegate:
136   virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
137     return false;
138   }
139   virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
140     return controller_->IsCommandEnabledForTab(
141         static_cast<TabStripModel::ContextMenuCommand>(command_id),
142         tab_);
143   }
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;
153   }
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_);
158   }
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_);
166   }
167
168   virtual void MenuClosed(ui::SimpleMenuModel* /*source*/) OVERRIDE {
169     if (controller_)
170       controller_->tabstrip_->StopAllHighlighting();
171   }
172
173  private:
174   scoped_ptr<TabMenuModel> model_;
175   scoped_ptr<views::MenuRunner> menu_runner_;
176
177   // The tab we're showing a menu for.
178   Tab* tab_;
179
180   // A pointer back to our hosting controller, for command state information.
181   BrowserTabStripController* controller_;
182
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_;
186
187   DISALLOW_COPY_AND_ASSIGN(TabContextMenuContents);
188 };
189
190 ////////////////////////////////////////////////////////////////////////////////
191 // BrowserTabStripController, public:
192
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);
201
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)));
207 }
208
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();
215
216   model_->RemoveObserver(this);
217 }
218
219 void BrowserTabStripController::InitFromModel(TabStrip* tabstrip) {
220   tabstrip_ = tabstrip;
221
222   UpdateLayoutType();
223
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);
228 }
229
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;
236 }
237
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);
244 }
245
246 bool BrowserTabStripController::IsTabPinned(Tab* tab) const {
247   return IsTabPinned(tabstrip_->GetModelIndexOfTab(tab));
248 }
249
250 const ui::ListSelectionModel& BrowserTabStripController::GetSelectionModel() {
251   return model_->selection_model();
252 }
253
254 int BrowserTabStripController::GetCount() const {
255   return model_->count();
256 }
257
258 bool BrowserTabStripController::IsValidIndex(int index) const {
259   return model_->ContainsIndex(index);
260 }
261
262 bool BrowserTabStripController::IsActiveTab(int model_index) const {
263   return model_->active_index() == model_index;
264 }
265
266 int BrowserTabStripController::GetActiveIndex() const {
267   return model_->active_index();
268 }
269
270 bool BrowserTabStripController::IsTabSelected(int model_index) const {
271   return model_->IsTabSelected(model_index);
272 }
273
274 bool BrowserTabStripController::IsTabPinned(int model_index) const {
275   return model_->ContainsIndex(model_index) && model_->IsTabPinned(model_index);
276 }
277
278 bool BrowserTabStripController::IsNewTabPage(int model_index) const {
279   if (!model_->ContainsIndex(model_index))
280     return false;
281
282   const WebContents* contents = model_->GetWebContentsAt(model_index);
283   return contents && (contents->GetURL() == GURL(chrome::kChromeUINewTabURL) ||
284       chrome::IsInstantNTP(contents));
285 }
286
287 void BrowserTabStripController::SelectTab(int model_index) {
288   model_->ActivateTabAt(model_index, true);
289 }
290
291 void BrowserTabStripController::ExtendSelectionTo(int model_index) {
292   model_->ExtendSelectionTo(model_index);
293 }
294
295 void BrowserTabStripController::ToggleSelected(int model_index) {
296   model_->ToggleSelectionAt(model_index);
297 }
298
299 void BrowserTabStripController::AddSelectionFromAnchorTo(int model_index) {
300   model_->AddSelectionFromAnchorTo(model_index);
301 }
302
303 void BrowserTabStripController::CloseTab(int model_index,
304                                          CloseTabSource source) {
305   // Cancel any pending tab transition.
306   hover_tab_selector_.CancelTabTransition();
307
308   tabstrip_->PrepareForCloseAt(model_index, source);
309   model_->CloseWebContentsAt(model_index,
310                              TabStripModel::CLOSE_USER_GESTURE |
311                              TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
312 }
313
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);
320 }
321
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));
331     }
332   }
333 }
334
335 int BrowserTabStripController::HasAvailableDragActions() const {
336   return model_->delegate()->GetDragActions();
337 }
338
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();
347   }
348 }
349
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;
355
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);
363   }
364   params.window_action = chrome::NavigateParams::SHOW_WINDOW;
365   chrome::Navigate(&params);
366 }
367
368 bool BrowserTabStripController::IsCompatibleWith(TabStrip* other) const {
369   Profile* other_profile =
370       static_cast<BrowserTabStripController*>(other->controller())->profile();
371   return other_profile == profile();
372 }
373
374 void BrowserTabStripController::CreateNewTab() {
375   model_->delegate()->AddTabAt(GURL(), -1, true);
376 }
377
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);
387 }
388
389 bool BrowserTabStripController::IsIncognito() {
390   return browser_->profile()->IsOffTheRecord();
391 }
392
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;
400
401   g_browser_process->local_state()->SetInteger(
402       prefs::kTabStripLayoutType,
403       static_cast<int>(tabstrip_->layout_type()));
404 }
405
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));
416   }
417 }
418
419 void BrowserTabStripController::OnStoppedDraggingTabs() {
420   immersive_reveal_lock_.reset();
421 }
422
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));
431 }
432
433 ////////////////////////////////////////////////////////////////////////////////
434 // BrowserTabStripController, TabStripModelObserver implementation:
435
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);
442 }
443
444 void BrowserTabStripController::TabDetachedAt(WebContents* contents,
445                                               int model_index) {
446   // Cancel any pending tab transition.
447   hover_tab_selector_.CancelTabTransition();
448
449   tabstrip_->RemoveTabAt(model_index);
450 }
451
452 void BrowserTabStripController::TabSelectionChanged(
453     TabStripModel* tab_strip_model,
454     const ui::ListSelectionModel& old_model) {
455   tabstrip_->SetSelection(old_model, model_->selection_model());
456 }
457
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();
463
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);
468 }
469
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;
477   }
478
479   SetTabDataAt(contents, model_index);
480 }
481
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);
487 }
488
489 void BrowserTabStripController::TabPinnedStateChanged(WebContents* contents,
490                                                       int model_index) {
491   // Currently none of the renderers render pinned state differently.
492 }
493
494 void BrowserTabStripController::TabMiniStateChanged(WebContents* contents,
495                                                     int model_index) {
496   SetTabDataAt(contents, model_index);
497 }
498
499 void BrowserTabStripController::TabBlockedStateChanged(WebContents* contents,
500                                                        int model_index) {
501   SetTabDataAt(contents, model_index);
502 }
503
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);
511
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);
524 }
525
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);
531 }
532
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);
545       }
546     }
547   }
548 }
549
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();
557   }
558 }
559
560 void BrowserTabStripController::AddTab(WebContents* contents,
561                                        int index,
562                                        bool is_active) {
563   // Cancel any pending tab transition.
564   hover_tab_selector_.CancelTabTransition();
565
566   TabRendererData data;
567   SetTabRendererDataFromModel(contents, index, &data, NEW_TAB);
568   tabstrip_->AddTabAt(index, data, is_active);
569 }
570
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);
577 }
578
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));
597 }