- add sources.
[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 "chrome/browser/browser_process.h"
11 #include "chrome/browser/chrome_notification_types.h"
12 #include "chrome/browser/extensions/tab_helper.h"
13 #include "chrome/browser/favicon/favicon_tab_helper.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/search/search.h"
16 #include "chrome/browser/ui/browser.h"
17 #include "chrome/browser/ui/browser_tabstrip.h"
18 #include "chrome/browser/ui/tabs/tab_menu_model.h"
19 #include "chrome/browser/ui/tabs/tab_strip_model.h"
20 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
21 #include "chrome/browser/ui/tabs/tab_utils.h"
22 #include "chrome/browser/ui/views/frame/browser_view.h"
23 #include "chrome/browser/ui/views/tabs/tab.h"
24 #include "chrome/browser/ui/views/tabs/tab_renderer_data.h"
25 #include "chrome/browser/ui/views/tabs/tab_strip.h"
26 #include "chrome/common/chrome_switches.h"
27 #include "chrome/common/pref_names.h"
28 #include "chrome/common/url_constants.h"
29 #include "content/public/browser/notification_service.h"
30 #include "content/public/browser/user_metrics.h"
31 #include "content/public/browser/web_contents.h"
32 #include "ui/base/layout.h"
33 #include "ui/base/models/list_selection_model.h"
34 #include "ui/gfx/image/image.h"
35 #include "ui/views/controls/menu/menu_item_view.h"
36 #include "ui/views/controls/menu/menu_runner.h"
37 #include "ui/views/widget/widget.h"
38
39 using content::UserMetricsAction;
40 using content::WebContents;
41
42 namespace {
43
44 TabRendererData::NetworkState TabContentsNetworkState(
45     WebContents* contents) {
46   if (!contents || !contents->IsLoading())
47     return TabRendererData::NETWORK_STATE_NONE;
48   if (contents->IsWaitingForResponse())
49     return TabRendererData::NETWORK_STATE_WAITING;
50   return TabRendererData::NETWORK_STATE_LOADING;
51 }
52
53 TabStripLayoutType DetermineTabStripLayout(PrefService* prefs,
54                                            bool* adjust_layout) {
55   *adjust_layout = false;
56   if (CommandLine::ForCurrentProcess()->HasSwitch(
57           switches::kEnableStackedTabStrip)) {
58     return TAB_STRIP_LAYOUT_STACKED;
59   }
60   // For chromeos always allow entering stacked mode.
61 #if !defined(OS_CHROMEOS)
62   if (ui::GetDisplayLayout() != ui::LAYOUT_TOUCH)
63     return TAB_STRIP_LAYOUT_SHRINK;
64 #endif
65   *adjust_layout = true;
66   switch (prefs->GetInteger(prefs::kTabStripLayoutType)) {
67     case TAB_STRIP_LAYOUT_STACKED:
68       return TAB_STRIP_LAYOUT_STACKED;
69     default:
70       return TAB_STRIP_LAYOUT_SHRINK;
71   }
72 }
73
74 }  // namespace
75
76 class BrowserTabStripController::TabContextMenuContents
77     : public ui::SimpleMenuModel::Delegate {
78  public:
79   TabContextMenuContents(Tab* tab,
80                          BrowserTabStripController* controller)
81       : tab_(tab),
82         controller_(controller),
83         last_command_(TabStripModel::CommandFirst) {
84     model_.reset(new TabMenuModel(
85         this, controller->model_,
86         controller->tabstrip_->GetModelIndexOfTab(tab)));
87     menu_runner_.reset(new views::MenuRunner(model_.get()));
88   }
89
90   virtual ~TabContextMenuContents() {
91     if (controller_)
92       controller_->tabstrip_->StopAllHighlighting();
93   }
94
95   void Cancel() {
96     controller_ = NULL;
97   }
98
99   void RunMenuAt(const gfx::Point& point, ui::MenuSourceType source_type) {
100     if (menu_runner_->RunMenuAt(
101             tab_->GetWidget(), NULL, gfx::Rect(point, gfx::Size()),
102             views::MenuItemView::TOPLEFT, source_type,
103             views::MenuRunner::HAS_MNEMONICS |
104             views::MenuRunner::CONTEXT_MENU) ==
105         views::MenuRunner::MENU_DELETED)
106       return;
107   }
108
109   // Overridden from ui::SimpleMenuModel::Delegate:
110   virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
111     return false;
112   }
113   virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
114     return controller_->IsCommandEnabledForTab(
115         static_cast<TabStripModel::ContextMenuCommand>(command_id),
116         tab_);
117   }
118   virtual bool GetAcceleratorForCommandId(
119       int command_id,
120       ui::Accelerator* accelerator) OVERRIDE {
121     int browser_cmd;
122     return TabStripModel::ContextMenuCommandToBrowserCommand(command_id,
123                                                              &browser_cmd) ?
124         controller_->tabstrip_->GetWidget()->GetAccelerator(browser_cmd,
125                                                             accelerator) :
126         false;
127   }
128   virtual void CommandIdHighlighted(int command_id) OVERRIDE {
129     controller_->StopHighlightTabsForCommand(last_command_, tab_);
130     last_command_ = static_cast<TabStripModel::ContextMenuCommand>(command_id);
131     controller_->StartHighlightTabsForCommand(last_command_, tab_);
132   }
133   virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE {
134     // Executing the command destroys |this|, and can also end up destroying
135     // |controller_|. So stop the highlights before executing the command.
136     controller_->tabstrip_->StopAllHighlighting();
137     controller_->ExecuteCommandForTab(
138         static_cast<TabStripModel::ContextMenuCommand>(command_id),
139         tab_);
140   }
141
142   virtual void MenuClosed(ui::SimpleMenuModel* /*source*/) OVERRIDE {
143     if (controller_)
144       controller_->tabstrip_->StopAllHighlighting();
145   }
146
147  private:
148   scoped_ptr<TabMenuModel> model_;
149   scoped_ptr<views::MenuRunner> menu_runner_;
150
151   // The tab we're showing a menu for.
152   Tab* tab_;
153
154   // A pointer back to our hosting controller, for command state information.
155   BrowserTabStripController* controller_;
156
157   // The last command that was selected, so that we can start/stop highlighting
158   // appropriately as the user moves through the menu.
159   TabStripModel::ContextMenuCommand last_command_;
160
161   DISALLOW_COPY_AND_ASSIGN(TabContextMenuContents);
162 };
163
164 ////////////////////////////////////////////////////////////////////////////////
165 // BrowserTabStripController, public:
166
167 BrowserTabStripController::BrowserTabStripController(Browser* browser,
168                                                      TabStripModel* model)
169     : model_(model),
170       tabstrip_(NULL),
171       browser_(browser),
172       hover_tab_selector_(model) {
173   model_->AddObserver(this);
174
175   local_pref_registrar_.Init(g_browser_process->local_state());
176   local_pref_registrar_.Add(
177       prefs::kTabStripLayoutType,
178       base::Bind(&BrowserTabStripController::UpdateLayoutType,
179                  base::Unretained(this)));
180 }
181
182 BrowserTabStripController::~BrowserTabStripController() {
183   // When we get here the TabStrip is being deleted. We need to explicitly
184   // cancel the menu, otherwise it may try to invoke something on the tabstrip
185   // from its destructor.
186   if (context_menu_contents_.get())
187     context_menu_contents_->Cancel();
188
189   model_->RemoveObserver(this);
190 }
191
192 void BrowserTabStripController::InitFromModel(TabStrip* tabstrip) {
193   tabstrip_ = tabstrip;
194
195   UpdateLayoutType();
196
197   // Walk the model, calling our insertion observer method for each item within
198   // it.
199   for (int i = 0; i < model_->count(); ++i)
200     AddTab(model_->GetWebContentsAt(i), i, model_->active_index() == i);
201 }
202
203 bool BrowserTabStripController::IsCommandEnabledForTab(
204     TabStripModel::ContextMenuCommand command_id,
205     Tab* tab) const {
206   int model_index = tabstrip_->GetModelIndexOfTab(tab);
207   return model_->ContainsIndex(model_index) ?
208       model_->IsContextMenuCommandEnabled(model_index, command_id) : false;
209 }
210
211 void BrowserTabStripController::ExecuteCommandForTab(
212     TabStripModel::ContextMenuCommand command_id,
213     Tab* tab) {
214   int model_index = tabstrip_->GetModelIndexOfTab(tab);
215   if (model_->ContainsIndex(model_index))
216     model_->ExecuteContextMenuCommand(model_index, command_id);
217 }
218
219 bool BrowserTabStripController::IsTabPinned(Tab* tab) const {
220   return IsTabPinned(tabstrip_->GetModelIndexOfTab(tab));
221 }
222
223 const ui::ListSelectionModel& BrowserTabStripController::GetSelectionModel() {
224   return model_->selection_model();
225 }
226
227 int BrowserTabStripController::GetCount() const {
228   return model_->count();
229 }
230
231 bool BrowserTabStripController::IsValidIndex(int index) const {
232   return model_->ContainsIndex(index);
233 }
234
235 bool BrowserTabStripController::IsActiveTab(int model_index) const {
236   return model_->active_index() == model_index;
237 }
238
239 int BrowserTabStripController::GetActiveIndex() const {
240   return model_->active_index();
241 }
242
243 bool BrowserTabStripController::IsTabSelected(int model_index) const {
244   return model_->IsTabSelected(model_index);
245 }
246
247 bool BrowserTabStripController::IsTabPinned(int model_index) const {
248   return model_->ContainsIndex(model_index) && model_->IsTabPinned(model_index);
249 }
250
251 bool BrowserTabStripController::IsNewTabPage(int model_index) const {
252   if (!model_->ContainsIndex(model_index))
253     return false;
254
255   const WebContents* contents = model_->GetWebContentsAt(model_index);
256   return contents && (contents->GetURL() == GURL(chrome::kChromeUINewTabURL) ||
257       chrome::IsInstantNTP(contents));
258 }
259
260 void BrowserTabStripController::SelectTab(int model_index) {
261   model_->ActivateTabAt(model_index, true);
262 }
263
264 void BrowserTabStripController::ExtendSelectionTo(int model_index) {
265   model_->ExtendSelectionTo(model_index);
266 }
267
268 void BrowserTabStripController::ToggleSelected(int model_index) {
269   model_->ToggleSelectionAt(model_index);
270 }
271
272 void BrowserTabStripController::AddSelectionFromAnchorTo(int model_index) {
273   model_->AddSelectionFromAnchorTo(model_index);
274 }
275
276 void BrowserTabStripController::CloseTab(int model_index,
277                                          CloseTabSource source) {
278   // Cancel any pending tab transition.
279   hover_tab_selector_.CancelTabTransition();
280
281   tabstrip_->PrepareForCloseAt(model_index, source);
282   model_->CloseWebContentsAt(model_index,
283                              TabStripModel::CLOSE_USER_GESTURE |
284                              TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
285 }
286
287 void BrowserTabStripController::ShowContextMenuForTab(
288     Tab* tab,
289     const gfx::Point& p,
290     ui::MenuSourceType source_type) {
291   context_menu_contents_.reset(new TabContextMenuContents(tab, this));
292   context_menu_contents_->RunMenuAt(p, source_type);
293 }
294
295 void BrowserTabStripController::UpdateLoadingAnimations() {
296   // Don't use the model count here as it's possible for this to be invoked
297   // before we've applied an update from the model (Browser::TabInsertedAt may
298   // be processed before us and invokes this).
299   for (int i = 0, tab_count = tabstrip_->tab_count(); i < tab_count; ++i) {
300     if (model_->ContainsIndex(i)) {
301       Tab* tab = tabstrip_->tab_at(i);
302       WebContents* contents = model_->GetWebContentsAt(i);
303       tab->UpdateLoadingAnimation(TabContentsNetworkState(contents));
304     }
305   }
306 }
307
308 int BrowserTabStripController::HasAvailableDragActions() const {
309   return model_->delegate()->GetDragActions();
310 }
311
312 void BrowserTabStripController::OnDropIndexUpdate(int index,
313                                                   bool drop_before) {
314   // Perform a delayed tab transition if hovering directly over a tab.
315   // Otherwise, cancel the pending one.
316   if (index != -1 && !drop_before) {
317     hover_tab_selector_.StartTabTransition(index);
318   } else {
319     hover_tab_selector_.CancelTabTransition();
320   }
321 }
322
323 void BrowserTabStripController::PerformDrop(bool drop_before,
324                                             int index,
325                                             const GURL& url) {
326   chrome::NavigateParams params(browser_, url, content::PAGE_TRANSITION_LINK);
327   params.tabstrip_index = index;
328
329   if (drop_before) {
330     content::RecordAction(UserMetricsAction("Tab_DropURLBetweenTabs"));
331     params.disposition = NEW_FOREGROUND_TAB;
332   } else {
333     content::RecordAction(UserMetricsAction("Tab_DropURLOnTab"));
334     params.disposition = CURRENT_TAB;
335     params.source_contents = model_->GetWebContentsAt(index);
336   }
337   params.window_action = chrome::NavigateParams::SHOW_WINDOW;
338   chrome::Navigate(&params);
339 }
340
341 bool BrowserTabStripController::IsCompatibleWith(TabStrip* other) const {
342   Profile* other_profile =
343       static_cast<BrowserTabStripController*>(other->controller())->profile();
344   return other_profile == profile();
345 }
346
347 void BrowserTabStripController::CreateNewTab() {
348   model_->delegate()->AddBlankTabAt(-1, true);
349 }
350
351 bool BrowserTabStripController::IsIncognito() {
352   return browser_->profile()->IsOffTheRecord();
353 }
354
355 void BrowserTabStripController::LayoutTypeMaybeChanged() {
356   bool adjust_layout = false;
357   TabStripLayoutType layout_type =
358       DetermineTabStripLayout(g_browser_process->local_state(), &adjust_layout);
359   if (!adjust_layout || layout_type == tabstrip_->layout_type())
360     return;
361
362   g_browser_process->local_state()->SetInteger(
363       prefs::kTabStripLayoutType,
364       static_cast<int>(tabstrip_->layout_type()));
365 }
366
367 void BrowserTabStripController::OnStartedDraggingTabs() {
368   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser_);
369   if (browser_view && !immersive_reveal_lock_.get()) {
370     // The top-of-window views should be revealed while the user is dragging
371     // tabs in immersive fullscreen. The top-of-window views may not be already
372     // revealed if the user is attempting to attach a tab to a tabstrip
373     // belonging to an immersive fullscreen window.
374     immersive_reveal_lock_.reset(
375         browser_view->immersive_mode_controller()->GetRevealedLock(
376             ImmersiveModeController::ANIMATE_REVEAL_NO));
377   }
378 }
379
380 void BrowserTabStripController::OnStoppedDraggingTabs() {
381   immersive_reveal_lock_.reset();
382 }
383
384 ////////////////////////////////////////////////////////////////////////////////
385 // BrowserTabStripController, TabStripModelObserver implementation:
386
387 void BrowserTabStripController::TabInsertedAt(WebContents* contents,
388                                               int model_index,
389                                               bool is_active) {
390   DCHECK(contents);
391   DCHECK(model_->ContainsIndex(model_index));
392   AddTab(contents, model_index, is_active);
393 }
394
395 void BrowserTabStripController::TabDetachedAt(WebContents* contents,
396                                               int model_index) {
397   // Cancel any pending tab transition.
398   hover_tab_selector_.CancelTabTransition();
399
400   tabstrip_->RemoveTabAt(model_index);
401 }
402
403 void BrowserTabStripController::TabSelectionChanged(
404     TabStripModel* tab_strip_model,
405     const ui::ListSelectionModel& old_model) {
406   tabstrip_->SetSelection(old_model, model_->selection_model());
407 }
408
409 void BrowserTabStripController::TabMoved(WebContents* contents,
410                                          int from_model_index,
411                                          int to_model_index) {
412   // Cancel any pending tab transition.
413   hover_tab_selector_.CancelTabTransition();
414
415   // Pass in the TabRendererData as the pinned state may have changed.
416   TabRendererData data;
417   SetTabRendererDataFromModel(contents, to_model_index, &data, EXISTING_TAB);
418   tabstrip_->MoveTab(from_model_index, to_model_index, data);
419 }
420
421 void BrowserTabStripController::TabChangedAt(WebContents* contents,
422                                              int model_index,
423                                              TabChangeType change_type) {
424   if (change_type == TITLE_NOT_LOADING) {
425     tabstrip_->TabTitleChangedNotLoading(model_index);
426     // We'll receive another notification of the change asynchronously.
427     return;
428   }
429
430   SetTabDataAt(contents, model_index);
431 }
432
433 void BrowserTabStripController::TabReplacedAt(TabStripModel* tab_strip_model,
434                                               WebContents* old_contents,
435                                               WebContents* new_contents,
436                                               int model_index) {
437   SetTabDataAt(new_contents, model_index);
438 }
439
440 void BrowserTabStripController::TabPinnedStateChanged(WebContents* contents,
441                                                       int model_index) {
442   // Currently none of the renderers render pinned state differently.
443 }
444
445 void BrowserTabStripController::TabMiniStateChanged(WebContents* contents,
446                                                     int model_index) {
447   SetTabDataAt(contents, model_index);
448 }
449
450 void BrowserTabStripController::TabBlockedStateChanged(WebContents* contents,
451                                                        int model_index) {
452   SetTabDataAt(contents, model_index);
453 }
454
455 void BrowserTabStripController::SetTabRendererDataFromModel(
456     WebContents* contents,
457     int model_index,
458     TabRendererData* data,
459     TabStatus tab_status) {
460   FaviconTabHelper* favicon_tab_helper =
461       FaviconTabHelper::FromWebContents(contents);
462
463   data->favicon = favicon_tab_helper->GetFavicon().AsImageSkia();
464   data->network_state = TabContentsNetworkState(contents);
465   data->title = contents->GetTitle();
466   data->url = contents->GetURL();
467   data->loading = contents->IsLoading();
468   data->crashed_status = contents->GetCrashedStatus();
469   data->incognito = contents->GetBrowserContext()->IsOffTheRecord();
470   data->show_icon = favicon_tab_helper->ShouldDisplayFavicon();
471   data->mini = model_->IsMiniTab(model_index);
472   data->blocked = model_->IsTabBlocked(model_index);
473   data->app = extensions::TabHelper::FromWebContents(contents)->is_app();
474   data->media_state = chrome::GetTabMediaStateForContents(contents);
475 }
476
477 void BrowserTabStripController::SetTabDataAt(content::WebContents* web_contents,
478                                              int model_index) {
479   TabRendererData data;
480   SetTabRendererDataFromModel(web_contents, model_index, &data, EXISTING_TAB);
481   tabstrip_->SetTabData(model_index, data);
482 }
483
484 void BrowserTabStripController::StartHighlightTabsForCommand(
485     TabStripModel::ContextMenuCommand command_id,
486     Tab* tab) {
487   if (command_id == TabStripModel::CommandCloseOtherTabs ||
488       command_id == TabStripModel::CommandCloseTabsToRight) {
489     int model_index = tabstrip_->GetModelIndexOfTab(tab);
490     if (IsValidIndex(model_index)) {
491       std::vector<int> indices =
492           model_->GetIndicesClosedByCommand(model_index, command_id);
493       for (std::vector<int>::const_iterator i(indices.begin());
494            i != indices.end(); ++i) {
495         tabstrip_->StartHighlight(*i);
496       }
497     }
498   }
499 }
500
501 void BrowserTabStripController::StopHighlightTabsForCommand(
502     TabStripModel::ContextMenuCommand command_id,
503     Tab* tab) {
504   if (command_id == TabStripModel::CommandCloseTabsToRight ||
505       command_id == TabStripModel::CommandCloseOtherTabs) {
506     // Just tell all Tabs to stop pulsing - it's safe.
507     tabstrip_->StopAllHighlighting();
508   }
509 }
510
511 void BrowserTabStripController::AddTab(WebContents* contents,
512                                        int index,
513                                        bool is_active) {
514   // Cancel any pending tab transition.
515   hover_tab_selector_.CancelTabTransition();
516
517   TabRendererData data;
518   SetTabRendererDataFromModel(contents, index, &data, NEW_TAB);
519   tabstrip_->AddTabAt(index, data, is_active);
520 }
521
522 void BrowserTabStripController::UpdateLayoutType() {
523   bool adjust_layout = false;
524   TabStripLayoutType layout_type =
525       DetermineTabStripLayout(g_browser_process->local_state(), &adjust_layout);
526   tabstrip_->SetLayoutType(layout_type, adjust_layout);
527 }