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

Unified Diff: ios/chrome/browser/ui/fullscreen_controller.mm

Issue 2590473002: Upstream Chrome on iOS source code [5/11]. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: ios/chrome/browser/ui/fullscreen_controller.mm
diff --git a/ios/chrome/browser/ui/fullscreen_controller.mm b/ios/chrome/browser/ui/fullscreen_controller.mm
new file mode 100644
index 0000000000000000000000000000000000000000..0f3632e09dfa84c896ac0a0942617d44040d6508
--- /dev/null
+++ b/ios/chrome/browser/ui/fullscreen_controller.mm
@@ -0,0 +1,841 @@
+// Copyright 2013 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/fullscreen_controller.h"
+
+#include <cmath>
+
+#include "base/logging.h"
+#include "base/mac/objc_property_releaser.h"
+#import "ios/chrome/browser/ui/browser_view_controller.h"
+#import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.h"
+#import "ios/chrome/browser/ui/tabs/tab_strip_controller.h"
+#import "ios/chrome/browser/ui/toolbar/toolbar_controller.h"
+#import "ios/chrome/browser/ui/toolbar/web_toolbar_controller.h"
+#import "ios/chrome/browser/ui/voice/voice_search_notification_names.h"
+#include "ios/web/public/navigation_item.h"
+#import "ios/web/public/navigation_manager.h"
+#include "ios/web/public/ssl_status.h"
+#import "ios/web/public/web_state/crw_web_view_proxy.h"
+#import "ios/web/web_state/ui/crw_web_controller.h"
+
+NSString* const kSetupForTestingWillCloseAllTabsNotification =
+ @"kSetupForTestingWillCloseAllTabsNotification";
+
+using web::NavigationManager;
+
+namespace {
+
+class ScopedIncrementer {
+ public:
+ explicit ScopedIncrementer(int* value) : value_(value) { ++(*value_); }
+ ~ScopedIncrementer() { --(*value_); }
+
+ private:
+ int* value_;
+};
+
+CGFloat kPrecision = 0.00001;
+
+// Duration for the delay before showing the omnibox.
+const double kShowOmniboxDelaySeconds = 0.5;
+// Default duration for the delay before hiding the omnibox.
+const double kDefaultHideOmniboxDelaySeconds = 3.0;
+// Duration for the delay before hiding the omnibox.
+double gHideOmniboxDelaySeconds = kDefaultHideOmniboxDelaySeconds;
+// Indicates if the FullScreenController returns nil from |init|. Used for
+// testing purposes.
+BOOL gEnabledForTests = YES;
+
+// Compares that two CGFloat a and b are within a range of kPrecision of each
+// other.
+BOOL CGFloatEquals(CGFloat a, CGFloat b) {
+ CGFloat delta = std::abs(a - b);
+
+ return delta < kPrecision;
+}
+
+} // anonymous namespace.
+
+@interface FullScreenController ()<UIGestureRecognizerDelegate> {
+ // Used to detect movement in the scrollview produced by this class.
+ int selfTriggered_;
+ // Used to detect if the keyboard is visible.
+ BOOL keyboardIsVisible_;
+ // Used to detect that the OverscrollActionsController is displaying its UI.
+ // The FullScreenController is disabled when the OverscrollActionsController's
+ // UI is displayed.
+ BOOL overscrollActionsInProgress_;
+ // Counter used to keep track of the number of actions currently disabling
+ // full screen.
+ uint fullScreenLock_;
+ // CRWWebViewProxy object allows web view manipulations.
+ base::scoped_nsprotocol<id<CRWWebViewProxy>> webViewProxy_;
+ base::mac::ObjCPropertyReleaser propertyReleaser_FullScreenController_;
+}
+
+// Access to the UIWebView's UIScrollView.
+@property(nonatomic, readonly) CRWWebViewScrollViewProxy* scrollViewProxy;
+// The navigation controller of the page.
+@property(nonatomic, readonly, assign) NavigationManager* navigationManager;
+// The gesture recognizer set on the scrollview to detect tap. Must be readwrite
+// for property releaser to work.
+@property(nonatomic, readwrite, retain)
+ UITapGestureRecognizer* userInteractionGestureRecognizer;
+// The delegate responsible for providing the header height and moving the
+// header.
+@property(nonatomic, readonly) id<FullScreenControllerDelegate> delegate;
+// Current height of the header, in points. This is a pass-through method that
+// fetches the header height from the FullScreenControllerDelegate.
+@property(nonatomic, readonly) CGFloat headerHeight;
+// |top| field of UIScrollView.contentInset value caused by header.
+// Always 0 for WKWebView, as it does not support contentInset.
+@property(nonatomic, readonly) CGFloat topContentInsetCausedByHeader;
+// Last known y offset of the content in the scroll view during a scroll. Used
+// to infer the direction of the current scroll.
+@property(nonatomic, assign) CGFloat previousContentOffset;
+// Last known y offset requested on the scroll view. In general the same value
+// as previous content offset unless the offset was corrected by the controller
+// to slide from under the toolbar.
+@property(nonatomic, assign) CGFloat previousRequestedContentOffset;
+// Whether or not the content of the scroll view fits entirely on screen when
+// the toolbar is visible.
+@property(nonatomic, readonly) BOOL contentFitsWithToolbarVisible;
+// During a drag operation stores and remember the length of the latest scroll
+// down operation. If a scroll up move happens later during the same gesture
+// this will be used to delay the apparition of the header.
+@property(nonatomic, assign) CGFloat lastScrollDownDistance;
+// Tracks whether the current scrollview movements are triggered by the user or
+// programmatically.
+@property(nonatomic, assign) BOOL isUserTriggered;
+// Tracks if fullscreen is currently disabled because of page load.
+@property(nonatomic, assign) BOOL isFullScreenDisabledForLoading;
+// Tracks if fullscreen is currently disabled because of unsecured page.
+@property(nonatomic, readonly, assign) BOOL isFullScreenDisabledForSSLStatus;
+// Tracks if fullscreen is currently disabled.
+@property(nonatomic, readonly, assign) BOOL isFullScreenDisabled;
+// Tracks if fullscreen is temporarily disabled for the current page.
+@property(nonatomic, readonly, assign) BOOL isFullScreenDisabledTemporarily;
+// Tracks if fullscreen is permanently disabled for the current page.
+@property(nonatomic, readonly, assign) BOOL isFullScreenDisabledPermanently;
+// Skip next attempt to correct the scroll offset for the toolbar height. This
+// is necessary when programatically scrolling down the y offset.
+@property(nonatomic, assign) BOOL skipNextScrollOffsetForHeader;
+// Incremented each time a timed request to remove the header is sent,
+// decremented when the timer fires. When it reach zero, the header is moved.
+@property(nonatomic, assign) unsigned int delayedHideHeaderCount;
+// ID of the session (each Tab represents a session).
+@property(nonatomic, copy) NSString* sessionID;
+
+// Returns if the given entry will be displayed with an error padlock. If this
+// is the case, the toolbar should never be hidden on this entry.
+- (BOOL)isEntryBrokenSSL:(web::NavigationItem*)item;
+// Called at the start of a user scroll.
+- (void)webViewScrollViewWillStartScrolling:
+ (CRWWebViewScrollViewProxy*)webViewScrollViewProxy;
+// Called at the end of a scroll.
+- (void)webViewScrollViewDidStopScrolling:
+ (CRWWebViewScrollViewProxy*)webViewScrollViewProxy;
+// Called before and after the keyboard is appearing. Used to allow scroll
+// events triggered by the keyboard appearing to go through.
+- (void)keyboardStart:(NSNotification*)notification;
+- (void)keyboardEnd:(NSNotification*)notification;
+// Called before and after an action that disables full screen. The version
+// resetting the timer will ensure that the header stay on screen for a little
+// while.
+- (void)incrementFullScreenLock;
+- (void)decrementFullScreenLock;
+// Called when the application is about to be the foreground application.
+- (void)applicationWillEnterForeground:(NSNotification*)notification;
+// TODO(shreyasv): Make the following methods act on a WebViewScrollView proxy
+// instead of taking in a UIScrollView directly.
+// Called from -webViewScrollViewDidScroll: Returns YES if the scroll should be
+// ignored.
+- (BOOL)shouldIgnoreScroll:(CRWWebViewScrollViewProxy*)webViewScrollViewProxy;
+// Processes a scroll event triggered by a user action.
+- (void)userTriggeredWebViewScrollViewDidScroll:
+ (CRWWebViewScrollViewProxy*)webViewScrollViewProxy;
+// Processes a scroll event triggered by code (these could be initiated via
+// Javascript, find in page or simply the keyboard sliding in and out).
+- (void)codeTriggeredWebViewScrollViewDidScroll:
+ (CRWWebViewScrollViewProxy*)webViewScrollViewProxy;
+// Returns YES if |scrollView_| is for the current tab.
+- (BOOL)isScrollViewForCurrentTab;
+// Shows the header. The header is hidden after kHideOmniboxDelaySeconds if the
+// page requested fullscreen explicitly.
+- (void)triggerHeader;
+// Sets top inset to content view, and updates scroll view content offset to
+// counteract the change in the content's view frame.
+- (void)setContentViewTopContentPadding:(CGFloat)newTopInset;
+// Hide the header if it is possible to do so.
+- (void)hideHeaderIfPossible;
+// Shows or hides the header as directed by |visible|. If necessary the delegate
+// will be called synchronously with the desired offset and |animate| value.
+// This method can be called when it is desirable to show or hide the header
+// programmatically. It must be called when the header size changes.
+- (void)moveHeaderToRestingPosition:(BOOL)visible animate:(BOOL)animate;
+@end
+
+@implementation FullScreenController
+
+@synthesize delegate = delegate_;
+@synthesize navigationManager = navigationManager_;
+@synthesize previousContentOffset = previousContentOffset_;
+@synthesize previousRequestedContentOffset = previousRequestedContentOffset_;
+@synthesize lastScrollDownDistance = lastScrollDownDistance_;
+@synthesize immediateDragDown = immediateDragDown_;
+@synthesize isUserTriggered = userTriggered_;
+@synthesize isFullScreenDisabledForLoading = isFullScreenDisabledForLoading_;
+@synthesize skipNextScrollOffsetForHeader = skipNextScrollOffsetForHeader_;
+@synthesize delayedHideHeaderCount = delayedHideHeaderCount_;
+@synthesize sessionID = sessionID_;
+@synthesize userInteractionGestureRecognizer =
+ userInteractionGestureRecognizer_;
+
+- (id)initWithDelegate:(id<FullScreenControllerDelegate>)delegate
+ navigationManager:(NavigationManager*)navigationManager
+ sessionID:(NSString*)sessionID {
+ if (!gEnabledForTests) {
+ propertyReleaser_FullScreenController_.Init(self,
+ [FullScreenController class]);
+ [self release];
+ return nil;
+ }
+ if ((self = [super init])) {
+ propertyReleaser_FullScreenController_.Init(self,
+ [FullScreenController class]);
+ DCHECK(sessionID);
+ DCHECK(delegate);
+ delegate_ = delegate;
+ sessionID_ = [sessionID copy];
+ navigationManager_ = navigationManager;
+
+ NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
+ [center addObserver:self
+ selector:@selector(keyboardStart:)
+ name:UIKeyboardWillShowNotification
+ object:nil];
+ [center addObserver:self
+ selector:@selector(keyboardEnd:)
+ name:UIKeyboardWillHideNotification
+ object:nil];
+ [center addObserver:self
+ selector:@selector(incrementFullScreenLock)
+ name:kMenuWillShowNotification
+ object:nil];
+ [center addObserver:self
+ selector:@selector(decrementFullScreenLock)
+ name:kMenuWillHideNotification
+ object:nil];
+ [center addObserver:self
+ selector:@selector(triggerHeader)
+ name:kWillStartTabStripTabAnimation
+ object:nil];
+ [center addObserver:self
+ selector:@selector(incrementFullScreenLock)
+ name:kTabHistoryPopupWillShowNotification
+ object:nil];
+ [center addObserver:self
+ selector:@selector(decrementFullScreenLock)
+ name:kTabHistoryPopupWillHideNotification
+ object:nil];
+ [center addObserver:self
+ selector:@selector(incrementFullScreenLock)
+ name:kVoiceSearchWillShowNotification
+ object:nil];
+ [center addObserver:self
+ selector:@selector(decrementFullScreenLock)
+ name:kVoiceSearchWillHideNotification
+ object:nil];
+ [center addObserver:self
+ selector:@selector(incrementFullScreenLock)
+ name:kVoiceSearchBarViewButtonSelectedNotification
+ object:nil];
+ [center addObserver:self
+ selector:@selector(decrementFullScreenLock)
+ name:kVoiceSearchBarViewButtonDeselectedNotification
+ object:nil];
+ [center addObserver:self
+ selector:@selector(applicationWillEnterForeground:)
+ name:UIApplicationWillEnterForegroundNotification
+ object:nil];
+ [center addObserver:self
+ selector:@selector(triggerHeader)
+ name:kSetupForTestingWillCloseAllTabsNotification
+ object:nil];
+ [center addObserver:self
+ selector:@selector(incrementFullScreenLock)
+ name:ios_internal::kPageInfoWillShowNotification
+ object:nil];
+ [center addObserver:self
+ selector:@selector(decrementFullScreenLock)
+ name:ios_internal::kPageInfoWillHideNotification
+ object:nil];
+ [center
+ addObserver:self
+ selector:@selector(incrementFullScreenLock)
+ name:ios_internal::kLocationBarBecomesFirstResponderNotification
+ object:nil];
+ [center
+ addObserver:self
+ selector:@selector(decrementFullScreenLock)
+ name:ios_internal::kLocationBarResignsFirstResponderNotification
+ object:nil];
+ [center addObserver:self
+ selector:@selector(incrementFullScreenLock)
+ name:kTabStripDragStarted
+ object:nil];
+ [center addObserver:self
+ selector:@selector(decrementFullScreenLock)
+ name:kTabStripDragEnded
+ object:nil];
+ [center addObserver:self
+ selector:@selector(incrementFullScreenLock)
+ name:ios_internal::kSideSwipeWillStartNotification
+ object:nil];
+ [center addObserver:self
+ selector:@selector(decrementFullScreenLock)
+ name:ios_internal::kSideSwipeDidStopNotification
+ object:nil];
+ // TODO(jbbegue): Evaluate using a listener instead of a notification
+ // crbug/451373.
+ [center addObserver:self
+ selector:@selector(overscrollActionsWillStart)
+ name:ios_internal::kOverscollActionsWillStart
+ object:nil];
+ [center addObserver:self
+ selector:@selector(overscrollActionsDidEnd)
+ name:ios_internal::kOverscollActionsDidEnd
+ object:nil];
+ [self moveHeaderToRestingPosition:YES];
+ }
+ return self;
+}
+
+- (void)invalidate {
+ delegate_ = nil;
+ navigationManager_ = NULL;
+ [self.scrollViewProxy removeObserver:self];
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+- (void)dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [super dealloc];
+}
+
+- (CRWWebViewScrollViewProxy*)scrollViewProxy {
+ return [webViewProxy_ scrollViewProxy];
+}
+
+- (CGFloat)headerHeight {
+ return [self.delegate headerHeight];
+}
+
+- (CGFloat)topContentInsetCausedByHeader {
+ if ([webViewProxy_ shouldUseInsetForTopPadding]) {
+ // If the web view's |shouldUseInsetForTopPadding| is YES, fullscreen
+ // header insets the content by modifying content inset.
+ return self.headerHeight;
+ }
+ return 0.0f;
+}
+
+- (void)moveHeaderToRestingPosition:(BOOL)visible {
+ [self moveHeaderToRestingPosition:visible animate:YES];
+}
+
+- (void)moveHeaderToRestingPosition:(BOOL)visible animate:(BOOL)animate {
+ // If there is no delegate there is no need to do anything as the headerHeight
+ // cannot be obtained.
+ if (!self.delegate)
+ return;
+ DCHECK(visible || !self.isFullScreenDisabled);
+
+ // The desired final position of the header.
+ CGFloat headerPosition = visible ? 0.0 : self.headerHeight;
+
+ // Check if there is anything to do.
+ CGFloat delta = self.delegate.currentHeaderOffset - headerPosition;
+ if (CGFloatEquals(delta, 0.0))
+ return;
+
+ // Do not further act on scrollview changes.
+ ScopedIncrementer stack(&(self->selfTriggered_));
+
+ // If the scrollview is not the current scrollview, don't update the UI.
+ if (![self isScrollViewForCurrentTab])
+ return;
+
+ if (self.scrollViewProxy.contentOffset.y < 0.0 && delta < 0.0) {
+ // If the delta is negative this means the header must be hidden more. Check
+ // if the scrollview extents to the right place, there may be a need to
+ // scroll it up.
+ [self.delegate fullScreenController:self
+ drawHeaderViewFromOffset:headerPosition
+ onWebViewProxy:webViewProxy_
+ changeTopContentPadding:NO
+ scrollingToOffset:0.0f];
+ } else {
+ if (!visible && ![webViewProxy_ shouldUseInsetForTopPadding]) {
+ // The header will be hidden, so if the content view is not using the
+ // content inset, it is necessary to decrease the top padding, so more
+ // content is visible to the user.
+ CGFloat newTopContentPadding = self.headerHeight - headerPosition;
+ CGFloat topContentPaddingChange =
+ [webViewProxy_ topContentPadding] - newTopContentPadding;
+ if (topContentPaddingChange <= self.scrollViewProxy.contentOffset.y) {
+ // Padding can be decreased immediately and without animation as there
+ // is enough content present behind the header.
+ [self setContentViewTopContentPadding:newTopContentPadding];
+ } else {
+ // Header is taller that amount of hidden content, hence animated hide
+ // is required.
+ [self.delegate fullScreenController:self
+ drawHeaderViewFromOffset:headerPosition
+ onWebViewProxy:webViewProxy_
+ changeTopContentPadding:YES
+ scrollingToOffset:0.0f];
+ return;
+ }
+ }
+ // Only move the header, the content doesn't need to move.
+ [self.delegate fullScreenController:self
+ drawHeaderViewFromOffset:headerPosition
+ animate:animate];
+ }
+}
+
+- (void)disableFullScreen {
+ [self moveHeaderToRestingPosition:YES];
+ self.isFullScreenDisabledForLoading = YES;
+}
+
+- (void)enableFullScreen {
+ self.isFullScreenDisabledForLoading = NO;
+}
+
+- (void)shouldSkipNextScrollOffsetForHeader {
+ self.skipNextScrollOffsetForHeader = YES;
+}
+
+- (void)moveContentBelowHeader {
+ DCHECK(delegate_);
+ DCHECK(webViewProxy_);
+ [self moveHeaderToRestingPosition:YES animate:NO];
+ CGPoint contentOffset = self.scrollViewProxy.contentOffset;
+ contentOffset.y = 0;
+ self.scrollViewProxy.contentOffset = contentOffset;
+}
+
+#pragma mark - private methods
+
+- (BOOL)isEntryBrokenSSL:(web::NavigationItem*)item {
+ if (!item)
+ return NO;
+ // Only BROKEN results in an error (vs. a warning); see toolbar_model_impl.cc.
+ // TODO(qsr): Find a way to share this logic with the omnibox.
+ const web::SSLStatus& ssl = item->GetSSL();
+ switch (ssl.security_style) {
+ case web::SECURITY_STYLE_UNKNOWN:
+ case web::SECURITY_STYLE_UNAUTHENTICATED:
+ case web::SECURITY_STYLE_AUTHENTICATED:
+ return NO;
+ case web::SECURITY_STYLE_AUTHENTICATION_BROKEN:
+ return YES;
+ default:
+ NOTREACHED();
+ return YES;
+ }
+}
+
+- (BOOL)isFullScreenDisabled {
+ return self.isFullScreenDisabledTemporarily ||
+ self.isFullScreenDisabledPermanently;
+}
+
+- (BOOL)isFullScreenDisabledTemporarily {
+ return fullScreenLock_ > 0 || self.isFullScreenDisabledForLoading;
+}
+
+- (BOOL)isFullScreenDisabledForSSLStatus {
+ return self.navigationManager &&
+ [self isEntryBrokenSSL:self.navigationManager->GetVisibleItem()];
+}
+
+- (BOOL)isFullScreenDisabledPermanently {
+ return UIAccessibilityIsVoiceOverRunning() ||
+ self.isFullScreenDisabledForSSLStatus ||
+ CGRectIsEmpty(self.scrollViewProxy.frame);
+}
+
+- (void)hideHeaderIfPossible {
+ // Covers a number of conditions, like a menu being up.
+ if (self.isFullScreenDisabled)
+ return;
+
+ // Another FullScreenController is in control.
+ if (![self isScrollViewForCurrentTab])
+ return;
+
+ // No autohide if the content needs to move.
+ if (self.scrollViewProxy.contentOffset.y < 0.0)
+ return;
+
+ // It is quite safe to move the toolbar away.
+ [self moveHeaderToRestingPosition:NO];
+}
+
+- (void)incrementFullScreenLock {
+ // This method may be called late enough that it is unsafe to access the
+ // delegate.
+ fullScreenLock_++;
+}
+
+- (void)decrementFullScreenLock {
+ // The corresponding notification for incrementing the lock may have been
+ // posted before the FullScreenController was initialized. This can occur
+ // when entering a URL or search query from the NTP since the CRWWebController
+ // begins loading the page before the keyboard is dismissed.
+ if (fullScreenLock_ > 0)
+ fullScreenLock_--;
+}
+
+- (void)keyboardStart:(NSNotification*)notification {
+ if (!keyboardIsVisible_) {
+ keyboardIsVisible_ = YES;
+ [self incrementFullScreenLock];
+ }
+ [self moveHeaderToRestingPosition:YES];
+}
+
+- (void)keyboardEnd:(NSNotification*)notification {
+ if (keyboardIsVisible_) {
+ keyboardIsVisible_ = NO;
+ [self decrementFullScreenLock];
+ }
+}
+
+- (void)applicationWillEnterForeground:(NSNotification*)notification {
+ if (!self.isFullScreenDisabled && [self isScrollViewForCurrentTab]) {
+ dispatch_time_t popTime = dispatch_time(
+ DISPATCH_TIME_NOW, (int64_t)(kShowOmniboxDelaySeconds * NSEC_PER_SEC));
+ dispatch_after(popTime, dispatch_get_main_queue(), ^{
+ [self triggerHeader];
+ });
+ }
+}
+
+- (void)webViewScrollViewWillStartScrolling:
+ (CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
+ self.isUserTriggered = YES;
+ self.lastScrollDownDistance = 0.0;
+}
+
+- (void)webViewScrollViewDidStopScrolling:
+ (CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
+ self.isUserTriggered = NO;
+ // If an overscroll action is in progress, it means the header is already
+ // shown, trying to reset its position would interfere with the
+ // OverscrollActionsController.
+ if (!overscrollActionsInProgress_) {
+ CGFloat threshold = self.headerHeight / 2.0;
+
+ BOOL visible = self.delegate.currentHeaderOffset < threshold ||
+ self.isFullScreenDisabled;
+ [self moveHeaderToRestingPosition:visible];
+ }
+}
+
+- (BOOL)shouldIgnoreScroll:(CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
+ if (overscrollActionsInProgress_)
+ return YES;
+
+ if (![self isScrollViewForCurrentTab])
+ return YES;
+
+ BOOL shouldIgnore = selfTriggered_ || webViewScrollViewProxy.isZooming ||
+ self.headerHeight == 0.0 || !self.delegate;
+
+ if (self.isUserTriggered)
+ return shouldIgnore;
+
+ // Ignore simple realignment moves by 1 one pixel on retina display, called
+ // sometimes at the end of an animation.
+ CGFloat moveMagnitude = std::abs(self.previousContentOffset -
+ webViewScrollViewProxy.contentOffset.y);
+ shouldIgnore = shouldIgnore || moveMagnitude <= 0.5;
+
+ // Never let the background show. The keyboard may sometimes center the
+ // input fields in such a way that the inset of the scrollview is showing.
+ // In those cases the header must be popped up unconditionally.
+ CGFloat headerOffset = self.headerHeight - self.delegate.currentHeaderOffset;
+ if (webViewScrollViewProxy.contentOffset.y + headerOffset < 0.0)
+ shouldIgnore = NO;
+
+ return shouldIgnore;
+}
+
+- (BOOL)contentFitsWithToolbarVisible {
+ CGFloat viewportHeight = CGRectGetHeight(self.scrollViewProxy.frame) -
+ self.topContentInsetCausedByHeader;
+ return self.scrollViewProxy.contentSize.height <= viewportHeight;
+}
+
+- (void)userTriggeredWebViewScrollViewDidScroll:
+ (CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
+ // Calculate the relative move compared to the last checked position: positive
+ // values are scroll up, negative are scroll down.
+ CGFloat verticalDelta =
+ webViewScrollViewProxy.contentOffset.y - self.previousContentOffset;
+
+ // Scroll view is scrolled all the way to the top. Ignore the bouce up.
+ BOOL isContentAtTop = webViewScrollViewProxy.contentOffset.y <=
+ -self.topContentInsetCausedByHeader;
+ BOOL ignoreScrollAtContentTop = isContentAtTop && (0.0f < verticalDelta);
+
+ // Scroll view is scrolled all the way to the bottom. Ignore the bounce down.
+ // Also ignore the scroll up if the page is visible with the toolbar on-screen
+ // as the toolbar should not be hidden in that case.
+ BOOL ignoreScrollAtContentBottom =
+ (webViewScrollViewProxy.contentOffset.y +
+ webViewScrollViewProxy.frame.size.height >=
+ webViewScrollViewProxy.contentSize.height) &&
+ (verticalDelta < 0.0 || [self contentFitsWithToolbarVisible]);
+
+ if (ignoreScrollAtContentTop || ignoreScrollAtContentBottom)
+ verticalDelta = 0.0;
+
+ if (!self.immediateDragDown) {
+ // Accumulate or reset the lastScrollDownDistance. Scrolling up consumes
+ // twice as fast as scrolling down accumulates.
+ if (verticalDelta > 0.0)
+ self.lastScrollDownDistance += verticalDelta;
+ else
+ self.lastScrollDownDistance += verticalDelta * 2.0;
+
+ if (self.lastScrollDownDistance < 0.0)
+ self.lastScrollDownDistance = 0.0;
+ }
+
+ // Changes the header offset and informs the delegate to perform the move.
+ CGFloat newHeaderOffset = self.delegate.currentHeaderOffset;
+ if (verticalDelta > 0.0 || webViewScrollViewProxy.contentOffset.y <= 0.0 ||
+ self.lastScrollDownDistance <= 0.0) {
+ newHeaderOffset += verticalDelta;
+ }
+ if (newHeaderOffset < 0.0)
+ newHeaderOffset = 0.0;
+ else if (newHeaderOffset > self.headerHeight)
+ newHeaderOffset = self.headerHeight;
+
+ [self.delegate fullScreenController:self
+ drawHeaderViewFromOffset:newHeaderOffset
+ animate:NO];
+}
+
+- (void)codeTriggeredWebViewScrollViewDidScroll:
+ (CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
+ if (webViewScrollViewProxy.contentOffset.y >= 0.0 && !keyboardIsVisible_)
+ return;
+
+ BOOL isFullyVisible = CGFloatEquals(self.delegate.currentHeaderOffset, 0.0);
+ if (keyboardIsVisible_) {
+ DCHECK(isFullyVisible);
+ return;
+ }
+
+ CGFloat newOffset;
+ if ([self contentFitsWithToolbarVisible] && !keyboardIsVisible_) {
+ // Align the content just below the header if the scroll view's content fits
+ // entirely on screen when the toolbar visible and if the keyboard is not
+ // visible.
+ // Note: The keyboard is visible when the user is editing a text field
+ // at the bottom of the page and the page is scrolled to make it visible
+ // for the user. Avoid changing the offset in this case.
+ newOffset = -self.headerHeight;
+ } else {
+ newOffset = webViewScrollViewProxy.contentOffset.y;
+ // Correct the offset to take into account the fact that the header is
+ // obscuring the top of the view when scrolling down.
+ if ((webViewScrollViewProxy.contentOffset.y <=
+ self.previousRequestedContentOffset ||
+ keyboardIsVisible_) &&
+ !self.skipNextScrollOffsetForHeader)
+ newOffset -= self.headerHeight;
+
+ // Make sure the content is not too low.
+ if (newOffset < -self.headerHeight)
+ newOffset = -self.headerHeight;
+ }
+
+ if (isFullyVisible) {
+ // As the header is already visible, just move the scrollview.
+ webViewScrollViewProxy.contentOffset =
+ CGPointMake(webViewScrollViewProxy.contentOffset.x, newOffset);
+ }
+}
+
+- (BOOL)isScrollViewForCurrentTab {
+ return [self.delegate isTabWithIDCurrent:self.sessionID];
+}
+
+- (void)triggerHeader {
+ if (self.isFullScreenDisabled || ![self isScrollViewForCurrentTab])
+ return;
+ [self moveHeaderToRestingPosition:YES];
+}
+
+- (void)setContentViewTopContentPadding:(CGFloat)newTopPadding {
+ [webViewProxy_ setTopContentPadding:newTopPadding];
+}
+
+- (void)setToolbarInsetsForHeaderOffset:(CGFloat)headerOffset
+ forceUpdate:(BOOL)forceUpdate {
+ // Make space for the header in the scroll view.
+ CGFloat topInset = self.headerHeight - headerOffset;
+ UIEdgeInsets insets = self.scrollViewProxy.contentInset;
+ insets.top = topInset;
+
+ [self setContentViewTopContentPadding:topInset];
+}
+
+#pragma mark -
+#pragma mark CRWWebControllerObserver methods
+
+- (void)setWebViewProxy:(id<CRWWebViewProxy>)webViewProxy
+ controller:(CRWWebController*)webController {
+ DCHECK([webViewProxy scrollViewProxy]);
+ webViewProxy_.reset([webViewProxy retain]);
+ [[webViewProxy scrollViewProxy] addObserver:self];
+}
+
+- (void)pageLoaded:(CRWWebController*)webController {
+ [self enableFullScreen];
+ web::WebState* webState = webController.webState;
+ if (webState) {
+ BOOL MIMETypeIsPDF = webState->GetContentsMimeType() == "application/pdf";
+ [webViewProxy_ setShouldUseInsetForTopPadding:MIMETypeIsPDF];
+ }
+}
+
+- (void)webControllerWillClose:(CRWWebController*)webController {
+ [webController removeObserver:self];
+}
+
+#pragma mark -
+#pragma mark CRWWebViewScrollViewObserver
+
+- (void)webViewScrollViewDidScroll:
+ (CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
+ CGFloat previousRequestedContentOffset =
+ webViewScrollViewProxy.contentOffset.y;
+ if ([self shouldIgnoreScroll:webViewScrollViewProxy]) {
+ // Do not act on those events, just record the eventual move.
+ self.previousContentOffset = previousRequestedContentOffset;
+ self.previousRequestedContentOffset = previousRequestedContentOffset;
+ return;
+ }
+
+ // Ignore any scroll moves called recursively.
+ ScopedIncrementer stack(&(self->selfTriggered_));
+
+ if (self.isUserTriggered) {
+ if (!self.isFullScreenDisabled)
+ [self userTriggeredWebViewScrollViewDidScroll:webViewScrollViewProxy];
+ } else {
+ [self codeTriggeredWebViewScrollViewDidScroll:webViewScrollViewProxy];
+ }
+ self.previousContentOffset = webViewScrollViewProxy.contentOffset.y;
+ self.previousRequestedContentOffset = previousRequestedContentOffset;
+}
+- (void)webViewScrollViewWillBeginDragging:
+ (CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
+ [self webViewScrollViewWillStartScrolling:webViewScrollViewProxy];
+}
+
+- (void)webViewScrollViewDidEndDragging:
+ (CRWWebViewScrollViewProxy*)webViewScrollViewProxy
+ willDecelerate:(BOOL)decelerate {
+ DCHECK(self.delegate);
+ if (!decelerate)
+ [self webViewScrollViewDidStopScrolling:webViewScrollViewProxy];
+}
+
+- (void)webViewScrollViewDidEndScrollingAnimation:
+ (CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
+ self.skipNextScrollOffsetForHeader = NO;
+}
+
+- (void)webViewScrollViewDidEndDecelerating:
+ (CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
+ DCHECK(self.delegate);
+ [self webViewScrollViewDidStopScrolling:webViewScrollViewProxy];
+}
+
+- (BOOL)webViewScrollViewShouldScrollToTop:
+ (CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
+ if (webViewScrollViewProxy.contentInset.top != self.headerHeight) {
+ // Move the toolbar first so the origin of the page moves down.
+ [self moveHeaderToRestingPosition:YES];
+ }
+ return YES;
+}
+
+#pragma mark -
+#pragma mark CRWWebViewScrollViewProxyObserver
+
+- (void)webViewScrollViewProxyDidSetScrollView:
+ (CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
+ webViewScrollViewProxy.contentOffset = CGPointMake(0.0, -self.headerHeight);
+ [self setToolbarInsetsForHeaderOffset:0.0 forceUpdate:YES];
+}
+
+#pragma mark - UIGestureRecognizerDelegate
+
+- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gestureRecognizer {
+ return YES;
+}
+
+- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
+ shouldRecognizeSimultaneouslyWithGestureRecognizer:
+ (UIGestureRecognizer*)otherGestureRecognizer {
+ // This is necessary for the gesture recognizer to receive all the touches.
+ // If the default value of NO is returned the default recognizers on the
+ // webview do take precedence.
+ return YES;
+}
+
+- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
+ shouldReceiveTouch:(UITouch*)touch {
+ return YES;
+}
+
+#pragma mark - Overscroll actions notifications handling
+
+- (void)overscrollActionsWillStart {
+ [self incrementFullScreenLock];
+ overscrollActionsInProgress_ = YES;
+}
+
+- (void)overscrollActionsDidEnd {
+ [self decrementFullScreenLock];
+ overscrollActionsInProgress_ = NO;
+}
+
+#pragma mark - Used for testing
+
++ (void)setHideOmniboxDelaySeconds:(double)hideOmniboxDelaySeconds {
+ gHideOmniboxDelaySeconds = hideOmniboxDelaySeconds;
+}
+
++ (void)resetHideOmniboxDelaySeconds {
+ gHideOmniboxDelaySeconds = kDefaultHideOmniboxDelaySeconds;
+}
+
++ (void)setEnabledForTests:(BOOL)enabled {
+ gEnabledForTests = enabled;
+}
+
+@end
« no previous file with comments | « ios/chrome/browser/ui/fullscreen_controller.h ('k') | ios/chrome/browser/ui/fullscreen_controller_unittest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698