OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #import "ios/chrome/browser/ui/activity_services/activity_service_controller.h" |
| 6 |
| 7 #import <MobileCoreServices/MobileCoreServices.h> |
| 8 |
| 9 #include "base/mac/scoped_nsobject.h" |
| 10 #import "base/test/ios/wait_util.h" |
| 11 #include "components/reading_list/core/reading_list_switches.h" |
| 12 #import "ios/chrome/browser/ui/activity_services/activity_type_util.h" |
| 13 #import "ios/chrome/browser/ui/activity_services/appex_constants.h" |
| 14 #import "ios/chrome/browser/ui/activity_services/chrome_activity_item_source.h" |
| 15 #import "ios/chrome/browser/ui/activity_services/print_activity.h" |
| 16 #import "ios/chrome/browser/ui/activity_services/share_to_data.h" |
| 17 #include "ios/web/public/test/test_web_thread_bundle.h" |
| 18 #include "testing/gtest_mac.h" |
| 19 #include "testing/platform_test.h" |
| 20 #import "third_party/ocmock/OCMock/OCMock.h" |
| 21 #import "third_party/ocmock/gtest_support.h" |
| 22 |
| 23 @interface ActivityServiceController (CrVisibleForTesting) |
| 24 - (NSArray*)activityItemsForData:(ShareToData*)data; |
| 25 - (NSArray*)applicationActivitiesForData:(ShareToData*)data |
| 26 controller:(UIViewController*)controller; |
| 27 - (BOOL)processItemsReturnedFromActivity:(NSString*)activityType |
| 28 status:(ShareTo::ShareResult)result |
| 29 items:(NSArray*)extensionItems; |
| 30 // Setter function for mocking during testing |
| 31 - (void)setShareToDelegateForTesting:(id<ShareToDelegate>)delegate; |
| 32 @end |
| 33 |
| 34 namespace { |
| 35 |
| 36 class ActivityServiceControllerTest : public PlatformTest { |
| 37 protected: |
| 38 void SetUp() override { |
| 39 PlatformTest::SetUp(); |
| 40 parentController_.reset( |
| 41 [[UIViewController alloc] initWithNibName:nil bundle:nil]); |
| 42 [[UIApplication sharedApplication] keyWindow].rootViewController = |
| 43 parentController_; |
| 44 shareToDelegate_.reset( |
| 45 [[OCMockObject mockForProtocol:@protocol(ShareToDelegate)] retain]); |
| 46 shareData_.reset([[ShareToData alloc] |
| 47 initWithURL:GURL("https://chromium.org") |
| 48 title:@"" |
| 49 isOriginalTitle:YES |
| 50 isPagePrintable:YES]); |
| 51 } |
| 52 |
| 53 void TearDown() override { |
| 54 [[UIApplication sharedApplication] keyWindow].rootViewController = nil; |
| 55 PlatformTest::TearDown(); |
| 56 } |
| 57 |
| 58 id<ShareToDelegate> GetShareToDelegate() { |
| 59 return static_cast<id<ShareToDelegate>>(shareToDelegate_.get()); |
| 60 } |
| 61 |
| 62 CGRect AnchorRect() { |
| 63 // On iPad, UIPopovers must be anchored to rectangles that have a non zero |
| 64 // size. |
| 65 return CGRectMake(0, 0, 1, 1); |
| 66 } |
| 67 |
| 68 UIView* AnchorView() { |
| 69 // On iPad, UIPopovers must be anchored to non nil views. |
| 70 return [parentController_.get() view]; |
| 71 } |
| 72 |
| 73 BOOL ArrayContainsImageSource(NSArray* array) { |
| 74 for (NSObject* item in array) { |
| 75 if ([item class] == [UIActivityImageSource class]) { |
| 76 return YES; |
| 77 } |
| 78 } |
| 79 return NO; |
| 80 } |
| 81 |
| 82 // Search |array| for id<UIActivityItemSource> objects. Returns an array of |
| 83 // matching NSExtensionItem objects returned by calling them. |
| 84 NSArray* FindItemsForActivityType(NSArray* array, NSString* activityType) { |
| 85 id mockActivityViewController = |
| 86 [OCMockObject niceMockForClass:[UIActivityViewController class]]; |
| 87 NSMutableArray* result = [NSMutableArray array]; |
| 88 for (id item in array) { |
| 89 if ([item respondsToSelector:@selector(activityViewController: |
| 90 itemForActivityType:)]) { |
| 91 id resultItem = [item activityViewController:mockActivityViewController |
| 92 itemForActivityType:activityType]; |
| 93 if ([resultItem isKindOfClass:[NSExtensionItem class]]) |
| 94 [result addObject:resultItem]; |
| 95 } |
| 96 } |
| 97 return result; |
| 98 } |
| 99 |
| 100 // Searches |array| for objects of class |klass| and returns them in an |
| 101 // autoreleased array. |
| 102 NSArray* FindItemsOfClass(NSArray* array, Class klass) { |
| 103 NSMutableArray* result = [NSMutableArray array]; |
| 104 for (id item in array) { |
| 105 if ([item isKindOfClass:klass]) |
| 106 [result addObject:item]; |
| 107 } |
| 108 return result; |
| 109 } |
| 110 |
| 111 // Searches |array| for objects returning |inUTType| conforming objects. |
| 112 // Returns an autoreleased array of conforming objects. |
| 113 NSArray* FindItemsEqualsToUTType(NSArray* array, |
| 114 NSString* activityType, |
| 115 NSString* inUTType) { |
| 116 id mockActivityViewController = |
| 117 [OCMockObject niceMockForClass:[UIActivityViewController class]]; |
| 118 NSMutableArray* result = [NSMutableArray array]; |
| 119 for (id item in array) { |
| 120 if (![item conformsToProtocol:@protocol(UIActivityItemSource)]) |
| 121 continue; |
| 122 SEL dataTypeSelector = |
| 123 @selector(activityViewController:dataTypeIdentifierForActivityType:); |
| 124 if (![item respondsToSelector:dataTypeSelector]) |
| 125 continue; |
| 126 NSString* itemDataType = |
| 127 [item activityViewController:mockActivityViewController |
| 128 dataTypeIdentifierForActivityType:activityType]; |
| 129 if ([itemDataType isEqualToString:inUTType]) { |
| 130 [result addObject:item]; |
| 131 } |
| 132 } |
| 133 return result; |
| 134 } |
| 135 |
| 136 // Calls -processItemsReturnedFromActivity:status:items: with the provided |
| 137 // |extensionItem| and expects failure. |
| 138 void ProcessItemsReturnedFromActivityFailure(NSArray* extensionItems, |
| 139 BOOL expectedResetUI) { |
| 140 base::scoped_nsobject<ActivityServiceController> activityController( |
| 141 [[ActivityServiceController alloc] init]); |
| 142 |
| 143 // Sets up a Mock ShareToDelegate object to check that the ShareToDelegate |
| 144 // callback function is not called. |
| 145 OCMockObject* shareToDelegateMock = |
| 146 [OCMockObject mockForProtocol:@protocol(ShareToDelegate)]; |
| 147 __block bool blockCalled = false; |
| 148 void (^validationBlock)(NSInvocation*) = ^(NSInvocation* invocation) { |
| 149 blockCalled = true; |
| 150 }; |
| 151 // OCMock does not allow "any" specification for non-object parameters. |
| 152 // To implement something that accept any non-SHARE_SUCCESS parameter |
| 153 // to calling this method, all the non-success values have to be |
| 154 // enumerated. |
| 155 [[[shareToDelegateMock stub] andDo:validationBlock] |
| 156 passwordAppExDidFinish:ShareTo::ShareResult::SHARE_CANCEL |
| 157 username:OCMOCK_ANY |
| 158 password:OCMOCK_ANY |
| 159 successMessage:OCMOCK_ANY]; |
| 160 [[[shareToDelegateMock stub] andDo:validationBlock] |
| 161 passwordAppExDidFinish:ShareTo::ShareResult::SHARE_NETWORK_FAILURE |
| 162 username:OCMOCK_ANY |
| 163 password:OCMOCK_ANY |
| 164 successMessage:OCMOCK_ANY]; |
| 165 [[[shareToDelegateMock stub] andDo:validationBlock] |
| 166 passwordAppExDidFinish:ShareTo::ShareResult::SHARE_SIGN_IN_FAILURE |
| 167 username:OCMOCK_ANY |
| 168 password:OCMOCK_ANY |
| 169 successMessage:OCMOCK_ANY]; |
| 170 [[[shareToDelegateMock stub] andDo:validationBlock] |
| 171 passwordAppExDidFinish:ShareTo::ShareResult::SHARE_ERROR |
| 172 username:OCMOCK_ANY |
| 173 password:OCMOCK_ANY |
| 174 successMessage:OCMOCK_ANY]; |
| 175 [[[shareToDelegateMock stub] andDo:validationBlock] |
| 176 passwordAppExDidFinish:ShareTo::ShareResult::SHARE_UNKNOWN_RESULT |
| 177 username:OCMOCK_ANY |
| 178 password:OCMOCK_ANY |
| 179 successMessage:OCMOCK_ANY]; |
| 180 [activityController setShareToDelegateForTesting:(id)shareToDelegateMock]; |
| 181 |
| 182 // Sets up the returned item from a Password Management App Extension. |
| 183 NSString* activityType = activity_services::kAppExtensionLastPass; |
| 184 ShareTo::ShareResult result = ShareTo::ShareResult::SHARE_SUCCESS; |
| 185 BOOL resetUI = |
| 186 [activityController processItemsReturnedFromActivity:activityType |
| 187 status:result |
| 188 items:extensionItems]; |
| 189 ASSERT_EQ(expectedResetUI, resetUI); |
| 190 base::test::ios::WaitUntilCondition(^{ |
| 191 return blockCalled; |
| 192 }); |
| 193 EXPECT_OCMOCK_VERIFY(shareToDelegateMock); |
| 194 } |
| 195 |
| 196 web::TestWebThreadBundle thread_bundle_; |
| 197 base::scoped_nsobject<UIViewController> parentController_; |
| 198 base::scoped_nsobject<OCMockObject> shareToDelegate_; |
| 199 base::scoped_nsobject<ShareToData> shareData_; |
| 200 }; |
| 201 |
| 202 TEST_F(ActivityServiceControllerTest, PresentAndDismissController) { |
| 203 [[shareToDelegate_ expect] shareDidComplete:ShareTo::ShareResult::SHARE_CANCEL |
| 204 successMessage:[OCMArg isNil]]; |
| 205 |
| 206 UIViewController* parentController = |
| 207 static_cast<UIViewController*>(parentController_.get()); |
| 208 base::scoped_nsobject<ActivityServiceController> activityController( |
| 209 [[ActivityServiceController alloc] init]); |
| 210 EXPECT_FALSE([activityController isActive]); |
| 211 |
| 212 // Test sharing. |
| 213 [activityController shareWithData:shareData_ |
| 214 controller:parentController |
| 215 browserState:nullptr |
| 216 shareToDelegate:GetShareToDelegate() |
| 217 fromRect:AnchorRect() |
| 218 inView:AnchorView()]; |
| 219 EXPECT_TRUE([activityController isActive]); |
| 220 |
| 221 // Cancels sharing and isActive flag should be turned off. |
| 222 [activityController cancelShareAnimated:NO]; |
| 223 base::test::ios::WaitUntilCondition(^bool() { |
| 224 return ![activityController isActive]; |
| 225 }); |
| 226 EXPECT_OCMOCK_VERIFY(shareToDelegate_); |
| 227 } |
| 228 |
| 229 // Verifies that an UIActivityImageSource is sent to the |
| 230 // UIActivityViewController if and only if the ShareToData contains an image. |
| 231 TEST_F(ActivityServiceControllerTest, ActivityItemsForData) { |
| 232 base::scoped_nsobject<ActivityServiceController> activityController( |
| 233 [[ActivityServiceController alloc] init]); |
| 234 |
| 235 // ShareToData does not contain an image, so the result items array will not |
| 236 // contain an image source. |
| 237 base::scoped_nsobject<ShareToData> data([[ShareToData alloc] |
| 238 initWithURL:GURL("https://chromium.org") |
| 239 title:@"foo" |
| 240 isOriginalTitle:YES |
| 241 isPagePrintable:YES]); |
| 242 NSArray* items = [activityController activityItemsForData:data]; |
| 243 EXPECT_FALSE(ArrayContainsImageSource(items)); |
| 244 |
| 245 // Adds an image to the ShareToData object and call -activityItemsForData: |
| 246 // again. Verifies that the result items array contains an image source. |
| 247 [data setImage:[UIImage imageNamed:@"activity_services_print"]]; |
| 248 items = [activityController activityItemsForData:data]; |
| 249 EXPECT_TRUE(ArrayContainsImageSource(items)); |
| 250 } |
| 251 |
| 252 // Verifies that when App Extension support is enabled, the URL string is |
| 253 // passed in a dictionary as part of the Activity Items to the App Extension. |
| 254 TEST_F(ActivityServiceControllerTest, ActivityItemsForDataWithPasswordAppEx) { |
| 255 base::scoped_nsobject<ActivityServiceController> activityController( |
| 256 [[ActivityServiceController alloc] init]); |
| 257 base::scoped_nsobject<ShareToData> data([[ShareToData alloc] |
| 258 initWithURL:GURL("https://chromium.org/login.html") |
| 259 title:@"kung fu fighting" |
| 260 isOriginalTitle:YES |
| 261 isPagePrintable:YES]); |
| 262 NSArray* items = [activityController activityItemsForData:data]; |
| 263 NSString* findLoginAction = |
| 264 (NSString*)activity_services::kUTTypeAppExtensionFindLoginAction; |
| 265 // Gets the list of NSExtensionItem objects returned by the array of |
| 266 // id<UIActivityItemSource> objects returned by -activityItemsForData:. |
| 267 NSArray* extensionItems = FindItemsForActivityType( |
| 268 items, activity_services::kAppExtensionOnePassword); |
| 269 ASSERT_EQ(1U, [extensionItems count]); |
| 270 NSExtensionItem* item = extensionItems[0]; |
| 271 EXPECT_EQ(1U, item.attachments.count); |
| 272 NSItemProvider* itemProvider = item.attachments[0]; |
| 273 // Extracts the dictionary back from the ItemProvider and then check that |
| 274 // it has the expected version and the page's URL. |
| 275 __block base::scoped_nsobject<NSDictionary> result; |
| 276 [itemProvider |
| 277 loadItemForTypeIdentifier:findLoginAction |
| 278 options:nil |
| 279 completionHandler:^(id item, NSError* error) { |
| 280 if (error || ![item isKindOfClass:[NSDictionary class]]) { |
| 281 result.reset([[NSDictionary dictionary] retain]); |
| 282 } else { |
| 283 result.reset([item retain]); |
| 284 } |
| 285 }]; |
| 286 base::test::ios::WaitUntilCondition(^{ |
| 287 return result.get() != nil; |
| 288 }); |
| 289 EXPECT_EQ(2U, [result count]); |
| 290 // Checks version. |
| 291 NSNumber* version = |
| 292 [result objectForKey:activity_services::kPasswordAppExVersionNumberKey]; |
| 293 EXPECT_NSEQ(activity_services::kPasswordAppExVersionNumber, version); |
| 294 // Checks URL. |
| 295 NSString* appExUrlString = |
| 296 [result objectForKey:activity_services::kPasswordAppExURLStringKey]; |
| 297 EXPECT_NSEQ(@"https://chromium.org/login.html", appExUrlString); |
| 298 |
| 299 // Checks that the list includes the page's title. |
| 300 NSArray* sources = |
| 301 FindItemsOfClass(items, [UIActivityFindLoginActionSource class]); |
| 302 EXPECT_EQ(1U, [sources count]); |
| 303 UIActivityFindLoginActionSource* actionSource = sources[0]; |
| 304 id mockActivityViewController = |
| 305 [OCMockObject niceMockForClass:[UIActivityViewController class]]; |
| 306 NSString* title = [actionSource |
| 307 activityViewController:mockActivityViewController |
| 308 subjectForActivityType:activity_services::kAppExtensionOnePassword]; |
| 309 EXPECT_NSEQ(@"kung fu fighting", title); |
| 310 } |
| 311 |
| 312 // Verifies that a Share extension can fetch a URL when Password App Extension |
| 313 // is enabled. |
| 314 TEST_F(ActivityServiceControllerTest, |
| 315 ActivityItemsForDataWithPasswordAppExReturnsURL) { |
| 316 base::scoped_nsobject<ActivityServiceController> activityController( |
| 317 [[ActivityServiceController alloc] init]); |
| 318 base::scoped_nsobject<ShareToData> data([[ShareToData alloc] |
| 319 initWithURL:GURL("https://chromium.org/login.html") |
| 320 title:@"kung fu fighting" |
| 321 isOriginalTitle:YES |
| 322 isPagePrintable:YES]); |
| 323 NSArray* items = [activityController activityItemsForData:data]; |
| 324 NSString* shareAction = @"com.apple.UIKit.activity.PostToFacebook"; |
| 325 NSArray* urlItems = |
| 326 FindItemsEqualsToUTType(items, shareAction, @"public.url"); |
| 327 ASSERT_EQ(1U, [urlItems count]); |
| 328 id<UIActivityItemSource> itemSource = urlItems[0]; |
| 329 id mockActivityViewController = |
| 330 [OCMockObject niceMockForClass:[UIActivityViewController class]]; |
| 331 id item = [itemSource activityViewController:mockActivityViewController |
| 332 itemForActivityType:shareAction]; |
| 333 ASSERT_TRUE([item isKindOfClass:[NSURL class]]); |
| 334 EXPECT_NSEQ(@"https://chromium.org/login.html", [item absoluteString]); |
| 335 } |
| 336 |
| 337 // Verifies that -processItemsReturnedFromActivity:status:item: contains |
| 338 // the username and password. |
| 339 TEST_F(ActivityServiceControllerTest, ProcessItemsReturnedSuccessfully) { |
| 340 base::scoped_nsobject<ActivityServiceController> activityController( |
| 341 [[ActivityServiceController alloc] init]); |
| 342 |
| 343 // Sets up a Mock ShareToDelegate object to check that the callback function |
| 344 // -passwordAppExDidFinish:username:password:successMessage: |
| 345 // is correct with the correct username and password. |
| 346 OCMockObject* shareToDelegateMock = |
| 347 [OCMockObject mockForProtocol:@protocol(ShareToDelegate)]; |
| 348 NSString* const kSecretUsername = @"john.doe"; |
| 349 NSString* const kSecretPassword = @"super!secret"; |
| 350 __block bool blockCalled = false; |
| 351 void (^validationBlock)(NSInvocation*) = ^(NSInvocation* invocation) { |
| 352 NSString* username; |
| 353 NSString* password; |
| 354 // Skips 0 and 1 index because they are |self| and |cmd|. |
| 355 [invocation getArgument:&username atIndex:3]; |
| 356 [invocation getArgument:&password atIndex:4]; |
| 357 EXPECT_NSEQ(kSecretUsername, username); |
| 358 EXPECT_NSEQ(kSecretPassword, password); |
| 359 blockCalled = true; |
| 360 }; |
| 361 [[[shareToDelegateMock stub] andDo:validationBlock] |
| 362 passwordAppExDidFinish:ShareTo::ShareResult::SHARE_SUCCESS |
| 363 username:OCMOCK_ANY |
| 364 password:OCMOCK_ANY |
| 365 successMessage:OCMOCK_ANY]; |
| 366 [activityController setShareToDelegateForTesting:(id)shareToDelegateMock]; |
| 367 |
| 368 // Sets up the returned item from a Password Management App Extension. |
| 369 NSString* activityType = @"com.software.find-login-action.extension"; |
| 370 ShareTo::ShareResult result = ShareTo::ShareResult::SHARE_SUCCESS; |
| 371 NSDictionary* dictionaryFromAppEx = |
| 372 @{ @"username" : kSecretUsername, |
| 373 @"password" : kSecretPassword }; |
| 374 base::scoped_nsobject<NSItemProvider> itemProvider([[NSItemProvider alloc] |
| 375 initWithItem:dictionaryFromAppEx |
| 376 typeIdentifier:(NSString*)kUTTypePropertyList]); |
| 377 base::scoped_nsobject<NSExtensionItem> extensionItem( |
| 378 [[NSExtensionItem alloc] init]); |
| 379 [extensionItem setAttachments:@[ itemProvider.get() ]]; |
| 380 |
| 381 BOOL resetUI = |
| 382 [activityController processItemsReturnedFromActivity:activityType |
| 383 status:result |
| 384 items:@[ extensionItem ]]; |
| 385 ASSERT_FALSE(resetUI); |
| 386 // Wait for -passwordAppExDidFinish:username:password:successMessage: |
| 387 // to be called. |
| 388 base::test::ios::WaitUntilCondition(^{ |
| 389 return blockCalled; |
| 390 }); |
| 391 EXPECT_OCMOCK_VERIFY(shareToDelegateMock); |
| 392 } |
| 393 |
| 394 // Verifies that -processItemsReturnedFromActivity:status:item: fails when |
| 395 // called with invalid NSExtensionItem. |
| 396 TEST_F(ActivityServiceControllerTest, ProcessItemsReturnedFailures) { |
| 397 ProcessItemsReturnedFromActivityFailure(@[], YES); |
| 398 |
| 399 // Extension Item is empty. |
| 400 base::scoped_nsobject<NSExtensionItem> extensionItem( |
| 401 [[NSExtensionItem alloc] init]); |
| 402 [extensionItem setAttachments:@[]]; |
| 403 ProcessItemsReturnedFromActivityFailure(@[ extensionItem ], YES); |
| 404 |
| 405 // Extension Item does not have a property list provider as the first |
| 406 // attachment. |
| 407 base::scoped_nsobject<NSItemProvider> itemProvider([[NSItemProvider alloc] |
| 408 initWithItem:@"some arbitrary garbage" |
| 409 typeIdentifier:(NSString*)kUTTypeText]); |
| 410 [extensionItem setAttachments:@[ itemProvider.get() ]]; |
| 411 ProcessItemsReturnedFromActivityFailure(@[ extensionItem ], YES); |
| 412 |
| 413 // Property list provider did not return a dictionary object. |
| 414 itemProvider.reset([[NSItemProvider alloc] |
| 415 initWithItem:@[ @"foo", @"bar" ] |
| 416 typeIdentifier:(NSString*)kUTTypePropertyList]); |
| 417 [extensionItem setAttachments:@[ itemProvider.get() ]]; |
| 418 ProcessItemsReturnedFromActivityFailure(@[ extensionItem ], NO); |
| 419 } |
| 420 |
| 421 // Verifies that the PrintActivity is sent to the UIActivityViewController if |
| 422 // and only if the activity is "printable". |
| 423 TEST_F(ActivityServiceControllerTest, ApplicationActivitiesForData) { |
| 424 base::scoped_nsobject<ActivityServiceController> activityController( |
| 425 [[ActivityServiceController alloc] init]); |
| 426 |
| 427 // Verify printable data. |
| 428 base::scoped_nsobject<ShareToData> data([[ShareToData alloc] |
| 429 initWithURL:GURL("https://chromium.org/printable") |
| 430 title:@"bar" |
| 431 isOriginalTitle:YES |
| 432 isPagePrintable:YES]); |
| 433 |
| 434 NSArray* items = |
| 435 [activityController applicationActivitiesForData:data controller:nil]; |
| 436 NSUInteger expected_items_count = |
| 437 reading_list::switches::IsReadingListEnabled() ? 2U : 1U; |
| 438 ASSERT_EQ(expected_items_count, [items count]); |
| 439 EXPECT_EQ([PrintActivity class], [[items objectAtIndex:0] class]); |
| 440 |
| 441 // Verify non-printable data. |
| 442 data.reset([[ShareToData alloc] |
| 443 initWithURL:GURL("https://chromium.org/unprintable") |
| 444 title:@"baz" |
| 445 isOriginalTitle:YES |
| 446 isPagePrintable:NO]); |
| 447 items = [activityController applicationActivitiesForData:data controller:nil]; |
| 448 EXPECT_EQ(expected_items_count - 1, [items count]); |
| 449 } |
| 450 |
| 451 TEST_F(ActivityServiceControllerTest, FindLoginActionTypeConformsToPublicURL) { |
| 452 // If this test fails, it is probably due to missing or incorrect |
| 453 // UTImportedTypeDeclarations in Info.plist. Note that there are |
| 454 // two Info.plist, |
| 455 // - ios/chrome/app/resources/Info.plist for Chrome app |
| 456 // - testing/gtest_ios/unittest-Info.plist for ios_chrome_unittests |
| 457 // Both of them must be changed. |
| 458 |
| 459 // 1Password defined the type @"org.appextension.find-login-action" so |
| 460 // any app can launch the 1Password app extension to fill in username and |
| 461 // password. This is being used by iOS native apps to launch 1Password app |
| 462 // extension and show *only* 1Password app extension as an option. |
| 463 // Therefore, this data type should *not* conform to public.url. |
| 464 // During the transition period, this test: |
| 465 // EXPECT_FALSE(UTTypeConformsTo(onePasswordFindLoginAction, kUTTypeURL)); |
| 466 // is not possible due to backward compatibility configurations. |
| 467 CFStringRef onePasswordFindLoginAction = |
| 468 reinterpret_cast<CFStringRef>(@"org.appextension.find-login-action"); |
| 469 |
| 470 // Chrome defines kUTTypeAppExtensionFindLoginAction which conforms to |
| 471 // public.url UTType in order to allow Share actions (e.g. Facebook, Twitter, |
| 472 // etc) to appear on UIActivityViewController opened by Chrome). |
| 473 CFStringRef chromeFindLoginAction = reinterpret_cast<CFStringRef>( |
| 474 activity_services::kUTTypeAppExtensionFindLoginAction); |
| 475 EXPECT_TRUE(UTTypeConformsTo(chromeFindLoginAction, kUTTypeURL)); |
| 476 EXPECT_TRUE( |
| 477 UTTypeConformsTo(chromeFindLoginAction, onePasswordFindLoginAction)); |
| 478 } |
| 479 |
| 480 } // namespace |
OLD | NEW |