blob: 7c2f3ede932d8ee3b2fb234baf50ac207bb085bb [file] [log] [blame]
// Copyright 2013 The Flutter 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 "FWFWebViewHostApi.h"
#import "FWFDataConverters.h"
@implementation FWFAssetManager
- (NSString *)lookupKeyForAsset:(NSString *)asset {
return [FlutterDartProject lookupKeyForAsset:asset];
}
@end
@implementation FWFWebView
- (instancetype)initWithFrame:(CGRect)frame
configuration:(nonnull WKWebViewConfiguration *)configuration
binaryMessenger:(id<FlutterBinaryMessenger>)binaryMessenger
instanceManager:(FWFInstanceManager *)instanceManager {
self = [self initWithFrame:frame configuration:configuration];
if (self) {
_objectApi = [[FWFObjectFlutterApiImpl alloc] initWithBinaryMessenger:binaryMessenger
instanceManager:instanceManager];
self.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
if (@available(iOS 13.0, *)) {
self.scrollView.automaticallyAdjustsScrollIndicatorInsets = NO;
}
}
return self;
}
- (void)setFrame:(CGRect)frame {
[super setFrame:frame];
// Prevents the contentInsets from being adjusted by iOS and gives control to Flutter.
self.scrollView.contentInset = UIEdgeInsetsZero;
// Adjust contentInset to compensate the adjustedContentInset so the sum will
// always be 0.
if (UIEdgeInsetsEqualToEdgeInsets(self.scrollView.adjustedContentInset, UIEdgeInsetsZero)) {
return;
}
UIEdgeInsets insetToAdjust = self.scrollView.adjustedContentInset;
self.scrollView.contentInset = UIEdgeInsetsMake(-insetToAdjust.top, -insetToAdjust.left,
-insetToAdjust.bottom, -insetToAdjust.right);
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey, id> *)change
context:(void *)context {
[self.objectApi observeValueForObject:self
keyPath:keyPath
object:object
change:change
completion:^(FlutterError *error) {
NSAssert(!error, @"%@", error);
}];
}
- (nonnull UIView *)view {
return self;
}
@end
@interface FWFWebViewHostApiImpl ()
// BinaryMessenger must be weak to prevent a circular reference with the host API it
// references.
@property(nonatomic, weak) id<FlutterBinaryMessenger> binaryMessenger;
// InstanceManager must be weak to prevent a circular reference with the object it stores.
@property(nonatomic, weak) FWFInstanceManager *instanceManager;
@property NSBundle *bundle;
@property FWFAssetManager *assetManager;
@end
@implementation FWFWebViewHostApiImpl
- (instancetype)initWithBinaryMessenger:(id<FlutterBinaryMessenger>)binaryMessenger
instanceManager:(FWFInstanceManager *)instanceManager {
return [self initWithBinaryMessenger:binaryMessenger
instanceManager:instanceManager
bundle:[NSBundle mainBundle]
assetManager:[[FWFAssetManager alloc] init]];
}
- (instancetype)initWithBinaryMessenger:(id<FlutterBinaryMessenger>)binaryMessenger
instanceManager:(FWFInstanceManager *)instanceManager
bundle:(NSBundle *)bundle
assetManager:(FWFAssetManager *)assetManager {
self = [self init];
if (self) {
_binaryMessenger = binaryMessenger;
_instanceManager = instanceManager;
_bundle = bundle;
_assetManager = assetManager;
}
return self;
}
- (FWFWebView *)webViewForIdentifier:(NSInteger)identifier {
return (FWFWebView *)[self.instanceManager instanceForIdentifier:identifier];
}
+ (nonnull FlutterError *)errorForURLString:(nonnull NSString *)string {
NSString *errorDetails = [NSString stringWithFormat:@"Initializing NSURL with the supplied "
@"'%@' path resulted in a nil value.",
string];
return [FlutterError errorWithCode:@"FWFURLParsingError"
message:@"Failed parsing file path."
details:errorDetails];
}
- (void)createWithIdentifier:(NSInteger)identifier
configurationIdentifier:(NSInteger)configurationIdentifier
error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error {
WKWebViewConfiguration *configuration = (WKWebViewConfiguration *)[self.instanceManager
instanceForIdentifier:configurationIdentifier];
FWFWebView *webView = [[FWFWebView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)
configuration:configuration
binaryMessenger:self.binaryMessenger
instanceManager:self.instanceManager];
[self.instanceManager addDartCreatedInstance:webView withIdentifier:identifier];
}
- (void)loadRequestForWebViewWithIdentifier:(NSInteger)identifier
request:(nonnull FWFNSUrlRequestData *)request
error:
(FlutterError *_Nullable __autoreleasing *_Nonnull)error {
NSURLRequest *urlRequest = FWFNativeNSURLRequestFromRequestData(request);
if (!urlRequest) {
*error = [FlutterError errorWithCode:@"FWFURLRequestParsingError"
message:@"Failed instantiating an NSURLRequest."
details:[NSString stringWithFormat:@"URL was: '%@'", request.url]];
return;
}
[[self webViewForIdentifier:identifier] loadRequest:urlRequest];
}
- (void)setCustomUserAgentForWebViewWithIdentifier:(NSInteger)identifier
userAgent:(nullable NSString *)userAgent
error:
(FlutterError *_Nullable __autoreleasing *_Nonnull)
error {
[[self webViewForIdentifier:identifier] setCustomUserAgent:userAgent];
}
- (nullable NSNumber *)
canGoBackForWebViewWithIdentifier:(NSInteger)identifier
error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error {
return @([self webViewForIdentifier:identifier].canGoBack);
}
- (nullable NSString *)
URLForWebViewWithIdentifier:(NSInteger)identifier
error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error {
return [self webViewForIdentifier:identifier].URL.absoluteString;
}
- (nullable NSNumber *)
canGoForwardForWebViewWithIdentifier:(NSInteger)identifier
error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error {
return @([[self webViewForIdentifier:identifier] canGoForward]);
}
- (nullable NSNumber *)
estimatedProgressForWebViewWithIdentifier:(NSInteger)identifier
error:(FlutterError *_Nullable __autoreleasing *_Nonnull)
error {
return @([[self webViewForIdentifier:identifier] estimatedProgress]);
}
- (void)evaluateJavaScriptForWebViewWithIdentifier:(NSInteger)identifier
javaScriptString:(nonnull NSString *)javaScriptString
completion:
(nonnull void (^)(id _Nullable,
FlutterError *_Nullable))completion {
[[self webViewForIdentifier:identifier]
evaluateJavaScript:javaScriptString
completionHandler:^(id _Nullable result, NSError *_Nullable error) {
id returnValue = nil;
FlutterError *flutterError = nil;
if (!error) {
if (!result || [result isKindOfClass:[NSString class]] ||
[result isKindOfClass:[NSNumber class]]) {
returnValue = result;
} else if (![result isKindOfClass:[NSNull class]]) {
NSString *className = NSStringFromClass([result class]);
NSLog(@"Return type of evaluateJavaScript is not directly supported: %@. Returned "
@"description of value.",
className);
returnValue = [result description];
}
} else {
flutterError = [FlutterError errorWithCode:@"FWFEvaluateJavaScriptError"
message:@"Failed evaluating JavaScript."
details:FWFNSErrorDataFromNativeNSError(error)];
}
completion(returnValue, flutterError);
}];
}
- (void)setInspectableForWebViewWithIdentifier:(NSInteger)identifier
inspectable:(BOOL)inspectable
error:(FlutterError *_Nullable *_Nonnull)error {
if (@available(macOS 13.3, iOS 16.4, tvOS 16.4, *)) {
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 130300 || __IPHONE_OS_VERSION_MAX_ALLOWED >= 160400
[[self webViewForIdentifier:identifier] setInspectable:inspectable];
#endif
} else {
*error = [FlutterError errorWithCode:@"FWFUnsupportedVersionError"
message:@"setInspectable is only supported on versions 16.4+."
details:nil];
}
}
- (void)goBackForWebViewWithIdentifier:(NSInteger)identifier
error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error {
[[self webViewForIdentifier:identifier] goBack];
}
- (void)goForwardForWebViewWithIdentifier:(NSInteger)identifier
error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error {
[[self webViewForIdentifier:identifier] goForward];
}
- (void)loadAssetForWebViewWithIdentifier:(NSInteger)identifier
assetKey:(nonnull NSString *)key
error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error {
NSString *assetFilePath = [self.assetManager lookupKeyForAsset:key];
NSURL *url = [self.bundle URLForResource:[assetFilePath stringByDeletingPathExtension]
withExtension:assetFilePath.pathExtension];
if (!url) {
*error = [FWFWebViewHostApiImpl errorForURLString:assetFilePath];
} else {
[[self webViewForIdentifier:identifier] loadFileURL:url
allowingReadAccessToURL:[url URLByDeletingLastPathComponent]];
}
}
- (void)loadFileForWebViewWithIdentifier:(NSInteger)identifier
fileURL:(nonnull NSString *)url
readAccessURL:(nonnull NSString *)readAccessUrl
error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error {
NSURL *fileURL = [NSURL fileURLWithPath:url isDirectory:NO];
NSURL *readAccessNSURL = [NSURL fileURLWithPath:readAccessUrl isDirectory:YES];
if (!fileURL) {
*error = [FWFWebViewHostApiImpl errorForURLString:url];
} else if (!readAccessNSURL) {
*error = [FWFWebViewHostApiImpl errorForURLString:readAccessUrl];
} else {
[[self webViewForIdentifier:identifier] loadFileURL:fileURL
allowingReadAccessToURL:readAccessNSURL];
}
}
- (void)loadHTMLForWebViewWithIdentifier:(NSInteger)identifier
HTMLString:(nonnull NSString *)string
baseURL:(nullable NSString *)baseUrl
error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error {
[[self webViewForIdentifier:identifier] loadHTMLString:string
baseURL:[NSURL URLWithString:baseUrl]];
}
- (void)reloadWebViewWithIdentifier:(NSInteger)identifier
error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error {
[[self webViewForIdentifier:identifier] reload];
}
- (void)
setAllowsBackForwardForWebViewWithIdentifier:(NSInteger)identifier
isAllowed:(BOOL)allow
error:(FlutterError *_Nullable __autoreleasing *_Nonnull)
error {
[[self webViewForIdentifier:identifier] setAllowsBackForwardNavigationGestures:allow];
}
- (void)
setNavigationDelegateForWebViewWithIdentifier:(NSInteger)identifier
delegateIdentifier:(nullable NSNumber *)navigationDelegateIdentifier
error:
(FlutterError *_Nullable __autoreleasing *_Nonnull)
error {
id<WKNavigationDelegate> navigationDelegate = (id<WKNavigationDelegate>)[self.instanceManager
instanceForIdentifier:navigationDelegateIdentifier.longValue];
[[self webViewForIdentifier:identifier] setNavigationDelegate:navigationDelegate];
}
- (void)setUIDelegateForWebViewWithIdentifier:(NSInteger)identifier
delegateIdentifier:(nullable NSNumber *)uiDelegateIdentifier
error:(FlutterError *_Nullable __autoreleasing *_Nonnull)
error {
id<WKUIDelegate> navigationDelegate =
(id<WKUIDelegate>)[self.instanceManager instanceForIdentifier:uiDelegateIdentifier.longValue];
[[self webViewForIdentifier:identifier] setUIDelegate:navigationDelegate];
}
- (nullable NSString *)
titleForWebViewWithIdentifier:(NSInteger)identifier
error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error {
return [[self webViewForIdentifier:identifier] title];
}
- (nullable NSString *)
customUserAgentForWebViewWithIdentifier:(NSInteger)identifier
error:
(FlutterError *_Nullable __autoreleasing *_Nonnull)error {
return [[self webViewForIdentifier:identifier] customUserAgent];
}
@end