Commit 7cfac2fe authored by Hiroshi Ichikawa's avatar Hiroshi Ichikawa Committed by Commit Bot

Allow CRWWebViewScrollViewProxy to be safely cast to UIScrollView.

There are still some missing parts, which I left as TODO.

Change-Id: I5fbe6e6c4e84bde922fb70ee1c8099d446025b6c
Bug: 1023250
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1906863Reviewed-by: default avatarHiroshi Ichikawa <ichikawa@chromium.org>
Reviewed-by: default avatarEugene But <eugenebut@chromium.org>
Reviewed-by: default avatarGauthier Ambard <gambard@chromium.org>
Commit-Queue: Hiroshi Ichikawa <ichikawa@chromium.org>
Auto-Submit: Hiroshi Ichikawa <ichikawa@chromium.org>
Cr-Commit-Position: refs/heads/master@{#721821}
parent c7ee8316
......@@ -56,20 +56,20 @@ class OverscrollActionsTabHelperTest : public PlatformTest {
// Simulates scroll on the |scroll_view_proxy_| view, which should trigger
// page refresh action.
void SimulatePullForRefreshAction() {
[scroll_view_proxy_ scrollViewWillBeginDragging:ui_scroll_view_];
[ui_scroll_view_.delegate scrollViewWillBeginDragging:ui_scroll_view_];
// Wait until scroll action is allowed. There is no condition to wait, just
// a time period.
base::test::ios::SpinRunLoopWithMinDelay(base::TimeDelta::FromSecondsD(
kMinimumPullDurationToTransitionToReadyInSeconds));
[scroll_view_proxy_ scrollViewDidScroll:ui_scroll_view_];
[ui_scroll_view_.delegate scrollViewDidScroll:ui_scroll_view_];
scroll_view_proxy_.contentOffset = CGPointMake(0, -293);
CGPoint target_offset = CGPointMake(0, -92);
[scroll_view_proxy_ scrollViewWillEndDragging:ui_scroll_view_
withVelocity:CGPointMake(0, -1.5)
targetContentOffset:&target_offset];
[ui_scroll_view_.delegate scrollViewWillEndDragging:ui_scroll_view_
withVelocity:CGPointMake(0, -1.5)
targetContentOffset:&target_offset];
[overscroll_delegate_.headerView layoutIfNeeded];
[scroll_view_proxy_ scrollViewDidEndDragging:ui_scroll_view_
willDecelerate:NO];
[ui_scroll_view_.delegate scrollViewDidEndDragging:ui_scroll_view_
willDecelerate:NO];
}
web::WebTaskEnvironment task_environment_;
......
......@@ -21,7 +21,7 @@
// The class forwards some of the methods onto the UIScrollView. For more
// information look at the UIScrollView documentation.
// TODO(crbug.com/546152): rename class to CRWContentViewScrollViewProxy.
@interface CRWWebViewScrollViewProxy : NSObject <UIScrollViewDelegate>
@interface CRWWebViewScrollViewProxy : NSObject
@property(nonatomic, assign) CGPoint contentOffset;
@property(nonatomic, assign) UIEdgeInsets contentInset;
@property(nonatomic, readonly, getter=isDecelerating) BOOL decelerating;
......@@ -60,6 +60,20 @@
// Removes |observer| as a subscriber for change notifications.
- (void)removeObserver:(id<CRWWebViewScrollViewProxyObserver>)observer;
// Returns a scroll view proxy which can be accessed as UIScrollView.
//
// Note: This method is introduced because CRWWebViewScrollViewProxy cannot
// simply be a subclass of UIScrollView. Its implementation relies on
// -forwardInvocation: which only works for methods not implemented in the class
// (including those implemented in its superclass). So -forwardInvocation:
// cannot forward methods in UIScrollView if CRWWebViewScrollViewProxy is a
// subclass of UIScrollView.
//
// TODO(crbug.com/1023250): Support KVO and delegate of this scroll view.
// TODO(crbug.com/1023250): Restore properties of the scroll view when the
// scroll view is reset.
- (UIScrollView*)asUIScrollView;
@end
// A protocol to be implemented by objects to listen for changes to the
......
......@@ -58,6 +58,9 @@ source_set("ui") {
"crw_web_request_controller.mm",
"crw_web_view_proxy_impl.h",
"crw_web_view_proxy_impl.mm",
"crw_web_view_scroll_view_delegate_proxy.h",
"crw_web_view_scroll_view_delegate_proxy.mm",
"crw_web_view_scroll_view_proxy+internal.h",
"crw_web_view_scroll_view_proxy.mm",
"crw_wk_ui_handler.h",
"crw_wk_ui_handler.mm",
......
// 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.
#ifndef IOS_WEB_WEB_STATE_UI_CRW_WEB_VIEW_SCROLL_VIEW_DELEGATE_PROXY_H_
#define IOS_WEB_WEB_STATE_UI_CRW_WEB_VIEW_SCROLL_VIEW_DELEGATE_PROXY_H_
#import <UIKit/UIKit.h>
@class CRWWebViewScrollViewProxy;
// A delegate object of the UIScrollView managed by CRWWebViewScrollViewProxy.
//
// This class is separated from CRWWebViewScrollViewProxy mainly because both
// of CRWWebViewScrollViewProxy and CRWWebViewScrollViewDelegateProxy use
// -forwardInvocation: to forward unimplemented methods to different objects.
@interface CRWWebViewScrollViewDelegateProxy : NSObject <UIScrollViewDelegate>
- (nonnull instancetype)initWithScrollViewProxy:
(nonnull CRWWebViewScrollViewProxy*)scrollViewProxy
NS_DESIGNATED_INITIALIZER;
- (nonnull instancetype)init NS_UNAVAILABLE;
@end
#endif // IOS_WEB_WEB_STATE_UI_CRW_WEB_VIEW_SCROLL_VIEW_DELEGATE_PROXY_H_
// 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"
#import "base/ios/crb_protocol_observers.h"
#include "base/logging.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) CRWWebViewScrollViewProxy* scrollViewProxy;
@end
// TODO(crbug.com/1023250): Delegate to _scrollViewProxy.delegate as well.
@implementation CRWWebViewScrollViewDelegateProxy
- (instancetype)initWithScrollViewProxy:
(CRWWebViewScrollViewProxy*)scrollViewProxy {
self = [super init];
if (self) {
_scrollViewProxy = scrollViewProxy;
}
return self;
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView*)scrollView {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView);
[self.scrollViewProxy.observers
webViewScrollViewDidScroll:self.scrollViewProxy];
}
- (void)scrollViewWillBeginDragging:(UIScrollView*)scrollView {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView);
[self.scrollViewProxy.observers
webViewScrollViewWillBeginDragging:self.scrollViewProxy];
}
- (void)scrollViewWillEndDragging:(UIScrollView*)scrollView
withVelocity:(CGPoint)velocity
targetContentOffset:(inout CGPoint*)targetContentOffset {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView);
[self.scrollViewProxy.observers
webViewScrollViewWillEndDragging:self.scrollViewProxy
withVelocity:velocity
targetContentOffset:targetContentOffset];
}
- (void)scrollViewDidEndDragging:(UIScrollView*)scrollView
willDecelerate:(BOOL)decelerate {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView);
[self.scrollViewProxy.observers
webViewScrollViewDidEndDragging:self.scrollViewProxy
willDecelerate:decelerate];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView*)scrollView {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView);
[self.scrollViewProxy.observers
webViewScrollViewDidEndDecelerating:self.scrollViewProxy];
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView*)scrollView {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView);
[self.scrollViewProxy.observers
webViewScrollViewDidEndScrollingAnimation:self.scrollViewProxy];
}
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView*)scrollView {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView);
__block BOOL shouldScrollToTop = YES;
[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);
[self.scrollViewProxy.observers
webViewScrollViewDidZoom:self.scrollViewProxy];
}
- (void)scrollViewWillBeginZooming:(UIScrollView*)scrollView
withView:(UIView*)view {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView);
[self.scrollViewProxy.observers
webViewScrollViewWillBeginZooming:self.scrollViewProxy];
}
- (void)scrollViewDidEndZooming:(UIScrollView*)scrollView
withView:(UIView*)view
atScale:(CGFloat)scale {
DCHECK_EQ(self.scrollViewProxy.underlyingScrollView, scrollView);
[self.scrollViewProxy.observers
webViewScrollViewDidEndZooming:self.scrollViewProxy
atScale:scale];
}
@end
// 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.
#ifndef IOS_WEB_WEB_STATE_UI_CRW_WEB_VIEW_SCROLL_VIEW_PROXY_INTERNAL_H_
#define IOS_WEB_WEB_STATE_UI_CRW_WEB_VIEW_SCROLL_VIEW_PROXY_INTERNAL_H_
#import "ios/web/public/ui/crw_web_view_scroll_view_proxy.h"
@class CRBProtocolObservers;
// Declares internal API for this class. This API should only be used in
// //ios/web.
@interface CRWWebViewScrollViewProxy (Internal)
// Observers of this proxy which subscribe to change notifications.
@property(nonatomic, readonly)
CRBProtocolObservers<CRWWebViewScrollViewProxyObserver>* observers;
// The underlying UIScrollView. It can change or become nil.
@property(nonatomic, weak, readonly) UIScrollView* underlyingScrollView;
@end
#endif // IOS_WEB_WEB_STATE_UI_CRW_WEB_VIEW_SCROLL_VIEW_PROXY_INTERNAL_H_
......@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/web/public/ui/crw_web_view_scroll_view_proxy.h"
#import "ios/web/web_state/ui/crw_web_view_scroll_view_proxy+internal.h"
#import <objc/runtime.h>
......@@ -11,19 +11,26 @@
#include "base/auto_reset.h"
#import "base/ios/crb_protocol_observers.h"
#include "base/mac/foundation_util.h"
#import "ios/web/web_state/ui/crw_web_view_scroll_view_delegate_proxy.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface CRWWebViewScrollViewProxy () {
__weak UIScrollView* _scrollView;
id _observers;
std::unique_ptr<UIScrollViewContentInsetAdjustmentBehavior>
_storedContentInsetAdjustmentBehavior API_AVAILABLE(ios(11.0));
std::unique_ptr<BOOL> _storedClipsToBounds;
}
// A delegate object of the UIScrollView managed by this class.
@property(nonatomic, strong, readonly)
CRWWebViewScrollViewDelegateProxy* delegateProxy;
@property(nonatomic, strong)
CRBProtocolObservers<CRWWebViewScrollViewProxyObserver>* observers;
@property(nonatomic, weak) UIScrollView* underlyingScrollView;
// Returns the key paths that need to be observed for UIScrollView.
+ (NSArray*)scrollViewObserverKeyPaths;
......@@ -34,27 +41,38 @@
@end
// Note: An instance of this class must be safely casted to UIScrollView. See
// -asUIScrollView. To make it happen:
// - When this class defines a method with the same selector as in a method of
// UIScrollView (or its ancestor classes), its API and the behavior should
// be consistent with the UIScrollView one's.
// - Calls to UIScrollView methods not implemented in this class are forwarded
// to the underlying UIScrollView by -methodSignatureForSelector: and
// -forwardInvocation:.
@implementation CRWWebViewScrollViewProxy
- (instancetype)init {
self = [super init];
if (self) {
Protocol* protocol = @protocol(CRWWebViewScrollViewProxyObserver);
_observers = [CRBProtocolObservers observersWithProtocol:protocol];
_observers =
static_cast<CRBProtocolObservers<CRWWebViewScrollViewProxyObserver>*>(
[CRBProtocolObservers observersWithProtocol:protocol]);
_delegateProxy = [[CRWWebViewScrollViewDelegateProxy alloc]
initWithScrollViewProxy:self];
}
return self;
}
- (void)dealloc {
[self stopObservingScrollView:_scrollView];
[self stopObservingScrollView:self.underlyingScrollView];
}
- (void)addGestureRecognizer:(UIGestureRecognizer*)gestureRecognizer {
[_scrollView addGestureRecognizer:gestureRecognizer];
[self.underlyingScrollView addGestureRecognizer:gestureRecognizer];
}
- (void)removeGestureRecognizer:(UIGestureRecognizer*)gestureRecognizer {
[_scrollView removeGestureRecognizer:gestureRecognizer];
[self.underlyingScrollView removeGestureRecognizer:gestureRecognizer];
}
- (void)addObserver:(id<CRWWebViewScrollViewProxyObserver>)observer {
......@@ -66,14 +84,14 @@
}
- (void)setScrollView:(UIScrollView*)scrollView {
if (_scrollView == scrollView)
if (self.underlyingScrollView == scrollView)
return;
[_scrollView setDelegate:nil];
[self stopObservingScrollView:_scrollView];
[self.underlyingScrollView setDelegate:nil];
[self stopObservingScrollView:self.underlyingScrollView];
DCHECK(!scrollView.delegate);
scrollView.delegate = self;
scrollView.delegate = self.delegateProxy;
[self startObservingScrollView:scrollView];
_scrollView = scrollView;
self.underlyingScrollView = scrollView;
if (_storedClipsToBounds) {
scrollView.clipsToBounds = *_storedClipsToBounds;
}
......@@ -81,7 +99,7 @@
// Assigns |contentInsetAdjustmentBehavior| which was set before setting the
// scroll view.
if (_storedContentInsetAdjustmentBehavior) {
_scrollView.contentInsetAdjustmentBehavior =
self.underlyingScrollView.contentInsetAdjustmentBehavior =
*_storedContentInsetAdjustmentBehavior;
}
......@@ -89,105 +107,111 @@
}
- (CGRect)frame {
return _scrollView ? [_scrollView frame] : CGRectZero;
return self.underlyingScrollView ? [self.underlyingScrollView frame]
: CGRectZero;
}
- (BOOL)isScrollEnabled {
return [_scrollView isScrollEnabled];
return [self.underlyingScrollView isScrollEnabled];
}
- (void)setScrollEnabled:(BOOL)scrollEnabled {
[_scrollView setScrollEnabled:scrollEnabled];
[self.underlyingScrollView setScrollEnabled:scrollEnabled];
}
- (BOOL)bounces {
return [_scrollView bounces];
return [self.underlyingScrollView bounces];
}
- (void)setBounces:(BOOL)bounces {
[_scrollView setBounces:bounces];
[self.underlyingScrollView setBounces:bounces];
}
- (BOOL)clipsToBounds {
if (!_scrollView && _storedClipsToBounds) {
if (!self.underlyingScrollView && _storedClipsToBounds) {
return *_storedClipsToBounds;
}
return _scrollView.clipsToBounds;
return self.underlyingScrollView.clipsToBounds;
}
- (void)setClipsToBounds:(BOOL)clipsToBounds {
_storedClipsToBounds = std::make_unique<BOOL>(clipsToBounds);
_scrollView.clipsToBounds = clipsToBounds;
self.underlyingScrollView.clipsToBounds = clipsToBounds;
}
- (BOOL)isDecelerating {
return [_scrollView isDecelerating];
return [self.underlyingScrollView isDecelerating];
}
- (BOOL)isDragging {
return [_scrollView isDragging];
return [self.underlyingScrollView isDragging];
}
- (BOOL)isTracking {
return [_scrollView isTracking];
return [self.underlyingScrollView isTracking];
}
- (BOOL)isZooming {
return [_scrollView isZooming];
return [self.underlyingScrollView isZooming];
}
- (CGFloat)zoomScale {
return [_scrollView zoomScale];
return [self.underlyingScrollView zoomScale];
}
- (void)setContentOffset:(CGPoint)contentOffset {
[_scrollView setContentOffset:contentOffset];
[self.underlyingScrollView setContentOffset:contentOffset];
}
- (CGPoint)contentOffset {
return _scrollView ? [_scrollView contentOffset] : CGPointZero;
return self.underlyingScrollView ? [self.underlyingScrollView contentOffset]
: CGPointZero;
}
- (void)setContentInset:(UIEdgeInsets)contentInset {
[_scrollView setContentInset:contentInset];
[self.underlyingScrollView setContentInset:contentInset];
}
- (UIEdgeInsets)contentInset {
return _scrollView ? [_scrollView contentInset] : UIEdgeInsetsZero;
return self.underlyingScrollView ? [self.underlyingScrollView contentInset]
: UIEdgeInsetsZero;
}
- (void)setScrollIndicatorInsets:(UIEdgeInsets)scrollIndicatorInsets {
[_scrollView setScrollIndicatorInsets:scrollIndicatorInsets];
[self.underlyingScrollView setScrollIndicatorInsets:scrollIndicatorInsets];
}
- (UIEdgeInsets)scrollIndicatorInsets {
return _scrollView ? [_scrollView scrollIndicatorInsets] : UIEdgeInsetsZero;
return self.underlyingScrollView
? [self.underlyingScrollView scrollIndicatorInsets]
: UIEdgeInsetsZero;
}
- (void)setContentSize:(CGSize)contentSize {
[_scrollView setContentSize:contentSize];
[self.underlyingScrollView setContentSize:contentSize];
}
- (CGSize)contentSize {
return _scrollView ? [_scrollView contentSize] : CGSizeZero;
return self.underlyingScrollView ? [self.underlyingScrollView contentSize]
: CGSizeZero;
}
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated {
[_scrollView setContentOffset:contentOffset animated:animated];
[self.underlyingScrollView setContentOffset:contentOffset animated:animated];
}
- (BOOL)scrollsToTop {
return [_scrollView scrollsToTop];
return [self.underlyingScrollView scrollsToTop];
}
- (void)setScrollsToTop:(BOOL)scrollsToTop {
[_scrollView setScrollsToTop:scrollsToTop];
[self.underlyingScrollView setScrollsToTop:scrollsToTop];
}
- (UIScrollViewContentInsetAdjustmentBehavior)contentInsetAdjustmentBehavior
API_AVAILABLE(ios(11.0)) {
if (_scrollView) {
return [_scrollView contentInsetAdjustmentBehavior];
if (self.underlyingScrollView) {
return [self.underlyingScrollView contentInsetAdjustmentBehavior];
} else if (_storedContentInsetAdjustmentBehavior) {
return *_storedContentInsetAdjustmentBehavior;
} else {
......@@ -196,13 +220,13 @@
}
- (UIEdgeInsets)adjustedContentInset API_AVAILABLE(ios(11.0)) {
return [_scrollView adjustedContentInset];
return [self.underlyingScrollView adjustedContentInset];
}
- (void)setContentInsetAdjustmentBehavior:
(UIScrollViewContentInsetAdjustmentBehavior)contentInsetAdjustmentBehavior
API_AVAILABLE(ios(11.0)) {
[_scrollView
[self.underlyingScrollView
setContentInsetAdjustmentBehavior:contentInsetAdjustmentBehavior];
_storedContentInsetAdjustmentBehavior =
std::make_unique<UIScrollViewContentInsetAdjustmentBehavior>(
......@@ -210,84 +234,15 @@
}
- (UIPanGestureRecognizer*)panGestureRecognizer {
return [_scrollView panGestureRecognizer];
return [self.underlyingScrollView panGestureRecognizer];
}
- (NSArray*)gestureRecognizers {
return [_scrollView gestureRecognizers];
return [self.underlyingScrollView gestureRecognizers];
}
- (NSArray<__kindof UIView*>*)subviews {
return _scrollView ? [_scrollView subviews] : @[];
}
#pragma mark -
#pragma mark UIScrollViewDelegate callbacks
- (void)scrollViewDidScroll:(UIScrollView*)scrollView {
DCHECK_EQ(_scrollView, scrollView);
[_observers webViewScrollViewDidScroll:self];
}
- (void)scrollViewWillBeginDragging:(UIScrollView*)scrollView {
DCHECK_EQ(_scrollView, scrollView);
[_observers webViewScrollViewWillBeginDragging:self];
}
- (void)scrollViewWillEndDragging:(UIScrollView*)scrollView
withVelocity:(CGPoint)velocity
targetContentOffset:(inout CGPoint*)targetContentOffset {
DCHECK_EQ(_scrollView, scrollView);
[_observers webViewScrollViewWillEndDragging:self
withVelocity:velocity
targetContentOffset:targetContentOffset];
}
- (void)scrollViewDidEndDragging:(UIScrollView*)scrollView
willDecelerate:(BOOL)decelerate {
DCHECK_EQ(_scrollView, scrollView);
[_observers webViewScrollViewDidEndDragging:self willDecelerate:decelerate];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView*)scrollView {
DCHECK_EQ(_scrollView, scrollView);
[_observers webViewScrollViewDidEndDecelerating:self];
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView*)scrollView {
DCHECK_EQ(_scrollView, scrollView);
[_observers webViewScrollViewDidEndScrollingAnimation:self];
}
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView*)scrollView {
DCHECK_EQ(_scrollView, scrollView);
__block BOOL shouldScrollToTop = YES;
[_observers executeOnObservers:^(id observer) {
if ([observer respondsToSelector:@selector
(webViewScrollViewShouldScrollToTop:)]) {
shouldScrollToTop = shouldScrollToTop &&
[observer webViewScrollViewShouldScrollToTop:self];
}
}];
return shouldScrollToTop;
}
- (void)scrollViewDidZoom:(UIScrollView*)scrollView {
DCHECK_EQ(_scrollView, scrollView);
[_observers webViewScrollViewDidZoom:self];
}
- (void)scrollViewWillBeginZooming:(UIScrollView*)scrollView
withView:(UIView*)view {
DCHECK_EQ(_scrollView, scrollView);
[_observers webViewScrollViewWillBeginZooming:self];
}
- (void)scrollViewDidEndZooming:(UIScrollView*)scrollView
withView:(UIView*)view
atScale:(CGFloat)scale {
DCHECK_EQ(_scrollView, scrollView);
[_observers webViewScrollViewDidEndZooming:self atScale:scale];
return self.underlyingScrollView ? [self.underlyingScrollView subviews] : @[];
}
#pragma mark -
......@@ -310,7 +265,7 @@
ofObject:(id)object
change:(NSDictionary*)change
context:(void*)context {
DCHECK_EQ(object, _scrollView);
DCHECK_EQ(object, self.underlyingScrollView);
if ([keyPath isEqualToString:@"frame"])
[_observers webViewScrollViewFrameDidChange:self];
if ([keyPath isEqualToString:@"contentSize"])
......@@ -319,4 +274,41 @@
[_observers webViewScrollViewDidResetContentInset:self];
}
- (UIScrollView*)asUIScrollView {
// See the comment of @implementation of this class for why this should be
// safe.
return (UIScrollView*)self;
}
#pragma mark - Forwards unimplemented UIScrollView methods
- (nullable NSMethodSignature*)methodSignatureForSelector:(SEL)sel {
// Called when this proxy is accessed through -asUIScrollView and the method
// is not implemented in this class. Do not call [self.underlyingScrollView
// methodSignatureForSelector:] here instead because self.underlyingScrollView
// may be nil.
return [UIScrollView instanceMethodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation*)invocation {
// Called when this proxy is accessed through -asUIScrollView and the method
// is not implemented in this class. self.underlyingScrollView may be nil, but
// it is safe. nil can respond to any invocation (and does nothing).
[invocation invokeWithTarget:self.underlyingScrollView];
}
#pragma mark - NSObject
- (BOOL)isKindOfClass:(Class)aClass {
// Pretend self to be a kind of UIScrollView.
return
[UIScrollView isSubclassOfClass:aClass] || [super isKindOfClass:aClass];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
// Respond to both of UIScrollView methods and its own methods.
return [UIScrollView instancesRespondToSelector:aSelector] ||
[super respondsToSelector:aSelector];
}
@end
......@@ -7,6 +7,7 @@
#import <UIKit/UIKit.h>
#include "base/compiler_specific.h"
#import "ios/web/web_state/ui/crw_web_view_scroll_view_delegate_proxy.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
......@@ -16,6 +17,14 @@
#error "This file requires ARC support."
#endif
// TODO(crbug.com/1030168): Rewrite tests Delegate, MultipleScrollView,
// DelegateClearingUp not to depend on this, and delete this.
@interface CRWWebViewScrollViewProxy (Testing)
@property(nonatomic, readonly) CRWWebViewScrollViewDelegateProxy* delegateProxy;
@end
namespace {
class CRWWebViewScrollViewProxyTest : public PlatformTest {
......@@ -34,7 +43,7 @@ class CRWWebViewScrollViewProxyTest : public PlatformTest {
// Tests that the UIScrollViewDelegate is set correctly.
TEST_F(CRWWebViewScrollViewProxyTest, Delegate) {
[static_cast<UIScrollView*>([mockScrollView_ expect])
setDelegate:webViewScrollViewProxy_];
setDelegate:webViewScrollViewProxy_.delegateProxy];
[webViewScrollViewProxy_ setScrollView:mockScrollView_];
EXPECT_OCMOCK_VERIFY(mockScrollView_);
}
......@@ -47,7 +56,7 @@ TEST_F(CRWWebViewScrollViewProxyTest, MultipleScrollView) {
[webViewScrollViewProxy_ setScrollView:mockScrollView1];
[webViewScrollViewProxy_ setScrollView:mockScrollView2];
EXPECT_FALSE([mockScrollView1 delegate]);
EXPECT_EQ(webViewScrollViewProxy_, [mockScrollView2 delegate]);
EXPECT_EQ(webViewScrollViewProxy_.delegateProxy, [mockScrollView2 delegate]);
[webViewScrollViewProxy_ setScrollView:nil];
}
......@@ -56,7 +65,7 @@ TEST_F(CRWWebViewScrollViewProxyTest, MultipleScrollView) {
TEST_F(CRWWebViewScrollViewProxyTest, DelegateClearingUp) {
UIScrollView* mockScrollView1 = [[UIScrollView alloc] init];
[webViewScrollViewProxy_ setScrollView:mockScrollView1];
EXPECT_EQ(webViewScrollViewProxy_, [mockScrollView1 delegate]);
EXPECT_EQ(webViewScrollViewProxy_.delegateProxy, [mockScrollView1 delegate]);
[webViewScrollViewProxy_ setScrollView:nil];
EXPECT_FALSE([mockScrollView1 delegate]);
}
......@@ -300,4 +309,84 @@ TEST_F(CRWWebViewScrollViewProxyTest, ContentInsetDidChange) {
[webViewScrollViewProxy_ setScrollView:nil];
}
// Verifies that method calls to -asUIScrollView are simply forwarded to the
// underlying scroll view if the method is not implemented in
// CRWWebViewScrollViewProxy.
TEST_F(CRWWebViewScrollViewProxyTest, AsUIScrollViewWithUnderlyingScrollView) {
[webViewScrollViewProxy_ setScrollView:mockScrollView_];
// Verifies that a return value is properly propagated.
// -viewPrintFormatter is not implemented in CRWWebViewScrollViewProxy.
UIViewPrintFormatter* print_formatter_mock =
OCMClassMock([UIViewPrintFormatter class]);
OCMStub([mockScrollView_ viewPrintFormatter]).andReturn(print_formatter_mock);
EXPECT_EQ(print_formatter_mock,
[[webViewScrollViewProxy_ asUIScrollView] viewPrintFormatter]);
// Verifies that a parameter is properly propagated.
// -drawRect: is not implemented in CRWWebViewScrollViewProxy.
CGRect rect = CGRectMake(0, 0, 1, 1);
OCMExpect([mockScrollView_ drawRect:rect]);
[[webViewScrollViewProxy_ asUIScrollView] drawRect:rect];
EXPECT_OCMOCK_VERIFY((id)mockScrollView_);
[webViewScrollViewProxy_ setScrollView:nil];
}
// Verifies that method calls to -asUIScrollView are no-op if the underlying
// scroll view is nil and the method is not implemented in
// CRWWebViewScrollViewProxy.
TEST_F(CRWWebViewScrollViewProxyTest,
AsUIScrollViewWithoutUnderlyingScrollView) {
[webViewScrollViewProxy_ setScrollView:nil];
// Any methods should return nil when the underlying scroll view is nil.
EXPECT_EQ(nil, [[webViewScrollViewProxy_ asUIScrollView] viewPrintFormatter]);
// It is expected that nothing happens. Just verifies that it doesn't crash.
CGRect rect = CGRectMake(0, 0, 1, 1);
[[webViewScrollViewProxy_ asUIScrollView] drawRect:rect];
}
// Verify that -[CRWWebViewScrollViewProxy isKindOfClass:] works as expected.
TEST_F(CRWWebViewScrollViewProxyTest, IsKindOfClass) {
// The proxy is a kind of its own class.
EXPECT_TRUE([webViewScrollViewProxy_
isKindOfClass:[CRWWebViewScrollViewProxy class]]);
// The proxy prentends itself to be a kind of UIScrollView.
EXPECT_TRUE([webViewScrollViewProxy_ isKindOfClass:[UIScrollView class]]);
// It should return YES for ancestor classes of UIScrollView.
EXPECT_TRUE([webViewScrollViewProxy_ isKindOfClass:[UIView class]]);
// Returns NO if none of above applies.
EXPECT_FALSE([webViewScrollViewProxy_ isKindOfClass:[NSString class]]);
}
// Verify that -[CRWWebViewScrollViewProxy respondsToSelector:] works as
// expected.
TEST_F(CRWWebViewScrollViewProxyTest, RespondsToSelector) {
// A method defined in CRWWebViewScrollViewProxy but not in UIScrollView.
EXPECT_TRUE(
[webViewScrollViewProxy_ respondsToSelector:@selector(addObserver:)]);
// A method defined in CRWWebViewScrollViewProxy and also in UIScrollView.
EXPECT_TRUE(
[webViewScrollViewProxy_ respondsToSelector:@selector(contentOffset)]);
// A method defined in UIScrollView but not in CRWWebViewScrollViewProxy.
EXPECT_TRUE(
[webViewScrollViewProxy_ respondsToSelector:@selector(indexDisplayMode)]);
// A method defined in UIView (a superclass of UIScrollView) but not in
// CRWWebViewScrollViewProxy.
EXPECT_TRUE([webViewScrollViewProxy_
respondsToSelector:@selector(viewPrintFormatter)]);
// A method defined in none of above.
EXPECT_FALSE(
[webViewScrollViewProxy_ respondsToSelector:@selector(containsString:)]);
}
} // namespace
......@@ -42,27 +42,28 @@ TEST_F(CWVScrollViewTest, DelegateCallbacks) {
scroll_view_.delegate = delegate;
[[delegate expect] scrollViewWillBeginDragging:scroll_view_];
[scroll_view_proxy_ scrollViewWillBeginDragging:ui_scroll_view_];
[ui_scroll_view_.delegate scrollViewWillBeginDragging:ui_scroll_view_];
CGPoint targetContentOffset;
[[delegate expect] scrollViewWillEndDragging:scroll_view_
withVelocity:CGPointZero
targetContentOffset:&targetContentOffset];
[scroll_view_proxy_ scrollViewWillEndDragging:ui_scroll_view_
withVelocity:CGPointZero
targetContentOffset:&targetContentOffset];
[ui_scroll_view_.delegate scrollViewWillEndDragging:ui_scroll_view_
withVelocity:CGPointZero
targetContentOffset:&targetContentOffset];
[[delegate expect] scrollViewDidScroll:scroll_view_];
[scroll_view_proxy_ scrollViewDidScroll:ui_scroll_view_];
[ui_scroll_view_.delegate scrollViewDidScroll:ui_scroll_view_];
[[delegate expect] scrollViewDidEndDecelerating:scroll_view_];
[scroll_view_proxy_ scrollViewDidEndDecelerating:ui_scroll_view_];
[ui_scroll_view_.delegate scrollViewDidEndDecelerating:ui_scroll_view_];
[[delegate expect] scrollViewShouldScrollToTop:scroll_view_];
[scroll_view_proxy_ scrollViewShouldScrollToTop:ui_scroll_view_];
[ui_scroll_view_.delegate scrollViewShouldScrollToTop:ui_scroll_view_];
[[delegate expect] scrollViewWillBeginZooming:scroll_view_];
[scroll_view_proxy_ scrollViewWillBeginZooming:ui_scroll_view_ withView:nil];
[ui_scroll_view_.delegate scrollViewWillBeginZooming:ui_scroll_view_
withView:nil];
[delegate verify];
}
......
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