OLD | NEW |
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/tabs/tab_strip_model.h" | 5 #include "chrome/browser/tabs/tab_strip_model.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 | 8 |
9 #include "base/command_line.h" | 9 #include "base/command_line.h" |
10 #include "base/stl_util-inl.h" | 10 #include "base/stl_util-inl.h" |
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
73 } | 73 } |
74 | 74 |
75 void TabStripModel::AddObserver(TabStripModelObserver* observer) { | 75 void TabStripModel::AddObserver(TabStripModelObserver* observer) { |
76 observers_.AddObserver(observer); | 76 observers_.AddObserver(observer); |
77 } | 77 } |
78 | 78 |
79 void TabStripModel::RemoveObserver(TabStripModelObserver* observer) { | 79 void TabStripModel::RemoveObserver(TabStripModelObserver* observer) { |
80 observers_.RemoveObserver(observer); | 80 observers_.RemoveObserver(observer); |
81 } | 81 } |
82 | 82 |
83 bool TabStripModel::HasNonPhantomTabs() const { | |
84 for (int i = 0; i < count(); i++) { | |
85 if (!IsPhantomTab(i)) | |
86 return true; | |
87 } | |
88 return false; | |
89 } | |
90 | |
91 void TabStripModel::SetInsertionPolicy(InsertionPolicy policy) { | 83 void TabStripModel::SetInsertionPolicy(InsertionPolicy policy) { |
92 order_controller_->set_insertion_policy(policy); | 84 order_controller_->set_insertion_policy(policy); |
93 } | 85 } |
94 | 86 |
95 TabStripModel::InsertionPolicy TabStripModel::insertion_policy() const { | 87 TabStripModel::InsertionPolicy TabStripModel::insertion_policy() const { |
96 return order_controller_->insertion_policy(); | 88 return order_controller_->insertion_policy(); |
97 } | 89 } |
98 | 90 |
99 bool TabStripModel::HasObserver(TabStripModelObserver* observer) { | 91 bool TabStripModel::HasObserver(TabStripModelObserver* observer) { |
100 return observers_.HasObserver(observer); | 92 return observers_.HasObserver(observer); |
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
166 InternalCloseTabs(closing_tabs, CLOSE_NONE); | 158 InternalCloseTabs(closing_tabs, CLOSE_NONE); |
167 } | 159 } |
168 | 160 |
169 TabContents* TabStripModel::DetachTabContentsAt(int index) { | 161 TabContents* TabStripModel::DetachTabContentsAt(int index) { |
170 if (contents_data_.empty()) | 162 if (contents_data_.empty()) |
171 return NULL; | 163 return NULL; |
172 | 164 |
173 DCHECK(ContainsIndex(index)); | 165 DCHECK(ContainsIndex(index)); |
174 TabContents* removed_contents = GetContentsAt(index); | 166 TabContents* removed_contents = GetContentsAt(index); |
175 int next_selected_index = | 167 int next_selected_index = |
176 order_controller_->DetermineNewSelectedIndex(index, true); | 168 order_controller_->DetermineNewSelectedIndex(index); |
177 delete contents_data_.at(index); | 169 delete contents_data_.at(index); |
178 contents_data_.erase(contents_data_.begin() + index); | 170 contents_data_.erase(contents_data_.begin() + index); |
179 next_selected_index = IndexOfNextNonPhantomTab(next_selected_index, -1); | 171 next_selected_index = IndexOfNextTab(next_selected_index); |
180 if (!HasNonPhantomTabs()) | 172 if (empty()) |
181 closing_all_ = true; | 173 closing_all_ = true; |
182 TabStripModelObservers::Iterator iter(observers_); | 174 TabStripModelObservers::Iterator iter(observers_); |
183 while (TabStripModelObserver* obs = iter.GetNext()) { | 175 while (TabStripModelObserver* obs = iter.GetNext()) { |
184 obs->TabDetachedAt(removed_contents, index); | 176 obs->TabDetachedAt(removed_contents, index); |
185 if (!HasNonPhantomTabs()) | 177 if (empty()) |
186 obs->TabStripEmpty(); | 178 obs->TabStripEmpty(); |
187 } | 179 } |
188 if (HasNonPhantomTabs()) { | 180 if (!empty()) { |
189 if (index == selected_index_) { | 181 if (index == selected_index_) { |
190 ChangeSelectedContentsFrom(removed_contents, next_selected_index, false); | 182 ChangeSelectedContentsFrom(removed_contents, next_selected_index, false); |
191 } else if (index < selected_index_) { | 183 } else if (index < selected_index_) { |
192 // The selected tab didn't change, but its position shifted; update our | 184 // The selected tab didn't change, but its position shifted; update our |
193 // index to continue to point at it. | 185 // index to continue to point at it. |
194 --selected_index_; | 186 --selected_index_; |
195 } | 187 } |
196 } | 188 } |
197 return removed_contents; | 189 return removed_contents; |
198 } | 190 } |
(...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
289 return contents_data_.at(index)->opener; | 281 return contents_data_.at(index)->opener; |
290 } | 282 } |
291 | 283 |
292 int TabStripModel::GetIndexOfNextTabContentsOpenedBy( | 284 int TabStripModel::GetIndexOfNextTabContentsOpenedBy( |
293 const NavigationController* opener, int start_index, bool use_group) const { | 285 const NavigationController* opener, int start_index, bool use_group) const { |
294 DCHECK(opener); | 286 DCHECK(opener); |
295 DCHECK(ContainsIndex(start_index)); | 287 DCHECK(ContainsIndex(start_index)); |
296 | 288 |
297 // Check tabs after start_index first. | 289 // Check tabs after start_index first. |
298 for (int i = start_index + 1; i < count(); ++i) { | 290 for (int i = start_index + 1; i < count(); ++i) { |
299 if (OpenerMatches(contents_data_[i], opener, use_group) && | 291 if (OpenerMatches(contents_data_[i], opener, use_group)) |
300 !IsPhantomTab(i)) { | |
301 return i; | 292 return i; |
302 } | |
303 } | 293 } |
304 // Then check tabs before start_index, iterating backwards. | 294 // Then check tabs before start_index, iterating backwards. |
305 for (int i = start_index - 1; i >= 0; --i) { | 295 for (int i = start_index - 1; i >= 0; --i) { |
306 if (OpenerMatches(contents_data_[i], opener, use_group) && | 296 if (OpenerMatches(contents_data_[i], opener, use_group)) |
307 !IsPhantomTab(i)) { | |
308 return i; | 297 return i; |
309 } | |
310 } | 298 } |
311 return kNoTab; | 299 return kNoTab; |
312 } | 300 } |
313 | 301 |
314 int TabStripModel::GetIndexOfFirstTabContentsOpenedBy( | 302 int TabStripModel::GetIndexOfFirstTabContentsOpenedBy( |
315 const NavigationController* opener, | 303 const NavigationController* opener, |
316 int start_index) const { | 304 int start_index) const { |
317 DCHECK(opener); | 305 DCHECK(opener); |
318 DCHECK(ContainsIndex(start_index)); | 306 DCHECK(ContainsIndex(start_index)); |
319 | 307 |
320 for (int i = 0; i < start_index; ++i) { | 308 for (int i = 0; i < start_index; ++i) { |
321 if (contents_data_[i]->opener == opener && !IsPhantomTab(i)) | 309 if (contents_data_[i]->opener == opener) |
322 return i; | 310 return i; |
323 } | 311 } |
324 return kNoTab; | 312 return kNoTab; |
325 } | 313 } |
326 | 314 |
327 int TabStripModel::GetIndexOfLastTabContentsOpenedBy( | 315 int TabStripModel::GetIndexOfLastTabContentsOpenedBy( |
328 const NavigationController* opener, int start_index) const { | 316 const NavigationController* opener, int start_index) const { |
329 DCHECK(opener); | 317 DCHECK(opener); |
330 DCHECK(ContainsIndex(start_index)); | 318 DCHECK(ContainsIndex(start_index)); |
331 | 319 |
332 TabContentsDataVector::const_iterator end = | 320 TabContentsDataVector::const_iterator end = |
333 contents_data_.begin() + start_index; | 321 contents_data_.begin() + start_index; |
334 TabContentsDataVector::const_iterator iter = contents_data_.end(); | 322 TabContentsDataVector::const_iterator iter = contents_data_.end(); |
335 TabContentsDataVector::const_iterator next; | 323 TabContentsDataVector::const_iterator next; |
336 for (; iter != end; --iter) { | 324 for (; iter != end; --iter) { |
337 next = iter - 1; | 325 next = iter - 1; |
338 if (next == end) | 326 if (next == end) |
339 break; | 327 break; |
340 if ((*next)->opener == opener && | 328 if ((*next)->opener == opener) |
341 !IsPhantomTab(static_cast<int>(next - contents_data_.begin()))) { | |
342 return static_cast<int>(next - contents_data_.begin()); | 329 return static_cast<int>(next - contents_data_.begin()); |
343 } | |
344 } | 330 } |
345 return kNoTab; | 331 return kNoTab; |
346 } | 332 } |
347 | 333 |
348 void TabStripModel::TabNavigating(TabContents* contents, | 334 void TabStripModel::TabNavigating(TabContents* contents, |
349 PageTransition::Type transition) { | 335 PageTransition::Type transition) { |
350 if (ShouldForgetOpenersForTransition(transition)) { | 336 if (ShouldForgetOpenersForTransition(transition)) { |
351 // Don't forget the openers if this tab is a New Tab page opened at the | 337 // Don't forget the openers if this tab is a New Tab page opened at the |
352 // end of the TabStrip (e.g. by pressing Ctrl+T). Give the user one | 338 // end of the TabStrip (e.g. by pressing Ctrl+T). Give the user one |
353 // navigation of one of these transition types before resetting the | 339 // navigation of one of these transition types before resetting the |
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
447 bool TabStripModel::IsToolbarVisible(int index) const { | 433 bool TabStripModel::IsToolbarVisible(int index) const { |
448 Extension* extension_app = GetTabContentsAt(index)->extension_app(); | 434 Extension* extension_app = GetTabContentsAt(index)->extension_app(); |
449 if (!extension_app) | 435 if (!extension_app) |
450 return true; | 436 return true; |
451 | 437 |
452 ExtensionsService* service = profile()->GetExtensionsService(); | 438 ExtensionsService* service = profile()->GetExtensionsService(); |
453 ExtensionPrefs* prefs = service->extension_prefs(); | 439 ExtensionPrefs* prefs = service->extension_prefs(); |
454 return prefs->AreAppTabToolbarsVisible(extension_app->id()); | 440 return prefs->AreAppTabToolbarsVisible(extension_app->id()); |
455 } | 441 } |
456 | 442 |
457 bool TabStripModel::IsPhantomTab(int index) const { | |
458 return IsTabPinned(index) && | |
459 GetTabContentsAt(index)->controller().needs_reload(); | |
460 } | |
461 | |
462 bool TabStripModel::IsTabBlocked(int index) const { | 443 bool TabStripModel::IsTabBlocked(int index) const { |
463 return contents_data_[index]->blocked; | 444 return contents_data_[index]->blocked; |
464 } | 445 } |
465 | 446 |
466 int TabStripModel::IndexOfFirstNonMiniTab() const { | 447 int TabStripModel::IndexOfFirstNonMiniTab() const { |
467 for (size_t i = 0; i < contents_data_.size(); ++i) { | 448 for (size_t i = 0; i < contents_data_.size(); ++i) { |
468 if (!contents_data_[i]->contents->is_app() && !contents_data_[i]->pinned) | 449 if (!contents_data_[i]->contents->is_app() && !contents_data_[i]->pinned) |
469 return static_cast<int>(i); | 450 return static_cast<int>(i); |
470 } | 451 } |
471 // No mini-tabs. | 452 // No mini-tabs. |
(...skipping 215 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
687 UserMetrics::RecordAction(UserMetricsAction("TabContextMenu_RestoreTab"), | 668 UserMetrics::RecordAction(UserMetricsAction("TabContextMenu_RestoreTab"), |
688 profile_); | 669 profile_); |
689 delegate_->RestoreTab(); | 670 delegate_->RestoreTab(); |
690 break; | 671 break; |
691 } | 672 } |
692 case CommandTogglePinned: { | 673 case CommandTogglePinned: { |
693 UserMetrics::RecordAction( | 674 UserMetrics::RecordAction( |
694 UserMetricsAction("TabContextMenu_TogglePinned"), | 675 UserMetricsAction("TabContextMenu_TogglePinned"), |
695 profile_); | 676 profile_); |
696 | 677 |
697 if (IsPhantomTab(context_index)) { | 678 SelectTabContentsAt(context_index, true); |
698 // The tab is a phantom tab, close it. | 679 SetTabPinned(context_index, !IsTabPinned(context_index)); |
699 CloseTabContentsAt(context_index, | |
700 CLOSE_USER_GESTURE | CLOSE_CREATE_HISTORICAL_TAB); | |
701 } else { | |
702 SelectTabContentsAt(context_index, true); | |
703 SetTabPinned(context_index, !IsTabPinned(context_index)); | |
704 } | |
705 break; | 680 break; |
706 } | 681 } |
707 case CommandToggleToolbar: { | 682 case CommandToggleToolbar: { |
708 UserMetrics::RecordAction( | 683 UserMetrics::RecordAction( |
709 UserMetricsAction("TabContextMenu_ToggleToolbar"), | 684 UserMetricsAction("TabContextMenu_ToggleToolbar"), |
710 profile_); | 685 profile_); |
711 | 686 |
712 SelectTabContentsAt(context_index, true); | 687 SelectTabContentsAt(context_index, true); |
713 | 688 |
714 Extension* extension_app = | 689 Extension* extension_app = |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
790 const NotificationDetails& details) { | 765 const NotificationDetails& details) { |
791 switch (type.value) { | 766 switch (type.value) { |
792 case NotificationType::TAB_CONTENTS_DESTROYED: { | 767 case NotificationType::TAB_CONTENTS_DESTROYED: { |
793 // Sometimes, on qemu, it seems like a TabContents object can be destroyed | 768 // Sometimes, on qemu, it seems like a TabContents object can be destroyed |
794 // while we still have a reference to it. We need to break this reference | 769 // while we still have a reference to it. We need to break this reference |
795 // here so we don't crash later. | 770 // here so we don't crash later. |
796 int index = GetIndexOfTabContents(Source<TabContents>(source).ptr()); | 771 int index = GetIndexOfTabContents(Source<TabContents>(source).ptr()); |
797 if (index != TabStripModel::kNoTab) { | 772 if (index != TabStripModel::kNoTab) { |
798 // Note that we only detach the contents here, not close it - it's | 773 // Note that we only detach the contents here, not close it - it's |
799 // already been closed. We just want to undo our bookkeeping. | 774 // already been closed. We just want to undo our bookkeeping. |
800 if (ShouldMakePhantomOnClose(index)) { | 775 DetachTabContentsAt(index); |
801 // We don't actually allow pinned tabs to close. Instead they become | |
802 // phantom. | |
803 MakePhantom(index); | |
804 } else { | |
805 DetachTabContentsAt(index); | |
806 } | |
807 } | 776 } |
808 break; | 777 break; |
809 } | 778 } |
810 | 779 |
811 case NotificationType::EXTENSION_UNLOADED: { | 780 case NotificationType::EXTENSION_UNLOADED: { |
812 Extension* extension = Details<Extension>(details).ptr(); | 781 Extension* extension = Details<Extension>(details).ptr(); |
813 // Iterate backwards as we may remove items while iterating. | 782 // Iterate backwards as we may remove items while iterating. |
814 for (int i = count() - 1; i >= 0; i--) { | 783 for (int i = count() - 1; i >= 0; i--) { |
815 TabContents* contents = GetTabContentsAt(i); | 784 TabContents* contents = GetTabContentsAt(i); |
816 if (contents->extension_app() == extension) { | 785 if (contents->extension_app() == extension) { |
(...skipping 147 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
964 int index = GetIndexOfTabContents(contents); | 933 int index = GetIndexOfTabContents(contents); |
965 contents_data_.at(index)->opener = &opener->controller(); | 934 contents_data_.at(index)->opener = &opener->controller(); |
966 } | 935 } |
967 | 936 |
968 void TabStripModel::SelectRelativeTab(bool next) { | 937 void TabStripModel::SelectRelativeTab(bool next) { |
969 // This may happen during automated testing or if a user somehow buffers | 938 // This may happen during automated testing or if a user somehow buffers |
970 // many key accelerators. | 939 // many key accelerators. |
971 if (contents_data_.empty()) | 940 if (contents_data_.empty()) |
972 return; | 941 return; |
973 | 942 |
974 // Skip pinned-app-phantom tabs when iterating. | |
975 int index = selected_index_; | |
976 int delta = next ? 1 : -1; | 943 int delta = next ? 1 : -1; |
977 do { | 944 int index = (selected_index_ + count() + delta) % count(); |
978 index = (index + count() + delta) % count(); | |
979 } while (index != selected_index_ && IsPhantomTab(index)); | |
980 SelectTabContentsAt(index, true); | 945 SelectTabContentsAt(index, true); |
981 } | 946 } |
982 | 947 |
983 int TabStripModel::IndexOfNextNonPhantomTab(int index, | 948 int TabStripModel::IndexOfNextTab(int index) { |
984 int ignore_index) { | 949 return (index == kNoTab || empty()) ? |
985 if (index == kNoTab) | 950 index : std::min(count() - 1, std::max(0, index)); |
986 return kNoTab; | |
987 | |
988 if (empty()) | |
989 return index; | |
990 | |
991 index = std::min(count() - 1, std::max(0, index)); | |
992 int start = index; | |
993 do { | |
994 if (index != ignore_index && !IsPhantomTab(index)) | |
995 return index; | |
996 index = (index + 1) % count(); | |
997 } while (index != start); | |
998 | |
999 // All phantom tabs. | |
1000 return start; | |
1001 } | 951 } |
1002 | 952 |
1003 bool TabStripModel::ShouldMakePhantomOnClose(int index) { | |
1004 if (IsTabPinned(index) && !IsPhantomTab(index) && !closing_all_ && | |
1005 profile()) { | |
1006 if (!IsAppTab(index)) | |
1007 return true; // Always make non-app tabs go phantom. | |
1008 | |
1009 ExtensionsService* extension_service = profile()->GetExtensionsService(); | |
1010 if (!extension_service) | |
1011 return false; | |
1012 | |
1013 Extension* extension_app = GetTabContentsAt(index)->extension_app(); | |
1014 DCHECK(extension_app); | |
1015 | |
1016 // Only allow the tab to be made phantom if the extension still exists. | |
1017 return extension_service->GetExtensionById(extension_app->id(), | |
1018 false) != NULL; | |
1019 } | |
1020 return false; | |
1021 } | |
1022 | |
1023 void TabStripModel::MakePhantom(int index) { | |
1024 TabContents* old_contents = GetContentsAt(index); | |
1025 TabContents* new_contents = old_contents->CloneAndMakePhantom(); | |
1026 | |
1027 contents_data_[index]->contents = new_contents; | |
1028 | |
1029 // And notify observers. | |
1030 FOR_EACH_OBSERVER(TabStripModelObserver, observers_, | |
1031 TabReplacedAt(old_contents, new_contents, index)); | |
1032 | |
1033 if (selected_index_ == index && HasNonPhantomTabs()) { | |
1034 // Change the selection, otherwise we're going to force the phantom tab | |
1035 // to become selected. | |
1036 // NOTE: we must do this after the call to Replace otherwise browser's | |
1037 // TabSelectedAt will send out updates for the old TabContents which we've | |
1038 // already told observers has been closed (we sent out TabClosing at). | |
1039 int new_selected_index = | |
1040 order_controller_->DetermineNewSelectedIndex(index, false); | |
1041 new_selected_index = IndexOfNextNonPhantomTab(new_selected_index, | |
1042 index); | |
1043 SelectTabContentsAt(new_selected_index, true); | |
1044 } | |
1045 | |
1046 if (!HasNonPhantomTabs()) | |
1047 FOR_EACH_OBSERVER(TabStripModelObserver, observers_, TabStripEmpty()); | |
1048 } | |
1049 | |
1050 | |
1051 void TabStripModel::MoveTabContentsAtImpl(int index, int to_position, | 953 void TabStripModel::MoveTabContentsAtImpl(int index, int to_position, |
1052 bool select_after_move) { | 954 bool select_after_move) { |
1053 TabContentsData* moved_data = contents_data_.at(index); | 955 TabContentsData* moved_data = contents_data_.at(index); |
1054 contents_data_.erase(contents_data_.begin() + index); | 956 contents_data_.erase(contents_data_.begin() + index); |
1055 contents_data_.insert(contents_data_.begin() + to_position, moved_data); | 957 contents_data_.insert(contents_data_.begin() + to_position, moved_data); |
1056 | 958 |
1057 // if !select_after_move, keep the same tab selected as was selected before. | 959 // if !select_after_move, keep the same tab selected as was selected before. |
1058 if (select_after_move || index == selected_index_) { | 960 if (select_after_move || index == selected_index_) { |
1059 selected_index_ = to_position; | 961 selected_index_ = to_position; |
1060 } else if (index < selected_index_ && to_position >= selected_index_) { | 962 } else if (index < selected_index_ && to_position >= selected_index_) { |
1061 selected_index_--; | 963 selected_index_--; |
1062 } else if (index > selected_index_ && to_position <= selected_index_) { | 964 } else if (index > selected_index_ && to_position <= selected_index_) { |
1063 selected_index_++; | 965 selected_index_++; |
1064 } | 966 } |
1065 | 967 |
1066 FOR_EACH_OBSERVER(TabStripModelObserver, observers_, | 968 FOR_EACH_OBSERVER(TabStripModelObserver, observers_, |
1067 TabMoved(moved_data->contents, index, to_position)); | 969 TabMoved(moved_data->contents, index, to_position)); |
1068 } | 970 } |
1069 | 971 |
1070 // static | 972 // static |
1071 bool TabStripModel::OpenerMatches(const TabContentsData* data, | 973 bool TabStripModel::OpenerMatches(const TabContentsData* data, |
1072 const NavigationController* opener, | 974 const NavigationController* opener, |
1073 bool use_group) { | 975 bool use_group) { |
1074 return data->opener == opener || (use_group && data->group == opener); | 976 return data->opener == opener || (use_group && data->group == opener); |
1075 } | 977 } |
OLD | NEW |