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(¶ms);
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