Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / print_preview / previewarea / preview_area.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 cr.define('print_preview', function() {
6   'use strict';
7
8   /**
9    * Creates a PreviewArea object. It represents the area where the preview
10    * document is displayed.
11    * @param {!print_preview.DestinationStore} destinationStore Used to get the
12    *     currently selected destination.
13    * @param {!print_preview.PrintTicketStore} printTicketStore Used to get
14    *     information about how the preview should be displayed.
15    * @param {!print_preview.NativeLayer} nativeLayer Needed to communicate with
16    *     Chromium's preview generation system.
17    * @param {!print_preview.DocumentInfo} documentInfo Document data model.
18    * @constructor
19    * @extends {print_preview.Component}
20    */
21   function PreviewArea(
22       destinationStore, printTicketStore, nativeLayer, documentInfo) {
23     print_preview.Component.call(this);
24     // TODO(rltoscano): Understand the dependencies of printTicketStore needed
25     // here, and add only those here (not the entire print ticket store).
26
27     /**
28      * Used to get the currently selected destination.
29      * @type {!print_preview.DestinationStore}
30      * @private
31      */
32     this.destinationStore_ = destinationStore;
33
34     /**
35      * Used to get information about how the preview should be displayed.
36      * @type {!print_preview.PrintTicketStore}
37      * @private
38      */
39     this.printTicketStore_ = printTicketStore;
40
41     /**
42      * Used to contruct the preview generator.
43      * @type {!print_preview.NativeLayer}
44      * @private
45      */
46     this.nativeLayer_ = nativeLayer;
47
48     /**
49      * Document data model.
50      * @type {!print_preview.DocumentInfo}
51      * @private
52      */
53     this.documentInfo_ = documentInfo;
54
55     /**
56      * Used to read generated page previews.
57      * @type {print_preview.PreviewGenerator}
58      * @private
59      */
60     this.previewGenerator_ = null;
61
62     /**
63      * The embedded pdf plugin object. It's value is null if not yet loaded.
64      * @type {HTMLEmbedElement}
65      * @private
66      */
67     this.plugin_ = null;
68
69     /**
70      * Custom margins component superimposed on the preview plugin.
71      * @type {!print_preview.MarginControlContainer}
72      * @private
73      */
74     this.marginControlContainer_ = new print_preview.MarginControlContainer(
75         this.documentInfo_,
76         this.printTicketStore_.marginsType,
77         this.printTicketStore_.customMargins,
78         this.printTicketStore_.measurementSystem);
79     this.addChild(this.marginControlContainer_);
80
81     /**
82      * Current zoom level as a percentage.
83      * @type {?number}
84      * @private
85      */
86     this.zoomLevel_ = null;
87
88     /**
89      * Current page offset which can be used to calculate scroll amount.
90      * @type {print_preview.Coordinate2d}
91      * @private
92      */
93     this.pageOffset_ = null;
94
95     /**
96      * Whether the plugin has finished reloading.
97      * @type {boolean}
98      * @private
99      */
100     this.isPluginReloaded_ = false;
101
102     /**
103      * Whether the document preview is ready.
104      * @type {boolean}
105      * @private
106      */
107     this.isDocumentReady_ = false;
108
109     /**
110      * Timeout object used to display a loading message if the preview is taking
111      * a long time to generate.
112      * @type {?number}
113      * @private
114      */
115     this.loadingTimeout_ = null;
116
117     /**
118      * Overlay element.
119      * @type {HTMLElement}
120      * @private
121      */
122     this.overlayEl_ = null;
123
124     /**
125      * The "Open system dialog" button.
126      * @type {HTMLButtonElement}
127      * @private
128      */
129     this.openSystemDialogButton_ = null;
130   };
131
132   /**
133    * Event types dispatched by the preview area.
134    * @enum {string}
135    */
136   PreviewArea.EventType = {
137     // Dispatched when the "Open system dialog" button is clicked.
138     OPEN_SYSTEM_DIALOG_CLICK:
139         'print_preview.PreviewArea.OPEN_SYSTEM_DIALOG_CLICK',
140
141     // Dispatched when the document preview is complete.
142     PREVIEW_GENERATION_DONE:
143         'print_preview.PreviewArea.PREVIEW_GENERATION_DONE',
144
145     // Dispatched when the document preview failed to be generated.
146     PREVIEW_GENERATION_FAIL:
147         'print_preview.PreviewArea.PREVIEW_GENERATION_FAIL',
148
149     // Dispatched when a new document preview is being generated.
150     PREVIEW_GENERATION_IN_PROGRESS:
151         'print_preview.PreviewArea.PREVIEW_GENERATION_IN_PROGRESS'
152   };
153
154   /**
155    * CSS classes used by the preview area.
156    * @enum {string}
157    * @private
158    */
159   PreviewArea.Classes_ = {
160     COMPATIBILITY_OBJECT: 'preview-area-compatibility-object',
161     OUT_OF_PROCESS_COMPATIBILITY_OBJECT:
162         'preview-area-compatibility-object-out-of-process',
163     CUSTOM_MESSAGE_TEXT: 'preview-area-custom-message-text',
164     MESSAGE: 'preview-area-message',
165     INVISIBLE: 'invisible',
166     OPEN_SYSTEM_DIALOG_BUTTON: 'preview-area-open-system-dialog-button',
167     OPEN_SYSTEM_DIALOG_BUTTON_THROBBER:
168         'preview-area-open-system-dialog-button-throbber',
169     OVERLAY: 'preview-area-overlay-layer'
170   };
171
172   /**
173    * Enumeration of IDs shown in the preview area.
174    * @enum {string}
175    * @private
176    */
177   PreviewArea.MessageId_ = {
178     CUSTOM: 'custom',
179     LOADING: 'loading',
180     PREVIEW_FAILED: 'preview-failed'
181   };
182
183   /**
184    * Enumeration of PDF plugin types for print preview.
185    * @enum {string}
186    * @private
187    */
188   PreviewArea.PluginType_ = {
189     // TODO(raymes): Remove all references to the IN_PROCESS plugin once it is
190     // removed.
191     IN_PROCESS: 'in-process',
192     OUT_OF_PROCESS: 'out-of-process',
193     NONE: 'none'
194   };
195
196   /**
197    * Maps message IDs to the CSS class that contains them.
198    * @type {object.<PreviewArea.MessageId_, string>}
199    * @private
200    */
201   PreviewArea.MessageIdClassMap_ = {};
202   PreviewArea.MessageIdClassMap_[PreviewArea.MessageId_.CUSTOM] =
203       'preview-area-custom-message';
204   PreviewArea.MessageIdClassMap_[PreviewArea.MessageId_.LOADING] =
205       'preview-area-loading-message';
206   PreviewArea.MessageIdClassMap_[PreviewArea.MessageId_.PREVIEW_FAILED] =
207       'preview-area-preview-failed-message';
208
209   /**
210    * Amount of time in milliseconds to wait after issueing a new preview before
211    * the loading message is shown.
212    * @type {number}
213    * @const
214    * @private
215    */
216   PreviewArea.LOADING_TIMEOUT_ = 200;
217
218   PreviewArea.prototype = {
219     __proto__: print_preview.Component.prototype,
220
221     /**
222      * Should only be called after calling this.render().
223      * @return {boolean} Whether the preview area has a compatible plugin to
224      *     display the print preview in.
225      */
226     get hasCompatiblePlugin() {
227       return this.previewGenerator_ != null;
228     },
229
230     /**
231      * Processes a keyboard event that could possibly be used to change state of
232      * the preview plugin.
233      * @param {MouseEvent} e Mouse event to process.
234      */
235     handleDirectionalKeyEvent: function(e) {
236       // Make sure the PDF plugin is there.
237       // We only care about: PageUp, PageDown, Left, Up, Right, Down.
238       // If the user is holding a modifier key, ignore.
239       if (!this.plugin_ ||
240           !arrayContains([33, 34, 37, 38, 39, 40], e.keyCode) ||
241           e.metaKey || e.altKey || e.shiftKey || e.ctrlKey) {
242         return;
243       }
244
245       // Don't handle the key event for these elements.
246       var tagName = document.activeElement.tagName;
247       if (arrayContains(['INPUT', 'SELECT', 'EMBED'], tagName)) {
248         return;
249       }
250
251       // For the most part, if any div of header was the last clicked element,
252       // then the active element is the body. Starting with the last clicked
253       // element, and work up the DOM tree to see if any element has a
254       // scrollbar. If there exists a scrollbar, do not handle the key event
255       // here.
256       var element = e.target;
257       while (element) {
258         if (element.scrollHeight > element.clientHeight ||
259             element.scrollWidth > element.clientWidth) {
260           return;
261         }
262         element = element.parentElement;
263       }
264
265       // No scroll bar anywhere, or the active element is something else, like a
266       // button. Note: buttons have a bigger scrollHeight than clientHeight.
267       this.plugin_.sendKeyEvent(e.keyCode);
268       e.preventDefault();
269     },
270
271     /**
272      * Shows a custom message on the preview area's overlay.
273      * @param {string} message Custom message to show.
274      */
275     showCustomMessage: function(message) {
276       this.showMessage_(PreviewArea.MessageId_.CUSTOM, message);
277     },
278
279     /** @override */
280     enterDocument: function() {
281       print_preview.Component.prototype.enterDocument.call(this);
282       this.tracker.add(
283           this.openSystemDialogButton_,
284           'click',
285           this.onOpenSystemDialogButtonClick_.bind(this));
286
287       this.tracker.add(
288           this.printTicketStore_,
289           print_preview.PrintTicketStore.EventType.INITIALIZE,
290           this.onTicketChange_.bind(this));
291       this.tracker.add(
292           this.printTicketStore_,
293           print_preview.PrintTicketStore.EventType.TICKET_CHANGE,
294           this.onTicketChange_.bind(this));
295       this.tracker.add(
296           this.printTicketStore_,
297           print_preview.PrintTicketStore.EventType.CAPABILITIES_CHANGE,
298           this.onTicketChange_.bind(this));
299       this.tracker.add(
300           this.printTicketStore_,
301           print_preview.PrintTicketStore.EventType.DOCUMENT_CHANGE,
302           this.onTicketChange_.bind(this));
303
304       this.tracker.add(
305           this.printTicketStore_.color,
306           print_preview.ticket_items.TicketItem.EventType.CHANGE,
307           this.onTicketChange_.bind(this));
308       this.tracker.add(
309           this.printTicketStore_.cssBackground,
310           print_preview.ticket_items.TicketItem.EventType.CHANGE,
311           this.onTicketChange_.bind(this));
312       this.tracker.add(
313         this.printTicketStore_.customMargins,
314           print_preview.ticket_items.TicketItem.EventType.CHANGE,
315           this.onTicketChange_.bind(this));
316       this.tracker.add(
317           this.printTicketStore_.fitToPage,
318           print_preview.ticket_items.TicketItem.EventType.CHANGE,
319           this.onTicketChange_.bind(this));
320       this.tracker.add(
321           this.printTicketStore_.headerFooter,
322           print_preview.ticket_items.TicketItem.EventType.CHANGE,
323           this.onTicketChange_.bind(this));
324       this.tracker.add(
325           this.printTicketStore_.landscape,
326           print_preview.ticket_items.TicketItem.EventType.CHANGE,
327           this.onTicketChange_.bind(this));
328       this.tracker.add(
329           this.printTicketStore_.marginsType,
330           print_preview.ticket_items.TicketItem.EventType.CHANGE,
331           this.onTicketChange_.bind(this));
332       this.tracker.add(
333           this.printTicketStore_.pageRange,
334           print_preview.ticket_items.TicketItem.EventType.CHANGE,
335           this.onTicketChange_.bind(this));
336       this.tracker.add(
337           this.printTicketStore_.selectionOnly,
338           print_preview.ticket_items.TicketItem.EventType.CHANGE,
339           this.onTicketChange_.bind(this));
340
341       this.pluginType_ = this.getPluginType_();
342       if (this.pluginType_ != PreviewArea.PluginType_.NONE) {
343         this.previewGenerator_ = new print_preview.PreviewGenerator(
344             this.destinationStore_,
345             this.printTicketStore_,
346             this.nativeLayer_,
347             this.documentInfo_);
348         this.tracker.add(
349             this.previewGenerator_,
350             print_preview.PreviewGenerator.EventType.PREVIEW_START,
351             this.onPreviewStart_.bind(this));
352         this.tracker.add(
353             this.previewGenerator_,
354             print_preview.PreviewGenerator.EventType.PAGE_READY,
355             this.onPagePreviewReady_.bind(this));
356         this.tracker.add(
357             this.previewGenerator_,
358             print_preview.PreviewGenerator.EventType.FAIL,
359             this.onPreviewGenerationFail_.bind(this));
360         this.tracker.add(
361             this.previewGenerator_,
362             print_preview.PreviewGenerator.EventType.DOCUMENT_READY,
363             this.onDocumentReady_.bind(this));
364       } else {
365         this.showCustomMessage(loadTimeData.getString('noPlugin'));
366       }
367     },
368
369     /** @override */
370     exitDocument: function() {
371       print_preview.Component.prototype.exitDocument.call(this);
372       if (this.previewGenerator_) {
373         this.previewGenerator_.removeEventListeners();
374       }
375       this.overlayEl_ = null;
376       this.openSystemDialogButton_ = null;
377     },
378
379     /** @override */
380     decorateInternal: function() {
381       this.marginControlContainer_.decorate(this.getElement());
382       this.overlayEl_ = this.getElement().getElementsByClassName(
383           PreviewArea.Classes_.OVERLAY)[0];
384       this.openSystemDialogButton_ = this.getElement().getElementsByClassName(
385           PreviewArea.Classes_.OPEN_SYSTEM_DIALOG_BUTTON)[0];
386     },
387
388     /**
389      * Checks to see if a suitable plugin for rendering the preview exists. If
390      * one does not exist, then an error message will be displayed.
391      * @return {string} A string constant indicating whether Chromium has a
392      *     plugin for rendering the preview.
393      *     PreviewArea.PluginType_.IN_PROCESS for an in-process plugin
394      *     PreviewArea.PluginType_.OUT_OF_PROCESS for an out-of-process plugin
395      *     PreviewArea.PluginType_.NONE if no plugin is available.
396      * @private
397      */
398     getPluginType_: function() {
399       // TODO(raymes): Remove the in-process check after we remove the
400       // in-process plugin. Change this function back to
401       // checkPluginCompatibility_().
402       var compatObj = this.getElement().getElementsByClassName(
403           PreviewArea.Classes_.COMPATIBILITY_OBJECT)[0];
404       var isCompatible =
405           compatObj.onload &&
406           compatObj.goToPage &&
407           compatObj.removePrintButton &&
408           compatObj.loadPreviewPage &&
409           compatObj.printPreviewPageCount &&
410           compatObj.resetPrintPreviewUrl &&
411           compatObj.onPluginSizeChanged &&
412           compatObj.onScroll &&
413           compatObj.pageXOffset &&
414           compatObj.pageYOffset &&
415           compatObj.setZoomLevel &&
416           compatObj.setPageNumbers &&
417           compatObj.setPageXOffset &&
418           compatObj.setPageYOffset &&
419           compatObj.getHorizontalScrollbarThickness &&
420           compatObj.getVerticalScrollbarThickness &&
421           compatObj.getPageLocationNormalized &&
422           compatObj.getHeight &&
423           compatObj.getWidth;
424       compatObj.parentElement.removeChild(compatObj);
425
426       // TODO(raymes): It's harder to test compatibility of the out of process
427       // plugin because it's asynchronous. We could do a better job at some
428       // point.
429       var oopCompatObj = this.getElement().getElementsByClassName(
430           PreviewArea.Classes_.OUT_OF_PROCESS_COMPATIBILITY_OBJECT)[0];
431       var isOOPCompatible = oopCompatObj.postMessage;
432       oopCompatObj.parentElement.removeChild(oopCompatObj);
433
434       if (isCompatible)
435         return PreviewArea.PluginType_.IN_PROCESS;
436       if (isOOPCompatible)
437         return PreviewArea.PluginType_.OUT_OF_PROCESS;
438       return PreviewArea.PluginType_.NONE;
439     },
440
441     /**
442      * Shows a given message on the overlay.
443      * @param {!print_preview.PreviewArea.MessageId_} messageId ID of the
444      *     message to show.
445      * @param {string=} opt_message Optional message to show that can be used
446      *     by some message IDs.
447      * @private
448      */
449     showMessage_: function(messageId, opt_message) {
450       // Hide all messages.
451       var messageEls = this.getElement().getElementsByClassName(
452           PreviewArea.Classes_.MESSAGE);
453       for (var i = 0, messageEl; messageEl = messageEls[i]; i++) {
454         setIsVisible(messageEl, false);
455       }
456       // Disable jumping animation to conserve cycles.
457       var jumpingDotsEl = this.getElement().querySelector(
458           '.preview-area-loading-message-jumping-dots');
459       jumpingDotsEl.classList.remove('jumping-dots');
460
461       // Show specific message.
462       if (messageId == PreviewArea.MessageId_.CUSTOM) {
463         var customMessageTextEl = this.getElement().getElementsByClassName(
464             PreviewArea.Classes_.CUSTOM_MESSAGE_TEXT)[0];
465         customMessageTextEl.textContent = opt_message;
466       } else if (messageId == PreviewArea.MessageId_.LOADING) {
467         jumpingDotsEl.classList.add('jumping-dots');
468       }
469       var messageEl = this.getElement().getElementsByClassName(
470             PreviewArea.MessageIdClassMap_[messageId])[0];
471       setIsVisible(messageEl, true);
472
473       // Show overlay.
474       this.overlayEl_.classList.remove(PreviewArea.Classes_.INVISIBLE);
475     },
476
477     /**
478      * Hides the message overlay.
479      * @private
480      */
481     hideOverlay_: function() {
482       this.overlayEl_.classList.add(PreviewArea.Classes_.INVISIBLE);
483       // Disable jumping animation to conserve cycles.
484       var jumpingDotsEl = this.getElement().querySelector(
485           '.preview-area-loading-message-jumping-dots');
486       jumpingDotsEl.classList.remove('jumping-dots');
487     },
488
489     /**
490      * Creates a preview plugin and adds it to the DOM.
491      * @param {string} srcUrl Initial URL of the plugin.
492      * @private
493      */
494     createPlugin_: function(srcUrl) {
495       if (this.plugin_) {
496         console.warn('Pdf preview plugin already created');
497         return;
498       }
499
500       if (this.pluginType_ == PreviewArea.PluginType_.IN_PROCESS) {
501         this.plugin_ = document.createElement('embed');
502         this.plugin_.setAttribute(
503             'type', 'application/x-google-chrome-print-preview-pdf');
504         this.plugin_.setAttribute('src', srcUrl);
505       } else {
506         this.plugin_ = PDFCreateOutOfProcessPlugin(srcUrl);
507       }
508
509       this.plugin_.setAttribute('class', 'preview-area-plugin');
510       this.plugin_.setAttribute('aria-live', 'polite');
511       this.plugin_.setAttribute('aria-atomic', 'true');
512       // NOTE: The plugin's 'id' field must be set to 'pdf-viewer' since
513       // chrome/renderer/printing/print_web_view_helper.cc actually references
514       // it.
515       this.plugin_.setAttribute('id', 'pdf-viewer');
516       this.getChildElement('.preview-area-plugin-wrapper').
517           appendChild(this.plugin_);
518
519
520       if (this.pluginType_ == PreviewArea.PluginType_.OUT_OF_PROCESS) {
521         var pageNumbers =
522             this.printTicketStore_.pageRange.getPageNumberSet().asArray();
523         var grayscale = !this.printTicketStore_.color.getValue();
524         this.plugin_.setLoadCallback(this.onPluginLoad_.bind(this));
525         this.plugin_.setViewportChangedCallback(
526             this.onPreviewVisualStateChange_.bind(this));
527         this.plugin_.resetPrintPreviewMode(srcUrl, grayscale, pageNumbers,
528                                            this.documentInfo_.isModifiable);
529       } else {
530         global['onPreviewPluginLoad'] = this.onPluginLoad_.bind(this);
531         this.plugin_.onload('onPreviewPluginLoad()');
532
533         global['onPreviewPluginVisualStateChange'] =
534             this.onPreviewVisualStateChange_.bind(this);
535         this.plugin_.onScroll('onPreviewPluginVisualStateChange()');
536         this.plugin_.onPluginSizeChanged('onPreviewPluginVisualStateChange()');
537
538         this.plugin_.removePrintButton();
539         this.plugin_.grayscale(!this.printTicketStore_.color.getValue());
540       }
541     },
542
543     /**
544      * Dispatches a PREVIEW_GENERATION_DONE event if all conditions are met.
545      * @private
546      */
547     dispatchPreviewGenerationDoneIfReady_: function() {
548       if (this.isDocumentReady_ && this.isPluginReloaded_) {
549         cr.dispatchSimpleEvent(
550             this, PreviewArea.EventType.PREVIEW_GENERATION_DONE);
551         this.marginControlContainer_.showMarginControlsIfNeeded();
552       }
553     },
554
555     /**
556      * Called when the open-system-dialog button is clicked. Disables the
557      * button, shows the throbber, and dispatches the OPEN_SYSTEM_DIALOG_CLICK
558      * event.
559      * @private
560      */
561     onOpenSystemDialogButtonClick_: function() {
562       this.openSystemDialogButton_.disabled = true;
563       var openSystemDialogThrobber = this.getElement().getElementsByClassName(
564           PreviewArea.Classes_.OPEN_SYSTEM_DIALOG_BUTTON_THROBBER)[0];
565       setIsVisible(openSystemDialogThrobber, true);
566       cr.dispatchSimpleEvent(
567           this, PreviewArea.EventType.OPEN_SYSTEM_DIALOG_CLICK);
568     },
569
570     /**
571      * Called when the print ticket changes. Updates the preview.
572      * @private
573      */
574     onTicketChange_: function() {
575       if (this.previewGenerator_ && this.previewGenerator_.requestPreview()) {
576         cr.dispatchSimpleEvent(
577             this, PreviewArea.EventType.PREVIEW_GENERATION_IN_PROGRESS);
578         if (this.loadingTimeout_ == null) {
579           this.loadingTimeout_ = setTimeout(
580               this.showMessage_.bind(this, PreviewArea.MessageId_.LOADING),
581               PreviewArea.LOADING_TIMEOUT_);
582         }
583       } else {
584         this.marginControlContainer_.showMarginControlsIfNeeded();
585       }
586     },
587
588     /**
589      * Called when the preview generator begins loading the preview.
590      * @param {Event} event Contains the URL to initialize the plugin to.
591      * @private
592      */
593     onPreviewStart_: function(event) {
594       this.isDocumentReady_ = false;
595       this.isPluginReloaded_ = false;
596       if (!this.plugin_) {
597         this.createPlugin_(event.previewUrl);
598       } else {
599         if (this.pluginType_ == PreviewArea.PluginType_.OUT_OF_PROCESS) {
600           var grayscale = !this.printTicketStore_.color.getValue();
601           var pageNumbers =
602               this.printTicketStore_.pageRange.getPageNumberSet().asArray();
603           var url = event.previewUrl;
604           this.plugin_.resetPrintPreviewMode(url, grayscale, pageNumbers,
605                                              this.documentInfo_.isModifiable);
606         } else if (this.pluginType_ == PreviewArea.PluginType_.IN_PROCESS) {
607           this.plugin_.goToPage('0');
608           this.plugin_.resetPrintPreviewUrl(event.previewUrl);
609           this.plugin_.reload();
610           this.plugin_.grayscale(!this.printTicketStore_.color.getValue());
611         }
612       }
613       cr.dispatchSimpleEvent(
614           this, PreviewArea.EventType.PREVIEW_GENERATION_IN_PROGRESS);
615     },
616
617     /**
618      * Called when a page preview has been generated. Updates the plugin with
619      * the new page.
620      * @param {Event} event Contains information about the page preview.
621      * @private
622      */
623     onPagePreviewReady_: function(event) {
624       this.plugin_.loadPreviewPage(event.previewUrl, event.previewIndex);
625     },
626
627     /**
628      * Called when the preview generation is complete and the document is ready
629      * to print.
630      * @private
631      */
632     onDocumentReady_: function(event) {
633       this.isDocumentReady_ = true;
634       this.dispatchPreviewGenerationDoneIfReady_();
635     },
636
637     /**
638      * Called when the generation of a preview fails. Shows an error message.
639      * @private
640      */
641     onPreviewGenerationFail_: function() {
642       if (this.loadingTimeout_) {
643         clearTimeout(this.loadingTimeout_);
644         this.loadingTimeout_ = null;
645       }
646       this.showMessage_(PreviewArea.MessageId_.PREVIEW_FAILED);
647       cr.dispatchSimpleEvent(
648           this, PreviewArea.EventType.PREVIEW_GENERATION_FAIL);
649     },
650
651     /**
652      * Called when the plugin loads. This is a consequence of calling
653      * plugin.reload(). Certain plugin state can only be set after the plugin
654      * has loaded.
655      * @private
656      */
657     onPluginLoad_: function() {
658       if (this.loadingTimeout_) {
659         clearTimeout(this.loadingTimeout_);
660         this.loadingTimeout_ = null;
661       }
662
663       if (this.pluginType_ == PreviewArea.PluginType_.IN_PROCESS) {
664         // Setting the plugin's page count can only be called after the plugin
665         // is loaded and the document must be modifiable.
666         if (this.documentInfo_.isModifiable) {
667           this.plugin_.printPreviewPageCount(
668               this.printTicketStore_.pageRange.getPageNumberSet().size);
669         }
670         this.plugin_.setPageNumbers(JSON.stringify(
671             this.printTicketStore_.pageRange.getPageNumberSet().asArray()));
672         if (this.zoomLevel_ != null && this.pageOffset_ != null) {
673           this.plugin_.setZoomLevel(this.zoomLevel_);
674           this.plugin_.setPageXOffset(this.pageOffset_.x);
675           this.plugin_.setPageYOffset(this.pageOffset_.y);
676         } else {
677           this.plugin_.fitToHeight();
678         }
679       }
680       this.hideOverlay_();
681       this.isPluginReloaded_ = true;
682       this.dispatchPreviewGenerationDoneIfReady_();
683     },
684
685     /**
686      * Called when the preview plugin's visual state has changed. This is a
687      * consequence of scrolling or zooming the plugin. Updates the custom
688      * margins component if shown.
689      * @private
690      */
691     onPreviewVisualStateChange_: function(pageX,
692                                           pageY,
693                                           pageWidth,
694                                           viewportWidth,
695                                           viewportHeight) {
696       if (this.pluginType_ == PreviewArea.PluginType_.IN_PROCESS) {
697         if (this.isPluginReloaded_) {
698           this.zoomLevel_ = this.plugin_.getZoomLevel();
699           this.pageOffset_ = new print_preview.Coordinate2d(
700               this.plugin_.pageXOffset(), this.plugin_.pageYOffset());
701         }
702
703         var pageLocationNormalizedStr =
704             this.plugin_.getPageLocationNormalized();
705         if (!pageLocationNormalizedStr) {
706           return;
707         }
708         var normalized = pageLocationNormalizedStr.split(';');
709         var pluginWidth = this.plugin_.getWidth();
710         var pluginHeight = this.plugin_.getHeight();
711         var verticalScrollbarThickness =
712             this.plugin_.getVerticalScrollbarThickness();
713         var horizontalScrollbarThickness =
714             this.plugin_.getHorizontalScrollbarThickness();
715
716         var translationTransform = new print_preview.Coordinate2d(
717             parseFloat(normalized[0]) * pluginWidth,
718             parseFloat(normalized[1]) * pluginHeight);
719         this.marginControlContainer_.updateTranslationTransform(
720             translationTransform);
721         var pageWidthInPixels = parseFloat(normalized[2]) * pluginWidth;
722         this.marginControlContainer_.updateScaleTransform(
723             pageWidthInPixels / this.documentInfo_.pageSize.width);
724         this.marginControlContainer_.updateClippingMask(
725             new print_preview.Size(
726                 pluginWidth - verticalScrollbarThickness,
727                 pluginHeight - horizontalScrollbarThickness));
728       } else if (this.pluginType_ == PreviewArea.PluginType_.OUT_OF_PROCESS) {
729         this.marginControlContainer_.updateTranslationTransform(
730             new print_preview.Coordinate2d(pageX, pageY));
731         this.marginControlContainer_.updateScaleTransform(
732             pageWidth / this.documentInfo_.pageSize.width);
733         this.marginControlContainer_.updateClippingMask(
734             new print_preview.Size(viewportWidth, viewportHeight));
735       }
736     }
737   };
738
739   // Export
740   return {
741     PreviewArea: PreviewArea
742   };
743 });