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