/* * Copyright (C) 2020 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "WKContactPicker.h" #if HAVE(CONTACTSUI) #import "ContactsUISPI.h" #import #import #import #import #import #import #import #import #if PLATFORM(IOS_FAMILY) #import "UIKitSPI.h" #endif SOFT_LINK_FRAMEWORK(Contacts) SOFT_LINK_CLASS(Contacts, CNContactFormatter) SOFT_LINK_CLASS(Contacts, CNLabeledValue) SOFT_LINK_CLASS(Contacts, CNMutableContact) SOFT_LINK_CLASS(Contacts, CNPhoneNumber) #if HAVE(CNCONTACTPICKERVIEWCONTROLLER) SOFT_LINK_FRAMEWORK(ContactsUI) SOFT_LINK_CLASS(ContactsUI, CNContactPickerViewController) #endif #pragma mark - Delegate Wrappers @interface WKCNContactPickerDelegate : NSObject { @protected WeakObjCPtr> _contactPickerDelegate; } - (instancetype)initWithContactPickerDelegate:(id)contactPickerDelegate; @end @implementation WKCNContactPickerDelegate - (instancetype)initWithContactPickerDelegate:(id)contactPickerDelegate { if (!(self = [super init])) return nil; _contactPickerDelegate = contactPickerDelegate; return self; } #if HAVE(CNCONTACTPICKERVIEWCONTROLLER) - (void)contactPickerDidCancel:(CNContactPickerViewController *)picker { [_contactPickerDelegate contactPickerDidCancel:picker]; } #endif @end @interface WKCNContactPickerSingleSelectDelegate : WKCNContactPickerDelegate @end @implementation WKCNContactPickerSingleSelectDelegate #if HAVE(CNCONTACTPICKERVIEWCONTROLLER) - (void)contactPicker:(CNContactPickerViewController *)picker didSelectContact:(CNContact *)contact { [_contactPickerDelegate contactPicker:picker didSelectContact:contact]; } #endif @end @interface WKCNContactPickerMultiSelectDelegate : WKCNContactPickerDelegate @end @implementation WKCNContactPickerMultiSelectDelegate #if HAVE(CNCONTACTPICKERVIEWCONTROLLER) - (void)contactPicker:(CNContactPickerViewController *)picker didSelectContacts:(NSArray *)contacts { [_contactPickerDelegate contactPicker:picker didSelectContacts:contacts]; } #endif @end #pragma mark - WKContactPicker @interface WKContactPicker () @end @implementation WKContactPicker { WeakObjCPtr _webView; WeakObjCPtr> _delegate; Vector _properties; WTF::CompletionHandler>&&)> _completionHandler; RetainPtr _contactPickerDelegate; #if HAVE(CNCONTACTPICKERVIEWCONTROLLER) RetainPtr _contactPickerViewController; #endif } - (id)delegate { return _delegate.get().get(); } - (void)setDelegate:(id)delegate { _delegate = delegate; } - (instancetype)initWithView:(WKWebView *)view { if (!(self = [super init])) return nil; _webView = view; return self; } - (void)presentWithRequestData:(const WebCore::ContactsRequestData&)requestData completionHandler:(WTF::CompletionHandler>&&)>&&)completionHandler { _properties = requestData.properties; _completionHandler = WTFMove(completionHandler); if (requestData.multiple) _contactPickerDelegate = adoptNS([[WKCNContactPickerMultiSelectDelegate alloc] initWithContactPickerDelegate:self]); else _contactPickerDelegate = adoptNS([[WKCNContactPickerSingleSelectDelegate alloc] initWithContactPickerDelegate:self]); #if HAVE(CNCONTACTPICKERVIEWCONTROLLER) _contactPickerViewController = adoptNS([allocCNContactPickerViewControllerInstance() init]); [_contactPickerViewController setDelegate:_contactPickerDelegate.get()]; [_contactPickerViewController setPrompt:requestData.url]; UIViewController *presentationViewController = [UIViewController _viewControllerForFullScreenPresentationFromView:_webView.get().get()]; [presentationViewController presentViewController:_contactPickerViewController.get() animated:YES completion:[weakSelf = WeakObjCPtr(self)] { auto strongSelf = weakSelf.get(); if (!strongSelf) return; if ([[strongSelf delegate] respondsToSelector:@selector(contactPickerDidPresent:)]) [[strongSelf delegate] contactPickerDidPresent:strongSelf.get()]; }]; #endif } #pragma mark - Completion #if HAVE(CNCONTACTPICKERVIEWCONTROLLER) - (void)contactPickerDidCancel:(CNContactPickerViewController *)picker { Vector info; [self _contactPickerDidDismissWithContactInfo:WTFMove(info)]; } - (void)contactPicker:(CNContactPickerViewController *)picker didSelectContact:(CNContact *)contact { Vector info = { [self _contactInfoFromCNContact:contact] }; [self _contactPickerDidDismissWithContactInfo:WTFMove(info)]; } - (void)contactPicker:(CNContactPickerViewController *)picker didSelectContacts:(NSArray *)contacts { Vector info; info.reserveInitialCapacity(contacts.count); for (CNContact *contact in contacts) info.uncheckedAppend([self _contactInfoFromCNContact:contact]); [self _contactPickerDidDismissWithContactInfo:WTFMove(info)]; } #endif - (void)_contactPickerDidDismissWithContactInfo:(Vector&&)info { _completionHandler(WTFMove(info)); if ([_delegate respondsToSelector:@selector(contactPickerDidDismiss:)]) [_delegate contactPickerDidDismiss:self]; } - (WebCore::ContactInfo)_contactInfoFromCNContact:(CNContact *)contact { WebCore::ContactInfo contactInfo; if (_properties.contains(WebCore::ContactProperty::Name)) { NSString *contactName = [getCNContactFormatterClass() stringFromContact:contact style:CNContactFormatterStyleFullName]; contactInfo.name = { contactName }; } if (_properties.contains(WebCore::ContactProperty::Email)) { for (CNLabeledValue *emailAddress in contact.emailAddresses) contactInfo.email.append(emailAddress.value); } if (_properties.contains(WebCore::ContactProperty::Tel)) { for (CNLabeledValue *phoneNumber in contact.phoneNumbers) contactInfo.tel.append(phoneNumber.value.stringValue); } return contactInfo; } #pragma mark - Testing - (void)dismissWithContacts:(NSArray *)contacts { #if HAVE(CNCONTACTPICKERVIEWCONTROLLER) [_contactPickerViewController dismissViewControllerAnimated:NO completion:[self, weakSelf = WeakObjCPtr(self), jsContacts = RetainPtr(contacts)] { auto strongSelf = weakSelf.get(); if (!strongSelf) return; [strongSelf contactPicker:_contactPickerViewController.get() didSelectContacts:[strongSelf _contactsFromJSContacts:jsContacts.get()]]; }]; #endif } - (NSArray *)_contactsFromJSContacts:(NSArray *)jsContacts { if (!jsContacts) return [NSArray array]; NSMutableArray *contacts = [NSMutableArray arrayWithCapacity:jsContacts.count]; NSPredicate *stringValuePredicate = [NSPredicate predicateWithFormat:@"self isKindOfClass: %@", [NSString class]]; for (id jsContact in jsContacts) { if (![jsContact isKindOfClass:[NSDictionary class]]) continue; auto contact = adoptNS([allocCNMutableContactInstance() init]); id names = [(NSDictionary *)jsContact objectForKey:@"name"]; if ([names isKindOfClass:[NSArray class]]) { for (NSString *name in [names filteredArrayUsingPredicate:stringValuePredicate]) { [contact setGivenName:name]; break; } } id emails = [(NSDictionary *)jsContact objectForKey:@"email"]; if ([emails isKindOfClass:[NSArray class]]) { NSMutableArray*> *emailAddresses = [NSMutableArray array]; for (NSString *email in [emails filteredArrayUsingPredicate:stringValuePredicate]) { CNLabeledValue *labeledValue = [getCNLabeledValueClass() labeledValueWithLabel:nil value:email]; [emailAddresses addObject:labeledValue]; } [contact setEmailAddresses:emailAddresses]; } id phoneNumbers = [(NSDictionary *)jsContact objectForKey:@"tel"]; if ([phoneNumbers isKindOfClass:[NSArray class]]) { NSMutableArray*> *numbers = [NSMutableArray array]; for (NSString *phoneNumber in [phoneNumbers filteredArrayUsingPredicate:stringValuePredicate]) { CNPhoneNumber *cnPhoneNumber = [getCNPhoneNumberClass() phoneNumberWithStringValue:phoneNumber]; CNLabeledValue *labeledValue = [getCNLabeledValueClass() labeledValueWithLabel:nil value:cnPhoneNumber]; [numbers addObject:labeledValue]; } [contact setPhoneNumbers:numbers]; } [contacts addObject:contact.get()]; } return contacts; } @end #endif // HAVE(CONTACTSUI)