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..b3d0a0fa42224291286ea5e3b7a3bcda08f2bf25 |
--- /dev/null |
+++ b/chrome/browser/resources/print_preview/previewarea/preview_area.js |
@@ -0,0 +1,446 @@ |
+// 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. |
+ |
+// TODO Add mechanism to reshow the loading overlay if the plugin is too long to |
+// refresh. |
+ |
+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.CustomMargins} |
+ * @private |
+ */ |
+ this.customMargins_ = new print_preview.CustomMargins( |
+ this.printTicketStore_); |
+ this.addChild(this.customMargins_); |
+ |
+ /** |
+ * 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; |
+ |
+ /** |
+ * A rectangle describing the postion of the most visible page normalized |
+ * with respect to the total height and width of the plugin. |
+ * @type {print_preview.Rect} |
+ * @private |
+ */ |
+ this.pageLocationNormalized_ = null; |
+ }; |
+ |
+ /** |
+ * Enumeration of events dispatched by the preview area. |
+ * @enum {string} |
+ */ |
+ PreviewArea.Event = { |
+ OPEN_SYSTEM_DIALOG_CLICK: |
+ 'print_preview.PreviewArea.OPEN_SYSTEM_DIALOG_CLICK', |
+ PREVIEW_GENERATION_FAIL: 'print_preview.PreviewArea.PREVIEW_GENERATION_FAIL' |
+ }; |
+ |
+ /** |
+ * CSS classes used by the preview area. |
+ * @enum {string} |
+ * @private |
+ */ |
+ PreviewArea.Classes_ = { |
+ COMPATIBILITY_OBJECT: 'preview-area-compatibility-object', |
+ LOADING_MESSAGE: 'preview-area-loading-message', |
+ CUSTOM_MESSAGE: 'preview-area-custom-message', |
+ CUSTOM_MESSAGE_TEXT: 'preview-area-custom-message-text', |
+ 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', |
+ PREVIEW_FAILED_MESSAGE: 'preview-failed-message' |
+ }; |
+ |
+ 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. |
+ if (!this.plugin_) { |
+ return; |
+ } |
+ |
+ // We only care about: PageUp, PageDown, Left, Up, Right, Down. |
+ if (!arrayContains([33, 34, 37, 38, 39, 40], e.keyCode)) { |
+ return; |
+ } |
+ |
+ // If the user is holding a modifier key, ignore. |
+ if (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(); |
+ }, |
+ |
+ showCustomMessage: function(message) { |
+ var customMessageTextEl = this.getElement().getElementsByClassName( |
+ PreviewArea.Classes_.CUSTOM_MESSAGE_TEXT)[0]; |
+ customMessageTextEl.textContent = message; |
+ var customMessageEl = this.getElement().getElementsByClassName( |
+ PreviewArea.Classes_.CUSTOM_MESSAGE)[0]; |
+ setIsVisible(customMessageEl, true); |
+ }, |
+ |
+ /** @override */ |
+ decorateInternal: function() { |
+ this.customMargins_.decorate(this.getElement()); |
+ }, |
+ |
+ /** @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.Event.INITIALIZE, |
+ this.onTicketInitialize_.bind(this)); |
+ this.tracker.add( |
+ this.printTicketStore_, |
+ print_preview.PrintTicketStore.Event.TICKET_CHANGE, |
+ this.onTicketChange_.bind(this)); |
+ this.tracker.add( |
+ this.printTicketStore_, |
+ print_preview.PrintTicketStore.Event.CAPABILITIES_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.Event.PAGE_READY, |
+ this.onPagePreviewReady_.bind(this)); |
+ this.tracker.add( |
+ this.previewGenerator_, |
+ print_preview.PreviewGenerator.Event.FAIL, |
+ this.onPreviewGenerationFail_.bind(this)); |
+ } else { |
+ // Hide loading message and show no plugin message. |
+ this.hideLoadingMessage_(); |
+ this.showCustomMessage(localStrings.getString('noPlugin')); |
+ } |
+ }, |
+ |
+ /** |
+ * @return {Element} Preview overlay element. |
+ * @private |
+ */ |
+ get overlayEl_() { |
+ return this.getElement().getElementsByClassName( |
+ PreviewArea.Classes_.OVERLAY)[0]; |
+ }, |
+ |
+ /** |
+ * @return {HTMLButtonElement} Button used to open the system dialog in the |
+ * event that no compatible preview plugin is found. |
+ * @private |
+ */ |
+ get openSystemDialogButton_() { |
+ return 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; |
+ }, |
+ |
+ /** |
+ * Hides the loading message. |
+ * @private |
+ */ |
+ hideLoadingMessage_: function() { |
+ var loadingMessage = this.getElement().getElementsByClassName( |
+ PreviewArea.Classes_.LOADING_MESSAGE)[0]; |
+ setIsVisible(loadingMessage, false); |
+ }, |
+ |
+ /** |
+ * 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_) { |
+ throw Error('Pdf preview plugin already created'); |
+ } |
+ 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()); |
+ }, |
+ |
+ /** |
+ * 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.Event.OPEN_SYSTEM_DIALOG_CLICK); |
+ }, |
+ |
+ /** |
+ * Called when the print ticket is initialized. Requests a preview from |
+ * the preview generator. |
+ * @private |
+ */ |
+ onTicketInitialize_: function() { |
+ if (this.previewGenerator_ && this.printTicketStore_.isTicketValid()) { |
+ this.previewGenerator_.requestPreview(); |
+ } |
+ }, |
+ |
+ /** |
+ * Called when the print ticket changes. Updates the preview. |
+ * @private |
+ */ |
+ onTicketChange_: function() { |
+ if (this.previewGenerator_ && this.printTicketStore_.isTicketValid()) { |
+ this.previewGenerator_.requestPreview(); |
+ } |
+ }, |
+ |
+ /** |
+ * Called when a page preview has been generated. Updates the plugin with |
+ * the new page. |
+ * @param {cr.Event} evt Contains information about the page preview. |
+ * @private |
+ */ |
+ onPagePreviewReady_: function(evt) { |
+ if (!this.plugin_) { |
+ this.createPlugin_(evt.previewUrl); |
+ } |
+ if (evt.previewIndex == 0) { |
+ this.plugin_.goToPage('0'); |
+ this.plugin_.resetPrintPreviewUrl(evt.previewUrl); |
+ this.plugin_.reload(); |
+ this.plugin_.grayscale(!this.printTicketStore_.isColorEnabled()); |
+ } |
+ this.plugin_.loadPreviewPage(evt.previewUrl, evt.previewIndex); |
+ }, |
+ |
+ /** |
+ * Called when the generation of a preview fails. Shows an error message. |
+ * @private |
+ */ |
+ onPreviewGenerationFail_: function() { |
+ // TODO test this method. |
+ this.overlayEl_.classList.remove('invisible'); |
+ var previewFailedMessage = this.getElement().getElementsByClassName( |
+ PreviewArea.Classes_.PREVIEW_FAILED_MESSAGE)[0]; |
+ setIsVisible(previewFailedMessage, true); |
+ cr.dispatchSimpleEvent(this, PreviewArea.Event.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() { |
+ // Setting the plugin's page count can only be called after the plugin is |
+ // loaded. |
+ this.plugin_.printPreviewPageCount( |
+ this.printTicketStore_.getPageNumberSet().size); |
+ 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.overlayEl_.classList.add('invisible'); |
+ this.hideLoadingMessage_(); |
+ }, |
+ |
+ /** |
+ * 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() { |
+ 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 - 9.0, |
+ parseFloat(normalized[1]) * pluginHeight - 9.0); |
+ this.customMargins_.updateTranslationTransform(translationTransform); |
+ var pageWidthInPixels = parseFloat(normalized[2]) * pluginWidth; |
+ this.customMargins_.updateScaleTransform( |
+ pageWidthInPixels / this.printTicketStore_.pageSize.width); |
+ } |
+ }; |
+ |
+ // Export |
+ return { |
+ PreviewArea: PreviewArea |
+ }; |
+}); |