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.
23 - (instancetype)init {
26 Protocol* protocol = @protocol(CRWWebViewScrollViewProxyObserver);
28 [[CRBProtocolObservers observersWithProtocol:protocol] retain]);
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)
52 [_scrollView setDelegate:nil];
54 DCHECK(!scrollView.delegate);
55 scrollView.delegate = self;
57 _scrollView.reset(scrollView);
58 [_observers webViewScrollViewProxyDidSetScrollView:self];
62 return _scrollView ? [_scrollView frame] : CGRectZero;
65 - (BOOL)isScrollEnabled {
66 return [_scrollView isScrollEnabled];
69 - (void)setScrollEnabled:(BOOL)scrollEnabled {
70 [_scrollView setScrollEnabled:scrollEnabled];
74 return [_scrollView bounces];
77 - (void)setBounces:(BOOL)bounces {
78 [_scrollView setBounces:bounces];
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 {
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);
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];
165 #pragma mark UIScrollViewDelegate callbacks
167 - (void)scrollViewDidScroll:(UIScrollView*)scrollView {
168 DCHECK_EQ(_scrollView, scrollView);
169 if (!_ignoreScroll) {
170 [_observers webViewScrollViewDidScroll:self];
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];
214 return shouldScrollToTop;