Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(199)

Side by Side Diff: ios/chrome/browser/ui/history/tab_history_view_controller.mm

Issue 2693013005: Updated tab history classes to use NavigationItemLists. (Closed)
Patch Set: update DEPS, include url_formatter in BUILD.gn Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 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 #import "ios/chrome/browser/ui/history/tab_history_view_controller.h" 5 #import "ios/chrome/browser/ui/history/tab_history_view_controller.h"
6 6
7 #include "base/logging.h" 7 #include "base/logging.h"
8 #include "base/mac/foundation_util.h" 8 #include "base/mac/foundation_util.h"
9 #include "base/strings/sys_string_conversions.h" 9 #include "base/strings/sys_string_conversions.h"
10 #import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h" 10 #import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h"
11 #include "ios/chrome/browser/ui/commands/ios_command_ids.h" 11 #include "ios/chrome/browser/ui/commands/ios_command_ids.h"
12 #import "ios/chrome/browser/ui/history/tab_history_cell.h" 12 #import "ios/chrome/browser/ui/history/tab_history_cell.h"
13 #include "ios/chrome/browser/ui/rtl_geometry.h" 13 #include "ios/chrome/browser/ui/rtl_geometry.h"
14 #import "ios/third_party/material_components_ios/src/components/Ink/src/Material Ink.h" 14 #import "ios/third_party/material_components_ios/src/components/Ink/src/Material Ink.h"
15 #import "ios/web/navigation/crw_session_entry.h"
16 #include "ios/web/public/favicon_status.h" 15 #include "ios/web/public/favicon_status.h"
17 #include "ios/web/public/navigation_item.h" 16 #include "ios/web/public/navigation_item.h"
18 #include "ui/gfx/image/image.h" 17 #include "ui/gfx/image/image.h"
19 18
20 #if !defined(__has_feature) || !__has_feature(objc_arc) 19 #if !defined(__has_feature) || !__has_feature(objc_arc)
21 #error "This file requires ARC support." 20 #error "This file requires ARC support."
22 #endif 21 #endif
23 22
24 namespace { 23 namespace {
25 24
26 // Visible percentage of the last visible row on the Tools menu if the 25 // Visible percentage of the last visible row on the Tools menu if the
27 // Tools menu is scrollable. 26 // Tools menu is scrollable.
28 const CGFloat kLastRowVisiblePercentage = 0.6; 27 const CGFloat kLastRowVisiblePercentage = 0.6;
29 // Reuse identifier for cells. 28 // Reuse identifier for cells.
30 NSString* cellIdentifier = @"TabHistoryCell"; 29 NSString* const kCellIdentifier = @"TabHistoryCell";
31 NSString* footerIdentifier = @"Footer"; 30 NSString* const kFooterIdentifier = @"Footer";
32 NSString* headerIdentifier = @"Header"; 31 NSString* const kHeaderIdentifier = @"Header";
32 // The collection view's a11y label.
33 NSString* const kCollectionViewLabel = @"Tab History";
33 // Height of rows. 34 // Height of rows.
34 const CGFloat kCellHeight = 48.0; 35 const CGFloat kCellHeight = 48.0;
35 // Fraction height for partially visible row. 36 // Fraction height for partially visible row.
36 const CGFloat kCellHeightLastRow = kCellHeight * kLastRowVisiblePercentage; 37 const CGFloat kCellHeightLastRow = kCellHeight * kLastRowVisiblePercentage;
37 // Width and leading for the header view. 38 // Width and leading for the header view.
38 const CGFloat kHeaderLeadingInset = 0; 39 const CGFloat kHeaderLeadingInset = 0;
39 const CGFloat kHeaderWidth = 48; 40 const CGFloat kHeaderWidth = 48;
40 // Leading and trailing insets for cell items. 41 // Leading and trailing insets for cell items.
41 const CGFloat kCellLeadingInset = kHeaderLeadingInset + kHeaderWidth; 42 const CGFloat kCellLeadingInset = kHeaderLeadingInset + kHeaderWidth;
42 const CGFloat kCellTrailingInset = 16; 43 const CGFloat kCellTrailingInset = 16;
(...skipping 21 matching lines...) Expand all
64 DCHECK(offsets.at([path section]).size()); 65 DCHECK(offsets.at([path section]).size());
65 66
66 return offsets.at([path section]).at(0); 67 return offsets.at([path section]).at(0);
67 } 68 }
68 69
69 // Height for the footer view. 70 // Height for the footer view.
70 NS_INLINE CGFloat FooterHeight() { 71 NS_INLINE CGFloat FooterHeight() {
71 return 1.0 / [[UIScreen mainScreen] scale]; 72 return 1.0 / [[UIScreen mainScreen] scale];
72 } 73 }
73 74
75 // Returns a vector of of NavigationItemLists where the NavigationItems in
76 // |items| are separated by host.
77 NS_INLINE std::vector<web::NavigationItemList> PartitionItemsByHost(
78 const web::NavigationItemList& items) {
79 std::vector<web::NavigationItemList> partitionedItems;
80 // Used to store the previous host when partitioning NavigationItems.
81 std::string previousHost;
82 // The NavigationItemList containing NavigationItems with the same host.
83 web::NavigationItemList itemsWithSameHostname;
84 // Separate the items in |items| by host.
85 for (web::NavigationItem* item : items) {
86 std::string currentHost = item->GetURL().host();
87 if (previousHost.empty())
88 previousHost = currentHost;
89 // TODO: This should use some sort of Top Level Domain matching instead of
90 // explicit host match so that images.googe.com matches shopping.google.com.
91 if (previousHost == currentHost) {
92 itemsWithSameHostname.push_back(item);
93 } else {
94 partitionedItems.push_back(itemsWithSameHostname);
95 itemsWithSameHostname = web::NavigationItemList(1, item);
96 previousHost = currentHost;
97 }
98 }
99 // Add the last list contiaining the same host.
100 if (!itemsWithSameHostname.empty())
101 partitionedItems.push_back(itemsWithSameHostname);
102 return partitionedItems;
103 }
104
74 } // namespace 105 } // namespace
75 106
76 @interface TabHistoryViewControllerLayout : UICollectionViewLayout 107 @interface TabHistoryViewControllerLayout : UICollectionViewLayout
77 @end 108 @end
78 109
79 @implementation TabHistoryViewControllerLayout { 110 @implementation TabHistoryViewControllerLayout {
80 ItemOffsetVector _itemYOffsets; 111 ItemOffsetVector _itemYOffsets;
81 CGFloat _containerCalculatedHeight; 112 CGFloat _containerCalculatedHeight;
82 CGFloat _containerWidth; 113 CGFloat _containerWidth;
83 CGFloat _cellItemWidth; 114 CGFloat _cellItemWidth;
(...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after
212 [attributes setZIndex:0]; 243 [attributes setZIndex:0];
213 } 244 }
214 245
215 return attributes; 246 return attributes;
216 } 247 }
217 248
218 @end 249 @end
219 250
220 @interface TabHistoryViewController ()<MDCInkTouchControllerDelegate> { 251 @interface TabHistoryViewController ()<MDCInkTouchControllerDelegate> {
221 MDCInkTouchController* _inkTouchController; 252 MDCInkTouchController* _inkTouchController;
222 NSArray* _partitionedEntries; 253 // A vector of NavigationItemLists where the NavigationItems are separated
223 NSArray* _sessionEntries; 254 // by hostname.
255 std::vector<web::NavigationItemList> _partitionedItems;
224 } 256 }
257
258 // Returns the NavigationItem corresponding with |indexPath|.
259 - (const web::NavigationItem*)itemAtIndexPath:(NSIndexPath*)indexPath;
260
261 // Removes all NavigationItem pointers from this class. Tapping a cell that
262 // triggers a navigation may delete NavigationItems, so NavigationItem
263 // references should be reset to avoid use-after-free errors.
264 - (void)clearNavigationItems;
265
225 @end 266 @end
226 267
227 @implementation TabHistoryViewController 268 @implementation TabHistoryViewController
228 269
229 - (NSArray*)sessionEntries { 270 - (instancetype)initWithItems:(const web::NavigationItemList&)items {
230 return _sessionEntries; 271 TabHistoryViewControllerLayout* layout =
272 [[TabHistoryViewControllerLayout alloc] init];
273 if ((self = [super initWithCollectionViewLayout:layout])) {
274 // Populate |_partitionedItems|.
275 _partitionedItems = PartitionItemsByHost(items);
276
277 // Set up the UICollectionView.
278 UICollectionView* collectionView = [self collectionView];
279 collectionView.accessibilityLabel = kCollectionViewLabel;
280 collectionView.backgroundColor = [UIColor whiteColor];
281 [collectionView registerClass:[TabHistoryCell class]
282 forCellWithReuseIdentifier:kCellIdentifier];
283 [collectionView registerClass:[TabHistorySectionHeader class]
284 forSupplementaryViewOfKind:UICollectionElementKindSectionHeader
285 withReuseIdentifier:kHeaderIdentifier];
286 [collectionView registerClass:[TabHistorySectionFooter class]
287 forSupplementaryViewOfKind:UICollectionElementKindSectionFooter
288 withReuseIdentifier:kFooterIdentifier];
289
290 // Set up the ink controller.
291 _inkTouchController =
292 [[MDCInkTouchController alloc] initWithView:collectionView];
293 [_inkTouchController setDelegate:self];
294 [_inkTouchController addInkView];
295 }
296 return self;
231 } 297 }
232 298
233 #pragma mark Public Methods 299 #pragma mark Public Methods
234 300
235 - (CGFloat)optimalHeight:(CGFloat)suggestedHeight { 301 - (CGFloat)optimalHeight:(CGFloat)suggestedHeight {
236 DCHECK(suggestedHeight >= kCellHeight); 302 DCHECK(suggestedHeight >= kCellHeight);
237 CGFloat optimalHeight = 0; 303 CGFloat optimalHeight = 0;
238 304
239 for (NSArray* sectionArray in _partitionedEntries) { 305 for (web::NavigationItemList& itemsWithSameHost : _partitionedItems) {
240 NSUInteger sectionItemCount = [sectionArray count]; 306 for (size_t count = 0; count < itemsWithSameHost.size(); ++count) {
241 for (NSUInteger i = 0; i < sectionItemCount; ++i) {
242 CGFloat proposedHeight = optimalHeight + kCellHeight; 307 CGFloat proposedHeight = optimalHeight + kCellHeight;
243 308
244 if (proposedHeight > suggestedHeight) { 309 if (proposedHeight > suggestedHeight) {
245 CGFloat difference = proposedHeight - suggestedHeight; 310 CGFloat difference = proposedHeight - suggestedHeight;
246 if (difference > kCellHeightLastRow) { 311 if (difference > kCellHeightLastRow) {
247 return optimalHeight + kCellHeightLastRow; 312 return optimalHeight + kCellHeightLastRow;
248 } else { 313 } else {
249 return optimalHeight - kCellHeight + kCellHeightLastRow; 314 return optimalHeight - kCellHeight + kCellHeightLastRow;
250 } 315 }
251 } 316 }
252 317
253 optimalHeight = proposedHeight; 318 optimalHeight = proposedHeight;
254 } 319 }
255 320
256 optimalHeight += FooterHeight(); 321 optimalHeight += FooterHeight();
257 } 322 }
258 323
259 // If this point is reached, it means the entire content fits and this last 324 // If this point is reached, it means the entire content fits and this last
260 // section should not include the footer. 325 // section should not include the footer.
261 optimalHeight -= FooterHeight(); 326 optimalHeight -= FooterHeight();
262 327
263 return optimalHeight; 328 return optimalHeight;
264 } 329 }
265 330
266 - (instancetype)init {
267 TabHistoryViewControllerLayout* layout =
268 [[TabHistoryViewControllerLayout alloc] init];
269
270 return [self initWithCollectionViewLayout:layout];
271 }
272
273 - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout*)layout {
274 self = [super initWithCollectionViewLayout:layout];
275 if (self) {
276 UICollectionView* collectionView = [self collectionView];
277 [collectionView setBackgroundColor:[UIColor whiteColor]];
278
279 [collectionView registerClass:[TabHistoryCell class]
280 forCellWithReuseIdentifier:cellIdentifier];
281
282 [collectionView registerClass:[TabHistorySectionHeader class]
283 forSupplementaryViewOfKind:UICollectionElementKindSectionHeader
284 withReuseIdentifier:headerIdentifier];
285
286 [collectionView registerClass:[TabHistorySectionFooter class]
287 forSupplementaryViewOfKind:UICollectionElementKindSectionFooter
288 withReuseIdentifier:footerIdentifier];
289
290 _inkTouchController =
291 [[MDCInkTouchController alloc] initWithView:collectionView];
292 [_inkTouchController setDelegate:self];
293 [_inkTouchController addInkView];
294 }
295
296 return self;
297 }
298
299 #pragma mark UICollectionViewDelegate 331 #pragma mark UICollectionViewDelegate
300 332
301 - (void)collectionView:(UICollectionView*)collectionView 333 - (void)collectionView:(UICollectionView*)collectionView
302 didSelectItemAtIndexPath:(NSIndexPath*)indexPath { 334 didSelectItemAtIndexPath:(NSIndexPath*)indexPath {
303 UICollectionViewCell* cell = 335 TabHistoryCell* cell = base::mac::ObjCCastStrict<TabHistoryCell>(
304 [collectionView cellForItemAtIndexPath:indexPath]; 336 [collectionView cellForItemAtIndexPath:indexPath]);
305 [collectionView chromeExecuteCommand:cell]; 337 [collectionView chromeExecuteCommand:cell];
338 [self clearNavigationItems];
306 } 339 }
307 340
308 #pragma mark UICollectionViewDataSource 341 #pragma mark UICollectionViewDataSource
309 342
310 - (CRWSessionEntry*)entryForIndexPath:(NSIndexPath*)indexPath {
311 NSInteger section = [indexPath section];
312 NSInteger item = [indexPath item];
313
314 DCHECK(section < (NSInteger)[_partitionedEntries count]);
315 DCHECK(item < (NSInteger)[[_partitionedEntries objectAtIndex:section] count]);
316 NSArray* sectionedArray = [_partitionedEntries objectAtIndex:section];
317
318 return [sectionedArray objectAtIndex:item];
319 }
320
321 - (NSInteger)collectionView:(UICollectionView*)collectionView 343 - (NSInteger)collectionView:(UICollectionView*)collectionView
322 numberOfItemsInSection:(NSInteger)section { 344 numberOfItemsInSection:(NSInteger)section {
323 DCHECK(section < (NSInteger)[_partitionedEntries count]); 345 size_t sectionIdx = static_cast<size_t>(section);
324 return [[_partitionedEntries objectAtIndex:section] count]; 346 DCHECK_LT(sectionIdx, _partitionedItems.size());
347 return _partitionedItems[sectionIdx].size();
325 } 348 }
326 349
327 - (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView 350 - (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView
328 cellForItemAtIndexPath:(NSIndexPath*)indexPath { 351 cellForItemAtIndexPath:(NSIndexPath*)indexPath {
329 TabHistoryCell* cell = 352 TabHistoryCell* cell =
330 [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier 353 [collectionView dequeueReusableCellWithReuseIdentifier:kCellIdentifier
331 forIndexPath:indexPath]; 354 forIndexPath:indexPath];
332 355 cell.item = [self itemAtIndexPath:indexPath];
333 [cell setEntry:[self entryForIndexPath:indexPath]]; 356 cell.tag = IDC_BACK_FORWARD_IN_TAB_HISTORY;
334 [cell setTag:IDC_BACK_FORWARD_IN_TAB_HISTORY];
335
336 return cell; 357 return cell;
337 } 358 }
338 359
339 - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView*)view { 360 - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView*)view {
340 return [_partitionedEntries count]; 361 return _partitionedItems.size();
341 } 362 }
342 363
343 - (UICollectionReusableView*)collectionView:(UICollectionView*)view 364 - (UICollectionReusableView*)collectionView:(UICollectionView*)view
344 viewForSupplementaryElementOfKind:(NSString*)kind 365 viewForSupplementaryElementOfKind:(NSString*)kind
345 atIndexPath:(NSIndexPath*)indexPath { 366 atIndexPath:(NSIndexPath*)indexPath {
367 // Return a footer cell if requested.
346 if ([kind isEqualToString:UICollectionElementKindSectionFooter]) { 368 if ([kind isEqualToString:UICollectionElementKindSectionFooter]) {
347 return [view dequeueReusableSupplementaryViewOfKind:kind 369 return [view dequeueReusableSupplementaryViewOfKind:kind
348 withReuseIdentifier:footerIdentifier 370 withReuseIdentifier:kFooterIdentifier
349 forIndexPath:indexPath]; 371 forIndexPath:indexPath];
350 } 372 }
373 DCHECK([kind isEqualToString:UICollectionElementKindSectionHeader]);
351 374
352 DCHECK([kind isEqualToString:UICollectionElementKindSectionHeader]); 375 // Dequeue a header cell and populate its favicon image.
353 CRWSessionEntry* sessionEntry = [self entryForIndexPath:indexPath];
354 web::NavigationItem* navigationItem = [sessionEntry navigationItem];
355
356 TabHistorySectionHeader* header = 376 TabHistorySectionHeader* header =
357 [view dequeueReusableSupplementaryViewOfKind:kind 377 [view dequeueReusableSupplementaryViewOfKind:kind
358 withReuseIdentifier:headerIdentifier 378 withReuseIdentifier:kHeaderIdentifier
359 forIndexPath:indexPath]; 379 forIndexPath:indexPath];
360
361 UIImage* iconImage = nil; 380 UIImage* iconImage = nil;
362 const gfx::Image& image = navigationItem->GetFavicon().image; 381 const gfx::Image& image =
382 [self itemAtIndexPath:indexPath]->GetFavicon().image;
363 if (!image.IsEmpty()) 383 if (!image.IsEmpty())
364 iconImage = image.ToUIImage(); 384 iconImage = image.ToUIImage();
365 else 385 else
366 iconImage = [UIImage imageNamed:@"default_favicon"]; 386 iconImage = [UIImage imageNamed:@"default_favicon"];
367
368 [[header iconView] setImage:iconImage]; 387 [[header iconView] setImage:iconImage];
369 388
370 return header; 389 return header;
371 } 390 }
372 391
373 - (void)setSessionEntries:(NSArray*)sessionEntries {
374 _sessionEntries = sessionEntries;
375
376 std::string previousHost;
377
378 NSMutableArray* sectionArray = [NSMutableArray array];
379 NSMutableArray* partitionedEntries = [NSMutableArray array];
380
381 NSInteger numberOfEntries = [_sessionEntries count];
382 for (NSInteger index = 0; index < numberOfEntries; ++index) {
383 CRWSessionEntry* sessionEntry = [_sessionEntries objectAtIndex:index];
384 web::NavigationItem* navigationItem = [sessionEntry navigationItem];
385
386 std::string currentHost;
387 if (navigationItem)
388 currentHost = navigationItem->GetURL().host();
389
390 if (previousHost.empty())
391 previousHost = currentHost;
392
393 // TODO: This should use some sort of Top Level Domain matching instead of
394 // explicit host match so that images.googe.com matches shopping.google.com.
395 if (previousHost == currentHost) {
396 [sectionArray addObject:sessionEntry];
397 } else {
398 [partitionedEntries addObject:sectionArray];
399 sectionArray = [NSMutableArray arrayWithObject:sessionEntry];
400 previousHost = currentHost;
401 }
402 }
403
404 if ([sectionArray count])
405 [partitionedEntries addObject:sectionArray];
406
407 if (![partitionedEntries count])
408 partitionedEntries = nil;
409
410 _partitionedEntries = partitionedEntries;
411 }
412
413 #pragma mark MDCInkTouchControllerDelegate 392 #pragma mark MDCInkTouchControllerDelegate
414 393
415 - (BOOL)inkTouchController:(MDCInkTouchController*)inkTouchController 394 - (BOOL)inkTouchController:(MDCInkTouchController*)inkTouchController
416 shouldProcessInkTouchesAtTouchLocation:(CGPoint)location { 395 shouldProcessInkTouchesAtTouchLocation:(CGPoint)location {
417 NSIndexPath* indexPath = 396 NSIndexPath* indexPath =
418 [self.collectionView indexPathForItemAtPoint:location]; 397 [self.collectionView indexPathForItemAtPoint:location];
419 TabHistoryCell* cell = base::mac::ObjCCastStrict<TabHistoryCell>( 398 TabHistoryCell* cell = base::mac::ObjCCastStrict<TabHistoryCell>(
420 [self.collectionView cellForItemAtIndexPath:indexPath]); 399 [self.collectionView cellForItemAtIndexPath:indexPath]);
421 if (!cell) { 400 if (!cell) {
422 return NO; 401 return NO;
423 } 402 }
424 403
425 // Increase the size of the ink view to cover the collection view from edge 404 // Increase the size of the ink view to cover the collection view from edge
426 // to edge. 405 // to edge.
427 CGRect inkViewFrame = [cell frame]; 406 CGRect inkViewFrame = [cell frame];
428 inkViewFrame.origin.x = 0; 407 inkViewFrame.origin.x = 0;
429 inkViewFrame.size.width = CGRectGetWidth([self.collectionView bounds]); 408 inkViewFrame.size.width = CGRectGetWidth([self.collectionView bounds]);
430 [[inkTouchController defaultInkView] setFrame:inkViewFrame]; 409 [[inkTouchController defaultInkView] setFrame:inkViewFrame];
431 return YES; 410 return YES;
432 } 411 }
433 412
413 #pragma mark -
414
415 - (const web::NavigationItem*)itemAtIndexPath:(NSIndexPath*)indexPath {
416 size_t section = static_cast<size_t>([indexPath section]);
417 size_t item = static_cast<size_t>([indexPath item]);
418 DCHECK_LT(section, _partitionedItems.size());
419 DCHECK_LT(item, _partitionedItems[section].size());
420 return _partitionedItems[section][item];
421 }
422
423 - (void)clearNavigationItems {
424 _partitionedItems.clear();
425 for (UICollectionViewCell* cell in self.collectionView.visibleCells) {
426 TabHistoryCell* historyCell = base::mac::ObjCCast<TabHistoryCell>(cell);
427 historyCell.item = nullptr;
428 }
429 }
430
434 @end 431 @end
OLDNEW
« no previous file with comments | « ios/chrome/browser/ui/history/tab_history_view_controller.h ('k') | ios/chrome/browser/ui/toolbar/web_toolbar_controller.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698