iOS web browser with a focus on security and privacy
at master 695 lines 44 kB view raw
1// 2// 1Password Extension 3// 4// Lovingly handcrafted by Dave Teare, Michael Fey, Rad Azzouz, and Roustem Karimov. 5// Copyright (c) 2014 AgileBits. All rights reserved. 6// 7 8#import "OnePasswordExtension.h" 9 10// Version 11#define VERSION_NUMBER @(184) 12static NSString *const AppExtensionVersionNumberKey = @"version_number"; 13 14// Available App Extension Actions 15static NSString *const kUTTypeAppExtensionFindLoginAction = @"org.appextension.find-login-action"; 16static NSString *const kUTTypeAppExtensionSaveLoginAction = @"org.appextension.save-login-action"; 17static NSString *const kUTTypeAppExtensionChangePasswordAction = @"org.appextension.change-password-action"; 18static NSString *const kUTTypeAppExtensionFillWebViewAction = @"org.appextension.fill-webview-action"; 19static NSString *const kUTTypeAppExtensionFillBrowserAction = @"org.appextension.fill-browser-action"; 20 21// WebView Dictionary keys 22static NSString *const AppExtensionWebViewPageFillScript = @"fillScript"; 23static NSString *const AppExtensionWebViewPageDetails = @"pageDetails"; 24 25@implementation OnePasswordExtension 26 27#pragma mark - Public Methods 28 29+ (OnePasswordExtension *)sharedExtension { 30 static dispatch_once_t onceToken; 31 static OnePasswordExtension *__sharedExtension; 32 33 dispatch_once(&onceToken, ^{ 34 __sharedExtension = [OnePasswordExtension new]; 35 }); 36 37 return __sharedExtension; 38} 39 40- (BOOL)isAppExtensionAvailable { 41 if ([self isSystemAppExtensionAPIAvailable]) { 42 return [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"org-appextension-feature-password-management://"]]; 43 } 44 45 return NO; 46} 47 48#pragma mark - Native app Login 49 50- (void)findLoginForURLString:(nonnull NSString *)URLString forViewController:(nonnull UIViewController *)viewController sender:(nullable id)sender completion:(nonnull OnePasswordLoginDictionaryCompletionBlock)completion { 51 NSAssert(URLString != nil, @"URLString must not be nil"); 52 NSAssert(viewController != nil, @"viewController must not be nil"); 53 54 if (NO == [self isSystemAppExtensionAPIAvailable]) { 55 NSLog(@"Failed to findLoginForURLString, system API is not available"); 56 if (completion) { 57 completion(nil, [OnePasswordExtension systemAppExtensionAPINotAvailableError]); 58 } 59 60 return; 61 } 62 63#ifdef __IPHONE_8_0 64 NSDictionary *item = @{ AppExtensionVersionNumberKey: VERSION_NUMBER, AppExtensionURLStringKey: URLString }; 65 66 UIActivityViewController *activityViewController = [self activityViewControllerForItem:item viewController:viewController sender:sender typeIdentifier:kUTTypeAppExtensionFindLoginAction]; 67 activityViewController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { 68 if (returnedItems.count == 0) { 69 NSError *error = nil; 70 if (activityError) { 71 NSLog(@"Failed to findLoginForURLString: %@", activityError); 72 error = [OnePasswordExtension failedToContactExtensionErrorWithActivityError:activityError]; 73 } 74 else { 75 error = [OnePasswordExtension extensionCancelledByUserError]; 76 } 77 78 if (completion) { 79 completion(nil, error); 80 } 81 82 return; 83 } 84 85 [self processExtensionItem:returnedItems.firstObject completion:^(NSDictionary *itemDictionary, NSError *error) { 86 if (completion) { 87 completion(itemDictionary, error); 88 } 89 }]; 90 }; 91 92 [viewController presentViewController:activityViewController animated:YES completion:nil]; 93#endif 94} 95 96#pragma mark - New User Registration 97 98- (void)storeLoginForURLString:(nonnull NSString *)URLString loginDetails:(nullable NSDictionary *)loginDetailsDictionary passwordGenerationOptions:(nullable NSDictionary *)passwordGenerationOptions forViewController:(nonnull UIViewController *)viewController sender:(nullable id)sender completion:(nonnull OnePasswordLoginDictionaryCompletionBlock)completion { 99 NSAssert(URLString != nil, @"URLString must not be nil"); 100 NSAssert(viewController != nil, @"viewController must not be nil"); 101 102 if (NO == [self isSystemAppExtensionAPIAvailable]) { 103 NSLog(@"Failed to storeLoginForURLString, system API is not available"); 104 if (completion) { 105 completion(nil, [OnePasswordExtension systemAppExtensionAPINotAvailableError]); 106 } 107 108 return; 109 } 110 111 112#ifdef __IPHONE_8_0 113 NSMutableDictionary *newLoginAttributesDict = [NSMutableDictionary new]; 114 newLoginAttributesDict[AppExtensionVersionNumberKey] = VERSION_NUMBER; 115 newLoginAttributesDict[AppExtensionURLStringKey] = URLString; 116 [newLoginAttributesDict addEntriesFromDictionary:loginDetailsDictionary]; 117 if (passwordGenerationOptions.count > 0) { 118 newLoginAttributesDict[AppExtensionPasswordGeneratorOptionsKey] = passwordGenerationOptions; 119 } 120 121 UIActivityViewController *activityViewController = [self activityViewControllerForItem:newLoginAttributesDict viewController:viewController sender:sender typeIdentifier:kUTTypeAppExtensionSaveLoginAction]; 122 activityViewController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { 123 if (returnedItems.count == 0) { 124 NSError *error = nil; 125 if (activityError) { 126 NSLog(@"Failed to storeLoginForURLString: %@", activityError); 127 error = [OnePasswordExtension failedToContactExtensionErrorWithActivityError:activityError]; 128 } 129 else { 130 error = [OnePasswordExtension extensionCancelledByUserError]; 131 } 132 133 if (completion) { 134 completion(nil, error); 135 } 136 137 return; 138 } 139 140 [self processExtensionItem:returnedItems.firstObject completion:^(NSDictionary *itemDictionary, NSError *error) { 141 if (completion) { 142 completion(itemDictionary, error); 143 } 144 }]; 145 }; 146 147 [viewController presentViewController:activityViewController animated:YES completion:nil]; 148#endif 149} 150 151#pragma mark - Change Password 152 153- (void)changePasswordForLoginForURLString:(nonnull NSString *)URLString loginDetails:(nullable NSDictionary *)loginDetailsDictionary passwordGenerationOptions:(nullable NSDictionary *)passwordGenerationOptions forViewController:(UIViewController *)viewController sender:(nullable id)sender completion:(nonnull OnePasswordLoginDictionaryCompletionBlock)completion { 154 NSAssert(URLString != nil, @"URLString must not be nil"); 155 NSAssert(viewController != nil, @"viewController must not be nil"); 156 157 if (NO == [self isSystemAppExtensionAPIAvailable]) { 158 NSLog(@"Failed to changePasswordForLoginWithUsername, system API is not available"); 159 if (completion) { 160 completion(nil, [OnePasswordExtension systemAppExtensionAPINotAvailableError]); 161 } 162 163 return; 164 } 165 166#ifdef __IPHONE_8_0 167 NSMutableDictionary *item = [NSMutableDictionary new]; 168 item[AppExtensionVersionNumberKey] = VERSION_NUMBER; 169 item[AppExtensionURLStringKey] = URLString; 170 [item addEntriesFromDictionary:loginDetailsDictionary]; 171 if (passwordGenerationOptions.count > 0) { 172 item[AppExtensionPasswordGeneratorOptionsKey] = passwordGenerationOptions; 173 } 174 175 UIActivityViewController *activityViewController = [self activityViewControllerForItem:item viewController:viewController sender:sender typeIdentifier:kUTTypeAppExtensionChangePasswordAction]; 176 177 activityViewController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { 178 if (returnedItems.count == 0) { 179 NSError *error = nil; 180 if (activityError) { 181 NSLog(@"Failed to changePasswordForLoginWithUsername: %@", activityError); 182 error = [OnePasswordExtension failedToContactExtensionErrorWithActivityError:activityError]; 183 } 184 else { 185 error = [OnePasswordExtension extensionCancelledByUserError]; 186 } 187 188 if (completion) { 189 completion(nil, error); 190 } 191 192 return; 193 } 194 195 [self processExtensionItem:returnedItems.firstObject completion:^(NSDictionary *itemDictionary, NSError *error) { 196 if (completion) { 197 completion(itemDictionary, error); 198 } 199 }]; 200 }; 201 202 [viewController presentViewController:activityViewController animated:YES completion:nil]; 203#endif 204} 205 206#pragma mark - Web View filling Support 207 208- (void)fillItemIntoWebView:(nonnull id)webView forViewController:(nonnull UIViewController *)viewController sender:(nullable id)sender showOnlyLogins:(BOOL)yesOrNo completion:(nonnull OnePasswordSuccessCompletionBlock)completion { 209 NSAssert(webView != nil, @"webView must not be nil"); 210 NSAssert(viewController != nil, @"viewController must not be nil"); 211 NSAssert([webView isKindOfClass:[UIWebView class]] || [webView isKindOfClass:[WKWebView class]], @"webView must be an instance of WKWebView or UIWebView."); 212 213#ifdef __IPHONE_8_0 214 if ([webView isKindOfClass:[UIWebView class]]) { 215 [self fillItemIntoUIWebView:webView webViewController:viewController sender:(id)sender showOnlyLogins:yesOrNo completion:^(BOOL success, NSError *error) { 216 if (completion) { 217 completion(success, error); 218 } 219 }]; 220 } 221 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 || ONE_PASSWORD_EXTENSION_ENABLE_WK_WEB_VIEW 222 else if ([webView isKindOfClass:[WKWebView class]]) { 223 [self fillItemIntoWKWebView:webView forViewController:viewController sender:(id)sender showOnlyLogins:yesOrNo completion:^(BOOL success, NSError *error) { 224 if (completion) { 225 completion(success, error); 226 } 227 }]; 228 } 229 #endif 230#endif 231} 232 233#pragma mark - Support for custom UIActivityViewControllers 234 235- (BOOL)isOnePasswordExtensionActivityType:(nullable NSString *)activityType { 236 return [@"com.agilebits.onepassword-ios.extension" isEqualToString:activityType] || [@"com.agilebits.beta.onepassword-ios.extension" isEqualToString:activityType]; 237} 238 239- (void)createExtensionItemForWebView:(nonnull id)webView completion:(nonnull OnePasswordExtensionItemCompletionBlock)completion { 240 NSAssert(webView != nil, @"webView must not be nil"); 241 NSAssert([webView isKindOfClass:[UIWebView class]] || [webView isKindOfClass:[WKWebView class]], @"webView must be an instance of WKWebView or UIWebView."); 242 243#ifdef __IPHONE_8_0 244 if ([webView isKindOfClass:[UIWebView class]]) { 245 UIWebView *uiWebView = (UIWebView *)webView; 246 NSString *collectedPageDetails = [uiWebView stringByEvaluatingJavaScriptFromString:OPWebViewCollectFieldsScript]; 247 248 [self createExtensionItemForURLString:uiWebView.request.URL.absoluteString webPageDetails:collectedPageDetails completion:completion]; 249 } 250 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 || ONE_PASSWORD_EXTENSION_ENABLE_WK_WEB_VIEW 251 else if ([webView isKindOfClass:[WKWebView class]]) { 252 WKWebView *wkWebView = (WKWebView *)webView; 253 [wkWebView evaluateJavaScript:OPWebViewCollectFieldsScript completionHandler:^(NSString *result, NSError *evaluateError) { 254 if (result == nil) { 255 NSLog(@"1Password Extension failed to collect web page fields: %@", evaluateError); 256 NSError *failedToCollectFieldsError = [OnePasswordExtension failedToCollectFieldsErrorWithUnderlyingError:evaluateError]; 257 if (completion) { 258 if ([NSThread isMainThread]) { 259 completion(nil, failedToCollectFieldsError); 260 } 261 else { 262 dispatch_async(dispatch_get_main_queue(), ^{ 263 completion(nil, failedToCollectFieldsError); 264 }); 265 } 266 } 267 268 return; 269 } 270 271 [self createExtensionItemForURLString:wkWebView.URL.absoluteString webPageDetails:result completion:completion]; 272 }]; 273 } 274 #endif 275#endif 276} 277 278- (void)fillReturnedItems:(nullable NSArray *)returnedItems intoWebView:(nonnull id)webView completion:(nonnull OnePasswordSuccessCompletionBlock)completion { 279 NSAssert(webView != nil, @"webView must not be nil"); 280 281 if (returnedItems.count == 0) { 282 NSError *error = [OnePasswordExtension extensionCancelledByUserError]; 283 if (completion) { 284 completion(NO, error); 285 } 286 287 return; 288 } 289 290 [self processExtensionItem:returnedItems.firstObject completion:^(NSDictionary *itemDictionary, NSError *error) { 291 if (itemDictionary.count == 0) { 292 if (completion) { 293 completion(NO, error); 294 } 295 296 return; 297 } 298 299 NSString *fillScript = itemDictionary[AppExtensionWebViewPageFillScript]; 300 [self executeFillScript:fillScript inWebView:webView completion:^(BOOL success, NSError *executeFillScriptError) { 301 if (completion) { 302 completion(success, executeFillScriptError); 303 } 304 }]; 305 }]; 306} 307 308#pragma mark - Private methods 309 310- (BOOL)isSystemAppExtensionAPIAvailable { 311#ifdef __IPHONE_8_0 312 return [NSExtensionItem class] != nil; 313#else 314 return NO; 315#endif 316} 317 318- (void)findLoginIn1PasswordWithURLString:(nonnull NSString *)URLString collectedPageDetails:(nullable NSString *)collectedPageDetails forWebViewController:(nonnull UIViewController *)forViewController sender:(nullable id)sender withWebView:(nonnull id)webView showOnlyLogins:(BOOL)yesOrNo completion:(nonnull OnePasswordSuccessCompletionBlock)completion { 319 if ([URLString length] == 0) { 320 NSError *URLStringError = [OnePasswordExtension failedToObtainURLStringFromWebViewError]; 321 NSLog(@"Failed to findLoginIn1PasswordWithURLString: %@", URLStringError); 322 if (completion) { 323 completion(NO, URLStringError); 324 } 325 return; 326 } 327 328 NSError *jsonError = nil; 329 NSData *data = [collectedPageDetails dataUsingEncoding:NSUTF8StringEncoding]; 330 NSDictionary *collectedPageDetailsDictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonError]; 331 332 if (collectedPageDetailsDictionary.count == 0) { 333 NSLog(@"Failed to parse JSON collected page details: %@", jsonError); 334 if (completion) { 335 completion(NO, jsonError); 336 } 337 return; 338 } 339 340 NSDictionary *item = @{ AppExtensionVersionNumberKey : VERSION_NUMBER, AppExtensionURLStringKey : URLString, AppExtensionWebViewPageDetails : collectedPageDetailsDictionary }; 341 342 NSString *typeIdentifier = yesOrNo ? kUTTypeAppExtensionFillWebViewAction : kUTTypeAppExtensionFillBrowserAction; 343 UIActivityViewController *activityViewController = [self activityViewControllerForItem:item viewController:forViewController sender:sender typeIdentifier:typeIdentifier]; 344 activityViewController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { 345 if (returnedItems.count == 0) { 346 NSError *error = nil; 347 if (activityError) { 348 NSLog(@"Failed to findLoginIn1PasswordWithURLString: %@", activityError); 349 error = [OnePasswordExtension failedToContactExtensionErrorWithActivityError:activityError]; 350 } 351 else { 352 error = [OnePasswordExtension extensionCancelledByUserError]; 353 } 354 355 if (completion) { 356 completion(NO, error); 357 } 358 359 return; 360 } 361 362 [self processExtensionItem:returnedItems.firstObject completion:^(NSDictionary *itemDictionary, NSError *processExtensionItemError) { 363 if (itemDictionary.count == 0) { 364 if (completion) { 365 completion(NO, processExtensionItemError); 366 } 367 368 return; 369 } 370 371 NSString *fillScript = itemDictionary[AppExtensionWebViewPageFillScript]; 372 [self executeFillScript:fillScript inWebView:webView completion:^(BOOL success, NSError *executeFillScriptError) { 373 if (completion) { 374 completion(success, executeFillScriptError); 375 } 376 }]; 377 }]; 378 }; 379 380 [forViewController presentViewController:activityViewController animated:YES completion:nil]; 381} 382 383#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 || ONE_PASSWORD_EXTENSION_ENABLE_WK_WEB_VIEW 384- (void)fillItemIntoWKWebView:(nonnull WKWebView *)webView forViewController:(nonnull UIViewController *)viewController sender:(nullable id)sender showOnlyLogins:(BOOL)yesOrNo completion:(nonnull OnePasswordSuccessCompletionBlock)completion { 385 [webView evaluateJavaScript:OPWebViewCollectFieldsScript completionHandler:^(NSString *result, NSError *error) { 386 if (result == nil) { 387 NSLog(@"1Password Extension failed to collect web page fields: %@", error); 388 if (completion) { 389 completion(NO,[OnePasswordExtension failedToCollectFieldsErrorWithUnderlyingError:error]); 390 } 391 392 return; 393 } 394 395 [self findLoginIn1PasswordWithURLString:webView.URL.absoluteString collectedPageDetails:result forWebViewController:viewController sender:sender withWebView:webView showOnlyLogins:yesOrNo completion:^(BOOL success, NSError *findLoginError) { 396 if (completion) { 397 completion(success, findLoginError); 398 } 399 }]; 400 }]; 401} 402#endif 403 404- (void)fillItemIntoUIWebView:(nonnull UIWebView *)webView webViewController:(nonnull UIViewController *)viewController sender:(nullable id)sender showOnlyLogins:(BOOL)yesOrNo completion:(nonnull OnePasswordSuccessCompletionBlock)completion { 405 NSString *collectedPageDetails = [webView stringByEvaluatingJavaScriptFromString:OPWebViewCollectFieldsScript]; 406 [self findLoginIn1PasswordWithURLString:webView.request.URL.absoluteString collectedPageDetails:collectedPageDetails forWebViewController:viewController sender:sender withWebView:webView showOnlyLogins:yesOrNo completion:^(BOOL success, NSError *error) { 407 if (completion) { 408 completion(success, error); 409 } 410 }]; 411} 412 413- (void)executeFillScript:(NSString * __nullable)fillScript inWebView:(nonnull id)webView completion:(nonnull OnePasswordSuccessCompletionBlock)completion { 414 415 if (fillScript == nil) { 416 NSLog(@"Failed to executeFillScript, fillScript is missing"); 417 if (completion) { 418 completion(NO, [OnePasswordExtension failedToFillFieldsErrorWithLocalizedErrorMessage:NSLocalizedStringFromTable(@"Failed to fill web page because script is missing", @"OnePasswordExtension", @"1Password Extension Error Message") underlyingError:nil]); 419 } 420 421 return; 422 } 423 424 NSMutableString *scriptSource = [OPWebViewFillScript mutableCopy]; 425 [scriptSource appendFormat:@"(document, %@, undefined);", fillScript]; 426 427#ifdef __IPHONE_8_0 428 if ([webView isKindOfClass:[UIWebView class]]) { 429 NSString *result = [((UIWebView *)webView) stringByEvaluatingJavaScriptFromString:scriptSource]; 430 BOOL success = (result != nil); 431 NSError *error = nil; 432 433 if (!success) { 434 NSLog(@"Cannot executeFillScript, stringByEvaluatingJavaScriptFromString failed"); 435 error = [OnePasswordExtension failedToFillFieldsErrorWithLocalizedErrorMessage:NSLocalizedStringFromTable(@"Failed to fill web page because script could not be evaluated", @"OnePasswordExtension", @"1Password Extension Error Message") underlyingError:nil]; 436 } 437 438 if (completion) { 439 completion(success, error); 440 } 441 } 442 443 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 || ONE_PASSWORD_EXTENSION_ENABLE_WK_WEB_VIEW 444 else if ([webView isKindOfClass:[WKWebView class]]) { 445 [((WKWebView *)webView) evaluateJavaScript:scriptSource completionHandler:^(NSString *result, NSError *evaluationError) { 446 BOOL success = (result != nil); 447 NSError *error = nil; 448 449 if (!success) { 450 NSLog(@"Cannot executeFillScript, evaluateJavaScript failed: %@", evaluationError); 451 error = [OnePasswordExtension failedToFillFieldsErrorWithLocalizedErrorMessage:NSLocalizedStringFromTable(@"Failed to fill web page because script could not be evaluated", @"OnePasswordExtension", @"1Password Extension Error Message") underlyingError:error]; 452 } 453 454 if (completion) { 455 completion(success, error); 456 } 457 }]; 458 } 459 #endif 460#endif 461} 462 463#ifdef __IPHONE_8_0 464- (void)processExtensionItem:(nullable NSExtensionItem *)extensionItem completion:(nonnull OnePasswordLoginDictionaryCompletionBlock)completion { 465 if (extensionItem.attachments.count == 0) { 466 NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Unexpected data returned by App Extension: extension item had no attachments." }; 467 NSError *error = [[NSError alloc] initWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeUnexpectedData userInfo:userInfo]; 468 if (completion) { 469 completion(nil, error); 470 } 471 return; 472 } 473 474 NSItemProvider *itemProvider = extensionItem.attachments.firstObject; 475 if (NO == [itemProvider hasItemConformingToTypeIdentifier:(__bridge NSString *)kUTTypePropertyList]) { 476 NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Unexpected data returned by App Extension: extension item attachment does not conform to kUTTypePropertyList type identifier" }; 477 NSError *error = [[NSError alloc] initWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeUnexpectedData userInfo:userInfo]; 478 if (completion) { 479 completion(nil, error); 480 } 481 return; 482 } 483 484 485 [itemProvider loadItemForTypeIdentifier:(__bridge NSString *)kUTTypePropertyList options:nil completionHandler:^(NSDictionary *itemDictionary, NSError *itemProviderError) { 486 NSError *error = nil; 487 if (itemDictionary.count == 0) { 488 NSLog(@"Failed to loadItemForTypeIdentifier: %@", itemProviderError); 489 error = [OnePasswordExtension failedToLoadItemProviderDataErrorWithUnderlyingError:itemProviderError]; 490 } 491 492 if (completion) { 493 if ([NSThread isMainThread]) { 494 completion(itemDictionary, error); 495 } 496 else { 497 dispatch_async(dispatch_get_main_queue(), ^{ 498 completion(itemDictionary, error); 499 }); 500 } 501 } 502 }]; 503} 504 505- (UIActivityViewController *)activityViewControllerForItem:(nonnull NSDictionary *)item viewController:(nonnull UIViewController*)viewController sender:(nullable id)sender typeIdentifier:(nonnull NSString *)typeIdentifier { 506#ifdef __IPHONE_8_0 507 NSAssert(NO == (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad && sender == nil), @"sender must not be nil on iPad."); 508 509 NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithItem:item typeIdentifier:typeIdentifier]; 510 511 NSExtensionItem *extensionItem = [[NSExtensionItem alloc] init]; 512 extensionItem.attachments = @[ itemProvider ]; 513 514 UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:@[ extensionItem ] applicationActivities:nil]; 515 516 if ([sender isKindOfClass:[UIBarButtonItem class]]) { 517 controller.popoverPresentationController.barButtonItem = sender; 518 } 519 else if ([sender isKindOfClass:[UIView class]]) { 520 controller.popoverPresentationController.sourceView = [sender superview]; 521 controller.popoverPresentationController.sourceRect = [sender frame]; 522 } 523 else { 524 NSLog(@"sender can be nil on iPhone"); 525 } 526 527 return controller; 528#else 529 return nil; 530#endif 531} 532 533#endif 534 535- (void)createExtensionItemForURLString:(nonnull NSString *)URLString webPageDetails:(nullable NSString *)webPageDetails completion:(nonnull OnePasswordExtensionItemCompletionBlock)completion { 536 NSError *jsonError = nil; 537 NSData *data = [webPageDetails dataUsingEncoding:NSUTF8StringEncoding]; 538 NSDictionary *webPageDetailsDictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonError]; 539 540 if (webPageDetailsDictionary.count == 0) { 541 NSLog(@"Failed to parse JSON collected page details: %@", jsonError); 542 if (completion) { 543 completion(nil, jsonError); 544 } 545 return; 546 } 547 548 NSDictionary *item = @{ AppExtensionVersionNumberKey : VERSION_NUMBER, AppExtensionURLStringKey : URLString, AppExtensionWebViewPageDetails : webPageDetailsDictionary }; 549 550 NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithItem:item typeIdentifier:kUTTypeAppExtensionFillBrowserAction]; 551 552 NSExtensionItem *extensionItem = [[NSExtensionItem alloc] init]; 553 extensionItem.attachments = @[ itemProvider ]; 554 555 if (completion) { 556 if ([NSThread isMainThread]) { 557 completion(extensionItem, nil); 558 } 559 else { 560 dispatch_async(dispatch_get_main_queue(), ^{ 561 completion(extensionItem, nil); 562 }); 563 } 564 } 565} 566 567#pragma mark - Errors 568 569+ (NSError *)systemAppExtensionAPINotAvailableError { 570 NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : NSLocalizedStringFromTable(@"App Extension API is not available in this version of iOS", @"OnePasswordExtension", @"1Password Extension Error Message") }; 571 return [NSError errorWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeAPINotAvailable userInfo:userInfo]; 572} 573 574 575+ (NSError *)extensionCancelledByUserError { 576 NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : NSLocalizedStringFromTable(@"1Password Extension was cancelled by the user", @"OnePasswordExtension", @"1Password Extension Error Message") }; 577 return [NSError errorWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeCancelledByUser userInfo:userInfo]; 578} 579 580+ (NSError *)failedToContactExtensionErrorWithActivityError:(nullable NSError *)activityError { 581 NSMutableDictionary *userInfo = [NSMutableDictionary new]; 582 userInfo[NSLocalizedDescriptionKey] = NSLocalizedStringFromTable(@"Failed to contact the 1Password Extension", @"OnePasswordExtension", @"1Password Extension Error Message"); 583 if (activityError) { 584 userInfo[NSUnderlyingErrorKey] = activityError; 585 } 586 587 return [NSError errorWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeFailedToContactExtension userInfo:userInfo]; 588} 589 590+ (NSError *)failedToCollectFieldsErrorWithUnderlyingError:(nullable NSError *)underlyingError { 591 NSMutableDictionary *userInfo = [NSMutableDictionary new]; 592 userInfo[NSLocalizedDescriptionKey] = NSLocalizedStringFromTable(@"Failed to execute script that collects web page information", @"OnePasswordExtension", @"1Password Extension Error Message"); 593 if (underlyingError) { 594 userInfo[NSUnderlyingErrorKey] = underlyingError; 595 } 596 597 return [NSError errorWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeCollectFieldsScriptFailed userInfo:userInfo]; 598} 599 600+ (NSError *)failedToFillFieldsErrorWithLocalizedErrorMessage:(nullable NSString *)errorMessage underlyingError:(nullable NSError *)underlyingError { 601 NSMutableDictionary *userInfo = [NSMutableDictionary new]; 602 if (errorMessage) { 603 userInfo[NSLocalizedDescriptionKey] = errorMessage; 604 } 605 if (underlyingError) { 606 userInfo[NSUnderlyingErrorKey] = underlyingError; 607 } 608 609 return [NSError errorWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeFillFieldsScriptFailed userInfo:userInfo]; 610} 611 612+ (NSError *)failedToLoadItemProviderDataErrorWithUnderlyingError:(nullable NSError *)underlyingError { 613 NSMutableDictionary *userInfo = [NSMutableDictionary new]; 614 userInfo[NSLocalizedDescriptionKey] = NSLocalizedStringFromTable(@"Failed to parse information returned by 1Password Extension", @"OnePasswordExtension", @"1Password Extension Error Message"); 615 if (underlyingError) { 616 userInfo[NSUnderlyingErrorKey] = underlyingError; 617 } 618 619 return [[NSError alloc] initWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeFailedToLoadItemProviderData userInfo:userInfo]; 620} 621 622+ (NSError *)failedToObtainURLStringFromWebViewError { 623 NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : NSLocalizedStringFromTable(@"Failed to obtain URL String from web view. The web view must be loaded completely when calling the 1Password Extension", @"OnePasswordExtension", @"1Password Extension Error Message") }; 624 return [NSError errorWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeFailedToObtainURLStringFromWebView userInfo:userInfo]; 625} 626 627#pragma mark - WebView field collection and filling scripts 628 629static NSString *const OPWebViewCollectFieldsScript = @";(function(document, undefined) {\ 630var isFirefox = false, isChrome = false, isSafari = true;\ 631\ 632 document.elementsByOPID={};document.addEventListener('input',function(b){!1!==b.a&&'input'===b.target.tagName.toLowerCase()&&(b.target.dataset['com.agilebits.onepassword.userEdited']='yes')},!0);\ 633function q(b,d){function f(a,e){var c=a[e];if('string'==typeof c)return c;c=a.getAttribute(e);return'string'==typeof c?c:null}function h(a,e){if(-1===['text','password'].indexOf(e.type.toLowerCase())||!(m.test(a.value)||m.test(a.htmlID)||m.test(a.htmlName)||m.test(a.placeholder)||m.test(a['label-tag'])||m.test(a['label-data'])||m.test(a['label-aria'])))return!1;if(!a.visible)return!0;if('password'==e.type.toLowerCase())return!1;var c=e.type;v(e,!0);return c!==e.type}function n(a){switch(p(a.type)){case 'checkbox':return a.checked?\ 634'':'';case 'hidden':a=a.value;if(!a||'number'!=typeof a.length)return'';254<a.length&&(a=a.substr(0,254)+'...SNIPPED');return a;default:return a.value}}function l(a){return a.options?(a=Array.prototype.slice.call(a.options).map(function(a){var c=a.text,c=c?p(c).replace(/\\s/mg,'').replace(/[~`!@$%^&*()\\-_+=:;'\"\\[\\]|\\\\,<.>\\/?]/mg,''):null;return[c?c:null,a.value]}),{options:a}):null}function r(a){var e;for(a=a.parentElement||a.parentNode;a&&'td'!=p(a.tagName);)a=a.parentElement||a.parentNode;if(!a||\ 635void 0===a)return null;e=a.parentElement||a.parentNode;if('tr'!=e.tagName.toLowerCase())return null;e=e.previousElementSibling;if(!e||'tr'!=(e.tagName+'').toLowerCase()||e.cells&&a.cellIndex>=e.cells.length)return null;a=e.cells[a.cellIndex];a=a.textContent||a.innerText;return a=x(a)}function s(a){var e,c=[];if(a.labels&&a.labels.length&&0<a.labels.length)c=Array.prototype.slice.call(a.labels);else{a.id&&(c=c.concat(Array.prototype.slice.call(w(b,'label[for='+JSON.stringify(a.id)+']'))));if(a.name){e=\ 636w(b,'label[for='+JSON.stringify(a.name)+']');for(var f=0;f<e.length;f++)-1===c.indexOf(e[f])&&c.push(e[f])}for(e=a;e&&e!=b;e=e.parentNode)'label'===p(e.tagName)&&-1===c.indexOf(e)&&c.push(e)}0===c.length&&(e=a.parentNode,'dd'===e.tagName.toLowerCase()&&null!==e.previousElementSibling&&'dt'===e.previousElementSibling.tagName.toLowerCase()&&c.push(e.previousElementSibling));return 0<c.length?c.map(function(a){return(a.textContent||a.innerText).replace(/^\\s+/,'').replace(/\\s+$/,'').replace('\\n','').replace(/\\s{2,}/,\ 637' ')}).join(''):null}function g(a,e,c,b){void 0!==b&&b===c||null===c||void 0===c||(a[e]=c)}function p(a){return'string'===typeof a?a.toLowerCase():(''+a).toLowerCase()}function w(a,b){var c=[];try{c=a.querySelectorAll(b)}catch(f){}return c}var t=b.defaultView?b.defaultView:window,m=RegExp('((\\\\b|_|-)pin(\\\\b|_|-)|password|passwort|kennwort|(\\\\b|_|-)passe(\\\\b|_|-)|contraseña|senha||adgangskode|hasło|wachtwoord)','i'),u=Array.prototype.slice.call(w(b,'form')).map(function(a,b){var c={},d='__form__'+\ 638b;a.opid=d;c.opid=d;g(c,'htmlName',f(a,'name'));g(c,'htmlID',f(a,'id'));d=f(a,'action');d=new URL(d,window.location.href);g(c,'htmlAction',d?d.href:null);g(c,'htmlMethod',f(a,'method'));return c}),E=Array.prototype.slice.call(y(b)).map(function(a,e){var c={},d='__'+e,k=-1==a.maxLength?999:a.maxLength;if(!k||'number'===typeof k&&isNaN(k))k=999;b.elementsByOPID[d]=a;a.opid=d;c.opid=d;c.elementNumber=e;g(c,'maxLength',Math.min(k,999),999);c.visible=z(a);c.viewable=A(a);g(c,'htmlID',f(a,'id'));g(c,'htmlName',\ 639f(a,'name'));g(c,'htmlClass',f(a,'class'));g(c,'tabindex',f(a,'tabindex'));g(c,'title',f(a,'title'));g(c,'userEdited',!!a.dataset['com.agilebits.onepassword.userEdited']);if('hidden'!=p(a.type)){g(c,'label-tag',s(a));g(c,'label-data',f(a,'data-label'));g(c,'label-aria',f(a,'aria-label'));g(c,'label-top',r(a));d=[];for(k=a;k&&k.nextSibling;){k=k.nextSibling;if(B(k))break;C(d,k)}g(c,'label-right',d.join(''));d=[];D(a,d);d=d.reverse().join('');g(c,'label-left',d);g(c,'placeholder',f(a,'placeholder'))}g(c,\ 640'rel',f(a,'rel'));g(c,'type',p(f(a,'type')));g(c,'value',n(a));g(c,'checked',a.checked,!1);g(c,'autoCompleteType',a.getAttribute('x-autocompletetype')||a.getAttribute('autocompletetype')||a.getAttribute('autocomplete'),'off');g(c,'disabled',a.disabled);g(c,'readonly',a.b||a.readOnly);g(c,'selectInfo',l(a));g(c,'aria-hidden','true'==a.getAttribute('aria-hidden'),!1);g(c,'aria-disabled','true'==a.getAttribute('aria-disabled'),!1);g(c,'aria-haspopup','true'==a.getAttribute('aria-haspopup'),!1);g(c,'data-unmasked',\ 641a.dataset.unmasked);g(c,'data-stripe',f(a,'data-stripe'));g(c,'onepasswordFieldType',a.dataset.onepasswordFieldType||a.type);g(c,'onepasswordDesignation',a.dataset.onepasswordDesignation);g(c,'onepasswordSignInUrl',a.dataset.onepasswordSignInUrl);g(c,'onepasswordSectionTitle',a.dataset.onepasswordSectionTitle);g(c,'onepasswordSectionFieldKind',a.dataset.onepasswordSectionFieldKind);g(c,'onepasswordSectionFieldTitle',a.dataset.onepasswordSectionFieldTitle);g(c,'onepasswordSectionFieldValue',a.dataset.onepasswordSectionFieldValue);\ 642a.form&&(c.form=f(a.form,'opid'));g(c,'fakeTested',h(c,a),!1);return c});E.filter(function(a){return a.fakeTested}).forEach(function(a){var e=b.elementsByOPID[a.opid];e.getBoundingClientRect();var c=e.value;!e||e&&'function'!==typeof e.click||e.click();v(e,!1);e.dispatchEvent(F(e,'keydown'));e.dispatchEvent(F(e,'keypress'));e.dispatchEvent(F(e,'keyup'));e.value!==c&&(e.value=c);e.click&&e.click();a.postFakeTestVisible=z(e);a.postFakeTestViewable=A(e);a.postFakeTestType=e.type;a=e.value;var c=e.ownerDocument.createEvent('HTMLEvents'),\ 643d=e.ownerDocument.createEvent('HTMLEvents');e.dispatchEvent(F(e,'keydown'));e.dispatchEvent(F(e,'keypress'));e.dispatchEvent(F(e,'keyup'));d.initEvent('input',!0,!0);e.dispatchEvent(d);c.initEvent('change',!0,!0);e.dispatchEvent(c);e.blur();e.value!==a&&(e.value=a)});t={documentUUID:d,title:b.title,url:t.location.href,documentUrl:b.location.href,tabUrl:t.location.href,forms:function(a){var b={};a.forEach(function(a){b[a.opid]=a});return b}(u),fields:E,collectedTimestamp:(new Date).getTime()};(u=document.querySelector('[data-onepassword-title]'))&&\ 644u.dataset[DISPLAY_TITLE_ATTRIBUE]&&(t.displayTitle=u.dataset.onepasswordTitle);return t};document.elementForOPID=G;function F(b,d){var f;isFirefox?(f=document.createEvent('KeyboardEvent'),f.initKeyEvent(d,!0,!1,null,!1,!1,!1,!1,0,0)):(f=b.ownerDocument.createEvent('Events'),f.initEvent(d,!0,!1),f.charCode=0,f.keyCode=0,f.which=0,f.srcElement=b,f.target=b);return f}window.LOGIN_TITLES=[/^\\W*log\\W*[oi]n\\W*$/i,/log\\W*[oi]n (?:securely|now)/i,/^\\W*sign\\W*[oi]n\\W*$/i,'continue','submit','weiter','accès','вход','connexion','entrar','anmelden','accedi','valider','',' ','change password'];\ 645window.LOGIN_RED_HERRING_TITLES=['already have an account','sign in with'];window.REGISTER_TITLES='register;sign up;signup;join;create my account;регистрация;inscription;regístrate;cadastre-se;registrieren;registrazione;; '.split(';');window.SEARCH_TITLES='search find поиск найти искать recherche suchen buscar suche ricerca procurar '.split(' ');window.FORGOT_PASSWORD_TITLES='forgot geändert vergessen hilfe changeemail español'.split(' ');\ 646window.REMEMBER_ME_TITLES=['remember me','rememberme','keep me signed in'];window.BACK_TITLES=['back','назад'];function x(b){var d=null;b&&(d=b.replace(/^\\s+|\\s+$|\\r?\\n.*$/mg,''),d=0<d.length?d:null);return d}function C(b,d){var f;f='';3===d.nodeType?f=d.nodeValue:1===d.nodeType&&(f=d.textContent||d.innerText);(f=x(f))&&b.push(f)}\ 647function B(b){var d;b&&void 0!==b?(d='select option input form textarea button table iframe body head script'.split(' '),b?(b=b?(b.tagName||'').toLowerCase():'',d=d.constructor==Array?0<=d.indexOf(b):b===d):d=!1):d=!0;return d}\ 648function D(b,d,f){var h;for(f||(f=0);b&&b.previousSibling;){b=b.previousSibling;if(B(b))return;C(d,b)}if(b&&0===d.length){for(h=null;!h;){b=b.parentElement||b.parentNode;if(!b)return;for(h=b.previousSibling;h&&!B(h)&&h.lastChild;)h=h.lastChild}B(h)||(C(d,h),0===d.length&&D(h,d,f+1))}}\ 649function z(b){var d=b;b=(b=b.ownerDocument)?b.defaultView:{};for(var f;d&&d!==document;){f=b.getComputedStyle?b.getComputedStyle(d,null):d.style;if(!f)return!0;if('none'===f.display||'hidden'==f.visibility)return!1;d=d.parentNode}return d===document}\ 650function A(b){var d=b.ownerDocument.documentElement,f=b.getBoundingClientRect(),h=d.scrollWidth,n=d.scrollHeight,l=f.left-d.clientLeft,d=f.top-d.clientTop,r;if(!z(b)||!b.offsetParent||10>b.clientWidth||10>b.clientHeight)return!1;var s=b.getClientRects();if(0===s.length)return!1;for(var g=0;g<s.length;g++)if(r=s[g],r.left>h||0>r.right)return!1;if(0>l||l>h||0>d||d>n)return!1;for(f=b.ownerDocument.elementFromPoint(l+(f.right>window.innerWidth?(window.innerWidth-l)/2:f.width/2),d+(f.bottom>window.innerHeight?\ 651(window.innerHeight-d)/2:f.height/2));f&&f!==b&&f!==document;){if(f.tagName&&'string'===typeof f.tagName&&'label'===f.tagName.toLowerCase()&&b.labels&&0<b.labels.length)return 0<=Array.prototype.slice.call(b.labels).indexOf(f);f=f.parentNode}return f===b}\ 652function G(b){var d;if(void 0===b||null===b)return null;try{var f=Array.prototype.slice.call(y(document)),h=f.filter(function(d){return d.opid==b});if(0<h.length)d=h[0],1<h.length&&console.warn('More than one element found with opid '+b);else{var n=parseInt(b.split('__')[1],10);isNaN(n)||(d=f[n])}}catch(l){console.error('An unexpected error occurred: '+l)}finally{return d}};function y(b){var d=[];try{d=b.querySelectorAll('input, select, button')}catch(f){}return d}function v(b,d){if(d){var f=b.value;b.focus();b.value!==f&&(b.value=f)}else b.focus()};\ 653 \ 654 return JSON.stringify(q(document, 'oneshotUUID'));\ 655})(document);\ 656\ 657"; 658 659static NSString *const OPWebViewFillScript = @";(function(document, fillScript, undefined) {\ 660var isFirefox = false, isChrome = false, isSafari = true;\ 661\ 662 var g=!0,k=!0;\ 663function n(a){var b=null;return a?0===a.indexOf('https://')&&'http:'===document.location.protocol&&(b=document.querySelectorAll('input[type=password]'),0<b.length&&(confirmResult=confirm('1Password warning: This is an unsecured HTTP page, and any information you submit can potentially be seen and changed by others. This Login was originally saved on a secure (HTTPS) page.\\n\\nDo you still wish to fill this login?'),0==confirmResult))?!0:!1:!1}\ 664function m(a){var b,c=[],d=a.properties,e=1,h,f=[];d&&d.delay_between_operations&&(e=d.delay_between_operations);if(!n(a.savedURL)){h=function(a,b){var d=a[0];if(void 0===d)b();else{if('delay'===d.operation||'delay'===d[0])e=d.parameters?d.parameters[0]:d[1];else{if(d=p(d))for(var l=0;l<d.length;l++)-1===f.indexOf(d[l])&&f.push(d[l]);c=c.concat(f.map(function(a){return a&&a.hasOwnProperty('opid')?a.opid:null}))}setTimeout(function(){h(a.slice(1),b)},e)}};if(b=a.options)b.hasOwnProperty('animate')&&\ 665(k=b.animate),b.hasOwnProperty('markFilling')&&(g=b.markFilling);a.itemType&&'fillPassword'===a.itemType&&(g=!1);a.hasOwnProperty('script')&&(b=a.script,h(b,function(){a.hasOwnProperty('autosubmit')&&'function'==typeof autosubmit&&(a.itemType&&'fillLogin'!==a.itemType||(0<f.length?setTimeout(function(){autosubmit(a.autosubmit,d.allow_clicky_autosubmit,f)},AUTOSUBMIT_DELAY):DEBUG_AUTOSUBMIT&&console.log('[AUTOSUBMIT] Not attempting to submit since no fields were filled: ',f)));'object'==typeof protectedGlobalPage&&\ 666protectedGlobalPage.b('fillItemResults',{documentUUID:documentUUID,fillContextIdentifier:a.fillContextIdentifier,usedOpids:c},function(){fillingItemType=null})}))}}var x={fill_by_opid:q,fill_by_query:r,click_on_opid:s,click_on_query:t,touch_all_fields:u,simple_set_value_by_query:v,focus_by_opid:w,delay:null};\ 667function p(a){var b;if(a.hasOwnProperty('operation')&&a.hasOwnProperty('parameters'))b=a.operation,a=a.parameters;else if('[object Array]'===Object.prototype.toString.call(a))b=a[0],a=a.splice(1);else return null;return x.hasOwnProperty(b)?x[b].apply(this,a):null}function q(a,b){var c;return(c=y(a))?(z(c,b),[c]):null}function r(a,b){var c;c=A(a);return Array.prototype.map.call(Array.prototype.slice.call(c),function(a){z(a,b);return a},this)}\ 668function v(a,b){var c,d=[];c=A(a);Array.prototype.forEach.call(Array.prototype.slice.call(c),function(a){a.disabled||a.a||a.readOnly||void 0===a.value||(a.value=b,d.push(a))});return d}function w(a){if(a=y(a))'function'===typeof a.click&&a.click(),'function'===typeof a.focus&&B(a,!0);return null}function s(a){return(a=y(a))?C(a)?[a]:null:null}\ 669function t(a){a=A(a);return Array.prototype.map.call(Array.prototype.slice.call(a),function(a){C(a);'function'===typeof a.click&&a.click();'function'===typeof a.focus&&B(a,!0);return[a]},this)}function u(){D()};var E={'true':!0,y:!0,1:!0,yes:!0,'':!0},F=200;function z(a,b){var c;if(a&&null!==b&&void 0!==b&&!(a.disabled||a.a||a.readOnly))switch(g&&a.form&&!a.form.opfilled&&(a.form.opfilled=!0),a.type?a.type.toLowerCase():null){case 'checkbox':c=b&&1<=b.length&&E.hasOwnProperty(b.toLowerCase())&&!0===E[b.toLowerCase()];a.checked===c||G(a,function(a){a.checked=c});break;case 'radio':!0===E[b.toLowerCase()]&&a.click();break;default:a.value==b||G(a,function(a){a.value=b})}}\ 670function G(a,b){H(a);b(a);I(a);J(a)&&(a.className+=' com-agilebits-onepassword-extension-animated-fill',setTimeout(function(){a&&a.className&&(a.className=a.className.replace(/(\\s)?com-agilebits-onepassword-extension-animated-fill/,''))},F))};document.elementForOPID=y;function K(a,b){var c;isFirefox?(c=document.createEvent('KeyboardEvent'),c.initKeyEvent(b,!0,!1,null,!1,!1,!1,!1,0,0)):(c=a.ownerDocument.createEvent('Events'),c.initEvent(b,!0,!1),c.charCode=0,c.keyCode=0,c.which=0,c.srcElement=a,c.target=a);return c}function H(a){var b=a.value;C(a);B(a,!1);a.dispatchEvent(K(a,'keydown'));a.dispatchEvent(K(a,'keypress'));a.dispatchEvent(K(a,'keyup'));a.value!==b&&(a.value=b)}\ 671function I(a){var b=a.value,c=a.ownerDocument.createEvent('HTMLEvents'),d=a.ownerDocument.createEvent('HTMLEvents');a.dispatchEvent(K(a,'keydown'));a.dispatchEvent(K(a,'keypress'));a.dispatchEvent(K(a,'keyup'));d.initEvent('input',!0,!0);a.dispatchEvent(d);c.initEvent('change',!0,!0);a.dispatchEvent(c);a.blur();a.value!==b&&(a.value=b)}function C(a){if(!a||a&&'function'!==typeof a.click)return!1;a.click();return!0}\ 672function L(){var a=RegExp('((\\\\b|_|-)pin(\\\\b|_|-)|password|passwort|kennwort|passe|contraseña|senha||adgangskode|hasło|wachtwoord)','i');return Array.prototype.slice.call(A(\"input[type='text']\")).filter(function(b){return b.value&&a.test(b.value)},this)}function D(){L().forEach(function(a){H(a);a.click&&a.click();I(a)})}\ 673window.LOGIN_TITLES=[/^\\W*log\\W*[oi]n\\W*$/i,/log\\W*[oi]n (?:securely|now)/i,/^\\W*sign\\W*[oi]n\\W*$/i,'continue','submit','weiter','accès','вход','connexion','entrar','anmelden','accedi','valider','',' ','change password'];window.LOGIN_RED_HERRING_TITLES=['already have an account','sign in with'];window.REGISTER_TITLES='register;sign up;signup;join;create my account;регистрация;inscription;regístrate;cadastre-se;registrieren;registrazione;; '.split(';');\ 674window.SEARCH_TITLES='search find поиск найти искать recherche suchen buscar suche ricerca procurar '.split(' ');window.FORGOT_PASSWORD_TITLES='forgot geändert vergessen hilfe changeemail español'.split(' ');window.REMEMBER_ME_TITLES=['remember me','rememberme','keep me signed in'];window.BACK_TITLES=['back','назад'];\ 675function J(a){var b;if(b=k)a:{b=a;for(var c=a.ownerDocument,c=c?c.defaultView:{},d;b&&b!==document;){d=c.getComputedStyle?c.getComputedStyle(b,null):b.style;if(!d){b=!0;break a}if('none'===d.display||'hidden'==d.visibility){b=!1;break a}b=b.parentNode}b=b===document}return b?-1!=='email text password number tel url'.split(' ').indexOf(a.type||''):!1}\ 676function y(a){var b;if(void 0===a||null===a)return null;try{var c=Array.prototype.slice.call(A('input, select, button')),d=c.filter(function(b){return b.opid==a});if(0<d.length)b=d[0],1<d.length&&console.warn('More than one element found with opid '+a);else{var e=parseInt(a.split('__')[1],10);isNaN(e)||(b=c[e])}}catch(h){console.error('An unexpected error occurred: '+h)}finally{return b}};function A(a){var b=document,c=[];try{c=b.querySelectorAll(a)}catch(d){}return c}function B(a,b){if(b){var c=a.value;a.focus();a.value!==c&&(a.value=c)}else a.focus()};\ 677\ 678 m(fillScript);\ 679 return JSON.stringify({'success': true});\ 680})\ 681\ 682"; 683 684 685#pragma mark - Deprecated methods 686 687/* 688 Deprecated in version 1.5 689 Use fillItemIntoWebView:forViewController:sender:showOnlyLogins:completion: instead 690 */ 691- (void)fillLoginIntoWebView:(nonnull id)webView forViewController:(nonnull UIViewController *)viewController sender:(nullable id)sender completion:(nonnull OnePasswordSuccessCompletionBlock)completion { 692 [self fillItemIntoWebView:webView forViewController:viewController sender:sender showOnlyLogins:YES completion:completion]; 693} 694 695@end