[sql] Remove _HAS_EXCEPTIONS=0 from build info.
[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}}
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}
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
65 this.destinationStore_ = destinationStore;
67 /**
68 * Used to get information about how the preview should be displayed.
69 * @type {!print_preview.PrintTicketStore}
70 * @private
72 this.printTicketStore_ = printTicketStore;
74 /**
75 * Used to contruct the preview generator.
76 * @type {!print_preview.NativeLayer}
77 * @private
79 this.nativeLayer_ = nativeLayer;
81 /**
82 * Document data model.
83 * @type {!print_preview.DocumentInfo}
84 * @private
86 this.documentInfo_ = documentInfo;
88 /**
89 * Used to read generated page previews.
90 * @type {print_preview.PreviewGenerator}
91 * @private
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
100 this.plugin_ = null;
103 * Custom margins component superimposed on the preview plugin.
104 * @type {!print_preview.MarginControlContainer}
105 * @private
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_);
116 * Current zoom level as a percentage.
117 * @type {?number}
118 * @private
120 this.zoomLevel_ = null;
123 * Current page offset which can be used to calculate scroll amount.
124 * @type {print_preview.Coordinate2d}
125 * @private
127 this.pageOffset_ = null;
130 * Whether the plugin has finished reloading.
131 * @type {boolean}
132 * @private
134 this.isPluginReloaded_ = false;
137 * Whether the document preview is ready.
138 * @type {boolean}
139 * @private
141 this.isDocumentReady_ = false;
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
149 this.loadingTimeout_ = null;
152 * Overlay element.
153 * @type {HTMLElement}
154 * @private
156 this.overlayEl_ = null;
159 * The "Open system dialog" button.
160 * @type {HTMLButtonElement}
161 * @private
163 this.openSystemDialogButton_ = null;
167 * Event types dispatched by the preview area.
168 * @enum {string}
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'
189 * CSS classes used by the preview area.
190 * @enum {string}
191 * @private
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'
208 * Enumeration of IDs shown in the preview area.
209 * @enum {string}
210 * @private
212 PreviewArea.MessageId_ = {
213 CUSTOM: 'custom',
214 LOADING: 'loading',
215 PREVIEW_FAILED: 'preview-failed'
219 * Maps message IDs to the CSS class that contains them.
220 * @type {Object<print_preview.PreviewArea.MessageId_, string>}
221 * @private
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';
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
238 PreviewArea.LOADING_TIMEOUT_ = 200;
240 PreviewArea.prototype = {
241 __proto__: print_preview.Component.prototype,
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.
248 get hasCompatiblePlugin() {
249 return this.previewGenerator_ != null;
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.
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;
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;
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;
284 element = element.parentElement;
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();
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.
299 setPluginKeyEventCallback: function(callback) {
300 this.keyEventCallback_ = callback;
304 * Shows a custom message on the preview area's overlay.
305 * @param {string} message Custom message to show.
307 showCustomMessage: function(message) {
308 this.showMessage_(PreviewArea.MessageId_.CUSTOM, message);
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'));
400 /** @override */
401 exitDocument: function() {
402 print_preview.Component.prototype.exitDocument.call(this);
403 if (this.previewGenerator_) {
404 this.previewGenerator_.removeEventListeners();
406 this.overlayEl_ = null;
407 this.openSystemDialogButton_ = null;
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];
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
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;
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
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);
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');
466 var messageEl = this.getElement().getElementsByClassName(
467 PreviewArea.MessageIdClassMap_[messageId])[0];
468 setIsVisible(messageEl, true);
470 this.setOverlayVisible_(true);
474 * Set the visibility of the message overlay.
475 * @param {boolean} visible Whether to make the overlay visible or not
476 * @private
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);
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);
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');
505 * Creates a preview plugin and adds it to the DOM.
506 * @param {string} srcUrl Initial URL of the plugin.
507 * @private
509 createPlugin_: function(srcUrl) {
510 if (this.plugin_) {
511 console.warn('Pdf preview plugin already created');
512 return;
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);
541 * Dispatches a PREVIEW_GENERATION_DONE event if all conditions are met.
542 * @private
544 dispatchPreviewGenerationDoneIfReady_: function() {
545 if (this.isDocumentReady_ && this.isPluginReloaded_) {
546 cr.dispatchSimpleEvent(
547 this, PreviewArea.EventType.PREVIEW_GENERATION_DONE);
548 this.marginControlContainer_.showMarginControlsIfNeeded();
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
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);
568 * Called when the print ticket changes. Updates the preview.
569 * @private
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_);
580 } else {
581 this.marginControlContainer_.showMarginControlsIfNeeded();
586 * Called when the preview generator begins loading the preview.
587 * @param {Event} event Contains the URL to initialize the plugin to.
588 * @private
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);
603 cr.dispatchSimpleEvent(
604 this, PreviewArea.EventType.PREVIEW_GENERATION_IN_PROGRESS);
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
613 onPagePreviewReady_: function(event) {
614 this.plugin_.loadPreviewPage(event.previewUrl, event.previewIndex);
618 * Called when the preview generation is complete and the document is ready
619 * to print.
620 * @private
622 onDocumentReady_: function(event) {
623 this.isDocumentReady_ = true;
624 this.dispatchPreviewGenerationDoneIfReady_();
628 * Called when the generation of a preview fails. Shows an error message.
629 * @private
631 onPreviewGenerationFail_: function() {
632 if (this.loadingTimeout_) {
633 clearTimeout(this.loadingTimeout_);
634 this.loadingTimeout_ = null;
636 this.showMessage_(PreviewArea.MessageId_.PREVIEW_FAILED);
637 cr.dispatchSimpleEvent(
638 this, PreviewArea.EventType.PREVIEW_GENERATION_FAIL);
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
647 onPluginLoad_: function() {
648 if (this.loadingTimeout_) {
649 clearTimeout(this.loadingTimeout_);
650 this.loadingTimeout_ = null;
653 this.setOverlayVisible_(false);
654 this.isPluginReloaded_ = true;
655 this.dispatchPreviewGenerationDoneIfReady_();
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
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));
678 * Called when dragging margins starts or stops.
679 * @param {boolean} isDragging True if the margin is currently being dragged
680 * and false otherwise.
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';
694 // Export
695 return {
696 PreviewArea: PreviewArea