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 { ...@@ -56,20 +56,20 @@ class OverscrollActionsTabHelperTest : public PlatformTest {
// Simulates scroll on the |scroll_view_proxy_| view, which should trigger // Simulates scroll on the |scroll_view_proxy_| view, which should trigger
// page refresh action. // page refresh action.
void SimulatePullForRefreshAction() { 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 // Wait until scroll action is allowed. There is no condition to wait, just
// a time period. // a time period.
base::test::ios::SpinRunLoopWithMinDelay(base::TimeDelta::FromSecondsD( base::test::ios::SpinRunLoopWithMinDelay(base::TimeDelta::FromSecondsD(
kMinimumPullDurationToTransitionToReadyInSeconds)); kMinimumPullDurationToTransitionToReadyInSeconds));
[scroll_view_proxy_ scrollViewDidScroll:ui_scroll_view_]; [ui_scroll_view_.delegate scrollViewDidScroll:ui_scroll_view_];
scroll_view_proxy_.contentOffset = CGPointMake(0, -293); scroll_view_proxy_.contentOffset = CGPointMake(0, -293);
CGPoint target_offset = CGPointMake(0, -92); CGPoint target_offset = CGPointMake(0, -92);
[scroll_view_proxy_ scrollViewWillEndDragging:ui_scroll_view_ [ui_scroll_view_.delegate scrollViewWillEndDragging:ui_scroll_view_
withVelocity:CGPointMake(0, -1.5) withVelocity:CGPointMake(0, -1.5)
targetContentOffset:&target_offset]; targetContentOffset:&target_offset];
[overscroll_delegate_.headerView layoutIfNeeded]; [overscroll_delegate_.headerView layoutIfNeeded];
[scroll_view_proxy_ scrollViewDidEndDragging:ui_scroll_view_ [ui_scroll_view_.delegate scrollViewDidEndDragging:ui_scroll_view_
willDecelerate:NO]; willDecelerate:NO];
} }
web::WebTaskEnvironment task_environment_; web::WebTaskEnvironment task_environment_;
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
// The class forwards some of the methods onto the UIScrollView. For more // The class forwards some of the methods onto the UIScrollView. For more
// information look at the UIScrollView documentation. // information look at the UIScrollView documentation.
// TODO(crbug.com/546152): rename class to CRWContentViewScrollViewProxy. // TODO(crbug.com/546152): rename class to CRWContentViewScrollViewProxy.
@interface CRWWebViewScrollViewProxy : NSObject <UIScrollViewDelegate> @interface CRWWebViewScrollViewProxy : NSObject
@property(nonatomic, assign) CGPoint contentOffset; @property(nonatomic, assign) CGPoint contentOffset;
@property(nonatomic, assign) UIEdgeInsets contentInset; @property(nonatomic, assign) UIEdgeInsets contentInset;
@property(nonatomic, readonly, getter=isDecelerating) BOOL decelerating; @property(nonatomic, readonly, getter=isDecelerating) BOOL decelerating;
...@@ -60,6 +60,20 @@ ...@@ -60,6 +60,20 @@
// Removes |observer| as a subscriber for change notifications. // Removes |observer| as a subscriber for change notifications.
- (void)removeObserver:(id<CRWWebViewScrollViewProxyObserver>)observer; - (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 @end
// A protocol to be implemented by objects to listen for changes to the // A protocol to be implemented by objects to listen for changes to the
......
...@@ -58,6 +58,9 @@ source_set("ui") { ...@@ -58,6 +58,9 @@ source_set("ui") {
"crw_web_request_controller.mm", "crw_web_request_controller.mm",
"crw_web_view_proxy_impl.h", "crw_web_view_proxy_impl.h",
"crw_web_view_proxy_impl.mm", "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_web_view_scroll_view_proxy.mm",
"crw_wk_ui_handler.h", "crw_wk_ui_handler.h",
"crw_wk_ui_handler.mm", "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_
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#include "base/compiler_specific.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/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h" #include "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h" #import "third_party/ocmock/OCMock/OCMock.h"
...@@ -16,6 +17,14 @@ ...@@ -16,6 +17,14 @@
#error "This file requires ARC support." #error "This file requires ARC support."
#endif #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 { namespace {
class CRWWebViewScrollViewProxyTest : public PlatformTest { class CRWWebViewScrollViewProxyTest : public PlatformTest {
...@@ -34,7 +43,7 @@ class CRWWebViewScrollViewProxyTest : public PlatformTest { ...@@ -34,7 +43,7 @@ class CRWWebViewScrollViewProxyTest : public PlatformTest {
// Tests that the UIScrollViewDelegate is set correctly. // Tests that the UIScrollViewDelegate is set correctly.
TEST_F(CRWWebViewScrollViewProxyTest, Delegate) { TEST_F(CRWWebViewScrollViewProxyTest, Delegate) {
[static_cast<UIScrollView*>([mockScrollView_ expect]) [static_cast<UIScrollView*>([mockScrollView_ expect])
setDelegate:webViewScrollViewProxy_]; setDelegate:webViewScrollViewProxy_.delegateProxy];
[webViewScrollViewProxy_ setScrollView:mockScrollView_]; [webViewScrollViewProxy_ setScrollView:mockScrollView_];
EXPECT_OCMOCK_VERIFY(mockScrollView_); EXPECT_OCMOCK_VERIFY(mockScrollView_);
} }
...@@ -47,7 +56,7 @@ TEST_F(CRWWebViewScrollViewProxyTest, MultipleScrollView) { ...@@ -47,7 +56,7 @@ TEST_F(CRWWebViewScrollViewProxyTest, MultipleScrollView) {
[webViewScrollViewProxy_ setScrollView:mockScrollView1]; [webViewScrollViewProxy_ setScrollView:mockScrollView1];
[webViewScrollViewProxy_ setScrollView:mockScrollView2]; [webViewScrollViewProxy_ setScrollView:mockScrollView2];
EXPECT_FALSE([mockScrollView1 delegate]); EXPECT_FALSE([mockScrollView1 delegate]);
EXPECT_EQ(webViewScrollViewProxy_, [mockScrollView2 delegate]); EXPECT_EQ(webViewScrollViewProxy_.delegateProxy, [mockScrollView2 delegate]);
[webViewScrollViewProxy_ setScrollView:nil]; [webViewScrollViewProxy_ setScrollView:nil];
} }
...@@ -56,7 +65,7 @@ TEST_F(CRWWebViewScrollViewProxyTest, MultipleScrollView) { ...@@ -56,7 +65,7 @@ TEST_F(CRWWebViewScrollViewProxyTest, MultipleScrollView) {
TEST_F(CRWWebViewScrollViewProxyTest, DelegateClearingUp) { TEST_F(CRWWebViewScrollViewProxyTest, DelegateClearingUp) {
UIScrollView* mockScrollView1 = [[UIScrollView alloc] init]; UIScrollView* mockScrollView1 = [[UIScrollView alloc] init];
[webViewScrollViewProxy_ setScrollView:mockScrollView1]; [webViewScrollViewProxy_ setScrollView:mockScrollView1];
EXPECT_EQ(webViewScrollViewProxy_, [mockScrollView1 delegate]); EXPECT_EQ(webViewScrollViewProxy_.delegateProxy, [mockScrollView1 delegate]);
[webViewScrollViewProxy_ setScrollView:nil]; [webViewScrollViewProxy_ setScrollView:nil];
EXPECT_FALSE([mockScrollView1 delegate]); EXPECT_FALSE([mockScrollView1 delegate]);
} }
...@@ -300,4 +309,84 @@ TEST_F(CRWWebViewScrollViewProxyTest, ContentInsetDidChange) { ...@@ -300,4 +309,84 @@ TEST_F(CRWWebViewScrollViewProxyTest, ContentInsetDidChange) {
[webViewScrollViewProxy_ setScrollView:nil]; [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 } // namespace
...@@ -42,27 +42,28 @@ TEST_F(CWVScrollViewTest, DelegateCallbacks) { ...@@ -42,27 +42,28 @@ TEST_F(CWVScrollViewTest, DelegateCallbacks) {
scroll_view_.delegate = delegate; scroll_view_.delegate = delegate;
[[delegate expect] scrollViewWillBeginDragging:scroll_view_]; [[delegate expect] scrollViewWillBeginDragging:scroll_view_];
[scroll_view_proxy_ scrollViewWillBeginDragging:ui_scroll_view_]; [ui_scroll_view_.delegate scrollViewWillBeginDragging:ui_scroll_view_];
CGPoint targetContentOffset; CGPoint targetContentOffset;
[[delegate expect] scrollViewWillEndDragging:scroll_view_ [[delegate expect] scrollViewWillEndDragging:scroll_view_
withVelocity:CGPointZero withVelocity:CGPointZero
targetContentOffset:&targetContentOffset]; targetContentOffset:&targetContentOffset];
[scroll_view_proxy_ scrollViewWillEndDragging:ui_scroll_view_ [ui_scroll_view_.delegate scrollViewWillEndDragging:ui_scroll_view_
withVelocity:CGPointZero withVelocity:CGPointZero
targetContentOffset:&targetContentOffset]; targetContentOffset:&targetContentOffset];
[[delegate expect] scrollViewDidScroll:scroll_view_]; [[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_]; [[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_]; [[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_]; [[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]; [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