Index: ios/chrome/browser/payments/payment_request_manager.mm |
diff --git a/ios/chrome/browser/payments/payment_request_manager.mm b/ios/chrome/browser/payments/payment_request_manager.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..657183145f2098c5333df7c8a8daa4518e4d0ad9 |
--- /dev/null |
+++ b/ios/chrome/browser/payments/payment_request_manager.mm |
@@ -0,0 +1,338 @@ |
+// Copyright 2016 The Chromium 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 "ios/chrome/browser/payments/payment_request_manager.h" |
+ |
+#include "base/ios/ios_util.h" |
+#import "base/ios/weak_nsobject.h" |
+#import "base/mac/bind_objc_block.h" |
+#include "base/mac/foundation_util.h" |
+#include "base/mac/scoped_nsobject.h" |
+#include "base/memory/ptr_util.h" |
+#include "base/strings/sys_string_conversions.h" |
+#import "base/values.h" |
+#include "components/autofill/core/browser/personal_data_manager.h" |
+#include "ios/chrome/browser/autofill/personal_data_manager_factory.h" |
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
+#import "ios/chrome/browser/payments/js_payment_request_manager.h" |
+#import "ios/chrome/browser/payments/payment_request_coordinator.h" |
+#import "ios/chrome/browser/payments/payment_request_web_state_observer.h" |
+#include "ios/web/public/favicon_status.h" |
+#include "ios/web/public/navigation_item.h" |
+#include "ios/web/public/navigation_manager.h" |
+#include "ios/web/public/payments/payment_request.h" |
+#include "ios/web/public/ssl_status.h" |
+#import "ios/web/public/url_scheme_util.h" |
+#import "ios/web/public/web_state/crw_web_view_proxy.h" |
+#import "ios/web/public/web_state/js/crw_js_injection_receiver.h" |
+#include "ios/web/public/web_state/url_verification_constants.h" |
+#include "ios/web/public/web_state/web_state.h" |
+ |
+namespace { |
+// Command prefix for injected JavaScript. |
+const std::string kCommandPrefix = "paymentRequest"; |
+} // namespace |
+ |
+@interface PaymentRequestManager ()<PaymentRequestCoordinatorDelegate, |
+ PaymentRequestWebStateDelegate> { |
+ // View controller used to present the PaymentRequest view controller. |
+ base::WeakNSObject<UIViewController> _baseViewController; |
+ |
+ // PersonalDataManager used to manage user credit cards and addresses. |
+ autofill::PersonalDataManager* _personalDataManager; |
+ |
+ // WebState for the tab this object is attached to. |
+ web::WebState* _webState; |
+ |
+ // Observer for |_webState|. |
+ std::unique_ptr<PaymentRequestWebStateObserver> _webStateObserver; |
+ |
+ // Object that manages JavaScript injection into the web view. |
+ base::WeakNSObject<JSPaymentRequestManager> _paymentRequestJsManager; |
+ |
+ // Boolean to track if the current WebState is enabled (JS callback is set |
+ // up). |
+ BOOL _webStateEnabled; |
+ |
+ // Boolean to track if the script has been injected in the current page. This |
+ // is a faster check than asking the JS controller. |
+ BOOL _isScriptInjected; |
+ |
+ // True when close has been called and the PaymentRequest coordinator has |
+ // been destroyed. |
+ BOOL _closed; |
+ |
+ // Coordinator used to create and present the PaymentRequest view controller. |
+ base::scoped_nsobject<PaymentRequestCoordinator> _paymentRequestCoordinator; |
+} |
+ |
+// Synchronous method executed by -asynchronouslyEnablePaymentRequest: |
+- (void)doEnablePaymentRequest:(BOOL)enabled; |
+ |
+// Initialize the PaymentRequest JavaScript. |
+- (void)initializeWebViewForPaymentRequest; |
+ |
+// Handler for injected JavaScript callbacks. |
+- (BOOL)handleScriptCommand:(const base::DictionaryValue&)JSONCommand; |
+ |
+// Handles invocations of PaymentRequest.show(). The value of the JavaScript |
+// PaymentRequest object should be provided in |message|. Returns YES if the |
+// invocation was successful. |
+- (BOOL)handleRequestShow:(const base::DictionaryValue&)message; |
+ |
+// Handles invocations of PaymentResponse.complete(). Returns YES if the |
+// invocation was successful. |
+- (BOOL)handleResponseComplete; |
+ |
+@end |
+ |
+@implementation PaymentRequestManager |
+ |
+@synthesize enabled = _enabled; |
+@synthesize webState = _webState; |
+ |
+- (instancetype)initWithBaseViewController:(UIViewController*)viewController |
+ browserState: |
+ (ios::ChromeBrowserState*)browserState { |
+ if ((self = [super init])) { |
+ _baseViewController.reset(viewController); |
+ |
+ _personalDataManager = |
+ autofill::PersonalDataManagerFactory::GetForBrowserState( |
+ browserState->GetOriginalChromeBrowserState()); |
+ |
+ // Set up the web state observer. This lasts as long as this object does, |
+ // but it will observe and un-observe the web tabs as it changes over time. |
+ _webStateObserver.reset(new PaymentRequestWebStateObserver(self)); |
+ } |
+ return self; |
+} |
+ |
+- (instancetype)init { |
+ NOTREACHED(); |
+ return nil; |
+} |
+ |
+- (void)setWebState:(web::WebState*)webState { |
+ [self disconnectWebState]; |
+ if (webState) { |
+ _paymentRequestJsManager.reset( |
+ base::mac::ObjCCastStrict<JSPaymentRequestManager>( |
+ [webState->GetJSInjectionReceiver() |
+ instanceOfClass:[JSPaymentRequestManager class]])); |
+ _webState = webState; |
+ _webStateObserver->ObserveWebState(webState); |
+ [self enableCurrentWebState]; |
+ } else { |
+ _webState = nullptr; |
+ } |
+} |
+ |
+- (void)enablePaymentRequest:(BOOL)enabled { |
+ // Asynchronously enables PaymentRequest, so that some preferences |
+ // (UIAccessibilityIsVoiceOverRunning(), for example) have time to synchronize |
+ // with their own notifications. |
+ base::WeakNSObject<PaymentRequestManager> weakSelf(self); |
+ dispatch_async(dispatch_get_main_queue(), ^{ |
+ [weakSelf doEnablePaymentRequest:enabled]; |
+ }); |
+} |
+ |
+- (void)doEnablePaymentRequest:(BOOL)enabled { |
+ BOOL changing = _enabled != enabled; |
+ if (changing) { |
+ if (!enabled) { |
+ [self dismissUI]; |
+ } |
+ _enabled = enabled; |
+ [self enableCurrentWebState]; |
+ } |
+} |
+ |
+- (void)cancelRequest { |
+ [self dismissUI]; |
+ [_paymentRequestJsManager rejectRequestPromise:@"Request cancelled by user." |
+ completionHandler:nil]; |
+} |
+ |
+- (void)close { |
+ if (_closed) |
+ return; |
+ |
+ _closed = YES; |
+ [self disableCurrentWebState]; |
+ [self setWebState:nil]; |
+ [self dismissUI]; |
+} |
+ |
+- (void)enableCurrentWebState { |
+ if (![self webState]) { |
+ return; |
+ } |
+ |
+ if (_enabled && [self webStateContentIsSecureHTML]) { |
+ if (!_webStateEnabled) { |
+ base::WeakNSObject<PaymentRequestManager> weakSelf(self); |
+ auto callback = |
+ base::BindBlock(^bool(const base::DictionaryValue& JSON, |
+ const GURL& originURL, bool userIsInteracting) { |
+ base::scoped_nsobject<PaymentRequestManager> strongSelf( |
+ [weakSelf retain]); |
+ // |originURL| and |userIsInteracting| aren't used. |
+ return [strongSelf handleScriptCommand:JSON]; |
+ }); |
+ [self webState]->AddScriptCommandCallback(callback, kCommandPrefix); |
+ |
+ _webStateEnabled = YES; |
+ } |
+ |
+ [self initializeWebViewForPaymentRequest]; |
+ } else { |
+ [self disableCurrentWebState]; |
+ } |
+} |
+ |
+- (void)disableCurrentWebState { |
+ if (_webStateEnabled) { |
+ _webState->RemoveScriptCommandCallback(kCommandPrefix); |
+ _webStateEnabled = NO; |
+ } |
+} |
+ |
+- (void)disconnectWebState { |
+ if (_webState) { |
+ _paymentRequestJsManager.reset(); |
+ _webStateObserver->ObserveWebState(nullptr); |
+ [self disableCurrentWebState]; |
+ } |
+} |
+ |
+- (void)initializeWebViewForPaymentRequest { |
+ DCHECK(_webStateEnabled); |
+ |
+ if (![self webStateContentIsSecureHTML]) { |
+ return; |
+ } |
+ |
+ [_paymentRequestJsManager inject]; |
+ _isScriptInjected = YES; |
+} |
+ |
+- (BOOL)handleScriptCommand:(const base::DictionaryValue&)JSONCommand { |
+ if (![self webStateContentIsSecureHTML]) { |
+ return NO; |
+ } |
+ |
+ std::string command; |
+ if (!JSONCommand.GetString("command", &command)) { |
+ DLOG(ERROR) << "RECEIVED BAD JSON - NO 'command' field"; |
+ return NO; |
+ } |
+ |
+ if (command == "paymentRequest.requestShow") { |
+ return [self handleRequestShow:JSONCommand]; |
+ } |
+ if (command == "paymentRequest.responseComplete") { |
+ return [self handleResponseComplete]; |
+ } |
+ return NO; |
+} |
+ |
+- (BOOL)handleRequestShow:(const base::DictionaryValue&)message { |
+ // TODO(crbug.com/602666): check that there's not already a pending request. |
+ // TODO(crbug.com/602666): compare our supported payment types (i.e. autofill |
+ // credit card types) against the merchant supported types and return NO |
+ // if the intersection is empty. |
+ |
+ const base::DictionaryValue* paymentRequestData; |
+ web::PaymentRequest paymentRequest; |
+ if (!message.GetDictionary("payment_request", &paymentRequestData)) { |
+ DLOG(ERROR) << "JS message parameter 'payment_request' is missing"; |
+ return NO; |
+ } |
+ if (!paymentRequest.FromDictionaryValue(*paymentRequestData)) { |
+ DLOG(ERROR) << "JS message parameter 'payment_request' is invalid"; |
+ return NO; |
+ } |
+ |
+ UIImage* pageFavicon = nil; |
+ web::NavigationItem* navigationItem = |
+ [self webState]->GetNavigationManager()->GetVisibleItem(); |
+ if (navigationItem && !navigationItem->GetFavicon().image.IsEmpty()) |
+ pageFavicon = navigationItem->GetFavicon().image.ToUIImage(); |
+ NSString* pageTitle = base::SysUTF16ToNSString([self webState]->GetTitle()); |
+ NSString* pageHost = |
+ base::SysUTF8ToNSString([self webState]->GetLastCommittedURL().host()); |
+ _paymentRequestCoordinator.reset([[PaymentRequestCoordinator alloc] |
+ initWithBaseViewController:_baseViewController |
+ personalDataManager:_personalDataManager]); |
+ [_paymentRequestCoordinator setPaymentRequest:paymentRequest]; |
+ [_paymentRequestCoordinator setPageFavicon:pageFavicon]; |
+ [_paymentRequestCoordinator setPageTitle:pageTitle]; |
+ [_paymentRequestCoordinator setPageHost:pageHost]; |
+ [_paymentRequestCoordinator setDelegate:self]; |
+ |
+ [_paymentRequestCoordinator start]; |
+ |
+ return YES; |
+} |
+ |
+- (BOOL)handleResponseComplete { |
+ // TODO(crbug.com/602666): Check that there *is* a pending response here. |
+ // TODO(crbug.com/602666): Indicate success or failure in the UI. |
+ |
+ [self dismissUI]; |
+ |
+ // TODO(crbug.com/602666): Reject the promise on failure. |
+ [_paymentRequestJsManager resolveResponsePromise:nil]; |
+ |
+ return YES; |
+} |
+ |
+- (void)dismissUI { |
+ [_paymentRequestCoordinator stop]; |
+ _paymentRequestCoordinator.reset(); |
+} |
+ |
+- (BOOL)webStateContentIsSecureHTML { |
+ if (![self webState]) { |
+ return NO; |
+ } |
+ |
+ if (!web::UrlHasWebScheme([self webState]->GetLastCommittedURL()) || |
+ ![self webState]->ContentIsHTML()) { |
+ return NO; |
+ } |
+ |
+ const web::NavigationItem* navigationItem = |
+ [self webState]->GetNavigationManager()->GetLastCommittedItem(); |
+ return navigationItem && |
+ navigationItem->GetSSL().security_style == |
+ web::SECURITY_STYLE_AUTHENTICATED; |
+} |
+ |
+#pragma mark - PaymentRequestCoordinatorDelegate methods |
+ |
+- (void)paymentRequestCoordinatorDidCancel { |
+ [self cancelRequest]; |
+} |
+ |
+- (void)paymentRequestCoordinatorDidConfirm: |
+ (web::PaymentResponse)paymentResponse { |
+ [_paymentRequestJsManager resolveRequestPromise:paymentResponse |
+ completionHandler:nil]; |
+} |
+ |
+#pragma mark - PaymentRequestWebStateDelegate methods |
+ |
+- (void)pageLoadedWithStatus:(web::PageLoadCompletionStatus)loadStatus { |
+ if (loadStatus != web::PageLoadCompletionStatus::SUCCESS) |
+ return; |
+ |
+ [self dismissUI]; |
+ _isScriptInjected = NO; |
+ [self enableCurrentWebState]; |
+} |
+ |
+@end |