// Copyright 2013 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/web/web_state/ui/crw_web_view_proxy_impl.h"
#import "base/check.h"
#import "ios/web/common/crw_content_view.h"
#import "ios/web/public/ui/crw_web_view_scroll_view_proxy.h"
#import "ios/web/web_state/ui/crw_web_controller.h"
namespace {
// Returns the first responder in the subviews of `view`, or nil if no view in
// the subtree is the first responder.
UIView* GetFirstResponderSubview(UIView* view) {
if ([view isFirstResponder])
return view;
for (UIView* subview in [view subviews]) {
UIView* firstResponder = GetFirstResponderSubview(subview);
if (firstResponder)
return firstResponder;
}
return nil;
}
} // namespace
@interface CRWWebViewScrollViewProxy (ContentInsetsAlgebra)
// Adds the given insets to the current content insets and scroll indicator
// insets of the receiver.
- (void)cr_addInsets:(UIEdgeInsets)insets;
// Removes the given insets to the current content insets and scroll indicator
// insets of the receiver.
- (void)cr_removeInsets:(UIEdgeInsets)insets;
@end
@implementation CRWWebViewScrollViewProxy (ContentInsetsAlgebra)
- (void)cr_addInsets:(UIEdgeInsets)insets {
if (UIEdgeInsetsEqualToEdgeInsets(insets, UIEdgeInsetsZero))
return;
UIEdgeInsets currentInsets = [self contentInset];
currentInsets.top += insets.top;
currentInsets.left += insets.left;
currentInsets.bottom += insets.bottom;
currentInsets.right += insets.right;
[self setContentInset:currentInsets];
[self setScrollIndicatorInsets:currentInsets];
}
- (void)cr_removeInsets:(UIEdgeInsets)insets {
UIEdgeInsets negativeInsets = UIEdgeInsetsZero;
negativeInsets.top = -insets.top;
negativeInsets.left = -insets.left;
negativeInsets.bottom = -insets.bottom;
negativeInsets.right = -insets.right;
[self cr_addInsets:negativeInsets];
}
@end
@implementation CRWWebViewProxyImpl {
__weak CRWWebController* _webController;
NSMutableDictionary* _registeredInsets;
// The WebViewScrollViewProxy is a wrapper around the web view's
// UIScrollView to give components access in a limited and controlled manner.
CRWWebViewScrollViewProxy* _contentViewScrollViewProxy;
}
@synthesize contentView = _contentView;
@dynamic keyboardVisible;
- (instancetype)initWithWebController:(CRWWebController*)webController {
self = [super init];
if (self) {
DCHECK(webController);
_registeredInsets = [[NSMutableDictionary alloc] init];
_webController = webController;
_contentViewScrollViewProxy = [[CRWWebViewScrollViewProxy alloc] init];
}
return self;
}
- (CRWWebViewScrollViewProxy*)scrollViewProxy {
return _contentViewScrollViewProxy;
}
- (BOOL)allowsBackForwardNavigationGestures {
return _webController.allowsBackForwardNavigationGestures;
}
- (void)setAllowsBackForwardNavigationGestures:
(BOOL)allowsBackForwardNavigationGestures {
_webController.allowsBackForwardNavigationGestures =
allowsBackForwardNavigationGestures;
}
- (CGRect)bounds {
return [_contentView bounds];
}
- (CGRect)frame {
return [_contentView frame];
}
- (CGPoint)contentOffset {
return _contentView.contentOffset;
}
- (void)setContentOffset:(CGPoint)contentOffset {
_contentView.contentOffset = contentOffset;
}
- (UIEdgeInsets)contentInset {
return _contentView.contentInset;
}
- (void)setContentInset:(UIEdgeInsets)contentInset {
_contentView.contentInset = contentInset;
}
- (NSArray*)gestureRecognizers {
return [_contentView gestureRecognizers];
}
- (BOOL)shouldUseViewContentInset {
SEL shouldUseInsetSelector = @selector(shouldUseViewContentInset);
return [_contentView respondsToSelector:shouldUseInsetSelector] &&
[_contentView shouldUseViewContentInset];
}
- (void)setShouldUseViewContentInset:(BOOL)shouldUseViewContentInset {
if ([_contentView
respondsToSelector:@selector(setShouldUseViewContentInset:)]) {
[_contentView setShouldUseViewContentInset:shouldUseViewContentInset];
}
}
- (void)registerInsets:(UIEdgeInsets)insets forCaller:(id)caller {
NSValue* callerValue = [NSValue valueWithNonretainedObject:caller];
if ([_registeredInsets objectForKey:callerValue])
[self unregisterInsetsForCaller:caller];
[self.scrollViewProxy cr_addInsets:insets];
[_registeredInsets setObject:[NSValue valueWithUIEdgeInsets:insets]
forKey:callerValue];
}
- (void)unregisterInsetsForCaller:(id)caller {
NSValue* callerValue = [NSValue valueWithNonretainedObject:caller];
NSValue* insetsValue = [_registeredInsets objectForKey:callerValue];
[self.scrollViewProxy cr_removeInsets:[insetsValue UIEdgeInsetsValue]];
[_registeredInsets removeObjectForKey:callerValue];
}
// Do not use with a `nil` `contentView`. Instead, use
// `clearContentViewAndAddPlaceholder` whenever `contentView` needs to be set to
// `nil`. This allows us to evaluate the need of setting up the placeholder
// scroll view when clearing the content view.
- (void)setContentView:(nonnull CRWContentView*)contentView {
DCHECK(contentView);
_contentView = contentView;
[_contentViewScrollViewProxy setScrollView:contentView.scrollView];
}
- (void)clearContentViewAndAddPlaceholder:(BOOL)addPlaceholder {
_contentView = nil;
if (addPlaceholder) {
[_contentViewScrollViewProxy setScrollView:nil];
}
}
- (void)addSubview:(UIView*)view {
return [_contentView addSubview:view];
}
- (BOOL)isKeyboardVisible {
if (!_contentView)
return NO;
UIView* firstResponder = GetFirstResponderSubview(_contentView);
return firstResponder.inputAccessoryView != nil;
}
- (BOOL)becomeFirstResponder {
return [_contentView becomeFirstResponder];
}
- (BOOL)isWebPageInFullscreenMode {
return [_webController isWebPageInFullscreenMode];
}
@end