OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 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/payments/payment_request_manager.h" |
| 6 |
| 7 #include "base/ios/ios_util.h" |
| 8 #import "base/ios/weak_nsobject.h" |
| 9 #import "base/mac/bind_objc_block.h" |
| 10 #include "base/mac/foundation_util.h" |
| 11 #include "base/mac/scoped_nsobject.h" |
| 12 #include "base/memory/ptr_util.h" |
| 13 #include "base/strings/sys_string_conversions.h" |
| 14 #import "base/values.h" |
| 15 #include "components/autofill/core/browser/personal_data_manager.h" |
| 16 #include "ios/chrome/browser/autofill/personal_data_manager_factory.h" |
| 17 #include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
| 18 #import "ios/chrome/browser/payments/js_payment_request_manager.h" |
| 19 #import "ios/chrome/browser/payments/payment_request_coordinator.h" |
| 20 #import "ios/chrome/browser/payments/payment_request_web_state_observer.h" |
| 21 #include "ios/web/public/favicon_status.h" |
| 22 #include "ios/web/public/navigation_item.h" |
| 23 #include "ios/web/public/navigation_manager.h" |
| 24 #include "ios/web/public/payments/payment_request.h" |
| 25 #include "ios/web/public/ssl_status.h" |
| 26 #import "ios/web/public/url_scheme_util.h" |
| 27 #import "ios/web/public/web_state/crw_web_view_proxy.h" |
| 28 #import "ios/web/public/web_state/js/crw_js_injection_receiver.h" |
| 29 #include "ios/web/public/web_state/url_verification_constants.h" |
| 30 #include "ios/web/public/web_state/web_state.h" |
| 31 |
| 32 namespace { |
| 33 // Command prefix for injected JavaScript. |
| 34 const std::string kCommandPrefix = "paymentRequest"; |
| 35 } // namespace |
| 36 |
| 37 @interface PaymentRequestManager ()<PaymentRequestCoordinatorDelegate, |
| 38 PaymentRequestWebStateDelegate> { |
| 39 // View controller used to present the PaymentRequest view controller. |
| 40 base::WeakNSObject<UIViewController> _baseViewController; |
| 41 |
| 42 // PersonalDataManager used to manage user credit cards and addresses. |
| 43 autofill::PersonalDataManager* _personalDataManager; |
| 44 |
| 45 // WebState for the tab this object is attached to. |
| 46 web::WebState* _webState; |
| 47 |
| 48 // Observer for |_webState|. |
| 49 std::unique_ptr<PaymentRequestWebStateObserver> _webStateObserver; |
| 50 |
| 51 // Object that manages JavaScript injection into the web view. |
| 52 base::WeakNSObject<JSPaymentRequestManager> _paymentRequestJsManager; |
| 53 |
| 54 // Boolean to track if the current WebState is enabled (JS callback is set |
| 55 // up). |
| 56 BOOL _webStateEnabled; |
| 57 |
| 58 // Boolean to track if the script has been injected in the current page. This |
| 59 // is a faster check than asking the JS controller. |
| 60 BOOL _isScriptInjected; |
| 61 |
| 62 // True when close has been called and the PaymentRequest coordinator has |
| 63 // been destroyed. |
| 64 BOOL _closed; |
| 65 |
| 66 // Coordinator used to create and present the PaymentRequest view controller. |
| 67 base::scoped_nsobject<PaymentRequestCoordinator> _paymentRequestCoordinator; |
| 68 } |
| 69 |
| 70 // Synchronous method executed by -asynchronouslyEnablePaymentRequest: |
| 71 - (void)doEnablePaymentRequest:(BOOL)enabled; |
| 72 |
| 73 // Initialize the PaymentRequest JavaScript. |
| 74 - (void)initializeWebViewForPaymentRequest; |
| 75 |
| 76 // Handler for injected JavaScript callbacks. |
| 77 - (BOOL)handleScriptCommand:(const base::DictionaryValue&)JSONCommand; |
| 78 |
| 79 // Handles invocations of PaymentRequest.show(). The value of the JavaScript |
| 80 // PaymentRequest object should be provided in |message|. Returns YES if the |
| 81 // invocation was successful. |
| 82 - (BOOL)handleRequestShow:(const base::DictionaryValue&)message; |
| 83 |
| 84 // Handles invocations of PaymentResponse.complete(). Returns YES if the |
| 85 // invocation was successful. |
| 86 - (BOOL)handleResponseComplete; |
| 87 |
| 88 @end |
| 89 |
| 90 @implementation PaymentRequestManager |
| 91 |
| 92 @synthesize enabled = _enabled; |
| 93 @synthesize webState = _webState; |
| 94 |
| 95 - (instancetype)initWithBaseViewController:(UIViewController*)viewController |
| 96 browserState: |
| 97 (ios::ChromeBrowserState*)browserState { |
| 98 if ((self = [super init])) { |
| 99 _baseViewController.reset(viewController); |
| 100 |
| 101 _personalDataManager = |
| 102 autofill::PersonalDataManagerFactory::GetForBrowserState( |
| 103 browserState->GetOriginalChromeBrowserState()); |
| 104 |
| 105 // Set up the web state observer. This lasts as long as this object does, |
| 106 // but it will observe and un-observe the web tabs as it changes over time. |
| 107 _webStateObserver.reset(new PaymentRequestWebStateObserver(self)); |
| 108 } |
| 109 return self; |
| 110 } |
| 111 |
| 112 - (instancetype)init { |
| 113 NOTREACHED(); |
| 114 return nil; |
| 115 } |
| 116 |
| 117 - (void)setWebState:(web::WebState*)webState { |
| 118 [self disconnectWebState]; |
| 119 if (webState) { |
| 120 _paymentRequestJsManager.reset( |
| 121 base::mac::ObjCCastStrict<JSPaymentRequestManager>( |
| 122 [webState->GetJSInjectionReceiver() |
| 123 instanceOfClass:[JSPaymentRequestManager class]])); |
| 124 _webState = webState; |
| 125 _webStateObserver->ObserveWebState(webState); |
| 126 [self enableCurrentWebState]; |
| 127 } else { |
| 128 _webState = nullptr; |
| 129 } |
| 130 } |
| 131 |
| 132 - (void)enablePaymentRequest:(BOOL)enabled { |
| 133 // Asynchronously enables PaymentRequest, so that some preferences |
| 134 // (UIAccessibilityIsVoiceOverRunning(), for example) have time to synchronize |
| 135 // with their own notifications. |
| 136 base::WeakNSObject<PaymentRequestManager> weakSelf(self); |
| 137 dispatch_async(dispatch_get_main_queue(), ^{ |
| 138 [weakSelf doEnablePaymentRequest:enabled]; |
| 139 }); |
| 140 } |
| 141 |
| 142 - (void)doEnablePaymentRequest:(BOOL)enabled { |
| 143 BOOL changing = _enabled != enabled; |
| 144 if (changing) { |
| 145 if (!enabled) { |
| 146 [self dismissUI]; |
| 147 } |
| 148 _enabled = enabled; |
| 149 [self enableCurrentWebState]; |
| 150 } |
| 151 } |
| 152 |
| 153 - (void)cancelRequest { |
| 154 [self dismissUI]; |
| 155 [_paymentRequestJsManager rejectRequestPromise:@"Request cancelled by user." |
| 156 completionHandler:nil]; |
| 157 } |
| 158 |
| 159 - (void)close { |
| 160 if (_closed) |
| 161 return; |
| 162 |
| 163 _closed = YES; |
| 164 [self disableCurrentWebState]; |
| 165 [self setWebState:nil]; |
| 166 [self dismissUI]; |
| 167 } |
| 168 |
| 169 - (void)enableCurrentWebState { |
| 170 if (![self webState]) { |
| 171 return; |
| 172 } |
| 173 |
| 174 if (_enabled && [self webStateContentIsSecureHTML]) { |
| 175 if (!_webStateEnabled) { |
| 176 base::WeakNSObject<PaymentRequestManager> weakSelf(self); |
| 177 auto callback = |
| 178 base::BindBlock(^bool(const base::DictionaryValue& JSON, |
| 179 const GURL& originURL, bool userIsInteracting) { |
| 180 base::scoped_nsobject<PaymentRequestManager> strongSelf( |
| 181 [weakSelf retain]); |
| 182 // |originURL| and |userIsInteracting| aren't used. |
| 183 return [strongSelf handleScriptCommand:JSON]; |
| 184 }); |
| 185 [self webState]->AddScriptCommandCallback(callback, kCommandPrefix); |
| 186 |
| 187 _webStateEnabled = YES; |
| 188 } |
| 189 |
| 190 [self initializeWebViewForPaymentRequest]; |
| 191 } else { |
| 192 [self disableCurrentWebState]; |
| 193 } |
| 194 } |
| 195 |
| 196 - (void)disableCurrentWebState { |
| 197 if (_webStateEnabled) { |
| 198 _webState->RemoveScriptCommandCallback(kCommandPrefix); |
| 199 _webStateEnabled = NO; |
| 200 } |
| 201 } |
| 202 |
| 203 - (void)disconnectWebState { |
| 204 if (_webState) { |
| 205 _paymentRequestJsManager.reset(); |
| 206 _webStateObserver->ObserveWebState(nullptr); |
| 207 [self disableCurrentWebState]; |
| 208 } |
| 209 } |
| 210 |
| 211 - (void)initializeWebViewForPaymentRequest { |
| 212 DCHECK(_webStateEnabled); |
| 213 |
| 214 if (![self webStateContentIsSecureHTML]) { |
| 215 return; |
| 216 } |
| 217 |
| 218 [_paymentRequestJsManager inject]; |
| 219 _isScriptInjected = YES; |
| 220 } |
| 221 |
| 222 - (BOOL)handleScriptCommand:(const base::DictionaryValue&)JSONCommand { |
| 223 if (![self webStateContentIsSecureHTML]) { |
| 224 return NO; |
| 225 } |
| 226 |
| 227 std::string command; |
| 228 if (!JSONCommand.GetString("command", &command)) { |
| 229 DLOG(ERROR) << "RECEIVED BAD JSON - NO 'command' field"; |
| 230 return NO; |
| 231 } |
| 232 |
| 233 if (command == "paymentRequest.requestShow") { |
| 234 return [self handleRequestShow:JSONCommand]; |
| 235 } |
| 236 if (command == "paymentRequest.responseComplete") { |
| 237 return [self handleResponseComplete]; |
| 238 } |
| 239 return NO; |
| 240 } |
| 241 |
| 242 - (BOOL)handleRequestShow:(const base::DictionaryValue&)message { |
| 243 // TODO(crbug.com/602666): check that there's not already a pending request. |
| 244 // TODO(crbug.com/602666): compare our supported payment types (i.e. autofill |
| 245 // credit card types) against the merchant supported types and return NO |
| 246 // if the intersection is empty. |
| 247 |
| 248 const base::DictionaryValue* paymentRequestData; |
| 249 web::PaymentRequest paymentRequest; |
| 250 if (!message.GetDictionary("payment_request", &paymentRequestData)) { |
| 251 DLOG(ERROR) << "JS message parameter 'payment_request' is missing"; |
| 252 return NO; |
| 253 } |
| 254 if (!paymentRequest.FromDictionaryValue(*paymentRequestData)) { |
| 255 DLOG(ERROR) << "JS message parameter 'payment_request' is invalid"; |
| 256 return NO; |
| 257 } |
| 258 |
| 259 UIImage* pageFavicon = nil; |
| 260 web::NavigationItem* navigationItem = |
| 261 [self webState]->GetNavigationManager()->GetVisibleItem(); |
| 262 if (navigationItem && !navigationItem->GetFavicon().image.IsEmpty()) |
| 263 pageFavicon = navigationItem->GetFavicon().image.ToUIImage(); |
| 264 NSString* pageTitle = base::SysUTF16ToNSString([self webState]->GetTitle()); |
| 265 NSString* pageHost = |
| 266 base::SysUTF8ToNSString([self webState]->GetLastCommittedURL().host()); |
| 267 _paymentRequestCoordinator.reset([[PaymentRequestCoordinator alloc] |
| 268 initWithBaseViewController:_baseViewController |
| 269 personalDataManager:_personalDataManager]); |
| 270 [_paymentRequestCoordinator setPaymentRequest:paymentRequest]; |
| 271 [_paymentRequestCoordinator setPageFavicon:pageFavicon]; |
| 272 [_paymentRequestCoordinator setPageTitle:pageTitle]; |
| 273 [_paymentRequestCoordinator setPageHost:pageHost]; |
| 274 [_paymentRequestCoordinator setDelegate:self]; |
| 275 |
| 276 [_paymentRequestCoordinator start]; |
| 277 |
| 278 return YES; |
| 279 } |
| 280 |
| 281 - (BOOL)handleResponseComplete { |
| 282 // TODO(crbug.com/602666): Check that there *is* a pending response here. |
| 283 // TODO(crbug.com/602666): Indicate success or failure in the UI. |
| 284 |
| 285 [self dismissUI]; |
| 286 |
| 287 // TODO(crbug.com/602666): Reject the promise on failure. |
| 288 [_paymentRequestJsManager resolveResponsePromise:nil]; |
| 289 |
| 290 return YES; |
| 291 } |
| 292 |
| 293 - (void)dismissUI { |
| 294 [_paymentRequestCoordinator stop]; |
| 295 _paymentRequestCoordinator.reset(); |
| 296 } |
| 297 |
| 298 - (BOOL)webStateContentIsSecureHTML { |
| 299 if (![self webState]) { |
| 300 return NO; |
| 301 } |
| 302 |
| 303 if (!web::UrlHasWebScheme([self webState]->GetLastCommittedURL()) || |
| 304 ![self webState]->ContentIsHTML()) { |
| 305 return NO; |
| 306 } |
| 307 |
| 308 const web::NavigationItem* navigationItem = |
| 309 [self webState]->GetNavigationManager()->GetLastCommittedItem(); |
| 310 return navigationItem && |
| 311 navigationItem->GetSSL().security_style == |
| 312 web::SECURITY_STYLE_AUTHENTICATED; |
| 313 } |
| 314 |
| 315 #pragma mark - PaymentRequestCoordinatorDelegate methods |
| 316 |
| 317 - (void)paymentRequestCoordinatorDidCancel { |
| 318 [self cancelRequest]; |
| 319 } |
| 320 |
| 321 - (void)paymentRequestCoordinatorDidConfirm: |
| 322 (web::PaymentResponse)paymentResponse { |
| 323 [_paymentRequestJsManager resolveRequestPromise:paymentResponse |
| 324 completionHandler:nil]; |
| 325 } |
| 326 |
| 327 #pragma mark - PaymentRequestWebStateDelegate methods |
| 328 |
| 329 - (void)pageLoadedWithStatus:(web::PageLoadCompletionStatus)loadStatus { |
| 330 if (loadStatus != web::PageLoadCompletionStatus::SUCCESS) |
| 331 return; |
| 332 |
| 333 [self dismissUI]; |
| 334 _isScriptInjected = NO; |
| 335 [self enableCurrentWebState]; |
| 336 } |
| 337 |
| 338 @end |
OLD | NEW |