| 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 |