Fix breakages in https://codereview.chromium.org/1155713003/
[chromium-blink-merge.git] / ios / web / web_state / crw_web_view_scroll_view_proxy.mm
blob6ae38d06d6985313ceed1401bdee9538f9fa55a3
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "ios/web/public/web_state/crw_web_view_scroll_view_proxy.h"
7 #include "base/auto_reset.h"
8 #import "base/ios/crb_protocol_observers.h"
9 #import "base/ios/weak_nsobject.h"
10 #include "base/mac/foundation_util.h"
11 #import "base/mac/scoped_nsobject.h"
13 @implementation CRWWebViewScrollViewProxy {
14   base::WeakNSObject<UIScrollView> _scrollView;
15   base::scoped_nsobject<id> _observers;
16   // When |_ignoreScroll| is set to YES, do not pass on -scrollViewDidScroll
17   // calls to observers.  This is used by -setContentInsetFast, which needs to
18   // update and reset the contentOffset to force a fast update.  These updates
19   // should be a no-op for the contentOffset, so the callbacks can be ignored.
20   BOOL _ignoreScroll;
23 - (instancetype)init {
24   self = [super init];
25   if (self) {
26     Protocol* protocol = @protocol(CRWWebViewScrollViewProxyObserver);
27     _observers.reset(
28         [[CRBProtocolObservers observersWithProtocol:protocol] retain]);
29   }
30   return self;
33 - (void)addGestureRecognizer:(UIGestureRecognizer*)gestureRecognizer {
34   [_scrollView addGestureRecognizer:gestureRecognizer];
37 - (void)removeGestureRecognizer:(UIGestureRecognizer*)gestureRecognizer {
38   [_scrollView removeGestureRecognizer:gestureRecognizer];
41 - (void)addObserver:(id<CRWWebViewScrollViewProxyObserver>)observer {
42   [_observers addObserver:observer];
45 - (void)removeObserver:(id<CRWWebViewScrollViewProxyObserver>)observer {
46   [_observers removeObserver:observer];
49 - (void)setScrollView:(UIScrollView*)scrollView {
50   if (_scrollView == scrollView)
51     return;
52   [_scrollView setDelegate:nil];
53   if (scrollView) {
54     DCHECK(!scrollView.delegate);
55     scrollView.delegate = self;
56   }
57   _scrollView.reset(scrollView);
58   [_observers webViewScrollViewProxyDidSetScrollView:self];
61 - (CGRect)frame {
62   return _scrollView ? [_scrollView frame] : CGRectZero;
65 - (BOOL)isScrollEnabled {
66   return [_scrollView isScrollEnabled];
69 - (void)setScrollEnabled:(BOOL)scrollEnabled {
70   [_scrollView setScrollEnabled:scrollEnabled];
73 - (BOOL)bounces {
74   return [_scrollView bounces];
77 - (void)setBounces:(BOOL)bounces {
78   [_scrollView setBounces:bounces];
81 - (BOOL)isZooming {
82   return [_scrollView isZooming];
85 - (void)setContentOffset:(CGPoint)contentOffset {
86   [_scrollView setContentOffset:contentOffset];
89 - (CGPoint)contentOffset {
90   return _scrollView ? [_scrollView contentOffset] : CGPointZero;
93 - (void)setContentInsetFast:(UIEdgeInsets)contentInset {
94   if (!_scrollView)
95     return;
97   // The method -scrollViewSetContentInsetImpl below is bypassing UIWebView's
98   // subclassed UIScrollView implemention of setContentOffset.  UIKIt's
99   // implementation calls the internal method |_updateViewSettings| after
100   // updating the contentInsets.  This ensures things like absolute positions
101   // are correctly updated.  The problem is |_updateViewSettings| does lots of
102   // other things and is very very slow.  The workaround below simply sets the
103   // scrollView's content insets directly, and then fiddles with the
104   // contentOffset below to correct the absolute positioning of elements.
105   static void (*scrollViewSetContentInsetImpl)(id, SEL, UIEdgeInsets);
106   static SEL setContentInset;
107   static dispatch_once_t onceToken;
108   dispatch_once(&onceToken, ^{
109       setContentInset = @selector(setContentInset:);
110       scrollViewSetContentInsetImpl =
111           (void (*)(id, SEL, UIEdgeInsets))class_getMethodImplementation(
112               [UIScrollView class], setContentInset);
113   });
114   scrollViewSetContentInsetImpl(_scrollView, setContentInset, contentInset);
116   // Change and then reset the contentOffset to force the view into updating the
117   // absolute position of elements and content frame. Updating the
118   // contentOffset will cause the -scrollViewDidScroll callback to fire.
119   // Because we are eventually setting the contentOffset back to it's original
120   // position, we can ignore these calls.
121   base::AutoReset<BOOL> autoReset(&_ignoreScroll, YES);
122   CGPoint contentOffset = [_scrollView contentOffset];
123   _scrollView.get().contentOffset =
124       CGPointMake(contentOffset.x, contentOffset.y + 1);
125   _scrollView.get().contentOffset = contentOffset;
128 - (void)setContentInset:(UIEdgeInsets)contentInset {
129   [_scrollView setContentInset:contentInset];
132 - (UIEdgeInsets)contentInset {
133   return _scrollView ? [_scrollView contentInset] : UIEdgeInsetsZero;
136 - (void)setScrollIndicatorInsets:(UIEdgeInsets)scrollIndicatorInsets {
137   [_scrollView setScrollIndicatorInsets:scrollIndicatorInsets];
140 - (UIEdgeInsets)scrollIndicatorInsets {
141   return _scrollView ? [_scrollView scrollIndicatorInsets] : UIEdgeInsetsZero;
144 - (void)setContentSize:(CGSize)contentSize {
145   [_scrollView setContentSize:contentSize];
148 - (CGSize)contentSize {
149   return _scrollView ? [_scrollView contentSize] : CGSizeZero;
152 - (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated {
153   [_scrollView setContentOffset:contentOffset animated:animated];
156 - (UIPanGestureRecognizer*)panGestureRecognizer {
157   return [_scrollView panGestureRecognizer];
160 - (NSArray*)gestureRecognizers {
161   return [_scrollView gestureRecognizers];
164 #pragma mark -
165 #pragma mark UIScrollViewDelegate callbacks
167 - (void)scrollViewDidScroll:(UIScrollView*)scrollView {
168   DCHECK_EQ(_scrollView, scrollView);
169   if (!_ignoreScroll) {
170     [_observers webViewScrollViewDidScroll:self];
171   }
174 - (void)scrollViewWillBeginDragging:(UIScrollView*)scrollView {
175   DCHECK_EQ(_scrollView, scrollView);
176   [_observers webViewScrollViewWillBeginDragging:self];
179 - (void)scrollViewWillEndDragging:(UIScrollView*)scrollView
180                      withVelocity:(CGPoint)velocity
181               targetContentOffset:(inout CGPoint*)targetContentOffset {
182   DCHECK_EQ(_scrollView, scrollView);
183   [_observers webViewScrollViewWillEndDragging:self
184                                   withVelocity:velocity
185                            targetContentOffset:targetContentOffset];
188 - (void)scrollViewDidEndDragging:(UIScrollView*)scrollView
189                   willDecelerate:(BOOL)decelerate {
190   DCHECK_EQ(_scrollView, scrollView);
191   [_observers webViewScrollViewDidEndDragging:self willDecelerate:decelerate];
194 - (void)scrollViewDidEndDecelerating:(UIScrollView*)scrollView {
195   DCHECK_EQ(_scrollView, scrollView);
196   [_observers webViewScrollViewDidEndDecelerating:self];
199 - (void)scrollViewDidEndScrollingAnimation:(UIScrollView*)scrollView {
200   DCHECK_EQ(_scrollView, scrollView);
201   [_observers webViewScrollViewDidEndScrollingAnimation:self];
204 - (BOOL)scrollViewShouldScrollToTop:(UIScrollView*)scrollView {
205   DCHECK_EQ(_scrollView, scrollView);
206   __block BOOL shouldScrollToTop = YES;
207   [_observers executeOnObservers:^(id observer) {
208       if ([observer respondsToSelector:@selector(
209           webViewScrollViewShouldScrollToTop:)]) {
210         shouldScrollToTop = shouldScrollToTop &&
211             [observer webViewScrollViewShouldScrollToTop:self];
212       }
213   }];
214   return shouldScrollToTop;
217 @end