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