Index: ios/chrome/browser/ui/print/print_controller.mm |
diff --git a/ios/chrome/browser/ui/print/print_controller.mm b/ios/chrome/browser/ui/print/print_controller.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..c5576d30a898cde2c86e3754d30c63828d643b7b |
--- /dev/null |
+++ b/ios/chrome/browser/ui/print/print_controller.mm |
@@ -0,0 +1,365 @@ |
+// Copyright 2012 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/ui/print/print_controller.h" |
+ |
+#import <MobileCoreServices/UTType.h> |
+#import <Webkit/Webkit.h> |
+ |
+#include <memory> |
+ |
+#include "base/callback_helpers.h" |
+#import "base/ios/ios_util.h" |
+#import "base/ios/weak_nsobject.h" |
+#include "base/location.h" |
+#include "base/logging.h" |
+#include "base/mac/bind_objc_block.h" |
+#include "base/mac/foundation_util.h" |
+#import "base/mac/scoped_nsobject.h" |
+#include "base/memory/ref_counted.h" |
+#include "base/metrics/user_metrics.h" |
+#include "base/metrics/user_metrics_action.h" |
+#include "components/strings/grit/components_strings.h" |
+#import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h" |
+#import "ios/chrome/browser/ui/alert_coordinator/loading_alert_coordinator.h" |
+#include "ios/chrome/grit/ios_strings.h" |
+#include "ios/web/public/web_thread.h" |
+#import "net/base/mac/url_conversions.h" |
+#include "net/http/http_response_headers.h" |
+#include "net/url_request/url_fetcher.h" |
+#include "net/url_request/url_fetcher_delegate.h" |
+#include "net/url_request/url_request_context_getter.h" |
+#include "ui/base/l10n/l10n_util_mac.h" |
+ |
+using net::URLFetcher; |
+using net::URLFetcherDelegate; |
+using net::URLRequestContextGetter; |
+ |
+@interface PrintController () |
+ |
+// Presents a UIPrintInteractionController with a default completion handler. |
+// |isPDF| indicates if |printInteractionController| is being presented to print |
+// a PDF. |
++ (void)displayPrintInteractionController: |
+ (UIPrintInteractionController*)printInteractionController |
+ forPDF:(BOOL)isPDF; |
+ |
+// Shows a dialog on |viewController| indicating that the print preview is being |
+// prepared. The dialog will appear only if the download has not completed or |
+// been cancelled |kPDFDownloadDialogDelay| seconds after this method is called. |
+- (void)showPDFDownloadingDialog:(UIViewController*)viewController; |
+ |
+// Dismisses the dialog which indicates that the print preview is being |
+// prepared. |
+- (void)dismissPDFDownloadingDialog; |
+ |
+// Shows an dialog on |viewController| indicating that there was an error when |
+// preparing the print preview, and providing the ability to retry. |URL| is the |
+// URL of the PDF which had an error. |
+- (void)showPDFDownloadErrorWithURL:(const GURL)URL |
+ viewController:(UIViewController*)viewController; |
+ |
+// Handles downloading the file at |URL| and presents dialogs on |
+// |viewController| if necessary. |
+- (void)downloadPDFFileWithURL:(const GURL&)URL |
+ viewController:(UIViewController*)viewController; |
+ |
+// Accesses the result of the PDF download, and if successful, presents the |
+// AirPrint menu with the downloaded PDF. It also ensures that the |
+// PDFDownloadingDialog is dismissed, and presents an error dialog on |
+// |viewController| if the download fails. This method should be called only by |
+// the URLFetcherDelegate. |
+- (void)finishedDownloadingPDF:(UIViewController*)viewController; |
+ |
+@end |
+ |
+namespace { |
+ |
+// The MIME type of a PDF file contained in a Web view. |
+const char kPDFMimeType[] = "application/pdf"; |
+ |
+// Delay after downloading begins that the |_PDFDownloadingDialog| appears. |
+const int64_t kPDFDownloadDialogDelay = 1; |
+ |
+// A delegate for the URLFetcher to tell owning PrintController that the |
+// download is complete. |
+class PrintPDFFetcherDelegate : public URLFetcherDelegate { |
+ public: |
+ explicit PrintPDFFetcherDelegate(PrintController* owner) : owner_(owner) {} |
+ void OnURLFetchComplete(const URLFetcher* source) override { |
+ DCHECK(view_controller_); |
+ [owner_ finishedDownloadingPDF:view_controller_]; |
+ } |
+ |
+ // The ViewController used to display an error if the download failed. |
+ void SetViewController(UIViewController* view_controller) { |
+ view_controller_ = view_controller; |
+ } |
+ |
+ private: |
+ __unsafe_unretained PrintController* owner_; |
+ __unsafe_unretained UIViewController* view_controller_; |
+ DISALLOW_COPY_AND_ASSIGN(PrintPDFFetcherDelegate); |
+}; |
+} // namespace |
+ |
+@implementation PrintController { |
+ // URLFetcher to download the PDF pointed to by the WKWebView. |
+ std::unique_ptr<URLFetcher> _fetcher; |
+ |
+ // A delegate to bridge between PrintController and the URLFetcher callback. |
+ std::unique_ptr<PrintPDFFetcherDelegate> _fetcherDelegate; |
+ |
+ // Context getter required by the URLFetcher. |
+ scoped_refptr<URLRequestContextGetter> _requestContextGetter; |
+ |
+ // A dialog which indicates that the print preview is being prepared. It |
+ // offers a cancel button which will cancel the download. It is created when |
+ // downloading begins and is released when downloading ends (either due to |
+ // cancellation or completion). |
+ base::scoped_nsobject<LoadingAlertCoordinator> _PDFDownloadingDialog; |
+ |
+ // A dialog which indicates that the print preview failed. |
+ base::scoped_nsobject<AlertCoordinator> _PDFDownloadingErrorDialog; |
+} |
+ |
+#pragma mark - Class methods. |
+ |
++ (void)displayPrintInteractionController: |
+ (UIPrintInteractionController*)printInteractionController |
+ forPDF:(BOOL)isPDF { |
+ void (^completionHandler)(UIPrintInteractionController*, BOOL, NSError*) = ^( |
+ UIPrintInteractionController* printInteractionController, BOOL completed, |
+ NSError* error) { |
+ if (error) |
+ DLOG(ERROR) << "Air printing error: " << error.description; |
+ |
+ // When printing a NSData object given to the |
+ // UIPrintInteractionController's |printingItem| object, a PDF file |
+ // representing the NSData object is created in the app's tmp directory |
+ // by the OS and never deleted. So, this workaround deletes PDF files in |
+ // tmp now that printing is done. When iOS9 is deprecated, this can |
+ // be removed since PDFs will no longer need to be downloaded to print, |
+ // and |printingItem| will no longer be used. |
+ if (!base::ios::IsRunningOnIOS10OrLater() && isPDF) { |
+ web::WebThread::PostBlockingPoolTask( |
+ FROM_HERE, base::BindBlock(^{ |
+ NSFileManager* manager = [NSFileManager defaultManager]; |
+ NSString* tempDir = NSTemporaryDirectory(); |
+ NSError* tempDirError = nil; |
+ |
+ // Iterate over files in tmp directory. |
+ for (NSString* file in |
+ [manager contentsOfDirectoryAtPath:tempDir |
+ error:&tempDirError]) { |
+ // If the file is a PDF file, delete it. |
+ if ([[file pathExtension] isEqualToString:@"pdf"]) { |
+ NSError* deletionError = nil; |
+ NSString* fullFilePath = |
+ [tempDir stringByAppendingPathComponent:file]; |
+ BOOL success = [manager removeItemAtPath:fullFilePath |
+ error:&deletionError]; |
+ if (!success) { |
+ DLOG(ERROR) << "AirPrint unable to remove tmp file:" << file |
+ << " error: " << deletionError.description; |
+ } |
+ } |
+ } |
+ if (tempDirError) { |
+ DLOG(ERROR) << "AirPrint tmp dir access error:" |
+ << tempDirError.description; |
+ } |
+ })); |
+ } |
+ }; |
+ [printInteractionController presentAnimated:YES |
+ completionHandler:completionHandler]; |
+} |
+ |
+#pragma mark - Public Methods |
+ |
+- (instancetype)initWithContextGetter: |
+ (scoped_refptr<net::URLRequestContextGetter>)getter { |
+ self = [super init]; |
+ if (self) { |
+ _requestContextGetter = std::move(getter); |
+ _fetcherDelegate.reset(new PrintPDFFetcherDelegate(self)); |
+ } |
+ return self; |
+} |
+ |
+- (instancetype)init { |
+ NOTREACHED(); |
+ return nil; |
+} |
+ |
+- (void)printView:(UIView*)view |
+ withTitle:(NSString*)title |
+ viewController:(UIViewController*)viewController { |
+ base::RecordAction(base::UserMetricsAction("MobilePrintMenuAirPrint")); |
+ UIPrintInteractionController* printInteractionController = |
+ [UIPrintInteractionController sharedPrintController]; |
+ UIPrintInfo* printInfo = [UIPrintInfo printInfo]; |
+ printInfo.outputType = UIPrintInfoOutputGeneral; |
+ printInfo.jobName = title; |
+ printInteractionController.printInfo = printInfo; |
+ printInteractionController.showsPageRange = YES; |
+ |
+ // Print Formatters do not work for PDFs in iOS9 WKWebView, but do in iOS10. |
+ // Instead, download the PDF and (eventually) pass it to the |
+ // UIPrintInteractionController. Remove this workaround and all associated PDF |
+ // specific code when iOS9 is deprecated. |
+ BOOL isPDFURL = NO; |
+ if (!base::ios::IsRunningOnIOS10OrLater() && |
+ [view isMemberOfClass:[WKWebView class]]) { |
+ WKWebView* webView = base::mac::ObjCCastStrict<WKWebView>(view); |
+ NSURL* URL = webView.URL; |
+ CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag( |
+ kUTTagClassFilenameExtension, |
+ (__bridge CFStringRef)[[URL path] pathExtension], NULL); |
+ if (UTI) { |
+ CFStringRef MIMEType = |
+ UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType); |
+ if (MIMEType) { |
+ isPDFURL = |
+ [@(kPDFMimeType) isEqualToString:(__bridge NSString*)MIMEType]; |
+ if (isPDFURL) { |
+ [self downloadPDFFileWithURL:net::GURLWithNSURL(URL) |
+ viewController:viewController]; |
+ } |
+ CFRelease(MIMEType); |
+ } |
+ CFRelease(UTI); |
+ } |
+ } |
+ |
+ if (!isPDFURL) { |
+ base::scoped_nsobject<UIPrintPageRenderer> renderer( |
+ [[UIPrintPageRenderer alloc] init]); |
+ [renderer addPrintFormatter:[view viewPrintFormatter] |
+ startingAtPageAtIndex:0]; |
+ printInteractionController.printPageRenderer = renderer; |
+ [PrintController |
+ displayPrintInteractionController:printInteractionController |
+ forPDF:NO]; |
+ } |
+} |
+ |
+- (void)dismissAnimated:(BOOL)animated { |
+ _fetcher.reset(); |
+ [self dismissPDFDownloadingDialog]; |
+ [_PDFDownloadingErrorDialog stop]; |
+ _PDFDownloadingErrorDialog.reset(); |
+ [[UIPrintInteractionController sharedPrintController] |
+ dismissAnimated:animated]; |
+} |
+ |
+#pragma mark - Private Methods |
+ |
+- (void)showPDFDownloadingDialog:(UIViewController*)viewController { |
+ if (_PDFDownloadingDialog) |
+ return; |
+ |
+ NSString* title = l10n_util::GetNSString(IDS_IOS_PRINT_PDF_PREPARATION); |
+ |
+ base::WeakNSObject<PrintController> weakSelf(self); |
+ ProceduralBlock cancelHandler = ^{ |
+ base::scoped_nsobject<PrintController> strongSelf([weakSelf retain]); |
+ if (strongSelf) |
+ strongSelf.get()->_fetcher.reset(); |
+ }; |
+ |
+ _PDFDownloadingDialog.reset([[LoadingAlertCoordinator alloc] |
+ initWithBaseViewController:viewController |
+ title:title |
+ cancelHandler:cancelHandler]); |
+ |
+ dispatch_after( |
+ dispatch_time(DISPATCH_TIME_NOW, kPDFDownloadDialogDelay * NSEC_PER_SEC), |
+ dispatch_get_main_queue(), ^{ |
+ base::scoped_nsobject<PrintController> strongSelf([weakSelf retain]); |
+ if (!strongSelf) |
+ return; |
+ [strongSelf.get()->_PDFDownloadingDialog start]; |
+ }); |
+} |
+ |
+- (void)dismissPDFDownloadingDialog { |
+ [_PDFDownloadingDialog stop]; |
+ _PDFDownloadingDialog.reset(); |
+} |
+ |
+- (void)showPDFDownloadErrorWithURL:(const GURL)URL |
+ viewController:(UIViewController*)viewController { |
+ NSString* title = l10n_util::GetNSString(IDS_IOS_PRINT_PDF_ERROR_TITLE); |
+ NSString* message = l10n_util::GetNSString(IDS_IOS_PRINT_PDF_ERROR_SUBTITLE); |
+ |
+ _PDFDownloadingErrorDialog.reset([[AlertCoordinator alloc] |
+ initWithBaseViewController:viewController |
+ title:title |
+ message:message]); |
+ |
+ base::WeakNSObject<PrintController> weakSelf(self); |
+ |
+ [_PDFDownloadingErrorDialog |
+ addItemWithTitle:l10n_util::GetNSString(IDS_IOS_PRINT_PDF_TRY_AGAIN) |
+ action:^{ |
+ [weakSelf downloadPDFFileWithURL:URL |
+ viewController:viewController]; |
+ } |
+ style:UIAlertActionStyleDefault]; |
+ |
+ [_PDFDownloadingErrorDialog |
+ addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL) |
+ action:nil |
+ style:UIAlertActionStyleCancel]; |
+ |
+ [_PDFDownloadingErrorDialog start]; |
+} |
+ |
+- (void)downloadPDFFileWithURL:(const GURL&)URL |
+ viewController:(UIViewController*)viewController { |
+ DCHECK(!_fetcher); |
+ _fetcherDelegate->SetViewController(viewController); |
+ _fetcher = URLFetcher::Create(URL, URLFetcher::GET, _fetcherDelegate.get()); |
+ _fetcher->SetRequestContext(_requestContextGetter.get()); |
+ _fetcher->Start(); |
+ [self showPDFDownloadingDialog:viewController]; |
+} |
+ |
+- (void)finishedDownloadingPDF:(UIViewController*)viewController { |
+ [self dismissPDFDownloadingDialog]; |
+ DCHECK(_fetcher); |
+ base::ScopedClosureRunner fetcherResetter(base::BindBlock(^{ |
+ _fetcher.reset(); |
+ })); |
+ int responseCode = _fetcher->GetResponseCode(); |
+ std::string response; |
+ std::string MIMEType; |
+ // If the request is not successful or does not match a PDF |
+ // MIME type, show an error. |
+ if (!_fetcher->GetStatus().is_success() || responseCode != 200 || |
+ !_fetcher->GetResponseAsString(&response) || |
+ !_fetcher->GetResponseHeaders()->GetMimeType(&MIMEType) || |
+ MIMEType != kPDFMimeType) { |
+ [self showPDFDownloadErrorWithURL:_fetcher->GetOriginalURL() |
+ viewController:viewController]; |
+ return; |
+ } |
+ NSData* data = |
+ [NSData dataWithBytes:response.c_str() length:response.length()]; |
+ // If the data cannot be printed, show an error. |
+ if (![UIPrintInteractionController canPrintData:data]) { |
+ [self showPDFDownloadErrorWithURL:_fetcher->GetOriginalURL() |
+ viewController:viewController]; |
+ return; |
+ } |
+ UIPrintInteractionController* printInteractionController = |
+ [UIPrintInteractionController sharedPrintController]; |
+ printInteractionController.printingItem = data; |
+ [PrintController displayPrintInteractionController:printInteractionController |
+ forPDF:YES]; |
+} |
+ |
+@end |