// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/ui/main_content/web_scroll_view_main_content_ui_forwarder.h"
#import <memory>
#import "base/check.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list_observer_bridge.h"
#import "ios/chrome/browser/ui/main_content/main_content_ui_state.h"
#import "ios/web/public/navigation/navigation_context.h"
#import "ios/web/public/ui/crw_web_view_proxy.h"
#import "ios/web/public/ui/crw_web_view_scroll_view_proxy.h"
#import "ios/web/public/web_state.h"
#import "ios/web/public/web_state_observer_bridge.h"
namespace {
// Uses the current values of `proxy`'s properties to update the
// MainContentUIState via `updater`.
void UpdateStateWithProxy(MainContentUIStateUpdater* updater,
CRWWebViewScrollViewProxy* proxy) {
[updater scrollViewSizeDidChange:proxy.frame.size];
[updater scrollViewDidResetContentSize:proxy.contentSize];
[updater scrollViewDidResetContentInset:proxy.contentInset];
}
}
@interface WebScrollViewMainContentUIForwarder ()<
CRWWebStateObserver,
CRWWebViewScrollViewProxyObserver,
WebStateListObserving> {
// The observer bridges.
std::unique_ptr<WebStateListObserver> _webStateListBridge;
std::unique_ptr<web::WebStateObserver> _webStateBridge;
}
// The updater being driven by this object.
@property(nonatomic, readonly, strong) MainContentUIStateUpdater* updater;
// The WebStateList whose active WebState's scroll state is being forwaded.
@property(nonatomic, readonly) WebStateList* webStateList;
// The WebStateList's active WebState.
@property(nonatomic, assign) web::WebState* webState;
// The scroll view proxy whose scroll events are forwarded to `updater`.
@property(nonatomic, readonly, strong) CRWWebViewScrollViewProxy* proxy;
@end
@implementation WebScrollViewMainContentUIForwarder
@synthesize updater = _updater;
@synthesize webStateList = _webStateList;
@synthesize webState = _webState;
@synthesize proxy = _proxy;
- (instancetype)initWithUpdater:(MainContentUIStateUpdater*)updater
webStateList:(WebStateList*)webStateList {
if ((self = [super init])) {
_updater = updater;
DCHECK(_updater);
_webStateList = webStateList;
DCHECK(_webStateList);
_webStateBridge = std::make_unique<web::WebStateObserverBridge>(self);
_webStateListBridge = std::make_unique<WebStateListObserverBridge>(self);
_webStateList->AddObserver(_webStateListBridge.get());
web::WebState* activeWebState = webStateList->GetActiveWebState();
if (activeWebState) {
_webState = activeWebState;
_webState->AddObserver(_webStateBridge.get());
_proxy = activeWebState->GetWebViewProxy().scrollViewProxy;
[_proxy addObserver:self];
UpdateStateWithProxy(_updater, _proxy);
}
}
return self;
}
- (void)dealloc {
// `-disconnect` must be called before deallocation.
DCHECK(!_webStateListBridge);
DCHECK(!_webStateBridge);
DCHECK(!_webState);
DCHECK(!_proxy);
}
#pragma mark Accessors
- (void)setWebState:(web::WebState*)webState {
if (_webState == webState)
return;
if (_webState)
_webState->RemoveObserver(_webStateBridge.get());
_webState = webState;
if (_webState)
_webState->AddObserver(_webStateBridge.get());
self.proxy =
_webState ? _webState->GetWebViewProxy().scrollViewProxy : nullptr;
}
- (void)setProxy:(CRWWebViewScrollViewProxy*)proxy {
if (_proxy == proxy)
return;
[_proxy removeObserver:self];
_proxy = proxy;
[_proxy addObserver:self];
UpdateStateWithProxy(_updater, _proxy);
}
#pragma mark Public
- (void)disconnect {
self.webStateList->RemoveObserver(_webStateListBridge.get());
_webStateListBridge = nullptr;
self.webState = nullptr;
_webStateBridge = nullptr;
}
#pragma mark CRWWebStateObserver
- (void)webState:(web::WebState*)webState
didFinishNavigation:(web::NavigationContext*)navigation {
if (!navigation->IsSameDocument())
[self.updater scrollWasInterrupted];
}
#pragma mark CRWWebViewScrollViewObserver
- (void)webViewScrollViewFrameDidChange:
(CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
// Frame changes may move the scroll view relative to its safe area, so check
// for content inset adjustments.
[self checkForContentInsetAdjustment];
[self.updater scrollViewSizeDidChange:webViewScrollViewProxy.frame.size];
}
- (void)webViewScrollViewDidScroll:
(CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
// Check whether this scroll is due to a content inset adjustment.
[self checkForContentInsetAdjustment];
[self.updater scrollViewDidScrollToOffset:self.proxy.contentOffset];
}
- (void)webViewScrollViewWillBeginDragging:
(CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
[self.updater
scrollViewWillBeginDraggingWithGesture:self.proxy.panGestureRecognizer];
}
- (void)webViewScrollViewWillEndDragging:
(CRWWebViewScrollViewProxy*)webViewScrollViewProxy
withVelocity:(CGPoint)velocity
targetContentOffset:(inout CGPoint*)targetContentOffset {
[self.updater
scrollViewDidEndDraggingWithGesture:self.proxy.panGestureRecognizer
targetContentOffset:*targetContentOffset];
}
- (void)webViewScrollViewDidEndDecelerating:
(CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
[self.updater scrollViewDidEndDecelerating];
}
- (void)webViewScrollViewDidResetContentSize:
(CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
[self.updater
scrollViewDidResetContentSize:webViewScrollViewProxy.contentSize];
}
- (void)webViewScrollViewDidResetContentInset:
(CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
[self checkForContentInsetAdjustment];
}
#pragma mark - WebStateListObserving
- (void)didChangeWebStateList:(WebStateList*)webStateList
change:(const WebStateListChange&)change
status:(const WebStateListStatus&)status {
if (status.active_web_state_change()) {
self.webState = status.new_active_web_state;
}
}
#pragma mark - Private
// Checks whether the content inset has been updated, notifying the updater of
// any changes.
- (void)checkForContentInsetAdjustment {
UIEdgeInsets inset = self.proxy.contentInset;
inset = self.proxy.adjustedContentInset;
if (!UIEdgeInsetsEqualToEdgeInsets(inset, self.updater.state.contentInset))
[self.updater scrollViewDidResetContentInset:inset];
}
@end