Home | History | Annotate | Download | only in print_preview
      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/source/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 
     75 using content::BrowserThread;
     76 using content::NavigationEntry;
     77 using content::OpenURLParams;
     78 using content::RenderViewHost;
     79 using content::Referrer;
     80 using content::WebContents;
     81 using printing::Metafile;
     82 
     83 namespace {
     84 
     85 // The cloud print OAuth2 scope.
     86 const char kCloudPrintAuth[] = "https://www.googleapis.com/auth/cloudprint";
     87 
     88 enum 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_CRASHED,  // UNUSED
     96   INITIATOR_CLOSED,
     97   PRINT_WITH_CLOUD_PRINT,
     98   USERACTION_BUCKET_BOUNDARY
     99 };
    100 
    101 enum 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 
    116 enum UiBucketGroups {
    117   DESTINATION_SEARCH,
    118   GCP_PROMO,
    119   UI_BUCKET_GROUP_BOUNDARY
    120 };
    121 
    122 enum 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 
    131 enum GcpPromoBuckets {
    132   PROMO_SHOWN,
    133   PROMO_CLOSED,
    134   PROMO_CLICKED,
    135   GCP_PROMO_BUCKET_BOUNDARY
    136 };
    137 
    138 void ReportUserActionHistogram(enum UserActionBuckets event) {
    139   UMA_HISTOGRAM_ENUMERATION("PrintPreview.UserAction", event,
    140                             USERACTION_BUCKET_BOUNDARY);
    141 }
    142 
    143 void ReportPrintSettingHistogram(enum PrintSettingsBuckets setting) {
    144   UMA_HISTOGRAM_ENUMERATION("PrintPreview.PrintSettings", setting,
    145                             PRINT_SETTINGS_BUCKET_BOUNDARY);
    146 }
    147 
    148 void ReportPrintDestinationHistogram(enum PrintDestinationBuckets event) {
    149   UMA_HISTOGRAM_ENUMERATION("PrintPreview.DestinationAction", event,
    150                             PRINT_DESTINATION_BUCKET_BOUNDARY);
    151 }
    152 
    153 void 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;
    159 const char kAppState[] = "appState";
    160 // Name of a dictionary field holding the initiator title.
    161 const char kInitiatorTitle[] = "initiatorTitle";
    162 // Name of a dictionary field holding the measurement system according to the
    163 // locale.
    164 const char kMeasurementSystem[] = "measurementSystem";
    165 // Name of a dictionary field holding the number format according to the locale.
    166 const char kNumberFormat[] = "numberFormat";
    167 // Name of a dictionary field specifying whether to print automatically in
    168 // kiosk mode. See http://crbug.com/31395.
    169 const char kPrintAutomaticallyInKioskMode[] = "printAutomaticallyInKioskMode";
    170 // Name of a dictionary field holding the state of selection for document.
    171 const 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.
    176 DictionaryValue* 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.
    202 void 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.
    243 void 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)
    252 void 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 
    267 static base::LazyInstance<printing::StickySettings> sticky_settings =
    268     LAZY_INSTANCE_INITIALIZER;
    269 
    270 }  // namespace
    271 
    272 class 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
    338 printing::StickySettings* PrintPreviewHandler::GetStickySettings() {
    339   return sticky_settings.Pointer();
    340 }
    341 
    342 PrintPreviewHandler::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 
    352 PrintPreviewHandler::~PrintPreviewHandler() {
    353   if (select_file_dialog_.get())
    354     select_file_dialog_->ListenerDestroyed();
    355 }
    356 
    357 void 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 
    411 WebContents* PrintPreviewHandler::preview_web_contents() const {
    412   return web_ui()->GetWebContents();
    413 }
    414 
    415 void 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 
    427 void 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 = GetInitiator();
    449   if (!initiator) {
    450     ReportUserActionHistogram(INITIATOR_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->GetTitle());
    465     std::string url;
    466     NavigationEntry* entry = initiator->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->GetRenderViewHost();
    495   rvh->Send(new PrintMsg_PrintPreview(rvh->GetRoutingID(), *settings));
    496 }
    497 
    498 void 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 = GetInitiator();
    507   if (initiator) {
    508     RenderViewHost* rvh = initiator->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 as well, so do not clear the
    560     // association with the initiator 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 can open a new print preview dialog, while the
    566     // current print preview dialog is still handling its print job.
    567     ClearInitiatorDetails();
    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) {
    587       printing::PrintViewManager* print_view_manager =
    588           printing::PrintViewManager::FromWebContents(initiator);
    589       print_view_manager->PrintPreviewDone();
    590     }
    591   }
    592 }
    593 
    594 void 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_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 
    622 void PrintPreviewHandler::HandleHidePreview(const ListValue* /*args*/) {
    623   PrintPreviewUI* print_preview_ui = static_cast<PrintPreviewUI*>(
    624       web_ui()->GetController());
    625   print_preview_ui->OnHidePreviewDialog();
    626 }
    627 
    628 void PrintPreviewHandler::HandleCancelPendingPrintRequest(
    629     const ListValue* /*args*/) {
    630   WebContents* initiator = GetInitiator();
    631   if (initiator)
    632     ClearInitiatorDetails();
    633   gfx::NativeWindow parent = initiator ?
    634       initiator->GetView()->GetTopLevelNativeWindow() :
    635       NULL;
    636   chrome::ShowPrintErrorDialog(parent);
    637 }
    638 
    639 void 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 
    648 void 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
    666 void 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 
    676 void 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 
    685 void 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 
    694 void 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 (at) 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 
    723 void 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 
    736 void PrintPreviewHandler::HandleShowSystemDialog(const ListValue* /*args*/) {
    737   ReportStats();
    738   ReportUserActionHistogram(FALLBACK_TO_ADVANCED_SETTINGS_DIALOG);
    739 
    740   WebContents* initiator = GetInitiator();
    741   if (!initiator)
    742     return;
    743 
    744   printing::PrintViewManager* print_view_manager =
    745       printing::PrintViewManager::FromWebContents(initiator);
    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 
    755 void PrintPreviewHandler::HandleManagePrinters(const ListValue* /*args*/) {
    756   ++manage_printers_dialog_request_count_;
    757   printing::PrinterManagerDialog::ShowPrinterManagerDialog();
    758 }
    759 
    760 void 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 
    771 void 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 
    781 void 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 
    788 void 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 
    804 void 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 
    815 void 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 
    847 void PrintPreviewHandler::HandleForceOpenNewTab(const ListValue* args) {
    848   std::string url;
    849   if (!args->GetString(0, &url))
    850     return;
    851   Browser* browser = chrome::FindBrowserWithWebContents(GetInitiator());
    852   if (!browser)
    853     return;
    854   chrome::AddSelectedTabWithURL(browser,
    855                                 GURL(url),
    856                                 content::PAGE_TRANSITION_LINK);
    857 }
    858 
    859 void 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(kInitiatorTitle,
    867                              print_preview_ui->initiator_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 
    891 void PrintPreviewHandler::ClosePreviewDialog() {
    892   PrintPreviewUI* print_preview_ui =
    893       static_cast<PrintPreviewUI*>(web_ui()->GetController());
    894   print_preview_ui->OnClosePrintPreviewDialog();
    895 }
    896 
    897 void 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 
    904 void PrintPreviewHandler::SendPrinterCapabilities(
    905     const DictionaryValue& settings_info) {
    906   VLOG(1) << "Get printer capabilities finished";
    907   web_ui()->CallJavascriptFunction("updateWithPrinterCapabilities",
    908                                    settings_info);
    909 }
    910 
    911 void 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 
    919 void PrintPreviewHandler::SetupPrinterList(const ListValue& printers) {
    920   web_ui()->CallJavascriptFunction("setPrinters", printers);
    921 }
    922 
    923 void 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 
    934 void 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 
    947 WebContents* PrintPreviewHandler::GetInitiator() const {
    948   printing::PrintPreviewDialogController* dialog_controller =
    949       printing::PrintPreviewDialogController::GetInstance();
    950   if (!dialog_controller)
    951     return NULL;
    952   return dialog_controller->GetInitiator(preview_web_contents());
    953 }
    954 
    955 void PrintPreviewHandler::OnPrintDialogShown() {
    956   ClosePreviewDialog();
    957 }
    958 
    959 void 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 
    993 void PrintPreviewHandler::OnPrintPreviewDialogDestroyed() {
    994   WebContents* initiator = GetInitiator();
    995   if (!initiator)
    996     return;
    997 
    998   printing::PrintViewManager* print_view_manager =
    999       printing::PrintViewManager::FromWebContents(initiator);
   1000   print_view_manager->set_observer(NULL);
   1001 }
   1002 
   1003 void PrintPreviewHandler::OnPrintPreviewFailed() {
   1004   if (reported_failed_preview_)
   1005     return;
   1006   reported_failed_preview_ = true;
   1007   ReportUserActionHistogram(PREVIEW_FAILED);
   1008 }
   1009 
   1010 void PrintPreviewHandler::ShowSystemDialog() {
   1011   HandleShowSystemDialog(NULL);
   1012 }
   1013 
   1014 void 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 
   1026 void 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 
   1052 void PrintPreviewHandler::FileSelectionCanceled(void* params) {
   1053   PrintPreviewUI* print_preview_ui = static_cast<PrintPreviewUI*>(
   1054       web_ui()->GetController());
   1055   print_preview_ui->OnFileSelectionCancelled();
   1056 }
   1057 
   1058 void PrintPreviewHandler::ClearInitiatorDetails() {
   1059   WebContents* initiator = GetInitiator();
   1060   if (!initiator)
   1061     return;
   1062 
   1063   // We no longer require the initiator details. Remove those details associated
   1064   // with the preview dialog to allow the initiator to create another preview
   1065   // dialog.
   1066   printing::PrintPreviewDialogController* dialog_controller =
   1067       printing::PrintPreviewDialogController::GetInstance();
   1068   if (dialog_controller)
   1069     dialog_controller->EraseInitiatorInfo(preview_web_contents());
   1070 }
   1071 
   1072 bool 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_title();
   1089   return true;
   1090 }
   1091 
   1092