Switch global error menu icon to vectorized MD asset
[chromium-blink-merge.git] / chrome / browser / resources / print_preview / previewarea / preview_area.js
blob567417fcdc184ccf55fc3c417c89290d2fa66880
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_.distillPage,
354           print_preview.ticket_items.TicketItem.EventType.CHANGE,
355           this.onTicketChange_.bind(this));
356       this.tracker.add(
357           this.printTicketStore_.headerFooter,
358           print_preview.ticket_items.TicketItem.EventType.CHANGE,
359           this.onTicketChange_.bind(this));
360       this.tracker.add(
361           this.printTicketStore_.landscape,
362           print_preview.ticket_items.TicketItem.EventType.CHANGE,
363           this.onTicketChange_.bind(this));
364       this.tracker.add(
365           this.printTicketStore_.marginsType,
366           print_preview.ticket_items.TicketItem.EventType.CHANGE,
367           this.onTicketChange_.bind(this));
368       this.tracker.add(
369           this.printTicketStore_.pageRange,
370           print_preview.ticket_items.TicketItem.EventType.CHANGE,
371           this.onTicketChange_.bind(this));
372       this.tracker.add(
373           this.printTicketStore_.selectionOnly,
374           print_preview.ticket_items.TicketItem.EventType.CHANGE,
375           this.onTicketChange_.bind(this));
377       if (this.checkPluginCompatibility_()) {
378         this.previewGenerator_ = new print_preview.PreviewGenerator(
379             this.destinationStore_,
380             this.printTicketStore_,
381             this.nativeLayer_,
382             this.documentInfo_);
383         this.tracker.add(
384             this.previewGenerator_,
385             print_preview.PreviewGenerator.EventType.PREVIEW_START,
386             this.onPreviewStart_.bind(this));
387         this.tracker.add(
388             this.previewGenerator_,
389             print_preview.PreviewGenerator.EventType.PAGE_READY,
390             this.onPagePreviewReady_.bind(this));
391         this.tracker.add(
392             this.previewGenerator_,
393             print_preview.PreviewGenerator.EventType.FAIL,
394             this.onPreviewGenerationFail_.bind(this));
395         this.tracker.add(
396             this.previewGenerator_,
397             print_preview.PreviewGenerator.EventType.DOCUMENT_READY,
398             this.onDocumentReady_.bind(this));
399       } else {
400         this.showCustomMessage(loadTimeData.getString('noPlugin'));
401       }
402     },
404     /** @override */
405     exitDocument: function() {
406       print_preview.Component.prototype.exitDocument.call(this);
407       if (this.previewGenerator_) {
408         this.previewGenerator_.removeEventListeners();
409       }
410       this.overlayEl_ = null;
411       this.openSystemDialogButton_ = null;
412     },
414     /** @override */
415     decorateInternal: function() {
416       this.marginControlContainer_.decorate(this.getElement());
417       this.overlayEl_ = this.getElement().getElementsByClassName(
418           PreviewArea.Classes_.OVERLAY)[0];
419       this.openSystemDialogButton_ = this.getElement().getElementsByClassName(
420           PreviewArea.Classes_.OPEN_SYSTEM_DIALOG_BUTTON)[0];
421     },
423     /**
424      * Checks to see if a suitable plugin for rendering the preview exists. If
425      * one does not exist, then an error message will be displayed.
426      * @return {boolean} true if Chromium has a plugin for rendering the
427      *     the preview.
428      * @private
429      */
430     checkPluginCompatibility_: function() {
431       // TODO(raymes): It's harder to test compatibility of the out of process
432       // plugin because it's asynchronous. We could do a better job at some
433       // point.
434       var oopCompatObj = this.getElement().getElementsByClassName(
435           PreviewArea.Classes_.OUT_OF_PROCESS_COMPATIBILITY_OBJECT)[0];
436       var isOOPCompatible = oopCompatObj.postMessage;
437       oopCompatObj.parentElement.removeChild(oopCompatObj);
439       return isOOPCompatible;
440     },
442     /**
443      * Shows a given message on the overlay.
444      * @param {!print_preview.PreviewArea.MessageId_} messageId ID of the
445      *     message to show.
446      * @param {string=} opt_message Optional message to show that can be used
447      *     by some message IDs.
448      * @private
449      */
450     showMessage_: function(messageId, opt_message) {
451       // Hide all messages.
452       var messageEls = this.getElement().getElementsByClassName(
453           PreviewArea.Classes_.MESSAGE);
454       for (var i = 0, messageEl; messageEl = messageEls[i]; i++) {
455         setIsVisible(messageEl, false);
456       }
457       // Disable jumping animation to conserve cycles.
458       var jumpingDotsEl = this.getElement().querySelector(
459           '.preview-area-loading-message-jumping-dots');
460       jumpingDotsEl.classList.remove('jumping-dots');
462       // Show specific message.
463       if (messageId == PreviewArea.MessageId_.CUSTOM) {
464         var customMessageTextEl = this.getElement().getElementsByClassName(
465             PreviewArea.Classes_.CUSTOM_MESSAGE_TEXT)[0];
466         customMessageTextEl.textContent = opt_message;
467       } else if (messageId == PreviewArea.MessageId_.LOADING) {
468         jumpingDotsEl.classList.add('jumping-dots');
469       }
470       var messageEl = this.getElement().getElementsByClassName(
471             PreviewArea.MessageIdClassMap_[messageId])[0];
472       setIsVisible(messageEl, true);
474       this.setOverlayVisible_(true);
475     },
477     /**
478      * Set the visibility of the message overlay.
479      * @param {boolean} visible Whether to make the overlay visible or not
480      * @private
481      */
482     setOverlayVisible_: function(visible) {
483       this.overlayEl_.classList.toggle(
484           PreviewArea.Classes_.INVISIBLE,
485           !visible);
486       this.overlayEl_.setAttribute('aria-hidden', !visible);
488       // Hide/show all controls that will overlap when the overlay is visible.
489       var marginControls = this.getElement().getElementsByClassName(
490           PreviewArea.Classes_.MARGIN_CONTROL);
491       for (var i = 0; i < marginControls.length; ++i) {
492         marginControls[i].setAttribute('aria-hidden', visible);
493       }
494       var previewAreaControls = this.getElement().getElementsByClassName(
495           PreviewArea.Classes_.PREVIEW_AREA);
496       for (var i = 0; i < previewAreaControls.length; ++i) {
497         previewAreaControls[i].setAttribute('aria-hidden', visible);
498       }
500       if (!visible) {
501         // Disable jumping animation to conserve cycles.
502         var jumpingDotsEl = this.getElement().querySelector(
503             '.preview-area-loading-message-jumping-dots');
504         jumpingDotsEl.classList.remove('jumping-dots');
505       }
506     },
508     /**
509      * Creates a preview plugin and adds it to the DOM.
510      * @param {string} srcUrl Initial URL of the plugin.
511      * @private
512      */
513     createPlugin_: function(srcUrl) {
514       if (this.plugin_) {
515         console.warn('Pdf preview plugin already created');
516         return;
517       }
519       this.plugin_ = /** @type {print_preview.PDFPlugin} */(
520           PDFCreateOutOfProcessPlugin(srcUrl));
521       this.plugin_.setKeyEventCallback(this.keyEventCallback_);
523       this.plugin_.setAttribute('class', 'preview-area-plugin');
524       this.plugin_.setAttribute('aria-live', 'polite');
525       this.plugin_.setAttribute('aria-atomic', 'true');
526       // NOTE: The plugin's 'id' field must be set to 'pdf-viewer' since
527       // chrome/renderer/printing/print_web_view_helper.cc actually references
528       // it.
529       this.plugin_.setAttribute('id', 'pdf-viewer');
530       this.getChildElement('.preview-area-plugin-wrapper').
531           appendChild(/** @type {Node} */(this.plugin_));
534       var pageNumbers =
535           this.printTicketStore_.pageRange.getPageNumberSet().asArray();
536       var grayscale = !this.printTicketStore_.color.getValue();
537       this.plugin_.setLoadCallback(this.onPluginLoad_.bind(this));
538       this.plugin_.setViewportChangedCallback(
539           this.onPreviewVisualStateChange_.bind(this));
540       this.plugin_.resetPrintPreviewMode(srcUrl, grayscale, pageNumbers,
541                                          this.documentInfo_.isModifiable);
542     },
544     /**
545      * Dispatches a PREVIEW_GENERATION_DONE event if all conditions are met.
546      * @private
547      */
548     dispatchPreviewGenerationDoneIfReady_: function() {
549       if (this.isDocumentReady_ && this.isPluginReloaded_) {
550         cr.dispatchSimpleEvent(
551             this, PreviewArea.EventType.PREVIEW_GENERATION_DONE);
552         this.marginControlContainer_.showMarginControlsIfNeeded();
553       }
554     },
556     /**
557      * Called when the open-system-dialog button is clicked. Disables the
558      * button, shows the throbber, and dispatches the OPEN_SYSTEM_DIALOG_CLICK
559      * event.
560      * @private
561      */
562     onOpenSystemDialogButtonClick_: function() {
563       this.openSystemDialogButton_.disabled = true;
564       var openSystemDialogThrobber = this.getElement().getElementsByClassName(
565           PreviewArea.Classes_.OPEN_SYSTEM_DIALOG_BUTTON_THROBBER)[0];
566       setIsVisible(openSystemDialogThrobber, true);
567       cr.dispatchSimpleEvent(
568           this, PreviewArea.EventType.OPEN_SYSTEM_DIALOG_CLICK);
569     },
571     /**
572      * Called when the print ticket changes. Updates the preview.
573      * @private
574      */
575     onTicketChange_: function() {
576       if (this.previewGenerator_ && this.previewGenerator_.requestPreview()) {
577         cr.dispatchSimpleEvent(
578             this, PreviewArea.EventType.PREVIEW_GENERATION_IN_PROGRESS);
579         if (this.loadingTimeout_ == null) {
580           this.loadingTimeout_ = setTimeout(
581               this.showMessage_.bind(this, PreviewArea.MessageId_.LOADING),
582               PreviewArea.LOADING_TIMEOUT_);
583         }
584       } else {
585         this.marginControlContainer_.showMarginControlsIfNeeded();
586       }
587     },
589     /**
590      * Called when the preview generator begins loading the preview.
591      * @param {Event} event Contains the URL to initialize the plugin to.
592      * @private
593      */
594     onPreviewStart_: function(event) {
595       this.isDocumentReady_ = false;
596       this.isPluginReloaded_ = false;
597       if (!this.plugin_) {
598         this.createPlugin_(event.previewUrl);
599       } else {
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       }
607       cr.dispatchSimpleEvent(
608           this, PreviewArea.EventType.PREVIEW_GENERATION_IN_PROGRESS);
609     },
611     /**
612      * Called when a page preview has been generated. Updates the plugin with
613      * the new page.
614      * @param {Event} event Contains information about the page preview.
615      * @private
616      */
617     onPagePreviewReady_: function(event) {
618       this.plugin_.loadPreviewPage(event.previewUrl, event.previewIndex);
619     },
621     /**
622      * Called when the preview generation is complete and the document is ready
623      * to print.
624      * @private
625      */
626     onDocumentReady_: function(event) {
627       this.isDocumentReady_ = true;
628       this.dispatchPreviewGenerationDoneIfReady_();
629     },
631     /**
632      * Called when the generation of a preview fails. Shows an error message.
633      * @private
634      */
635     onPreviewGenerationFail_: function() {
636       if (this.loadingTimeout_) {
637         clearTimeout(this.loadingTimeout_);
638         this.loadingTimeout_ = null;
639       }
640       this.showMessage_(PreviewArea.MessageId_.PREVIEW_FAILED);
641       cr.dispatchSimpleEvent(
642           this, PreviewArea.EventType.PREVIEW_GENERATION_FAIL);
643     },
645     /**
646      * Called when the plugin loads. This is a consequence of calling
647      * plugin.reload(). Certain plugin state can only be set after the plugin
648      * has loaded.
649      * @private
650      */
651     onPluginLoad_: function() {
652       if (this.loadingTimeout_) {
653         clearTimeout(this.loadingTimeout_);
654         this.loadingTimeout_ = null;
655       }
657       this.setOverlayVisible_(false);
658       this.isPluginReloaded_ = true;
659       this.dispatchPreviewGenerationDoneIfReady_();
660     },
662     /**
663      * Called when the preview plugin's visual state has changed. This is a
664      * consequence of scrolling or zooming the plugin. Updates the custom
665      * margins component if shown.
666      * @private
667      */
668     onPreviewVisualStateChange_: function(pageX,
669                                           pageY,
670                                           pageWidth,
671                                           viewportWidth,
672                                           viewportHeight) {
673       this.marginControlContainer_.updateTranslationTransform(
674           new print_preview.Coordinate2d(pageX, pageY));
675       this.marginControlContainer_.updateScaleTransform(
676           pageWidth / this.documentInfo_.pageSize.width);
677       this.marginControlContainer_.updateClippingMask(
678           new print_preview.Size(viewportWidth, viewportHeight));
679     },
681     /**
682      * Called when dragging margins starts or stops.
683      * @param {boolean} isDragging True if the margin is currently being dragged
684      *     and false otherwise.
685      */
686     onMarginDragChanged_: function(isDragging) {
687       if (!this.plugin_)
688         return;
690       // When hovering over the plugin (which may be in a separate iframe)
691       // pointer events will be sent to the frame. When dragging the margins,
692       // we don't want this to happen as it can cause the margin to stop
693       // being draggable.
694       this.plugin_.style.pointerEvents = isDragging ? 'none' : 'auto';
695     }
696   };
698   // Export
699   return {
700     PreviewArea: PreviewArea
701   };