• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 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 "chrome/browser/extensions/extension_tab_helper.h"
10 #include "chrome/browser/metrics/user_metrics.h"
11 #include "chrome/browser/profiles/profile.h"
12 #include "chrome/browser/tabs/tab_strip_model.h"
13 #include "chrome/browser/ui/browser.h"
14 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
15 #include "chrome/browser/ui/tabs/tab_menu_model.h"
16 #include "chrome/browser/ui/views/tabs/base_tab_strip.h"
17 #include "chrome/browser/ui/views/tabs/tab_renderer_data.h"
18 #include "chrome/common/url_constants.h"
19 #include "content/browser/renderer_host/render_view_host.h"
20 #include "content/browser/tab_contents/tab_contents.h"
21 #include "content/common/notification_service.h"
22 #include "views/controls/menu/menu_2.h"
23 #include "views/widget/widget.h"
24 
TabContentsNetworkState(TabContents * contents)25 static TabRendererData::NetworkState TabContentsNetworkState(
26     TabContents* contents) {
27   if (!contents || !contents->is_loading())
28     return TabRendererData::NETWORK_STATE_NONE;
29   if (contents->waiting_for_response())
30     return TabRendererData::NETWORK_STATE_WAITING;
31   return TabRendererData::NETWORK_STATE_LOADING;
32 }
33 
34 class BrowserTabStripController::TabContextMenuContents
35     : public ui::SimpleMenuModel::Delegate {
36  public:
TabContextMenuContents(BaseTab * tab,BrowserTabStripController * controller)37   TabContextMenuContents(BaseTab* tab,
38                          BrowserTabStripController* controller)
39       : ALLOW_THIS_IN_INITIALIZER_LIST(
40           model_(this,
41                  controller->model_,
42                  controller->tabstrip_->GetModelIndexOfBaseTab(tab))),
43         tab_(tab),
44         controller_(controller),
45         last_command_(TabStripModel::CommandFirst) {
46     Build();
47   }
~TabContextMenuContents()48   virtual ~TabContextMenuContents() {
49     menu_->CancelMenu();
50     if (controller_)
51       controller_->tabstrip_->StopAllHighlighting();
52   }
53 
Cancel()54   void Cancel() {
55     controller_ = NULL;
56   }
57 
RunMenuAt(const gfx::Point & point)58   void RunMenuAt(const gfx::Point& point) {
59     menu_->RunMenuAt(point, views::Menu2::ALIGN_TOPLEFT);
60     // We could be gone now. Assume |this| is junk!
61   }
62 
63   // Overridden from ui::SimpleMenuModel::Delegate:
IsCommandIdChecked(int command_id) const64   virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
65     return controller_->IsCommandCheckedForTab(
66         static_cast<TabStripModel::ContextMenuCommand>(command_id),
67         tab_);
68   }
IsCommandIdEnabled(int command_id) const69   virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
70     return controller_->IsCommandEnabledForTab(
71         static_cast<TabStripModel::ContextMenuCommand>(command_id),
72         tab_);
73   }
GetAcceleratorForCommandId(int command_id,ui::Accelerator * accelerator)74   virtual bool GetAcceleratorForCommandId(
75       int command_id,
76       ui::Accelerator* accelerator) OVERRIDE {
77     int browser_cmd;
78     return TabStripModel::ContextMenuCommandToBrowserCommand(command_id,
79                                                              &browser_cmd) ?
80         controller_->tabstrip_->GetWidget()->GetAccelerator(browser_cmd,
81                                                             accelerator) :
82         false;
83   }
CommandIdHighlighted(int command_id)84   virtual void CommandIdHighlighted(int command_id) OVERRIDE {
85     controller_->StopHighlightTabsForCommand(last_command_, tab_);
86     last_command_ = static_cast<TabStripModel::ContextMenuCommand>(command_id);
87     controller_->StartHighlightTabsForCommand(last_command_, tab_);
88   }
ExecuteCommand(int command_id)89   virtual void ExecuteCommand(int command_id) OVERRIDE {
90     // Executing the command destroys |this|, and can also end up destroying
91     // |controller_| (e.g. for |CommandUseVerticalTabs|). So stop the highlights
92     // before executing the command.
93     controller_->tabstrip_->StopAllHighlighting();
94     controller_->ExecuteCommandForTab(
95         static_cast<TabStripModel::ContextMenuCommand>(command_id),
96         tab_);
97   }
98 
MenuClosed()99   virtual void MenuClosed() OVERRIDE {
100     if (controller_)
101       controller_->tabstrip_->StopAllHighlighting();
102   }
103 
104  private:
Build()105   void Build() {
106     menu_.reset(new views::Menu2(&model_));
107   }
108 
109   TabMenuModel model_;
110   scoped_ptr<views::Menu2> menu_;
111 
112   // The tab we're showing a menu for.
113   BaseTab* tab_;
114 
115   // A pointer back to our hosting controller, for command state information.
116   BrowserTabStripController* controller_;
117 
118   // The last command that was selected, so that we can start/stop highlighting
119   // appropriately as the user moves through the menu.
120   TabStripModel::ContextMenuCommand last_command_;
121 
122   DISALLOW_COPY_AND_ASSIGN(TabContextMenuContents);
123 };
124 
125 ////////////////////////////////////////////////////////////////////////////////
126 // BrowserTabStripController, public:
127 
BrowserTabStripController(Browser * browser,TabStripModel * model)128 BrowserTabStripController::BrowserTabStripController(Browser* browser,
129                                                      TabStripModel* model)
130     : model_(model),
131       tabstrip_(NULL),
132       browser_(browser) {
133   model_->AddObserver(this);
134 
135   notification_registrar_.Add(this,
136       NotificationType::TAB_CLOSEABLE_STATE_CHANGED,
137       NotificationService::AllSources());
138 }
139 
~BrowserTabStripController()140 BrowserTabStripController::~BrowserTabStripController() {
141   // When we get here the TabStrip is being deleted. We need to explicitly
142   // cancel the menu, otherwise it may try to invoke something on the tabstrip
143   // from it's destructor.
144   if (context_menu_contents_.get())
145     context_menu_contents_->Cancel();
146 
147   model_->RemoveObserver(this);
148 }
149 
InitFromModel(BaseTabStrip * tabstrip)150 void BrowserTabStripController::InitFromModel(BaseTabStrip* tabstrip) {
151   tabstrip_ = tabstrip;
152   // Walk the model, calling our insertion observer method for each item within
153   // it.
154   for (int i = 0; i < model_->count(); ++i)
155     TabInsertedAt(model_->GetTabContentsAt(i), i, model_->active_index() == i);
156 }
157 
IsCommandEnabledForTab(TabStripModel::ContextMenuCommand command_id,BaseTab * tab) const158 bool BrowserTabStripController::IsCommandEnabledForTab(
159     TabStripModel::ContextMenuCommand command_id,
160     BaseTab* tab) const {
161   int model_index = tabstrip_->GetModelIndexOfBaseTab(tab);
162   return model_->ContainsIndex(model_index) ?
163       model_->IsContextMenuCommandEnabled(model_index, command_id) : false;
164 }
165 
IsCommandCheckedForTab(TabStripModel::ContextMenuCommand command_id,BaseTab * tab) const166 bool BrowserTabStripController::IsCommandCheckedForTab(
167     TabStripModel::ContextMenuCommand command_id,
168     BaseTab* tab) const {
169   int model_index = tabstrip_->GetModelIndexOfBaseTab(tab);
170   return model_->ContainsIndex(model_index) ?
171       model_->IsContextMenuCommandChecked(model_index, command_id) : false;
172 }
173 
ExecuteCommandForTab(TabStripModel::ContextMenuCommand command_id,BaseTab * tab)174 void BrowserTabStripController::ExecuteCommandForTab(
175     TabStripModel::ContextMenuCommand command_id,
176     BaseTab* tab) {
177   int model_index = tabstrip_->GetModelIndexOfBaseTab(tab);
178   if (model_->ContainsIndex(model_index))
179     model_->ExecuteContextMenuCommand(model_index, command_id);
180 }
181 
IsTabPinned(BaseTab * tab) const182 bool BrowserTabStripController::IsTabPinned(BaseTab* tab) const {
183   return IsTabPinned(tabstrip_->GetModelIndexOfBaseTab(tab));
184 }
185 
GetCount() const186 int BrowserTabStripController::GetCount() const {
187   return model_->count();
188 }
189 
IsValidIndex(int index) const190 bool BrowserTabStripController::IsValidIndex(int index) const {
191   return model_->ContainsIndex(index);
192 }
193 
IsActiveTab(int model_index) const194 bool BrowserTabStripController::IsActiveTab(int model_index) const {
195   return model_->active_index() == model_index;
196 }
197 
IsTabSelected(int model_index) const198 bool BrowserTabStripController::IsTabSelected(int model_index) const {
199   return model_->IsTabSelected(model_index);
200 }
201 
IsTabPinned(int model_index) const202 bool BrowserTabStripController::IsTabPinned(int model_index) const {
203   return model_->ContainsIndex(model_index) && model_->IsTabPinned(model_index);
204 }
205 
IsTabCloseable(int model_index) const206 bool BrowserTabStripController::IsTabCloseable(int model_index) const {
207   return !model_->ContainsIndex(model_index) ||
208       model_->delegate()->CanCloseTab();
209 }
210 
IsNewTabPage(int model_index) const211 bool BrowserTabStripController::IsNewTabPage(int model_index) const {
212   return model_->ContainsIndex(model_index) &&
213       model_->GetTabContentsAt(model_index)->tab_contents()->GetURL() ==
214       GURL(chrome::kChromeUINewTabURL);
215 }
216 
SelectTab(int model_index)217 void BrowserTabStripController::SelectTab(int model_index) {
218   model_->ActivateTabAt(model_index, true);
219 }
220 
ExtendSelectionTo(int model_index)221 void BrowserTabStripController::ExtendSelectionTo(int model_index) {
222   model_->ExtendSelectionTo(model_index);
223 }
224 
ToggleSelected(int model_index)225 void BrowserTabStripController::ToggleSelected(int model_index) {
226   model_->ToggleSelectionAt(model_index);
227 }
228 
AddSelectionFromAnchorTo(int model_index)229 void BrowserTabStripController::AddSelectionFromAnchorTo(int model_index) {
230   model_->AddSelectionFromAnchorTo(model_index);
231 }
232 
CloseTab(int model_index)233 void BrowserTabStripController::CloseTab(int model_index) {
234   tabstrip_->PrepareForCloseAt(model_index);
235   model_->CloseTabContentsAt(model_index,
236                              TabStripModel::CLOSE_USER_GESTURE |
237                              TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
238 }
239 
ShowContextMenuForTab(BaseTab * tab,const gfx::Point & p)240 void BrowserTabStripController::ShowContextMenuForTab(BaseTab* tab,
241                                                       const gfx::Point& p) {
242   context_menu_contents_.reset(new TabContextMenuContents(tab, this));
243   context_menu_contents_->RunMenuAt(p);
244 }
245 
UpdateLoadingAnimations()246 void BrowserTabStripController::UpdateLoadingAnimations() {
247   // Don't use the model count here as it's possible for this to be invoked
248   // before we've applied an update from the model (Browser::TabInsertedAt may
249   // be processed before us and invokes this).
250   for (int tab_index = 0, tab_count = tabstrip_->tab_count();
251        tab_index < tab_count; ++tab_index) {
252     BaseTab* tab = tabstrip_->base_tab_at_tab_index(tab_index);
253     int model_index = tabstrip_->GetModelIndexOfBaseTab(tab);
254     if (model_->ContainsIndex(model_index)) {
255       TabContentsWrapper* contents = model_->GetTabContentsAt(model_index);
256       tab->UpdateLoadingAnimation(
257           TabContentsNetworkState(contents->tab_contents()));
258     }
259   }
260 }
261 
HasAvailableDragActions() const262 int BrowserTabStripController::HasAvailableDragActions() const {
263   return model_->delegate()->GetDragActions();
264 }
265 
PerformDrop(bool drop_before,int index,const GURL & url)266 void BrowserTabStripController::PerformDrop(bool drop_before,
267                                             int index,
268                                             const GURL& url) {
269   browser::NavigateParams params(browser_, url, PageTransition::LINK);
270   params.tabstrip_index = index;
271 
272   if (drop_before) {
273     UserMetrics::RecordAction(UserMetricsAction("Tab_DropURLBetweenTabs"),
274                               model_->profile());
275     params.disposition = NEW_FOREGROUND_TAB;
276   } else {
277     UserMetrics::RecordAction(UserMetricsAction("Tab_DropURLOnTab"),
278                               model_->profile());
279     params.disposition = CURRENT_TAB;
280     params.source_contents = model_->GetTabContentsAt(index);
281   }
282 
283   browser::Navigate(&params);
284 }
285 
IsCompatibleWith(BaseTabStrip * other) const286 bool BrowserTabStripController::IsCompatibleWith(BaseTabStrip* other) const {
287   Profile* other_profile =
288       static_cast<BrowserTabStripController*>(other->controller())->profile();
289   return other_profile == profile();
290 }
291 
CreateNewTab()292 void BrowserTabStripController::CreateNewTab() {
293   UserMetrics::RecordAction(UserMetricsAction("NewTab_Button"),
294                             model_->profile());
295 
296   model_->delegate()->AddBlankTab(true);
297 }
298 
299 ////////////////////////////////////////////////////////////////////////////////
300 // BrowserTabStripController, TabStripModelObserver implementation:
301 
TabInsertedAt(TabContentsWrapper * contents,int model_index,bool active)302 void BrowserTabStripController::TabInsertedAt(TabContentsWrapper* contents,
303                                               int model_index,
304                                               bool active) {
305   DCHECK(contents);
306   DCHECK(model_index == TabStripModel::kNoTab ||
307          model_->ContainsIndex(model_index));
308 
309   TabRendererData data;
310   SetTabRendererDataFromModel(contents->tab_contents(), model_index, &data);
311   tabstrip_->AddTabAt(model_index, data);
312 }
313 
TabDetachedAt(TabContentsWrapper * contents,int model_index)314 void BrowserTabStripController::TabDetachedAt(TabContentsWrapper* contents,
315                                               int model_index) {
316   tabstrip_->RemoveTabAt(model_index);
317 }
318 
TabSelectedAt(TabContentsWrapper * old_contents,TabContentsWrapper * contents,int model_index,bool user_gesture)319 void BrowserTabStripController::TabSelectedAt(TabContentsWrapper* old_contents,
320                                               TabContentsWrapper* contents,
321                                               int model_index,
322                                               bool user_gesture) {
323   tabstrip_->SelectTabAt(model_->GetIndexOfTabContents(old_contents),
324                          model_index);
325 }
326 
TabMoved(TabContentsWrapper * contents,int from_model_index,int to_model_index)327 void BrowserTabStripController::TabMoved(TabContentsWrapper* contents,
328                                          int from_model_index,
329                                          int to_model_index) {
330   // Update the data first as the pinned state may have changed.
331   TabRendererData data;
332   SetTabRendererDataFromModel(contents->tab_contents(), to_model_index, &data);
333   tabstrip_->SetTabData(from_model_index, data);
334 
335   tabstrip_->MoveTab(from_model_index, to_model_index);
336 }
337 
TabChangedAt(TabContentsWrapper * contents,int model_index,TabChangeType change_type)338 void BrowserTabStripController::TabChangedAt(TabContentsWrapper* contents,
339                                              int model_index,
340                                              TabChangeType change_type) {
341   if (change_type == TITLE_NOT_LOADING) {
342     tabstrip_->TabTitleChangedNotLoading(model_index);
343     // We'll receive another notification of the change asynchronously.
344     return;
345   }
346 
347   SetTabDataAt(contents, model_index);
348 }
349 
TabReplacedAt(TabStripModel * tab_strip_model,TabContentsWrapper * old_contents,TabContentsWrapper * new_contents,int model_index)350 void BrowserTabStripController::TabReplacedAt(TabStripModel* tab_strip_model,
351                                               TabContentsWrapper* old_contents,
352                                               TabContentsWrapper* new_contents,
353                                               int model_index) {
354   SetTabDataAt(new_contents, model_index);
355 }
356 
TabPinnedStateChanged(TabContentsWrapper * contents,int model_index)357 void BrowserTabStripController::TabPinnedStateChanged(
358     TabContentsWrapper* contents,
359     int model_index) {
360   // Currently none of the renderers render pinned state differently.
361 }
362 
TabMiniStateChanged(TabContentsWrapper * contents,int model_index)363 void BrowserTabStripController::TabMiniStateChanged(
364     TabContentsWrapper* contents,
365     int model_index) {
366   SetTabDataAt(contents, model_index);
367 }
368 
TabBlockedStateChanged(TabContentsWrapper * contents,int model_index)369 void BrowserTabStripController::TabBlockedStateChanged(
370     TabContentsWrapper* contents,
371     int model_index) {
372   SetTabDataAt(contents, model_index);
373 }
374 
SetTabDataAt(TabContentsWrapper * contents,int model_index)375 void BrowserTabStripController::SetTabDataAt(
376     TabContentsWrapper* contents,
377     int model_index) {
378   TabRendererData data;
379   SetTabRendererDataFromModel(contents->tab_contents(), model_index, &data);
380   tabstrip_->SetTabData(model_index, data);
381 }
382 
SetTabRendererDataFromModel(TabContents * contents,int model_index,TabRendererData * data)383 void BrowserTabStripController::SetTabRendererDataFromModel(
384     TabContents* contents,
385     int model_index,
386     TabRendererData* data) {
387   SkBitmap* app_icon = NULL;
388   TabContentsWrapper* wrapper =
389       TabContentsWrapper::GetCurrentWrapperForContents(contents);
390 
391   // Extension App icons are slightly larger than favicons, so only allow
392   // them if permitted by the model.
393   if (model_->delegate()->LargeIconsPermitted())
394     app_icon = wrapper->extension_tab_helper()->GetExtensionAppIcon();
395 
396   if (app_icon)
397     data->favicon = *app_icon;
398   else
399     data->favicon = contents->GetFavicon();
400   data->network_state = TabContentsNetworkState(contents);
401   data->title = contents->GetTitle();
402   data->url = contents->GetURL();
403   data->loading = contents->is_loading();
404   data->crashed_status = contents->crashed_status();
405   data->incognito = contents->profile()->IsOffTheRecord();
406   data->show_icon = contents->ShouldDisplayFavicon();
407   data->mini = model_->IsMiniTab(model_index);
408   data->blocked = model_->IsTabBlocked(model_index);
409   data->app = wrapper->extension_tab_helper()->is_app();
410 }
411 
StartHighlightTabsForCommand(TabStripModel::ContextMenuCommand command_id,BaseTab * tab)412 void BrowserTabStripController::StartHighlightTabsForCommand(
413     TabStripModel::ContextMenuCommand command_id,
414     BaseTab* tab) {
415   if (command_id == TabStripModel::CommandCloseOtherTabs ||
416       command_id == TabStripModel::CommandCloseTabsToRight) {
417     int model_index = tabstrip_->GetModelIndexOfBaseTab(tab);
418     if (IsValidIndex(model_index)) {
419       std::vector<int> indices =
420           model_->GetIndicesClosedByCommand(model_index, command_id);
421       for (std::vector<int>::const_iterator i = indices.begin();
422            i != indices.end(); ++i) {
423         tabstrip_->StartHighlight(*i);
424       }
425     }
426   }
427 }
428 
StopHighlightTabsForCommand(TabStripModel::ContextMenuCommand command_id,BaseTab * tab)429 void BrowserTabStripController::StopHighlightTabsForCommand(
430     TabStripModel::ContextMenuCommand command_id,
431     BaseTab* tab) {
432   if (command_id == TabStripModel::CommandCloseTabsToRight ||
433       command_id == TabStripModel::CommandCloseOtherTabs) {
434     // Just tell all Tabs to stop pulsing - it's safe.
435     tabstrip_->StopAllHighlighting();
436   }
437 }
438 
439 ////////////////////////////////////////////////////////////////////////////////
440 // BrowserTabStripController, NotificationObserver implementation:
441 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)442 void BrowserTabStripController::Observe(NotificationType type,
443     const NotificationSource& source, const NotificationDetails& details) {
444   DCHECK(type.value == NotificationType::TAB_CLOSEABLE_STATE_CHANGED);
445   // Note that this notification may be fired during a model mutation and
446   // possibly before the tabstrip has processed the change.
447   // Here, we just re-layout each existing tab to reflect the change in its
448   // closeable state, and then schedule paint for entire tabstrip.
449   for (int i = 0; i < tabstrip_->tab_count(); ++i) {
450     tabstrip_->base_tab_at_tab_index(i)->Layout();
451   }
452   tabstrip_->SchedulePaint();
453 }
454