Commit d82618ea authored by Hiroshi Ichikawa's avatar Hiroshi Ichikawa Committed by Commit Bot

Implement -[CRWWebViewScrollViewProxy asUIScrollView].delegate.

Bug: 1023250
Change-Id: I2210c0f38619ac8bf051fccf3d912f959c7a0787
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1939158
Commit-Queue: Hiroshi Ichikawa <ichikawa@chromium.org>
Reviewed-by: default avatarGauthier Ambard <gambard@chromium.org>
Reviewed-by: default avatarEugene But <eugenebut@chromium.org>
Cr-Commit-Position: refs/heads/master@{#721910}
parent e6cd7631
...@@ -69,7 +69,7 @@ ...@@ -69,7 +69,7 @@
// cannot forward methods in UIScrollView if CRWWebViewScrollViewProxy is a // cannot forward methods in UIScrollView if CRWWebViewScrollViewProxy is a
// subclass of UIScrollView. // subclass of UIScrollView.
// //
// TODO(crbug.com/1023250): Support KVO and delegate of this scroll view. // TODO(crbug.com/1023250): Support KVO of this scroll view.
// TODO(crbug.com/1023250): Restore properties of the scroll view when the // TODO(crbug.com/1023250): Restore properties of the scroll view when the
// scroll view is reset. // scroll view is reset.
- (UIScrollView*)asUIScrollView; - (UIScrollView*)asUIScrollView;
......
...@@ -18,7 +18,10 @@ ...@@ -18,7 +18,10 @@
@end @end
// TODO(crbug.com/1023250): Delegate to _scrollViewProxy.delegate as well. // 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 @implementation CRWWebViewScrollViewDelegateProxy
- (instancetype)initWithScrollViewProxy: - (instancetype)initWithScrollViewProxy:
...@@ -30,16 +33,82 @@ ...@@ -30,16 +33,82 @@
return self; 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) {
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 #pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView*)scrollView { - (void)scrollViewDidScroll:(UIScrollView*)scrollView {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView); DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView);
if ([self.delegateOfProxy
respondsToSelector:@selector(scrollViewDidScroll:)]) {
[self.delegateOfProxy
scrollViewDidScroll:[self.scrollViewProxy asUIScrollView]];
}
[self.scrollViewProxy.observers [self.scrollViewProxy.observers
webViewScrollViewDidScroll:self.scrollViewProxy]; webViewScrollViewDidScroll:self.scrollViewProxy];
} }
- (void)scrollViewWillBeginDragging:(UIScrollView*)scrollView { - (void)scrollViewWillBeginDragging:(UIScrollView*)scrollView {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView); DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView);
if ([self.delegateOfProxy
respondsToSelector:@selector(scrollViewWillBeginDragging:)]) {
[self.delegateOfProxy
scrollViewWillBeginDragging:[self.scrollViewProxy asUIScrollView]];
}
[self.scrollViewProxy.observers [self.scrollViewProxy.observers
webViewScrollViewWillBeginDragging:self.scrollViewProxy]; webViewScrollViewWillBeginDragging:self.scrollViewProxy];
} }
...@@ -48,6 +117,14 @@ ...@@ -48,6 +117,14 @@
withVelocity:(CGPoint)velocity withVelocity:(CGPoint)velocity
targetContentOffset:(inout CGPoint*)targetContentOffset { targetContentOffset:(inout CGPoint*)targetContentOffset {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView); 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 [self.scrollViewProxy.observers
webViewScrollViewWillEndDragging:self.scrollViewProxy webViewScrollViewWillEndDragging:self.scrollViewProxy
withVelocity:velocity withVelocity:velocity
...@@ -57,6 +134,12 @@ ...@@ -57,6 +134,12 @@
- (void)scrollViewDidEndDragging:(UIScrollView*)scrollView - (void)scrollViewDidEndDragging:(UIScrollView*)scrollView
willDecelerate:(BOOL)decelerate { willDecelerate:(BOOL)decelerate {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView); DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView);
if ([self.delegateOfProxy respondsToSelector:@selector
(scrollViewDidEndDragging:willDecelerate:)]) {
[self.delegateOfProxy
scrollViewDidEndDragging:[self.scrollViewProxy asUIScrollView]
willDecelerate:decelerate];
}
[self.scrollViewProxy.observers [self.scrollViewProxy.observers
webViewScrollViewDidEndDragging:self.scrollViewProxy webViewScrollViewDidEndDragging:self.scrollViewProxy
willDecelerate:decelerate]; willDecelerate:decelerate];
...@@ -64,12 +147,23 @@ ...@@ -64,12 +147,23 @@
- (void)scrollViewDidEndDecelerating:(UIScrollView*)scrollView { - (void)scrollViewDidEndDecelerating:(UIScrollView*)scrollView {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView); DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView);
if ([self.delegateOfProxy
respondsToSelector:@selector(scrollViewDidEndDecelerating:)]) {
[self.delegateOfProxy
scrollViewDidEndDecelerating:[self.scrollViewProxy asUIScrollView]];
}
[self.scrollViewProxy.observers [self.scrollViewProxy.observers
webViewScrollViewDidEndDecelerating:self.scrollViewProxy]; webViewScrollViewDidEndDecelerating:self.scrollViewProxy];
} }
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView*)scrollView { - (void)scrollViewDidEndScrollingAnimation:(UIScrollView*)scrollView {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView); DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView);
if ([self.delegateOfProxy
respondsToSelector:@selector(scrollViewDidEndScrollingAnimation:)]) {
[self.delegateOfProxy
scrollViewDidEndScrollingAnimation:[self.scrollViewProxy
asUIScrollView]];
}
[self.scrollViewProxy.observers [self.scrollViewProxy.observers
webViewScrollViewDidEndScrollingAnimation:self.scrollViewProxy]; webViewScrollViewDidEndScrollingAnimation:self.scrollViewProxy];
} }
...@@ -77,6 +171,15 @@ ...@@ -77,6 +171,15 @@
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView*)scrollView { - (BOOL)scrollViewShouldScrollToTop:(UIScrollView*)scrollView {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView); DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView);
__block BOOL shouldScrollToTop = YES; __block BOOL shouldScrollToTop = YES;
if ([self.delegateOfProxy
respondsToSelector:@selector(scrollViewShouldScrollToTop:)]) {
shouldScrollToTop =
shouldScrollToTop &&
[self.delegateOfProxy
scrollViewShouldScrollToTop:[self.scrollViewProxy asUIScrollView]];
}
[self.scrollViewProxy.observers executeOnObservers:^(id observer) { [self.scrollViewProxy.observers executeOnObservers:^(id observer) {
if ([observer respondsToSelector:@selector if ([observer respondsToSelector:@selector
(webViewScrollViewShouldScrollToTop:)]) { (webViewScrollViewShouldScrollToTop:)]) {
...@@ -85,11 +188,16 @@ ...@@ -85,11 +188,16 @@
[observer webViewScrollViewShouldScrollToTop:self.scrollViewProxy]; [observer webViewScrollViewShouldScrollToTop:self.scrollViewProxy];
} }
}]; }];
return shouldScrollToTop; return shouldScrollToTop;
} }
- (void)scrollViewDidZoom:(UIScrollView*)scrollView { - (void)scrollViewDidZoom:(UIScrollView*)scrollView {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView); DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView);
if ([self.delegateOfProxy respondsToSelector:@selector(scrollViewDidZoom:)]) {
[self.delegateOfProxy
scrollViewDidZoom:[self.scrollViewProxy asUIScrollView]];
}
[self.scrollViewProxy.observers [self.scrollViewProxy.observers
webViewScrollViewDidZoom:self.scrollViewProxy]; webViewScrollViewDidZoom:self.scrollViewProxy];
} }
...@@ -97,6 +205,12 @@ ...@@ -97,6 +205,12 @@
- (void)scrollViewWillBeginZooming:(UIScrollView*)scrollView - (void)scrollViewWillBeginZooming:(UIScrollView*)scrollView
withView:(UIView*)view { withView:(UIView*)view {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView); DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView);
if ([self.delegateOfProxy
respondsToSelector:@selector(scrollViewWillBeginZooming:withView:)]) {
[self.delegateOfProxy
scrollViewWillBeginZooming:[self.scrollViewProxy asUIScrollView]
withView:view];
}
[self.scrollViewProxy.observers [self.scrollViewProxy.observers
webViewScrollViewWillBeginZooming:self.scrollViewProxy]; webViewScrollViewWillBeginZooming:self.scrollViewProxy];
} }
...@@ -105,9 +219,23 @@ ...@@ -105,9 +219,23 @@
withView:(UIView*)view withView:(UIView*)view
atScale:(CGFloat)scale { atScale:(CGFloat)scale {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView); 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 [self.scrollViewProxy.observers
webViewScrollViewDidEndZooming:self.scrollViewProxy webViewScrollViewDidEndZooming:self.scrollViewProxy
atScale:scale]; atScale:scale];
} }
#pragma mark - Helpers
// The delegate of the scroll view proxy.
- (id<UIScrollViewDelegate>)delegateOfProxy {
return [self.scrollViewProxy asUIScrollView].delegate;
}
@end @end
...@@ -31,6 +31,9 @@ ...@@ -31,6 +31,9 @@
CRBProtocolObservers<CRWWebViewScrollViewProxyObserver>* observers; CRBProtocolObservers<CRWWebViewScrollViewProxyObserver>* observers;
@property(nonatomic, weak) UIScrollView* underlyingScrollView; @property(nonatomic, weak) UIScrollView* underlyingScrollView;
// This exists for compatibility with UIScrollView (see -asUIScrollView).
@property(nonatomic, weak) id<UIScrollViewDelegate> delegate;
// Returns the key paths that need to be observed for UIScrollView. // Returns the key paths that need to be observed for UIScrollView.
+ (NSArray*)scrollViewObserverKeyPaths; + (NSArray*)scrollViewObserverKeyPaths;
...@@ -282,7 +285,7 @@ ...@@ -282,7 +285,7 @@
#pragma mark - Forwards unimplemented UIScrollView methods #pragma mark - Forwards unimplemented UIScrollView methods
- (nullable NSMethodSignature*)methodSignatureForSelector:(SEL)sel { - (NSMethodSignature*)methodSignatureForSelector:(SEL)sel {
// Called when this proxy is accessed through -asUIScrollView and the method // Called when this proxy is accessed through -asUIScrollView and the method
// is not implemented in this class. Do not call [self.underlyingScrollView // is not implemented in this class. Do not call [self.underlyingScrollView
// methodSignatureForSelector:] here instead because self.underlyingScrollView // methodSignatureForSelector:] here instead because self.underlyingScrollView
......
...@@ -389,4 +389,111 @@ TEST_F(CRWWebViewScrollViewProxyTest, RespondsToSelector) { ...@@ -389,4 +389,111 @@ TEST_F(CRWWebViewScrollViewProxyTest, RespondsToSelector) {
[webViewScrollViewProxy_ respondsToSelector:@selector(containsString:)]); [webViewScrollViewProxy_ respondsToSelector:@selector(containsString:)]);
} }
// Tests delegate method forwarding to [webViewScrollViewProxy_
// asUIScrollView].delegate when:
// - [webViewScrollViewProxy_ asUIScrollView].delegate is not nil
// - CRWWebViewScrollViewDelegateProxy implements the method
//
// Expects that a method call to the delegate of the underlying scroll view is
// forwarded to [webViewScrollViewProxy_ asUIScrollView].delegate.
TEST_F(CRWWebViewScrollViewProxyTest,
ProxyDelegateMethodForwardingForImplementedMethod) {
UIScrollView* underlying_scroll_view = [[UIScrollView alloc] init];
[webViewScrollViewProxy_ setScrollView:underlying_scroll_view];
id<UIScrollViewDelegate> mock_proxy_delegate =
OCMProtocolMock(@protocol(UIScrollViewDelegate));
[webViewScrollViewProxy_ asUIScrollView].delegate = mock_proxy_delegate;
UIView* mock_view = OCMClassMock([UIView class]);
OCMExpect([mock_proxy_delegate
scrollViewWillBeginZooming:[webViewScrollViewProxy_ asUIScrollView]
withView:mock_view]);
EXPECT_TRUE([underlying_scroll_view.delegate
respondsToSelector:@selector(scrollViewWillBeginZooming:withView:)]);
[underlying_scroll_view.delegate
scrollViewWillBeginZooming:underlying_scroll_view
withView:mock_view];
EXPECT_OCMOCK_VERIFY(static_cast<id>(mock_proxy_delegate));
[webViewScrollViewProxy_ setScrollView:nil];
}
// Tests delegate method forwarding to [webViewScrollViewProxy_
// asUIScrollView].delegate when:
// - [webViewScrollViewProxy_ asUIScrollView].delegate is not nil
// - CRWWebViewScrollViewDelegateProxy does *not* implement the method
//
// Expects that a method call to the delegate of the underlying scroll view is
// forwarded to [webViewScrollViewProxy_ asUIScrollView].delegate.
TEST_F(CRWWebViewScrollViewProxyTest,
ProxyDelegateMethodForwardingForUnimplementedMethod) {
UIScrollView* underlying_scroll_view = [[UIScrollView alloc] init];
[webViewScrollViewProxy_ setScrollView:underlying_scroll_view];
id<UIScrollViewDelegate> mock_proxy_delegate =
OCMProtocolMock(@protocol(UIScrollViewDelegate));
[webViewScrollViewProxy_ asUIScrollView].delegate = mock_proxy_delegate;
UIView* mock_view = OCMClassMock([UIView class]);
OCMExpect(
[mock_proxy_delegate
viewForZoomingInScrollView:[webViewScrollViewProxy_ asUIScrollView]])
.andReturn(mock_view);
EXPECT_TRUE([underlying_scroll_view.delegate
respondsToSelector:@selector(viewForZoomingInScrollView:)]);
EXPECT_EQ(mock_view, [underlying_scroll_view.delegate
viewForZoomingInScrollView:underlying_scroll_view]);
EXPECT_OCMOCK_VERIFY(static_cast<id>(mock_proxy_delegate));
[webViewScrollViewProxy_ setScrollView:nil];
}
// Tests delegate method forwarding to [webViewScrollViewProxy_
// asUIScrollView].delegate when:
// - [webViewScrollViewProxy_ asUIScrollView].delegate is nil
// - CRWWebViewScrollViewDelegateProxy implements the method
//
// Expects that the delegate of the underlying scroll view responds to the
// method but does nothing.
TEST_F(CRWWebViewScrollViewProxyTest,
ProxyDelegateMethodForwardingForImplementedMethodWhenDelegateIsNil) {
UIScrollView* underlying_scroll_view = [[UIScrollView alloc] init];
[webViewScrollViewProxy_ setScrollView:underlying_scroll_view];
[webViewScrollViewProxy_ asUIScrollView].delegate = nil;
EXPECT_TRUE([underlying_scroll_view.delegate
respondsToSelector:@selector(scrollViewWillBeginZooming:withView:)]);
UIView* mock_view = OCMClassMock([UIView class]);
// Expects that nothing happens by calling this.
[underlying_scroll_view.delegate
scrollViewWillBeginZooming:underlying_scroll_view
withView:mock_view];
[webViewScrollViewProxy_ setScrollView:nil];
}
// Tests delegate method forwarding to [webViewScrollViewProxy_
// asUIScrollView].delegate when:
// - [webViewScrollViewProxy_ asUIScrollView].delegate is nil
// - CRWWebViewScrollViewDelegateProxy does *not* implement the method
//
// Expects that the delegate of the underlying scroll view does *not* respond to
// the method.
TEST_F(CRWWebViewScrollViewProxyTest,
ProxyDelegateMethodForwardingForUnimplementedMethodWhenDelegateIsNil) {
UIScrollView* underlying_scroll_view = [[UIScrollView alloc] init];
[webViewScrollViewProxy_ setScrollView:underlying_scroll_view];
[webViewScrollViewProxy_ asUIScrollView].delegate = nil;
EXPECT_FALSE([underlying_scroll_view.delegate
respondsToSelector:@selector(viewForZoomingInScrollView:)]);
[webViewScrollViewProxy_ setScrollView:nil];
}
} // namespace } // namespace
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment