print_preview_handler.cc revision 868fa2fe829687343ffae624259930155e16dbd8
1// Copyright (c) 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#include "chrome/browser/ui/webui/print_preview/print_preview_handler.h"
6
7#include <ctype.h>
8
9#include <string>
10
11#include "base/base64.h"
12#include "base/bind.h"
13#include "base/bind_helpers.h"
14#include "base/command_line.h"
15#include "base/i18n/file_util_icu.h"
16#include "base/i18n/number_formatting.h"
17#include "base/json/json_reader.h"
18#include "base/lazy_instance.h"
19#include "base/memory/linked_ptr.h"
20#include "base/memory/ref_counted_memory.h"
21#include "base/metrics/histogram.h"
22#include "base/path_service.h"
23#include "base/prefs/pref_service.h"
24#include "base/strings/utf_string_conversions.h"
25#include "base/threading/thread.h"
26#include "base/threading/thread_restrictions.h"
27#include "base/values.h"
28#include "chrome/browser/browser_process.h"
29#include "chrome/browser/platform_util.h"
30#include "chrome/browser/printing/cloud_print/cloud_print_url.h"
31#include "chrome/browser/printing/print_dialog_cloud.h"
32#include "chrome/browser/printing/print_error_dialog.h"
33#include "chrome/browser/printing/print_job_manager.h"
34#include "chrome/browser/printing/print_preview_dialog_controller.h"
35#include "chrome/browser/printing/print_system_task_proxy.h"
36#include "chrome/browser/printing/print_view_manager.h"
37#include "chrome/browser/printing/printer_manager_dialog.h"
38#include "chrome/browser/profiles/profile.h"
39#include "chrome/browser/signin/oauth2_token_service.h"
40#include "chrome/browser/signin/profile_oauth2_token_service.h"
41#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
42#include "chrome/browser/ui/browser_finder.h"
43#include "chrome/browser/ui/browser_tabstrip.h"
44#include "chrome/browser/ui/chrome_select_file_policy.h"
45#include "chrome/browser/ui/webui/print_preview/print_preview_ui.h"
46#include "chrome/browser/ui/webui/print_preview/sticky_settings.h"
47#include "chrome/common/chrome_paths.h"
48#include "chrome/common/chrome_switches.h"
49#include "chrome/common/pref_names.h"
50#include "chrome/common/print_messages.h"
51#include "content/public/browser/browser_context.h"
52#include "content/public/browser/browser_thread.h"
53#include "content/public/browser/navigation_controller.h"
54#include "content/public/browser/navigation_entry.h"
55#include "content/public/browser/render_view_host.h"
56#include "content/public/browser/web_contents.h"
57#include "content/public/browser/web_contents_view.h"
58#include "content/public/browser/web_ui.h"
59#include "printing/backend/print_backend.h"
60#include "printing/metafile.h"
61#include "printing/metafile_impl.h"
62#include "printing/page_range.h"
63#include "printing/page_size_margins.h"
64#include "printing/print_settings.h"
65#include "third_party/icu/public/i18n/unicode/ulocdata.h"
66
67#if defined(OS_CHROMEOS)
68// TODO(kinaba): provide more non-intrusive way for handling local/remote
69// distinction and remove these ugly #ifdef's. http://crbug.com/140425
70#include "chrome/browser/chromeos/drive/file_system_util.h"
71#include "chrome/browser/chromeos/settings/device_oauth2_token_service.h"
72#include "chrome/browser/chromeos/settings/device_oauth2_token_service_factory.h"
73#endif
74
75using content::BrowserThread;
76using content::NavigationEntry;
77using content::OpenURLParams;
78using content::RenderViewHost;
79using content::Referrer;
80using content::WebContents;
81using printing::Metafile;
82
83namespace {
84
85// The cloud print OAuth2 scope.
86const char kCloudPrintAuth[] = "https://www.googleapis.com/auth/cloudprint";
87
88enum UserActionBuckets {
89  PRINT_TO_PRINTER,
90  PRINT_TO_PDF,
91  CANCEL,
92  FALLBACK_TO_ADVANCED_SETTINGS_DIALOG,
93  PREVIEW_FAILED,
94  PREVIEW_STARTED,
95  INITIATOR_TAB_CRASHED,  // UNUSED
96  INITIATOR_TAB_CLOSED,
97  PRINT_WITH_CLOUD_PRINT,
98  USERACTION_BUCKET_BOUNDARY
99};
100
101enum PrintSettingsBuckets {
102  LANDSCAPE = 0,
103  PORTRAIT,
104  COLOR,
105  BLACK_AND_WHITE,
106  COLLATE,
107  SIMPLEX,
108  DUPLEX,
109  TOTAL,
110  HEADERS_AND_FOOTERS,
111  CSS_BACKGROUND,
112  SELECTION_ONLY,
113  PRINT_SETTINGS_BUCKET_BOUNDARY
114};
115
116enum UiBucketGroups {
117  DESTINATION_SEARCH,
118  GCP_PROMO,
119  UI_BUCKET_GROUP_BOUNDARY
120};
121
122enum PrintDestinationBuckets {
123  DESTINATION_SHOWN,
124  DESTINATION_CLOSED_CHANGED,
125  DESTINATION_CLOSED_UNCHANGED,
126  SIGNIN_PROMPT,
127  SIGNIN_TRIGGERED,
128  PRINT_DESTINATION_BUCKET_BOUNDARY
129};
130
131enum GcpPromoBuckets {
132  PROMO_SHOWN,
133  PROMO_CLOSED,
134  PROMO_CLICKED,
135  GCP_PROMO_BUCKET_BOUNDARY
136};
137
138void ReportUserActionHistogram(enum UserActionBuckets event) {
139  UMA_HISTOGRAM_ENUMERATION("PrintPreview.UserAction", event,
140                            USERACTION_BUCKET_BOUNDARY);
141}
142
143void ReportPrintSettingHistogram(enum PrintSettingsBuckets setting) {
144  UMA_HISTOGRAM_ENUMERATION("PrintPreview.PrintSettings", setting,
145                            PRINT_SETTINGS_BUCKET_BOUNDARY);
146}
147
148void ReportPrintDestinationHistogram(enum PrintDestinationBuckets event) {
149  UMA_HISTOGRAM_ENUMERATION("PrintPreview.DestinationAction", event,
150                            PRINT_DESTINATION_BUCKET_BOUNDARY);
151}
152
153void ReportGcpPromoHistogram(enum GcpPromoBuckets event) {
154  UMA_HISTOGRAM_ENUMERATION("PrintPreview.GcpPromo", event,
155                            GCP_PROMO_BUCKET_BOUNDARY);
156}
157
158// Name of a dictionary field holding cloud print related data;
159const char kAppState[] = "appState";
160// Name of a dictionary field holding the initiator tab title.
161const char kInitiatorTabTitle[] = "initiatorTabTitle";
162// Name of a dictionary field holding the measurement system according to the
163// locale.
164const char kMeasurementSystem[] = "measurementSystem";
165// Name of a dictionary field holding the number format according to the locale.
166const char kNumberFormat[] = "numberFormat";
167// Name of a dictionary field specifying whether to print automatically in
168// kiosk mode. See http://crbug.com/31395.
169const char kPrintAutomaticallyInKioskMode[] = "printAutomaticallyInKioskMode";
170// Name of a dictionary field holding the state of selection for document.
171const char kDocumentHasSelection[] = "documentHasSelection";
172
173
174// Get the print job settings dictionary from |args|. The caller takes
175// ownership of the returned DictionaryValue. Returns NULL on failure.
176DictionaryValue* GetSettingsDictionary(const ListValue* args) {
177  std::string json_str;
178  if (!args->GetString(0, &json_str)) {
179    NOTREACHED() << "Could not read JSON argument";
180    return NULL;
181  }
182  if (json_str.empty()) {
183    NOTREACHED() << "Empty print job settings";
184    return NULL;
185  }
186  scoped_ptr<DictionaryValue> settings(static_cast<DictionaryValue*>(
187      base::JSONReader::Read(json_str)));
188  if (!settings.get() || !settings->IsType(Value::TYPE_DICTIONARY)) {
189    NOTREACHED() << "Print job settings must be a dictionary.";
190    return NULL;
191  }
192
193  if (settings->empty()) {
194    NOTREACHED() << "Print job settings dictionary is empty";
195    return NULL;
196  }
197
198  return settings.release();
199}
200
201// Track the popularity of print settings and report the stats.
202void ReportPrintSettingsStats(const DictionaryValue& settings) {
203  ReportPrintSettingHistogram(TOTAL);
204
205  bool landscape = false;
206  if (settings.GetBoolean(printing::kSettingLandscape, &landscape))
207    ReportPrintSettingHistogram(landscape ? LANDSCAPE : PORTRAIT);
208
209  bool collate = false;
210  if (settings.GetBoolean(printing::kSettingCollate, &collate) && collate)
211    ReportPrintSettingHistogram(COLLATE);
212
213  int duplex_mode = 0;
214  if (settings.GetInteger(printing::kSettingDuplexMode, &duplex_mode))
215    ReportPrintSettingHistogram(duplex_mode ? DUPLEX : SIMPLEX);
216
217  int color_mode = 0;
218  if (settings.GetInteger(printing::kSettingColor, &color_mode)) {
219    ReportPrintSettingHistogram(
220        printing::isColorModelSelected(color_mode) ? COLOR : BLACK_AND_WHITE);
221  }
222
223  bool headers = false;
224  if (settings.GetBoolean(printing::kSettingHeaderFooterEnabled, &headers) &&
225      headers) {
226    ReportPrintSettingHistogram(HEADERS_AND_FOOTERS);
227  }
228
229  bool css_background = false;
230  if (settings.GetBoolean(printing::kSettingShouldPrintBackgrounds,
231                          &css_background) && css_background) {
232    ReportPrintSettingHistogram(CSS_BACKGROUND);
233  }
234
235  bool selection_only = false;
236  if (settings.GetBoolean(printing::kSettingShouldPrintSelectionOnly,
237                          &selection_only) && selection_only) {
238    ReportPrintSettingHistogram(SELECTION_ONLY);
239  }
240}
241
242// Callback that stores a PDF file on disk.
243void PrintToPdfCallback(Metafile* metafile, const base::FilePath& path) {
244  metafile->SaveTo(path);
245  // |metafile| must be deleted on the UI thread.
246  BrowserThread::PostTask(
247      BrowserThread::UI, FROM_HERE,
248      base::Bind(&base::DeletePointer<Metafile>, metafile));
249}
250
251#if defined(OS_CHROMEOS)
252void PrintToPdfCallbackWithCheck(Metafile* metafile,
253                                 drive::FileError error,
254                                 const base::FilePath& path) {
255  if (error != drive::FILE_ERROR_OK) {
256    LOG(ERROR) << "Save to pdf failed to write: " << error;
257  } else {
258    metafile->SaveTo(path);
259  }
260  // |metafile| must be deleted on the UI thread.
261  BrowserThread::PostTask(
262      BrowserThread::UI, FROM_HERE,
263      base::Bind(&base::DeletePointer<Metafile>, metafile));
264}
265#endif
266
267static base::LazyInstance<printing::StickySettings> sticky_settings =
268    LAZY_INSTANCE_INITIALIZER;
269
270}  // namespace
271
272class PrintPreviewHandler::AccessTokenService
273    : public OAuth2TokenService::Consumer {
274 public:
275  explicit AccessTokenService(PrintPreviewHandler* handler)
276      : handler_(handler) {
277  }
278
279  void RequestToken(const std::string& type) {
280    if (requests_.find(type) != requests_.end())
281      return;  // Already in progress.
282
283    OAuth2TokenService* service = NULL;
284    if (type == "profile") {
285      Profile* profile = Profile::FromWebUI(handler_->web_ui());
286      if (profile)
287        service = ProfileOAuth2TokenServiceFactory::GetForProfile(profile);
288    } else if (type == "device") {
289#if defined(OS_CHROMEOS)
290      service = chromeos::DeviceOAuth2TokenServiceFactory::Get();
291#endif
292    }
293
294    if (service) {
295      OAuth2TokenService::ScopeSet oauth_scopes;
296      oauth_scopes.insert(kCloudPrintAuth);
297      scoped_ptr<OAuth2TokenService::Request> request(
298          service->StartRequest(oauth_scopes, this));
299      requests_[type].reset(request.release());
300    } else {
301      handler_->SendAccessToken(type, std::string());  // Unknown type.
302    }
303  }
304
305  virtual void OnGetTokenSuccess(const OAuth2TokenService::Request* request,
306                                 const std::string& access_token,
307                                 const base::Time& expiration_time) OVERRIDE {
308    OnServiceResponce(request, access_token);
309  }
310
311  virtual void OnGetTokenFailure(const OAuth2TokenService::Request* request,
312                                 const GoogleServiceAuthError& error) OVERRIDE {
313    OnServiceResponce(request, std::string());
314  }
315
316 private:
317  void OnServiceResponce(const OAuth2TokenService::Request* request,
318                         const std::string& access_token) {
319    for (Requests::iterator i = requests_.begin(); i != requests_.end(); ++i) {
320      if (i->second == request) {
321        handler_->SendAccessToken(i->first, access_token);
322        requests_.erase(i);
323        return;
324      }
325    }
326    NOTREACHED();
327  }
328
329  typedef std::map<std::string,
330                   linked_ptr<OAuth2TokenService::Request> > Requests;
331  Requests requests_;
332  PrintPreviewHandler* handler_;
333
334  DISALLOW_COPY_AND_ASSIGN(AccessTokenService);
335};
336
337// static
338printing::StickySettings* PrintPreviewHandler::GetStickySettings() {
339  return sticky_settings.Pointer();
340}
341
342PrintPreviewHandler::PrintPreviewHandler()
343    : print_backend_(printing::PrintBackend::CreateInstance(NULL)),
344      regenerate_preview_request_count_(0),
345      manage_printers_dialog_request_count_(0),
346      manage_cloud_printers_dialog_request_count_(0),
347      reported_failed_preview_(false),
348      has_logged_printers_count_(false) {
349  ReportUserActionHistogram(PREVIEW_STARTED);
350}
351
352PrintPreviewHandler::~PrintPreviewHandler() {
353  if (select_file_dialog_.get())
354    select_file_dialog_->ListenerDestroyed();
355}
356
357void PrintPreviewHandler::RegisterMessages() {
358  web_ui()->RegisterMessageCallback("getPrinters",
359      base::Bind(&PrintPreviewHandler::HandleGetPrinters,
360                 base::Unretained(this)));
361  web_ui()->RegisterMessageCallback("getPreview",
362      base::Bind(&PrintPreviewHandler::HandleGetPreview,
363                 base::Unretained(this)));
364  web_ui()->RegisterMessageCallback("print",
365      base::Bind(&PrintPreviewHandler::HandlePrint,
366                 base::Unretained(this)));
367  web_ui()->RegisterMessageCallback("getPrinterCapabilities",
368      base::Bind(&PrintPreviewHandler::HandleGetPrinterCapabilities,
369                 base::Unretained(this)));
370  web_ui()->RegisterMessageCallback("showSystemDialog",
371      base::Bind(&PrintPreviewHandler::HandleShowSystemDialog,
372                 base::Unretained(this)));
373  web_ui()->RegisterMessageCallback("signIn",
374      base::Bind(&PrintPreviewHandler::HandleSignin,
375                 base::Unretained(this)));
376  web_ui()->RegisterMessageCallback("getAccessToken",
377      base::Bind(&PrintPreviewHandler::HandleGetAccessToken,
378                 base::Unretained(this)));
379  web_ui()->RegisterMessageCallback("manageCloudPrinters",
380      base::Bind(&PrintPreviewHandler::HandleManageCloudPrint,
381                 base::Unretained(this)));
382  web_ui()->RegisterMessageCallback("manageLocalPrinters",
383      base::Bind(&PrintPreviewHandler::HandleManagePrinters,
384                 base::Unretained(this)));
385  web_ui()->RegisterMessageCallback("closePrintPreviewDialog",
386      base::Bind(&PrintPreviewHandler::HandleClosePreviewDialog,
387                 base::Unretained(this)));
388  web_ui()->RegisterMessageCallback("hidePreview",
389      base::Bind(&PrintPreviewHandler::HandleHidePreview,
390                 base::Unretained(this)));
391  web_ui()->RegisterMessageCallback("cancelPendingPrintRequest",
392      base::Bind(&PrintPreviewHandler::HandleCancelPendingPrintRequest,
393                 base::Unretained(this)));
394  web_ui()->RegisterMessageCallback("saveAppState",
395      base::Bind(&PrintPreviewHandler::HandleSaveAppState,
396                 base::Unretained(this)));
397  web_ui()->RegisterMessageCallback("getInitialSettings",
398      base::Bind(&PrintPreviewHandler::HandleGetInitialSettings,
399                 base::Unretained(this)));
400  web_ui()->RegisterMessageCallback("reportUiEvent",
401      base::Bind(&PrintPreviewHandler::HandleReportUiEvent,
402                 base::Unretained(this)));
403  web_ui()->RegisterMessageCallback("printWithCloudPrintDialog",
404      base::Bind(&PrintPreviewHandler::HandlePrintWithCloudPrintDialog,
405                 base::Unretained(this)));
406  web_ui()->RegisterMessageCallback("forceOpenNewTab",
407        base::Bind(&PrintPreviewHandler::HandleForceOpenNewTab,
408                   base::Unretained(this)));
409}
410
411WebContents* PrintPreviewHandler::preview_web_contents() const {
412  return web_ui()->GetWebContents();
413}
414
415void PrintPreviewHandler::HandleGetPrinters(const ListValue* /*args*/) {
416  scoped_refptr<PrintSystemTaskProxy> task =
417      new PrintSystemTaskProxy(AsWeakPtr(),
418                               print_backend_.get(),
419                               has_logged_printers_count_);
420  has_logged_printers_count_ = true;
421
422  BrowserThread::PostTask(
423      BrowserThread::FILE, FROM_HERE,
424      base::Bind(&PrintSystemTaskProxy::EnumeratePrinters, task.get()));
425}
426
427void PrintPreviewHandler::HandleGetPreview(const ListValue* args) {
428  DCHECK_EQ(3U, args->GetSize());
429  scoped_ptr<DictionaryValue> settings(GetSettingsDictionary(args));
430  if (!settings.get())
431    return;
432  int request_id = -1;
433  if (!settings->GetInteger(printing::kPreviewRequestID, &request_id))
434    return;
435
436  PrintPreviewUI* print_preview_ui = static_cast<PrintPreviewUI*>(
437      web_ui()->GetController());
438  print_preview_ui->OnPrintPreviewRequest(request_id);
439  // Add an additional key in order to identify |print_preview_ui| later on
440  // when calling PrintPreviewUI::GetCurrentPrintPreviewStatus() on the IO
441  // thread.
442  settings->SetInteger(printing::kPreviewUIID,
443                       print_preview_ui->GetIDForPrintPreviewUI());
444
445  // Increment request count.
446  ++regenerate_preview_request_count_;
447
448  WebContents* initiator_tab = GetInitiatorTab();
449  if (!initiator_tab) {
450    ReportUserActionHistogram(INITIATOR_TAB_CLOSED);
451    print_preview_ui->OnClosePrintPreviewDialog();
452    return;
453  }
454
455  // Retrieve the page title and url and send it to the renderer process if
456  // headers and footers are to be displayed.
457  bool display_header_footer = false;
458  if (!settings->GetBoolean(printing::kSettingHeaderFooterEnabled,
459                            &display_header_footer)) {
460    NOTREACHED();
461  }
462  if (display_header_footer) {
463    settings->SetString(printing::kSettingHeaderFooterTitle,
464                        initiator_tab->GetTitle());
465    std::string url;
466    NavigationEntry* entry = initiator_tab->GetController().GetActiveEntry();
467    if (entry)
468      url = entry->GetVirtualURL().spec();
469    settings->SetString(printing::kSettingHeaderFooterURL, url);
470  }
471
472  bool generate_draft_data = false;
473  bool success = settings->GetBoolean(printing::kSettingGenerateDraftData,
474                                      &generate_draft_data);
475  DCHECK(success);
476
477  if (!generate_draft_data) {
478    double draft_page_count_double = -1;
479    success = args->GetDouble(1, &draft_page_count_double);
480    DCHECK(success);
481    int draft_page_count = static_cast<int>(draft_page_count_double);
482
483    bool preview_modifiable = false;
484    success = args->GetBoolean(2, &preview_modifiable);
485    DCHECK(success);
486
487    if (draft_page_count != -1 && preview_modifiable &&
488        print_preview_ui->GetAvailableDraftPageCount() != draft_page_count) {
489      settings->SetBoolean(printing::kSettingGenerateDraftData, true);
490    }
491  }
492
493  VLOG(1) << "Print preview request start";
494  RenderViewHost* rvh = initiator_tab->GetRenderViewHost();
495  rvh->Send(new PrintMsg_PrintPreview(rvh->GetRoutingID(), *settings));
496}
497
498void PrintPreviewHandler::HandlePrint(const ListValue* args) {
499  ReportStats();
500
501  // Record the number of times the user requests to regenerate preview data
502  // before printing.
503  UMA_HISTOGRAM_COUNTS("PrintPreview.RegeneratePreviewRequest.BeforePrint",
504                       regenerate_preview_request_count_);
505
506  WebContents* initiator_tab = GetInitiatorTab();
507  if (initiator_tab) {
508    RenderViewHost* rvh = initiator_tab->GetRenderViewHost();
509    rvh->Send(new PrintMsg_ResetScriptedPrintCount(rvh->GetRoutingID()));
510  }
511
512  scoped_ptr<DictionaryValue> settings(GetSettingsDictionary(args));
513  if (!settings.get())
514    return;
515
516  // Never try to add headers/footers here. It's already in the generated PDF.
517  settings->SetBoolean(printing::kSettingHeaderFooterEnabled, false);
518
519  bool print_to_pdf = false;
520  bool is_cloud_printer = false;
521
522  bool open_pdf_in_preview = false;
523#if defined(OS_MACOSX)
524  open_pdf_in_preview = settings->HasKey(printing::kSettingOpenPDFInPreview);
525#endif
526
527  if (!open_pdf_in_preview) {
528    settings->GetBoolean(printing::kSettingPrintToPDF, &print_to_pdf);
529    is_cloud_printer = settings->HasKey(printing::kSettingCloudPrintId);
530  }
531
532  int page_count = 0;
533  settings->GetInteger(printing::kSettingPreviewPageCount, &page_count);
534
535  if (print_to_pdf) {
536    UMA_HISTOGRAM_COUNTS("PrintPreview.PageCount.PrintToPDF", page_count);
537    ReportUserActionHistogram(PRINT_TO_PDF);
538    PrintToPdf();
539    return;
540  }
541
542  scoped_refptr<base::RefCountedBytes> data;
543  string16 title;
544  if (!GetPreviewDataAndTitle(&data, &title)) {
545    // Nothing to print, no preview available.
546    return;
547  }
548
549  if (is_cloud_printer) {
550    UMA_HISTOGRAM_COUNTS("PrintPreview.PageCount.PrintToCloudPrint",
551                         page_count);
552    ReportUserActionHistogram(PRINT_WITH_CLOUD_PRINT);
553    SendCloudPrintJob(data.get());
554  } else {
555    UMA_HISTOGRAM_COUNTS("PrintPreview.PageCount.PrintToPrinter", page_count);
556    ReportUserActionHistogram(PRINT_TO_PRINTER);
557    ReportPrintSettingsStats(*settings);
558
559    // This tries to activate the initiator tab as well, so do not clear the
560    // association with the initiator tab yet.
561    PrintPreviewUI* print_preview_ui = static_cast<PrintPreviewUI*>(
562        web_ui()->GetController());
563    print_preview_ui->OnHidePreviewDialog();
564
565    // Do this so the initiator tab can open a new print preview dialog, while
566    // the current print preview dialog is still handling its print job.
567    ClearInitiatorTabDetails();
568
569    // The PDF being printed contains only the pages that the user selected,
570    // so ignore the page range and print all pages.
571    settings->Remove(printing::kSettingPageRange, NULL);
572    // Remove selection only flag for the same reason.
573    settings->Remove(printing::kSettingShouldPrintSelectionOnly, NULL);
574
575    // Set ID to know whether printing is for preview.
576    settings->SetInteger(printing::kPreviewUIID,
577                         print_preview_ui->GetIDForPrintPreviewUI());
578    RenderViewHost* rvh = preview_web_contents()->GetRenderViewHost();
579    rvh->Send(new PrintMsg_PrintForPrintPreview(rvh->GetRoutingID(),
580                                                *settings));
581
582    // For all other cases above, the preview dialog will stay open until the
583    // printing has finished. Then the dialog closes and PrintPreviewDone() gets
584    // called. In the case below, since the preview dialog will be hidden and
585    // not closed, we need to make this call.
586    if (initiator_tab) {
587      printing::PrintViewManager* print_view_manager =
588          printing::PrintViewManager::FromWebContents(initiator_tab);
589      print_view_manager->PrintPreviewDone();
590    }
591  }
592}
593
594void PrintPreviewHandler::PrintToPdf() {
595  if (print_to_pdf_path_.get()) {
596    // User has already selected a path, no need to show the dialog again.
597    PostPrintToPdfTask();
598  } else if (!select_file_dialog_.get() ||
599             !select_file_dialog_->IsRunning(platform_util::GetTopLevel(
600                 preview_web_contents()->GetView()->GetNativeView()))) {
601    PrintPreviewUI* print_preview_ui = static_cast<PrintPreviewUI*>(
602        web_ui()->GetController());
603    // Pre-populating select file dialog with print job title.
604    string16 print_job_title_utf16 = print_preview_ui->initiator_tab_title();
605
606#if defined(OS_WIN)
607    base::FilePath::StringType print_job_title(print_job_title_utf16);
608#elif defined(OS_POSIX)
609    base::FilePath::StringType print_job_title =
610        UTF16ToUTF8(print_job_title_utf16);
611#endif
612
613    file_util::ReplaceIllegalCharactersInPath(&print_job_title, '_');
614    base::FilePath default_filename(print_job_title);
615    default_filename =
616        default_filename.ReplaceExtension(FILE_PATH_LITERAL("pdf"));
617
618    SelectFile(default_filename);
619  }
620}
621
622void PrintPreviewHandler::HandleHidePreview(const ListValue* /*args*/) {
623  PrintPreviewUI* print_preview_ui = static_cast<PrintPreviewUI*>(
624      web_ui()->GetController());
625  print_preview_ui->OnHidePreviewDialog();
626}
627
628void PrintPreviewHandler::HandleCancelPendingPrintRequest(
629    const ListValue* /*args*/) {
630  WebContents* initiator_tab = GetInitiatorTab();
631  if (initiator_tab)
632    ClearInitiatorTabDetails();
633  gfx::NativeWindow parent = initiator_tab ?
634      initiator_tab->GetView()->GetTopLevelNativeWindow() :
635      NULL;
636  chrome::ShowPrintErrorDialog(parent);
637}
638
639void PrintPreviewHandler::HandleSaveAppState(const ListValue* args) {
640  std::string data_to_save;
641  printing::StickySettings* sticky_settings = GetStickySettings();
642  if (args->GetString(0, &data_to_save) && !data_to_save.empty())
643    sticky_settings->StoreAppState(data_to_save);
644  sticky_settings->SaveInPrefs(Profile::FromBrowserContext(
645      preview_web_contents()->GetBrowserContext())->GetPrefs());
646}
647
648void PrintPreviewHandler::HandleGetPrinterCapabilities(const ListValue* args) {
649  std::string printer_name;
650  bool ret = args->GetString(0, &printer_name);
651  if (!ret || printer_name.empty())
652    return;
653
654  scoped_refptr<PrintSystemTaskProxy> task =
655      new PrintSystemTaskProxy(AsWeakPtr(),
656                               print_backend_.get(),
657                               has_logged_printers_count_);
658
659  BrowserThread::PostTask(
660      BrowserThread::FILE, FROM_HERE,
661      base::Bind(&PrintSystemTaskProxy::GetPrinterCapabilities, task.get(),
662                 printer_name));
663}
664
665// static
666void PrintPreviewHandler::OnSigninComplete(
667    const base::WeakPtr<PrintPreviewHandler>& handler) {
668  if (handler.get()) {
669    PrintPreviewUI* print_preview_ui =
670        static_cast<PrintPreviewUI*>(handler->web_ui()->GetController());
671    if (print_preview_ui)
672      print_preview_ui->OnReloadPrintersList();
673  }
674}
675
676void PrintPreviewHandler::HandleSignin(const ListValue* /*args*/) {
677  gfx::NativeWindow modal_parent = platform_util::GetTopLevel(
678      preview_web_contents()->GetView()->GetNativeView());
679  print_dialog_cloud::CreateCloudPrintSigninDialog(
680      preview_web_contents()->GetBrowserContext(),
681      modal_parent,
682      base::Bind(&PrintPreviewHandler::OnSigninComplete, AsWeakPtr()));
683}
684
685void PrintPreviewHandler::HandleGetAccessToken(const base::ListValue* args) {
686  std::string type;
687  if (!args->GetString(0, &type))
688    return;
689  if (!token_service_)
690    token_service_.reset(new AccessTokenService(this));
691  token_service_->RequestToken(type);
692}
693
694void PrintPreviewHandler::PrintWithCloudPrintDialog() {
695  // Record the number of times the user asks to print via cloud print
696  // instead of the print preview dialog.
697  ReportStats();
698
699  scoped_refptr<base::RefCountedBytes> data;
700  string16 title;
701  if (!GetPreviewDataAndTitle(&data, &title)) {
702    // Nothing to print, no preview available.
703    return;
704  }
705
706  gfx::NativeWindow modal_parent = platform_util::GetTopLevel(
707      preview_web_contents()->GetView()->GetNativeView());
708  print_dialog_cloud::CreatePrintDialogForBytes(
709      preview_web_contents()->GetBrowserContext(),
710      modal_parent,
711      data.get(),
712      title,
713      string16(),
714      std::string("application/pdf"));
715
716  // Once the cloud print dialog comes up we're no longer in a background
717  // printing situation.  Close the print preview.
718  // TODO(abodenha@chromium.org) The flow should be changed as described in
719  // http://code.google.com/p/chromium/issues/detail?id=44093
720  ClosePreviewDialog();
721}
722
723void PrintPreviewHandler::HandleManageCloudPrint(const ListValue* /*args*/) {
724  ++manage_cloud_printers_dialog_request_count_;
725  Profile* profile = Profile::FromBrowserContext(
726      preview_web_contents()->GetBrowserContext());
727  preview_web_contents()->OpenURL(
728      OpenURLParams(
729          CloudPrintURL(profile).GetCloudPrintServiceManageURL(),
730          Referrer(),
731          NEW_FOREGROUND_TAB,
732          content::PAGE_TRANSITION_LINK,
733          false));
734}
735
736void PrintPreviewHandler::HandleShowSystemDialog(const ListValue* /*args*/) {
737  ReportStats();
738  ReportUserActionHistogram(FALLBACK_TO_ADVANCED_SETTINGS_DIALOG);
739
740  WebContents* initiator_tab = GetInitiatorTab();
741  if (!initiator_tab)
742    return;
743
744  printing::PrintViewManager* print_view_manager =
745      printing::PrintViewManager::FromWebContents(initiator_tab);
746  print_view_manager->set_observer(this);
747  print_view_manager->PrintForSystemDialogNow();
748
749  // Cancel the pending preview request if exists.
750  PrintPreviewUI* print_preview_ui = static_cast<PrintPreviewUI*>(
751      web_ui()->GetController());
752  print_preview_ui->OnCancelPendingPreviewRequest();
753}
754
755void PrintPreviewHandler::HandleManagePrinters(const ListValue* /*args*/) {
756  ++manage_printers_dialog_request_count_;
757  printing::PrinterManagerDialog::ShowPrinterManagerDialog();
758}
759
760void PrintPreviewHandler::HandlePrintWithCloudPrintDialog(
761    const base::ListValue* args) {
762  int page_count = 0;
763  if (!args || !args->GetInteger(0, &page_count))
764    return;
765  UMA_HISTOGRAM_COUNTS("PrintPreview.PageCount.PrintToCloudPrintWebDialog",
766                       page_count);
767
768  PrintWithCloudPrintDialog();
769}
770
771void PrintPreviewHandler::HandleClosePreviewDialog(const ListValue* /*args*/) {
772  ReportStats();
773  ReportUserActionHistogram(CANCEL);
774
775  // Record the number of times the user requests to regenerate preview data
776  // before cancelling.
777  UMA_HISTOGRAM_COUNTS("PrintPreview.RegeneratePreviewRequest.BeforeCancel",
778                       regenerate_preview_request_count_);
779}
780
781void PrintPreviewHandler::ReportStats() {
782  UMA_HISTOGRAM_COUNTS("PrintPreview.ManagePrinters",
783                       manage_printers_dialog_request_count_);
784  UMA_HISTOGRAM_COUNTS("PrintPreview.ManageCloudPrinters",
785                       manage_cloud_printers_dialog_request_count_);
786}
787
788void PrintPreviewHandler::GetNumberFormatAndMeasurementSystem(
789    base::DictionaryValue* settings) {
790
791  // Getting the measurement system based on the locale.
792  UErrorCode errorCode = U_ZERO_ERROR;
793  const char* locale = g_browser_process->GetApplicationLocale().c_str();
794  UMeasurementSystem system = ulocdata_getMeasurementSystem(locale, &errorCode);
795  if (errorCode > U_ZERO_ERROR || system == UMS_LIMIT)
796    system = UMS_SI;
797
798  // Getting the number formatting based on the locale and writing to
799  // dictionary.
800  settings->SetString(kNumberFormat, base::FormatDouble(123456.78, 2));
801  settings->SetInteger(kMeasurementSystem, system);
802}
803
804void PrintPreviewHandler::HandleGetInitialSettings(const ListValue* /*args*/) {
805  scoped_refptr<PrintSystemTaskProxy> task =
806      new PrintSystemTaskProxy(AsWeakPtr(),
807                                print_backend_.get(),
808                                has_logged_printers_count_);
809  BrowserThread::PostTask(
810      BrowserThread::FILE, FROM_HERE,
811      base::Bind(&PrintSystemTaskProxy::GetDefaultPrinter, task.get()));
812  SendCloudPrintEnabled();
813}
814
815void PrintPreviewHandler::HandleReportUiEvent(const ListValue* args) {
816  int event_group, event_number;
817  if (!args->GetInteger(0, &event_group) || !args->GetInteger(1, &event_number))
818    return;
819
820  enum UiBucketGroups ui_bucket_group =
821      static_cast<enum UiBucketGroups>(event_group);
822  if (ui_bucket_group >= UI_BUCKET_GROUP_BOUNDARY)
823    return;
824
825  switch (ui_bucket_group) {
826    case DESTINATION_SEARCH: {
827      enum PrintDestinationBuckets event =
828          static_cast<enum PrintDestinationBuckets>(event_number);
829      if (event >= PRINT_DESTINATION_BUCKET_BOUNDARY)
830        return;
831      ReportPrintDestinationHistogram(event);
832      break;
833    }
834    case GCP_PROMO: {
835      enum GcpPromoBuckets event =
836          static_cast<enum GcpPromoBuckets>(event_number);
837      if (event >= GCP_PROMO_BUCKET_BOUNDARY)
838        return;
839      ReportGcpPromoHistogram(event);
840      break;
841    }
842    default:
843      break;
844  }
845}
846
847void PrintPreviewHandler::HandleForceOpenNewTab(const ListValue* args) {
848  std::string url;
849  if (!args->GetString(0, &url))
850    return;
851  Browser* browser = chrome::FindBrowserWithWebContents(GetInitiatorTab());
852  if (!browser)
853    return;
854  chrome::AddSelectedTabWithURL(browser,
855                                GURL(url),
856                                content::PAGE_TRANSITION_LINK);
857}
858
859void PrintPreviewHandler::SendInitialSettings(
860    const std::string& default_printer,
861    const std::string& cloud_print_data) {
862  PrintPreviewUI* print_preview_ui = static_cast<PrintPreviewUI*>(
863      web_ui()->GetController());
864
865  base::DictionaryValue initial_settings;
866  initial_settings.SetString(kInitiatorTabTitle,
867                             print_preview_ui->initiator_tab_title());
868  initial_settings.SetBoolean(printing::kSettingPreviewModifiable,
869                              print_preview_ui->source_is_modifiable());
870  initial_settings.SetString(printing::kSettingPrinterName, default_printer);
871  initial_settings.SetBoolean(kDocumentHasSelection,
872                              print_preview_ui->source_has_selection());
873  initial_settings.SetBoolean(printing::kSettingShouldPrintSelectionOnly,
874                              print_preview_ui->print_selection_only());
875  printing::StickySettings* sticky_settings = GetStickySettings();
876  sticky_settings->RestoreFromPrefs(Profile::FromBrowserContext(
877      preview_web_contents()->GetBrowserContext())->GetPrefs());
878  if (sticky_settings->printer_app_state())
879    initial_settings.SetString(kAppState,
880                               *sticky_settings->printer_app_state());
881
882  CommandLine* cmdline = CommandLine::ForCurrentProcess();
883  initial_settings.SetBoolean(kPrintAutomaticallyInKioskMode,
884                              cmdline->HasSwitch(switches::kKioskModePrinting));
885
886  if (print_preview_ui->source_is_modifiable())
887    GetNumberFormatAndMeasurementSystem(&initial_settings);
888  web_ui()->CallJavascriptFunction("setInitialSettings", initial_settings);
889}
890
891void PrintPreviewHandler::ClosePreviewDialog() {
892  PrintPreviewUI* print_preview_ui =
893      static_cast<PrintPreviewUI*>(web_ui()->GetController());
894  print_preview_ui->OnClosePrintPreviewDialog();
895}
896
897void PrintPreviewHandler::SendAccessToken(const std::string& type,
898                                          const std::string& access_token) {
899  VLOG(1) << "Get getAccessToken finished";
900  web_ui()->CallJavascriptFunction("onDidGetAccessToken", StringValue(type),
901                                   StringValue(access_token));
902}
903
904void PrintPreviewHandler::SendPrinterCapabilities(
905    const DictionaryValue& settings_info) {
906  VLOG(1) << "Get printer capabilities finished";
907  web_ui()->CallJavascriptFunction("updateWithPrinterCapabilities",
908                                   settings_info);
909}
910
911void PrintPreviewHandler::SendFailedToGetPrinterCapabilities(
912    const std::string& printer_name) {
913  VLOG(1) << "Get printer capabilities failed";
914  base::StringValue printer_name_value(printer_name);
915  web_ui()->CallJavascriptFunction("failedToGetPrinterCapabilities",
916                                   printer_name_value);
917}
918
919void PrintPreviewHandler::SetupPrinterList(const ListValue& printers) {
920  web_ui()->CallJavascriptFunction("setPrinters", printers);
921}
922
923void PrintPreviewHandler::SendCloudPrintEnabled() {
924  Profile* profile = Profile::FromBrowserContext(
925      preview_web_contents()->GetBrowserContext());
926  PrefService* prefs = profile->GetPrefs();
927  if (prefs->GetBoolean(prefs::kCloudPrintSubmitEnabled)) {
928    GURL gcp_url(CloudPrintURL(profile).GetCloudPrintServiceURL());
929    base::StringValue gcp_url_value(gcp_url.spec());
930    web_ui()->CallJavascriptFunction("setUseCloudPrint", gcp_url_value);
931  }
932}
933
934void PrintPreviewHandler::SendCloudPrintJob(const base::RefCountedBytes* data) {
935  // BASE64 encode the job data.
936  std::string raw_data(reinterpret_cast<const char*>(data->front()),
937                       data->size());
938  std::string base64_data;
939  if (!base::Base64Encode(raw_data, &base64_data)) {
940    NOTREACHED() << "Base64 encoding PDF data.";
941  }
942  StringValue data_value(base64_data);
943
944  web_ui()->CallJavascriptFunction("printToCloud", data_value);
945}
946
947WebContents* PrintPreviewHandler::GetInitiatorTab() const {
948  printing::PrintPreviewDialogController* dialog_controller =
949      printing::PrintPreviewDialogController::GetInstance();
950  if (!dialog_controller)
951    return NULL;
952  return dialog_controller->GetInitiatorTab(preview_web_contents());
953}
954
955void PrintPreviewHandler::OnPrintDialogShown() {
956  ClosePreviewDialog();
957}
958
959void PrintPreviewHandler::SelectFile(const base::FilePath& default_filename) {
960  ui::SelectFileDialog::FileTypeInfo file_type_info;
961  file_type_info.extensions.resize(1);
962  file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("pdf"));
963  file_type_info.support_drive = true;
964
965  // Initializing save_path_ if it is not already initialized.
966  printing::StickySettings* sticky_settings = GetStickySettings();
967  if (!sticky_settings->save_path()) {
968    // Allowing IO operation temporarily. It is ok to do so here because
969    // the select file dialog performs IO anyway in order to display the
970    // folders and also it is modal.
971    base::ThreadRestrictions::ScopedAllowIO allow_io;
972    base::FilePath file_path;
973    PathService::Get(chrome::DIR_USER_DOCUMENTS, &file_path);
974    sticky_settings->StoreSavePath(file_path);
975    sticky_settings->SaveInPrefs(Profile::FromBrowserContext(
976        preview_web_contents()->GetBrowserContext())->GetPrefs());
977  }
978
979  select_file_dialog_ = ui::SelectFileDialog::Create(
980      this, new ChromeSelectFilePolicy(preview_web_contents())),
981  select_file_dialog_->SelectFile(
982      ui::SelectFileDialog::SELECT_SAVEAS_FILE,
983      string16(),
984      sticky_settings->save_path()->Append(default_filename),
985      &file_type_info,
986      0,
987      base::FilePath::StringType(),
988      platform_util::GetTopLevel(
989          preview_web_contents()->GetView()->GetNativeView()),
990      NULL);
991}
992
993void PrintPreviewHandler::OnPrintPreviewDialogDestroyed() {
994  WebContents* initiator_tab = GetInitiatorTab();
995  if (!initiator_tab)
996    return;
997
998  printing::PrintViewManager* print_view_manager =
999      printing::PrintViewManager::FromWebContents(initiator_tab);
1000  print_view_manager->set_observer(NULL);
1001}
1002
1003void PrintPreviewHandler::OnPrintPreviewFailed() {
1004  if (reported_failed_preview_)
1005    return;
1006  reported_failed_preview_ = true;
1007  ReportUserActionHistogram(PREVIEW_FAILED);
1008}
1009
1010void PrintPreviewHandler::ShowSystemDialog() {
1011  HandleShowSystemDialog(NULL);
1012}
1013
1014void PrintPreviewHandler::FileSelected(const base::FilePath& path,
1015                                       int index, void* params) {
1016  // Updating |save_path_| to the newly selected folder.
1017  printing::StickySettings* sticky_settings = GetStickySettings();
1018  sticky_settings->StoreSavePath(path.DirName());
1019  sticky_settings->SaveInPrefs(Profile::FromBrowserContext(
1020      preview_web_contents()->GetBrowserContext())->GetPrefs());
1021  web_ui()->CallJavascriptFunction("fileSelectionCompleted");
1022  print_to_pdf_path_.reset(new base::FilePath(path));
1023  PostPrintToPdfTask();
1024}
1025
1026void PrintPreviewHandler::PostPrintToPdfTask() {
1027  scoped_refptr<base::RefCountedBytes> data;
1028  string16 title;
1029  if (!GetPreviewDataAndTitle(&data, &title)) {
1030    NOTREACHED() << "Preview data was checked before file dialog.";
1031    return;
1032  }
1033  printing::PreviewMetafile* metafile = new printing::PreviewMetafile;
1034  metafile->InitFromData(static_cast<const void*>(data->front()), data->size());
1035  // PrintToPdfCallback takes ownership of |metafile|.
1036#if defined(OS_CHROMEOS)
1037  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1038  drive::util::PrepareWritableFileAndRun(
1039      Profile::FromBrowserContext(preview_web_contents()->GetBrowserContext()),
1040      *print_to_pdf_path_,
1041      base::Bind(&PrintToPdfCallbackWithCheck, metafile));
1042#else
1043  BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
1044                          base::Bind(&PrintToPdfCallback, metafile,
1045                                     *print_to_pdf_path_));
1046#endif
1047
1048  print_to_pdf_path_.reset();
1049  ClosePreviewDialog();
1050}
1051
1052void PrintPreviewHandler::FileSelectionCanceled(void* params) {
1053  PrintPreviewUI* print_preview_ui = static_cast<PrintPreviewUI*>(
1054      web_ui()->GetController());
1055  print_preview_ui->OnFileSelectionCancelled();
1056}
1057
1058void PrintPreviewHandler::ClearInitiatorTabDetails() {
1059  WebContents* initiator_tab = GetInitiatorTab();
1060  if (!initiator_tab)
1061    return;
1062
1063  // We no longer require the initiator tab details. Remove those details
1064  // associated with the preview dialog to allow the initiator tab to create
1065  // another preview dialog.
1066  printing::PrintPreviewDialogController* dialog_controller =
1067      printing::PrintPreviewDialogController::GetInstance();
1068  if (dialog_controller)
1069    dialog_controller->EraseInitiatorTabInfo(preview_web_contents());
1070}
1071
1072bool PrintPreviewHandler::GetPreviewDataAndTitle(
1073    scoped_refptr<base::RefCountedBytes>* data,
1074    string16* title) const {
1075  PrintPreviewUI* print_preview_ui = static_cast<PrintPreviewUI*>(
1076      web_ui()->GetController());
1077  scoped_refptr<base::RefCountedBytes> tmp_data;
1078  print_preview_ui->GetPrintPreviewDataForIndex(
1079      printing::COMPLETE_PREVIEW_DOCUMENT_INDEX, &tmp_data);
1080
1081  if (!tmp_data.get()) {
1082    // Nothing to print, no preview available.
1083    return false;
1084  }
1085  DCHECK(tmp_data->size() && tmp_data->front());
1086
1087  *data = tmp_data;
1088  *title = print_preview_ui->initiator_tab_title();
1089  return true;
1090}
1091
1092