Index: ios/chrome/browser/ui/history/tab_history_view_controller.mm |
diff --git a/ios/chrome/browser/ui/history/tab_history_view_controller.mm b/ios/chrome/browser/ui/history/tab_history_view_controller.mm |
index 954ea175ae541159381e16051280b8637e80e1e0..5f84835e3722f9a9a0541c0884779ea516126c50 100644 |
--- a/ios/chrome/browser/ui/history/tab_history_view_controller.mm |
+++ b/ios/chrome/browser/ui/history/tab_history_view_controller.mm |
@@ -12,7 +12,6 @@ |
#import "ios/chrome/browser/ui/history/tab_history_cell.h" |
#include "ios/chrome/browser/ui/rtl_geometry.h" |
#import "ios/third_party/material_components_ios/src/components/Ink/src/MaterialInk.h" |
-#import "ios/web/navigation/crw_session_entry.h" |
#include "ios/web/public/favicon_status.h" |
#include "ios/web/public/navigation_item.h" |
#include "ui/gfx/image/image.h" |
@@ -27,9 +26,11 @@ |
// Tools menu is scrollable. |
const CGFloat kLastRowVisiblePercentage = 0.6; |
// Reuse identifier for cells. |
-NSString* cellIdentifier = @"TabHistoryCell"; |
-NSString* footerIdentifier = @"Footer"; |
-NSString* headerIdentifier = @"Header"; |
+NSString* const kCellIdentifier = @"TabHistoryCell"; |
+NSString* const kFooterIdentifier = @"Footer"; |
+NSString* const kHeaderIdentifier = @"Header"; |
+// The collection view's a11y label. |
+NSString* const kCollectionViewLabel = @"Tab History"; |
// Height of rows. |
const CGFloat kCellHeight = 48.0; |
// Fraction height for partially visible row. |
@@ -71,6 +72,36 @@ NS_INLINE CGFloat FooterHeight() { |
return 1.0 / [[UIScreen mainScreen] scale]; |
} |
+// Returns a vector of of NavigationItemLists where the NavigationItems in |
+// |items| are separated by host. |
+NS_INLINE std::vector<web::NavigationItemList> PartitionItemsByHost( |
+ const web::NavigationItemList& items) { |
+ std::vector<web::NavigationItemList> partitionedItems; |
+ // Used to store the previous host when partitioning NavigationItems. |
+ std::string previousHost; |
+ // The NavigationItemList containing NavigationItems with the same host. |
+ web::NavigationItemList itemsWithSameHostname; |
+ // Separate the items in |items| by host. |
+ for (web::NavigationItem* item : items) { |
+ std::string currentHost = item->GetURL().host(); |
+ if (previousHost.empty()) |
+ previousHost = currentHost; |
+ // TODO: This should use some sort of Top Level Domain matching instead of |
+ // explicit host match so that images.googe.com matches shopping.google.com. |
+ if (previousHost == currentHost) { |
+ itemsWithSameHostname.push_back(item); |
+ } else { |
+ partitionedItems.push_back(itemsWithSameHostname); |
+ itemsWithSameHostname = web::NavigationItemList(1, item); |
+ previousHost = currentHost; |
+ } |
+ } |
+ // Add the last list contiaining the same host. |
+ if (!itemsWithSameHostname.empty()) |
+ partitionedItems.push_back(itemsWithSameHostname); |
+ return partitionedItems; |
+} |
+ |
} // namespace |
@interface TabHistoryViewControllerLayout : UICollectionViewLayout |
@@ -219,15 +250,50 @@ - (UICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath: |
@interface TabHistoryViewController ()<MDCInkTouchControllerDelegate> { |
MDCInkTouchController* _inkTouchController; |
- NSArray* _partitionedEntries; |
- NSArray* _sessionEntries; |
+ // A vector of NavigationItemLists where the NavigationItems are separated |
+ // by hostname. |
+ std::vector<web::NavigationItemList> _partitionedItems; |
} |
+ |
+// Returns the NavigationItem corresponding with |indexPath|. |
+- (const web::NavigationItem*)itemAtIndexPath:(NSIndexPath*)indexPath; |
+ |
+// Removes all NavigationItem pointers from this class. Tapping a cell that |
+// triggers a navigation may delete NavigationItems, so NavigationItem |
+// references should be reset to avoid use-after-free errors. |
+- (void)clearNavigationItems; |
+ |
@end |
@implementation TabHistoryViewController |
-- (NSArray*)sessionEntries { |
- return _sessionEntries; |
+- (instancetype)initWithItems:(const web::NavigationItemList&)items { |
+ TabHistoryViewControllerLayout* layout = |
+ [[TabHistoryViewControllerLayout alloc] init]; |
+ if ((self = [super initWithCollectionViewLayout:layout])) { |
+ // Populate |_partitionedItems|. |
+ _partitionedItems = PartitionItemsByHost(items); |
+ |
+ // Set up the UICollectionView. |
+ UICollectionView* collectionView = [self collectionView]; |
+ collectionView.accessibilityLabel = kCollectionViewLabel; |
+ collectionView.backgroundColor = [UIColor whiteColor]; |
+ [collectionView registerClass:[TabHistoryCell class] |
+ forCellWithReuseIdentifier:kCellIdentifier]; |
+ [collectionView registerClass:[TabHistorySectionHeader class] |
+ forSupplementaryViewOfKind:UICollectionElementKindSectionHeader |
+ withReuseIdentifier:kHeaderIdentifier]; |
+ [collectionView registerClass:[TabHistorySectionFooter class] |
+ forSupplementaryViewOfKind:UICollectionElementKindSectionFooter |
+ withReuseIdentifier:kFooterIdentifier]; |
+ |
+ // Set up the ink controller. |
+ _inkTouchController = |
+ [[MDCInkTouchController alloc] initWithView:collectionView]; |
+ [_inkTouchController setDelegate:self]; |
+ [_inkTouchController addInkView]; |
+ } |
+ return self; |
} |
#pragma mark Public Methods |
@@ -236,9 +302,8 @@ - (CGFloat)optimalHeight:(CGFloat)suggestedHeight { |
DCHECK(suggestedHeight >= kCellHeight); |
CGFloat optimalHeight = 0; |
- for (NSArray* sectionArray in _partitionedEntries) { |
- NSUInteger sectionItemCount = [sectionArray count]; |
- for (NSUInteger i = 0; i < sectionItemCount; ++i) { |
+ for (web::NavigationItemList& itemsWithSameHost : _partitionedItems) { |
+ for (size_t count = 0; count < itemsWithSameHost.size(); ++count) { |
CGFloat proposedHeight = optimalHeight + kCellHeight; |
if (proposedHeight > suggestedHeight) { |
@@ -263,153 +328,67 @@ - (CGFloat)optimalHeight:(CGFloat)suggestedHeight { |
return optimalHeight; |
} |
-- (instancetype)init { |
- TabHistoryViewControllerLayout* layout = |
- [[TabHistoryViewControllerLayout alloc] init]; |
- |
- return [self initWithCollectionViewLayout:layout]; |
-} |
- |
-- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout*)layout { |
- self = [super initWithCollectionViewLayout:layout]; |
- if (self) { |
- UICollectionView* collectionView = [self collectionView]; |
- [collectionView setBackgroundColor:[UIColor whiteColor]]; |
- |
- [collectionView registerClass:[TabHistoryCell class] |
- forCellWithReuseIdentifier:cellIdentifier]; |
- |
- [collectionView registerClass:[TabHistorySectionHeader class] |
- forSupplementaryViewOfKind:UICollectionElementKindSectionHeader |
- withReuseIdentifier:headerIdentifier]; |
- |
- [collectionView registerClass:[TabHistorySectionFooter class] |
- forSupplementaryViewOfKind:UICollectionElementKindSectionFooter |
- withReuseIdentifier:footerIdentifier]; |
- |
- _inkTouchController = |
- [[MDCInkTouchController alloc] initWithView:collectionView]; |
- [_inkTouchController setDelegate:self]; |
- [_inkTouchController addInkView]; |
- } |
- |
- return self; |
-} |
- |
#pragma mark UICollectionViewDelegate |
- (void)collectionView:(UICollectionView*)collectionView |
didSelectItemAtIndexPath:(NSIndexPath*)indexPath { |
- UICollectionViewCell* cell = |
- [collectionView cellForItemAtIndexPath:indexPath]; |
+ TabHistoryCell* cell = base::mac::ObjCCastStrict<TabHistoryCell>( |
+ [collectionView cellForItemAtIndexPath:indexPath]); |
[collectionView chromeExecuteCommand:cell]; |
+ [self clearNavigationItems]; |
} |
#pragma mark UICollectionViewDataSource |
-- (CRWSessionEntry*)entryForIndexPath:(NSIndexPath*)indexPath { |
- NSInteger section = [indexPath section]; |
- NSInteger item = [indexPath item]; |
- |
- DCHECK(section < (NSInteger)[_partitionedEntries count]); |
- DCHECK(item < (NSInteger)[[_partitionedEntries objectAtIndex:section] count]); |
- NSArray* sectionedArray = [_partitionedEntries objectAtIndex:section]; |
- |
- return [sectionedArray objectAtIndex:item]; |
-} |
- |
- (NSInteger)collectionView:(UICollectionView*)collectionView |
numberOfItemsInSection:(NSInteger)section { |
- DCHECK(section < (NSInteger)[_partitionedEntries count]); |
- return [[_partitionedEntries objectAtIndex:section] count]; |
+ size_t sectionIdx = static_cast<size_t>(section); |
+ DCHECK_LT(sectionIdx, _partitionedItems.size()); |
+ return _partitionedItems[sectionIdx].size(); |
} |
- (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView |
cellForItemAtIndexPath:(NSIndexPath*)indexPath { |
TabHistoryCell* cell = |
- [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier |
+ [collectionView dequeueReusableCellWithReuseIdentifier:kCellIdentifier |
forIndexPath:indexPath]; |
- |
- [cell setEntry:[self entryForIndexPath:indexPath]]; |
- [cell setTag:IDC_BACK_FORWARD_IN_TAB_HISTORY]; |
- |
+ cell.item = [self itemAtIndexPath:indexPath]; |
+ cell.tag = IDC_BACK_FORWARD_IN_TAB_HISTORY; |
return cell; |
} |
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView*)view { |
- return [_partitionedEntries count]; |
+ return _partitionedItems.size(); |
} |
- (UICollectionReusableView*)collectionView:(UICollectionView*)view |
viewForSupplementaryElementOfKind:(NSString*)kind |
atIndexPath:(NSIndexPath*)indexPath { |
+ // Return a footer cell if requested. |
if ([kind isEqualToString:UICollectionElementKindSectionFooter]) { |
return [view dequeueReusableSupplementaryViewOfKind:kind |
- withReuseIdentifier:footerIdentifier |
+ withReuseIdentifier:kFooterIdentifier |
forIndexPath:indexPath]; |
} |
- |
DCHECK([kind isEqualToString:UICollectionElementKindSectionHeader]); |
- CRWSessionEntry* sessionEntry = [self entryForIndexPath:indexPath]; |
- web::NavigationItem* navigationItem = [sessionEntry navigationItem]; |
+ // Dequeue a header cell and populate its favicon image. |
TabHistorySectionHeader* header = |
[view dequeueReusableSupplementaryViewOfKind:kind |
- withReuseIdentifier:headerIdentifier |
+ withReuseIdentifier:kHeaderIdentifier |
forIndexPath:indexPath]; |
- |
UIImage* iconImage = nil; |
- const gfx::Image& image = navigationItem->GetFavicon().image; |
+ const gfx::Image& image = |
+ [self itemAtIndexPath:indexPath]->GetFavicon().image; |
if (!image.IsEmpty()) |
iconImage = image.ToUIImage(); |
else |
iconImage = [UIImage imageNamed:@"default_favicon"]; |
- |
[[header iconView] setImage:iconImage]; |
return header; |
} |
-- (void)setSessionEntries:(NSArray*)sessionEntries { |
- _sessionEntries = sessionEntries; |
- |
- std::string previousHost; |
- |
- NSMutableArray* sectionArray = [NSMutableArray array]; |
- NSMutableArray* partitionedEntries = [NSMutableArray array]; |
- |
- NSInteger numberOfEntries = [_sessionEntries count]; |
- for (NSInteger index = 0; index < numberOfEntries; ++index) { |
- CRWSessionEntry* sessionEntry = [_sessionEntries objectAtIndex:index]; |
- web::NavigationItem* navigationItem = [sessionEntry navigationItem]; |
- |
- std::string currentHost; |
- if (navigationItem) |
- currentHost = navigationItem->GetURL().host(); |
- |
- if (previousHost.empty()) |
- previousHost = currentHost; |
- |
- // TODO: This should use some sort of Top Level Domain matching instead of |
- // explicit host match so that images.googe.com matches shopping.google.com. |
- if (previousHost == currentHost) { |
- [sectionArray addObject:sessionEntry]; |
- } else { |
- [partitionedEntries addObject:sectionArray]; |
- sectionArray = [NSMutableArray arrayWithObject:sessionEntry]; |
- previousHost = currentHost; |
- } |
- } |
- |
- if ([sectionArray count]) |
- [partitionedEntries addObject:sectionArray]; |
- |
- if (![partitionedEntries count]) |
- partitionedEntries = nil; |
- |
- _partitionedEntries = partitionedEntries; |
-} |
- |
#pragma mark MDCInkTouchControllerDelegate |
- (BOOL)inkTouchController:(MDCInkTouchController*)inkTouchController |
@@ -431,4 +410,22 @@ - (BOOL)inkTouchController:(MDCInkTouchController*)inkTouchController |
return YES; |
} |
+#pragma mark - |
+ |
+- (const web::NavigationItem*)itemAtIndexPath:(NSIndexPath*)indexPath { |
+ size_t section = static_cast<size_t>([indexPath section]); |
+ size_t item = static_cast<size_t>([indexPath item]); |
+ DCHECK_LT(section, _partitionedItems.size()); |
+ DCHECK_LT(item, _partitionedItems[section].size()); |
+ return _partitionedItems[section][item]; |
+} |
+ |
+- (void)clearNavigationItems { |
+ _partitionedItems.clear(); |
+ for (UICollectionViewCell* cell in self.collectionView.visibleCells) { |
+ TabHistoryCell* historyCell = base::mac::ObjCCast<TabHistoryCell>(cell); |
+ historyCell.item = nullptr; |
+ } |
+} |
+ |
@end |