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 // TODO(rltoscano): Move data/* into print_preview.data namespace
      6 
      7 var localStrings = new LocalStrings(templateData);
      8 
      9 <include src="component.js"/>
     10 
     11 cr.define('print_preview', function() {
     12   'use strict';
     13 
     14   /**
     15    * Container class for Chromium's print preview.
     16    * @constructor
     17    * @extends {print_preview.Component}
     18    */
     19   function PrintPreview() {
     20     print_preview.Component.call(this);
     21 
     22     /**
     23      * Used to communicate with Chromium's print system.
     24      * @type {!print_preview.NativeLayer}
     25      * @private
     26      */
     27     this.nativeLayer_ = new print_preview.NativeLayer();
     28 
     29     /**
     30      * Event target that contains information about the logged in user.
     31      * @type {!print_preview.UserInfo}
     32      * @private
     33      */
     34     this.userInfo_ = new print_preview.UserInfo();
     35 
     36     /**
     37      * Metrics object used to report usage statistics.
     38      * @type {!print_preview.Metrics}
     39      * @private
     40      */
     41     this.metrics_ = new print_preview.Metrics();
     42 
     43     /**
     44      * Application state.
     45      * @type {!print_preview.AppState}
     46      * @private
     47      */
     48     this.appState_ = new print_preview.AppState();
     49 
     50     /**
     51      * Data model that holds information about the document to print.
     52      * @type {!print_preview.DocumentInfo}
     53      * @private
     54      */
     55     this.documentInfo_ = new print_preview.DocumentInfo();
     56 
     57     /**
     58      * Data store which holds print destinations.
     59      * @type {!print_preview.DestinationStore}
     60      * @private
     61      */
     62     this.destinationStore_ = new print_preview.DestinationStore(
     63         this.nativeLayer_, this.userInfo_, this.appState_, this.metrics_);
     64 
     65     /**
     66      * Storage of the print ticket used to create the print job.
     67      * @type {!print_preview.PrintTicketStore}
     68      * @private
     69      */
     70     this.printTicketStore_ = new print_preview.PrintTicketStore(
     71         this.destinationStore_, this.appState_, this.documentInfo_);
     72 
     73     /**
     74      * Holds the print and cancel buttons and renders some document statistics.
     75      * @type {!print_preview.PrintHeader}
     76      * @private
     77      */
     78     this.printHeader_ = new print_preview.PrintHeader(
     79         this.printTicketStore_, this.destinationStore_);
     80     this.addChild(this.printHeader_);
     81 
     82     /**
     83      * Component used to search for print destinations.
     84      * @type {!print_preview.DestinationSearch}
     85      * @private
     86      */
     87     this.destinationSearch_ = new print_preview.DestinationSearch(
     88         this.destinationStore_, this.userInfo_, this.metrics_);
     89     this.addChild(this.destinationSearch_);
     90 
     91     /**
     92      * Component that renders the print destination.
     93      * @type {!print_preview.DestinationSettings}
     94      * @private
     95      */
     96     this.destinationSettings_ = new print_preview.DestinationSettings(
     97         this.destinationStore_);
     98     this.addChild(this.destinationSettings_);
     99 
    100     /**
    101      * Component that renders UI for entering in page range.
    102      * @type {!print_preview.PageSettings}
    103      * @private
    104      */
    105     this.pageSettings_ = new print_preview.PageSettings(
    106         this.printTicketStore_.pageRange);
    107     this.addChild(this.pageSettings_);
    108 
    109     /**
    110      * Component that renders the copies settings.
    111      * @type {!print_preview.CopiesSettings}
    112      * @private
    113      */
    114     this.copiesSettings_ = new print_preview.CopiesSettings(
    115         this.printTicketStore_.copies, this.printTicketStore_.collate);
    116     this.addChild(this.copiesSettings_);
    117 
    118     /**
    119      * Component that renders the media size settings.
    120      * @type {!print_preview.MediaSizeSettings}
    121      * @private
    122      */
    123     this.mediaSizeSettings_ =
    124         new print_preview.MediaSizeSettings(this.printTicketStore_.mediaSize);
    125     this.addChild(this.mediaSizeSettings_);
    126 
    127     /**
    128      * Component that renders the layout settings.
    129      * @type {!print_preview.LayoutSettings}
    130      * @private
    131      */
    132     this.layoutSettings_ =
    133         new print_preview.LayoutSettings(this.printTicketStore_.landscape);
    134     this.addChild(this.layoutSettings_);
    135 
    136     /**
    137      * Component that renders the color options.
    138      * @type {!print_preview.ColorSettings}
    139      * @private
    140      */
    141     this.colorSettings_ =
    142         new print_preview.ColorSettings(this.printTicketStore_.color);
    143     this.addChild(this.colorSettings_);
    144 
    145     /**
    146      * Component that renders a select box for choosing margin settings.
    147      * @type {!print_preview.MarginSettings}
    148      * @private
    149      */
    150     this.marginSettings_ =
    151         new print_preview.MarginSettings(this.printTicketStore_.marginsType);
    152     this.addChild(this.marginSettings_);
    153 
    154     /**
    155      * Component that renders miscellaneous print options.
    156      * @type {!print_preview.OtherOptionsSettings}
    157      * @private
    158      */
    159     this.otherOptionsSettings_ = new print_preview.OtherOptionsSettings(
    160         this.printTicketStore_.duplex,
    161         this.printTicketStore_.fitToPage,
    162         this.printTicketStore_.cssBackground,
    163         this.printTicketStore_.selectionOnly,
    164         this.printTicketStore_.headerFooter);
    165     this.addChild(this.otherOptionsSettings_);
    166 
    167     /**
    168      * Area of the UI that holds the print preview.
    169      * @type {!print_preview.PreviewArea}
    170      * @private
    171      */
    172     this.previewArea_ = new print_preview.PreviewArea(this.destinationStore_,
    173                                                       this.printTicketStore_,
    174                                                       this.nativeLayer_,
    175                                                       this.documentInfo_);
    176     this.addChild(this.previewArea_);
    177 
    178     /**
    179      * Interface to the Google Cloud Print API. Null if Google Cloud Print
    180      * integration is disabled.
    181      * @type {cloudprint.CloudPrintInterface}
    182      * @private
    183      */
    184     this.cloudPrintInterface_ = null;
    185 
    186     /**
    187      * Whether in kiosk mode where print preview can print automatically without
    188      * user intervention. See http://crbug.com/31395. Print will start when
    189      * both the print ticket has been initialized, and an initial printer has
    190      * been selected.
    191      * @type {boolean}
    192      * @private
    193      */
    194     this.isInKioskAutoPrintMode_ = false;
    195 
    196     /**
    197      * State of the print preview UI.
    198      * @type {print_preview.PrintPreview.UiState_}
    199      * @private
    200      */
    201     this.uiState_ = PrintPreview.UiState_.INITIALIZING;
    202 
    203     /**
    204      * Whether document preview generation is in progress.
    205      * @type {boolean}
    206      * @private
    207      */
    208     this.isPreviewGenerationInProgress_ = true;
    209   };
    210 
    211   /**
    212    * States of the print preview.
    213    * @enum {string}
    214    * @private
    215    */
    216   PrintPreview.UiState_ = {
    217     INITIALIZING: 'initializing',
    218     READY: 'ready',
    219     OPENING_PDF_PREVIEW: 'opening-pdf-preview',
    220     OPENING_CLOUD_PRINT_DIALOG: 'opening-cloud-print-dialog',
    221     OPENING_NATIVE_PRINT_DIALOG: 'opening-native-print-dialog',
    222     PRINTING: 'printing',
    223     FILE_SELECTION: 'file-selection',
    224     CLOSING: 'closing',
    225     ERROR: 'error'
    226   };
    227 
    228   /**
    229    * What can happen when print preview tries to print.
    230    * @enum {string}
    231    * @private
    232    */
    233   PrintPreview.PrintAttemptResult_ = {
    234     NOT_READY: 'not-ready',
    235     PRINTED: 'printed',
    236     READY_WAITING_FOR_PREVIEW: 'ready-waiting-for-preview'
    237   };
    238 
    239   PrintPreview.prototype = {
    240     __proto__: print_preview.Component.prototype,
    241 
    242     /** Sets up the page and print preview by getting the printer list. */
    243     initialize: function() {
    244       this.decorate($('print-preview'));
    245       i18nTemplate.process(document, templateData);
    246       if (!this.previewArea_.hasCompatiblePlugin) {
    247         this.setIsEnabled_(false);
    248       }
    249       this.nativeLayer_.startGetInitialSettings();
    250       cr.ui.FocusOutlineManager.forDocument(document);
    251     },
    252 
    253     /** @override */
    254     enterDocument: function() {
    255       // Native layer events.
    256       this.tracker.add(
    257           this.nativeLayer_,
    258           print_preview.NativeLayer.EventType.INITIAL_SETTINGS_SET,
    259           this.onInitialSettingsSet_.bind(this));
    260       this.tracker.add(
    261           this.nativeLayer_,
    262           print_preview.NativeLayer.EventType.CLOUD_PRINT_ENABLE,
    263           this.onCloudPrintEnable_.bind(this));
    264       this.tracker.add(
    265           this.nativeLayer_,
    266           print_preview.NativeLayer.EventType.PRINT_TO_CLOUD,
    267           this.onPrintToCloud_.bind(this));
    268       this.tracker.add(
    269           this.nativeLayer_,
    270           print_preview.NativeLayer.EventType.FILE_SELECTION_CANCEL,
    271           this.onFileSelectionCancel_.bind(this));
    272       this.tracker.add(
    273           this.nativeLayer_,
    274           print_preview.NativeLayer.EventType.FILE_SELECTION_COMPLETE,
    275           this.onFileSelectionComplete_.bind(this));
    276       this.tracker.add(
    277           this.nativeLayer_,
    278           print_preview.NativeLayer.EventType.SETTINGS_INVALID,
    279           this.onSettingsInvalid_.bind(this));
    280       this.tracker.add(
    281           this.nativeLayer_,
    282           print_preview.NativeLayer.EventType.DISABLE_SCALING,
    283           this.onDisableScaling_.bind(this));
    284       this.tracker.add(
    285           this.nativeLayer_,
    286           print_preview.NativeLayer.EventType.PRIVET_PRINT_FAILED,
    287           this.onPrivetPrintFailed_.bind(this));
    288 
    289 
    290       this.tracker.add(
    291           $('system-dialog-link'),
    292           'click',
    293           this.openSystemPrintDialog_.bind(this));
    294       this.tracker.add(
    295           $('cloud-print-dialog-link'),
    296           'click',
    297           this.onCloudPrintDialogLinkClick_.bind(this));
    298       this.tracker.add(
    299           $('open-pdf-in-preview-link'),
    300           'click',
    301           this.onOpenPdfInPreviewLinkClick_.bind(this));
    302 
    303       this.tracker.add(
    304           this.previewArea_,
    305           print_preview.PreviewArea.EventType.PREVIEW_GENERATION_IN_PROGRESS,
    306           this.onPreviewGenerationInProgress_.bind(this));
    307       this.tracker.add(
    308           this.previewArea_,
    309           print_preview.PreviewArea.EventType.PREVIEW_GENERATION_DONE,
    310           this.onPreviewGenerationDone_.bind(this));
    311       this.tracker.add(
    312           this.previewArea_,
    313           print_preview.PreviewArea.EventType.PREVIEW_GENERATION_FAIL,
    314           this.onPreviewGenerationFail_.bind(this));
    315       this.tracker.add(
    316           this.previewArea_,
    317           print_preview.PreviewArea.EventType.OPEN_SYSTEM_DIALOG_CLICK,
    318           this.openSystemPrintDialog_.bind(this));
    319 
    320       this.tracker.add(
    321           this.destinationStore_,
    322           print_preview.DestinationStore.EventType.
    323               SELECTED_DESTINATION_CAPABILITIES_READY,
    324           this.printIfReady_.bind(this));
    325       this.tracker.add(
    326           this.destinationStore_,
    327           print_preview.DestinationStore.EventType.DESTINATION_SELECT,
    328           this.onDestinationSelect_.bind(this));
    329       this.tracker.add(
    330           this.destinationStore_,
    331           print_preview.DestinationStore.EventType.DESTINATION_SEARCH_DONE,
    332           this.onDestinationSearchDone_.bind(this));
    333 
    334       this.tracker.add(
    335           this.printHeader_,
    336           print_preview.PrintHeader.EventType.PRINT_BUTTON_CLICK,
    337           this.onPrintButtonClick_.bind(this));
    338       this.tracker.add(
    339           this.printHeader_,
    340           print_preview.PrintHeader.EventType.CANCEL_BUTTON_CLICK,
    341           this.onCancelButtonClick_.bind(this));
    342 
    343       this.tracker.add(window, 'keydown', this.onKeyDown_.bind(this));
    344 
    345       this.tracker.add(
    346           this.destinationSettings_,
    347           print_preview.DestinationSettings.EventType.CHANGE_BUTTON_ACTIVATE,
    348           this.onDestinationChangeButtonActivate_.bind(this));
    349 
    350       this.tracker.add(
    351           this.destinationSearch_,
    352           print_preview.DestinationSearch.EventType.MANAGE_CLOUD_DESTINATIONS,
    353           this.onManageCloudDestinationsActivated_.bind(this));
    354       this.tracker.add(
    355           this.destinationSearch_,
    356           print_preview.DestinationSearch.EventType.MANAGE_LOCAL_DESTINATIONS,
    357           this.onManageLocalDestinationsActivated_.bind(this));
    358       this.tracker.add(
    359           this.destinationSearch_,
    360           print_preview.DestinationSearch.EventType.ADD_ACCOUNT,
    361           this.onCloudPrintSignInActivated_.bind(this, true /*addAccount*/));
    362       this.tracker.add(
    363           this.destinationSearch_,
    364           print_preview.DestinationSearch.EventType.SIGN_IN,
    365           this.onCloudPrintSignInActivated_.bind(this, false /*addAccount*/));
    366       this.tracker.add(
    367           this.destinationSearch_,
    368           print_preview.DestinationListItem.EventType.REGISTER_PROMO_CLICKED,
    369           this.onCloudPrintRegisterPromoClick_.bind(this));
    370 
    371       // TODO(rltoscano): Move no-destinations-promo into its own component
    372       // instead being part of PrintPreview.
    373       this.tracker.add(
    374           this.getChildElement('#no-destinations-promo .close-button'),
    375           'click',
    376           this.onNoDestinationsPromoClose_.bind(this));
    377       this.tracker.add(
    378           this.getChildElement('#no-destinations-promo .not-now-button'),
    379           'click',
    380           this.onNoDestinationsPromoClose_.bind(this));
    381       this.tracker.add(
    382           this.getChildElement('#no-destinations-promo .add-printer-button'),
    383           'click',
    384           this.onNoDestinationsPromoClick_.bind(this));
    385     },
    386 
    387     /** @override */
    388     decorateInternal: function() {
    389       this.printHeader_.decorate($('print-header'));
    390       this.destinationSearch_.decorate($('destination-search'));
    391       this.destinationSettings_.decorate($('destination-settings'));
    392       this.pageSettings_.decorate($('page-settings'));
    393       this.copiesSettings_.decorate($('copies-settings'));
    394       this.mediaSizeSettings_.decorate($('media-size-settings'));
    395       this.layoutSettings_.decorate($('layout-settings'));
    396       this.colorSettings_.decorate($('color-settings'));
    397       this.marginSettings_.decorate($('margin-settings'));
    398       this.otherOptionsSettings_.decorate($('other-options-settings'));
    399       this.previewArea_.decorate($('preview-area'));
    400 
    401       setIsVisible($('open-pdf-in-preview-link'), cr.isMac);
    402     },
    403 
    404     /**
    405      * Sets whether the controls in the print preview are enabled.
    406      * @param {boolean} isEnabled Whether the controls in the print preview are
    407      *     enabled.
    408      * @private
    409      */
    410     setIsEnabled_: function(isEnabled) {
    411       $('system-dialog-link').disabled = !isEnabled;
    412       $('cloud-print-dialog-link').disabled = !isEnabled;
    413       $('open-pdf-in-preview-link').disabled = !isEnabled;
    414       this.printHeader_.isEnabled = isEnabled;
    415       this.destinationSettings_.isEnabled = isEnabled;
    416       this.pageSettings_.isEnabled = isEnabled;
    417       this.copiesSettings_.isEnabled = isEnabled;
    418       this.mediaSizeSettings_.isEnabled = isEnabled;
    419       this.layoutSettings_.isEnabled = isEnabled;
    420       this.colorSettings_.isEnabled = isEnabled;
    421       this.marginSettings_.isEnabled = isEnabled;
    422       this.otherOptionsSettings_.isEnabled = isEnabled;
    423     },
    424 
    425     /**
    426      * Prints the document or launches a pdf preview on the local system.
    427      * @param {boolean} isPdfPreview Whether to launch the pdf preview.
    428      * @private
    429      */
    430     printDocumentOrOpenPdfPreview_: function(isPdfPreview) {
    431       assert(this.uiState_ == PrintPreview.UiState_.READY,
    432              'Print document request received when not in ready state: ' +
    433                  this.uiState_);
    434       if (isPdfPreview) {
    435         this.uiState_ = PrintPreview.UiState_.OPENING_PDF_PREVIEW;
    436       } else if (this.destinationStore_.selectedDestination.id ==
    437           print_preview.Destination.GooglePromotedId.SAVE_AS_PDF) {
    438         this.uiState_ = PrintPreview.UiState_.FILE_SELECTION;
    439       } else {
    440         this.uiState_ = PrintPreview.UiState_.PRINTING;
    441       }
    442       this.setIsEnabled_(false);
    443       this.printHeader_.isCancelButtonEnabled = true;
    444       var printAttemptResult = this.printIfReady_();
    445       if (printAttemptResult == PrintPreview.PrintAttemptResult_.PRINTED ||
    446           printAttemptResult ==
    447               PrintPreview.PrintAttemptResult_.READY_WAITING_FOR_PREVIEW) {
    448         if ((this.destinationStore_.selectedDestination.isLocal &&
    449              !this.destinationStore_.selectedDestination.isPrivet &&
    450              this.destinationStore_.selectedDestination.id !=
    451                  print_preview.Destination.GooglePromotedId.SAVE_AS_PDF) ||
    452              this.uiState_ == PrintPreview.UiState_.OPENING_PDF_PREVIEW) {
    453           // Hide the dialog for now. The actual print command will be issued
    454           // when the preview generation is done.
    455           this.nativeLayer_.startHideDialog();
    456         }
    457       }
    458     },
    459 
    460     /**
    461      * Attempts to print if needed and if ready.
    462      * @return {PrintPreview.PrintAttemptResult_} Attempt result.
    463      * @private
    464      */
    465     printIfReady_: function() {
    466       var okToPrint =
    467           (this.uiState_ == PrintPreview.UiState_.PRINTING ||
    468            this.uiState_ == PrintPreview.UiState_.OPENING_PDF_PREVIEW ||
    469            this.uiState_ == PrintPreview.UiState_.FILE_SELECTION ||
    470            this.uiState_ == PrintPreview.UiState_.OPENING_CLOUD_PRINT_DIALOG ||
    471            this.isInKioskAutoPrintMode_) &&
    472           this.destinationStore_.selectedDestination &&
    473           this.destinationStore_.selectedDestination.capabilities;
    474       if (!okToPrint) {
    475         return PrintPreview.PrintAttemptResult_.NOT_READY;
    476       }
    477       if (this.isPreviewGenerationInProgress_) {
    478         return PrintPreview.PrintAttemptResult_.READY_WAITING_FOR_PREVIEW;
    479       }
    480       assert(this.printTicketStore_.isTicketValid(),
    481           'Trying to print with invalid ticket');
    482       if (this.uiState_ == PrintPreview.UiState_.OPENING_CLOUD_PRINT_DIALOG) {
    483         this.nativeLayer_.startShowCloudPrintDialog(
    484             this.printTicketStore_.pageRange.getPageNumberSet().size);
    485       } else {
    486         this.nativeLayer_.startPrint(
    487             this.destinationStore_.selectedDestination,
    488             this.printTicketStore_,
    489             this.cloudPrintInterface_,
    490             this.documentInfo_,
    491             this.uiState_ == PrintPreview.UiState_.OPENING_PDF_PREVIEW);
    492       }
    493       return PrintPreview.PrintAttemptResult_.PRINTED;
    494     },
    495 
    496     /**
    497      * Closes the print preview.
    498      * @private
    499      */
    500     close_: function() {
    501       this.exitDocument();
    502       this.uiState_ = PrintPreview.UiState_.CLOSING;
    503       this.nativeLayer_.startCloseDialog();
    504     },
    505 
    506     /**
    507      * Opens the native system print dialog after disabling all controls.
    508      * @private
    509      */
    510     openSystemPrintDialog_: function() {
    511       setIsVisible($('system-dialog-throbber'), true);
    512       this.setIsEnabled_(false);
    513       this.uiState_ = PrintPreview.UiState_.OPENING_NATIVE_PRINT_DIALOG;
    514       this.nativeLayer_.startShowSystemDialog();
    515     },
    516 
    517     /**
    518      * Called when the native layer has initial settings to set. Sets the
    519      * initial settings of the print preview and begins fetching print
    520      * destinations.
    521      * @param {Event} event Contains the initial print preview settings
    522      *     persisted through the session.
    523      * @private
    524      */
    525     onInitialSettingsSet_: function(event) {
    526       assert(this.uiState_ == PrintPreview.UiState_.INITIALIZING,
    527              'Updating initial settings when not in initializing state: ' +
    528                  this.uiState_);
    529       this.uiState_ = PrintPreview.UiState_.READY;
    530 
    531       var settings = event.initialSettings;
    532       this.isInKioskAutoPrintMode_ = settings.isInKioskAutoPrintMode;
    533 
    534       // The following components must be initialized in this order.
    535       this.appState_.init(
    536           settings.serializedAppStateStr,
    537           settings.systemDefaultDestinationId);
    538       this.documentInfo_.init(
    539           settings.isDocumentModifiable,
    540           settings.documentTitle,
    541           settings.documentHasSelection);
    542       this.printTicketStore_.init(
    543           settings.thousandsDelimeter,
    544           settings.decimalDelimeter,
    545           settings.unitType,
    546           settings.selectionOnly);
    547       this.destinationStore_.init();
    548       this.appState_.setInitialized();
    549 
    550       $('document-title').innerText = settings.documentTitle;
    551       setIsVisible($('system-dialog-link'),
    552                    !settings.hidePrintWithSystemDialogLink);
    553     },
    554 
    555     /**
    556      * Calls when the native layer enables Google Cloud Print integration.
    557      * Fetches the user's cloud printers.
    558      * @param {Event} event Contains the base URL of the Google Cloud Print
    559      *     service.
    560      * @private
    561      */
    562     onCloudPrintEnable_: function(event) {
    563       this.cloudPrintInterface_ = new cloudprint.CloudPrintInterface(
    564           event.baseCloudPrintUrl,
    565           this.nativeLayer_,
    566           this.userInfo_);
    567       this.tracker.add(
    568           this.cloudPrintInterface_,
    569           cloudprint.CloudPrintInterface.EventType.SUBMIT_DONE,
    570           this.onCloudPrintSubmitDone_.bind(this));
    571       this.tracker.add(
    572           this.cloudPrintInterface_,
    573           cloudprint.CloudPrintInterface.EventType.SEARCH_FAILED,
    574           this.onCloudPrintError_.bind(this));
    575       this.tracker.add(
    576           this.cloudPrintInterface_,
    577           cloudprint.CloudPrintInterface.EventType.SUBMIT_FAILED,
    578           this.onCloudPrintError_.bind(this));
    579       this.tracker.add(
    580           this.cloudPrintInterface_,
    581           cloudprint.CloudPrintInterface.EventType.PRINTER_FAILED,
    582           this.onCloudPrintError_.bind(this));
    583       this.tracker.add(
    584           this.cloudPrintInterface_,
    585           cloudprint.CloudPrintInterface.EventType.
    586               UPDATE_PRINTER_TOS_ACCEPTANCE_FAILED,
    587           this.onCloudPrintError_.bind(this));
    588 
    589       this.destinationStore_.setCloudPrintInterface(this.cloudPrintInterface_);
    590       if (this.destinationSearch_.getIsVisible()) {
    591         this.destinationStore_.startLoadCloudDestinations();
    592       }
    593     },
    594 
    595     /**
    596      * Called from the native layer when ready to print to Google Cloud Print.
    597      * @param {Event} event Contains the body to send in the HTTP request.
    598      * @private
    599      */
    600     onPrintToCloud_: function(event) {
    601       assert(this.uiState_ == PrintPreview.UiState_.PRINTING,
    602              'Document ready to be sent to the cloud when not in printing ' +
    603                  'state: ' + this.uiState_);
    604       assert(this.cloudPrintInterface_ != null,
    605              'Google Cloud Print is not enabled');
    606       this.cloudPrintInterface_.submit(
    607           this.destinationStore_.selectedDestination,
    608           this.printTicketStore_,
    609           this.documentInfo_,
    610           event.data);
    611     },
    612 
    613     /**
    614      * Called from the native layer when the user cancels the save-to-pdf file
    615      * selection dialog.
    616      * @private
    617      */
    618     onFileSelectionCancel_: function() {
    619       assert(this.uiState_ == PrintPreview.UiState_.FILE_SELECTION,
    620              'File selection cancelled when not in file-selection state: ' +
    621                  this.uiState_);
    622       this.setIsEnabled_(true);
    623       this.uiState_ = PrintPreview.UiState_.READY;
    624     },
    625 
    626     /**
    627      * Called from the native layer when save-to-pdf file selection is complete.
    628      * @private
    629      */
    630     onFileSelectionComplete_: function() {
    631       assert(this.uiState_ == PrintPreview.UiState_.FILE_SELECTION,
    632              'File selection completed when not in file-selection state: ' +
    633                  this.uiState_);
    634       this.previewArea_.showCustomMessage(
    635           localStrings.getString('printingToPDFInProgress'));
    636       this.uiState_ = PrintPreview.UiState_.PRINTING;
    637     },
    638 
    639     /**
    640      * Called after successfully submitting a job to Google Cloud Print.
    641      * @param {!Event} event Contains the ID of the submitted print job.
    642      * @private
    643      */
    644     onCloudPrintSubmitDone_: function(event) {
    645       assert(this.uiState_ == PrintPreview.UiState_.PRINTING,
    646              'Submited job to Google Cloud Print but not in printing state ' +
    647                  this.uiState_);
    648       if (this.destinationStore_.selectedDestination.id ==
    649               print_preview.Destination.GooglePromotedId.FEDEX) {
    650         this.nativeLayer_.startForceOpenNewTab(
    651             'https://www.google.com/cloudprint/fedexcode.html?jobid=' +
    652             event.jobId);
    653       }
    654       this.close_();
    655     },
    656 
    657     /**
    658      * Called when there was an error communicating with Google Cloud print.
    659      * Displays an error message in the print header.
    660      * @param {!Event} event Contains the error message.
    661      * @private
    662      */
    663     onCloudPrintError_: function(event) {
    664       if (event.status == 403) {
    665         this.destinationSearch_.showCloudPrintPromo();
    666       } else if (event.status == 0) {
    667         return; // Ignore, the system does not have internet connectivity.
    668       } else {
    669         this.printHeader_.setErrorMessage(event.message);
    670       }
    671       if (event.status == 200) {
    672         console.error('Google Cloud Print Error: (' + event.errorCode + ') ' +
    673                       event.message);
    674       } else {
    675         console.error('Google Cloud Print Error: HTTP status ' + event.status);
    676       }
    677     },
    678 
    679     /**
    680      * Called when the preview area's preview generation is in progress.
    681      * @private
    682      */
    683     onPreviewGenerationInProgress_: function() {
    684       this.isPreviewGenerationInProgress_ = true;
    685     },
    686 
    687     /**
    688      * Called when the preview area's preview generation is complete.
    689      * @private
    690      */
    691     onPreviewGenerationDone_: function() {
    692       this.isPreviewGenerationInProgress_ = false;
    693       this.printHeader_.isPrintButtonEnabled = true;
    694       this.printIfReady_();
    695     },
    696 
    697     /**
    698      * Called when the preview area's preview failed to load.
    699      * @private
    700      */
    701     onPreviewGenerationFail_: function() {
    702       this.isPreviewGenerationInProgress_ = false;
    703       this.printHeader_.isPrintButtonEnabled = false;
    704       if (this.uiState_ == PrintPreview.UiState_.PRINTING) {
    705         this.nativeLayer_.startCancelPendingPrint();
    706       } else if (this.uiState_ ==
    707             PrintPreview.UiState_.OPENING_CLOUD_PRINT_DIALOG) {
    708         this.uiState_ = PrintPreview.UiState_.READY;
    709       }
    710     },
    711 
    712     /**
    713      * Called when the 'Open pdf in preview' link is clicked. Launches the pdf
    714      * preview app.
    715      * @private
    716      */
    717     onOpenPdfInPreviewLinkClick_: function() {
    718       assert(this.uiState_ == PrintPreview.UiState_.READY,
    719              'Trying to open pdf in preview when not in ready state: ' +
    720                  this.uiState_);
    721       setIsVisible($('open-preview-app-throbber'), true);
    722       this.previewArea_.showCustomMessage(
    723           localStrings.getString('openingPDFInPreview'));
    724       this.printDocumentOrOpenPdfPreview_(true /*isPdfPreview*/);
    725     },
    726 
    727     /**
    728      * Called when the print header's print button is clicked. Prints the
    729      * document.
    730      * @private
    731      */
    732     onPrintButtonClick_: function() {
    733       assert(this.uiState_ == PrintPreview.UiState_.READY,
    734              'Trying to print when not in ready state: ' + this.uiState_);
    735       this.printDocumentOrOpenPdfPreview_(false /*isPdfPreview*/);
    736     },
    737 
    738     /**
    739      * Called when the print header's cancel button is clicked. Closes the
    740      * print dialog.
    741      * @private
    742      */
    743     onCancelButtonClick_: function() {
    744       this.close_();
    745     },
    746 
    747     /**
    748      * Called when the register promo for Cloud Print is clicked.
    749      * @private
    750      */
    751      onCloudPrintRegisterPromoClick_: function(e) {
    752        this.metrics_.incrementDestinationSearchBucket(
    753          print_preview.Metrics.DestinationSearchBucket.REGISTER_PROMO_SELECTED);
    754        var devicesUrl = 'chrome://devices/register?id=' + e.destination.id;
    755        this.nativeLayer_.startForceOpenNewTab(devicesUrl);
    756        this.destinationStore_.waitForRegister(e.destination.id);
    757      },
    758 
    759     /**
    760      * Consume escape key presses and ctrl + shift + p. Delegate everything else
    761      * to the preview area.
    762      * @param {KeyboardEvent} e The keyboard event.
    763      * @private
    764      */
    765     onKeyDown_: function(e) {
    766       // Escape key closes the dialog.
    767       if (e.keyCode == 27 && !e.shiftKey && !e.ctrlKey && !e.altKey &&
    768           !e.metaKey) {
    769         if (this.destinationSearch_.getIsVisible()) {
    770           this.destinationSearch_.setIsVisible(false);
    771           this.metrics_.incrementDestinationSearchBucket(
    772               print_preview.Metrics.DestinationSearchBucket.CANCELED);
    773         } else {
    774           <if expr="toolkit_views">
    775           // On the toolkit_views environment, ESC key is handled by C++-side
    776           // instead of JS-side.
    777           return;
    778           </if>
    779           <if expr="not toolkit_views">
    780           this.close_();
    781           </if>
    782         }
    783         e.preventDefault();
    784         return;
    785       }
    786 
    787       // Ctrl + Shift + p / Mac equivalent.
    788       if (e.keyCode == 80) {
    789         if ((cr.isMac && e.metaKey && e.altKey && !e.shiftKey && !e.ctrlKey) ||
    790             (!cr.isMac && e.shiftKey && e.ctrlKey && !e.altKey && !e.metaKey)) {
    791           this.openSystemPrintDialog_();
    792           e.preventDefault();
    793           return;
    794         }
    795       }
    796 
    797       if (e.keyCode == 13 /*enter*/ &&
    798           !this.destinationSearch_.getIsVisible() &&
    799           this.destinationStore_.selectedDestination &&
    800           this.printTicketStore_.isTicketValid()) {
    801         assert(this.uiState_ == PrintPreview.UiState_.READY,
    802             'Trying to print when not in ready state: ' + this.uiState_);
    803         var activeElementTag = document.activeElement ?
    804             document.activeElement.tagName.toUpperCase() : '';
    805         if (activeElementTag != 'BUTTON' && activeElementTag != 'SELECT') {
    806           this.printDocumentOrOpenPdfPreview_(false /*isPdfPreview*/);
    807           e.preventDefault();
    808         }
    809         return;
    810       }
    811 
    812       // Pass certain directional keyboard events to the PDF viewer.
    813       this.previewArea_.handleDirectionalKeyEvent(e);
    814     },
    815 
    816     /**
    817      * Called when native layer receives invalid settings for a print request.
    818      * @private
    819      */
    820     onSettingsInvalid_: function() {
    821       this.uiState_ = PrintPreview.UiState_.ERROR;
    822       console.error('Invalid settings error reported from native layer');
    823       this.previewArea_.showCustomMessage(
    824           localStrings.getString('invalidPrinterSettings'));
    825     },
    826 
    827     /**
    828      * Called when the destination settings' change button is activated.
    829      * Displays the destination search component.
    830      * @private
    831      */
    832     onDestinationChangeButtonActivate_: function() {
    833       this.destinationSearch_.setIsVisible(true);
    834       this.destinationStore_.startLoadCloudDestinations();
    835       this.destinationStore_.startLoadLocalDestinations();
    836       this.destinationStore_.startLoadPrivetDestinations();
    837       this.metrics_.incrementDestinationSearchBucket(
    838           print_preview.Metrics.DestinationSearchBucket.SHOWN);
    839     },
    840 
    841     /**
    842      * Called when the destination search dispatches manage cloud destinations
    843      * event. Calls corresponding native layer method.
    844      * @private
    845      */
    846     onManageCloudDestinationsActivated_: function() {
    847       this.nativeLayer_.startManageCloudDestinations();
    848     },
    849 
    850     /**
    851      * Called when the destination search dispatches manage local destinations
    852      * event. Calls corresponding native layer method.
    853      * @private
    854      */
    855     onManageLocalDestinationsActivated_: function() {
    856       this.nativeLayer_.startManageLocalDestinations();
    857     },
    858 
    859     /**
    860      * Called when the user wants to sign in to Google Cloud Print. Calls the
    861      * corresponding native layer event.
    862      * @param {boolean} addAccount Whether to open an 'add a new account' or
    863      *     default sign in page.
    864      * @private
    865      */
    866     onCloudPrintSignInActivated_: function(addAccount) {
    867       this.nativeLayer_.startCloudPrintSignIn(addAccount);
    868     },
    869 
    870     /**
    871      * Called when the native layer dispatches a DISABLE_SCALING event. Resets
    872      * fit-to-page selection and updates document info.
    873      * @private
    874      */
    875     onDisableScaling_: function() {
    876       this.printTicketStore_.fitToPage.updateValue(null);
    877       this.documentInfo_.updateIsScalingDisabled(true);
    878     },
    879 
    880     /**
    881      * Called when privet printing fails.
    882      * @param {Event} event Event object representing the failure.
    883      * @private
    884      */
    885     onPrivetPrintFailed_: function(event) {
    886       console.error('Privet printing failed with error code ' +
    887                     event.httpError);
    888       this.printHeader_.setErrorMessage(
    889         localStrings.getString('couldNotPrint'));
    890     },
    891 
    892     /**
    893      * Called when the open-cloud-print-dialog link is clicked. Opens the Google
    894      * Cloud Print web dialog.
    895      * @private
    896      */
    897     onCloudPrintDialogLinkClick_: function() {
    898       assert(this.uiState_ == PrintPreview.UiState_.READY,
    899              'Opening Google Cloud Print dialog when not in ready state: ' +
    900                  this.uiState_);
    901       setIsVisible($('cloud-print-dialog-throbber'), true);
    902       this.setIsEnabled_(false);
    903       this.uiState_ = PrintPreview.UiState_.OPENING_CLOUD_PRINT_DIALOG;
    904       this.printIfReady_();
    905     },
    906 
    907     /**
    908      * Called when a print destination is selected. Shows/hides the "Print with
    909      * Cloud Print" link in the navbar.
    910      * @private
    911      */
    912     onDestinationSelect_: function() {
    913       var selectedDest = this.destinationStore_.selectedDestination;
    914       setIsVisible(
    915           $('cloud-print-dialog-link'),
    916           selectedDest && !cr.isChromeOS && !selectedDest.isLocal);
    917       if (selectedDest && this.isInKioskAutoPrintMode_) {
    918         this.onPrintButtonClick_();
    919       }
    920     },
    921 
    922     /**
    923      * Called when the destination store loads a group of destinations. Shows
    924      * a promo on Chrome OS if the user has no print destinations promoting
    925      * Google Cloud Print.
    926      * @private
    927      */
    928     onDestinationSearchDone_: function() {
    929       var isPromoVisible = cr.isChromeOS &&
    930           this.cloudPrintInterface_ &&
    931           this.userInfo_.activeUser &&
    932           !this.appState_.isGcpPromoDismissed &&
    933           !this.destinationStore_.isLocalDestinationsSearchInProgress &&
    934           !this.destinationStore_.isCloudDestinationsSearchInProgress &&
    935           this.destinationStore_.hasOnlyDefaultCloudDestinations();
    936       setIsVisible(this.getChildElement('#no-destinations-promo'),
    937                    isPromoVisible);
    938       if (isPromoVisible) {
    939         this.metrics_.incrementGcpPromoBucket(
    940             print_preview.Metrics.GcpPromoBucket.SHOWN);
    941       }
    942     },
    943 
    944     /**
    945      * Called when the close button on the no-destinations-promotion is clicked.
    946      * Hides the promotion.
    947      * @private
    948      */
    949     onNoDestinationsPromoClose_: function() {
    950       this.metrics_.incrementGcpPromoBucket(
    951           print_preview.Metrics.GcpPromoBucket.DISMISSED);
    952       setIsVisible(this.getChildElement('#no-destinations-promo'), false);
    953       this.appState_.persistIsGcpPromoDismissed(true);
    954     },
    955 
    956     /**
    957      * Called when the no-destinations promotion link is clicked. Opens the
    958      * Google Cloud Print management page and closes the print preview.
    959      * @private
    960      */
    961     onNoDestinationsPromoClick_: function() {
    962       this.metrics_.incrementGcpPromoBucket(
    963           print_preview.Metrics.GcpPromoBucket.CLICKED);
    964       this.appState_.persistIsGcpPromoDismissed(true);
    965       window.open(this.cloudPrintInterface_.baseUrl + '?user=' +
    966                   this.userInfo_.activeUser + '#printers');
    967       this.close_();
    968     }
    969   };
    970 
    971   // Export
    972   return {
    973     PrintPreview: PrintPreview
    974   };
    975 });
    976 
    977 // Pull in all other scripts in a single shot.
    978 <include src="data/page_number_set.js"/>
    979 <include src="data/destination.js"/>
    980 <include src="data/local_parsers.js"/>
    981 <include src="data/cloud_parsers.js"/>
    982 <include src="data/destination_store.js"/>
    983 <include src="data/margins.js"/>
    984 <include src="data/document_info.js"/>
    985 <include src="data/printable_area.js"/>
    986 <include src="data/measurement_system.js"/>
    987 <include src="data/print_ticket_store.js"/>
    988 <include src="data/coordinate2d.js"/>
    989 <include src="data/size.js"/>
    990 <include src="data/capabilities_holder.js"/>
    991 <include src="data/user_info.js"/>
    992 <include src="data/app_state.js"/>
    993 
    994 <include src="data/ticket_items/ticket_item.js"/>
    995 
    996 <include src="data/ticket_items/custom_margins.js"/>
    997 <include src="data/ticket_items/collate.js"/>
    998 <include src="data/ticket_items/color.js"/>
    999 <include src="data/ticket_items/copies.js"/>
   1000 <include src="data/ticket_items/duplex.js"/>
   1001 <include src="data/ticket_items/header_footer.js"/>
   1002 <include src="data/ticket_items/media_size.js"/>
   1003 <include src="data/ticket_items/landscape.js"/>
   1004 <include src="data/ticket_items/margins_type.js"/>
   1005 <include src="data/ticket_items/page_range.js"/>
   1006 <include src="data/ticket_items/fit_to_page.js"/>
   1007 <include src="data/ticket_items/css_background.js"/>
   1008 <include src="data/ticket_items/selection_only.js"/>
   1009 
   1010 <include src="native_layer.js"/>
   1011 <include src="print_preview_animations.js"/>
   1012 <include src="cloud_print_interface.js"/>
   1013 <include src="print_preview_utils.js"/>
   1014 <include src="print_header.js"/>
   1015 <include src="metrics.js"/>
   1016 
   1017 <include src="settings/page_settings.js"/>
   1018 <include src="settings/copies_settings.js"/>
   1019 <include src="settings/media_size_settings.js"/>
   1020 <include src="settings/layout_settings.js"/>
   1021 <include src="settings/color_settings.js"/>
   1022 <include src="settings/margin_settings.js"/>
   1023 <include src="settings/destination_settings.js"/>
   1024 <include src="settings/other_options_settings.js"/>
   1025 
   1026 <include src="previewarea/margin_control.js"/>
   1027 <include src="previewarea/margin_control_container.js"/>
   1028 <include src="../pdf/pdf_scripting_api.js" />
   1029 <include src="previewarea/preview_area.js"/>
   1030 <include src="preview_generator.js"/>
   1031 
   1032 <include src="search/destination_list.js"/>
   1033 <include src="search/cloud_destination_list.js"/>
   1034 <include src="search/recent_destination_list.js"/>
   1035 <include src="search/destination_list_item.js"/>
   1036 <include src="search/destination_search.js"/>
   1037 <include src="search/search_box.js"/>
   1038 <include src="search/fedex_tos.js"/>
   1039 
   1040 window.addEventListener('DOMContentLoaded', function() {
   1041   printPreview = new print_preview.PrintPreview();
   1042   printPreview.initialize();
   1043 });
   1044