blob: 526907895dd5a442f9f67658c5b35c48fa3629dd [file] [log] [blame]
// Copyright 2019 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/web/web_state/ui/crw_web_view_scroll_view_delegate_proxy.h"
#include <ostream>
#include "base/check_op.h"
#import "base/ios/crb_protocol_observers.h"
#import "ios/web/web_state/ui/crw_web_view_scroll_view_proxy+internal.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface CRWWebViewScrollViewDelegateProxy ()
@property(nonatomic, weak) CRWWebViewScrollViewProxy* scrollViewProxy;
@end
// Calls to methods supported by CRWWebViewScrollViewProxyObserver are forwarded
// to both of the delegate and the observers of self.scrollViewProxy.
// Calls to other methods are forwarded only to self.delegateOfProxy
// using -methodSignatoreForSelector: and -forwardInvocation:.
@implementation CRWWebViewScrollViewDelegateProxy
- (instancetype)initWithScrollViewProxy:
(CRWWebViewScrollViewProxy*)scrollViewProxy {
self = [super init];
if (self) {
_scrollViewProxy = scrollViewProxy;
}
return self;
}
#pragma mark - NSObject
- (BOOL)respondsToSelector:(SEL)aSelector {
// This class forwards unimplemented methods to the delegate of the scroll
// view proxy. So it also responds to methods defined in the delegate of the
// scroll view proxy.
return [self.delegateOfProxy respondsToSelector:aSelector] ||
[super respondsToSelector:aSelector];
}
#pragma mark Forwards unimplemented methods
- (NSMethodSignature*)methodSignatureForSelector:(SEL)sel {
// Called when the method is not implemented in this class. Forwards the
// method to the delegate of the scroll view proxy.
// This cast is necessary because -methodSignatureForSelector: is a method of
// NSObject. It is pretty safe to assume that the delegate is an instance of
// NSObject.
NSObject* delegateAsObject = static_cast<NSObject*>(self.delegateOfProxy);
return [delegateAsObject methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation*)invocation {
// Called when the method is not implemented in this class. Forwards the
// method to the delegate of the scroll view proxy.
// Replaces the |sender| argument of the delegate method call with
// [self.scrollViewProxy asUIScrollView]. |sender| should be the first
// argument of every delegate method according to Apple's style guide:
// https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/NamingMethods.html#//apple_ref/doc/uid/20001282-BCIGIJJF
// and it is true for all methods of UIScrollViewDelegate as of today. But
// here performs a few safety checks to make sure that the first argument is
// |sender|:
// - The method has at least one argument
// - The first argument is typed UIScrollView
// - The first argument is equal to the underlying scroll view
//
// Note that the first (normal) argument is at index 2. Index 0 and 1 are for
// self and _cmd respectively.
NSMethodSignature* signature = invocation.methodSignature;
if (signature.numberOfArguments >= 3 &&
strcmp([signature getArgumentTypeAtIndex:2], @encode(UIScrollView*)) ==
0) {
__unsafe_unretained UIScrollView* sender;
[invocation getArgument:&sender atIndex:2];
if (sender == self.scrollViewProxy.underlyingScrollView) {
sender = [self.scrollViewProxy asUIScrollView];
[invocation setArgument:&sender atIndex:2];
}
}
[invocation invokeWithTarget:self.delegateOfProxy];
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView*)scrollView {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView);
if ([self.delegateOfProxy
respondsToSelector:@selector(scrollViewDidScroll:)]) {
[self.delegateOfProxy
scrollViewDidScroll:[self.scrollViewProxy asUIScrollView]];
}
[self.scrollViewProxy.observers
webViewScrollViewDidScroll:self.scrollViewProxy];
}
- (void)scrollViewWillBeginDragging:(UIScrollView*)scrollView {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView);
if ([self.delegateOfProxy
respondsToSelector:@selector(scrollViewWillBeginDragging:)]) {
[self.delegateOfProxy
scrollViewWillBeginDragging:[self.scrollViewProxy asUIScrollView]];
}
[self.scrollViewProxy.observers
webViewScrollViewWillBeginDragging:self.scrollViewProxy];
}
- (void)scrollViewWillEndDragging:(UIScrollView*)scrollView
withVelocity:(CGPoint)velocity
targetContentOffset:(inout CGPoint*)targetContentOffset {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView);
if ([self.delegateOfProxy respondsToSelector:@selector
(scrollViewWillEndDragging:
withVelocity:targetContentOffset:)]) {
[self.delegateOfProxy
scrollViewWillEndDragging:[self.scrollViewProxy asUIScrollView]
withVelocity:velocity
targetContentOffset:targetContentOffset];
}
[self.scrollViewProxy.observers
webViewScrollViewWillEndDragging:self.scrollViewProxy
withVelocity:velocity
targetContentOffset:targetContentOffset];
}
- (void)scrollViewDidEndDragging:(UIScrollView*)scrollView
willDecelerate:(BOOL)decelerate {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView);
if ([self.delegateOfProxy respondsToSelector:@selector
(scrollViewDidEndDragging:willDecelerate:)]) {
[self.delegateOfProxy
scrollViewDidEndDragging:[self.scrollViewProxy asUIScrollView]
willDecelerate:decelerate];
}
[self.scrollViewProxy.observers
webViewScrollViewDidEndDragging:self.scrollViewProxy
willDecelerate:decelerate];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView*)scrollView {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView);
if ([self.delegateOfProxy
respondsToSelector:@selector(scrollViewDidEndDecelerating:)]) {
[self.delegateOfProxy
scrollViewDidEndDecelerating:[self.scrollViewProxy asUIScrollView]];
}
[self.scrollViewProxy.observers
webViewScrollViewDidEndDecelerating:self.scrollViewProxy];
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView*)scrollView {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView);
if ([self.delegateOfProxy
respondsToSelector:@selector(scrollViewDidEndScrollingAnimation:)]) {
[self.delegateOfProxy
scrollViewDidEndScrollingAnimation:[self.scrollViewProxy
asUIScrollView]];
}
[self.scrollViewProxy.observers
webViewScrollViewDidEndScrollingAnimation:self.scrollViewProxy];
}
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView*)scrollView {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView);
__block BOOL shouldScrollToTop = YES;
if ([self.delegateOfProxy
respondsToSelector:@selector(scrollViewShouldScrollToTop:)]) {
shouldScrollToTop =
shouldScrollToTop &&
[self.delegateOfProxy
scrollViewShouldScrollToTop:[self.scrollViewProxy asUIScrollView]];
}
[self.scrollViewProxy.observers executeOnObservers:^(id observer) {
if ([observer respondsToSelector:@selector
(webViewScrollViewShouldScrollToTop:)]) {
shouldScrollToTop =
shouldScrollToTop &&
[observer webViewScrollViewShouldScrollToTop:self.scrollViewProxy];
}
}];
return shouldScrollToTop;
}
- (void)scrollViewDidZoom:(UIScrollView*)scrollView {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView);
if ([self.delegateOfProxy respondsToSelector:@selector(scrollViewDidZoom:)]) {
[self.delegateOfProxy
scrollViewDidZoom:[self.scrollViewProxy asUIScrollView]];
}
[self.scrollViewProxy.observers
webViewScrollViewDidZoom:self.scrollViewProxy];
}
- (void)scrollViewWillBeginZooming:(UIScrollView*)scrollView
withView:(UIView*)view {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView);
if ([self.delegateOfProxy
respondsToSelector:@selector(scrollViewWillBeginZooming:withView:)]) {
[self.delegateOfProxy
scrollViewWillBeginZooming:[self.scrollViewProxy asUIScrollView]
withView:view];
}
[self.scrollViewProxy.observers
webViewScrollViewWillBeginZooming:self.scrollViewProxy];
}
- (void)scrollViewDidEndZooming:(UIScrollView*)scrollView
withView:(UIView*)view
atScale:(CGFloat)scale {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView);
if ([self.delegateOfProxy respondsToSelector:@selector
(scrollViewDidEndZooming:withView:atScale:)]) {
[self.delegateOfProxy
scrollViewDidEndZooming:[self.scrollViewProxy asUIScrollView]
withView:view
atScale:scale];
}
[self.scrollViewProxy.observers
webViewScrollViewDidEndZooming:self.scrollViewProxy
atScale:scale];
}
#pragma mark - Helpers
// The delegate of the scroll view proxy.
- (id<UIScrollViewDelegate>)delegateOfProxy {
return [self.scrollViewProxy asUIScrollView].delegate;
}
@end