Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(44)

Side by Side Diff: ios/chrome/browser/ui/print/print_controller.mm

Issue 2589803002: Upstream Chrome on iOS source code [6/11]. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2012 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/print/print_controller.h"
6
7 #import <MobileCoreServices/UTType.h>
8 #import <Webkit/Webkit.h>
9
10 #include <memory>
11
12 #include "base/callback_helpers.h"
13 #import "base/ios/ios_util.h"
14 #import "base/ios/weak_nsobject.h"
15 #include "base/location.h"
16 #include "base/logging.h"
17 #include "base/mac/bind_objc_block.h"
18 #include "base/mac/foundation_util.h"
19 #import "base/mac/scoped_nsobject.h"
20 #include "base/memory/ref_counted.h"
21 #include "base/metrics/user_metrics.h"
22 #include "base/metrics/user_metrics_action.h"
23 #include "components/strings/grit/components_strings.h"
24 #import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
25 #import "ios/chrome/browser/ui/alert_coordinator/loading_alert_coordinator.h"
26 #include "ios/chrome/grit/ios_strings.h"
27 #include "ios/web/public/web_thread.h"
28 #import "net/base/mac/url_conversions.h"
29 #include "net/http/http_response_headers.h"
30 #include "net/url_request/url_fetcher.h"
31 #include "net/url_request/url_fetcher_delegate.h"
32 #include "net/url_request/url_request_context_getter.h"
33 #include "ui/base/l10n/l10n_util_mac.h"
34
35 using net::URLFetcher;
36 using net::URLFetcherDelegate;
37 using net::URLRequestContextGetter;
38
39 @interface PrintController ()
40
41 // Presents a UIPrintInteractionController with a default completion handler.
42 // |isPDF| indicates if |printInteractionController| is being presented to print
43 // a PDF.
44 + (void)displayPrintInteractionController:
45 (UIPrintInteractionController*)printInteractionController
46 forPDF:(BOOL)isPDF;
47
48 // Shows a dialog on |viewController| indicating that the print preview is being
49 // prepared. The dialog will appear only if the download has not completed or
50 // been cancelled |kPDFDownloadDialogDelay| seconds after this method is called.
51 - (void)showPDFDownloadingDialog:(UIViewController*)viewController;
52
53 // Dismisses the dialog which indicates that the print preview is being
54 // prepared.
55 - (void)dismissPDFDownloadingDialog;
56
57 // Shows an dialog on |viewController| indicating that there was an error when
58 // preparing the print preview, and providing the ability to retry. |URL| is the
59 // URL of the PDF which had an error.
60 - (void)showPDFDownloadErrorWithURL:(const GURL)URL
61 viewController:(UIViewController*)viewController;
62
63 // Handles downloading the file at |URL| and presents dialogs on
64 // |viewController| if necessary.
65 - (void)downloadPDFFileWithURL:(const GURL&)URL
66 viewController:(UIViewController*)viewController;
67
68 // Accesses the result of the PDF download, and if successful, presents the
69 // AirPrint menu with the downloaded PDF. It also ensures that the
70 // PDFDownloadingDialog is dismissed, and presents an error dialog on
71 // |viewController| if the download fails. This method should be called only by
72 // the URLFetcherDelegate.
73 - (void)finishedDownloadingPDF:(UIViewController*)viewController;
74
75 @end
76
77 namespace {
78
79 // The MIME type of a PDF file contained in a Web view.
80 const char kPDFMimeType[] = "application/pdf";
81
82 // Delay after downloading begins that the |_PDFDownloadingDialog| appears.
83 const int64_t kPDFDownloadDialogDelay = 1;
84
85 // A delegate for the URLFetcher to tell owning PrintController that the
86 // download is complete.
87 class PrintPDFFetcherDelegate : public URLFetcherDelegate {
88 public:
89 explicit PrintPDFFetcherDelegate(PrintController* owner) : owner_(owner) {}
90 void OnURLFetchComplete(const URLFetcher* source) override {
91 DCHECK(view_controller_);
92 [owner_ finishedDownloadingPDF:view_controller_];
93 }
94
95 // The ViewController used to display an error if the download failed.
96 void SetViewController(UIViewController* view_controller) {
97 view_controller_ = view_controller;
98 }
99
100 private:
101 __unsafe_unretained PrintController* owner_;
102 __unsafe_unretained UIViewController* view_controller_;
103 DISALLOW_COPY_AND_ASSIGN(PrintPDFFetcherDelegate);
104 };
105 } // namespace
106
107 @implementation PrintController {
108 // URLFetcher to download the PDF pointed to by the WKWebView.
109 std::unique_ptr<URLFetcher> _fetcher;
110
111 // A delegate to bridge between PrintController and the URLFetcher callback.
112 std::unique_ptr<PrintPDFFetcherDelegate> _fetcherDelegate;
113
114 // Context getter required by the URLFetcher.
115 scoped_refptr<URLRequestContextGetter> _requestContextGetter;
116
117 // A dialog which indicates that the print preview is being prepared. It
118 // offers a cancel button which will cancel the download. It is created when
119 // downloading begins and is released when downloading ends (either due to
120 // cancellation or completion).
121 base::scoped_nsobject<LoadingAlertCoordinator> _PDFDownloadingDialog;
122
123 // A dialog which indicates that the print preview failed.
124 base::scoped_nsobject<AlertCoordinator> _PDFDownloadingErrorDialog;
125 }
126
127 #pragma mark - Class methods.
128
129 + (void)displayPrintInteractionController:
130 (UIPrintInteractionController*)printInteractionController
131 forPDF:(BOOL)isPDF {
132 void (^completionHandler)(UIPrintInteractionController*, BOOL, NSError*) = ^(
133 UIPrintInteractionController* printInteractionController, BOOL completed,
134 NSError* error) {
135 if (error)
136 DLOG(ERROR) << "Air printing error: " << error.description;
137
138 // When printing a NSData object given to the
139 // UIPrintInteractionController's |printingItem| object, a PDF file
140 // representing the NSData object is created in the app's tmp directory
141 // by the OS and never deleted. So, this workaround deletes PDF files in
142 // tmp now that printing is done. When iOS9 is deprecated, this can
143 // be removed since PDFs will no longer need to be downloaded to print,
144 // and |printingItem| will no longer be used.
145 if (!base::ios::IsRunningOnIOS10OrLater() && isPDF) {
146 web::WebThread::PostBlockingPoolTask(
147 FROM_HERE, base::BindBlock(^{
148 NSFileManager* manager = [NSFileManager defaultManager];
149 NSString* tempDir = NSTemporaryDirectory();
150 NSError* tempDirError = nil;
151
152 // Iterate over files in tmp directory.
153 for (NSString* file in
154 [manager contentsOfDirectoryAtPath:tempDir
155 error:&tempDirError]) {
156 // If the file is a PDF file, delete it.
157 if ([[file pathExtension] isEqualToString:@"pdf"]) {
158 NSError* deletionError = nil;
159 NSString* fullFilePath =
160 [tempDir stringByAppendingPathComponent:file];
161 BOOL success = [manager removeItemAtPath:fullFilePath
162 error:&deletionError];
163 if (!success) {
164 DLOG(ERROR) << "AirPrint unable to remove tmp file:" << file
165 << " error: " << deletionError.description;
166 }
167 }
168 }
169 if (tempDirError) {
170 DLOG(ERROR) << "AirPrint tmp dir access error:"
171 << tempDirError.description;
172 }
173 }));
174 }
175 };
176 [printInteractionController presentAnimated:YES
177 completionHandler:completionHandler];
178 }
179
180 #pragma mark - Public Methods
181
182 - (instancetype)initWithContextGetter:
183 (scoped_refptr<net::URLRequestContextGetter>)getter {
184 self = [super init];
185 if (self) {
186 _requestContextGetter = std::move(getter);
187 _fetcherDelegate.reset(new PrintPDFFetcherDelegate(self));
188 }
189 return self;
190 }
191
192 - (instancetype)init {
193 NOTREACHED();
194 return nil;
195 }
196
197 - (void)printView:(UIView*)view
198 withTitle:(NSString*)title
199 viewController:(UIViewController*)viewController {
200 base::RecordAction(base::UserMetricsAction("MobilePrintMenuAirPrint"));
201 UIPrintInteractionController* printInteractionController =
202 [UIPrintInteractionController sharedPrintController];
203 UIPrintInfo* printInfo = [UIPrintInfo printInfo];
204 printInfo.outputType = UIPrintInfoOutputGeneral;
205 printInfo.jobName = title;
206 printInteractionController.printInfo = printInfo;
207 printInteractionController.showsPageRange = YES;
208
209 // Print Formatters do not work for PDFs in iOS9 WKWebView, but do in iOS10.
210 // Instead, download the PDF and (eventually) pass it to the
211 // UIPrintInteractionController. Remove this workaround and all associated PDF
212 // specific code when iOS9 is deprecated.
213 BOOL isPDFURL = NO;
214 if (!base::ios::IsRunningOnIOS10OrLater() &&
215 [view isMemberOfClass:[WKWebView class]]) {
216 WKWebView* webView = base::mac::ObjCCastStrict<WKWebView>(view);
217 NSURL* URL = webView.URL;
218 CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(
219 kUTTagClassFilenameExtension,
220 (__bridge CFStringRef)[[URL path] pathExtension], NULL);
221 if (UTI) {
222 CFStringRef MIMEType =
223 UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType);
224 if (MIMEType) {
225 isPDFURL =
226 [@(kPDFMimeType) isEqualToString:(__bridge NSString*)MIMEType];
227 if (isPDFURL) {
228 [self downloadPDFFileWithURL:net::GURLWithNSURL(URL)
229 viewController:viewController];
230 }
231 CFRelease(MIMEType);
232 }
233 CFRelease(UTI);
234 }
235 }
236
237 if (!isPDFURL) {
238 base::scoped_nsobject<UIPrintPageRenderer> renderer(
239 [[UIPrintPageRenderer alloc] init]);
240 [renderer addPrintFormatter:[view viewPrintFormatter]
241 startingAtPageAtIndex:0];
242 printInteractionController.printPageRenderer = renderer;
243 [PrintController
244 displayPrintInteractionController:printInteractionController
245 forPDF:NO];
246 }
247 }
248
249 - (void)dismissAnimated:(BOOL)animated {
250 _fetcher.reset();
251 [self dismissPDFDownloadingDialog];
252 [_PDFDownloadingErrorDialog stop];
253 _PDFDownloadingErrorDialog.reset();
254 [[UIPrintInteractionController sharedPrintController]
255 dismissAnimated:animated];
256 }
257
258 #pragma mark - Private Methods
259
260 - (void)showPDFDownloadingDialog:(UIViewController*)viewController {
261 if (_PDFDownloadingDialog)
262 return;
263
264 NSString* title = l10n_util::GetNSString(IDS_IOS_PRINT_PDF_PREPARATION);
265
266 base::WeakNSObject<PrintController> weakSelf(self);
267 ProceduralBlock cancelHandler = ^{
268 base::scoped_nsobject<PrintController> strongSelf([weakSelf retain]);
269 if (strongSelf)
270 strongSelf.get()->_fetcher.reset();
271 };
272
273 _PDFDownloadingDialog.reset([[LoadingAlertCoordinator alloc]
274 initWithBaseViewController:viewController
275 title:title
276 cancelHandler:cancelHandler]);
277
278 dispatch_after(
279 dispatch_time(DISPATCH_TIME_NOW, kPDFDownloadDialogDelay * NSEC_PER_SEC),
280 dispatch_get_main_queue(), ^{
281 base::scoped_nsobject<PrintController> strongSelf([weakSelf retain]);
282 if (!strongSelf)
283 return;
284 [strongSelf.get()->_PDFDownloadingDialog start];
285 });
286 }
287
288 - (void)dismissPDFDownloadingDialog {
289 [_PDFDownloadingDialog stop];
290 _PDFDownloadingDialog.reset();
291 }
292
293 - (void)showPDFDownloadErrorWithURL:(const GURL)URL
294 viewController:(UIViewController*)viewController {
295 NSString* title = l10n_util::GetNSString(IDS_IOS_PRINT_PDF_ERROR_TITLE);
296 NSString* message = l10n_util::GetNSString(IDS_IOS_PRINT_PDF_ERROR_SUBTITLE);
297
298 _PDFDownloadingErrorDialog.reset([[AlertCoordinator alloc]
299 initWithBaseViewController:viewController
300 title:title
301 message:message]);
302
303 base::WeakNSObject<PrintController> weakSelf(self);
304
305 [_PDFDownloadingErrorDialog
306 addItemWithTitle:l10n_util::GetNSString(IDS_IOS_PRINT_PDF_TRY_AGAIN)
307 action:^{
308 [weakSelf downloadPDFFileWithURL:URL
309 viewController:viewController];
310 }
311 style:UIAlertActionStyleDefault];
312
313 [_PDFDownloadingErrorDialog
314 addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
315 action:nil
316 style:UIAlertActionStyleCancel];
317
318 [_PDFDownloadingErrorDialog start];
319 }
320
321 - (void)downloadPDFFileWithURL:(const GURL&)URL
322 viewController:(UIViewController*)viewController {
323 DCHECK(!_fetcher);
324 _fetcherDelegate->SetViewController(viewController);
325 _fetcher = URLFetcher::Create(URL, URLFetcher::GET, _fetcherDelegate.get());
326 _fetcher->SetRequestContext(_requestContextGetter.get());
327 _fetcher->Start();
328 [self showPDFDownloadingDialog:viewController];
329 }
330
331 - (void)finishedDownloadingPDF:(UIViewController*)viewController {
332 [self dismissPDFDownloadingDialog];
333 DCHECK(_fetcher);
334 base::ScopedClosureRunner fetcherResetter(base::BindBlock(^{
335 _fetcher.reset();
336 }));
337 int responseCode = _fetcher->GetResponseCode();
338 std::string response;
339 std::string MIMEType;
340 // If the request is not successful or does not match a PDF
341 // MIME type, show an error.
342 if (!_fetcher->GetStatus().is_success() || responseCode != 200 ||
343 !_fetcher->GetResponseAsString(&response) ||
344 !_fetcher->GetResponseHeaders()->GetMimeType(&MIMEType) ||
345 MIMEType != kPDFMimeType) {
346 [self showPDFDownloadErrorWithURL:_fetcher->GetOriginalURL()
347 viewController:viewController];
348 return;
349 }
350 NSData* data =
351 [NSData dataWithBytes:response.c_str() length:response.length()];
352 // If the data cannot be printed, show an error.
353 if (![UIPrintInteractionController canPrintData:data]) {
354 [self showPDFDownloadErrorWithURL:_fetcher->GetOriginalURL()
355 viewController:viewController];
356 return;
357 }
358 UIPrintInteractionController* printInteractionController =
359 [UIPrintInteractionController sharedPrintController];
360 printInteractionController.printingItem = data;
361 [PrintController displayPrintInteractionController:printInteractionController
362 forPDF:YES];
363 }
364
365 @end
OLDNEW
« no previous file with comments | « ios/chrome/browser/ui/print/print_controller.h ('k') | ios/chrome/browser/ui/print/print_controller_egtest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698