Commit 2fd59389 authored by sdefresne's avatar sdefresne Committed by Commit bot

Upstream Chrome on iOS source code [9/11].

Upstream part of Chrome on iOS source code. Nothing is built yet,
just new files added. The files will be added to the build as part
of the last CL to avoid breaking downstream tree.

BUG=653086

Review-Url: https://codereview.chromium.org/2588733002
Cr-Commit-Position: refs/heads/master@{#439473}
parent 6b437a09
# Tab Grid
-----
**The files in this directory are only used in the new architecture for iOS
Chrome**
-----
The tab grid is the grid of open tabs that is "underneath" the other views in
Chrome on iOS.
This is a **Placeholder** implementation; it presents the barest possible UI,
and in its current state is only used as part of a worked example of Chrome
architecture.
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// ====== New Architecture =====
// = This code is only used in the new iOS Chrome architecture. =
// ============================================================================
#ifndef IOS_CHROME_BROWSER_UI_TAB_GRID_TAB_GRID_COORDINATOR_H_
#define IOS_CHROME_BROWSER_UI_TAB_GRID_TAB_GRID_COORDINATOR_H_
#import <Foundation/Foundation.h>
#import "ios/chrome/browser/browser_coordinator.h"
#import "ios/chrome/browser/url_opening.h"
// Coordinator that drives a UI showing a scrollable grid of tabs,
// which each represent a web browsing tab that can be expanded by tapping.
@interface TabGridCoordinator : BrowserCoordinator<URLOpening>
@end
#endif // IOS_CHROME_BROWSER_UI_TAB_GRID_TAB_GRID_COORDINATOR_H_
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// ====== New Architecture =====
// = This code is only used in the new iOS Chrome architecture. =
// ============================================================================
#import "ios/chrome/browser/ui/tab_grid/tab_grid_coordinator.h"
#include <memory>
#include "base/strings/sys_string_conversions.h"
#import "ios/chrome/browser/browser_coordinator+internal.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
#import "ios/chrome/browser/ui/settings/settings_coordinator.h"
#import "ios/chrome/browser/ui/tab/tab_coordinator.h"
#import "ios/chrome/browser/ui/tab_grid/tab_grid_view_controller.h"
#import "ios/web/public/navigation_manager.h"
#include "ios/web/public/web_state/web_state.h"
#import "net/base/mac/url_conversions.h"
#include "ui/base/page_transition_types.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface TabGridCoordinator ()<TabGridDataSource,
TabGridActionDelegate,
SettingsActionDelegate>
@property(nonatomic, strong) TabGridViewController* viewController;
@property(nonatomic, weak) SettingsCoordinator* settingsCoordinator;
@end
@implementation TabGridCoordinator {
std::unique_ptr<web::WebState> _placeholderWebState;
}
@synthesize viewController = _viewController;
@synthesize settingsCoordinator = _settingsCoordinator;
#pragma mark - BrowserCoordinator
- (void)start {
self.viewController = [[TabGridViewController alloc] init];
self.viewController.dataSource = self;
self.viewController.actionDelegate = self;
// |rootViewController| is nullable, so this is by design a no-op if it hasn't
// been set. This may be true in a unit test, or if this coordinator is being
// used as a root coordinator.
[self.rootViewController presentViewController:self.viewController
animated:YES
completion:nil];
}
#pragma mark - TabGridDataSource
- (NSUInteger)numberOfTabsInTabGrid {
return 1;
}
- (NSString*)titleAtIndex:(NSInteger)index {
// Placeholder implementation: ignore |index| and return the placeholder
// web state, lazily creating it if needed.
if (!_placeholderWebState.get()) {
web::WebState::CreateParams webStateCreateParams(self.browserState);
_placeholderWebState = web::WebState::Create(webStateCreateParams);
_placeholderWebState->SetWebUsageEnabled(true);
}
GURL url = _placeholderWebState.get()->GetVisibleURL();
NSString* urlText = @"<New Tab>";
if (!url.is_valid()) {
urlText = base::SysUTF8ToNSString(url.spec());
}
return urlText;
}
#pragma mark - TabGridActionDelegate
- (void)showTabAtIndexPath:(NSIndexPath*)indexPath {
DCHECK(_placeholderWebState);
TabCoordinator* tabCoordinator = [[TabCoordinator alloc] init];
tabCoordinator.webState = _placeholderWebState.get();
tabCoordinator.presentationKey = indexPath;
[self addChildCoordinator:tabCoordinator];
[tabCoordinator start];
}
- (void)showTabGrid {
// This object should only ever have at most one child.
DCHECK_LE(self.children.count, 1UL);
BrowserCoordinator* child = [self.children anyObject];
[child stop];
[self removeChildCoordinator:child];
}
#pragma mark - TabGridActionDelegate
- (void)showSettings {
SettingsCoordinator* settingsCoordinator = [[SettingsCoordinator alloc] init];
settingsCoordinator.actionDelegate = self;
[self addOverlayCoordinator:settingsCoordinator];
self.settingsCoordinator = settingsCoordinator;
[settingsCoordinator start];
}
#pragma mark - SettingsActionDelegate
- (void)closeSettings {
[self.settingsCoordinator stop];
[self.settingsCoordinator.parentCoordinator
removeChildCoordinator:self.settingsCoordinator];
// self.settingsCoordinator should be presumed to be nil after this point.
}
#pragma mark - URLOpening
- (void)openURL:(NSURL*)URL {
[self.overlayCoordinator stop];
[self removeOverlayCoordinator];
web::NavigationManager::WebLoadParams params(net::GURLWithNSURL(URL));
params.transition_type = ui::PAGE_TRANSITION_LINK;
_placeholderWebState->GetNavigationManager()->LoadURLWithParams(params);
if (!self.children.count) {
// Placeholder — since there's only one tab in the grid, just open
// the tab at index path (0,0).
[self showTabAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
}
}
@end
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// ====== New Architecture =====
// = This code is only used in the new iOS Chrome architecture. =
// ============================================================================
#ifndef IOS_CHROME_BROWSER_UI_TAB_GRID_TAB_GRID_TAB_CELL_H_
#define IOS_CHROME_BROWSER_UI_TAB_GRID_TAB_GRID_TAB_CELL_H_
#import <UIKit/UIKit.h>
// Placeholder cell implementation for use in the tab grid.
// A square cell with rounded corners and a label placed in the center.
@interface TabGridTabCell : UICollectionViewCell
// The label in the center of the tab cell.
@property(nonatomic, readonly) UILabel* label;
@end
#endif // IOS_CHROME_BROWSER_UI_TAB_GRID_TAB_GRID_TAB_CELL_H_
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// ====== New Architecture =====
// = This code is only used in the new iOS Chrome architecture. =
// ============================================================================
#import "ios/chrome/browser/ui/tab_grid/tab_grid_tab_cell.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
const CGFloat kCornerRadius = 4.0;
const CGFloat kSelectedBorderWidth = 2.0;
}
@implementation TabGridTabCell
@synthesize label = _label;
- (instancetype)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
self.contentView.layer.cornerRadius = kCornerRadius;
self.selectedBackgroundView.layer.cornerRadius = kCornerRadius;
self.selectedBackgroundView.layer.borderWidth = kSelectedBorderWidth;
self.selectedBackgroundView.layer.borderColor = [UIColor blueColor].CGColor;
self.selectedBackgroundView.transform = CGAffineTransformScale(
self.selectedBackgroundView.transform, 1.05, 1.05);
_label = [[UILabel alloc] initWithFrame:CGRectZero];
_label.translatesAutoresizingMaskIntoConstraints = NO;
_label.numberOfLines = 1;
_label.textAlignment = NSTextAlignmentCenter;
[self.contentView addSubview:_label];
[NSLayoutConstraint activateConstraints:@[
[_label.centerYAnchor
constraintEqualToAnchor:self.contentView.centerYAnchor],
[_label.leadingAnchor
constraintEqualToAnchor:self.contentView.layoutMarginsGuide
.leadingAnchor],
[_label.trailingAnchor
constraintEqualToAnchor:self.contentView.layoutMarginsGuide
.trailingAnchor],
]];
}
return self;
}
@end
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// ====== New Architecture =====
// = This code is only used in the new iOS Chrome architecture. =
// ============================================================================
#ifndef IOS_CHROME_BROWSER_UI_TAB_GRID_TAB_GRID_VIEW_CONTROLLER_H_
#define IOS_CHROME_BROWSER_UI_TAB_GRID_TAB_GRID_VIEW_CONTROLLER_H_
#import <UIKit/UIKit.h>
#import "ios/chrome/browser/ui/animators/zoom_transition_delegate.h"
// The data source for tab grid UI.
// Conceptually the tab grid represents a group of WebState objects (which
// are ultimately the model-layer representation of a browser tab). The data
// source must be able to map between indices and WebStates.
@protocol TabGridDataSource<NSObject>
// The number of tabs to be displayed in the grid.
- (NSUInteger)numberOfTabsInTabGrid;
// Title for the tab at |index| in the grid.
- (NSString*)titleAtIndex:(NSInteger)index;
@end
@protocol TabGridActionDelegate<NSObject>
- (void)showTabAtIndexPath:(NSIndexPath*)indexPath;
- (void)showTabGrid;
- (void)showSettings;
@end
// Controller for a scrolling view displaying square cells that represent
// the user's open tabs.
@interface TabGridViewController : UIViewController<ZoomTransitionDelegate>
// Data source for the tabs to be displayed.
@property(nonatomic, weak) id<TabGridDataSource> dataSource;
@property(nonatomic, weak) id<TabGridActionDelegate> actionDelegate;
@end
#endif // IOS_CHROME_BROWSER_UI_TAB_GRID_TAB_GRID_VIEW_CONTROLLER_H_
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// ====== New Architecture =====
// = This code is only used in the new iOS Chrome architecture. =
// ============================================================================
#import "ios/chrome/browser/ui/tab_grid/tab_grid_view_controller.h"
#include "base/mac/foundation_util.h"
#import "ios/chrome/browser/ui/actions/settings_actions.h"
#import "ios/chrome/browser/ui/actions/tab_grid_actions.h"
#import "ios/chrome/browser/ui/tab_grid/tab_grid_tab_cell.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
NSString* const kTabGridCellIdentifier = @"tabGridCell";
const CGFloat kSpace = 20;
const CGFloat kTabSize = 150;
}
@interface TabGridViewController ()<SettingsActions,
TabGridActions,
UICollectionViewDataSource,
UICollectionViewDelegate>
@property(nonatomic, weak) UICollectionView* grid;
@end
@implementation TabGridViewController
@synthesize dataSource = _dataSource;
@synthesize actionDelegate = _actionDelegate;
@synthesize grid = _grid;
- (void)viewDidLoad {
// Placeholder dark grey stripe at the top of the view.
UIView* stripe = [[UIView alloc] initWithFrame:CGRectZero];
stripe.translatesAutoresizingMaskIntoConstraints = NO;
stripe.backgroundColor = [UIColor darkGrayColor];
[self.view addSubview:stripe];
// Placeholder settings button at in the stripe.
UIButton* settings = [UIButton buttonWithType:UIButtonTypeSystem];
settings.translatesAutoresizingMaskIntoConstraints = NO;
[settings setTitle:@"⚙" forState:UIControlStateNormal];
settings.titleLabel.font = [UIFont systemFontOfSize:24.0];
[settings addTarget:nil
action:@selector(showSettings:)
forControlEvents:UIControlEventTouchUpInside];
[stripe addSubview:settings];
[NSLayoutConstraint activateConstraints:@[
[stripe.heightAnchor constraintEqualToConstant:64.0],
[stripe.widthAnchor constraintEqualToAnchor:self.view.widthAnchor],
[stripe.topAnchor constraintEqualToAnchor:self.view.topAnchor],
[stripe.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
[settings.leadingAnchor
constraintEqualToAnchor:stripe.layoutMarginsGuide.leadingAnchor],
[settings.centerYAnchor constraintEqualToAnchor:stripe.centerYAnchor]
]];
UICollectionViewFlowLayout* layout =
[[UICollectionViewFlowLayout alloc] init];
layout.minimumLineSpacing = kSpace;
layout.minimumInteritemSpacing = kSpace;
layout.sectionInset = UIEdgeInsetsMake(kSpace, kSpace, kSpace, kSpace);
layout.itemSize = CGSizeMake(kTabSize, kTabSize);
UICollectionView* grid = [[UICollectionView alloc] initWithFrame:CGRectZero
collectionViewLayout:layout];
grid.translatesAutoresizingMaskIntoConstraints = NO;
grid.backgroundColor = [UIColor blackColor];
[self.view addSubview:grid];
self.grid = grid;
self.grid.dataSource = self;
self.grid.delegate = self;
[self.grid registerClass:[TabGridTabCell class]
forCellWithReuseIdentifier:kTabGridCellIdentifier];
[NSLayoutConstraint activateConstraints:@[
[self.grid.topAnchor constraintEqualToAnchor:stripe.bottomAnchor],
[self.grid.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
[self.grid.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.grid.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
]];
}
- (void)viewWillAppear:(BOOL)animated {
[self.grid reloadData];
}
#pragma mark - UICollectionViewDataSource methods
- (NSInteger)numberOfSectionsInCollectionView:
(UICollectionView*)collectionView {
return 1;
}
- (NSInteger)collectionView:(UICollectionView*)collectionView
numberOfItemsInSection:(NSInteger)section {
return [self.dataSource numberOfTabsInTabGrid];
}
- (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView
cellForItemAtIndexPath:(nonnull NSIndexPath*)indexPath {
TabGridTabCell* cell =
base::mac::ObjCCastStrict<TabGridTabCell>([collectionView
dequeueReusableCellWithReuseIdentifier:kTabGridCellIdentifier
forIndexPath:indexPath]);
cell.contentView.backgroundColor = [UIColor purpleColor];
cell.selected = YES;
cell.label.text = [self.dataSource titleAtIndex:indexPath.item];
return cell;
}
#pragma mark - UICollectionViewDelegate methods
- (void)collectionView:(UICollectionView*)collectionView
didSelectItemAtIndexPath:(NSIndexPath*)indexPath {
[self.actionDelegate showTabAtIndexPath:indexPath];
}
#pragma mark - ZoomTransitionDelegate methods
- (CGRect)rectForZoomWithKey:(NSObject*)key inView:(UIView*)view {
NSIndexPath* cellPath = base::mac::ObjCCastStrict<NSIndexPath>(key);
if (!key)
return CGRectNull;
UICollectionViewCell* cell = [self.grid cellForItemAtIndexPath:cellPath];
return [view convertRect:cell.bounds fromView:cell];
}
#pragma mark - SettingsActions
- (void)showSettings:(id)sender {
[self.actionDelegate showSettings];
}
#pragma mark - TabGridActions
- (void)showTabGrid:(id)sender {
[self.actionDelegate showTabGrid];
}
@end
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_TAB_SWITCHER_SESSION_CHANGES_H_
#define IOS_CHROME_BROWSER_UI_TAB_SWITCHER_SESSION_CHANGES_H_
#include <vector>
namespace ios_internal {
// This structure represents the changes a session undergoes.
// It is used to update the UICollectionView showing a set of tabs.
class SessionChanges {
public:
SessionChanges(std::vector<size_t> const& tabHashesInInitialState,
std::vector<size_t> const& tabHashesInFinalState);
~SessionChanges();
SessionChanges(const SessionChanges& sessionChanges) = delete;
SessionChanges& operator=(const SessionChanges& sessionChanges) = delete;
std::vector<size_t> const& deletions() const;
std::vector<size_t> const& insertions() const;
std::vector<size_t> const& updates() const;
bool hasChanges() const;
private:
// Those vectors contain indexes of tabs.
// The indexes are relative to a tab model snapshot, or a distant session.
// To be in accordance with the UICollectionView's |performBatchUpdates|
// method:
// -the indexes in |updates| are relative to the previous state of the
// session.
// -the indexes in |deletions| are relative to the previous state of the
// session.
// -the indexes in |insertions| are relative to the final state of the
// session.
std::vector<size_t> deletions_;
std::vector<size_t> insertions_;
std::vector<size_t> updates_;
};
} // namespace ios_internal
#endif // IOS_CHROME_BROWSER_UI_TAB_SWITCHER_SESSION_CHANGES_H_
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/ui/tab_switcher/session_changes.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_utils.h"
namespace ios_internal {
SessionChanges::SessionChanges(
std::vector<size_t> const& tabHashesInInitialState,
std::vector<size_t> const& tabHashesInFinalState) {
ios_internal::MinimalReplacementOperations(tabHashesInInitialState,
tabHashesInFinalState, &updates_,
&deletions_, &insertions_);
}
SessionChanges::~SessionChanges() {}
std::vector<size_t> const& SessionChanges::deletions() const {
return deletions_;
}
std::vector<size_t> const& SessionChanges::insertions() const {
return insertions_;
}
std::vector<size_t> const& SessionChanges::updates() const {
return updates_;
}
bool SessionChanges::hasChanges() const {
return updates_.size() || deletions_.size() || insertions_.size();
}
} // namespace ios_internal
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_MODEL_SNAPSHOT_H_
#define IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_MODEL_SNAPSHOT_H_
#import <Foundation/Foundation.h>
#include <vector>
#import "base/ios/weak_nsobject.h"
@class TabModel;
@class Tab;
// Contains an approximate snapshot of the tab model passed to the initializer.
// Used to feed the cells to UICollectionViews, and to compute the changes
// required to update UICollectionViews.
// The changes are computed based on a list of hashes of the tabs.
// One resulting limitation is that there is no differenciation between a tab
// that is partially updated (because the snapshot changed), and a completely
// new tab replacing an older tab. This limitation has little impact because
// very few navigations occur when the tab switcher is shown.
class TabModelSnapshot {
public:
// Default constructor. |tabModel| can be nil.
explicit TabModelSnapshot(TabModel* tabModel);
~TabModelSnapshot();
// Returns the list of hashes for every tabs contained in the TabModel during
// the initialization.
std::vector<size_t> const& hashes() const;
// Returns a list of weak pointers to the tabs.
std::vector<base::WeakNSObject<Tab>> const& tabs() const;
// Returns a hash of the properties of a tab that are visible in the tab
// switcher's UI.
static size_t hashOfTheVisiblePropertiesOfATab(Tab* tab);
private:
std::vector<base::WeakNSObject<Tab>> _tabs;
std::vector<size_t> _hashes;
};
#endif // IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_MODEL_SNAPSHOT_H_
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/ui/tab_switcher/tab_model_snapshot.h"
#include "base/strings/sys_string_conversions.h"
#import "ios/chrome/browser/tabs/tab.h"
TabModelSnapshot::TabModelSnapshot(TabModel* tabModel) {
for (Tab* tab in tabModel) {
_hashes.push_back(hashOfTheVisiblePropertiesOfATab(tab));
_tabs.push_back(base::WeakNSObject<Tab>(tab));
}
}
TabModelSnapshot::~TabModelSnapshot() {}
std::vector<size_t> const& TabModelSnapshot::hashes() const {
DCHECK_EQ(_tabs.size(), _hashes.size());
return _hashes;
}
std::vector<base::WeakNSObject<Tab>> const& TabModelSnapshot::tabs() const {
DCHECK_EQ(_tabs.size(), _hashes.size());
return _tabs;
}
size_t TabModelSnapshot::hashOfTheVisiblePropertiesOfATab(Tab* tab) {
DCHECK(tab);
std::stringstream ss;
// lastVisitedTimestamp is used as an approximation for whether the tab's
// snapshot changed.
ss << tab.tabId << std::endl
<< base::SysNSStringToUTF8(tab.urlDisplayString) << std::endl
<< std::hexfloat << tab.lastVisitedTimestamp << std::endl;
return std::hash<std::string>()(ss.str());
}
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/ui/tab_switcher/tab_model_snapshot.h"
#import "base/mac/scoped_nsobject.h"
#import "ios/chrome/browser/tabs/tab.h"
#include "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
@interface TabModelMock : NSObject<NSFastEnumeration> {
base::scoped_nsobject<NSArray> tabs_;
}
@end
@implementation TabModelMock
- (id)initWithTabs:(NSArray*)tabs {
if ((self = [super init])) {
tabs_.reset([tabs retain]);
}
return self;
}
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState*)state
objects:(id*)stackbuf
count:(NSUInteger)len {
return [tabs_ countByEnumeratingWithState:state objects:stackbuf count:len];
}
@end
namespace {
class TabModelSnapshotTest : public PlatformTest {
protected:
Tab* TabMock(NSString* tabId, NSString* url, double time) {
id tabMock = [OCMockObject mockForClass:[Tab class]];
[[[tabMock stub] andReturn:tabId] tabId];
[[[tabMock stub] andReturn:url] urlDisplayString];
[[[tabMock stub] andReturnValue:OCMOCK_VALUE(time)] lastVisitedTimestamp];
return (Tab*)tabMock;
}
};
TEST_F(TabModelSnapshotTest, TestSingleHash) {
Tab* tab1 = TabMock(@"id1", @"url1", 12345.6789);
Tab* tab2 = TabMock(@"id2", @"url1", 12345.6789);
Tab* tab3 = TabMock(@"id1", @"url2", 12345.6789);
Tab* tab4 = TabMock(@"id1", @"url1", 12345);
// Same tab
size_t hash1 = TabModelSnapshot::hashOfTheVisiblePropertiesOfATab(tab1);
size_t hash2 = TabModelSnapshot::hashOfTheVisiblePropertiesOfATab(tab1);
EXPECT_EQ(hash1, hash2);
// Different ids
size_t hash3 = TabModelSnapshot::hashOfTheVisiblePropertiesOfATab(tab1);
size_t hash4 = TabModelSnapshot::hashOfTheVisiblePropertiesOfATab(tab2);
EXPECT_NE(hash3, hash4);
// Different urls
size_t hash5 = TabModelSnapshot::hashOfTheVisiblePropertiesOfATab(tab1);
size_t hash6 = TabModelSnapshot::hashOfTheVisiblePropertiesOfATab(tab3);
EXPECT_NE(hash5, hash6);
// Different timestamps
size_t hash7 = TabModelSnapshot::hashOfTheVisiblePropertiesOfATab(tab1);
size_t hash8 = TabModelSnapshot::hashOfTheVisiblePropertiesOfATab(tab4);
EXPECT_NE(hash7, hash8);
}
TEST_F(TabModelSnapshotTest, TestSnapshotHashes) {
Tab* tab1 = TabMock(@"id1", @"url1", 12345.6789);
Tab* tab2 = TabMock(@"id2", @"url1", 12345.6789);
base::scoped_nsobject<TabModelMock> tabModel(
[[TabModelMock alloc] initWithTabs:@[ tab1, tab2 ]]);
TabModelSnapshot tabModelSnapshot((TabModel*)tabModel.get());
tabModel.reset();
EXPECT_EQ(tabModelSnapshot.hashes().size(), 2UL);
EXPECT_EQ(tabModelSnapshot.hashes()[0],
TabModelSnapshot::hashOfTheVisiblePropertiesOfATab(tab1));
EXPECT_EQ(tabModelSnapshot.hashes()[1],
TabModelSnapshot::hashOfTheVisiblePropertiesOfATab(tab2));
}
} // namespace
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_H_
#define IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_H_
#import <UIKit/UIKit.h>
#import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h"
#include "ios/chrome/browser/ui/tab_switcher/tab_switcher_transition_context.h"
#include "ui/base/page_transition_types.h"
#include "url/gurl.h"
@class Tab;
@class TabModel;
@protocol TabSwitcher;
@protocol ToolbarOwner;
// This delegate is used to drive the TabSwitcher dismissal and execute code
// when the presentation and dismmiss animations finishes. The main controller
// is a good exemple of the implementation of this delegate.
@protocol TabSwitcherDelegate<NSObject>
// Informs the delegate the stack controller is starting to be dismissed with
// the given model active.
- (void)tabSwitcher:(id<TabSwitcher>)tabSwitcher
dismissTransitionWillStartWithActiveModel:(TabModel*)tabModel;
// Informs the delegate that the stack controller is done and should be
// dismissed.
- (void)tabSwitcherDismissTransitionDidEnd:(id<TabSwitcher>)tabSwitcher;
// Informs the delegate that the stack controller has finished its
// presentation transition animation.
- (void)tabSwitcherPresentationTransitionDidEnd:(id<TabSwitcher>)tabSwitcher;
// Returns a reference to the owner of the toolbar that should be used in the
// transition animations.
- (id<ToolbarOwner>)tabSwitcherTransitionToolbarOwner;
@end
// This protocol describes the common interface between the two implementations
// of the tab switcher. StackViewController for iPhone and TabSwitcherController
// for iPad are examples of implementers of this protocol.
@protocol TabSwitcher
// This delegate must be set on the tab switcher in order to drive the tab
// switcher.
@property(nonatomic, assign) id<TabSwitcherDelegate> delegate;
// Restores the internal state of the tab switcher with the given tab models,
// which must not be nil. |activeTabModel| is the model which starts active,
// and must be one of the other two models. Should only be called when the
// object is not being shown.
- (void)restoreInternalStateWithMainTabModel:(TabModel*)mainModel
otrTabModel:(TabModel*)otrModel
activeTabModel:(TabModel*)activeModel;
// Returns the root view of the tab switcher.
- (UIView*)view;
// Performs an animation of the selected tab from its presented state to its
// place in the tab switcher. Should be called after the tab switcher's view has
// been presented.
- (void)showWithSelectedTabAnimation;
// Performs an animation from the selected tab in the tab switcher to the
// presented tab in the content area. When the animation completes, calls the
// delegate methods:
// |-tabSwitcher:dismissTransitionWillStartWithActiveModel:| and
// |-tabSwitcherDismissTransitionDidEnd:|
- (Tab*)dismissWithNewTabAnimationToModel:(TabModel*)targetModel
withURL:(const GURL&)url
atIndex:(NSUInteger)position
transition:(ui::PageTransition)transition;
// Updates the OTR (Off The Record) tab model. Should only be called when both
// the current OTR tab model and the new OTR tab model are either nil or contain
// no tabs. This must be called after the otr tab model has been deleted because
// the incognito browser state is deleted.
- (void)setOtrTabModel:(TabModel*)otrModel;
@optional
@property(nonatomic, retain) TabSwitcherTransitionContext* transitionContext;
// Dismisses the tab switcher using the given tab model. The dismissal of the
// tab switcher will be animated if the |animated| parameter is set to YES.
- (void)tabSwitcherDismissWithModel:(TabModel*)model animated:(BOOL)animated;
@end
#endif // IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_H_
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_BUTTON_H_
#define IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_BUTTON_H_
#import <UIKit/UIKit.h>
// This class is a UIButton with an inkview.
// Replaces MDCButton, because MDCButton does a lot of blending, which is a
// problem in the tab switcher where almost the entire screen can be filled
// by buttons.
@interface TabSwitcherButton : UIButton
// Resets the state of the button, in particular the inkview state.
- (void)resetState;
@end
#endif // IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_BUTTON_H_
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ios/chrome/browser/ui/tab_switcher/tab_switcher_button.h"
#include "base/mac/scoped_nsobject.h"
#import "ios/third_party/material_components_ios/src/components/Ink/src/MaterialInk.h"
@interface TabSwitcherButton () {
base::scoped_nsobject<MDCInkTouchController> _inkTouchController;
}
@end
@implementation TabSwitcherButton
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
_inkTouchController.reset(
[[MDCInkTouchController alloc] initWithView:self]);
[_inkTouchController addInkView];
// TODO(crbug.com/606807): Adjust the desired ink color.
[_inkTouchController defaultInkView].inkColor =
[[UIColor whiteColor] colorWithAlphaComponent:0.4];
}
return self;
}
- (void)resetState {
[_inkTouchController cancelInkTouchProcessing];
}
@end
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_CACHE_H_
#define IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_CACHE_H_
#import <UIKit/UIKit.h>
#import "ios/chrome/browser/tabs/tab_model_observer.h"
@class Tab;
typedef void (^SnapshotCompletionBlock)(UIImage*);
struct PendingSnapshotRequest {
CFTimeInterval requestId;
NSUInteger sessionId;
PendingSnapshotRequest() : requestId(0), sessionId(0) {}
void clear() {
requestId = 0;
sessionId = 0;
}
};
// A class that provides access to resized snapshots of tabs. The snapshots are
// cached and freed under memory pressure.
@interface TabSwitcherCache : NSObject<TabModelObserver>
@property(nonatomic, readonly) TabModel* mainTabModel;
// Request a snapshot for the given |tab| of the given |size|, |completionBlock|
// will be called with the requested snapshot. Must be called from the main
// thread with a non nil |tab|, a non nil |completionBlock|, and a non
// CGSizeZero |size|.
// If the resized snapshot is cached, |completionBlock| is called synchronously.
// Otherwise it is called asynchronously on the main thread. The block's
// execution can be cancelled with |cancelPendingSnapshotRequest:|.
// Requesting a snapshot for a given tab will cancel any previous request for
// that same tab. The completion handler for a cancelled request may never be
// called.
- (PendingSnapshotRequest)requestSnapshotForTab:(Tab*)tab
withSize:(CGSize)size
completionBlock:
(SnapshotCompletionBlock)completionBlock;
// Updates the snapshot for the given |tab| with |image| resized to |size|.
- (void)updateSnapshotForTab:(Tab*)tab
withImage:(UIImage*)image
size:(CGSize)size;
// Cancels the request identified by |pendingRequest|. Does nothing if no
// request matches |pendingRequest|.
- (void)cancelPendingSnapshotRequest:(PendingSnapshotRequest)pendingRequest;
// Sets the main and incognito tab models.
- (void)setMainTabModel:(TabModel*)mainTabModel
otrTabModel:(TabModel*)otrTabModel;
@end
#endif // IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_CACHE_H_
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_cache.h"
#include <unordered_map>
#import "base/ios/weak_nsobject.h"
#include "base/logging.h"
#include "base/mac/scoped_nsobject.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/lock.h"
#import "ios/chrome/browser/tabs/tab.h"
#import "ios/chrome/browser/tabs/tab_model.h"
#import "ios/chrome/browser/ui/uikit_ui_util.h"
#include "ios/chrome/common/ios_app_bundle_id_prefix.h"
#include "ios/web/public/navigation_item.h"
namespace {
// The maximum amount of pixels the cache should hold.
NSUInteger kCacheMaxPixelCount = 2048 * 1536 * 4;
// Two floats that are different from less than |kMaxFloatDelta| are considered
// equals.
const CGFloat kMaxFloatDelta = 0.01;
} // namespace
@interface TabSwitcherCache ()
// Clears the cache. Called when a low memory warning was received.
- (void)lowMemoryWarningReceived;
// Returns a autoreleased resized image of |image|.
+ (UIImage*)resizedImage:(UIImage*)image toSize:(CGSize)size;
@end
@implementation TabSwitcherCache {
base::scoped_nsobject<NSCache> _cache;
dispatch_queue_t _cacheQueue;
// The tab models.
TabModel* _mainTabModel; // weak
TabModel* _otrTabModel; // weak
// Lock protecting the pending requests map.
base::Lock _lock;
std::unordered_map<NSUInteger, PendingSnapshotRequest> _pendingRequests;
}
@synthesize mainTabModel = _mainTabModel;
- (instancetype)init {
self = [super init];
if (self) {
_cache.reset([[NSCache alloc] init]);
[_cache setTotalCostLimit:kCacheMaxPixelCount];
NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:@selector(lowMemoryWarningReceived)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
std::string queueName =
base::StringPrintf("%s.chrome.ios.TabSwitcherCacheQueue",
BUILDFLAG(IOS_APP_BUNDLE_ID_PREFIX));
_cacheQueue =
dispatch_queue_create(queueName.c_str(), DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (void)dealloc {
NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
[nc removeObserver:self
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
dispatch_release(_cacheQueue);
[_mainTabModel removeObserver:self];
[_otrTabModel removeObserver:self];
[super dealloc];
}
- (PendingSnapshotRequest)requestSnapshotForTab:(Tab*)tab
withSize:(CGSize)size
completionBlock:
(SnapshotCompletionBlock)completionBlock {
DCHECK([NSThread isMainThread]);
DCHECK(tab);
DCHECK(completionBlock);
DCHECK(!CGSizeEqualToSize(size, CGSizeZero));
PendingSnapshotRequest currentRequest;
UIImage* snapshot = [_cache objectForKey:[self keyForTab:tab]];
if (snapshot) {
CGFloat tabContentAreaRatio = tab.snapshotContentArea.size.width /
tab.snapshotContentArea.size.height;
CGFloat cachedSnapshotRatio =
[snapshot size].width / [snapshot size].height;
// Check that the cached snapshot's ratio matches the content area ratio.
if (std::abs(tabContentAreaRatio - cachedSnapshotRatio) < kMaxFloatDelta &&
[snapshot size].width >= size.width) {
// Cache hit.
completionBlock(snapshot);
return currentRequest;
}
}
// Cache miss.
currentRequest = [self recordPendingRequestForTab:tab];
NSString* key = [self keyForTab:tab];
[tab retrieveSnapshot:^(UIImage* snapshot) {
PendingSnapshotRequest requestForSession = [self pendingRequestForTab:tab];
// Cancel this request if another one has replaced it for this sessionId.
if (currentRequest.requestId != requestForSession.requestId)
return;
dispatch_async(_cacheQueue, ^{
DCHECK(![NSThread isMainThread]);
UIImage* resizedSnapshot =
[TabSwitcherCache resizedImage:snapshot toSize:size];
if ([self storeImage:resizedSnapshot forKey:key request:currentRequest]) {
dispatch_async(dispatch_get_main_queue(), ^{
// Cancel this request if another one has replaced it for this
// sessionId.
PendingSnapshotRequest requestForSession =
[self pendingRequestForTab:tab];
if (currentRequest.requestId != requestForSession.requestId)
return;
completionBlock(resizedSnapshot);
[self removePendingSnapshotRequest:currentRequest];
});
}
});
}];
return currentRequest;
}
- (void)updateSnapshotForTab:(Tab*)tab
withImage:(UIImage*)image
size:(CGSize)size {
DCHECK([NSThread isMainThread]);
DCHECK(tab);
DCHECK(image);
PendingSnapshotRequest currentRequest = [self recordPendingRequestForTab:tab];
NSString* key = [self keyForTab:tab];
dispatch_async(_cacheQueue, ^{
DCHECK(![NSThread isMainThread]);
UIImage* resizedSnapshot =
[TabSwitcherCache resizedImage:image toSize:size];
[self storeImage:resizedSnapshot forKey:key request:currentRequest];
[self removePendingSnapshotRequest:currentRequest];
});
}
- (void)cancelPendingSnapshotRequest:(PendingSnapshotRequest)pendingRequest {
[self removePendingSnapshotRequest:pendingRequest];
}
#pragma mark - Private
- (NSString*)keyForTab:(Tab*)tab {
DCHECK([NSThread isMainThread]);
return [tab currentSessionID];
}
- (PendingSnapshotRequest)recordPendingRequestForTab:(Tab*)tab {
PendingSnapshotRequest pendingRequest;
pendingRequest.requestId = [[NSDate date] timeIntervalSince1970];
pendingRequest.sessionId = [[self keyForTab:tab] hash];
base::AutoLock guard(_lock);
_pendingRequests[pendingRequest.sessionId] = pendingRequest;
return pendingRequest;
}
- (PendingSnapshotRequest)pendingRequestForTab:(Tab*)tab {
DCHECK([NSThread isMainThread]);
PendingSnapshotRequest pendingRequest;
if (![tab webStateImpl])
return pendingRequest;
NSUInteger sessionId = [[self keyForTab:tab] hash];
base::AutoLock guard(_lock);
auto it = _pendingRequests.find(sessionId);
if (it != _pendingRequests.end())
pendingRequest = it->second;
return pendingRequest;
}
- (void)removePendingSnapshotRequest:(PendingSnapshotRequest)pendingRequest {
base::AutoLock guard(_lock);
auto itRequest = _pendingRequests.find(pendingRequest.sessionId);
if (itRequest != _pendingRequests.end() &&
pendingRequest.requestId == itRequest->second.requestId) {
_pendingRequests.erase(itRequest);
}
}
- (void)removePendingSnapshotRequestForTab:(Tab*)tab {
base::AutoLock guard(_lock);
auto itRequest = _pendingRequests.find([[self keyForTab:tab] hash]);
if (itRequest != _pendingRequests.end())
_pendingRequests.erase(itRequest);
}
- (BOOL)storeImage:(UIImage*)image
forKey:(NSString*)key
request:(PendingSnapshotRequest)request {
DCHECK(request.requestId != 0);
if (!image)
return NO;
{
base::AutoLock guard(_lock);
auto it = _pendingRequests.find(request.sessionId);
if (it == _pendingRequests.end())
return NO;
// Only write the image in cache if the request is still valid.
if (request.requestId != it->second.requestId)
return NO;
}
const CGFloat screenScale = [[UIScreen mainScreen] scale];
const NSUInteger cost =
image.size.width * screenScale * image.size.height * screenScale;
[_cache setObject:image forKey:key cost:cost];
return YES;
}
+ (UIImage*)resizedImage:(UIImage*)image toSize:(CGSize)size {
DCHECK(image.scale == 1);
CGFloat screenScale = [[UIScreen mainScreen] scale];
CGSize pixelSize = size;
pixelSize.width *= screenScale;
pixelSize.height *= screenScale;
UIImage* resizedSnapshot =
ResizeImage(image, pixelSize, ProjectionMode::kAspectFillNoClipping, YES);
// Creates a new image with the correct |scale| attribute.
return [[[UIImage alloc] initWithCGImage:resizedSnapshot.CGImage
scale:screenScale
orientation:UIImageOrientationUp] autorelease];
}
- (void)lowMemoryWarningReceived {
[_cache removeAllObjects];
}
- (void)setMainTabModel:(TabModel*)mainTabModel
otrTabModel:(TabModel*)otrTabModel {
if (mainTabModel != _mainTabModel)
[self replaceOldTabModel:&_mainTabModel withTabModel:mainTabModel];
if (otrTabModel != _otrTabModel)
[self replaceOldTabModel:&_otrTabModel withTabModel:otrTabModel];
}
- (void)replaceOldTabModel:(TabModel**)oldTabModel
withTabModel:(TabModel*)newTabModel {
[*oldTabModel removeObserver:self];
*oldTabModel = newTabModel;
[newTabModel addObserver:self];
}
#pragma mark - TabModelObserver
- (void)tabModel:(TabModel*)model
didRemoveTab:(Tab*)tab
atIndex:(NSUInteger)index {
[self removePendingSnapshotRequestForTab:tab];
[_cache removeObjectForKey:[self keyForTab:tab]];
}
- (void)tabModel:(TabModel*)model
didChangeTabSnapshot:(Tab*)tab
withImage:image {
[self removePendingSnapshotRequestForTab:tab];
[_cache removeObjectForKey:[self keyForTab:tab]];
}
@end
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_CONTROLLER_H_
#define IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_CONTROLLER_H_
#import <UIKit/UIKit.h>
#import "ios/chrome/browser/ui/tab_switcher/tab_switcher.h"
namespace ios {
class ChromeBrowserState;
}
@interface TabSwitcherController : UIViewController<TabSwitcher>
- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState
mainTabModel:(TabModel*)mainTabModel
otrTabModel:(TabModel*)otrTabModel
activeTabModel:(TabModel*)activeTabModel;
@end
#endif // IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_CONTROLLER_H_
This diff is collapsed.
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <EarlGrey/EarlGrey.h>
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#include "base/mac/scoped_nsobject.h"
#include "base/test/scoped_command_line.h"
#import "ios/chrome/app/main_controller_private.h"
#include "ios/chrome/browser/chrome_switches.h"
#import "ios/chrome/browser/ui/commands/generic_chrome_command.h"
#include "ios/chrome/browser/ui/commands/ios_command_ids.h"
#import "ios/chrome/browser/ui/tools_menu/tools_menu_view_controller.h"
#import "ios/chrome/browser/ui/ui_util.h"
#include "ios/chrome/grit/ios_strings.h"
#import "ios/chrome/test/app/chrome_test_util.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey_ui.h"
#import "ios/chrome/test/earl_grey/chrome_matchers.h"
#import "ios/chrome/test/earl_grey/chrome_test_case.h"
#include "ui/base/l10n/l10n_util.h"
using chrome_test_util::buttonWithAccessibilityLabel;
using chrome_test_util::buttonWithAccessibilityLabelId;
using chrome_test_util::staticTextWithAccessibilityLabelId;
namespace {
// Returns the GREYMatcher for the button that closes the tab switcher.
id<GREYMatcher> tabSwitcherCloseButton() {
return buttonWithAccessibilityLabelId(IDS_IOS_TAB_STRIP_LEAVE_TAB_SWITCHER);
}
// Returns the GREYMatcher for the button that creates new non incognito tabs
// from within the tab switcher.
id<GREYMatcher> tabSwitcherNewTabButton() {
return grey_allOf(
buttonWithAccessibilityLabelId(IDS_IOS_TAB_SWITCHER_CREATE_NEW_TAB),
grey_sufficientlyVisible(), nil);
}
// Returns the GREYMatcher for the button that creates new incognito tabs from
// within the tab switcher.
id<GREYMatcher> tabSwitcherNewIncognitoTabButton() {
return grey_allOf(buttonWithAccessibilityLabelId(
IDS_IOS_TAB_SWITCHER_CREATE_NEW_INCOGNITO_TAB),
grey_sufficientlyVisible(), nil);
}
// Returns the GREYMatcher for the button to go to the non incognito panel in
// the tab switcher.
id<GREYMatcher> tabSwitcherHeaderPanelButton() {
NSString* accessibility_label = l10n_util::GetNSStringWithFixup(
IDS_IOS_TAB_SWITCHER_HEADER_NON_INCOGNITO_TABS);
return grey_accessibilityLabel(accessibility_label);
}
// Returns the GREYMatcher for the button that closes tabs on iPad.
id<GREYMatcher> closeTabButton() {
return buttonWithAccessibilityLabelId(IDS_IOS_TOOLS_MENU_CLOSE_TAB);
}
// Opens a new incognito tabs using the tools menu.
void openNewIncognitoTabUsingUI() {
[ChromeEarlGreyUI openToolsMenu];
id<GREYMatcher> newIncognitoTabButtonMatcher =
grey_accessibilityID(kToolsMenuNewIncognitoTabId);
[[EarlGrey selectElementWithMatcher:newIncognitoTabButtonMatcher]
performAction:grey_tap()];
}
// Triggers the opening of the tab switcher by launching a command. Should be
// called only when the tab switcher is not presented.
void enterTabSwitcherWithCommand() {
base::scoped_nsobject<GenericChromeCommand> command(
[[GenericChromeCommand alloc] initWithTag:IDC_TOGGLE_TAB_SWITCHER]);
chrome_test_util::RunCommandWithActiveViewController(command);
}
} // namespace
@interface TabSwitcherControllerTestCase : ChromeTestCase
@end
@implementation TabSwitcherControllerTestCase {
std::unique_ptr<base::test::ScopedCommandLine> scoped_command_line_;
}
- (void)setUp {
[super setUp];
scoped_command_line_.reset(new base::test::ScopedCommandLine());
scoped_command_line_->GetProcessCommandLine()->AppendSwitch(
switches::kEnableTabSwitcher);
}
// Checks that the tab switcher is not presented.
- (void)assertTabSwitcherIsInactive {
[[GREYUIThreadExecutor sharedInstance] drainUntilIdle];
MainController* main_controller = chrome_test_util::GetMainController();
GREYAssertTrue(![main_controller isTabSwitcherActive],
@"Tab Switcher should be inactive");
}
// Checks that the tab switcher is active.
- (void)assertTabSwitcherIsActive {
[[GREYUIThreadExecutor sharedInstance] drainUntilIdle];
MainController* main_controller = chrome_test_util::GetMainController();
GREYAssertTrue([main_controller isTabSwitcherActive],
@"Tab Switcher should be active");
}
// Checks that the text associated with |messageId| is somewhere on screen.
- (void)assertMessageIsVisible:(int)messageId {
id<GREYMatcher> messageMatcher =
grey_allOf(staticTextWithAccessibilityLabelId(messageId),
grey_sufficientlyVisible(), nil);
[[EarlGrey selectElementWithMatcher:messageMatcher]
assertWithMatcher:grey_notNil()];
}
// Checks that the text associated with |messageId| is not visible.
- (void)assertMessageIsNotVisible:(int)messageId {
id<GREYMatcher> messageMatcher =
grey_allOf(staticTextWithAccessibilityLabelId(messageId),
grey_sufficientlyVisible(), nil);
[[EarlGrey selectElementWithMatcher:messageMatcher]
assertWithMatcher:grey_nil()];
}
// Tests entering and leaving the tab switcher.
- (void)testEnteringTabSwitcher {
if (!IsIPadIdiom())
return;
[self assertTabSwitcherIsInactive];
enterTabSwitcherWithCommand();
[self assertTabSwitcherIsActive];
// Check that the "No Open Tabs" message is not displayed.
[self assertMessageIsNotVisible:
IDS_IOS_TAB_SWITCHER_NO_LOCAL_NON_INCOGNITO_TABS_TITLE];
// Press the :: icon to exit the tab switcher.
[[EarlGrey selectElementWithMatcher:tabSwitcherCloseButton()]
performAction:grey_tap()];
[self assertTabSwitcherIsInactive];
}
// Tests entering tab switcher by closing all tabs, and leaving the tab switcher
// by creating a new tab.
- (void)testClosingAllTabsAndCreatingNewTab {
if (!IsIPadIdiom())
return;
[self assertTabSwitcherIsInactive];
// Close the tab.
[[EarlGrey selectElementWithMatcher:closeTabButton()]
performAction:grey_tap()];
[self assertTabSwitcherIsActive];
// Check that the "No Open Tabs" message is displayed.
[self assertMessageIsVisible:
IDS_IOS_TAB_SWITCHER_NO_LOCAL_NON_INCOGNITO_TABS_TITLE];
// Create a new tab.
[[EarlGrey selectElementWithMatcher:tabSwitcherNewTabButton()]
performAction:grey_tap()];
[self assertTabSwitcherIsInactive];
}
// Tests entering tab switcher from incognito mode.
- (void)testIncognitoTabs {
if (!IsIPadIdiom())
return;
[self assertTabSwitcherIsInactive];
// Create new incognito tab from tools menu.
openNewIncognitoTabUsingUI();
// Close the incognito tab and check that the we are entering the tab
// switcher.
[[EarlGrey selectElementWithMatcher:closeTabButton()]
performAction:grey_tap()];
[self assertTabSwitcherIsActive];
// Check that the "No Incognito Tabs" message is shown.
[self assertMessageIsVisible:
IDS_IOS_TAB_SWITCHER_NO_LOCAL_INCOGNITO_TABS_PROMO];
// Create new incognito tab.
[[EarlGrey selectElementWithMatcher:tabSwitcherNewIncognitoTabButton()]
performAction:grey_tap()];
// Verify that we've left the tab switcher.
[self assertTabSwitcherIsInactive];
// Close tab and verify we've entered the tab switcher again.
[[EarlGrey selectElementWithMatcher:closeTabButton()]
performAction:grey_tap()];
[self assertTabSwitcherIsActive];
// Switch to the non incognito panel.
[[EarlGrey selectElementWithMatcher:tabSwitcherHeaderPanelButton()]
performAction:grey_tap()];
// Press the :: icon to exit the tab switcher.
[[EarlGrey selectElementWithMatcher:tabSwitcherCloseButton()]
performAction:grey_tap()];
// Verify that we've left the tab switcher.
[self assertTabSwitcherIsInactive];
}
@end
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_HEADER_CELL_H_
#define IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_HEADER_CELL_H_
#import <UIKit/UIKit.h>
@class SessionCellData;
// This class is the cell class used in the table view of the tab switcher
// header.
@interface TabSwitcherHeaderCell : UICollectionViewCell
// Default table view cell identifier.
+ (NSString*)identifier;
// Load the cell content using the given SessionCellData object.
- (void)loadSessionCellData:(SessionCellData*)sessionCellData;
@end
#endif // IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_HEADER_CELL_H_
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_header_cell.h"
#include "base/logging.h"
#include "base/mac/scoped_nsobject.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_session_cell_data.h"
#import "ios/chrome/browser/ui/uikit_ui_util.h"
#import "ios/third_party/material_components_ios/src/components/Palettes/src/MaterialPalettes.h"
#import "ios/third_party/material_components_ios/src/components/Typography/src/MaterialTypography.h"
namespace {
// View alpha value used when the header cell is not selected.
const CGFloat kInactiveAlpha = 0.54;
const CGFloat kImageViewWidth = 24;
}
@interface TabSwitcherHeaderCell () {
base::scoped_nsobject<UIImageView> _imageView;
base::scoped_nsobject<UILabel> _label;
}
@end
@implementation TabSwitcherHeaderCell
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [[MDCPalette greyPalette] tint900];
[self loadSubviews];
[self setSelected:NO];
}
return self;
}
+ (NSString*)identifier {
return @"TabSwitcherHeaderCell";
}
- (NSString*)reuseIdentifier {
return [[self class] identifier];
}
- (void)loadSessionCellData:(SessionCellData*)sessionCellData {
[_imageView setImage:sessionCellData.image];
[_label setText:sessionCellData.title];
[self setNeedsLayout];
}
- (void)setSelected:(BOOL)selected {
[super setSelected:selected];
[_imageView setAlpha:selected ? 1. : kInactiveAlpha];
[_label setAlpha:selected ? 1. : kInactiveAlpha];
[self setNeedsLayout];
}
#pragma mark - Private
- (void)loadSubviews {
_imageView.reset([[UIImageView alloc] initWithFrame:CGRectZero]);
[_imageView setContentMode:UIViewContentModeCenter];
[_imageView setTranslatesAutoresizingMaskIntoConstraints:NO];
[_imageView setTintColor:[UIColor whiteColor]];
_label.reset([[UILabel alloc] initWithFrame:CGRectZero]);
[_label setBackgroundColor:[UIColor clearColor]];
[_label setTranslatesAutoresizingMaskIntoConstraints:NO];
[_label setTextColor:[UIColor whiteColor]];
[_label setFont:[MDCTypography body2Font]];
// Configure layout.
// The icon and the title are centered within |contentView|, have a spacing of
// one-third of icon width and the icon is on the leading side of title.
base::scoped_nsobject<UIStackView> stackView(
[[UIStackView alloc] initWithArrangedSubviews:@[ _imageView, _label ]]);
[stackView setSpacing:kImageViewWidth / 3];
[self.contentView addSubview:stackView];
[stackView setTranslatesAutoresizingMaskIntoConstraints:NO];
[NSLayoutConstraint activateConstraints:@[
[self.contentView.centerYAnchor
constraintEqualToAnchor:[stackView centerYAnchor]],
[self.contentView.centerXAnchor
constraintEqualToAnchor:[stackView centerXAnchor]]
]];
}
@end
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_HEADER_VIEW_H_
#define IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_HEADER_VIEW_H_
#import <UIKit/UIKit.h>
#include "base/ios/block_types.h"
@class SessionCellData;
@class TabSwitcherHeaderView;
@protocol TabSwitcherHeaderViewDelegate<NSObject>
- (void)tabSwitcherHeaderViewDismiss:(TabSwitcherHeaderView*)view;
- (void)tabSwitcherHeaderViewDidSelectSessionAtIndex:(NSInteger)index;
// Called to get the currently selected panel index. Returns NSNotFound when no
// panel is selected.
- (NSInteger)tabSwitcherHeaderViewSelectedPanelIndex;
@end
@protocol TabSwitcherHeaderViewDataSource<NSObject>
- (NSInteger)tabSwitcherHeaderViewSessionCount;
- (SessionCellData*)sessionCellDataAtIndex:(NSUInteger)index;
@end
@interface TabSwitcherHeaderView : UIView
@property(nonatomic, assign) id<TabSwitcherHeaderViewDelegate> delegate;
@property(nonatomic, assign) id<TabSwitcherHeaderViewDataSource> dataSource;
@property(nonatomic, readonly) UIView* dismissButton;
// Selects the item at the specified index.
// The delegate is not called.
- (void)selectItemAtIndex:(NSInteger)index;
// Reload the collection view.
- (void)reloadData;
// Performs an update on the header view using the passed block.
- (void)performUpdate:(void (^)(TabSwitcherHeaderView* headerView))updateBlock;
// Performs an update on the header view using the passed block, the completion
// handler block is called at the end of the update animations.
- (void)performUpdate:(void (^)(TabSwitcherHeaderView* headerView))updateBlock
completion:(ProceduralBlock)completion;
// Updates methods can only be called inside an update block passed to the
// - (void)performUpdate: method.
// Update methods takes arrays of NSNumbers.
- (void)insertSessionsAtIndexes:(NSArray*)indexes;
- (void)removeSessionsAtIndexes:(NSArray*)indexes;
// Returns the accessibility title of the panel at index |index|.
- (NSString*)panelTitleAtIndex:(NSInteger)index;
@end
#endif // IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_HEADER_VIEW_H_
This diff is collapsed.
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_MODEL_H_
#define IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_MODEL_H_
#import <Foundation/Foundation.h>
#include <string>
#include "base/ios/block_types.h"
#include "base/mac/scoped_nsobject.h"
#import "ios/chrome/browser/tabs/tab_model_observer.h"
#import "ios/chrome/browser/ui/ntp/recent_tabs/synced_sessions.h"
#import "ios/chrome/browser/ui/ntp/recent_tabs/synced_sessions_bridge.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_model_snapshot.h"
@class TabModel;
@class TabSwitcherCache;
enum class TabSwitcherSignInPanelsType {
PANEL_USER_SIGNED_OUT,
PANEL_USER_SIGNED_IN_SYNC_OFF,
PANEL_USER_SIGNED_IN_SYNC_IN_PROGRESS,
PANEL_USER_SIGNED_IN_SYNC_ON_NO_SESSIONS,
NO_PANEL,
};
namespace ios_internal {
enum class SessionType {
OFF_THE_RECORD_SESSION,
REGULAR_SESSION,
DISTANT_SESSION
};
class ChromeBrowserState;
// Returns true if the session type is a local session. A local session is a
// "regular session" or an "off the record" session.
bool IsLocalSession(SessionType sessionType);
} // namespace ios_internal
// Protocol to observe changes to the TabSwitcherModel.
@protocol TabSwitcherModelDelegate<NSObject>
// Called when distant sessions were removed or inserted. |removedIndexes| and
// |insertedIndexes| contains indexes in ascending order.
- (void)distantSessionsRemovedAtSortedIndexes:(NSArray*)removedIndexes
insertedAtSortedIndexes:(NSArray*)insertedIndexes;
// Called when the distant session with the tag |tag| may need to be updated.
- (void)distantSessionMayNeedUpdate:(std::string const&)tag;
// Called when a local session of type |type| needs to be updated.
- (void)localSessionMayNeedUpdate:(ios_internal::SessionType)type;
// Called when the signed-in panel (if any) must change.
- (void)signInPanelChangedTo:(TabSwitcherSignInPanelsType)panelType;
// Called to retrieve the size of the item at the |index| in |session|.
- (CGSize)sizeForItemAtIndex:(NSUInteger)index
inSession:(ios_internal::SessionType)session;
@end
// This class serves as a bridge between Chrome-level data (CLD), and what is
// presented on screen in the Tab Switcher.
// The CLD is:
// -The "distant sessions", i.e. the list of Chrome instances the user is
// signed-in, and the tabs each of the session contains.
// -The non incognito/incognito tab models. Also known as the "local sessions".
//
// The TabSwitcher observes changes in the CLD, and takes snapshots of its
// state.
// Whenever the delegate can be notified, the TabSwitcher computes a diff
// and notifies the delegate of the changes.
@interface TabSwitcherModel : NSObject<SyncedSessionsObserver, TabModelObserver>
@property(nonatomic, readonly) TabModel* mainTabModel;
@property(nonatomic, readonly) TabModel* otrTabModel;
// Default initializer. |browserState| must not be nullptr, |delegate| must not
// be nil.
- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState
delegate:(id<TabSwitcherModelDelegate>)delegate
mainTabModel:(TabModel*)mainTabModel
otrTabModel:(TabModel*)otrTabModel
withCache:(TabSwitcherCache*)cache;
// Sets the main and incognito tab models, and notify the delegate of changes,
// if any. |mainTabModel| and |otrTabModel| can be nil.
- (void)setMainTabModel:(TabModel*)mainTabModel
otrTabModel:(TabModel*)otrTabModel;
// Returns the browserState.
- (ios::ChromeBrowserState*)browserState;
// Returns the latest data for the local session of type |type|.
- (std::unique_ptr<TabModelSnapshot>)tabModelSnapshotForLocalSession:
(ios_internal::SessionType)type;
// Returns the latest data for the distant session of tag |tag|.
- (std::unique_ptr<const synced_sessions::DistantSession>)distantSessionForTag:
(std::string const&)tag;
// Returns the current count of sessions including main and incognito.
- (NSInteger)sessionCount;
// Returns the current count of distant sessions.
- (NSInteger)distantSessionCount;
// Returns the type of the sign in panel.
- (TabSwitcherSignInPanelsType)signInPanelType;
// Returns the tag of the session at index |index|.
- (std::string const&)tagOfDistantSessionAtIndex:(int)index;
// Notifies the delegate that changes occured for the distant sessions.
- (void)syncedSessionsChanged;
@end
#endif // IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_MODEL_H_
This diff is collapsed.
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_MODEL_PRIVATE_H_
#define IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_MODEL_PRIVATE_H_
#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_model.h"
@interface TabSwitcherModel (TestingSupport)
// Notifies |delegate| about changes from |oldSessions| to |newSessions|,
// including the insertion/deletions of distant sessions, and the
// insertions/deletions/updates of tabs in the distant sessions.
// Does not notify the delegate when the ordering of the distant sessions
// changes.
+ (void)notifyDelegate:(id<TabSwitcherModelDelegate>)delegate
aboutChangeFrom:(synced_sessions::SyncedSessions&)oldSessions
to:(synced_sessions::SyncedSessions&)newSessions;
@end
#endif // IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_MODEL_PRIVATE_H_
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_PANEL_CELL_H_
#define IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_PANEL_CELL_H_
#import <UIKit/UIKit.h>
#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_model.h"
class GURL;
@class TabSwitcherCache;
@class Tab;
namespace ios {
class ChromeBrowserState;
} // namespace ios
CGFloat tabSwitcherLocalSessionCellTopBarHeight();
@protocol SessionCellDelegate<NSObject>
- (TabSwitcherCache*)tabSwitcherCache;
- (void)cellPressed:(UICollectionViewCell*)cell;
- (void)deleteButtonPressedForCell:(UICollectionViewCell*)cell;
@end
@interface TabSwitcherSessionCell : UICollectionViewCell
// Returns the cell's identifier used for the cell's re-use.
+ (NSString*)identifier;
@end
// Cell showing information about a local session.
@interface TabSwitcherLocalSessionCell : TabSwitcherSessionCell
// Returns the top bar of the cell. The top bar holds the favicon and the tab
// title.
@property(nonatomic, readonly) UIView* topBar;
// Sets the cell's appearance using information in |tab|.
- (void)setAppearanceForTab:(Tab*)tab cellSize:(CGSize)cellSize;
// Sets the cell's appearance depending on |type|.
- (void)setSessionType:(ios_internal::SessionType)type;
// Sets the cell's delegate.
- (void)setDelegate:(id<SessionCellDelegate>)delegate;
// Returns the current screenshot set on the cell.
- (UIImage*)screenshot;
// Sets the snapshot.
- (void)setSnapshot:(UIImage*)snapshot;
@end
@interface TabSwitcherDistantSessionCell : TabSwitcherSessionCell
// Sets the cell's title.
- (void)setTitle:(NSString*)title;
// Sets the session's URL to obtain the cell's favicon.
- (void)setSessionGURL:(GURL const&)gurl
withBrowserState:(ios::ChromeBrowserState*)browserState;
// Sets the cell's delegate.
- (void)setDelegate:(id<SessionCellDelegate>)delegate;
@end
#endif // IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_PANEL_CELL_H_
This diff is collapsed.
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_PANEL_COLLECTION_VIEW_LAYOUT_H_
#define IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_PANEL_COLLECTION_VIEW_LAYOUT_H_
#import <UIKit/UIKit.h>
@interface TabSwitcherPanelCollectionViewLayout : UICollectionViewFlowLayout
@end
#endif // IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_PANEL_COLLECTION_VIEW_LAYOUT_H_
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_collection_view_layout.h"
#include <algorithm>
#include "base/logging.h"
#include "base/mac/scoped_nsobject.h"
namespace {
const CGFloat minWidthOfTab = 200;
const CGFloat kInterTabSpacing = 16;
const UIEdgeInsets kCollectionViewEdgeInsets = {16, 16, 16, 16};
const CGFloat kMaxSizeAsAFactorOfBounds = 0.5;
const CGFloat kMinCellHeightWidthRatio = 0.6;
const CGFloat kMaxCellHeightWidthRatio = 1.8;
}
@implementation TabSwitcherPanelCollectionViewLayout {
// Keeps track of the inserted and deleted index paths.
base::scoped_nsobject<NSMutableArray> _deletedIndexPaths;
base::scoped_nsobject<NSMutableArray> _insertedIndexPaths;
}
- (int)maxRowCountWithColumnCount:(int)columnCount inBounds:(CGSize)boundsSize {
int cellWidth = (boundsSize.width / columnCount) - (kInterTabSpacing * 2.0);
int minCellHeight = cellWidth * kMinCellHeightWidthRatio;
return boundsSize.height / (minCellHeight + (kInterTabSpacing * 2.0));
}
- (void)updateLayoutWithBounds:(CGSize)boundsSize {
// Ignore initial call to |updateLayoutWithBounds| when the frame of the
// collection view is CGRectZero, because it creates very small cells with
// broken constraints.
if (boundsSize.height == 0 && boundsSize.width == 0)
return;
int tabCount = [[self collectionView] numberOfItemsInSection:0];
// Early return because there's nothing to layout.
if (tabCount == 0)
return;
int numberOfColumns = 0;
int numberOfRows = 0;
int maxNumberOfColums = static_cast<int>(
floor(boundsSize.width / (minWidthOfTab + kInterTabSpacing * 2.0)));
// No need to have more columns than tabs.
maxNumberOfColums = std::min(maxNumberOfColums, tabCount);
if ([self maxRowCountWithColumnCount:maxNumberOfColums inBounds:boundsSize] *
maxNumberOfColums <
tabCount) {
// It is impossible for all the tabs to be shown on screen at once.
// Layout the tabs using the highest density possible, i.e. using the
// maximum number of columns.
numberOfColumns = maxNumberOfColums;
numberOfRows =
[self maxRowCountWithColumnCount:maxNumberOfColums inBounds:boundsSize];
} else {
// Find the most squarish configuration that allows showing all the tabs.
// A squarish configuration is a layout were the number of rows and columns
// are roughly equal.
// |bestScore| contains abs(rowCount - columnCount).
// The lower |bestScore| is, the better the configuration is.
int bestScore = INT_MAX;
int loopStart;
int loopEnd;
int loopDirection;
if (boundsSize.width > boundsSize.height) {
// In landscape, consider in priority layouts with a large number of
// columns.
loopStart = maxNumberOfColums;
loopEnd = 0;
loopDirection = -1;
} else {
// In landscape, consider in priority layouts with a large number of rows,
// i.e. a small number of columns.
loopStart = 1;
loopEnd = maxNumberOfColums + 1;
loopDirection = 1;
}
int columnCountIterator = loopStart;
while (columnCountIterator != loopEnd) {
// Find the minimum number of rows needed to show |tabCount| tab in
// |columnCount| columns.
int maxRowCount = [self maxRowCountWithColumnCount:columnCountIterator
inBounds:boundsSize];
int idealRowCount = static_cast<int>(
ceil(static_cast<float>(tabCount) / columnCountIterator));
if (idealRowCount <= maxRowCount) {
int score = abs(idealRowCount - columnCountIterator);
if (score < bestScore) {
bestScore = score;
numberOfColumns = columnCountIterator;
numberOfRows = idealRowCount;
}
}
columnCountIterator += loopDirection;
}
DCHECK_NE(bestScore, INT_MAX);
}
DCHECK_NE(numberOfColumns, 0);
DCHECK_NE(numberOfRows, 0);
// Compute the size of the cells.
CGFloat horizontalFreeSpace =
boundsSize.width - (kInterTabSpacing * 2 * (numberOfColumns - 1)) -
kCollectionViewEdgeInsets.left - kCollectionViewEdgeInsets.right;
CGFloat verticalFreeSpace =
boundsSize.height - (kInterTabSpacing * 2 * (numberOfRows - 1)) -
kCollectionViewEdgeInsets.top - kCollectionViewEdgeInsets.bottom;
CGSize newCellSize = CGSizeMake(horizontalFreeSpace / numberOfColumns,
verticalFreeSpace / numberOfRows);
// The cells must not be larger than half of the bounds because the @1x
// snapshots would look blurry on retina screens.
newCellSize.width =
std::min(newCellSize.width, boundsSize.width * kMaxSizeAsAFactorOfBounds);
newCellSize.height = std::min(newCellSize.height,
boundsSize.height * kMaxSizeAsAFactorOfBounds);
// Avoid having cells be too narrow.
newCellSize.height = std::min(newCellSize.height,
newCellSize.width * kMaxCellHeightWidthRatio);
[self setItemSize:newCellSize];
[self setMinimumInteritemSpacing:kInterTabSpacing];
[self setMinimumLineSpacing:kInterTabSpacing * 2];
bool forceVerticalCentering = numberOfRows == 1;
if (forceVerticalCentering) {
UIEdgeInsets insets = kCollectionViewEdgeInsets;
insets.top = (boundsSize.height - newCellSize.height) / 2.0;
insets.bottom = (boundsSize.height - newCellSize.height) / 2.0;
[self setSectionInset:insets];
} else {
[self setSectionInset:kCollectionViewEdgeInsets];
}
}
- (void)prepareLayout {
[super prepareLayout];
[self updateLayoutWithBounds:[[self collectionView] bounds].size];
}
@end
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_PANEL_OVERLAY_VIEW_H_
#define IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_PANEL_OVERLAY_VIEW_H_
#import <UIKit/UIKit.h>
namespace ios {
class ChromeBrowserState;
}
enum class TabSwitcherPanelOverlayType {
OVERLAY_PANEL_EMPTY,
OVERLAY_PANEL_USER_SIGNED_OUT,
OVERLAY_PANEL_USER_SIGNED_IN_SYNC_OFF,
OVERLAY_PANEL_USER_SIGNED_IN_SYNC_ON_NO_SESSIONS,
OVERLAY_PANEL_USER_SIGNED_IN_SYNC_IN_PROGRESS,
OVERLAY_PANEL_USER_NO_OPEN_TABS,
OVERLAY_PANEL_USER_NO_INCOGNITO_TABS
};
enum class TabSwitcherSignInPanelsType;
TabSwitcherPanelOverlayType PanelOverlayTypeFromSignInPanelsType(
TabSwitcherSignInPanelsType signInPanelType);
@interface TabSwitcherPanelOverlayView : UIView
@property(nonatomic, assign) TabSwitcherPanelOverlayType overlayType;
- (instancetype)initWithFrame:(CGRect)frame
browserState:(ios::ChromeBrowserState*)browserState;
@end
#endif // IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_PANEL_OVERLAY_VIEW_H_
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_PANEL_VIEW_H_
#define IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_PANEL_VIEW_H_
#import <UIKit/UIKit.h>
#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_model.h"
@class TabSwitcherCache;
@interface TabSwitcherPanelView : UIView
@property(nonatomic, readonly) UICollectionView* collectionView;
- (instancetype)initWithSessionType:(ios_internal::SessionType)sessionType
NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder*)aDecoder NS_UNAVAILABLE;
// Updates the collectionView's layout to ensure that the optimal amount of tabs
// are displayed. The completion block is called at the end of the layout
// update.
- (void)updateCollectionLayoutWithCompletion:(void (^)(void))completion;
// Returns the size of the cells displayed in the collectionView.
- (CGSize)cellSize;
@end
#endif // IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_SWITCHER_PANEL_VIEW_H_
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
# Toolbar
-----
**Some of the files in this directory are only used in the new iOS Chrome
architecture:**
* `toolbar_coordinator.h` and `.mm`
* `toolbar_view_controller.h` and `.mm`
-----
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment