blob: 50f6203b9870222c03eb20a2ef8a1b2add0ff9fb [file] [log] [blame]
// 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/side_swipe/side_swipe_controller.h"
#include <memory>
#include "base/feature_list.h"
#import "base/ios/block_types.h"
#include "base/scoped_observer.h"
#import "ios/chrome/browser/browser_state/chrome_browser_state.h"
#import "ios/chrome/browser/snapshots/snapshot_cache.h"
#import "ios/chrome/browser/snapshots/snapshot_cache_factory.h"
#import "ios/chrome/browser/snapshots/snapshot_tab_helper.h"
#import "ios/chrome/browser/tabs/tab.h"
#import "ios/chrome/browser/ui/fullscreen/animated_scoped_fullscreen_disabler.h"
#import "ios/chrome/browser/ui/fullscreen/fullscreen_controller_factory.h"
#import "ios/chrome/browser/ui/fullscreen/scoped_fullscreen_disabler.h"
#import "ios/chrome/browser/ui/side_swipe/card_side_swipe_view.h"
#import "ios/chrome/browser/ui/side_swipe/side_swipe_gesture_recognizer.h"
#import "ios/chrome/browser/ui/side_swipe/side_swipe_navigation_view.h"
#import "ios/chrome/browser/ui/side_swipe/side_swipe_util.h"
#import "ios/chrome/browser/ui/tabs/requirements/tab_strip_highlighting.h"
#include "ios/chrome/browser/ui/toolbar/public/side_swipe_toolbar_interacting.h"
#import "ios/chrome/browser/ui/toolbar/public/side_swipe_toolbar_interacting.h"
#include "ios/chrome/browser/ui/util/ui_util.h"
#import "ios/chrome/browser/web/page_placeholder_tab_helper.h"
#import "ios/chrome/browser/web/tab_id_tab_helper.h"
#import "ios/chrome/browser/web/web_navigation_util.h"
#import "ios/chrome/browser/web_state_list/web_state_list.h"
#import "ios/chrome/browser/web_state_list/web_state_list_observer_bridge.h"
#import "ios/web/public/navigation_item.h"
#import "ios/web/public/web_client.h"
#import "ios/web/public/web_state/web_state.h"
#import "ios/web/public/web_state/web_state_observer_bridge.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
NSString* const kSideSwipeWillStartNotification =
@"kSideSwipeWillStartNotification";
NSString* const kSideSwipeDidStopNotification =
@"kSideSwipeDidStopNotification";
namespace {
enum class SwipeType { NONE, CHANGE_TAB, CHANGE_PAGE };
// Swipe starting distance from edge.
const CGFloat kSwipeEdge = 20;
// Distance between sections of iPad side swipe.
const CGFloat kIpadTabSwipeDistance = 100;
// Number of tabs to keep in the grey image cache.
const NSUInteger kIpadGreySwipeTabCount = 8;
}
@interface SideSwipeController () <CRWWebStateObserver,
UIGestureRecognizerDelegate,
WebStateListObserving> {
@private
__weak TabModel* model_;
// Side swipe view for tab navigation.
CardSideSwipeView* tabSideSwipeView_;
// Side swipe view for page navigation.
SideSwipeNavigationView* pageSideSwipeView_;
// YES if the user is currently swiping.
BOOL inSwipe_;
// Swipe gesture recognizer.
SideSwipeGestureRecognizer* swipeGestureRecognizer_;
SideSwipeGestureRecognizer* panGestureRecognizer_;
// Used in iPad side swipe gesture, tracks the starting tab index.
NSUInteger startingTabIndex_;
// If the swipe is for a page change or a tab change.
SwipeType swipeType_;
// Bridge to observe the web state from Objective-C.
std::unique_ptr<web::WebStateObserverBridge> webStateObserverBridge_;
// Bridge to observe the WebStateList from Objective-C.
std::unique_ptr<WebStateListObserverBridge> webStateListObserver_;
// Scoped observer used to track registration of the WebStateObserverBridge.
std::unique_ptr<ScopedObserver<web::WebState, web::WebStateObserver>>
scopedWebStateObserver_;
// Curtain over web view while waiting for it to load.
UIView* curtain_;
// The disabler that prevents the toolbar from being scrolled away when the
// side swipe gesture is being recognized.
std::unique_ptr<ScopedFullscreenDisabler> fullscreenDisabler_;
// The animated disabler displays the toolbar when a side swipe navigation
// gesture is being recognized.
std::unique_ptr<AnimatedScopedFullscreenDisabler> animatedFullscreenDisabler_;
// Browser state passed to the initialiser.
ios::ChromeBrowserState* browserState_;
}
// Whether to allow navigating from the leading edge.
@property(nonatomic, assign) BOOL leadingEdgeNavigationEnabled;
// Whether to allow navigating from the trailing edge.
@property(nonatomic, assign) BOOL trailingEdgeNavigationEnabled;
// The current active WebState.
@property(nonatomic, readonly) web::WebState* activeWebState;
// Load grey snapshots for the next |kIpadGreySwipeTabCount| tabs in
// |direction|.
- (void)createGreyCache:(UISwipeGestureRecognizerDirection)direction;
// Tell snapshot cache to clear grey cache.
- (void)deleteGreyCache;
// Handle tab side swipe for iPad. Change tabs according to swipe distance.
- (void)handleiPadTabSwipe:(SideSwipeGestureRecognizer*)gesture;
// Handle tab side swipe for iPhone. Introduces a CardSideSwipeView to convey
// the tab change.
- (void)handleiPhoneTabSwipe:(SideSwipeGestureRecognizer*)gesture;
// Overlays |curtain_| as a white view to hide the web view while it updates.
// Calls |completionHandler| when the curtain is removed.
- (void)addCurtainWithCompletionHandler:(ProceduralBlock)completionHandler;
// Removes the |curtain_| and calls |completionHandler| when the curtain is
// removed.
- (void)dismissCurtainWithCompletionHandler:(ProceduralBlock)completionHandler;
// Removes the |curtain_| if there was an active swipe, and resets
// |inSwipe_| value.
- (void)dismissCurtain;
@end
@implementation SideSwipeController
@synthesize inSwipe = inSwipe_;
@synthesize swipeDelegate = swipeDelegate_;
@synthesize toolbarInteractionHandler = _toolbarInteractionHandler;
@synthesize primaryToolbarSnapshotProvider = _primaryToolbarSnapshotProvider;
@synthesize secondaryToolbarSnapshotProvider =
_secondaryToolbarSnapshotProvider;
@synthesize snapshotDelegate = snapshotDelegate_;
@synthesize tabStripDelegate = tabStripDelegate_;
- (id)initWithTabModel:(TabModel*)model
browserState:(ios::ChromeBrowserState*)browserState {
DCHECK(model);
self = [super init];
if (self) {
model_ = model;
webStateListObserver_ = std::make_unique<WebStateListObserverBridge>(self);
model_.webStateList->AddObserver(webStateListObserver_.get());
webStateObserverBridge_ =
std::make_unique<web::WebStateObserverBridge>(self);
scopedWebStateObserver_ =
std::make_unique<ScopedObserver<web::WebState, web::WebStateObserver>>(
webStateObserverBridge_.get());
if (self.activeWebState)
scopedWebStateObserver_->Add(self.activeWebState);
browserState_ = browserState;
}
return self;
}
- (void)dealloc {
if (model_.webStateList) {
// |model_| is a weak reference which owns |webStateList|. Make sure that
// |model_| is still alive before accessing |webStateList|.
model_.webStateList->RemoveObserver(webStateListObserver_.get());
}
scopedWebStateObserver_.reset();
webStateObserverBridge_.reset();
}
- (void)addHorizontalGesturesToView:(UIView*)view {
swipeGestureRecognizer_ = [[SideSwipeGestureRecognizer alloc]
initWithTarget:self
action:@selector(handleSwipe:)];
[swipeGestureRecognizer_ setMaximumNumberOfTouches:1];
[swipeGestureRecognizer_ setDelegate:self];
[swipeGestureRecognizer_ setSwipeEdge:kSwipeEdge];
[view addGestureRecognizer:swipeGestureRecognizer_];
// Add a second gesture recognizer to handle swiping on the toolbar to change
// tabs.
panGestureRecognizer_ =
[[SideSwipeGestureRecognizer alloc] initWithTarget:self
action:@selector(handlePan:)];
[panGestureRecognizer_ setMaximumNumberOfTouches:1];
[panGestureRecognizer_ setSwipeThreshold:48];
[panGestureRecognizer_ setDelegate:self];
[view addGestureRecognizer:panGestureRecognizer_];
}
- (web::WebState*)activeWebState {
return model_.webStateList ? model_.webStateList->GetActiveWebState()
: nullptr;
}
- (NSSet*)swipeRecognizers {
return [NSSet setWithObjects:swipeGestureRecognizer_, nil];
}
- (void)setEnabled:(BOOL)enabled {
[swipeGestureRecognizer_ setEnabled:enabled];
}
- (BOOL)shouldAutorotate {
return !([tabSideSwipeView_ window] || inSwipe_);
}
// Always return yes, as this swipe should work with various recognizers,
// including UITextTapRecognizer, UILongPressGestureRecognizer,
// UIScrollViewPanGestureRecognizer and others.
- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:
(UIGestureRecognizer*)otherGestureRecognizer {
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
shouldBeRequiredToFailByGestureRecognizer:
(UIGestureRecognizer*)otherGestureRecognizer {
// Take precedence over a pan gesture recognizer so that moving up and
// down while swiping doesn't trigger overscroll actions.
if ([otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
return YES;
}
// Take precedence over a WKWebView side swipe gesture.
if ([otherGestureRecognizer
isKindOfClass:[UIScreenEdgePanGestureRecognizer class]]) {
return YES;
}
return NO;
}
// Gestures should only be recognized within |contentArea_| or the toolbar.
- (BOOL)gestureRecognizerShouldBegin:(SideSwipeGestureRecognizer*)gesture {
if (inSwipe_) {
return NO;
}
if ([swipeDelegate_ preventSideSwipe])
return NO;
CGPoint location = [gesture locationInView:gesture.view];
// Since the toolbar and the contentView can overlap, check the toolbar frame
// first, and confirm the right gesture recognizer is firing.
if ([self.toolbarInteractionHandler
isInsideToolbar:[gesture.view convertPoint:location toView:nil]]) {
if (![gesture isEqual:panGestureRecognizer_]) {
return NO;
}
return [swipeDelegate_ canBeginToolbarSwipe];
}
// Otherwise, only allow contentView touches with |swipeGestureRecognizer_|.
// The content view frame is inset by -1 because CGRectContainsPoint does
// include points on the max X and Y edges, which will happen frequently with
// edge swipes from the right side.
CGRect contentViewFrame =
CGRectInset([[swipeDelegate_ sideSwipeContentView] frame], -1, -1);
if (CGRectContainsPoint(contentViewFrame, location)) {
if (![gesture isEqual:swipeGestureRecognizer_]) {
return NO;
}
if (gesture.direction == UISwipeGestureRecognizerDirectionRight &&
!self.leadingEdgeNavigationEnabled) {
return NO;
}
if (gesture.direction == UISwipeGestureRecognizerDirectionLeft &&
!self.trailingEdgeNavigationEnabled) {
return NO;
}
swipeType_ = SwipeType::CHANGE_PAGE;
return YES;
}
return NO;
}
- (void)createGreyCache:(UISwipeGestureRecognizerDirection)direction {
NSInteger dx = (direction == UISwipeGestureRecognizerDirectionLeft) ? -1 : 1;
NSInteger index = startingTabIndex_ + dx;
NSMutableArray* sessionIDs =
[NSMutableArray arrayWithCapacity:kIpadGreySwipeTabCount];
for (NSUInteger count = 0; count < kIpadGreySwipeTabCount; count++) {
// Wrap around edges.
if (index >= (NSInteger)[model_ count])
index = 0;
else if (index < 0)
index = [model_ count] - 1;
// Don't wrap past the starting index.
if (index == (NSInteger)startingTabIndex_)
break;
Tab* tab = [model_ tabAtIndex:index];
if (tab && PagePlaceholderTabHelper::FromWebState(tab.webState)
->will_add_placeholder_for_next_navigation()) {
[sessionIDs
addObject:TabIdTabHelper::FromWebState(tab.webState)->tab_id()];
}
index = index + dx;
}
[SnapshotCacheFactory::GetForBrowserState(browserState_)
createGreyCache:sessionIDs];
}
- (void)deleteGreyCache {
[SnapshotCacheFactory::GetForBrowserState(browserState_) removeGreyCache];
}
- (void)handlePan:(SideSwipeGestureRecognizer*)gesture {
if (!IsIPadIdiom()) {
return [self handleiPhoneTabSwipe:gesture];
} else {
return [self handleiPadTabSwipe:gesture];
}
}
- (void)handleSwipe:(SideSwipeGestureRecognizer*)gesture {
DCHECK(swipeType_ != SwipeType::NONE);
if (swipeType_ == SwipeType::CHANGE_TAB) {
if (!IsIPadIdiom()) {
return [self handleiPhoneTabSwipe:gesture];
} else {
return [self handleiPadTabSwipe:gesture];
}
}
if (swipeType_ == SwipeType::CHANGE_PAGE) {
return [self handleSwipeToNavigate:gesture];
}
NOTREACHED();
}
- (void)handleiPadTabSwipe:(SideSwipeGestureRecognizer*)gesture {
// Don't handle swipe when there are no tabs.
NSInteger count = [model_ count];
if (count == 0)
return;
if (gesture.state == UIGestureRecognizerStateBegan) {
// Disable fullscreen while the side swipe gesture is occurring.
fullscreenDisabler_ = std::make_unique<ScopedFullscreenDisabler>(
FullscreenControllerFactory::GetInstance()->GetForBrowserState(
browserState_));
SnapshotTabHelper::FromWebState(self.activeWebState)
->UpdateSnapshotWithCallback(nil);
[[NSNotificationCenter defaultCenter]
postNotificationName:kSideSwipeWillStartNotification
object:nil];
[self.tabStripDelegate setHighlightsSelectedTab:YES];
startingTabIndex_ = [model_ indexOfTab:[model_ currentTab]];
[self createGreyCache:gesture.direction];
} else if (gesture.state == UIGestureRecognizerStateChanged) {
// Side swipe for iPad involves changing the selected tab as the swipe moves
// across the width of the view. The screen is broken up into
// |kIpadTabSwipeDistance| / |width| segments, with the current tab in the
// first section. The swipe does not wrap edges.
CGFloat distance = [gesture locationInView:gesture.view].x;
if (gesture.direction == UISwipeGestureRecognizerDirectionLeft) {
distance = gesture.startPoint.x - distance;
} else {
distance -= gesture.startPoint.x;
}
NSInteger indexDelta = std::floor(distance / kIpadTabSwipeDistance);
// Don't wrap past the first tab.
if (indexDelta < count) {
// Flip delta when swiping forward.
if (IsSwipingForward(gesture.direction))
indexDelta = 0 - indexDelta;
Tab* currentTab = [model_ currentTab];
NSInteger currentIndex = [model_ indexOfTab:currentTab];
// Wrap around edges.
NSInteger newIndex = (NSInteger)(startingTabIndex_ + indexDelta) % count;
// C99 defines the modulo result as negative if our offset is negative.
if (newIndex < 0)
newIndex += count;
if (newIndex != currentIndex) {
Tab* tab = [model_ tabAtIndex:newIndex];
// Toggle overlay preview mode for selected tab.
PagePlaceholderTabHelper::FromWebState(tab.webState)
->AddPlaceholderForNextNavigation();
[model_ setCurrentTab:tab];
// And disable overlay preview mode for last selected tab.
PagePlaceholderTabHelper::FromWebState(currentTab.webState)
->CancelPlaceholderForNextNavigation();
}
}
} else {
if (gesture.state == UIGestureRecognizerStateCancelled) {
Tab* tab = [model_ tabAtIndex:startingTabIndex_];
PagePlaceholderTabHelper::FromWebState(tab.webState)
->CancelPlaceholderForNextNavigation();
[model_ setCurrentTab:tab];
}
PagePlaceholderTabHelper::FromWebState(self.activeWebState)
->CancelPlaceholderForNextNavigation();
// Redisplay the view if it was in overlay preview mode.
[swipeDelegate_ sideSwipeRedisplayWebState:self.activeWebState];
[self.tabStripDelegate setHighlightsSelectedTab:NO];
[self deleteGreyCache];
[[NSNotificationCenter defaultCenter]
postNotificationName:kSideSwipeDidStopNotification
object:nil];
// Stop disabling fullscreen.
fullscreenDisabler_ = nullptr;
}
}
- (BOOL)canNavigate:(BOOL)goBack {
if (!self.activeWebState)
return NO;
if (goBack && self.activeWebState->GetNavigationManager()->CanGoBack()) {
return YES;
}
if (!goBack && self.activeWebState->GetNavigationManager()->CanGoForward()) {
return YES;
}
return NO;
}
// Show swipe to navigate.
- (void)handleSwipeToNavigate:(SideSwipeGestureRecognizer*)gesture {
if (gesture.state == UIGestureRecognizerStateBegan) {
// Make sure the Toolbar is visible by disabling Fullscreen.
animatedFullscreenDisabler_ =
std::make_unique<AnimatedScopedFullscreenDisabler>(
FullscreenControllerFactory::GetInstance()->GetForBrowserState(
browserState_));
animatedFullscreenDisabler_->StartAnimation();
inSwipe_ = YES;
[swipeDelegate_ updateAccessoryViewsForSideSwipeWithVisibility:NO];
BOOL goBack = IsSwipingBack(gesture.direction);
CGRect gestureBounds = gesture.view.bounds;
CGFloat headerHeight = [swipeDelegate_ headerHeightForSideSwipe];
CGRect navigationFrame =
CGRectMake(CGRectGetMinX(gestureBounds),
CGRectGetMinY(gestureBounds) + headerHeight,
CGRectGetWidth(gestureBounds),
CGRectGetHeight(gestureBounds) - headerHeight);
pageSideSwipeView_ = [[SideSwipeNavigationView alloc]
initWithFrame:navigationFrame
withDirection:gesture.direction
canNavigate:[self canNavigate:goBack]
image:[UIImage imageNamed:@"side_swipe_navigation_back"]];
[pageSideSwipeView_ setTargetView:[swipeDelegate_ sideSwipeContentView]];
[gesture.view insertSubview:pageSideSwipeView_
belowSubview:[swipeDelegate_ topToolbarView]];
} else if (gesture.state == UIGestureRecognizerStateCancelled ||
gesture.state == UIGestureRecognizerStateEnded ||
gesture.state == UIGestureRecognizerStateFailed) {
// Enable fullscreen functionality after the Toolbar has been shown, and
// the gesture is over.
animatedFullscreenDisabler_ = nullptr;
}
[pageSideSwipeView_ handleHorizontalPan:gesture
onOverThresholdCompletion:^{
web::WebState* webState = self.activeWebState;
BOOL wantsBack = IsSwipingBack(gesture.direction);
if (webState) {
if (wantsBack) {
web_navigation_util::GoBack(webState);
} else {
web_navigation_util::GoForward(webState);
}
}
// Checking -IsLoading() is likely incorrect, but to narrow the scope of
// fixes for slim navigation manager, only ignore this state when
// slim is disabled. With slim navigation enabled, this false when
// pages can be served from WKWebView's page cache.
if (webState &&
(web::GetWebClient()->IsSlimNavigationManagerEnabled() ||
webState->IsLoading())) {
[self addCurtainWithCompletionHandler:^{
inSwipe_ = NO;
}];
} else {
inSwipe_ = NO;
}
[swipeDelegate_ updateAccessoryViewsForSideSwipeWithVisibility:YES];
}
onUnderThresholdCompletion:^{
[swipeDelegate_ updateAccessoryViewsForSideSwipeWithVisibility:YES];
inSwipe_ = NO;
}];
}
// Show horizontal swipe stack view for iPhone.
- (void)handleiPhoneTabSwipe:(SideSwipeGestureRecognizer*)gesture {
if (gesture.state == UIGestureRecognizerStateBegan) {
inSwipe_ = YES;
CGRect frame = [[swipeDelegate_ sideSwipeContentView] frame];
// Add horizontal stack view controller.
// TODO(crbug.com/904992): Do not use SnapshotGeneratorDelegate from
// SideSwipeController.
CGFloat headerHeight = 0;
if (self.activeWebState) {
headerHeight =
[self.snapshotDelegate snapshotGenerator:nil
snapshotEdgeInsetsForWebState:self.activeWebState]
.top;
}
if (tabSideSwipeView_) {
[tabSideSwipeView_ setFrame:frame];
[tabSideSwipeView_ setTopMargin:headerHeight];
} else {
tabSideSwipeView_ = [[CardSideSwipeView alloc] initWithFrame:frame
topMargin:headerHeight
model:model_];
tabSideSwipeView_.topToolbarSnapshotProvider =
self.primaryToolbarSnapshotProvider;
tabSideSwipeView_.bottomToolbarSnapshotProvider =
self.secondaryToolbarSnapshotProvider;
[tabSideSwipeView_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleHeight];
[tabSideSwipeView_ setDelegate:swipeDelegate_];
[tabSideSwipeView_ setBackgroundColor:[UIColor blackColor]];
}
// Ensure that there's an up-to-date snapshot of the current tab.
if (self.activeWebState) {
SnapshotTabHelper::FromWebState(self.activeWebState)
->UpdateSnapshotWithCallback(nil);
}
// Layout tabs with new snapshots in the current orientation.
[tabSideSwipeView_ updateViewsForDirection:gesture.direction];
// Insert behind infobar container (which is below toolbar)
// so card border doesn't look janky during animation.
DCHECK([swipeDelegate_ verifyToolbarViewPlacementInView:gesture.view]);
// Insert above the toolbar.
[gesture.view addSubview:tabSideSwipeView_];
}
[tabSideSwipeView_ handleHorizontalPan:gesture];
}
- (void)addCurtainWithCompletionHandler:(ProceduralBlock)completionHandler {
if (!curtain_) {
curtain_ = [[UIView alloc]
initWithFrame:[swipeDelegate_ sideSwipeContentView].bounds];
[curtain_ setBackgroundColor:[UIColor whiteColor]];
}
[[swipeDelegate_ sideSwipeContentView] addSubview:curtain_];
// Fallback in case load takes a while. 3 seconds is a balance between how
// long it can take a web view to clear the previous page image, and what
// feels like to 'too long' to see the curtain.
[self performSelector:@selector(dismissCurtainWithCompletionHandler:)
withObject:[completionHandler copy]
afterDelay:3];
}
- (void)resetContentView {
CGRect frame = [swipeDelegate_ sideSwipeContentView].frame;
frame.origin.x = 0;
[swipeDelegate_ sideSwipeContentView].frame = frame;
}
- (void)dismissCurtainWithCompletionHandler:(ProceduralBlock)completionHandler {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[curtain_ removeFromSuperview];
curtain_ = nil;
completionHandler();
}
- (void)dismissCurtain {
if (!inSwipe_)
return;
[self dismissCurtainWithCompletionHandler:^{
inSwipe_ = NO;
}];
}
- (void)updateNavigationEdgeSwipeForWebState:(web::WebState*)webState {
// With slim nav disabled, always use SideSwipeController's edge swipe for
// navigation.
if (!web::GetWebClient()->IsSlimNavigationManagerEnabled()) {
self.leadingEdgeNavigationEnabled = YES;
self.trailingEdgeNavigationEnabled = YES;
return;
}
if (!webState)
return;
// With slim nav enabled, disable SideSwipeController's edge swipe for a
// typical navigation. Continue to use SideSwipeController when on, before,
// or after a native page.
self.leadingEdgeNavigationEnabled = NO;
self.trailingEdgeNavigationEnabled = NO;
web::NavigationItem* item =
webState->GetNavigationManager()->GetVisibleItem();
if (UseNativeSwipe(item)) {
self.leadingEdgeNavigationEnabled = YES;
self.trailingEdgeNavigationEnabled = YES;
}
// If the previous page is an NTP, enable leading edge swipe.
web::NavigationItemList backItems =
webState->GetNavigationManager()->GetBackwardItems();
if (backItems.size() > 0 && UseNativeSwipe(backItems[0]))
self.leadingEdgeNavigationEnabled = YES;
// If the next page is an NTP, enable trailing edge swipe.
web::NavigationItemList fordwardItems =
webState->GetNavigationManager()->GetForwardItems();
if (fordwardItems.size() > 0 && UseNativeSwipe(fordwardItems[0]))
self.trailingEdgeNavigationEnabled = YES;
}
#pragma mark - CRWWebStateObserver Methods
// Checking -webStateDidStopLoading is likely incorrect, but to narrow the scope
// of fixes for slim navigation manager, only ignore this callback when slim is
// disabled.
- (void)webStateDidStopLoading:(web::WebState*)webState {
if (web::GetWebClient()->IsSlimNavigationManagerEnabled())
return;
[self dismissCurtain];
}
- (void)webState:(web::WebState*)webState didLoadPageWithSuccess:(BOOL)success {
if (!web::GetWebClient()->IsSlimNavigationManagerEnabled())
return;
[self dismissCurtain];
}
- (void)webState:(web::WebState*)webState
didFinishNavigation:(web::NavigationContext*)navigation {
[self updateNavigationEdgeSwipeForWebState:webState];
}
#pragma mark - WebStateListObserving Methods
- (void)webStateList:(WebStateList*)webStateList
didChangeActiveWebState:(web::WebState*)newWebState
oldWebState:(web::WebState*)oldWebState
atIndex:(int)atIndex
reason:(int)reason {
// If there is any an ongoing swipe for the old webState, cancel it and
// dismiss the curtain.
[self dismissCurtain];
// Toggling the gesture's enabled state off and on will effectively cancel
// the gesture recognizer.
[swipeGestureRecognizer_ setEnabled:NO];
[swipeGestureRecognizer_ setEnabled:YES];
// Track the new active WebState for navigation events. Also remove the old if
// there was one.
if (oldWebState)
scopedWebStateObserver_->Remove(oldWebState);
if (newWebState)
scopedWebStateObserver_->Add(newWebState);
[self updateNavigationEdgeSwipeForWebState:newWebState];
}
@end