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

Side by Side 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "ios/chrome/browser/ui/fullscreen_controller.h"
6
7 #include <cmath>
8
9 #include "base/logging.h"
10 #include "base/mac/objc_property_releaser.h"
11 #import "ios/chrome/browser/ui/browser_view_controller.h"
12 #import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller. h"
13 #import "ios/chrome/browser/ui/tabs/tab_strip_controller.h"
14 #import "ios/chrome/browser/ui/toolbar/toolbar_controller.h"
15 #import "ios/chrome/browser/ui/toolbar/web_toolbar_controller.h"
16 #import "ios/chrome/browser/ui/voice/voice_search_notification_names.h"
17 #include "ios/web/public/navigation_item.h"
18 #import "ios/web/public/navigation_manager.h"
19 #include "ios/web/public/ssl_status.h"
20 #import "ios/web/public/web_state/crw_web_view_proxy.h"
21 #import "ios/web/web_state/ui/crw_web_controller.h"
22
23 NSString* const kSetupForTestingWillCloseAllTabsNotification =
24 @"kSetupForTestingWillCloseAllTabsNotification";
25
26 using web::NavigationManager;
27
28 namespace {
29
30 class ScopedIncrementer {
31 public:
32 explicit ScopedIncrementer(int* value) : value_(value) { ++(*value_); }
33 ~ScopedIncrementer() { --(*value_); }
34
35 private:
36 int* value_;
37 };
38
39 CGFloat kPrecision = 0.00001;
40
41 // Duration for the delay before showing the omnibox.
42 const double kShowOmniboxDelaySeconds = 0.5;
43 // Default duration for the delay before hiding the omnibox.
44 const double kDefaultHideOmniboxDelaySeconds = 3.0;
45 // Duration for the delay before hiding the omnibox.
46 double gHideOmniboxDelaySeconds = kDefaultHideOmniboxDelaySeconds;
47 // Indicates if the FullScreenController returns nil from |init|. Used for
48 // testing purposes.
49 BOOL gEnabledForTests = YES;
50
51 // Compares that two CGFloat a and b are within a range of kPrecision of each
52 // other.
53 BOOL CGFloatEquals(CGFloat a, CGFloat b) {
54 CGFloat delta = std::abs(a - b);
55
56 return delta < kPrecision;
57 }
58
59 } // anonymous namespace.
60
61 @interface FullScreenController ()<UIGestureRecognizerDelegate> {
62 // Used to detect movement in the scrollview produced by this class.
63 int selfTriggered_;
64 // Used to detect if the keyboard is visible.
65 BOOL keyboardIsVisible_;
66 // Used to detect that the OverscrollActionsController is displaying its UI.
67 // The FullScreenController is disabled when the OverscrollActionsController's
68 // UI is displayed.
69 BOOL overscrollActionsInProgress_;
70 // Counter used to keep track of the number of actions currently disabling
71 // full screen.
72 uint fullScreenLock_;
73 // CRWWebViewProxy object allows web view manipulations.
74 base::scoped_nsprotocol<id<CRWWebViewProxy>> webViewProxy_;
75 base::mac::ObjCPropertyReleaser propertyReleaser_FullScreenController_;
76 }
77
78 // Access to the UIWebView's UIScrollView.
79 @property(nonatomic, readonly) CRWWebViewScrollViewProxy* scrollViewProxy;
80 // The navigation controller of the page.
81 @property(nonatomic, readonly, assign) NavigationManager* navigationManager;
82 // The gesture recognizer set on the scrollview to detect tap. Must be readwrite
83 // for property releaser to work.
84 @property(nonatomic, readwrite, retain)
85 UITapGestureRecognizer* userInteractionGestureRecognizer;
86 // The delegate responsible for providing the header height and moving the
87 // header.
88 @property(nonatomic, readonly) id<FullScreenControllerDelegate> delegate;
89 // Current height of the header, in points. This is a pass-through method that
90 // fetches the header height from the FullScreenControllerDelegate.
91 @property(nonatomic, readonly) CGFloat headerHeight;
92 // |top| field of UIScrollView.contentInset value caused by header.
93 // Always 0 for WKWebView, as it does not support contentInset.
94 @property(nonatomic, readonly) CGFloat topContentInsetCausedByHeader;
95 // Last known y offset of the content in the scroll view during a scroll. Used
96 // to infer the direction of the current scroll.
97 @property(nonatomic, assign) CGFloat previousContentOffset;
98 // Last known y offset requested on the scroll view. In general the same value
99 // as previous content offset unless the offset was corrected by the controller
100 // to slide from under the toolbar.
101 @property(nonatomic, assign) CGFloat previousRequestedContentOffset;
102 // Whether or not the content of the scroll view fits entirely on screen when
103 // the toolbar is visible.
104 @property(nonatomic, readonly) BOOL contentFitsWithToolbarVisible;
105 // During a drag operation stores and remember the length of the latest scroll
106 // down operation. If a scroll up move happens later during the same gesture
107 // this will be used to delay the apparition of the header.
108 @property(nonatomic, assign) CGFloat lastScrollDownDistance;
109 // Tracks whether the current scrollview movements are triggered by the user or
110 // programmatically.
111 @property(nonatomic, assign) BOOL isUserTriggered;
112 // Tracks if fullscreen is currently disabled because of page load.
113 @property(nonatomic, assign) BOOL isFullScreenDisabledForLoading;
114 // Tracks if fullscreen is currently disabled because of unsecured page.
115 @property(nonatomic, readonly, assign) BOOL isFullScreenDisabledForSSLStatus;
116 // Tracks if fullscreen is currently disabled.
117 @property(nonatomic, readonly, assign) BOOL isFullScreenDisabled;
118 // Tracks if fullscreen is temporarily disabled for the current page.
119 @property(nonatomic, readonly, assign) BOOL isFullScreenDisabledTemporarily;
120 // Tracks if fullscreen is permanently disabled for the current page.
121 @property(nonatomic, readonly, assign) BOOL isFullScreenDisabledPermanently;
122 // Skip next attempt to correct the scroll offset for the toolbar height. This
123 // is necessary when programatically scrolling down the y offset.
124 @property(nonatomic, assign) BOOL skipNextScrollOffsetForHeader;
125 // Incremented each time a timed request to remove the header is sent,
126 // decremented when the timer fires. When it reach zero, the header is moved.
127 @property(nonatomic, assign) unsigned int delayedHideHeaderCount;
128 // ID of the session (each Tab represents a session).
129 @property(nonatomic, copy) NSString* sessionID;
130
131 // Returns if the given entry will be displayed with an error padlock. If this
132 // is the case, the toolbar should never be hidden on this entry.
133 - (BOOL)isEntryBrokenSSL:(web::NavigationItem*)item;
134 // Called at the start of a user scroll.
135 - (void)webViewScrollViewWillStartScrolling:
136 (CRWWebViewScrollViewProxy*)webViewScrollViewProxy;
137 // Called at the end of a scroll.
138 - (void)webViewScrollViewDidStopScrolling:
139 (CRWWebViewScrollViewProxy*)webViewScrollViewProxy;
140 // Called before and after the keyboard is appearing. Used to allow scroll
141 // events triggered by the keyboard appearing to go through.
142 - (void)keyboardStart:(NSNotification*)notification;
143 - (void)keyboardEnd:(NSNotification*)notification;
144 // Called before and after an action that disables full screen. The version
145 // resetting the timer will ensure that the header stay on screen for a little
146 // while.
147 - (void)incrementFullScreenLock;
148 - (void)decrementFullScreenLock;
149 // Called when the application is about to be the foreground application.
150 - (void)applicationWillEnterForeground:(NSNotification*)notification;
151 // TODO(shreyasv): Make the following methods act on a WebViewScrollView proxy
152 // instead of taking in a UIScrollView directly.
153 // Called from -webViewScrollViewDidScroll: Returns YES if the scroll should be
154 // ignored.
155 - (BOOL)shouldIgnoreScroll:(CRWWebViewScrollViewProxy*)webViewScrollViewProxy;
156 // Processes a scroll event triggered by a user action.
157 - (void)userTriggeredWebViewScrollViewDidScroll:
158 (CRWWebViewScrollViewProxy*)webViewScrollViewProxy;
159 // Processes a scroll event triggered by code (these could be initiated via
160 // Javascript, find in page or simply the keyboard sliding in and out).
161 - (void)codeTriggeredWebViewScrollViewDidScroll:
162 (CRWWebViewScrollViewProxy*)webViewScrollViewProxy;
163 // Returns YES if |scrollView_| is for the current tab.
164 - (BOOL)isScrollViewForCurrentTab;
165 // Shows the header. The header is hidden after kHideOmniboxDelaySeconds if the
166 // page requested fullscreen explicitly.
167 - (void)triggerHeader;
168 // Sets top inset to content view, and updates scroll view content offset to
169 // counteract the change in the content's view frame.
170 - (void)setContentViewTopContentPadding:(CGFloat)newTopInset;
171 // Hide the header if it is possible to do so.
172 - (void)hideHeaderIfPossible;
173 // Shows or hides the header as directed by |visible|. If necessary the delegate
174 // will be called synchronously with the desired offset and |animate| value.
175 // This method can be called when it is desirable to show or hide the header
176 // programmatically. It must be called when the header size changes.
177 - (void)moveHeaderToRestingPosition:(BOOL)visible animate:(BOOL)animate;
178 @end
179
180 @implementation FullScreenController
181
182 @synthesize delegate = delegate_;
183 @synthesize navigationManager = navigationManager_;
184 @synthesize previousContentOffset = previousContentOffset_;
185 @synthesize previousRequestedContentOffset = previousRequestedContentOffset_;
186 @synthesize lastScrollDownDistance = lastScrollDownDistance_;
187 @synthesize immediateDragDown = immediateDragDown_;
188 @synthesize isUserTriggered = userTriggered_;
189 @synthesize isFullScreenDisabledForLoading = isFullScreenDisabledForLoading_;
190 @synthesize skipNextScrollOffsetForHeader = skipNextScrollOffsetForHeader_;
191 @synthesize delayedHideHeaderCount = delayedHideHeaderCount_;
192 @synthesize sessionID = sessionID_;
193 @synthesize userInteractionGestureRecognizer =
194 userInteractionGestureRecognizer_;
195
196 - (id)initWithDelegate:(id<FullScreenControllerDelegate>)delegate
197 navigationManager:(NavigationManager*)navigationManager
198 sessionID:(NSString*)sessionID {
199 if (!gEnabledForTests) {
200 propertyReleaser_FullScreenController_.Init(self,
201 [FullScreenController class]);
202 [self release];
203 return nil;
204 }
205 if ((self = [super init])) {
206 propertyReleaser_FullScreenController_.Init(self,
207 [FullScreenController class]);
208 DCHECK(sessionID);
209 DCHECK(delegate);
210 delegate_ = delegate;
211 sessionID_ = [sessionID copy];
212 navigationManager_ = navigationManager;
213
214 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
215 [center addObserver:self
216 selector:@selector(keyboardStart:)
217 name:UIKeyboardWillShowNotification
218 object:nil];
219 [center addObserver:self
220 selector:@selector(keyboardEnd:)
221 name:UIKeyboardWillHideNotification
222 object:nil];
223 [center addObserver:self
224 selector:@selector(incrementFullScreenLock)
225 name:kMenuWillShowNotification
226 object:nil];
227 [center addObserver:self
228 selector:@selector(decrementFullScreenLock)
229 name:kMenuWillHideNotification
230 object:nil];
231 [center addObserver:self
232 selector:@selector(triggerHeader)
233 name:kWillStartTabStripTabAnimation
234 object:nil];
235 [center addObserver:self
236 selector:@selector(incrementFullScreenLock)
237 name:kTabHistoryPopupWillShowNotification
238 object:nil];
239 [center addObserver:self
240 selector:@selector(decrementFullScreenLock)
241 name:kTabHistoryPopupWillHideNotification
242 object:nil];
243 [center addObserver:self
244 selector:@selector(incrementFullScreenLock)
245 name:kVoiceSearchWillShowNotification
246 object:nil];
247 [center addObserver:self
248 selector:@selector(decrementFullScreenLock)
249 name:kVoiceSearchWillHideNotification
250 object:nil];
251 [center addObserver:self
252 selector:@selector(incrementFullScreenLock)
253 name:kVoiceSearchBarViewButtonSelectedNotification
254 object:nil];
255 [center addObserver:self
256 selector:@selector(decrementFullScreenLock)
257 name:kVoiceSearchBarViewButtonDeselectedNotification
258 object:nil];
259 [center addObserver:self
260 selector:@selector(applicationWillEnterForeground:)
261 name:UIApplicationWillEnterForegroundNotification
262 object:nil];
263 [center addObserver:self
264 selector:@selector(triggerHeader)
265 name:kSetupForTestingWillCloseAllTabsNotification
266 object:nil];
267 [center addObserver:self
268 selector:@selector(incrementFullScreenLock)
269 name:ios_internal::kPageInfoWillShowNotification
270 object:nil];
271 [center addObserver:self
272 selector:@selector(decrementFullScreenLock)
273 name:ios_internal::kPageInfoWillHideNotification
274 object:nil];
275 [center
276 addObserver:self
277 selector:@selector(incrementFullScreenLock)
278 name:ios_internal::kLocationBarBecomesFirstResponderNotification
279 object:nil];
280 [center
281 addObserver:self
282 selector:@selector(decrementFullScreenLock)
283 name:ios_internal::kLocationBarResignsFirstResponderNotification
284 object:nil];
285 [center addObserver:self
286 selector:@selector(incrementFullScreenLock)
287 name:kTabStripDragStarted
288 object:nil];
289 [center addObserver:self
290 selector:@selector(decrementFullScreenLock)
291 name:kTabStripDragEnded
292 object:nil];
293 [center addObserver:self
294 selector:@selector(incrementFullScreenLock)
295 name:ios_internal::kSideSwipeWillStartNotification
296 object:nil];
297 [center addObserver:self
298 selector:@selector(decrementFullScreenLock)
299 name:ios_internal::kSideSwipeDidStopNotification
300 object:nil];
301 // TODO(jbbegue): Evaluate using a listener instead of a notification
302 // crbug/451373.
303 [center addObserver:self
304 selector:@selector(overscrollActionsWillStart)
305 name:ios_internal::kOverscollActionsWillStart
306 object:nil];
307 [center addObserver:self
308 selector:@selector(overscrollActionsDidEnd)
309 name:ios_internal::kOverscollActionsDidEnd
310 object:nil];
311 [self moveHeaderToRestingPosition:YES];
312 }
313 return self;
314 }
315
316 - (void)invalidate {
317 delegate_ = nil;
318 navigationManager_ = NULL;
319 [self.scrollViewProxy removeObserver:self];
320 [[NSNotificationCenter defaultCenter] removeObserver:self];
321 }
322
323 - (void)dealloc {
324 [[NSNotificationCenter defaultCenter] removeObserver:self];
325 [super dealloc];
326 }
327
328 - (CRWWebViewScrollViewProxy*)scrollViewProxy {
329 return [webViewProxy_ scrollViewProxy];
330 }
331
332 - (CGFloat)headerHeight {
333 return [self.delegate headerHeight];
334 }
335
336 - (CGFloat)topContentInsetCausedByHeader {
337 if ([webViewProxy_ shouldUseInsetForTopPadding]) {
338 // If the web view's |shouldUseInsetForTopPadding| is YES, fullscreen
339 // header insets the content by modifying content inset.
340 return self.headerHeight;
341 }
342 return 0.0f;
343 }
344
345 - (void)moveHeaderToRestingPosition:(BOOL)visible {
346 [self moveHeaderToRestingPosition:visible animate:YES];
347 }
348
349 - (void)moveHeaderToRestingPosition:(BOOL)visible animate:(BOOL)animate {
350 // If there is no delegate there is no need to do anything as the headerHeight
351 // cannot be obtained.
352 if (!self.delegate)
353 return;
354 DCHECK(visible || !self.isFullScreenDisabled);
355
356 // The desired final position of the header.
357 CGFloat headerPosition = visible ? 0.0 : self.headerHeight;
358
359 // Check if there is anything to do.
360 CGFloat delta = self.delegate.currentHeaderOffset - headerPosition;
361 if (CGFloatEquals(delta, 0.0))
362 return;
363
364 // Do not further act on scrollview changes.
365 ScopedIncrementer stack(&(self->selfTriggered_));
366
367 // If the scrollview is not the current scrollview, don't update the UI.
368 if (![self isScrollViewForCurrentTab])
369 return;
370
371 if (self.scrollViewProxy.contentOffset.y < 0.0 && delta < 0.0) {
372 // If the delta is negative this means the header must be hidden more. Check
373 // if the scrollview extents to the right place, there may be a need to
374 // scroll it up.
375 [self.delegate fullScreenController:self
376 drawHeaderViewFromOffset:headerPosition
377 onWebViewProxy:webViewProxy_
378 changeTopContentPadding:NO
379 scrollingToOffset:0.0f];
380 } else {
381 if (!visible && ![webViewProxy_ shouldUseInsetForTopPadding]) {
382 // The header will be hidden, so if the content view is not using the
383 // content inset, it is necessary to decrease the top padding, so more
384 // content is visible to the user.
385 CGFloat newTopContentPadding = self.headerHeight - headerPosition;
386 CGFloat topContentPaddingChange =
387 [webViewProxy_ topContentPadding] - newTopContentPadding;
388 if (topContentPaddingChange <= self.scrollViewProxy.contentOffset.y) {
389 // Padding can be decreased immediately and without animation as there
390 // is enough content present behind the header.
391 [self setContentViewTopContentPadding:newTopContentPadding];
392 } else {
393 // Header is taller that amount of hidden content, hence animated hide
394 // is required.
395 [self.delegate fullScreenController:self
396 drawHeaderViewFromOffset:headerPosition
397 onWebViewProxy:webViewProxy_
398 changeTopContentPadding:YES
399 scrollingToOffset:0.0f];
400 return;
401 }
402 }
403 // Only move the header, the content doesn't need to move.
404 [self.delegate fullScreenController:self
405 drawHeaderViewFromOffset:headerPosition
406 animate:animate];
407 }
408 }
409
410 - (void)disableFullScreen {
411 [self moveHeaderToRestingPosition:YES];
412 self.isFullScreenDisabledForLoading = YES;
413 }
414
415 - (void)enableFullScreen {
416 self.isFullScreenDisabledForLoading = NO;
417 }
418
419 - (void)shouldSkipNextScrollOffsetForHeader {
420 self.skipNextScrollOffsetForHeader = YES;
421 }
422
423 - (void)moveContentBelowHeader {
424 DCHECK(delegate_);
425 DCHECK(webViewProxy_);
426 [self moveHeaderToRestingPosition:YES animate:NO];
427 CGPoint contentOffset = self.scrollViewProxy.contentOffset;
428 contentOffset.y = 0;
429 self.scrollViewProxy.contentOffset = contentOffset;
430 }
431
432 #pragma mark - private methods
433
434 - (BOOL)isEntryBrokenSSL:(web::NavigationItem*)item {
435 if (!item)
436 return NO;
437 // Only BROKEN results in an error (vs. a warning); see toolbar_model_impl.cc.
438 // TODO(qsr): Find a way to share this logic with the omnibox.
439 const web::SSLStatus& ssl = item->GetSSL();
440 switch (ssl.security_style) {
441 case web::SECURITY_STYLE_UNKNOWN:
442 case web::SECURITY_STYLE_UNAUTHENTICATED:
443 case web::SECURITY_STYLE_AUTHENTICATED:
444 return NO;
445 case web::SECURITY_STYLE_AUTHENTICATION_BROKEN:
446 return YES;
447 default:
448 NOTREACHED();
449 return YES;
450 }
451 }
452
453 - (BOOL)isFullScreenDisabled {
454 return self.isFullScreenDisabledTemporarily ||
455 self.isFullScreenDisabledPermanently;
456 }
457
458 - (BOOL)isFullScreenDisabledTemporarily {
459 return fullScreenLock_ > 0 || self.isFullScreenDisabledForLoading;
460 }
461
462 - (BOOL)isFullScreenDisabledForSSLStatus {
463 return self.navigationManager &&
464 [self isEntryBrokenSSL:self.navigationManager->GetVisibleItem()];
465 }
466
467 - (BOOL)isFullScreenDisabledPermanently {
468 return UIAccessibilityIsVoiceOverRunning() ||
469 self.isFullScreenDisabledForSSLStatus ||
470 CGRectIsEmpty(self.scrollViewProxy.frame);
471 }
472
473 - (void)hideHeaderIfPossible {
474 // Covers a number of conditions, like a menu being up.
475 if (self.isFullScreenDisabled)
476 return;
477
478 // Another FullScreenController is in control.
479 if (![self isScrollViewForCurrentTab])
480 return;
481
482 // No autohide if the content needs to move.
483 if (self.scrollViewProxy.contentOffset.y < 0.0)
484 return;
485
486 // It is quite safe to move the toolbar away.
487 [self moveHeaderToRestingPosition:NO];
488 }
489
490 - (void)incrementFullScreenLock {
491 // This method may be called late enough that it is unsafe to access the
492 // delegate.
493 fullScreenLock_++;
494 }
495
496 - (void)decrementFullScreenLock {
497 // The corresponding notification for incrementing the lock may have been
498 // posted before the FullScreenController was initialized. This can occur
499 // when entering a URL or search query from the NTP since the CRWWebController
500 // begins loading the page before the keyboard is dismissed.
501 if (fullScreenLock_ > 0)
502 fullScreenLock_--;
503 }
504
505 - (void)keyboardStart:(NSNotification*)notification {
506 if (!keyboardIsVisible_) {
507 keyboardIsVisible_ = YES;
508 [self incrementFullScreenLock];
509 }
510 [self moveHeaderToRestingPosition:YES];
511 }
512
513 - (void)keyboardEnd:(NSNotification*)notification {
514 if (keyboardIsVisible_) {
515 keyboardIsVisible_ = NO;
516 [self decrementFullScreenLock];
517 }
518 }
519
520 - (void)applicationWillEnterForeground:(NSNotification*)notification {
521 if (!self.isFullScreenDisabled && [self isScrollViewForCurrentTab]) {
522 dispatch_time_t popTime = dispatch_time(
523 DISPATCH_TIME_NOW, (int64_t)(kShowOmniboxDelaySeconds * NSEC_PER_SEC));
524 dispatch_after(popTime, dispatch_get_main_queue(), ^{
525 [self triggerHeader];
526 });
527 }
528 }
529
530 - (void)webViewScrollViewWillStartScrolling:
531 (CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
532 self.isUserTriggered = YES;
533 self.lastScrollDownDistance = 0.0;
534 }
535
536 - (void)webViewScrollViewDidStopScrolling:
537 (CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
538 self.isUserTriggered = NO;
539 // If an overscroll action is in progress, it means the header is already
540 // shown, trying to reset its position would interfere with the
541 // OverscrollActionsController.
542 if (!overscrollActionsInProgress_) {
543 CGFloat threshold = self.headerHeight / 2.0;
544
545 BOOL visible = self.delegate.currentHeaderOffset < threshold ||
546 self.isFullScreenDisabled;
547 [self moveHeaderToRestingPosition:visible];
548 }
549 }
550
551 - (BOOL)shouldIgnoreScroll:(CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
552 if (overscrollActionsInProgress_)
553 return YES;
554
555 if (![self isScrollViewForCurrentTab])
556 return YES;
557
558 BOOL shouldIgnore = selfTriggered_ || webViewScrollViewProxy.isZooming ||
559 self.headerHeight == 0.0 || !self.delegate;
560
561 if (self.isUserTriggered)
562 return shouldIgnore;
563
564 // Ignore simple realignment moves by 1 one pixel on retina display, called
565 // sometimes at the end of an animation.
566 CGFloat moveMagnitude = std::abs(self.previousContentOffset -
567 webViewScrollViewProxy.contentOffset.y);
568 shouldIgnore = shouldIgnore || moveMagnitude <= 0.5;
569
570 // Never let the background show. The keyboard may sometimes center the
571 // input fields in such a way that the inset of the scrollview is showing.
572 // In those cases the header must be popped up unconditionally.
573 CGFloat headerOffset = self.headerHeight - self.delegate.currentHeaderOffset;
574 if (webViewScrollViewProxy.contentOffset.y + headerOffset < 0.0)
575 shouldIgnore = NO;
576
577 return shouldIgnore;
578 }
579
580 - (BOOL)contentFitsWithToolbarVisible {
581 CGFloat viewportHeight = CGRectGetHeight(self.scrollViewProxy.frame) -
582 self.topContentInsetCausedByHeader;
583 return self.scrollViewProxy.contentSize.height <= viewportHeight;
584 }
585
586 - (void)userTriggeredWebViewScrollViewDidScroll:
587 (CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
588 // Calculate the relative move compared to the last checked position: positive
589 // values are scroll up, negative are scroll down.
590 CGFloat verticalDelta =
591 webViewScrollViewProxy.contentOffset.y - self.previousContentOffset;
592
593 // Scroll view is scrolled all the way to the top. Ignore the bouce up.
594 BOOL isContentAtTop = webViewScrollViewProxy.contentOffset.y <=
595 -self.topContentInsetCausedByHeader;
596 BOOL ignoreScrollAtContentTop = isContentAtTop && (0.0f < verticalDelta);
597
598 // Scroll view is scrolled all the way to the bottom. Ignore the bounce down.
599 // Also ignore the scroll up if the page is visible with the toolbar on-screen
600 // as the toolbar should not be hidden in that case.
601 BOOL ignoreScrollAtContentBottom =
602 (webViewScrollViewProxy.contentOffset.y +
603 webViewScrollViewProxy.frame.size.height >=
604 webViewScrollViewProxy.contentSize.height) &&
605 (verticalDelta < 0.0 || [self contentFitsWithToolbarVisible]);
606
607 if (ignoreScrollAtContentTop || ignoreScrollAtContentBottom)
608 verticalDelta = 0.0;
609
610 if (!self.immediateDragDown) {
611 // Accumulate or reset the lastScrollDownDistance. Scrolling up consumes
612 // twice as fast as scrolling down accumulates.
613 if (verticalDelta > 0.0)
614 self.lastScrollDownDistance += verticalDelta;
615 else
616 self.lastScrollDownDistance += verticalDelta * 2.0;
617
618 if (self.lastScrollDownDistance < 0.0)
619 self.lastScrollDownDistance = 0.0;
620 }
621
622 // Changes the header offset and informs the delegate to perform the move.
623 CGFloat newHeaderOffset = self.delegate.currentHeaderOffset;
624 if (verticalDelta > 0.0 || webViewScrollViewProxy.contentOffset.y <= 0.0 ||
625 self.lastScrollDownDistance <= 0.0) {
626 newHeaderOffset += verticalDelta;
627 }
628 if (newHeaderOffset < 0.0)
629 newHeaderOffset = 0.0;
630 else if (newHeaderOffset > self.headerHeight)
631 newHeaderOffset = self.headerHeight;
632
633 [self.delegate fullScreenController:self
634 drawHeaderViewFromOffset:newHeaderOffset
635 animate:NO];
636 }
637
638 - (void)codeTriggeredWebViewScrollViewDidScroll:
639 (CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
640 if (webViewScrollViewProxy.contentOffset.y >= 0.0 && !keyboardIsVisible_)
641 return;
642
643 BOOL isFullyVisible = CGFloatEquals(self.delegate.currentHeaderOffset, 0.0);
644 if (keyboardIsVisible_) {
645 DCHECK(isFullyVisible);
646 return;
647 }
648
649 CGFloat newOffset;
650 if ([self contentFitsWithToolbarVisible] && !keyboardIsVisible_) {
651 // Align the content just below the header if the scroll view's content fits
652 // entirely on screen when the toolbar visible and if the keyboard is not
653 // visible.
654 // Note: The keyboard is visible when the user is editing a text field
655 // at the bottom of the page and the page is scrolled to make it visible
656 // for the user. Avoid changing the offset in this case.
657 newOffset = -self.headerHeight;
658 } else {
659 newOffset = webViewScrollViewProxy.contentOffset.y;
660 // Correct the offset to take into account the fact that the header is
661 // obscuring the top of the view when scrolling down.
662 if ((webViewScrollViewProxy.contentOffset.y <=
663 self.previousRequestedContentOffset ||
664 keyboardIsVisible_) &&
665 !self.skipNextScrollOffsetForHeader)
666 newOffset -= self.headerHeight;
667
668 // Make sure the content is not too low.
669 if (newOffset < -self.headerHeight)
670 newOffset = -self.headerHeight;
671 }
672
673 if (isFullyVisible) {
674 // As the header is already visible, just move the scrollview.
675 webViewScrollViewProxy.contentOffset =
676 CGPointMake(webViewScrollViewProxy.contentOffset.x, newOffset);
677 }
678 }
679
680 - (BOOL)isScrollViewForCurrentTab {
681 return [self.delegate isTabWithIDCurrent:self.sessionID];
682 }
683
684 - (void)triggerHeader {
685 if (self.isFullScreenDisabled || ![self isScrollViewForCurrentTab])
686 return;
687 [self moveHeaderToRestingPosition:YES];
688 }
689
690 - (void)setContentViewTopContentPadding:(CGFloat)newTopPadding {
691 [webViewProxy_ setTopContentPadding:newTopPadding];
692 }
693
694 - (void)setToolbarInsetsForHeaderOffset:(CGFloat)headerOffset
695 forceUpdate:(BOOL)forceUpdate {
696 // Make space for the header in the scroll view.
697 CGFloat topInset = self.headerHeight - headerOffset;
698 UIEdgeInsets insets = self.scrollViewProxy.contentInset;
699 insets.top = topInset;
700
701 [self setContentViewTopContentPadding:topInset];
702 }
703
704 #pragma mark -
705 #pragma mark CRWWebControllerObserver methods
706
707 - (void)setWebViewProxy:(id<CRWWebViewProxy>)webViewProxy
708 controller:(CRWWebController*)webController {
709 DCHECK([webViewProxy scrollViewProxy]);
710 webViewProxy_.reset([webViewProxy retain]);
711 [[webViewProxy scrollViewProxy] addObserver:self];
712 }
713
714 - (void)pageLoaded:(CRWWebController*)webController {
715 [self enableFullScreen];
716 web::WebState* webState = webController.webState;
717 if (webState) {
718 BOOL MIMETypeIsPDF = webState->GetContentsMimeType() == "application/pdf";
719 [webViewProxy_ setShouldUseInsetForTopPadding:MIMETypeIsPDF];
720 }
721 }
722
723 - (void)webControllerWillClose:(CRWWebController*)webController {
724 [webController removeObserver:self];
725 }
726
727 #pragma mark -
728 #pragma mark CRWWebViewScrollViewObserver
729
730 - (void)webViewScrollViewDidScroll:
731 (CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
732 CGFloat previousRequestedContentOffset =
733 webViewScrollViewProxy.contentOffset.y;
734 if ([self shouldIgnoreScroll:webViewScrollViewProxy]) {
735 // Do not act on those events, just record the eventual move.
736 self.previousContentOffset = previousRequestedContentOffset;
737 self.previousRequestedContentOffset = previousRequestedContentOffset;
738 return;
739 }
740
741 // Ignore any scroll moves called recursively.
742 ScopedIncrementer stack(&(self->selfTriggered_));
743
744 if (self.isUserTriggered) {
745 if (!self.isFullScreenDisabled)
746 [self userTriggeredWebViewScrollViewDidScroll:webViewScrollViewProxy];
747 } else {
748 [self codeTriggeredWebViewScrollViewDidScroll:webViewScrollViewProxy];
749 }
750 self.previousContentOffset = webViewScrollViewProxy.contentOffset.y;
751 self.previousRequestedContentOffset = previousRequestedContentOffset;
752 }
753 - (void)webViewScrollViewWillBeginDragging:
754 (CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
755 [self webViewScrollViewWillStartScrolling:webViewScrollViewProxy];
756 }
757
758 - (void)webViewScrollViewDidEndDragging:
759 (CRWWebViewScrollViewProxy*)webViewScrollViewProxy
760 willDecelerate:(BOOL)decelerate {
761 DCHECK(self.delegate);
762 if (!decelerate)
763 [self webViewScrollViewDidStopScrolling:webViewScrollViewProxy];
764 }
765
766 - (void)webViewScrollViewDidEndScrollingAnimation:
767 (CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
768 self.skipNextScrollOffsetForHeader = NO;
769 }
770
771 - (void)webViewScrollViewDidEndDecelerating:
772 (CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
773 DCHECK(self.delegate);
774 [self webViewScrollViewDidStopScrolling:webViewScrollViewProxy];
775 }
776
777 - (BOOL)webViewScrollViewShouldScrollToTop:
778 (CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
779 if (webViewScrollViewProxy.contentInset.top != self.headerHeight) {
780 // Move the toolbar first so the origin of the page moves down.
781 [self moveHeaderToRestingPosition:YES];
782 }
783 return YES;
784 }
785
786 #pragma mark -
787 #pragma mark CRWWebViewScrollViewProxyObserver
788
789 - (void)webViewScrollViewProxyDidSetScrollView:
790 (CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
791 webViewScrollViewProxy.contentOffset = CGPointMake(0.0, -self.headerHeight);
792 [self setToolbarInsetsForHeaderOffset:0.0 forceUpdate:YES];
793 }
794
795 #pragma mark - UIGestureRecognizerDelegate
796
797 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gestureRecognizer {
798 return YES;
799 }
800
801 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
802 shouldRecognizeSimultaneouslyWithGestureRecognizer:
803 (UIGestureRecognizer*)otherGestureRecognizer {
804 // This is necessary for the gesture recognizer to receive all the touches.
805 // If the default value of NO is returned the default recognizers on the
806 // webview do take precedence.
807 return YES;
808 }
809
810 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
811 shouldReceiveTouch:(UITouch*)touch {
812 return YES;
813 }
814
815 #pragma mark - Overscroll actions notifications handling
816
817 - (void)overscrollActionsWillStart {
818 [self incrementFullScreenLock];
819 overscrollActionsInProgress_ = YES;
820 }
821
822 - (void)overscrollActionsDidEnd {
823 [self decrementFullScreenLock];
824 overscrollActionsInProgress_ = NO;
825 }
826
827 #pragma mark - Used for testing
828
829 + (void)setHideOmniboxDelaySeconds:(double)hideOmniboxDelaySeconds {
830 gHideOmniboxDelaySeconds = hideOmniboxDelaySeconds;
831 }
832
833 + (void)resetHideOmniboxDelaySeconds {
834 gHideOmniboxDelaySeconds = kDefaultHideOmniboxDelaySeconds;
835 }
836
837 + (void)setEnabledForTests:(BOOL)enabled {
838 gEnabledForTests = enabled;
839 }
840
841 @end
OLDNEW
« 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