diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFInstanceManagerTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFInstanceManagerTests.m index 264b623dd8cf..2ad4bd48b2e8 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFInstanceManagerTests.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFInstanceManagerTests.m @@ -3,38 +3,40 @@ // found in the LICENSE file. #import + @import webview_flutter_wkwebview; +@import webview_flutter_wkwebview.Test; @interface FWFInstanceManagerTests : XCTestCase @end @implementation FWFInstanceManagerTests -- (void)testAddInstanceCreatedFromDart { +- (void)testAddDartCreatedInstance { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; NSObject *object = [[NSObject alloc] init]; - [instanceManager addDartCreatedInstance:object withIdentifier:5]; - XCTAssertEqualObjects([instanceManager instanceForIdentifier:5], object); - XCTAssertEqual([instanceManager identifierForInstance:object], 5); + [instanceManager addDartCreatedInstance:object withIdentifier:0]; + XCTAssertEqualObjects([instanceManager instanceForIdentifier:0], object); + XCTAssertEqual([instanceManager identifierWithStrongReferenceForInstance:object], 0); } -- (void)testRemoveInstance { +- (void)testAddHostCreatedInstance { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; NSObject *object = [[NSObject alloc] init]; - [instanceManager addDartCreatedInstance:object withIdentifier:5]; + [instanceManager addHostCreatedInstance:object]; - [instanceManager removeInstance:object]; - XCTAssertNil([instanceManager instanceForIdentifier:5]); - XCTAssertEqual([instanceManager identifierForInstance:object], NSNotFound); + long identifier = [instanceManager identifierWithStrongReferenceForInstance:object]; + XCTAssertNotEqual(identifier, NSNotFound); + XCTAssertEqualObjects([instanceManager instanceForIdentifier:identifier], object); } - (void)testRemoveInstanceWithIdentifier { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; NSObject *object = [[NSObject alloc] init]; - [instanceManager addDartCreatedInstance:object withIdentifier:5]; - [instanceManager removeInstanceWithIdentifier:5]; - XCTAssertNil([instanceManager instanceForIdentifier:5]); - XCTAssertEqual([instanceManager identifierForInstance:object], NSNotFound); + [instanceManager addDartCreatedInstance:object withIdentifier:0]; + + XCTAssertEqualObjects([instanceManager removeInstanceWithIdentifier:0], object); + XCTAssertEqual([instanceManager strongInstanceCount], 0); } @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFNavigationDelegateHostApiTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFNavigationDelegateHostApiTests.m index 9025b2e5ce43..206137e301f5 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFNavigationDelegateHostApiTests.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFNavigationDelegateHostApiTests.m @@ -14,8 +14,9 @@ @interface FWFNavigationDelegateHostApiTests : XCTestCase @implementation FWFNavigationDelegateHostApiTests - (void)testCreateWithIdentifier { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; - FWFNavigationDelegateHostApiImpl *hostAPI = - [[FWFNavigationDelegateHostApiImpl alloc] initWithInstanceManager:instanceManager]; + FWFNavigationDelegateHostApiImpl *hostAPI = [[FWFNavigationDelegateHostApiImpl alloc] + initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) + instanceManager:instanceManager]; FlutterError *error; [hostAPI createWithIdentifier:@0 error:&error]; @@ -25,4 +26,34 @@ - (void)testCreateWithIdentifier { XCTAssertTrue([navigationDelegate conformsToProtocol:@protocol(WKNavigationDelegate)]); XCTAssertNil(error); } + +- (void)testDidFinishNavigation { + FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; + FWFNavigationDelegateHostApiImpl *hostAPI = [[FWFNavigationDelegateHostApiImpl alloc] + initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) + instanceManager:instanceManager]; + + FlutterError *error; + [hostAPI createWithIdentifier:@0 error:&error]; + FWFNavigationDelegate *navigationDelegate = + (FWFNavigationDelegate *)[instanceManager instanceForIdentifier:0]; + id mockDelegate = OCMPartialMock(navigationDelegate); + + FWFNavigationDelegateFlutterApiImpl *flutterAPI = [[FWFNavigationDelegateFlutterApiImpl alloc] + initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) + instanceManager:instanceManager]; + id mockFlutterApi = OCMPartialMock(flutterAPI); + + OCMStub([mockDelegate navigationDelegateAPI]).andReturn(mockFlutterApi); + + WKWebView *mockWebView = OCMClassMock([WKWebView class]); + OCMStub([mockWebView URL]).andReturn([NSURL URLWithString:@"https://flutter.dev/"]); + [instanceManager addDartCreatedInstance:mockWebView withIdentifier:1]; + + [mockDelegate webView:mockWebView didFinishNavigation:OCMClassMock([WKNavigation class])]; + OCMVerify([mockFlutterApi didFinishNavigationForDelegateWithIdentifier:@0 + webViewIdentifier:@1 + URL:@"https://flutter.dev/" + completion:OCMOCK_ANY]); +} @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFObjectHostApiTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFObjectHostApiTests.m index 271a1d0eb696..bdaeae4c09dc 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFObjectHostApiTests.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFObjectHostApiTests.m @@ -76,7 +76,10 @@ - (void)testDispose { FlutterError *error; [hostAPI disposeObjectWithIdentifier:@0 error:&error]; - XCTAssertEqual([instanceManager identifierForInstance:object], NSNotFound); + // Only the strong reference is removed, so the weak reference will remain until object is set to + // nil. + object = nil; + XCTAssertFalse([instanceManager containsInstance:object]); XCTAssertNil(error); } @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.h index 3617b4ecc731..b291f4167725 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.h @@ -303,9 +303,6 @@ NSObject *FWFWKNavigationDelegateHostApiGetCodec(void); @protocol FWFWKNavigationDelegateHostApi - (void)createWithIdentifier:(NSNumber *)identifier error:(FlutterError *_Nullable *_Nonnull)error; -- (void)setDidFinishNavigationForDelegateWithIdentifier:(NSNumber *)identifier - functionIdentifier:(nullable NSNumber *)functionIdentifier - error:(FlutterError *_Nullable *_Nonnull)error; @end extern void FWFWKNavigationDelegateHostApiSetup( @@ -317,7 +314,7 @@ NSObject *FWFWKNavigationDelegateFlutterApiGetCodec(void); @interface FWFWKNavigationDelegateFlutterApi : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger; -- (void)didFinishNavigationForDelegateWithIdentifier:(NSNumber *)functionIdentifier +- (void)didFinishNavigationForDelegateWithIdentifier:(NSNumber *)identifier webViewIdentifier:(NSNumber *)webViewIdentifier URL:(nullable NSString *)url completion:(void (^)(NSError *_Nullable))completion; @@ -343,13 +340,11 @@ NSObject *FWFNSObjectHostApiGetCodec(void); extern void FWFNSObjectHostApiSetup(id binaryMessenger, NSObject *_Nullable api); -/// The codec used by FWFFunctionFlutterApi. -NSObject *FWFFunctionFlutterApiGetCodec(void); +/// The codec used by FWFNSObjectFlutterApi. +NSObject *FWFNSObjectFlutterApiGetCodec(void); -@interface FWFFunctionFlutterApi : NSObject +@interface FWFNSObjectFlutterApi : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger; -- (void)disposeFunctionWithIdentifier:(NSNumber *)identifier - completion:(void (^)(NSError *_Nullable))completion; @end /// The codec used by FWFWKWebViewHostApi. NSObject *FWFWKWebViewHostApiGetCodec(void); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.m index 1d20cb350175..f936d151ddd3 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.m @@ -1150,32 +1150,6 @@ void FWFWKNavigationDelegateHostApiSetup(id binaryMessen [channel setMessageHandler:nil]; } } - { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.WKNavigationDelegateHostApi.setDidFinishNavigation" - binaryMessenger:binaryMessenger - codec:FWFWKNavigationDelegateHostApiGetCodec()]; - if (api) { - NSCAssert( - [api respondsToSelector:@selector - (setDidFinishNavigationForDelegateWithIdentifier:functionIdentifier:error:)], - @"FWFWKNavigationDelegateHostApi api (%@) doesn't respond to " - @"@selector(setDidFinishNavigationForDelegateWithIdentifier:functionIdentifier:error:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); - NSNumber *arg_functionIdentifier = GetNullableObjectAtIndex(args, 1); - FlutterError *error; - [api setDidFinishNavigationForDelegateWithIdentifier:arg_identifier - functionIdentifier:arg_functionIdentifier - error:&error]; - callback(wrapResult(nil, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } } @interface FWFWKNavigationDelegateFlutterApiCodecReader : FlutterStandardReader @end @@ -1222,7 +1196,7 @@ - (instancetype)initWithBinaryMessenger:(NSObject *)bina } return self; } -- (void)didFinishNavigationForDelegateWithIdentifier:(NSNumber *)arg_functionIdentifier +- (void)didFinishNavigationForDelegateWithIdentifier:(NSNumber *)arg_identifier webViewIdentifier:(NSNumber *)arg_webViewIdentifier URL:(nullable NSString *)arg_url completion:(void (^)(NSError *_Nullable))completion { @@ -1232,7 +1206,7 @@ - (void)didFinishNavigationForDelegateWithIdentifier:(NSNumber *)arg_functionIde binaryMessenger:self.binaryMessenger codec:FWFWKNavigationDelegateFlutterApiGetCodec()]; [channel sendMessage:@[ - arg_functionIdentifier ?: [NSNull null], arg_webViewIdentifier ?: [NSNull null], + arg_identifier ?: [NSNull null], arg_webViewIdentifier ?: [NSNull null], arg_url ?: [NSNull null] ] reply:^(id reply) { @@ -1373,43 +1347,43 @@ void FWFNSObjectHostApiSetup(id binaryMessenger, } } } -@interface FWFFunctionFlutterApiCodecReader : FlutterStandardReader +@interface FWFNSObjectFlutterApiCodecReader : FlutterStandardReader @end -@implementation FWFFunctionFlutterApiCodecReader +@implementation FWFNSObjectFlutterApiCodecReader @end -@interface FWFFunctionFlutterApiCodecWriter : FlutterStandardWriter +@interface FWFNSObjectFlutterApiCodecWriter : FlutterStandardWriter @end -@implementation FWFFunctionFlutterApiCodecWriter +@implementation FWFNSObjectFlutterApiCodecWriter @end -@interface FWFFunctionFlutterApiCodecReaderWriter : FlutterStandardReaderWriter +@interface FWFNSObjectFlutterApiCodecReaderWriter : FlutterStandardReaderWriter @end -@implementation FWFFunctionFlutterApiCodecReaderWriter +@implementation FWFNSObjectFlutterApiCodecReaderWriter - (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { - return [[FWFFunctionFlutterApiCodecWriter alloc] initWithData:data]; + return [[FWFNSObjectFlutterApiCodecWriter alloc] initWithData:data]; } - (FlutterStandardReader *)readerWithData:(NSData *)data { - return [[FWFFunctionFlutterApiCodecReader alloc] initWithData:data]; + return [[FWFNSObjectFlutterApiCodecReader alloc] initWithData:data]; } @end -NSObject *FWFFunctionFlutterApiGetCodec() { +NSObject *FWFNSObjectFlutterApiGetCodec() { static dispatch_once_t sPred = 0; static FlutterStandardMessageCodec *sSharedObject = nil; dispatch_once(&sPred, ^{ - FWFFunctionFlutterApiCodecReaderWriter *readerWriter = - [[FWFFunctionFlutterApiCodecReaderWriter alloc] init]; + FWFNSObjectFlutterApiCodecReaderWriter *readerWriter = + [[FWFNSObjectFlutterApiCodecReaderWriter alloc] init]; sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; }); return sSharedObject; } -@interface FWFFunctionFlutterApi () +@interface FWFNSObjectFlutterApi () @property(nonatomic, strong) NSObject *binaryMessenger; @end -@implementation FWFFunctionFlutterApi +@implementation FWFNSObjectFlutterApi - (instancetype)initWithBinaryMessenger:(NSObject *)binaryMessenger { self = [super init]; @@ -1418,17 +1392,6 @@ - (instancetype)initWithBinaryMessenger:(NSObject *)bina } return self; } -- (void)disposeFunctionWithIdentifier:(NSNumber *)arg_identifier - completion:(void (^)(NSError *_Nullable))completion { - FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel - messageChannelWithName:@"dev.flutter.pigeon.FunctionFlutterApi.dispose" - binaryMessenger:self.binaryMessenger - codec:FWFFunctionFlutterApiGetCodec()]; - [channel sendMessage:@[ arg_identifier ?: [NSNull null] ] - reply:^(id reply) { - completion(nil); - }]; -} @end @interface FWFWKWebViewHostApiCodecReader : FlutterStandardReader @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFInstanceManager.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFInstanceManager.h index cd84d705aaef..5dec08055ce5 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFInstanceManager.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFInstanceManager.h @@ -6,17 +6,30 @@ NS_ASSUME_NONNULL_BEGIN +typedef void (^FWFOnDeallocCallback)(long identifier); + /** - * Maintains instances to intercommunicate with Dart objects. + * Maintains instances used to communicate with the corresponding objects in Dart. * * When an instance is added with an identifier, either can be used to retrieve the other. + * + * Added instances are added as a weak reference and a strong reference. When the strong reference + * is removed with `removeStrongReferenceWithIdentifier:` and the weak reference is deallocated, + * the `deallocCallback` is made with the instance's identifier. However, if the strong reference is + * removed and then the identifier is retrieved with the intention to pass the identifier to Dart + * (e.g. calling `identifierForInstance:identifierWillBePassedToFlutter:` with + * `identifierWillBePassedToFlutter` set to YES), the strong reference to the instance is recreated. + * The strong reference will then need to be removed manually again. + * + * Accessing and inserting to an InstanceManager is thread safe. */ @interface FWFInstanceManager : NSObject +@property(readonly) FWFOnDeallocCallback deallocCallback; +- (instancetype)initWithDeallocCallback:(FWFOnDeallocCallback)callback; // TODO(bparrishMines): Pairs should not be able to be overwritten and this feature -// should be replaced with a call to clear the manager in the event of a hot restart -// instead. +// should be replaced with a call to clear the manager in the event of a hot restart. /** - * Adds a new instance to the manager that was instantiated by Dart. + * Adds a new instance that was instantiated from Dart. * * If an instance or identifier has already been added, it will be replaced by the new values. The * Dart InstanceManager is considered the source of truth and has the capability to overwrite stored @@ -28,31 +41,30 @@ NS_ASSUME_NONNULL_BEGIN - (void)addDartCreatedInstance:(NSObject *)instance withIdentifier:(long)instanceIdentifier; /** - * Removes the instance paired with a given identifier from the manager. - * - * @param instanceIdentifier The identifier paired to an instance. + * Adds a new instance that was instantiated from the host platform. * - * @return The removed instance if the manager contains the given instanceIdentifier, otherwise - * nil. + * @param instance The instance to be stored. + * @return The unique identifier stored with instance. */ -- (nullable NSObject *)removeInstanceWithIdentifier:(long)instanceIdentifier; +- (long)addHostCreatedInstance:(nonnull NSObject *)instance; /** - * Removes the instance from the manager. + * Removes `instanceIdentifier` and its associated strongly referenced instance, if present, from + * the manager. * - * @param instance The instance to be removed from the manager. + * @param instanceIdentifier The identifier paired to an instance. * - * @return The identifier of the removed instance if the manager contains the given instance, - * otherwise NSNotFound. + * @return The removed instance if the manager contains the given instanceIdentifier, otherwise + * nil. */ -- (long)removeInstance:(NSObject *)instance; +- (nullable NSObject *)removeInstanceWithIdentifier:(long)instanceIdentifier; /** - * Retrieves the instance paired with identifier. + * Retrieves the instance associated with identifier. * * @param instanceIdentifier The identifier paired to an instance. * - * @return The paired instance if the manager contains the given instanceIdentifier, + * @return The instance associated with `instanceIdentifier` if the manager contains the value, * otherwise nil. */ - (nullable NSObject *)instanceForIdentifier:(long)instanceIdentifier; @@ -60,11 +72,26 @@ NS_ASSUME_NONNULL_BEGIN /** * Retrieves the identifier paired with an instance. * + * If the manager contains `instance`, as a strong or weak reference, the strong reference to + * `instance` will be recreated and will need to be removed again with + * `removeInstanceWithIdentifier:`. + * + * This method also expects the Dart `InstanceManager` to have, or recreate, a weak reference to the + * instance the identifier is associated with once it receives it. + * * @param instance An instance that may be stored in the manager. * - * @return The paired identifer if the manager contains the given instance, otherwise NSNotFound. + * @return The identifier associated with `instance` if the manager contains the value, otherwise + * NSNotFound. + */ +- (long)identifierWithStrongReferenceForInstance:(nonnull NSObject *)instance; + +/** + * Returns whether this manager contains the given `instance`. + * + * @return Whether this manager contains the given `instance`. */ -- (long)identifierForInstance:(NSObject *)instance; +- (BOOL)containsInstance:(nonnull NSObject *)instance; @end NS_ASSUME_NONNULL_END diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFInstanceManager.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFInstanceManager.m index 995e37424bb9..1fe04a39503f 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFInstanceManager.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFInstanceManager.m @@ -3,71 +3,162 @@ // found in the LICENSE file. #import "FWFInstanceManager.h" +#import "FWFInstanceManager_Test.h" + +#import + +// Attaches to an object to receive a callback when the object is deallocated. +@interface FWFFinalizer : NSObject +@end + +// Attaches to an object to receive a callback when the object is deallocated. +@implementation FWFFinalizer { + long _identifier; + // Callbacks are no longer made once FWFInstanceManager is inaccessible. + FWFOnDeallocCallback __weak _callback; +} + +- (instancetype)initWithIdentifier:(long)identifier callback:(FWFOnDeallocCallback)callback { + self = [self init]; + if (self) { + _identifier = identifier; + _callback = callback; + } + return self; +} + ++ (void)attachToInstance:(NSObject *)instance + withIdentifier:(long)identifier + callback:(FWFOnDeallocCallback)callback { + FWFFinalizer *finalizer = [[FWFFinalizer alloc] initWithIdentifier:identifier callback:callback]; + objc_setAssociatedObject(instance, _cmd, finalizer, OBJC_ASSOCIATION_RETAIN); +} + ++ (void)detachFromInstance:(NSObject *)instance { + objc_setAssociatedObject(instance, @selector(attachToInstance:withIdentifier:callback:), nil, + OBJC_ASSOCIATION_ASSIGN); +} + +- (void)dealloc { + _callback(_identifier); +} +@end @interface FWFInstanceManager () @property dispatch_queue_t lockQueue; -@property NSMapTable *identifiersToInstances; -@property NSMapTable *instancesToIdentifiers; +@property NSMapTable *identifiers; +@property NSMapTable *weakInstances; +@property NSMapTable *strongInstances; +@property long nextIdentifier; @end @implementation FWFInstanceManager +// Identifiers are locked to a specific range to avoid collisions with objects +// created simultaneously from Dart. +// Host uses identifiers >= 2^16 and Dart is expected to use values n where, +// 0 <= n < 2^16. +static long const FWFMinHostCreatedIdentifier = 65536; + - (instancetype)init { + self = [super init]; if (self) { + _deallocCallback = _deallocCallback ? _deallocCallback : ^(long identifier) { + }; _lockQueue = dispatch_queue_create("FWFInstanceManager", DISPATCH_QUEUE_SERIAL); - _identifiersToInstances = [NSMapTable strongToStrongObjectsMapTable]; - _instancesToIdentifiers = [NSMapTable strongToStrongObjectsMapTable]; + _identifiers = [NSMapTable weakToStrongObjectsMapTable]; + _weakInstances = [NSMapTable strongToWeakObjectsMapTable]; + _strongInstances = [NSMapTable strongToStrongObjectsMapTable]; + _nextIdentifier = FWFMinHostCreatedIdentifier; + } + return self; +} + +- (instancetype)initWithDeallocCallback:(FWFOnDeallocCallback)callback { + self = [self init]; + if (self) { + _deallocCallback = callback; } return self; } -- (void)addDartCreatedInstance:(nonnull NSObject *)instance - withIdentifier:(long)instanceIdentifier { - NSAssert(instance && instanceIdentifier >= 0, - @"Instance must be nonnull and identifier must be >= 0."); +- (void)addDartCreatedInstance:(NSObject *)instance withIdentifier:(long)instanceIdentifier { + NSParameterAssert(instance); + NSParameterAssert(instanceIdentifier >= 0); dispatch_async(_lockQueue, ^{ - [self.instancesToIdentifiers setObject:@(instanceIdentifier) forKey:instance]; - [self.identifiersToInstances setObject:instance forKey:@(instanceIdentifier)]; + [self addInstance:instance withIdentifier:instanceIdentifier]; }); } +- (long)addHostCreatedInstance:(nonnull NSObject *)instance { + NSParameterAssert(instance); + long __block identifier = -1; + dispatch_sync(_lockQueue, ^{ + identifier = self.nextIdentifier++; + [self addInstance:instance withIdentifier:identifier]; + }); + return identifier; +} + - (nullable NSObject *)removeInstanceWithIdentifier:(long)instanceIdentifier { NSObject *__block instance = nil; dispatch_sync(_lockQueue, ^{ - instance = [self.identifiersToInstances objectForKey:@(instanceIdentifier)]; + instance = [self.strongInstances objectForKey:@(instanceIdentifier)]; if (instance) { - [self.identifiersToInstances removeObjectForKey:@(instanceIdentifier)]; - [self.instancesToIdentifiers removeObjectForKey:instance]; + [self.strongInstances removeObjectForKey:@(instanceIdentifier)]; } }); return instance; } -- (long)removeInstance:(NSObject *)instance { - NSAssert(instance, @"Instance must be nonnull."); +- (nullable NSObject *)instanceForIdentifier:(long)instanceIdentifier { + NSObject *__block instance = nil; + dispatch_sync(_lockQueue, ^{ + instance = [self.weakInstances objectForKey:@(instanceIdentifier)]; + }); + return instance; +} + +- (void)addInstance:(nonnull NSObject *)instance withIdentifier:(long)instanceIdentifier { + [self.identifiers setObject:@(instanceIdentifier) forKey:instance]; + [self.weakInstances setObject:instance forKey:@(instanceIdentifier)]; + [self.strongInstances setObject:instance forKey:@(instanceIdentifier)]; + [FWFFinalizer attachToInstance:instance + withIdentifier:instanceIdentifier + callback:self.deallocCallback]; +} + +- (long)identifierWithStrongReferenceForInstance:(nonnull NSObject *)instance { NSNumber *__block identifierNumber = nil; dispatch_sync(_lockQueue, ^{ - identifierNumber = [self.instancesToIdentifiers objectForKey:instance]; + identifierNumber = [self.identifiers objectForKey:instance]; if (identifierNumber) { - [self.identifiersToInstances removeObjectForKey:identifierNumber]; - [self.instancesToIdentifiers removeObjectForKey:instance]; + [self.strongInstances setObject:instance forKey:identifierNumber]; } }); return identifierNumber ? identifierNumber.longValue : NSNotFound; } -- (nullable NSObject *)instanceForIdentifier:(long)instanceIdentifier { - NSObject *__block instance = nil; +- (BOOL)containsInstance:(nonnull NSObject *)instance { + BOOL __block containsInstance; dispatch_sync(_lockQueue, ^{ - instance = [self.identifiersToInstances objectForKey:@(instanceIdentifier)]; + containsInstance = [self.identifiers objectForKey:instance]; }); - return instance; + return containsInstance; } -- (long)identifierForInstance:(nonnull NSObject *)instance { - NSNumber *__block identifierNumber = nil; +- (NSUInteger)strongInstanceCount { + NSUInteger __block count = -1; dispatch_sync(_lockQueue, ^{ - identifierNumber = [self.instancesToIdentifiers objectForKey:instance]; + count = self.strongInstances.count; }); - return identifierNumber ? identifierNumber.longValue : NSNotFound; + return count; +} + +- (NSUInteger)weakInstanceCount { + NSUInteger __block count = -1; + dispatch_sync(_lockQueue, ^{ + count = self.weakInstances.count; + }); + return count; } @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFInstanceManager_Test.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFInstanceManager_Test.h new file mode 100644 index 000000000000..4f609049de0e --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFInstanceManager_Test.h @@ -0,0 +1,26 @@ +// 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 + +NS_ASSUME_NONNULL_BEGIN + +@interface FWFInstanceManager () +/** + * The number of instances stored as a strong reference. + * + * Added for debugging purposes. + */ +- (NSUInteger)strongInstanceCount; + +/** + * The number of instances stored as a weak reference. + * + * Added for debugging purposes. NSMapTables that store keys or objects as weak reference will be + * reclaimed nondeterministically. + */ +- (NSUInteger)weakInstanceCount; +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFNavigationDelegateHostApi.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFNavigationDelegateHostApi.h index fca1e83c86e0..2ac599603152 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFNavigationDelegateHostApi.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFNavigationDelegateHostApi.h @@ -3,17 +3,30 @@ // found in the LICENSE file. #import +#import #import #import "FWFGeneratedWebKitApis.h" #import "FWFInstanceManager.h" +#import "FWFObjectHostApi.h" NS_ASSUME_NONNULL_BEGIN +/** + * Flutter api implementation for WKNavigationDelegate. + * + * Handles making callbacks to Dart for a WKNavigationDelegate. + */ +@interface FWFNavigationDelegateFlutterApiImpl : FWFWKNavigationDelegateFlutterApi +- (instancetype)initWithBinaryMessenger:(id)binaryMessenger + instanceManager:(FWFInstanceManager *)instanceManager; +@end + /** * Implementation of WKNavigationDelegate for FWFNavigationDelegateHostApiImpl. */ -@interface FWFNavigationDelegate : NSObject +@interface FWFNavigationDelegate : FWFObject +@property(readonly, nonnull) FWFNavigationDelegateFlutterApiImpl *navigationDelegateAPI; @end /** @@ -22,7 +35,8 @@ NS_ASSUME_NONNULL_BEGIN * Handles creating WKNavigationDelegate that intercommunicate with a paired Dart object. */ @interface FWFNavigationDelegateHostApiImpl : NSObject -- (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager; +- (instancetype)initWithBinaryMessenger:(id)binaryMessenger + instanceManager:(FWFInstanceManager *)instanceManager; @end NS_ASSUME_NONNULL_END diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFNavigationDelegateHostApi.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFNavigationDelegateHostApi.m index 836349b5f29b..be651c942c0e 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFNavigationDelegateHostApi.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFNavigationDelegateHostApi.m @@ -5,17 +5,70 @@ #import "FWFNavigationDelegateHostApi.h" #import "FWFWebViewConfigurationHostApi.h" +@interface FWFNavigationDelegateFlutterApiImpl () +// This reference must be weak to prevent a circular reference with the objects it stores. +@property(nonatomic, weak) FWFInstanceManager *instanceManager; +@end + +@implementation FWFNavigationDelegateFlutterApiImpl +- (instancetype)initWithBinaryMessenger:(id)binaryMessenger + instanceManager:(FWFInstanceManager *)instanceManager { + self = [self initWithBinaryMessenger:binaryMessenger]; + if (self) { + _instanceManager = instanceManager; + } + return self; +} + +- (long)identifierForDelegate:(FWFNavigationDelegate *)instance { + return [self.instanceManager identifierWithStrongReferenceForInstance:instance]; +} + +- (void)didFinishNavigationForDelegate:(FWFNavigationDelegate *)instance + webView:(WKWebView *)webView + URL:(NSString *)URL { + [self didFinishNavigationForDelegateWithIdentifier:@([self identifierForDelegate:instance]) + webViewIdentifier: + @([self.instanceManager + identifierWithStrongReferenceForInstance:webView]) + URL:URL + completion:^(NSError *error) { + NSAssert(!error, @"%@", error); + }]; +} +@end + @implementation FWFNavigationDelegate +- (instancetype)initWithBinaryMessenger:(id)binaryMessenger + instanceManager:(FWFInstanceManager *)instanceManager { + self = [super initWithBinaryMessenger:binaryMessenger instanceManager:instanceManager]; + if (self) { + _navigationDelegateAPI = + [[FWFNavigationDelegateFlutterApiImpl alloc] initWithBinaryMessenger:binaryMessenger + instanceManager:instanceManager]; + } + return self; +} + +- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { + [self.navigationDelegateAPI didFinishNavigationForDelegate:self + webView:webView + URL:webView.URL.absoluteString]; +} @end @interface FWFNavigationDelegateHostApiImpl () -@property(nonatomic) FWFInstanceManager *instanceManager; +@property(weak) id binaryMessenger; +// This reference must be weak to prevent a circular reference with the objects it stores. +@property(nonatomic, weak) FWFInstanceManager *instanceManager; @end @implementation FWFNavigationDelegateHostApiImpl -- (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager { +- (instancetype)initWithBinaryMessenger:(id)binaryMessenger + instanceManager:(FWFInstanceManager *)instanceManager { self = [self init]; if (self) { + _binaryMessenger = binaryMessenger; _instanceManager = instanceManager; } return self; @@ -26,16 +79,11 @@ - (FWFNavigationDelegate *)navigationDelegateForIdentifier:(NSNumber *)identifie } - (void)createWithIdentifier:(nonnull NSNumber *)identifier - error:(FlutterError *_Nullable *_Nonnull)error { - FWFNavigationDelegate *navigationDelegate = [[FWFNavigationDelegate alloc] init]; + error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { + FWFNavigationDelegate *navigationDelegate = + [[FWFNavigationDelegate alloc] initWithBinaryMessenger:self.binaryMessenger + instanceManager:self.instanceManager]; [self.instanceManager addDartCreatedInstance:navigationDelegate withIdentifier:identifier.longValue]; } - -- (void)setDidFinishNavigationForDelegateWithIdentifier:(nonnull NSNumber *)identifier - functionIdentifier:(nullable NSNumber *)functionIdentifier - error:(FlutterError *_Nullable __autoreleasing - *_Nonnull)error { - // TODO(bparrishMines): Implement when callback method design is finalized. -} @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFObjectHostApi.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFObjectHostApi.h index 23c38fbe482d..f1dbdbb20776 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFObjectHostApi.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFObjectHostApi.h @@ -9,6 +9,25 @@ NS_ASSUME_NONNULL_BEGIN +/** + * Flutter api implementation for NSObject. + * + * Handles making callbacks to Dart for an NSObject. + */ +@interface FWFObjectFlutterApi : FWFNSObjectFlutterApi +- (instancetype)initWithBinaryMessenger:(id)binaryMessenger + instanceManager:(FWFInstanceManager *)instanceManager; +@end + +/** + * Implementation of NSObject for FWFObjectHostApiImpl. + */ +@interface FWFObject : NSObject +@property(readonly, nonnull) FWFObjectFlutterApi *objectApi; +- (instancetype)initWithBinaryMessenger:(id)binaryMessenger + instanceManager:(FWFInstanceManager *)instanceManager; +@end + /** * Host api implementation for NSObject. * diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFObjectHostApi.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFObjectHostApi.m index 38417f9297f0..0cc4c5693342 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFObjectHostApi.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFObjectHostApi.m @@ -5,8 +5,37 @@ #import "FWFObjectHostApi.h" #import "FWFDataConverters.h" +@interface FWFObjectFlutterApi () +// This reference must be weak to prevent a circular reference with the objects it stores. +@property(nonatomic, weak) FWFInstanceManager *instanceManager; +@end + +@implementation FWFObjectFlutterApi +- (instancetype)initWithBinaryMessenger:(id)binaryMessenger + instanceManager:(FWFInstanceManager *)instanceManager { + self = [self initWithBinaryMessenger:binaryMessenger]; + if (self) { + _instanceManager = instanceManager; + } + return self; +} +@end + +@implementation FWFObject +- (instancetype)initWithBinaryMessenger:(id)binaryMessenger + instanceManager:(FWFInstanceManager *)instanceManager { + self = [self init]; + if (self) { + _objectApi = [[FWFObjectFlutterApi alloc] initWithBinaryMessenger:binaryMessenger + instanceManager:instanceManager]; + } + return self; +} +@end + @interface FWFObjectHostApiImpl () -@property(nonatomic) FWFInstanceManager *instanceManager; +// This reference must be weak to prevent a circular reference with the objects it stores. +@property(nonatomic, weak) FWFInstanceManager *instanceManager; @end @implementation FWFObjectHostApiImpl diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.modulemap b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.modulemap index 096507557688..639d89498d00 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.modulemap +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.modulemap @@ -7,5 +7,6 @@ framework module webview_flutter_wkwebview { explicit module Test { header "FlutterWebView_Test.h" header "FLTCookieManager_Test.h" + header "FWFInstanceManager_Test.h" } } diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/function_flutter_api_impls.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/function_flutter_api_impls.dart deleted file mode 100644 index ebbf032aa673..000000000000 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/function_flutter_api_impls.dart +++ /dev/null @@ -1,25 +0,0 @@ -// 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 'instance_manager.dart'; -import 'web_kit.pigeon.dart'; - -/// Flutter api to dispose functions. -class FunctionFlutterApiImpl extends FunctionFlutterApi { - /// Constructs a [FunctionFlutterApiImpl]. - FunctionFlutterApiImpl({InstanceManager? instanceManager}) { - this.instanceManager = instanceManager ?? InstanceManager.instance; - } - - /// Maintains instances stored to communicate with native language objects. - late final InstanceManager instanceManager; - - @override - void dispose(int identifier) { - final Function? function = instanceManager.getInstance(identifier); - if (function != null) { - instanceManager.removeWeakReference(function); - } - } -} diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/instance_manager.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/instance_manager.dart index b855022d57e5..c9c8c2324cc6 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/instance_manager.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/instance_manager.dart @@ -2,49 +2,199 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -/// Maintains instances stored to communicate with Objective-C objects. +import 'package:flutter/foundation.dart'; + +/// An immutable object that can provide functional copies of itself. +/// +/// All implementers are expected to be immutable as defined by the annotation. +@immutable +mixin Copyable { + /// Instantiates and returns a functionally identical object to oneself. + /// + /// Outside of tests, this method should only ever be called by + /// [InstanceManager]. + /// + /// Subclasses should always override their parent's implementation of this + /// method. + @protected + Copyable copy(); +} + +/// Maintains instances used to communicate with the native objects they +/// represent. +/// +/// Added instances are stored as weak references and their copies are stored +/// as strong references to maintain access to their variables and callback +/// methods. Both are stored with the same identifier. +/// +/// When a weak referenced instance becomes inaccessible, +/// [onWeakReferenceRemoved] is called with its associated identifier. +/// +/// If an instance is retrieved and has the possibility to be used, +/// (e.g. calling [getInstanceWithWeakReference]) a copy of the strong reference +/// is added as a weak reference with the same identifier. This prevents a +/// scenario where the weak referenced instance was released and then later +/// returned by the host platform. class InstanceManager { - final Map _strongInstances = {}; - final Map _identifiers = {}; + /// Constructs an [InstanceManager]. + InstanceManager({required void Function(int) onWeakReferenceRemoved}) { + this.onWeakReferenceRemoved = (int identifier) { + _weakInstances.remove(identifier); + onWeakReferenceRemoved(identifier); + }; + _finalizer = Finalizer(this.onWeakReferenceRemoved); + } + + // Identifiers are locked to a specific range to avoid collisions with objects + // created simultaneously by the host platform. + // Host uses identifiers >= 2^16 and Dart is expected to use values n where, + // 0 <= n < 2^16. + static const int _maxDartCreatedIdentifier = 65536; + // Expando is used because it doesn't prevent its keys from becoming + // inaccessible. This allows the manager to efficiently retrieve an identifier + // of an instance without holding a strong reference to that instance. + // + // It also doesn't use `==` to search for identifiers, which would lead to an + // infinite loop when comparing an object to its copy. (i.e. which was caused + // by calling instanceManager.getIdentifier() inside of `==` while this was a + // HashMap). + final Expando _identifiers = Expando(); + final Map> _weakInstances = + >{}; + final Map _strongInstances = {}; + late final Finalizer _finalizer; int _nextIdentifier = 0; - /// Global instance of [InstanceManager]. - static final InstanceManager instance = InstanceManager(); + /// Called when a weak referenced instance is removed by [removeWeakReference] + /// or becomes inaccessible. + late final void Function(int) onWeakReferenceRemoved; - /// Attempt to add a new instance. + /// Adds a new instance that was instantiated by Dart. + /// + /// In other words, Dart wants to add a new instance that will represent + /// an object that will be instantiated on the host platform. + /// + /// Throws assertion error if the instance has already been added. /// - /// Returns new if [instance] has already been added. Otherwise, it is added - /// with a new instance id. - int addDartCreatedInstance(Object instance) { + /// Returns the randomly generated id of the [instance] added. + int addDartCreatedInstance(Copyable instance) { assert(getIdentifier(instance) == null); - final int instanceId = _nextIdentifier++; - _identifiers[instance] = instanceId; - _strongInstances[instanceId] = instance; - return instanceId; + final int identifier = _nextUniqueIdentifier(); + _addInstanceWithIdentifier(instance, identifier); + return identifier; } - /// Remove the instance from the manager. + /// Removes the instance, if present, and call [onWeakReferenceRemoved] with + /// its identifier. /// - /// Returns null if the instance is removed. Otherwise, return the instanceId - /// of the removed instance. - int? removeWeakReference(T instance) { - final int? instanceId = _identifiers[instance]; - if (instanceId != null) { - _identifiers.remove(instance); - _strongInstances.remove(instanceId); + /// Returns the identifier associated with the removed instance. Otherwise, + /// `null` if the instance was not found in this manager. + /// + /// This does not remove the the strong referenced instance associated with + /// [instance]. This can be done with [remove]. + int? removeWeakReference(Copyable instance) { + final int? identifier = getIdentifier(instance); + if (identifier == null) { + return null; } - return instanceId; + + _identifiers[instance] = null; + _finalizer.detach(instance); + onWeakReferenceRemoved(identifier); + + return identifier; } - /// Retrieve the Object paired with instanceId. - T? getInstance(int identifier) { - return _strongInstances[identifier] as T?; + /// Removes [identifier] and its associated strongly referenced instance, if + /// present, from the manager. + /// + /// Returns the strong referenced instance associated with [identifier] before + /// it was removed. Returns `null` if [identifier] was not associated with + /// any strong reference. + /// + /// This does not remove the the weak referenced instance associtated with + /// [identifier]. This can be done with [removeWeakReference]. + T? remove(int identifier) { + return _strongInstances.remove(identifier) as T?; + } + + /// Retrieves the instance associated with identifier. + /// + /// The value returned is chosen from the following order: + /// + /// 1. A weakly referenced instance associated with identifier. + /// 2. If the only instance associated with identifier is a strongly + /// referenced instance, a copy of the instance is added as a weak reference + /// with the same identifier. Returning the newly created copy. + /// 3. If no instance is associated with identifier, returns null. + /// + /// This method also expects the host `InstanceManager` to have a strong + /// reference to the instance the identifier is associated with. + T? getInstanceWithWeakReference(int identifier) { + final Copyable? weakInstance = _weakInstances[identifier]?.target; + + if (weakInstance == null) { + final Copyable? strongInstance = _strongInstances[identifier]; + if (strongInstance != null) { + final Copyable copy = strongInstance.copy(); + _identifiers[copy] = identifier; + _weakInstances[identifier] = WeakReference(copy); + _finalizer.attach(copy, identifier, detach: copy); + return copy as T; + } + return strongInstance as T?; + } + + return weakInstance as T; } - /// Retrieve the instanceId paired with instance. - int? getIdentifier(Object instance) { + /// Retrieves the identifier associated with instance. + int? getIdentifier(Copyable instance) { return _identifiers[instance]; } + + /// Adds a new instance that was instantiated by the host platform. + /// + /// In other words, the host platform wants to add a new instance that + /// represents an object on the host platform. Stored with [identifier]. + /// + /// Throws assertion error if the instance or its identifier has already been + /// added. + /// + /// Returns unique identifier of the [instance] added. + void addHostCreatedInstance(Copyable instance, int identifier) { + assert(!containsIdentifier(identifier)); + assert(getIdentifier(instance) == null); + assert(identifier >= 0); + _addInstanceWithIdentifier(instance, identifier); + } + + void _addInstanceWithIdentifier(Copyable instance, int identifier) { + _identifiers[instance] = identifier; + _weakInstances[identifier] = WeakReference(instance); + _finalizer.attach(instance, identifier, detach: instance); + + final Copyable copy = instance.copy(); + _identifiers[copy] = identifier; + _strongInstances[identifier] = copy; + + assert(instance == copy); + } + + /// Whether this manager contains the given [identifier]. + bool containsIdentifier(int identifier) { + return _weakInstances.containsKey(identifier) || + _strongInstances.containsKey(identifier); + } + + int _nextUniqueIdentifier() { + late int identifier; + do { + identifier = _nextIdentifier; + _nextIdentifier = (_nextIdentifier + 1) % _maxDartCreatedIdentifier; + } while (containsIdentifier(identifier)); + return identifier; + } } diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/web_kit.pigeon.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/web_kit.pigeon.dart index 3072cb1cd0e7..9d928a38a2ef 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/web_kit.pigeon.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/web_kit.pigeon.dart @@ -1074,33 +1074,6 @@ class WKNavigationDelegateHostApi { return; } } - - Future setDidFinishNavigation( - int arg_identifier, int? arg_functionIdentifier) async { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.WKNavigationDelegateHostApi.setDidFinishNavigation', - codec, - binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_identifier, arg_functionIdentifier]) - as Map?; - if (replyMap == null) { - throw PlatformException( - code: 'channel-error', - message: 'Unable to establish connection on channel.', - ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; - throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], - ); - } else { - return; - } - } } class _WKNavigationDelegateFlutterApiCodec extends StandardMessageCodec { @@ -1111,8 +1084,7 @@ abstract class WKNavigationDelegateFlutterApi { static const MessageCodec codec = _WKNavigationDelegateFlutterApiCodec(); - void didFinishNavigation( - int functionIdentifier, int webViewIdentifier, String? url); + void didFinishNavigation(int identifier, int webViewIdentifier, String? url); static void setup(WKNavigationDelegateFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -1127,15 +1099,15 @@ abstract class WKNavigationDelegateFlutterApi { assert(message != null, 'Argument for dev.flutter.pigeon.WKNavigationDelegateFlutterApi.didFinishNavigation was null.'); final List args = (message as List?)!; - final int? arg_functionIdentifier = (args[0] as int?); - assert(arg_functionIdentifier != null, + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.WKNavigationDelegateFlutterApi.didFinishNavigation was null, expected non-null int.'); final int? arg_webViewIdentifier = (args[1] as int?); assert(arg_webViewIdentifier != null, 'Argument for dev.flutter.pigeon.WKNavigationDelegateFlutterApi.didFinishNavigation was null, expected non-null int.'); final String? arg_url = (args[2] as String?); api.didFinishNavigation( - arg_functionIdentifier!, arg_webViewIdentifier!, arg_url); + arg_identifier!, arg_webViewIdentifier!, arg_url); return; }); } @@ -1261,36 +1233,15 @@ class NSObjectHostApi { } } -class _FunctionFlutterApiCodec extends StandardMessageCodec { - const _FunctionFlutterApiCodec(); +class _NSObjectFlutterApiCodec extends StandardMessageCodec { + const _NSObjectFlutterApiCodec(); } -abstract class FunctionFlutterApi { - static const MessageCodec codec = _FunctionFlutterApiCodec(); +abstract class NSObjectFlutterApi { + static const MessageCodec codec = _NSObjectFlutterApiCodec(); - void dispose(int identifier); - static void setup(FunctionFlutterApi? api, - {BinaryMessenger? binaryMessenger}) { - { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.FunctionFlutterApi.dispose', codec, - binaryMessenger: binaryMessenger); - if (api == null) { - channel.setMessageHandler(null); - } else { - channel.setMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.FunctionFlutterApi.dispose was null.'); - final List args = (message as List?)!; - final int? arg_identifier = (args[0] as int?); - assert(arg_identifier != null, - 'Argument for dev.flutter.pigeon.FunctionFlutterApi.dispose was null, expected non-null int.'); - api.dispose(arg_identifier!); - return; - }); - } - } - } + static void setup(NSObjectFlutterApi? api, + {BinaryMessenger? binaryMessenger}) {} } class _WKWebViewHostApiCodec extends StandardMessageCodec { diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation.dart index 5c127c5654c6..ca3ba780435c 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation.dart @@ -233,18 +233,31 @@ class NSHttpCookie { } /// The root class of most Objective-C class hierarchies. -class NSObject { - /// Constructs an [NSObject]. - NSObject({BinaryMessenger? binaryMessenger, InstanceManager? instanceManager}) - : _api = NSObjectHostApiImpl( +@immutable +class NSObject with Copyable { + // TODO(bparrishMines): Change constructor name to `detached`. + /// Constructs a [NSObject] without creating the associated + /// Objective-C object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies. + NSObject({ + BinaryMessenger? binaryMessenger, + InstanceManager? instanceManager, + }) : _api = NSObjectHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ) { - // Ensures FlutterApis for the Foundation library and FunctionFlutterApi are - // set up. + // Ensures FlutterApis for the Foundation library are set up. FoundationFlutterApis.instance.ensureSetUp(); } + /// Global instance of [InstanceManager]. + static final InstanceManager globalInstanceManager = + InstanceManager(onWeakReferenceRemoved: (int instanceId) { + NSObjectHostApiImpl().dispose(instanceId); + }); + final NSObjectHostApiImpl _api; /// Registers the observer object to receive KVO notifications. @@ -268,8 +281,8 @@ class NSObject { } /// Release the reference to the Objective-C object. - Future dispose() { - return _api.disposeForInstances(this); + static void dispose(NSObject instance) { + instance._api.instanceManager.removeWeakReference(instance); } /// Informs the observing object when the value at the specified key path has changed. @@ -283,4 +296,25 @@ class NSObject { ) { throw UnimplementedError(); } + + @override + Copyable copy() { + return NSObject( + binaryMessenger: _api.binaryMessenger, + instanceManager: _api.instanceManager, + ); + } + + @override + int get hashCode { + return Object.hash(_api, _api.instanceManager.getIdentifier(this)); + } + + @override + bool operator ==(Object other) { + return other is NSObject && + _api == other._api && + _api.instanceManager.getIdentifier(this) == + other._api.instanceManager.getIdentifier(other); + } } diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation_api_impls.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation_api_impls.dart index 5e5e577a4f41..9575cb51c648 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation_api_impls.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation_api_impls.dart @@ -5,7 +5,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; -import '../common/function_flutter_api_impls.dart'; import '../common/instance_manager.dart'; import '../common/web_kit.pigeon.dart'; import 'foundation.dart'; @@ -38,16 +37,16 @@ Iterable } /// Handles initialization of Flutter APIs for the Foundation library. +// TODO(bparrishMines): Add NSObjectFlutterApiImpl once the callback methods +// are added. class FoundationFlutterApis { /// Constructs a [FoundationFlutterApis]. @visibleForTesting FoundationFlutterApis({ BinaryMessenger? binaryMessenger, + // ignore: avoid_unused_constructor_parameters InstanceManager? instanceManager, - }) : _binaryMessenger = binaryMessenger { - functionFlutterApi = - FunctionFlutterApiImpl(instanceManager: instanceManager); - } + }) : _binaryMessenger = binaryMessenger; static FoundationFlutterApis _instance = FoundationFlutterApis(); @@ -62,36 +61,33 @@ class FoundationFlutterApis { return _instance; } + // ignore: unused_field final BinaryMessenger? _binaryMessenger; bool _hasBeenSetUp = false; - /// Flutter Api for disposing functions. - /// - /// This FlutterApi is placed here because [FoundationFlutterApis.ensureSetUp] - /// is called inside [NSObject] and [NSObject] is the parent class of all - /// objects. - @visibleForTesting - late final FunctionFlutterApiImpl functionFlutterApi; - /// Ensures all the Flutter APIs have been set up to receive calls from native code. void ensureSetUp() { if (!_hasBeenSetUp) { - FunctionFlutterApi.setup( - functionFlutterApi, - binaryMessenger: _binaryMessenger, - ); _hasBeenSetUp = true; } } } /// Host api implementation for [NSObject]. +@immutable class NSObjectHostApiImpl extends NSObjectHostApi { /// Constructs an [NSObjectHostApiImpl]. NSObjectHostApiImpl({ - super.binaryMessenger, + this.binaryMessenger, InstanceManager? instanceManager, - }) : instanceManager = instanceManager ?? InstanceManager.instance; + }) : instanceManager = instanceManager ?? NSObject.globalInstanceManager, + super(binaryMessenger: binaryMessenger); + + /// Sends binary data across the Flutter platform barrier. + /// + /// If it is null, the default BinaryMessenger will be used which routes to + /// the host platform. + final BinaryMessenger? binaryMessenger; /// Maintains instances stored to communicate with Objective-C objects. final InstanceManager instanceManager; @@ -124,11 +120,15 @@ class NSObjectHostApiImpl extends NSObjectHostApi { ); } - /// Calls [dispose] with the ids of the provided object instances. - Future disposeForInstances(NSObject instance) async { - final int? identifier = instanceManager.removeWeakReference(instance); - if (identifier != null) { - await dispose(identifier); - } + @override + int get hashCode { + return Object.hash(binaryMessenger, instanceManager); + } + + @override + bool operator ==(Object other) { + return other is NSObjectHostApiImpl && + binaryMessenger == other.binaryMessenger && + instanceManager == other.instanceManager; } } diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/ui_kit/ui_kit.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/ui_kit/ui_kit.dart index 7c1bdd01724c..1d89b18c9f05 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/ui_kit/ui_kit.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/ui_kit/ui_kit.dart @@ -12,6 +12,10 @@ import '../foundation/foundation.dart'; import '../web_kit/web_kit.dart'; import 'ui_kit_api_impls.dart'; +// TODO(bparrishMines): All subclasses of NSObject need to pass their +// InstanceManager and BinaryMessenger to its parent. They also need to +// override copy(); + /// A view that allows the scrolling and zooming of its contained views. /// /// Wraps [UIScrollView](https://developer.apple.com/documentation/uikit/uiscrollview?language=objc). diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/ui_kit/ui_kit_api_impls.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/ui_kit/ui_kit_api_impls.dart index 1d962ee788db..e921c4bad15a 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/ui_kit/ui_kit_api_impls.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/ui_kit/ui_kit_api_impls.dart @@ -7,6 +7,8 @@ import 'dart:math'; import 'package:flutter/painting.dart' show Color; +import 'package:webview_flutter_wkwebview/src/foundation/foundation.dart'; + import '../common/instance_manager.dart'; import '../common/web_kit.pigeon.dart'; import '../web_kit/web_kit.dart'; @@ -18,7 +20,7 @@ class UIScrollViewHostApiImpl extends UIScrollViewHostApi { UIScrollViewHostApiImpl({ super.binaryMessenger, InstanceManager? instanceManager, - }) : instanceManager = instanceManager ?? InstanceManager.instance; + }) : instanceManager = instanceManager ?? NSObject.globalInstanceManager; /// Maintains instances stored to communicate with Objective-C objects. final InstanceManager instanceManager; @@ -75,7 +77,7 @@ class UIViewHostApiImpl extends UIViewHostApi { UIViewHostApiImpl({ super.binaryMessenger, InstanceManager? instanceManager, - }) : instanceManager = instanceManager ?? InstanceManager.instance; + }) : instanceManager = instanceManager ?? NSObject.globalInstanceManager; /// Maintains instances stored to communicate with Objective-C objects. final InstanceManager instanceManager; diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit.dart index 16490a24d474..2b887e97adcf 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit.dart @@ -10,6 +10,10 @@ import '../foundation/foundation.dart'; import '../ui_kit/ui_kit.dart'; import 'web_kit_api_impls.dart'; +// TODO(bparrishMines): All subclasses of NSObject need to pass their +// InstanceManager and BinaryMessenger to its parent. They also need to +// override copy(): https://github.com/flutter/flutter/issues/105245 + /// Times at which to inject script content into a webpage. /// /// Wraps [WKUserScriptInjectionTime](https://developer.apple.com/documentation/webkit/wkuserscriptinjectiontime?language=objc). @@ -210,7 +214,7 @@ class WKScriptMessage { /// Encapsulates the standard behaviors to apply to websites. /// /// Wraps [WKPreferences](https://developer.apple.com/documentation/webkit/wkpreferences?language=objc). -class WKPreferences { +class WKPreferences extends NSObject { /// Constructs a [WKPreferences] that is owned by [configuration]. @visibleForTesting WKPreferences.fromWebViewConfiguration( @@ -241,7 +245,7 @@ class WKPreferences { /// Manages cookies, disk and memory caches, and other types of data for a web view. /// /// Wraps [WKWebsiteDataStore](https://developer.apple.com/documentation/webkit/wkwebsitedatastore?language=objc). -class WKWebsiteDataStore { +class WKWebsiteDataStore extends NSObject { WKWebsiteDataStore._({ BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, @@ -343,7 +347,7 @@ class WKHttpCookieStore extends NSObject { /// An interface for receiving messages from JavaScript code running in a webpage. /// /// Wraps [WKScriptMessageHandler](https://developer.apple.com/documentation/webkit/wkscriptmessagehandler?language=objc) -class WKScriptMessageHandler { +class WKScriptMessageHandler extends NSObject { /// Constructs a [WKScriptMessageHandler]. WKScriptMessageHandler({ BinaryMessenger? binaryMessenger, @@ -382,7 +386,7 @@ class WKScriptMessageHandler { /// code. /// /// Wraps [WKUserContentController](https://developer.apple.com/documentation/webkit/wkusercontentcontroller?language=objc). -class WKUserContentController { +class WKUserContentController extends NSObject { /// Constructs a [WKUserContentController] that is owned by [configuration]. @visibleForTesting WKUserContentController.fromWebViewConfiguration( @@ -465,7 +469,7 @@ class WKUserContentController { /// A collection of properties that you use to initialize a web view. /// /// Wraps [WKWebViewConfiguration](https://developer.apple.com/documentation/webkit/wkwebviewconfiguration?language=objc). -class WKWebViewConfiguration { +class WKWebViewConfiguration extends NSObject { /// Constructs a [WKWebViewConfiguration]. factory WKWebViewConfiguration({ BinaryMessenger? binaryMessenger, @@ -565,7 +569,7 @@ class WKWebViewConfiguration { /// The methods for presenting native user interface elements on behalf of a webpage. /// /// Wraps [WKUIDelegate](https://developer.apple.com/documentation/webkit/wkuidelegate?language=objc). -class WKUIDelegate { +class WKUIDelegate extends NSObject { /// Constructs a [WKUIDelegate]. WKUIDelegate({ BinaryMessenger? binaryMessenger, @@ -597,9 +601,11 @@ class WKUIDelegate { /// coordinate changes in your web view’s main frame. /// /// Wraps [WKNavigationDelegate](https://developer.apple.com/documentation/webkit/wknavigationdelegate?language=objc). +@immutable class WKNavigationDelegate extends NSObject { /// Constructs a [WKNavigationDelegate]. WKNavigationDelegate({ + this.didFinishNavigation, super.binaryMessenger, super.instanceManager, }) : _navigationDelegateApi = WKNavigationDelegateHostApiImpl( @@ -610,8 +616,25 @@ class WKNavigationDelegate extends NSObject { _navigationDelegateApi.createForInstances(this); } + /// Constructs a [WKNavigationDelegate] without creating the associated + /// Objective-C object. + /// + /// This should only be used outside of tests by subclasses created by this + /// library or to create a copy for an InstanceManager. + WKNavigationDelegate.detached({ + this.didFinishNavigation, + super.binaryMessenger, + super.instanceManager, + }) : _navigationDelegateApi = WKNavigationDelegateHostApiImpl( + binaryMessenger: binaryMessenger, + instanceManager: instanceManager, + ); + final WKNavigationDelegateHostApiImpl _navigationDelegateApi; + /// Called when navigation is complete. + final void Function(WKWebView webView, String? url)? didFinishNavigation; + /// Called when navigation from the main frame has started. Future setDidStartProvisionalNavigation( void Function(WKWebView webView, String? url)? @@ -620,16 +643,6 @@ class WKNavigationDelegate extends NSObject { throw UnimplementedError(); } - /// Called when navigation is complete. - Future setDidFinishNavigation( - void Function(WKWebView webView, String? url)? didFinishNavigation, - ) { - return _navigationDelegateApi.setDidFinishNavigationFromInstance( - this, - didFinishNavigation, - ); - } - /// Called when permission is needed to navigate to new content. Future setDecidePolicyForNavigationAction( Future Function( @@ -661,6 +674,30 @@ class WKNavigationDelegate extends NSObject { ) { throw UnimplementedError(); } + + @override + Copyable copy() { + return WKNavigationDelegate.detached( + didFinishNavigation: didFinishNavigation, + binaryMessenger: _navigationDelegateApi.binaryMessenger, + instanceManager: _navigationDelegateApi.instanceManager, + ); + } + + @override + int get hashCode { + return Object.hash(didFinishNavigation, _navigationDelegateApi, + _navigationDelegateApi.instanceManager.getIdentifier(this)); + } + + @override + bool operator ==(Object other) { + return other is WKNavigationDelegate && + didFinishNavigation == other.didFinishNavigation && + _navigationDelegateApi == other._navigationDelegateApi && + _navigationDelegateApi.instanceManager.getIdentifier(this) == + other._navigationDelegateApi.instanceManager.getIdentifier(other); + } } /// Object that displays interactive web content, such as for an in-app browser. diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit_api_impls.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit_api_impls.dart index fe4d7d85d2d2..85b9b492eb87 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit_api_impls.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit_api_impls.dart @@ -226,7 +226,7 @@ class WKWebsiteDataStoreHostApiImpl extends WKWebsiteDataStoreHostApi { WKWebsiteDataStoreHostApiImpl({ super.binaryMessenger, InstanceManager? instanceManager, - }) : instanceManager = instanceManager ?? InstanceManager.instance; + }) : instanceManager = instanceManager ?? NSObject.globalInstanceManager; /// Maintains instances stored to communicate with Objective-C objects. final InstanceManager instanceManager; @@ -247,7 +247,8 @@ class WKWebsiteDataStoreHostApiImpl extends WKWebsiteDataStoreHostApi { WKWebsiteDataStore instance, ) { return createDefaultDataStore( - instanceManager.addDartCreatedInstance(instance)); + instanceManager.addDartCreatedInstance(instance), + ); } /// Calls [removeDataOfTypes] with the ids of the provided object instances. @@ -270,7 +271,7 @@ class WKScriptMessageHandlerHostApiImpl extends WKScriptMessageHandlerHostApi { WKScriptMessageHandlerHostApiImpl({ super.binaryMessenger, InstanceManager? instanceManager, - }) : instanceManager = instanceManager ?? InstanceManager.instance; + }) : instanceManager = instanceManager ?? NSObject.globalInstanceManager; /// Maintains instances stored to communicate with Objective-C objects. final InstanceManager instanceManager; @@ -287,7 +288,7 @@ class WKPreferencesHostApiImpl extends WKPreferencesHostApi { WKPreferencesHostApiImpl({ super.binaryMessenger, InstanceManager? instanceManager, - }) : instanceManager = instanceManager ?? InstanceManager.instance; + }) : instanceManager = instanceManager ?? NSObject.globalInstanceManager; /// Maintains instances stored to communicate with Objective-C objects. final InstanceManager instanceManager; @@ -321,7 +322,7 @@ class WKHttpCookieStoreHostApiImpl extends WKHttpCookieStoreHostApi { WKHttpCookieStoreHostApiImpl({ super.binaryMessenger, InstanceManager? instanceManager, - }) : instanceManager = instanceManager ?? InstanceManager.instance; + }) : instanceManager = instanceManager ?? NSObject.globalInstanceManager; /// Maintains instances stored to communicate with Objective-C objects. final InstanceManager instanceManager; @@ -356,7 +357,7 @@ class WKUserContentControllerHostApiImpl WKUserContentControllerHostApiImpl({ super.binaryMessenger, InstanceManager? instanceManager, - }) : instanceManager = instanceManager ?? InstanceManager.instance; + }) : instanceManager = instanceManager ?? NSObject.globalInstanceManager; /// Maintains instances stored to communicate with Objective-C objects. final InstanceManager instanceManager; @@ -430,7 +431,7 @@ class WKWebViewConfigurationHostApiImpl extends WKWebViewConfigurationHostApi { WKWebViewConfigurationHostApiImpl({ super.binaryMessenger, InstanceManager? instanceManager, - }) : instanceManager = instanceManager ?? InstanceManager.instance; + }) : instanceManager = instanceManager ?? NSObject.globalInstanceManager; /// Maintains instances stored to communicate with Objective-C objects. final InstanceManager instanceManager; @@ -480,47 +481,51 @@ class WKUIDelegateHostApiImpl extends WKUIDelegateHostApi { WKUIDelegateHostApiImpl({ super.binaryMessenger, InstanceManager? instanceManager, - }) : instanceManager = instanceManager ?? InstanceManager.instance; + }) : instanceManager = instanceManager ?? NSObject.globalInstanceManager; /// Maintains instances stored to communicate with Objective-C objects. final InstanceManager instanceManager; /// Calls [create] with the ids of the provided object instances. - Future createForInstances(WKUIDelegate instance) { + Future createForInstances(WKUIDelegate instance) async { return create(instanceManager.addDartCreatedInstance(instance)); } } /// Host api implementation for [WKNavigationDelegate]. +@immutable class WKNavigationDelegateHostApiImpl extends WKNavigationDelegateHostApi { /// Constructs a [WKNavigationDelegateHostApiImpl]. WKNavigationDelegateHostApiImpl({ - super.binaryMessenger, + this.binaryMessenger, InstanceManager? instanceManager, - }) : instanceManager = instanceManager ?? InstanceManager.instance; + }) : instanceManager = instanceManager ?? NSObject.globalInstanceManager, + super(binaryMessenger: binaryMessenger); + + /// Sends binary data across the Flutter platform barrier. + /// + /// If it is null, the default BinaryMessenger will be used which routes to + /// the host platform. + final BinaryMessenger? binaryMessenger; /// Maintains instances stored to communicate with Objective-C objects. final InstanceManager instanceManager; /// Calls [create] with the ids of the provided object instances. - Future createForInstances(WKNavigationDelegate instance) { + Future createForInstances(WKNavigationDelegate instance) async { return create(instanceManager.addDartCreatedInstance(instance)); } - /// Calls [setDidFinishNavigation] with the ids of the provided object instances. - Future setDidFinishNavigationFromInstance( - WKNavigationDelegate instance, - void Function(WKWebView, String?)? didFinishNavigation, - ) { - int? functionInstanceId; - if (didFinishNavigation != null) { - functionInstanceId = instanceManager.getIdentifier(didFinishNavigation) ?? - instanceManager.addDartCreatedInstance(didFinishNavigation); - } - return setDidFinishNavigation( - instanceManager.getIdentifier(instance)!, - functionInstanceId, - ); + @override + int get hashCode { + return Object.hash(binaryMessenger, instanceManager); + } + + @override + bool operator ==(Object other) { + return other is WKNavigationDelegateHostApiImpl && + binaryMessenger == other.binaryMessenger && + instanceManager == other.instanceManager; } } @@ -529,23 +534,29 @@ class WKNavigationDelegateFlutterApiImpl extends WKNavigationDelegateFlutterApi { /// Constructs a [WKNavigationDelegateFlutterApiImpl]. WKNavigationDelegateFlutterApiImpl({InstanceManager? instanceManager}) { - this.instanceManager = instanceManager ?? InstanceManager.instance; + this.instanceManager = instanceManager ?? NSObject.globalInstanceManager; } /// Maintains instances stored to communicate with native language objects. late final InstanceManager instanceManager; + WKNavigationDelegate _getDelegate(int identifier) { + return instanceManager.getInstanceWithWeakReference(identifier)!; + } + @override void didFinishNavigation( - int functionIdentifier, + int identifier, int webViewIdentifier, String? url, ) { - final void Function( - WKWebView webView, - String? url, - ) function = instanceManager.getInstance(functionIdentifier)!; - function(instanceManager.getInstance(webViewIdentifier)!, url); + final void Function(WKWebView, String?)? function = + _getDelegate(identifier).didFinishNavigation; + function?.call( + instanceManager.getInstanceWithWeakReference(webViewIdentifier)! + as WKWebView, + url, + ); } } @@ -555,7 +566,7 @@ class WKWebViewHostApiImpl extends WKWebViewHostApi { WKWebViewHostApiImpl({ super.binaryMessenger, InstanceManager? instanceManager, - }) : instanceManager = instanceManager ?? InstanceManager.instance; + }) : instanceManager = instanceManager ?? NSObject.globalInstanceManager; /// Maintains instances stored to communicate with Objective-C objects. final InstanceManager instanceManager; @@ -573,7 +584,9 @@ class WKWebViewHostApiImpl extends WKWebViewHostApi { /// Calls [loadRequest] with the ids of the provided object instances. Future loadRequestForInstances( - WKWebView webView, NSUrlRequest request) { + WKWebView webView, + NSUrlRequest request, + ) { return loadRequest( instanceManager.getIdentifier(webView)!, request.toNSUrlRequestData(), diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit_webview_widget.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit_webview_widget.dart index 7001c3131147..90f1554bf99b 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit_webview_widget.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit_webview_widget.dart @@ -117,13 +117,14 @@ class WebKitWebViewPlatformController extends WebViewPlatformController { /// Methods for handling navigation changes and tracking navigation requests. @visibleForTesting late final WKNavigationDelegate navigationDelegate = - webViewProxy.createNavigationDelegate() + webViewProxy.createNavigationDelegate( + didFinishNavigation: (WKWebView webView, String? url) { + callbacksHandler.onPageFinished(url ?? ''); + }, + ) ..setDidStartProvisionalNavigation((WKWebView webView, String? url) { callbacksHandler.onPageStarted(url ?? ''); }) - ..setDidFinishNavigation((WKWebView webView, String? url) { - callbacksHandler.onPageFinished(url ?? ''); - }) ..setDidFailNavigation((WKWebView webView, NSError error) { callbacksHandler.onWebResourceError(_toWebResourceError(error)); }) @@ -619,7 +620,13 @@ class WebViewWidgetProxy { } /// Constructs a [WKNavigationDelegate]. - WKNavigationDelegate createNavigationDelegate() { - return WKNavigationDelegate(); + WKNavigationDelegate createNavigationDelegate({ + void Function( + WKWebView webView, + String? url, + )? + didFinishNavigation, + }) { + return WKNavigationDelegate(didFinishNavigation: didFinishNavigation); } } diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pigeons/web_kit.dart b/packages/webview_flutter/webview_flutter_wkwebview/pigeons/web_kit.dart index 41806d3f4601..91541e8e741b 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/pigeons/web_kit.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/pigeons/web_kit.dart @@ -380,14 +380,9 @@ abstract class WKScriptMessageHandlerHostApi { abstract class WKNavigationDelegateHostApi { @ObjCSelector('createWithIdentifier:') void create(int identifier); - - @ObjCSelector( - 'setDidFinishNavigationForDelegateWithIdentifier:functionIdentifier:', - ) - void setDidFinishNavigation(int identifier, int? functionIdentifier); } -/// Mirror of WKNavigationDelegate. +/// Handles callbacks from an WKNavigationDelegate instance. /// /// See https://developer.apple.com/documentation/webkit/wknavigationdelegate?language=objc. @FlutterApi() @@ -396,7 +391,7 @@ abstract class WKNavigationDelegateFlutterApi { 'didFinishNavigationForDelegateWithIdentifier:webViewIdentifier:URL:', ) void didFinishNavigation( - int functionIdentifier, + int identifier, int webViewIdentifier, String? url, ); @@ -426,12 +421,11 @@ abstract class NSObjectHostApi { void removeObserver(int identifier, int observerIdentifier, String keyPath); } -/// Disposes references to functions. +/// Handles callbacks from an NSObject instance. +/// +/// See https://developer.apple.com/documentation/objectivec/nsobject. @FlutterApi() -abstract class FunctionFlutterApi { - @ObjCSelector('disposeFunctionWithIdentifier:') - void dispose(int identifier); -} +abstract class NSObjectFlutterApi {} /// Mirror of WKWebView. /// diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/common/function_flutter_api_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/common/function_flutter_api_test.dart deleted file mode 100644 index 74264bf7e577..000000000000 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/common/function_flutter_api_test.dart +++ /dev/null @@ -1,34 +0,0 @@ -// 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 'package:flutter_test/flutter_test.dart'; -import 'package:webview_flutter_wkwebview/src/common/instance_manager.dart'; -import 'package:webview_flutter_wkwebview/src/common/web_kit.pigeon.dart'; -import 'package:webview_flutter_wkwebview/src/foundation/foundation_api_impls.dart'; - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - group('$FunctionFlutterApi', () { - late InstanceManager instanceManager; - - setUp(() { - instanceManager = InstanceManager(); - }); - - test('dispose', () { - void function() {} - final int functionInstanceId = - instanceManager.addDartCreatedInstance(function); - - FoundationFlutterApis.instance = FoundationFlutterApis( - instanceManager: instanceManager, - )..ensureSetUp(); - - FoundationFlutterApis.instance.functionFlutterApi - .dispose(functionInstanceId); - expect(instanceManager.getIdentifier(function), isNull); - }); - }); -} diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/common/instance_manager_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/common/instance_manager_test.dart index dad44f9cd535..2fc68a489b6a 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/common/instance_manager_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/common/instance_manager_test.dart @@ -7,28 +7,147 @@ import 'package:webview_flutter_wkwebview/src/common/instance_manager.dart'; void main() { group('InstanceManager', () { - late InstanceManager testInstanceManager; + test('addHostCreatedInstance', () { + final CopyableObject object = CopyableObject(); - setUp(() { - testInstanceManager = InstanceManager(); + final InstanceManager instanceManager = + InstanceManager(onWeakReferenceRemoved: (_) {}); + + instanceManager.addHostCreatedInstance(object, 0); + + expect(instanceManager.getIdentifier(object), 0); + expect( + instanceManager.getInstanceWithWeakReference(0), + object, + ); + }); + + test('addHostCreatedInstance prevents already used objects and ids', () { + final CopyableObject object = CopyableObject(); + + final InstanceManager instanceManager = + InstanceManager(onWeakReferenceRemoved: (_) {}); + + instanceManager.addHostCreatedInstance(object, 0); + + expect( + () => instanceManager.addHostCreatedInstance(object, 0), + throwsAssertionError, + ); + + expect( + () => instanceManager.addHostCreatedInstance(CopyableObject(), 0), + throwsAssertionError, + ); + }); + + test('addFlutterCreatedInstance', () { + final CopyableObject object = CopyableObject(); + + final InstanceManager instanceManager = + InstanceManager(onWeakReferenceRemoved: (_) {}); + + instanceManager.addDartCreatedInstance(object); + + final int? instanceId = instanceManager.getIdentifier(object); + expect(instanceId, isNotNull); + expect( + instanceManager.getInstanceWithWeakReference(instanceId!), + object, + ); + }); + + test('removeWeakReference', () { + final CopyableObject object = CopyableObject(); + + int? weakInstanceId; + final InstanceManager instanceManager = + InstanceManager(onWeakReferenceRemoved: (int instanceId) { + weakInstanceId = instanceId; + }); + + instanceManager.addHostCreatedInstance(object, 0); + + expect(instanceManager.removeWeakReference(object), 0); + expect( + instanceManager.getInstanceWithWeakReference(0), + isA(), + ); + expect(weakInstanceId, 0); }); - test('tryAddInstance', () { - final Object object = Object(); + test('removeWeakReference removes only weak reference', () { + final CopyableObject object = CopyableObject(); + + final InstanceManager instanceManager = + InstanceManager(onWeakReferenceRemoved: (_) {}); + + instanceManager.addHostCreatedInstance(object, 0); - expect(testInstanceManager.addDartCreatedInstance(object), 0); - expect(testInstanceManager.getIdentifier(object), 0); - expect(testInstanceManager.getInstance(0), object); + expect(instanceManager.removeWeakReference(object), 0); + final CopyableObject copy = instanceManager.getInstanceWithWeakReference( + 0, + )!; + expect(identical(object, copy), isFalse); }); - test('removeInstance', () { - final Object object = Object(); - testInstanceManager.addDartCreatedInstance(object); + test('removeStrongReference', () { + final CopyableObject object = CopyableObject(); - expect(testInstanceManager.removeWeakReference(object), 0); - expect(testInstanceManager.getIdentifier(object), null); - expect(testInstanceManager.getInstance(0), null); - expect(testInstanceManager.removeWeakReference(object), null); + final InstanceManager instanceManager = + InstanceManager(onWeakReferenceRemoved: (_) {}); + + instanceManager.addHostCreatedInstance(object, 0); + instanceManager.removeWeakReference(object); + expect(instanceManager.remove(0), isA()); + expect(instanceManager.containsIdentifier(0), isFalse); + }); + + test('removeStrongReference removes only strong reference', () { + final CopyableObject object = CopyableObject(); + + final InstanceManager instanceManager = + InstanceManager(onWeakReferenceRemoved: (_) {}); + + instanceManager.addHostCreatedInstance(object, 0); + expect(instanceManager.remove(0), isA()); + expect( + instanceManager.getInstanceWithWeakReference(0), + object, + ); + }); + + test('getInstance can add a new weak reference', () { + final CopyableObject object = CopyableObject(); + + final InstanceManager instanceManager = + InstanceManager(onWeakReferenceRemoved: (_) {}); + + instanceManager.addHostCreatedInstance(object, 0); + instanceManager.removeWeakReference(object); + + final CopyableObject newWeakCopy = + instanceManager.getInstanceWithWeakReference( + 0, + )!; + expect(identical(object, newWeakCopy), isFalse); }); }); } + +class CopyableObject with Copyable { + @override + Copyable copy() { + return CopyableObject(); + } + + @override + int get hashCode { + return 0; + } + + @override + bool operator ==(Object other) { + return other is CopyableObject; + } +} diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/common/test_web_kit.pigeon.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/common/test_web_kit.pigeon.dart index 4a0002569058..00865d66f2bc 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/common/test_web_kit.pigeon.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/common/test_web_kit.pigeon.dart @@ -707,7 +707,6 @@ abstract class TestWKNavigationDelegateHostApi { _TestWKNavigationDelegateHostApiCodec(); void create(int identifier); - void setDidFinishNavigation(int identifier, int? functionIdentifier); static void setup(TestWKNavigationDelegateHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -729,27 +728,6 @@ abstract class TestWKNavigationDelegateHostApi { }); } } - { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.WKNavigationDelegateHostApi.setDidFinishNavigation', - codec, - binaryMessenger: binaryMessenger); - if (api == null) { - channel.setMockMessageHandler(null); - } else { - channel.setMockMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.WKNavigationDelegateHostApi.setDidFinishNavigation was null.'); - final List args = (message as List?)!; - final int? arg_identifier = (args[0] as int?); - assert(arg_identifier != null, - 'Argument for dev.flutter.pigeon.WKNavigationDelegateHostApi.setDidFinishNavigation was null, expected non-null int.'); - final int? arg_functionIdentifier = (args[1] as int?); - api.setDidFinishNavigation(arg_identifier!, arg_functionIdentifier); - return {}; - }); - } - } } } diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.dart index 03bb7747a048..ca84369c9789 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.dart @@ -22,7 +22,7 @@ void main() { late InstanceManager instanceManager; setUp(() { - instanceManager = InstanceManager(); + instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); }); group('NSObject', () { @@ -88,12 +88,17 @@ void main() { }); test('dispose', () async { - final int instanceId = instanceManager.getIdentifier(object)!; + int? callbackIdentifier; + final InstanceManager instanceManager = + InstanceManager(onWeakReferenceRemoved: (int identifier) { + callbackIdentifier = identifier; + }); - await object.dispose(); - verify( - mockPlatformHostApi.dispose(instanceId), - ); + final NSObject object = NSObject(instanceManager: instanceManager); + final int identifier = instanceManager.addDartCreatedInstance(object); + + NSObject.dispose(object); + expect(callbackIdentifier, identifier); }); }); }); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.dart index ef68bbb41809..aeb4e1cc54ce 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.dart @@ -28,7 +28,7 @@ void main() { late InstanceManager instanceManager; setUp(() { - instanceManager = InstanceManager(); + instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); }); group('UIScrollView', () { diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.dart index da92ab71999b..f486474b4cfb 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.dart @@ -35,7 +35,7 @@ void main() { late WebKitFlutterApis flutterApis; setUp(() { - instanceManager = InstanceManager(); + instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); flutterApis = WebKitFlutterApis(instanceManager: instanceManager); WebKitFlutterApis.instance = flutterApis; }); @@ -81,7 +81,7 @@ void main() { WKWebsiteDataStore.defaultDataStore; verify( mockPlatformHostApi.createDefaultDataStore( - InstanceManager.instance.getIdentifier(defaultDataStore), + NSObject.globalInstanceManager.getIdentifier(defaultDataStore), ), ); }); @@ -438,29 +438,33 @@ void main() { }); test('create', () async { + navigationDelegate = WKNavigationDelegate( + instanceManager: instanceManager, + ); + verify(mockPlatformHostApi.create( instanceManager.getIdentifier(navigationDelegate), )); }); - test('setDidFinishNavigation', () async { + test('didFinishNavigation', () async { final Completer> argsCompleter = Completer>(); - navigationDelegate.setDidFinishNavigation( - (WKWebView webView, String? url) { + WebKitFlutterApis.instance = WebKitFlutterApis( + instanceManager: instanceManager, + ); + + navigationDelegate = WKNavigationDelegate( + instanceManager: instanceManager, + didFinishNavigation: (WKWebView webView, String? url) { argsCompleter.complete([webView, url]); }, ); - final int functionInstanceId = - verify(mockPlatformHostApi.setDidFinishNavigation( - instanceManager.getIdentifier(navigationDelegate), - captureAny, - )).captured.single as int; - - flutterApis.navigationDelegateFlutterApi.didFinishNavigation( - functionInstanceId, + WebKitFlutterApis.instance.navigationDelegateFlutterApi + .didFinishNavigation( + instanceManager.getIdentifier(navigationDelegate)!, instanceManager.getIdentifier(webView)!, 'url', ); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.mocks.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.mocks.dart index 4ffb7d4c19d3..16d80b22c22b 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.mocks.dart @@ -56,12 +56,6 @@ class MockTestWKNavigationDelegateHostApi extends _i1.Mock void create(int? instanceId) => super.noSuchMethod(Invocation.method(#create, [instanceId]), returnValueForMissingStub: null); - @override - void setDidFinishNavigation(int? instanceId, int? functionInstanceId) => - super.noSuchMethod( - Invocation.method( - #setDidFinishNavigation, [instanceId, functionInstanceId]), - returnValueForMissingStub: null); } /// A class which mocks [TestWKPreferencesHostApi]. @@ -311,9 +305,9 @@ class MockTestWKWebsiteDataStoreHostApi extends _i1.Mock _i3.Future removeDataOfTypes( int? instanceId, List<_i4.WKWebsiteDataTypeEnumData?>? dataTypes, - double? secondsModifiedSinceEpoch) => + double? modificationTimeInSecondsSinceEpoch) => (super.noSuchMethod( Invocation.method(#removeDataOfTypes, - [instanceId, dataTypes, secondsModifiedSinceEpoch]), + [instanceId, dataTypes, modificationTimeInSecondsSinceEpoch]), returnValue: Future.value(false)) as _i3.Future); } diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit_cookie_manager_test.mocks.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit_cookie_manager_test.mocks.dart index 8d7751da785f..5989c138ff25 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit_cookie_manager_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit_cookie_manager_test.mocks.dart @@ -2,12 +2,14 @@ // in webview_flutter_wkwebview/example/ios/.symlinks/plugins/webview_flutter_wkwebview/test/src/web_kit_cookie_manager_test.dart. // Do not manually edit this file. -import 'dart:async' as _i3; +import 'dart:async' as _i4; import 'package:mockito/mockito.dart' as _i1; +import 'package:webview_flutter_wkwebview/src/common/instance_manager.dart' + as _i2; import 'package:webview_flutter_wkwebview/src/foundation/foundation.dart' - as _i4; -import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart' as _i2; + as _i5; +import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart' as _i3; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -19,68 +21,93 @@ import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart' as _i2; // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types -class _FakeWKHttpCookieStore_0 extends _i1.Fake - implements _i2.WKHttpCookieStore {} +class _FakeCopyable_0 extends _i1.Fake implements _i2.Copyable {} + +class _FakeWKHttpCookieStore_1 extends _i1.Fake + implements _i3.WKHttpCookieStore {} /// A class which mocks [WKHttpCookieStore]. /// /// See the documentation for Mockito's code generation for more information. -class MockWKHttpCookieStore extends _i1.Mock implements _i2.WKHttpCookieStore { +class MockWKHttpCookieStore extends _i1.Mock implements _i3.WKHttpCookieStore { MockWKHttpCookieStore() { _i1.throwOnMissingStub(this); } @override - _i3.Future setCookie(_i4.NSHttpCookie? cookie) => + _i4.Future setCookie(_i5.NSHttpCookie? cookie) => (super.noSuchMethod(Invocation.method(#setCookie, [cookie]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i3.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i3.Future addObserver(_i4.NSObject? observer, - {String? keyPath, Set<_i4.NSKeyValueObservingOptions>? options}) => + _i4.Future addObserver(_i5.NSObject? observer, + {String? keyPath, Set<_i5.NSKeyValueObservingOptions>? options}) => (super.noSuchMethod( Invocation.method( #addObserver, [observer], {#keyPath: keyPath, #options: options}), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i3.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i3.Future removeObserver(_i4.NSObject? observer, {String? keyPath}) => + _i4.Future removeObserver(_i5.NSObject? observer, {String? keyPath}) => (super.noSuchMethod( Invocation.method(#removeObserver, [observer], {#keyPath: keyPath}), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i3.Future); - @override - _i3.Future dispose() => - (super.noSuchMethod(Invocation.method(#dispose, []), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i3.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i3.Future setObserveValue( + _i4.Future setObserveValue( void Function( - String, _i4.NSObject, Map<_i4.NSKeyValueChangeKey, Object?>)? + String, _i5.NSObject, Map<_i5.NSKeyValueChangeKey, Object?>)? observeValue) => (super.noSuchMethod(Invocation.method(#setObserveValue, [observeValue]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i3.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i2.Copyable copy() => (super.noSuchMethod(Invocation.method(#copy, []), + returnValue: _FakeCopyable_0()) as _i2.Copyable); } /// A class which mocks [WKWebsiteDataStore]. /// /// See the documentation for Mockito's code generation for more information. class MockWKWebsiteDataStore extends _i1.Mock - implements _i2.WKWebsiteDataStore { + implements _i3.WKWebsiteDataStore { MockWKWebsiteDataStore() { _i1.throwOnMissingStub(this); } @override - _i2.WKHttpCookieStore get httpCookieStore => + _i3.WKHttpCookieStore get httpCookieStore => (super.noSuchMethod(Invocation.getter(#httpCookieStore), - returnValue: _FakeWKHttpCookieStore_0()) as _i2.WKHttpCookieStore); + returnValue: _FakeWKHttpCookieStore_1()) as _i3.WKHttpCookieStore); @override - _i3.Future removeDataOfTypes( - Set<_i2.WKWebsiteDataType>? dataTypes, DateTime? since) => + _i4.Future removeDataOfTypes( + Set<_i3.WKWebsiteDataType>? dataTypes, DateTime? since) => (super.noSuchMethod( Invocation.method(#removeDataOfTypes, [dataTypes, since]), - returnValue: Future.value(false)) as _i3.Future); + returnValue: Future.value(false)) as _i4.Future); + @override + _i4.Future addObserver(_i5.NSObject? observer, + {String? keyPath, Set<_i5.NSKeyValueObservingOptions>? options}) => + (super.noSuchMethod( + Invocation.method( + #addObserver, [observer], {#keyPath: keyPath, #options: options}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future removeObserver(_i5.NSObject? observer, {String? keyPath}) => + (super.noSuchMethod( + Invocation.method(#removeObserver, [observer], {#keyPath: keyPath}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future setObserveValue( + void Function( + String, _i5.NSObject, Map<_i5.NSKeyValueChangeKey, Object?>)? + observeValue) => + (super.noSuchMethod(Invocation.method(#setObserveValue, [observeValue]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i2.Copyable copy() => (super.noSuchMethod(Invocation.method(#copy, []), + returnValue: _FakeCopyable_0()) as _i2.Copyable); } diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit_webview_widget_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit_webview_widget_test.dart index 271fd5c062e2..6af5f7d16279 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit_webview_widget_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit_webview_widget_test.dart @@ -64,8 +64,9 @@ void main() { when(mockWebViewWidgetProxy.createWebView(any)).thenReturn(mockWebView); when(mockWebViewWidgetProxy.createUIDelgate()).thenReturn(mockUIDelegate); - when(mockWebViewWidgetProxy.createNavigationDelegate()) - .thenReturn(mockNavigationDelegate); + when(mockWebViewWidgetProxy.createNavigationDelegate( + didFinishNavigation: anyNamed('didFinishNavigation'), + )).thenReturn(mockNavigationDelegate); when(mockWebView.configuration).thenReturn(mockWebViewConfiguration); when(mockWebViewConfiguration.userContentController).thenReturn( mockUserContentController, @@ -919,9 +920,9 @@ void main() { await buildWidget(tester); final dynamic didFinishNavigation = - verify(mockNavigationDelegate.setDidFinishNavigation(captureAny)) - .captured - .single as void Function(WKWebView, String); + verify(mockWebViewWidgetProxy.createNavigationDelegate( + didFinishNavigation: captureAnyNamed('didFinishNavigation'), + )).captured.single as void Function(WKWebView, String); didFinishNavigation(mockWebView, 'https://google.com'); verify(mockCallbacksHandler.onPageFinished('https://google.com')); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit_webview_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit_webview_widget_test.mocks.dart index 066f33a9774e..f2a9876a71e4 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit_webview_widget_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit_webview_widget_test.mocks.dart @@ -2,23 +2,25 @@ // in webview_flutter_wkwebview/example/ios/.symlinks/plugins/webview_flutter_wkwebview/test/src/web_kit_webview_widget_test.dart. // Do not manually edit this file. -import 'dart:async' as _i5; +import 'dart:async' as _i6; import 'dart:math' as _i2; -import 'dart:ui' as _i6; +import 'dart:ui' as _i7; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_platform_interface/src/types/javascript_channel.dart' - as _i9; -import 'package:webview_flutter_platform_interface/src/types/types.dart' as _i10; +import 'package:webview_flutter_platform_interface/src/types/types.dart' + as _i11; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart' - as _i8; + as _i9; +import 'package:webview_flutter_wkwebview/src/common/instance_manager.dart' + as _i3; import 'package:webview_flutter_wkwebview/src/foundation/foundation.dart' - as _i7; -import 'package:webview_flutter_wkwebview/src/ui_kit/ui_kit.dart' as _i4; -import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart' as _i3; + as _i8; +import 'package:webview_flutter_wkwebview/src/ui_kit/ui_kit.dart' as _i5; +import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart' as _i4; import 'package:webview_flutter_wkwebview/src/web_kit_webview_widget.dart' - as _i11; + as _i12; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -32,491 +34,631 @@ import 'package:webview_flutter_wkwebview/src/web_kit_webview_widget.dart' class _FakePoint_0 extends _i1.Fake implements _i2.Point {} -class _FakeWKWebViewConfiguration_1 extends _i1.Fake - implements _i3.WKWebViewConfiguration {} +class _FakeCopyable_1 extends _i1.Fake implements _i3.Copyable {} + +class _FakeWKWebViewConfiguration_2 extends _i1.Fake + implements _i4.WKWebViewConfiguration {} -class _FakeUIScrollView_2 extends _i1.Fake implements _i4.UIScrollView {} +class _FakeUIScrollView_3 extends _i1.Fake implements _i5.UIScrollView {} -class _FakeWKUserContentController_3 extends _i1.Fake - implements _i3.WKUserContentController {} +class _FakeWKUserContentController_4 extends _i1.Fake + implements _i4.WKUserContentController {} -class _FakeWKPreferences_4 extends _i1.Fake implements _i3.WKPreferences {} +class _FakeWKPreferences_5 extends _i1.Fake implements _i4.WKPreferences {} -class _FakeWKWebsiteDataStore_5 extends _i1.Fake - implements _i3.WKWebsiteDataStore {} +class _FakeWKWebsiteDataStore_6 extends _i1.Fake + implements _i4.WKWebsiteDataStore {} -class _FakeWKHttpCookieStore_6 extends _i1.Fake - implements _i3.WKHttpCookieStore {} +class _FakeWKHttpCookieStore_7 extends _i1.Fake + implements _i4.WKHttpCookieStore {} -class _FakeWKWebView_7 extends _i1.Fake implements _i3.WKWebView {} +class _FakeWKWebView_8 extends _i1.Fake implements _i4.WKWebView {} -class _FakeWKScriptMessageHandler_8 extends _i1.Fake - implements _i3.WKScriptMessageHandler {} +class _FakeWKScriptMessageHandler_9 extends _i1.Fake + implements _i4.WKScriptMessageHandler {} -class _FakeWKUIDelegate_9 extends _i1.Fake implements _i3.WKUIDelegate {} +class _FakeWKUIDelegate_10 extends _i1.Fake implements _i4.WKUIDelegate {} -class _FakeWKNavigationDelegate_10 extends _i1.Fake - implements _i3.WKNavigationDelegate {} +class _FakeWKNavigationDelegate_11 extends _i1.Fake + implements _i4.WKNavigationDelegate {} /// A class which mocks [UIScrollView]. /// /// See the documentation for Mockito's code generation for more information. -class MockUIScrollView extends _i1.Mock implements _i4.UIScrollView { +class MockUIScrollView extends _i1.Mock implements _i5.UIScrollView { MockUIScrollView() { _i1.throwOnMissingStub(this); } @override - _i5.Future<_i2.Point> getContentOffset() => (super.noSuchMethod( + _i6.Future<_i2.Point> getContentOffset() => (super.noSuchMethod( Invocation.method(#getContentOffset, []), returnValue: Future<_i2.Point>.value(_FakePoint_0())) - as _i5.Future<_i2.Point>); + as _i6.Future<_i2.Point>); @override - _i5.Future scrollBy(_i2.Point? offset) => + _i6.Future scrollBy(_i2.Point? offset) => (super.noSuchMethod(Invocation.method(#scrollBy, [offset]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future setContentOffset(_i2.Point? offset) => + _i6.Future setContentOffset(_i2.Point? offset) => (super.noSuchMethod(Invocation.method(#setContentOffset, [offset]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future setBackgroundColor(_i6.Color? color) => + _i6.Future setBackgroundColor(_i7.Color? color) => (super.noSuchMethod(Invocation.method(#setBackgroundColor, [color]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future setOpaque(bool? opaque) => + _i6.Future setOpaque(bool? opaque) => (super.noSuchMethod(Invocation.method(#setOpaque, [opaque]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future addObserver(_i7.NSObject? observer, - {String? keyPath, Set<_i7.NSKeyValueObservingOptions>? options}) => + _i6.Future addObserver(_i8.NSObject? observer, + {String? keyPath, Set<_i8.NSKeyValueObservingOptions>? options}) => (super.noSuchMethod( Invocation.method( #addObserver, [observer], {#keyPath: keyPath, #options: options}), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future removeObserver(_i7.NSObject? observer, {String? keyPath}) => + _i6.Future removeObserver(_i8.NSObject? observer, {String? keyPath}) => (super.noSuchMethod( Invocation.method(#removeObserver, [observer], {#keyPath: keyPath}), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future dispose() => - (super.noSuchMethod(Invocation.method(#dispose, []), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future setObserveValue( + _i6.Future setObserveValue( void Function( - String, _i7.NSObject, Map<_i7.NSKeyValueChangeKey, Object?>)? + String, _i8.NSObject, Map<_i8.NSKeyValueChangeKey, Object?>)? observeValue) => (super.noSuchMethod(Invocation.method(#setObserveValue, [observeValue]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + _i3.Copyable copy() => (super.noSuchMethod(Invocation.method(#copy, []), + returnValue: _FakeCopyable_1()) as _i3.Copyable); } /// A class which mocks [WKNavigationDelegate]. /// /// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable class MockWKNavigationDelegate extends _i1.Mock - implements _i3.WKNavigationDelegate { + implements _i4.WKNavigationDelegate { MockWKNavigationDelegate() { _i1.throwOnMissingStub(this); } @override - _i5.Future setDidStartProvisionalNavigation( - void Function(_i3.WKWebView, String?)? + _i6.Future setDidStartProvisionalNavigation( + void Function(_i4.WKWebView, String?)? didStartProvisionalNavigation) => (super.noSuchMethod( Invocation.method(#setDidStartProvisionalNavigation, [didStartProvisionalNavigation]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future setDidFinishNavigation( - void Function(_i3.WKWebView, String?)? didFinishNavigation) => - (super.noSuchMethod( - Invocation.method(#setDidFinishNavigation, [didFinishNavigation]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future setDecidePolicyForNavigationAction( - _i5.Future<_i3.WKNavigationActionPolicy> Function( - _i3.WKWebView, _i3.WKNavigationAction)? + _i6.Future setDecidePolicyForNavigationAction( + _i6.Future<_i4.WKNavigationActionPolicy> Function( + _i4.WKWebView, _i4.WKNavigationAction)? decidePolicyForNavigationAction) => (super.noSuchMethod( Invocation.method(#setDecidePolicyForNavigationAction, [decidePolicyForNavigationAction]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future setDidFailNavigation( - void Function(_i3.WKWebView, _i7.NSError)? didFailNavigation) => + _i6.Future setDidFailNavigation( + void Function(_i4.WKWebView, _i8.NSError)? didFailNavigation) => (super.noSuchMethod( Invocation.method(#setDidFailNavigation, [didFailNavigation]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future setDidFailProvisionalNavigation( - void Function(_i3.WKWebView, _i7.NSError)? + _i6.Future setDidFailProvisionalNavigation( + void Function(_i4.WKWebView, _i8.NSError)? didFailProvisionalNavigation) => (super.noSuchMethod( Invocation.method( #setDidFailProvisionalNavigation, [didFailProvisionalNavigation]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future setWebViewWebContentProcessDidTerminate( - void Function(_i3.WKWebView)? webViewWebContentProcessDidTerminate) => + _i6.Future setWebViewWebContentProcessDidTerminate( + void Function(_i4.WKWebView)? webViewWebContentProcessDidTerminate) => (super.noSuchMethod( Invocation.method(#setWebViewWebContentProcessDidTerminate, [webViewWebContentProcessDidTerminate]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + _i3.Copyable copy() => (super.noSuchMethod(Invocation.method(#copy, []), + returnValue: _FakeCopyable_1()) as _i3.Copyable); @override - _i5.Future addObserver(_i7.NSObject? observer, - {String? keyPath, Set<_i7.NSKeyValueObservingOptions>? options}) => + _i6.Future addObserver(_i8.NSObject? observer, + {String? keyPath, Set<_i8.NSKeyValueObservingOptions>? options}) => (super.noSuchMethod( Invocation.method( #addObserver, [observer], {#keyPath: keyPath, #options: options}), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future removeObserver(_i7.NSObject? observer, {String? keyPath}) => + _i6.Future removeObserver(_i8.NSObject? observer, {String? keyPath}) => (super.noSuchMethod( Invocation.method(#removeObserver, [observer], {#keyPath: keyPath}), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future dispose() => - (super.noSuchMethod(Invocation.method(#dispose, []), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future setObserveValue( + _i6.Future setObserveValue( void Function( - String, _i7.NSObject, Map<_i7.NSKeyValueChangeKey, Object?>)? + String, _i8.NSObject, Map<_i8.NSKeyValueChangeKey, Object?>)? observeValue) => (super.noSuchMethod(Invocation.method(#setObserveValue, [observeValue]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); } /// A class which mocks [WKPreferences]. /// /// See the documentation for Mockito's code generation for more information. -class MockWKPreferences extends _i1.Mock implements _i3.WKPreferences { +class MockWKPreferences extends _i1.Mock implements _i4.WKPreferences { MockWKPreferences() { _i1.throwOnMissingStub(this); } @override - _i5.Future setJavaScriptEnabled(bool? enabled) => + _i6.Future setJavaScriptEnabled(bool? enabled) => (super.noSuchMethod(Invocation.method(#setJavaScriptEnabled, [enabled]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + _i6.Future addObserver(_i8.NSObject? observer, + {String? keyPath, Set<_i8.NSKeyValueObservingOptions>? options}) => + (super.noSuchMethod( + Invocation.method( + #addObserver, [observer], {#keyPath: keyPath, #options: options}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + _i6.Future removeObserver(_i8.NSObject? observer, {String? keyPath}) => + (super.noSuchMethod( + Invocation.method(#removeObserver, [observer], {#keyPath: keyPath}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + _i6.Future setObserveValue( + void Function( + String, _i8.NSObject, Map<_i8.NSKeyValueChangeKey, Object?>)? + observeValue) => + (super.noSuchMethod(Invocation.method(#setObserveValue, [observeValue]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + _i3.Copyable copy() => (super.noSuchMethod(Invocation.method(#copy, []), + returnValue: _FakeCopyable_1()) as _i3.Copyable); } /// A class which mocks [WKScriptMessageHandler]. /// /// See the documentation for Mockito's code generation for more information. class MockWKScriptMessageHandler extends _i1.Mock - implements _i3.WKScriptMessageHandler { + implements _i4.WKScriptMessageHandler { MockWKScriptMessageHandler() { _i1.throwOnMissingStub(this); } @override - _i5.Future setDidReceiveScriptMessage( - void Function(_i3.WKUserContentController, _i3.WKScriptMessage)? + _i6.Future setDidReceiveScriptMessage( + void Function(_i4.WKUserContentController, _i4.WKScriptMessage)? didReceiveScriptMessage) => (super.noSuchMethod( Invocation.method( #setDidReceiveScriptMessage, [didReceiveScriptMessage]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + _i6.Future addObserver(_i8.NSObject? observer, + {String? keyPath, Set<_i8.NSKeyValueObservingOptions>? options}) => + (super.noSuchMethod( + Invocation.method( + #addObserver, [observer], {#keyPath: keyPath, #options: options}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + _i6.Future removeObserver(_i8.NSObject? observer, {String? keyPath}) => + (super.noSuchMethod( + Invocation.method(#removeObserver, [observer], {#keyPath: keyPath}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + _i6.Future setObserveValue( + void Function( + String, _i8.NSObject, Map<_i8.NSKeyValueChangeKey, Object?>)? + observeValue) => + (super.noSuchMethod(Invocation.method(#setObserveValue, [observeValue]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + _i3.Copyable copy() => (super.noSuchMethod(Invocation.method(#copy, []), + returnValue: _FakeCopyable_1()) as _i3.Copyable); } /// A class which mocks [WKWebView]. /// /// See the documentation for Mockito's code generation for more information. -class MockWKWebView extends _i1.Mock implements _i3.WKWebView { +class MockWKWebView extends _i1.Mock implements _i4.WKWebView { MockWKWebView() { _i1.throwOnMissingStub(this); } @override - _i3.WKWebViewConfiguration get configuration => + _i4.WKWebViewConfiguration get configuration => (super.noSuchMethod(Invocation.getter(#configuration), - returnValue: _FakeWKWebViewConfiguration_1()) - as _i3.WKWebViewConfiguration); + returnValue: _FakeWKWebViewConfiguration_2()) + as _i4.WKWebViewConfiguration); @override - _i4.UIScrollView get scrollView => + _i5.UIScrollView get scrollView => (super.noSuchMethod(Invocation.getter(#scrollView), - returnValue: _FakeUIScrollView_2()) as _i4.UIScrollView); + returnValue: _FakeUIScrollView_3()) as _i5.UIScrollView); @override - _i5.Future setUIDelegate(_i3.WKUIDelegate? delegate) => + _i6.Future setUIDelegate(_i4.WKUIDelegate? delegate) => (super.noSuchMethod(Invocation.method(#setUIDelegate, [delegate]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future setNavigationDelegate(_i3.WKNavigationDelegate? delegate) => + _i6.Future setNavigationDelegate(_i4.WKNavigationDelegate? delegate) => (super.noSuchMethod(Invocation.method(#setNavigationDelegate, [delegate]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future getUrl() => + _i6.Future getUrl() => (super.noSuchMethod(Invocation.method(#getUrl, []), - returnValue: Future.value()) as _i5.Future); + returnValue: Future.value()) as _i6.Future); @override - _i5.Future getEstimatedProgress() => + _i6.Future getEstimatedProgress() => (super.noSuchMethod(Invocation.method(#getEstimatedProgress, []), - returnValue: Future.value(0.0)) as _i5.Future); + returnValue: Future.value(0.0)) as _i6.Future); @override - _i5.Future loadRequest(_i7.NSUrlRequest? request) => + _i6.Future loadRequest(_i8.NSUrlRequest? request) => (super.noSuchMethod(Invocation.method(#loadRequest, [request]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future loadHtmlString(String? string, {String? baseUrl}) => + _i6.Future loadHtmlString(String? string, {String? baseUrl}) => (super.noSuchMethod( Invocation.method(#loadHtmlString, [string], {#baseUrl: baseUrl}), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future loadFileUrl(String? url, {String? readAccessUrl}) => + _i6.Future loadFileUrl(String? url, {String? readAccessUrl}) => (super.noSuchMethod( Invocation.method( #loadFileUrl, [url], {#readAccessUrl: readAccessUrl}), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future loadFlutterAsset(String? key) => + _i6.Future loadFlutterAsset(String? key) => (super.noSuchMethod(Invocation.method(#loadFlutterAsset, [key]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future canGoBack() => + _i6.Future canGoBack() => (super.noSuchMethod(Invocation.method(#canGoBack, []), - returnValue: Future.value(false)) as _i5.Future); + returnValue: Future.value(false)) as _i6.Future); @override - _i5.Future canGoForward() => + _i6.Future canGoForward() => (super.noSuchMethod(Invocation.method(#canGoForward, []), - returnValue: Future.value(false)) as _i5.Future); + returnValue: Future.value(false)) as _i6.Future); @override - _i5.Future goBack() => + _i6.Future goBack() => (super.noSuchMethod(Invocation.method(#goBack, []), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future goForward() => + _i6.Future goForward() => (super.noSuchMethod(Invocation.method(#goForward, []), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future reload() => + _i6.Future reload() => (super.noSuchMethod(Invocation.method(#reload, []), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future getTitle() => + _i6.Future getTitle() => (super.noSuchMethod(Invocation.method(#getTitle, []), - returnValue: Future.value()) as _i5.Future); + returnValue: Future.value()) as _i6.Future); @override - _i5.Future setAllowsBackForwardNavigationGestures(bool? allow) => + _i6.Future setAllowsBackForwardNavigationGestures(bool? allow) => (super.noSuchMethod( Invocation.method(#setAllowsBackForwardNavigationGestures, [allow]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future setCustomUserAgent(String? userAgent) => + _i6.Future setCustomUserAgent(String? userAgent) => (super.noSuchMethod(Invocation.method(#setCustomUserAgent, [userAgent]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future evaluateJavaScript(String? javaScriptString) => (super + _i6.Future evaluateJavaScript(String? javaScriptString) => (super .noSuchMethod(Invocation.method(#evaluateJavaScript, [javaScriptString]), - returnValue: Future.value()) as _i5.Future); + returnValue: Future.value()) as _i6.Future); @override - _i5.Future setBackgroundColor(_i6.Color? color) => + _i6.Future setBackgroundColor(_i7.Color? color) => (super.noSuchMethod(Invocation.method(#setBackgroundColor, [color]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future setOpaque(bool? opaque) => + _i6.Future setOpaque(bool? opaque) => (super.noSuchMethod(Invocation.method(#setOpaque, [opaque]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future addObserver(_i7.NSObject? observer, - {String? keyPath, Set<_i7.NSKeyValueObservingOptions>? options}) => + _i6.Future addObserver(_i8.NSObject? observer, + {String? keyPath, Set<_i8.NSKeyValueObservingOptions>? options}) => (super.noSuchMethod( Invocation.method( #addObserver, [observer], {#keyPath: keyPath, #options: options}), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future removeObserver(_i7.NSObject? observer, {String? keyPath}) => + _i6.Future removeObserver(_i8.NSObject? observer, {String? keyPath}) => (super.noSuchMethod( Invocation.method(#removeObserver, [observer], {#keyPath: keyPath}), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future dispose() => - (super.noSuchMethod(Invocation.method(#dispose, []), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future setObserveValue( + _i6.Future setObserveValue( void Function( - String, _i7.NSObject, Map<_i7.NSKeyValueChangeKey, Object?>)? + String, _i8.NSObject, Map<_i8.NSKeyValueChangeKey, Object?>)? observeValue) => (super.noSuchMethod(Invocation.method(#setObserveValue, [observeValue]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + _i3.Copyable copy() => (super.noSuchMethod(Invocation.method(#copy, []), + returnValue: _FakeCopyable_1()) as _i3.Copyable); } /// A class which mocks [WKWebViewConfiguration]. /// /// See the documentation for Mockito's code generation for more information. class MockWKWebViewConfiguration extends _i1.Mock - implements _i3.WKWebViewConfiguration { + implements _i4.WKWebViewConfiguration { MockWKWebViewConfiguration() { _i1.throwOnMissingStub(this); } @override - _i3.WKUserContentController get userContentController => + _i4.WKUserContentController get userContentController => (super.noSuchMethod(Invocation.getter(#userContentController), - returnValue: _FakeWKUserContentController_3()) - as _i3.WKUserContentController); + returnValue: _FakeWKUserContentController_4()) + as _i4.WKUserContentController); @override - _i3.WKPreferences get preferences => + _i4.WKPreferences get preferences => (super.noSuchMethod(Invocation.getter(#preferences), - returnValue: _FakeWKPreferences_4()) as _i3.WKPreferences); + returnValue: _FakeWKPreferences_5()) as _i4.WKPreferences); @override - _i3.WKWebsiteDataStore get websiteDataStore => + _i4.WKWebsiteDataStore get websiteDataStore => (super.noSuchMethod(Invocation.getter(#websiteDataStore), - returnValue: _FakeWKWebsiteDataStore_5()) as _i3.WKWebsiteDataStore); + returnValue: _FakeWKWebsiteDataStore_6()) as _i4.WKWebsiteDataStore); @override - _i5.Future setAllowsInlineMediaPlayback(bool? allow) => (super + _i6.Future setAllowsInlineMediaPlayback(bool? allow) => (super .noSuchMethod(Invocation.method(#setAllowsInlineMediaPlayback, [allow]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future setMediaTypesRequiringUserActionForPlayback( - Set<_i3.WKAudiovisualMediaType>? types) => + _i6.Future setMediaTypesRequiringUserActionForPlayback( + Set<_i4.WKAudiovisualMediaType>? types) => (super.noSuchMethod( Invocation.method( #setMediaTypesRequiringUserActionForPlayback, [types]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + _i6.Future addObserver(_i8.NSObject? observer, + {String? keyPath, Set<_i8.NSKeyValueObservingOptions>? options}) => + (super.noSuchMethod( + Invocation.method( + #addObserver, [observer], {#keyPath: keyPath, #options: options}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + _i6.Future removeObserver(_i8.NSObject? observer, {String? keyPath}) => + (super.noSuchMethod( + Invocation.method(#removeObserver, [observer], {#keyPath: keyPath}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + _i6.Future setObserveValue( + void Function( + String, _i8.NSObject, Map<_i8.NSKeyValueChangeKey, Object?>)? + observeValue) => + (super.noSuchMethod(Invocation.method(#setObserveValue, [observeValue]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + _i3.Copyable copy() => (super.noSuchMethod(Invocation.method(#copy, []), + returnValue: _FakeCopyable_1()) as _i3.Copyable); } /// A class which mocks [WKWebsiteDataStore]. /// /// See the documentation for Mockito's code generation for more information. class MockWKWebsiteDataStore extends _i1.Mock - implements _i3.WKWebsiteDataStore { + implements _i4.WKWebsiteDataStore { MockWKWebsiteDataStore() { _i1.throwOnMissingStub(this); } @override - _i3.WKHttpCookieStore get httpCookieStore => + _i4.WKHttpCookieStore get httpCookieStore => (super.noSuchMethod(Invocation.getter(#httpCookieStore), - returnValue: _FakeWKHttpCookieStore_6()) as _i3.WKHttpCookieStore); + returnValue: _FakeWKHttpCookieStore_7()) as _i4.WKHttpCookieStore); @override - _i5.Future removeDataOfTypes( - Set<_i3.WKWebsiteDataType>? dataTypes, DateTime? since) => + _i6.Future removeDataOfTypes( + Set<_i4.WKWebsiteDataType>? dataTypes, DateTime? since) => (super.noSuchMethod( Invocation.method(#removeDataOfTypes, [dataTypes, since]), - returnValue: Future.value(false)) as _i5.Future); + returnValue: Future.value(false)) as _i6.Future); + @override + _i6.Future addObserver(_i8.NSObject? observer, + {String? keyPath, Set<_i8.NSKeyValueObservingOptions>? options}) => + (super.noSuchMethod( + Invocation.method( + #addObserver, [observer], {#keyPath: keyPath, #options: options}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + _i6.Future removeObserver(_i8.NSObject? observer, {String? keyPath}) => + (super.noSuchMethod( + Invocation.method(#removeObserver, [observer], {#keyPath: keyPath}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + _i6.Future setObserveValue( + void Function( + String, _i8.NSObject, Map<_i8.NSKeyValueChangeKey, Object?>)? + observeValue) => + (super.noSuchMethod(Invocation.method(#setObserveValue, [observeValue]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + _i3.Copyable copy() => (super.noSuchMethod(Invocation.method(#copy, []), + returnValue: _FakeCopyable_1()) as _i3.Copyable); } /// A class which mocks [WKUIDelegate]. /// /// See the documentation for Mockito's code generation for more information. -class MockWKUIDelegate extends _i1.Mock implements _i3.WKUIDelegate { +class MockWKUIDelegate extends _i1.Mock implements _i4.WKUIDelegate { MockWKUIDelegate() { _i1.throwOnMissingStub(this); } @override - _i5.Future setOnCreateWebView( - void Function(_i3.WKWebViewConfiguration, _i3.WKNavigationAction)? + _i6.Future setOnCreateWebView( + void Function(_i4.WKWebViewConfiguration, _i4.WKNavigationAction)? onCreateWebView) => (super.noSuchMethod( Invocation.method(#setOnCreateWebView, [onCreateWebView]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + _i6.Future addObserver(_i8.NSObject? observer, + {String? keyPath, Set<_i8.NSKeyValueObservingOptions>? options}) => + (super.noSuchMethod( + Invocation.method( + #addObserver, [observer], {#keyPath: keyPath, #options: options}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + _i6.Future removeObserver(_i8.NSObject? observer, {String? keyPath}) => + (super.noSuchMethod( + Invocation.method(#removeObserver, [observer], {#keyPath: keyPath}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + _i6.Future setObserveValue( + void Function( + String, _i8.NSObject, Map<_i8.NSKeyValueChangeKey, Object?>)? + observeValue) => + (super.noSuchMethod(Invocation.method(#setObserveValue, [observeValue]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + _i3.Copyable copy() => (super.noSuchMethod(Invocation.method(#copy, []), + returnValue: _FakeCopyable_1()) as _i3.Copyable); } /// A class which mocks [WKUserContentController]. /// /// See the documentation for Mockito's code generation for more information. class MockWKUserContentController extends _i1.Mock - implements _i3.WKUserContentController { + implements _i4.WKUserContentController { MockWKUserContentController() { _i1.throwOnMissingStub(this); } @override - _i5.Future addScriptMessageHandler( - _i3.WKScriptMessageHandler? handler, String? name) => + _i6.Future addScriptMessageHandler( + _i4.WKScriptMessageHandler? handler, String? name) => (super.noSuchMethod( Invocation.method(#addScriptMessageHandler, [handler, name]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future removeScriptMessageHandler(String? name) => (super + _i6.Future removeScriptMessageHandler(String? name) => (super .noSuchMethod(Invocation.method(#removeScriptMessageHandler, [name]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future removeAllScriptMessageHandlers() => (super.noSuchMethod( + _i6.Future removeAllScriptMessageHandlers() => (super.noSuchMethod( Invocation.method(#removeAllScriptMessageHandlers, []), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future addUserScript(_i3.WKUserScript? userScript) => + _i6.Future addUserScript(_i4.WKUserScript? userScript) => (super.noSuchMethod(Invocation.method(#addUserScript, [userScript]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); @override - _i5.Future removeAllUserScripts() => + _i6.Future removeAllUserScripts() => (super.noSuchMethod(Invocation.method(#removeAllUserScripts, []), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + _i6.Future addObserver(_i8.NSObject? observer, + {String? keyPath, Set<_i8.NSKeyValueObservingOptions>? options}) => + (super.noSuchMethod( + Invocation.method( + #addObserver, [observer], {#keyPath: keyPath, #options: options}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + _i6.Future removeObserver(_i8.NSObject? observer, {String? keyPath}) => + (super.noSuchMethod( + Invocation.method(#removeObserver, [observer], {#keyPath: keyPath}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + _i6.Future setObserveValue( + void Function( + String, _i8.NSObject, Map<_i8.NSKeyValueChangeKey, Object?>)? + observeValue) => + (super.noSuchMethod(Invocation.method(#setObserveValue, [observeValue]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + _i3.Copyable copy() => (super.noSuchMethod(Invocation.method(#copy, []), + returnValue: _FakeCopyable_1()) as _i3.Copyable); } /// A class which mocks [JavascriptChannelRegistry]. /// /// See the documentation for Mockito's code generation for more information. class MockJavascriptChannelRegistry extends _i1.Mock - implements _i8.JavascriptChannelRegistry { + implements _i9.JavascriptChannelRegistry { MockJavascriptChannelRegistry() { _i1.throwOnMissingStub(this); } @override - Map get channels => + Map get channels => (super.noSuchMethod(Invocation.getter(#channels), - returnValue: {}) - as Map); + returnValue: {}) + as Map); @override void onJavascriptChannelMessage(String? channel, String? message) => super.noSuchMethod( Invocation.method(#onJavascriptChannelMessage, [channel, message]), returnValueForMissingStub: null); @override - void updateJavascriptChannelsFromSet(Set<_i9.JavascriptChannel>? channels) => + void updateJavascriptChannelsFromSet(Set<_i10.JavascriptChannel>? channels) => super.noSuchMethod( Invocation.method(#updateJavascriptChannelsFromSet, [channels]), returnValueForMissingStub: null); @@ -526,17 +668,17 @@ class MockJavascriptChannelRegistry extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockWebViewPlatformCallbacksHandler extends _i1.Mock - implements _i8.WebViewPlatformCallbacksHandler { + implements _i9.WebViewPlatformCallbacksHandler { MockWebViewPlatformCallbacksHandler() { _i1.throwOnMissingStub(this); } @override - _i5.FutureOr onNavigationRequest({String? url, bool? isForMainFrame}) => + _i6.FutureOr onNavigationRequest({String? url, bool? isForMainFrame}) => (super.noSuchMethod( Invocation.method(#onNavigationRequest, [], {#url: url, #isForMainFrame: isForMainFrame}), - returnValue: Future.value(false)) as _i5.FutureOr); + returnValue: Future.value(false)) as _i6.FutureOr); @override void onPageStarted(String? url) => super.noSuchMethod(Invocation.method(#onPageStarted, [url]), @@ -550,7 +692,7 @@ class MockWebViewPlatformCallbacksHandler extends _i1.Mock super.noSuchMethod(Invocation.method(#onProgress, [progress]), returnValueForMissingStub: null); @override - void onWebResourceError(_i10.WebResourceError? error) => + void onWebResourceError(_i11.WebResourceError? error) => super.noSuchMethod(Invocation.method(#onWebResourceError, [error]), returnValueForMissingStub: null); } @@ -559,26 +701,30 @@ class MockWebViewPlatformCallbacksHandler extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockWebViewWidgetProxy extends _i1.Mock - implements _i11.WebViewWidgetProxy { + implements _i12.WebViewWidgetProxy { MockWebViewWidgetProxy() { _i1.throwOnMissingStub(this); } @override - _i3.WKWebView createWebView(_i3.WKWebViewConfiguration? configuration) => + _i4.WKWebView createWebView(_i4.WKWebViewConfiguration? configuration) => (super.noSuchMethod(Invocation.method(#createWebView, [configuration]), - returnValue: _FakeWKWebView_7()) as _i3.WKWebView); + returnValue: _FakeWKWebView_8()) as _i4.WKWebView); @override - _i3.WKScriptMessageHandler createScriptMessageHandler() => + _i4.WKScriptMessageHandler createScriptMessageHandler() => (super.noSuchMethod(Invocation.method(#createScriptMessageHandler, []), - returnValue: _FakeWKScriptMessageHandler_8()) - as _i3.WKScriptMessageHandler); + returnValue: _FakeWKScriptMessageHandler_9()) + as _i4.WKScriptMessageHandler); @override - _i3.WKUIDelegate createUIDelgate() => + _i4.WKUIDelegate createUIDelgate() => (super.noSuchMethod(Invocation.method(#createUIDelgate, []), - returnValue: _FakeWKUIDelegate_9()) as _i3.WKUIDelegate); + returnValue: _FakeWKUIDelegate_10()) as _i4.WKUIDelegate); @override - _i3.WKNavigationDelegate createNavigationDelegate() => (super.noSuchMethod( - Invocation.method(#createNavigationDelegate, []), - returnValue: _FakeWKNavigationDelegate_10()) as _i3.WKNavigationDelegate); + _i4.WKNavigationDelegate createNavigationDelegate( + {void Function(_i4.WKWebView, String?)? didFinishNavigation}) => + (super.noSuchMethod( + Invocation.method(#createNavigationDelegate, [], + {#didFinishNavigation: didFinishNavigation}), + returnValue: _FakeWKNavigationDelegate_11()) + as _i4.WKNavigationDelegate); }