Index: chrome/browser/resources/print_preview/previewarea/preview_area.js |
diff --git a/chrome/browser/resources/print_preview/previewarea/preview_area.js b/chrome/browser/resources/print_preview/previewarea/preview_area.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..1ede80a584b3192191f41ca0db5561c901e5b40c |
--- /dev/null |
+++ b/chrome/browser/resources/print_preview/previewarea/preview_area.js |
@@ -0,0 +1,592 @@ |
+// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+cr.define('print_preview', function() { |
+ 'use strict'; |
+ |
+ /** |
+ * Creates a PreviewArea object. It represents the area where the preview |
+ * document is displayed. |
+ * @param {!print_preview.DestinationStore} destinationStore Used to get the |
+ * currently selected destination. |
+ * @param {!print_preview.PrintTicketStore} printTicketStore Used to get |
+ * information about how the preview should be displayed. |
+ * @param {!print_preview.NativeLayer} nativeLayer Needed to communicate with |
+ * Chromium's preview generation system. |
+ * @constructor |
+ * @extends {print_preview.Component} |
+ */ |
+ function PreviewArea(destinationStore, printTicketStore, nativeLayer) { |
+ print_preview.Component.call(this); |
+ |
+ /** |
+ * Used to get the currently selected destination. |
+ * @type {!print_preview.DestinationStore} |
+ * @private |
+ */ |
+ this.destinationStore_ = destinationStore; |
+ |
+ /** |
+ * Used to get information about how the preview should be displayed. |
+ * @type {!print_preview.PrintTicketStore} |
+ * @private |
+ */ |
+ this.printTicketStore_ = printTicketStore; |
+ |
+ /** |
+ * Used to contruct the preview generator. |
+ * @type {!print_preview.NativeLayer} |
+ * @private |
+ */ |
+ this.nativeLayer_ = nativeLayer; |
+ |
+ /** |
+ * Used to read generated page previews. |
+ * @type {print_preview.PreviewGenerator} |
+ * @private |
+ */ |
+ this.previewGenerator_ = null; |
+ |
+ /** |
+ * The embedded pdf plugin object. It's value is null if not yet loaded. |
+ * @type {HTMLEmbedElement} |
+ * @private |
+ */ |
+ this.plugin_ = null; |
+ |
+ /** |
+ * Custom margins component superimposed on the preview plugin. |
+ * @type {!print_preview.MarginControlContainer} |
+ * @private |
+ */ |
+ this.marginControlContainer_ = |
+ new print_preview.MarginControlContainer(this.printTicketStore_); |
+ this.addChild(this.marginControlContainer_); |
+ |
+ /** |
+ * Current zoom level as a percentage. |
+ * @type {?number} |
+ * @private |
+ */ |
+ this.zoomLevel_ = null; |
+ |
+ /** |
+ * Current page offset which can be used to calculate scroll amount. |
+ * @type {print_preview.Coordinate2d} |
+ * @private |
+ */ |
+ this.pageOffset_ = null; |
+ |
+ /** |
+ * Whether the plugin has finished reloading. |
+ * @type {boolean} |
+ * @private |
+ */ |
+ this.isPluginReloaded_ = false; |
+ |
+ /** |
+ * Whether the document preview is ready. |
+ * @type {boolean} |
+ * @private |
+ */ |
+ this.isDocumentReady_ = false; |
+ |
+ /** |
+ * Timeout object used to display a loading message if the preview is taking |
+ * a long time to generate. |
+ * @type {Object} |
+ * @private |
+ */ |
+ this.loadingTimeout_ = null; |
+ |
+ /** |
+ * Overlay element. |
+ * @type {HTMLElement} |
+ * @private |
+ */ |
+ this.overlayEl_ = null; |
+ |
+ /** |
+ * The "Open system dialog" button. |
+ * @type {HTMLButtonElement} |
+ * @private |
+ */ |
+ this.openSystemDialogButton_ = null; |
+ }; |
+ |
+ /** |
+ * Event types dispatched by the preview area. |
+ * @enum {string} |
+ */ |
+ PreviewArea.EventType = { |
+ // Dispatched when the "Open system dialog" button is clicked. |
+ OPEN_SYSTEM_DIALOG_CLICK: |
+ 'print_preview.PreviewArea.OPEN_SYSTEM_DIALOG_CLICK', |
+ |
+ // Dispatched when the document preview is complete. |
+ PREVIEW_GENERATION_DONE: |
+ 'print_preview.PreviewArea.PREVIEW_GENERATION_DONE', |
+ |
+ // Dispatched when the document preview failed to be generated. |
+ PREVIEW_GENERATION_FAIL: |
+ 'print_preview.PreviewArea.PREVIEW_GENERATION_FAIL', |
+ |
+ // Dispatched when a new document preview is being generated. |
+ PREVIEW_GENERATION_IN_PROGRESS: |
+ 'print_preview.PreviewArea.PREVIEW_GENERATION_IN_PROGRESS' |
+ }; |
+ |
+ /** |
+ * CSS classes used by the preview area. |
+ * @enum {string} |
+ * @private |
+ */ |
+ PreviewArea.Classes_ = { |
+ COMPATIBILITY_OBJECT: 'preview-area-compatibility-object', |
+ CUSTOM_MESSAGE_TEXT: 'preview-area-custom-message-text', |
+ MESSAGE: 'preview-area-message', |
+ INVISIBLE: 'invisible', |
+ OPEN_SYSTEM_DIALOG_BUTTON: 'preview-area-open-system-dialog-button', |
+ OPEN_SYSTEM_DIALOG_BUTTON_THROBBER: |
+ 'preview-area-open-system-dialog-button-throbber', |
+ OVERLAY: 'preview-area-overlay-layer', |
+ PDF_PLUGIN: 'preview-area-pdf-plugin' |
+ }; |
+ |
+ /** |
+ * Enumeration of IDs shown in the preview area. |
+ * @enum {string} |
+ * @private |
+ */ |
+ PreviewArea.MessageId_ = { |
+ CUSTOM: 'custom', |
+ LOADING: 'loading', |
+ PREVIEW_FAILED: 'preview-failed' |
+ }; |
+ |
+ /** |
+ * Maps message IDs to the CSS class that contains them. |
+ * @type {object.<PreviewArea.MessageId_, string>} |
+ * @private |
+ */ |
+ PreviewArea.MessageIdClassMap_ = {}; |
+ PreviewArea.MessageIdClassMap_[PreviewArea.MessageId_.CUSTOM] = |
+ 'preview-area-custom-message'; |
+ PreviewArea.MessageIdClassMap_[PreviewArea.MessageId_.LOADING] = |
+ 'preview-area-loading-message'; |
+ PreviewArea.MessageIdClassMap_[PreviewArea.MessageId_.PREVIEW_FAILED] = |
+ 'preview-area-preview-failed-message'; |
+ |
+ /** |
+ * Amount of time in milliseconds to wait after issueing a new preview before |
+ * the loading message is shown. |
+ * @type {number} |
+ * @const |
+ * @private |
+ */ |
+ PreviewArea.LOADING_TIMEOUT_ = 200; |
+ |
+ PreviewArea.prototype = { |
+ __proto__: print_preview.Component.prototype, |
+ |
+ /** |
+ * Should only be called after calling this.render(). |
+ * @return {boolean} Whether the preview area has a compatible plugin to |
+ * display the print preview in. |
+ */ |
+ get hasCompatiblePlugin() { |
+ return this.previewGenerator_ != null; |
+ }, |
+ |
+ /** |
+ * Processes a keyboard event that could possibly be used to change state of |
+ * the preview plugin. |
+ * @param {MouseEvent} e Mouse event to process. |
+ */ |
+ handleDirectionalKeyEvent: function(e) { |
+ // Make sure the PDF plugin is there. |
+ // We only care about: PageUp, PageDown, Left, Up, Right, Down. |
+ // If the user is holding a modifier key, ignore. |
+ if (!this.plugin_ || |
+ !arrayContains([33, 34, 37, 38, 39, 40], e.keyCode) || |
+ e.metaKey || e.altKey || e.shiftKey || e.ctrlKey) { |
+ return; |
+ } |
+ |
+ // Don't handle the key event for these elements. |
+ var tagName = document.activeElement.tagName; |
+ if (arrayContains(['INPUT', 'SELECT', 'EMBED'], tagName)) { |
+ return; |
+ } |
+ |
+ // For the most part, if any div of header was the last clicked element, |
+ // then the active element is the body. Starting with the last clicked |
+ // element, and work up the DOM tree to see if any element has a |
+ // scrollbar. If there exists a scrollbar, do not handle the key event |
+ // here. |
+ var element = e.target; |
+ while (element) { |
+ if (element.scrollHeight > element.clientHeight || |
+ element.scrollWidth > element.clientWidth) { |
+ return; |
+ } |
+ element = element.parentElement; |
+ } |
+ |
+ // No scroll bar anywhere, or the active element is something else, like a |
+ // button. Note: buttons have a bigger scrollHeight than clientHeight. |
+ this.plugin_.sendKeyEvent(e.keyCode); |
+ e.preventDefault(); |
+ }, |
+ |
+ /** |
+ * Shows a custom message on the preview area's overlay. |
+ * @param {string} message Custom message to show. |
+ */ |
+ showCustomMessage: function(message) { |
+ this.showMessage_(PreviewArea.MessageId_.CUSTOM, message); |
+ }, |
+ |
+ /** @override */ |
+ enterDocument: function() { |
+ print_preview.Component.prototype.enterDocument.call(this); |
+ this.tracker.add( |
+ this.openSystemDialogButton_, |
+ 'click', |
+ this.onOpenSystemDialogButtonClick_.bind(this)); |
+ |
+ this.tracker.add( |
+ this.printTicketStore_, |
+ print_preview.PrintTicketStore.EventType.INITIALIZE, |
+ this.onTicketChange_.bind(this)); |
+ this.tracker.add( |
+ this.printTicketStore_, |
+ print_preview.PrintTicketStore.EventType.TICKET_CHANGE, |
+ this.onTicketChange_.bind(this)); |
+ this.tracker.add( |
+ this.printTicketStore_, |
+ print_preview.PrintTicketStore.EventType.CAPABILITIES_CHANGE, |
+ this.onTicketChange_.bind(this)); |
+ this.tracker.add( |
+ this.printTicketStore_, |
+ print_preview.PrintTicketStore.EventType.DOCUMENT_CHANGE, |
+ this.onTicketChange_.bind(this)); |
+ |
+ if (this.checkPluginCompatibility_()) { |
+ this.previewGenerator_ = new print_preview.PreviewGenerator( |
+ this.destinationStore_, this.printTicketStore_, this.nativeLayer_); |
+ this.tracker.add( |
+ this.previewGenerator_, |
+ print_preview.PreviewGenerator.EventType.PREVIEW_START, |
+ this.onPreviewStart_.bind(this)); |
+ this.tracker.add( |
+ this.previewGenerator_, |
+ print_preview.PreviewGenerator.EventType.PAGE_READY, |
+ this.onPagePreviewReady_.bind(this)); |
+ this.tracker.add( |
+ this.previewGenerator_, |
+ print_preview.PreviewGenerator.EventType.FAIL, |
+ this.onPreviewGenerationFail_.bind(this)); |
+ this.tracker.add( |
+ this.previewGenerator_, |
+ print_preview.PreviewGenerator.EventType.DOCUMENT_READY, |
+ this.onDocumentReady_.bind(this)); |
+ } else { |
+ this.showCustomMessage(localStrings.getString('noPlugin')); |
+ } |
+ }, |
+ |
+ /** @override */ |
+ exitDocument: function() { |
+ print_preview.Component.prototype.exitDocument.call(this); |
+ if (this.previewGenerator_) { |
+ this.previewGenerator_.removeEventListeners(); |
+ } |
+ this.overlayEl_ = null; |
+ this.openSystemDialogButton_ = null; |
+ }, |
+ |
+ /** @override */ |
+ decorateInternal: function() { |
+ this.marginControlContainer_.decorate(this.getElement()); |
+ this.overlayEl_ = this.getElement().getElementsByClassName( |
+ PreviewArea.Classes_.OVERLAY)[0]; |
+ this.openSystemDialogButton_ = this.getElement().getElementsByClassName( |
+ PreviewArea.Classes_.OPEN_SYSTEM_DIALOG_BUTTON)[0]; |
+ }, |
+ |
+ /** |
+ * Checks to see if a suitable plugin for rendering the preview exists. If |
+ * one does not exist, then an error message will be displayed. |
+ * @return {boolean} Whether Chromium has a suitable plugin for rendering |
+ * the preview. |
+ * @private |
+ */ |
+ checkPluginCompatibility_: function() { |
+ var compatObj = this.getElement().getElementsByClassName( |
+ PreviewArea.Classes_.COMPATIBILITY_OBJECT)[0]; |
+ var isCompatible = |
+ compatObj.onload && |
+ compatObj.goToPage && |
+ compatObj.removePrintButton && |
+ compatObj.loadPreviewPage && |
+ compatObj.printPreviewPageCount && |
+ compatObj.resetPrintPreviewUrl && |
+ compatObj.onPluginSizeChanged && |
+ compatObj.onScroll && |
+ compatObj.pageXOffset && |
+ compatObj.pageYOffset && |
+ compatObj.setZoomLevel && |
+ compatObj.setPageNumbers && |
+ compatObj.setPageXOffset && |
+ compatObj.setPageYOffset && |
+ compatObj.getHorizontalScrollbarThickness && |
+ compatObj.getVerticalScrollbarThickness && |
+ compatObj.getPageLocationNormalized && |
+ compatObj.getHeight && |
+ compatObj.getWidth; |
+ compatObj.parentElement.removeChild(compatObj); |
+ return isCompatible; |
+ }, |
+ |
+ /** |
+ * Shows a given message on the overlay. |
+ * @param {print_preview.PreviewArea.MessageId_} messageId ID of the message |
+ * to show. |
+ * @param {string=} opt_message Optional message to show that can be used |
+ * by some message IDs. |
+ * @private |
+ */ |
+ showMessage_: function(messageId, opt_message) { |
+ // Hide all messages. |
+ var messageEls = this.getElement().getElementsByClassName( |
+ PreviewArea.Classes_.MESSAGE); |
+ for (var i = 0, messageEl; messageEl = messageEls[i]; i++) { |
+ setIsVisible(messageEl, false); |
+ } |
+ // Disable jumping animation to conserve cycles. |
+ var jumpingDotsEl = this.getElement().querySelector( |
+ '.preview-area-loading-message-jumping-dots'); |
+ jumpingDotsEl.classList.remove('jumping-dots'); |
+ |
+ // Show specific message. |
+ if (messageId == PreviewArea.MessageId_.CUSTOM) { |
+ var customMessageTextEl = this.getElement().getElementsByClassName( |
+ PreviewArea.Classes_.CUSTOM_MESSAGE_TEXT)[0]; |
+ customMessageTextEl.textContent = opt_message; |
+ } else if (messageId == PreviewArea.MessageId_.LOADING) { |
+ jumpingDotsEl.classList.add('jumping-dots'); |
+ } |
+ var messageEl = this.getElement().getElementsByClassName( |
+ PreviewArea.MessageIdClassMap_[messageId])[0]; |
+ setIsVisible(messageEl, true); |
+ |
+ // Show overlay. |
+ this.overlayEl_.classList.remove(PreviewArea.Classes_.INVISIBLE); |
+ }, |
+ |
+ /** |
+ * Hides the message overlay. |
+ * @private |
+ */ |
+ hideOverlay_: function() { |
+ this.overlayEl_.classList.add(PreviewArea.Classes_.INVISIBLE); |
+ // Disable jumping animation to conserve cycles. |
+ var jumpingDotsEl = this.getElement().querySelector( |
+ '.preview-area-loading-message-jumping-dots'); |
+ jumpingDotsEl.classList.remove('jumping-dots'); |
+ }, |
+ |
+ /** |
+ * Creates a preview plugin and adds it to the DOM. |
+ * @param {string} srcUrl Initial URL of the plugin. |
+ * @private |
+ */ |
+ createPlugin_: function(srcUrl) { |
+ if (this.plugin_) { |
+ console.warn('Pdf preview plugin already created'); |
+ return; |
+ } |
+ this.plugin_ = document.createElement('embed'); |
+ // NOTE: The plugin's 'id' field must be set to 'pdf-viewer' since |
+ // chrome/renderer/print_web_view_helper.cc actually references it. |
+ this.plugin_.setAttribute('id', 'pdf-viewer'); |
+ this.plugin_.setAttribute('class', 'preview-area-plugin'); |
+ this.plugin_.setAttribute( |
+ 'type', 'application/x-google-chrome-print-preview-pdf'); |
+ this.plugin_.setAttribute('src', srcUrl); |
+ this.plugin_.setAttribute('aria-live', 'polite'); |
+ this.plugin_.setAttribute('aria-atomic', 'true'); |
+ this.getElement().appendChild(this.plugin_); |
+ |
+ global['onPreviewPluginLoad'] = this.onPluginLoad_.bind(this); |
+ this.plugin_.onload('onPreviewPluginLoad()'); |
+ |
+ global['onPreviewPluginVisualStateChange'] = |
+ this.onPreviewVisualStateChange_.bind(this); |
+ this.plugin_.onScroll('onPreviewPluginVisualStateChange()'); |
+ this.plugin_.onPluginSizeChanged('onPreviewPluginVisualStateChange()'); |
+ |
+ this.plugin_.removePrintButton(); |
+ this.plugin_.grayscale(!this.printTicketStore_.isColorEnabled()); |
+ }, |
+ |
+ /** |
+ * Dispatches a PREVIEW_GENERATION_DONE event if all conditions are met. |
+ * @private |
+ */ |
+ dispatchPreviewGenerationDoneIfReady_: function() { |
+ if (this.isDocumentReady_ && this.isPluginReloaded_) { |
+ cr.dispatchSimpleEvent( |
+ this, PreviewArea.EventType.PREVIEW_GENERATION_DONE); |
+ this.marginControlContainer_.showMarginControlsIfNeeded(); |
+ } |
+ }, |
+ |
+ /** |
+ * Called when the open-system-dialog button is clicked. Disables the |
+ * button, shows the throbber, and dispatches the OPEN_SYSTEM_DIALOG_CLICK |
+ * event. |
+ * @private |
+ */ |
+ onOpenSystemDialogButtonClick_: function() { |
+ this.openSystemDialogButton_.disabled = true; |
+ var openSystemDialogThrobber = this.getElement().getElementsByClassName( |
+ PreviewArea.Classes_.OPEN_SYSTEM_DIALOG_BUTTON_THROBBER)[0]; |
+ setIsVisible(openSystemDialogThrobber, true); |
+ cr.dispatchSimpleEvent( |
+ this, PreviewArea.EventType.OPEN_SYSTEM_DIALOG_CLICK); |
+ }, |
+ |
+ /** |
+ * Called when the print ticket changes. Updates the preview. |
+ * @private |
+ */ |
+ onTicketChange_: function() { |
+ if (this.previewGenerator_ && this.previewGenerator_.requestPreview()) { |
+ if (this.loadingTimeout_ == null) { |
+ this.loadingTimeout_ = setTimeout( |
+ this.showMessage_.bind(this, PreviewArea.MessageId_.LOADING), |
+ PreviewArea.LOADING_TIMEOUT_); |
+ } |
+ } else { |
+ this.marginControlContainer_.showMarginControlsIfNeeded(); |
+ } |
+ }, |
+ |
+ /** |
+ * Called when the preview generator begins loading the preview. |
+ * @param {cr.Event} Contains the URL to initialize the plugin to. |
+ * @private |
+ */ |
+ onPreviewStart_: function(event) { |
+ this.isDocumentReady_ = false; |
+ this.isPluginReloaded_ = false; |
+ if (!this.plugin_) { |
+ this.createPlugin_(event.previewUrl); |
+ } |
+ this.plugin_.goToPage('0'); |
+ this.plugin_.resetPrintPreviewUrl(event.previewUrl); |
+ this.plugin_.reload(); |
+ this.plugin_.grayscale(!this.printTicketStore_.isColorEnabled()); |
+ cr.dispatchSimpleEvent( |
+ this, PreviewArea.EventType.PREVIEW_GENERATION_IN_PROGRESS); |
+ }, |
+ |
+ /** |
+ * Called when a page preview has been generated. Updates the plugin with |
+ * the new page. |
+ * @param {cr.Event} event Contains information about the page preview. |
+ * @private |
+ */ |
+ onPagePreviewReady_: function(event) { |
+ this.plugin_.loadPreviewPage(event.previewUrl, event.previewIndex); |
+ }, |
+ |
+ /** |
+ * Called when the preview generation is complete and the document is ready |
+ * to print. |
+ * @private |
+ */ |
+ onDocumentReady_: function(event) { |
+ this.isDocumentReady_ = true; |
+ this.dispatchPreviewGenerationDoneIfReady_(); |
+ }, |
+ |
+ /** |
+ * Called when the generation of a preview fails. Shows an error message. |
+ * @private |
+ */ |
+ onPreviewGenerationFail_: function() { |
+ this.showMessage_(PreviewArea.MessageId_.PREVIEW_FAILED); |
+ cr.dispatchSimpleEvent( |
+ this, PreviewArea.EventType.PREVIEW_GENERATION_FAIL); |
+ }, |
+ |
+ /** |
+ * Called when the plugin loads. This is a consequence of calling |
+ * plugin.reload(). Certain plugin state can only be set after the plugin |
+ * has loaded. |
+ * @private |
+ */ |
+ onPluginLoad_: function() { |
+ if (this.loadingTimeout_) { |
+ clearTimeout(this.loadingTimeout_); |
+ this.loadingTimeout_ = null; |
+ } |
+ // Setting the plugin's page count can only be called after the plugin is |
+ // loaded and the document must be modifiable. |
+ if (this.printTicketStore_.isDocumentModifiable) { |
+ this.plugin_.printPreviewPageCount( |
+ this.printTicketStore_.getPageNumberSet().size); |
+ } |
+ this.plugin_.setPageNumbers(JSON.stringify( |
+ this.printTicketStore_.getPageNumberSet().asArray())); |
+ if (this.zoomLevel_ != null && this.pageOffset_ != null) { |
+ this.plugin_.setZoomLevel(this.zoomLevel_); |
+ this.plugin_.setPageXOffset(this.pageOffset_.x); |
+ this.plugin_.setPageYOffset(this.pageOffset_.y); |
+ } else { |
+ this.plugin_.fitToHeight(); |
+ } |
+ this.hideOverlay_(); |
+ this.isPluginReloaded_ = true; |
+ this.dispatchPreviewGenerationDoneIfReady_(); |
+ }, |
+ |
+ /** |
+ * Called when the preview plugin's visual state has changed. This is a |
+ * consequence of scrolling or zooming the plugin. Updates the custom |
+ * margins component if shown. |
+ * @private |
+ */ |
+ onPreviewVisualStateChange_: function() { |
+ if (this.isPluginReloaded_) { |
+ this.zoomLevel_ = this.plugin_.getZoomLevel(); |
+ this.pageOffset_ = new print_preview.Coordinate2d( |
+ this.plugin_.pageXOffset(), this.plugin_.pageYOffset()); |
+ } |
+ var normalized = this.plugin_.getPageLocationNormalized().split(';'); |
+ var pluginWidth = this.plugin_.getWidth(); |
+ var pluginHeight = this.plugin_.getHeight(); |
+ var translationTransform = new print_preview.Coordinate2d( |
+ parseFloat(normalized[0]) * pluginWidth, |
+ parseFloat(normalized[1]) * pluginHeight); |
+ this.marginControlContainer_.updateTranslationTransform( |
+ translationTransform); |
+ var pageWidthInPixels = parseFloat(normalized[2]) * pluginWidth; |
+ this.marginControlContainer_.updateScaleTransform( |
+ pageWidthInPixels / this.printTicketStore_.pageSize.width); |
+ this.marginControlContainer_.updateClippingMask( |
+ new print_preview.Size( |
+ pluginWidth - this.plugin_.getVerticalScrollbarThickness(), |
+ pluginHeight - this.plugin_.getHorizontalScrollbarThickness())); |
+ } |
+ }; |
+ |
+ // Export |
+ return { |
+ PreviewArea: PreviewArea |
+ }; |
+}); |