OLD | NEW |
(Empty) | |
| 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. |
| 4 |
| 5 // TODO Add mechanism to reshow the loading overlay if the plugin is too long to |
| 6 // refresh. |
| 7 |
| 8 cr.define('print_preview', function() { |
| 9 'use strict'; |
| 10 |
| 11 /** |
| 12 * Creates a PreviewArea object. It represents the area where the preview |
| 13 * document is displayed. |
| 14 * @param {!print_preview.DestinationStore} destinationStore Used to get the |
| 15 * currently selected destination. |
| 16 * @param {!print_preview.PrintTicketStore} printTicketStore Used to get |
| 17 * information about how the preview should be displayed. |
| 18 * @param {!print_preview.NativeLayer} nativeLayer Needed to communicate with |
| 19 * Chromium's preview generation system. |
| 20 * @constructor |
| 21 * @extends {print_preview.Component} |
| 22 */ |
| 23 function PreviewArea(destinationStore, printTicketStore, nativeLayer) { |
| 24 print_preview.Component.call(this); |
| 25 |
| 26 /** |
| 27 * Used to get the currently selected destination. |
| 28 * @type {!print_preview.DestinationStore} |
| 29 * @private |
| 30 */ |
| 31 this.destinationStore_ = destinationStore; |
| 32 |
| 33 /** |
| 34 * Used to get information about how the preview should be displayed. |
| 35 * @type {!print_preview.PrintTicketStore} |
| 36 * @private |
| 37 */ |
| 38 this.printTicketStore_ = printTicketStore; |
| 39 |
| 40 /** |
| 41 * Used to contruct the preview generator. |
| 42 * @type {!print_preview.NativeLayer} |
| 43 * @private |
| 44 */ |
| 45 this.nativeLayer_ = nativeLayer; |
| 46 |
| 47 /** |
| 48 * Used to read generated page previews. |
| 49 * @type {print_preview.PreviewGenerator} |
| 50 * @private |
| 51 */ |
| 52 this.previewGenerator_ = null; |
| 53 |
| 54 /** |
| 55 * The embedded pdf plugin object. It's value is null if not yet loaded. |
| 56 * @type {HTMLEmbedElement} |
| 57 * @private |
| 58 */ |
| 59 this.plugin_ = null; |
| 60 |
| 61 /** |
| 62 * Custom margins component superimposed on the preview plugin. |
| 63 * @type {!print_preview.MarginControlContainer} |
| 64 * @private |
| 65 */ |
| 66 this.marginControlContainer_ = |
| 67 new print_preview.MarginControlContainer(this.printTicketStore_); |
| 68 this.addChild(this.marginControlContainer_); |
| 69 |
| 70 /** |
| 71 * Current zoom level as a percentage. |
| 72 * @type {?number} |
| 73 * @private |
| 74 */ |
| 75 this.zoomLevel_ = null; |
| 76 |
| 77 /** |
| 78 * Current page offset which can be used to calculate scroll amount. |
| 79 * @type {print_preview.Coordinate2d} |
| 80 * @private |
| 81 */ |
| 82 this.pageOffset_ = null; |
| 83 |
| 84 /** |
| 85 * Whether the plugin has finished reloading. |
| 86 * @type {boolean} |
| 87 * @private |
| 88 */ |
| 89 this.isPluginReloaded_ = false; |
| 90 |
| 91 /** |
| 92 * Whether the document preview is ready. |
| 93 * @type {boolean} |
| 94 * @private |
| 95 */ |
| 96 this.isDocumentReady_ = false; |
| 97 }; |
| 98 |
| 99 /** |
| 100 * Event types dispatched by the preview area. |
| 101 * @enum {string} |
| 102 */ |
| 103 PreviewArea.EventType = { |
| 104 // Dispatched when the "Open system dialog" button is clicked. |
| 105 OPEN_SYSTEM_DIALOG_CLICK: |
| 106 'print_preview.PreviewArea.OPEN_SYSTEM_DIALOG_CLICK', |
| 107 |
| 108 // Dispatched when the document preview is complete. |
| 109 PREVIEW_GENERATION_DONE: |
| 110 'print_preview.PreviewArea.PREVIEW_GENERATION_DONE', |
| 111 |
| 112 // Dispatched when the document preview failed to be generated. |
| 113 PREVIEW_GENERATION_FAIL: |
| 114 'print_preview.PreviewArea.PREVIEW_GENERATION_FAIL', |
| 115 |
| 116 // Dispatched when a new document preview is being generated. |
| 117 PREVIEW_GENERATION_IN_PROGRESS: |
| 118 'print_preview.PreviewArea.PREVIEW_GENERATION_IN_PROGRESS' |
| 119 }; |
| 120 |
| 121 /** |
| 122 * CSS classes used by the preview area. |
| 123 * @enum {string} |
| 124 * @private |
| 125 */ |
| 126 PreviewArea.Classes_ = { |
| 127 COMPATIBILITY_OBJECT: 'preview-area-compatibility-object', |
| 128 LOADING_MESSAGE: 'preview-area-loading-message', |
| 129 CUSTOM_MESSAGE: 'preview-area-custom-message', |
| 130 CUSTOM_MESSAGE_TEXT: 'preview-area-custom-message-text', |
| 131 INVISIBLE: 'invisible', |
| 132 OPEN_SYSTEM_DIALOG_BUTTON: 'preview-area-open-system-dialog-button', |
| 133 OPEN_SYSTEM_DIALOG_BUTTON_THROBBER: |
| 134 'preview-area-open-system-dialog-button-throbber', |
| 135 OVERLAY: 'preview-area-overlay-layer', |
| 136 PDF_PLUGIN: 'preview-area-pdf-plugin', |
| 137 PREVIEW_FAILED_MESSAGE: 'preview-area-preview-failed-message' |
| 138 }; |
| 139 |
| 140 PreviewArea.prototype = { |
| 141 __proto__: print_preview.Component.prototype, |
| 142 |
| 143 /** |
| 144 * Should only be called after calling this.render(). |
| 145 * @return {boolean} Whether the preview area has a compatible plugin to |
| 146 * display the print preview in. |
| 147 */ |
| 148 get hasCompatiblePlugin() { |
| 149 return this.previewGenerator_ != null; |
| 150 }, |
| 151 |
| 152 /** |
| 153 * Processes a keyboard event that could possibly be used to change state of |
| 154 * the preview plugin. |
| 155 * @param {MouseEvent} e Mouse event to process. |
| 156 */ |
| 157 handleDirectionalKeyEvent: function(e) { |
| 158 // Make sure the PDF plugin is there. |
| 159 // We only care about: PageUp, PageDown, Left, Up, Right, Down. |
| 160 // If the user is holding a modifier key, ignore. |
| 161 if (!this.plugin_ || |
| 162 !arrayContains([33, 34, 37, 38, 39, 40], e.keyCode) || |
| 163 e.metaKey || e.altKey || e.shiftKey || e.ctrlKey) { |
| 164 return; |
| 165 } |
| 166 |
| 167 // Don't handle the key event for these elements. |
| 168 var tagName = document.activeElement.tagName; |
| 169 if (arrayContains(['INPUT', 'SELECT', 'EMBED'], tagName)) { |
| 170 return; |
| 171 } |
| 172 |
| 173 // For the most part, if any div of header was the last clicked element, |
| 174 // then the active element is the body. Starting with the last clicked |
| 175 // element, and work up the DOM tree to see if any element has a |
| 176 // scrollbar. If there exists a scrollbar, do not handle the key event |
| 177 // here. |
| 178 var element = e.target; |
| 179 while (element) { |
| 180 if (element.scrollHeight > element.clientHeight || |
| 181 element.scrollWidth > element.clientWidth) { |
| 182 return; |
| 183 } |
| 184 element = element.parentElement; |
| 185 } |
| 186 |
| 187 // No scroll bar anywhere, or the active element is something else, like a |
| 188 // button. Note: buttons have a bigger scrollHeight than clientHeight. |
| 189 this.plugin_.sendKeyEvent(e.keyCode); |
| 190 e.preventDefault(); |
| 191 }, |
| 192 |
| 193 showCustomMessage: function(message) { |
| 194 var customMessageTextEl = this.getElement().getElementsByClassName( |
| 195 PreviewArea.Classes_.CUSTOM_MESSAGE_TEXT)[0]; |
| 196 customMessageTextEl.textContent = message; |
| 197 var customMessageEl = this.getElement().getElementsByClassName( |
| 198 PreviewArea.Classes_.CUSTOM_MESSAGE)[0]; |
| 199 setIsVisible(customMessageEl, true); |
| 200 }, |
| 201 |
| 202 /** @override */ |
| 203 decorateInternal: function() { |
| 204 this.marginControlContainer_.decorate(this.getElement()); |
| 205 }, |
| 206 |
| 207 /** @override */ |
| 208 enterDocument: function() { |
| 209 print_preview.Component.prototype.enterDocument.call(this); |
| 210 this.tracker.add( |
| 211 this.openSystemDialogButton_, |
| 212 'click', |
| 213 this.onOpenSystemDialogButtonClick_.bind(this)); |
| 214 |
| 215 this.tracker.add( |
| 216 this.printTicketStore_, |
| 217 print_preview.PrintTicketStore.EventType.INITIALIZE, |
| 218 this.onTicketChange_.bind(this)); |
| 219 this.tracker.add( |
| 220 this.printTicketStore_, |
| 221 print_preview.PrintTicketStore.EventType.TICKET_CHANGE, |
| 222 this.onTicketChange_.bind(this)); |
| 223 this.tracker.add( |
| 224 this.printTicketStore_, |
| 225 print_preview.PrintTicketStore.EventType.CAPABILITIES_CHANGE, |
| 226 this.onTicketChange_.bind(this)); |
| 227 |
| 228 if (this.checkPluginCompatibility_()) { |
| 229 this.previewGenerator_ = new print_preview.PreviewGenerator( |
| 230 this.destinationStore_, this.printTicketStore_, this.nativeLayer_); |
| 231 this.tracker.add( |
| 232 this.previewGenerator_, |
| 233 print_preview.PreviewGenerator.EventType.PREVIEW_START, |
| 234 this.onPreviewStart_.bind(this)); |
| 235 this.tracker.add( |
| 236 this.previewGenerator_, |
| 237 print_preview.PreviewGenerator.EventType.PAGE_READY, |
| 238 this.onPagePreviewReady_.bind(this)); |
| 239 this.tracker.add( |
| 240 this.previewGenerator_, |
| 241 print_preview.PreviewGenerator.EventType.FAIL, |
| 242 this.onPreviewGenerationFail_.bind(this)); |
| 243 this.tracker.add( |
| 244 this.previewGenerator_, |
| 245 print_preview.PreviewGenerator.EventType.DOCUMENT_READY, |
| 246 this.onDocumentReady_.bind(this)); |
| 247 } else { |
| 248 // Hide loading message and show no plugin message. |
| 249 this.hideLoadingMessage_(); |
| 250 this.showCustomMessage(localStrings.getString('noPlugin')); |
| 251 } |
| 252 }, |
| 253 |
| 254 /** @override */ |
| 255 exitDocument: function() { |
| 256 print_preview.Component.prototype.exitDocument.call(this); |
| 257 if (this.previewGenerator_) { |
| 258 this.previewGenerator_.removeEventListeners(); |
| 259 } |
| 260 }, |
| 261 |
| 262 /** |
| 263 * @return {Element} Preview overlay element. |
| 264 * @private |
| 265 */ |
| 266 get overlayEl_() { |
| 267 return this.getElement().getElementsByClassName( |
| 268 PreviewArea.Classes_.OVERLAY)[0]; |
| 269 }, |
| 270 |
| 271 /** |
| 272 * @return {HTMLButtonElement} Button used to open the system dialog in the |
| 273 * event that no compatible preview plugin is found. |
| 274 * @private |
| 275 */ |
| 276 get openSystemDialogButton_() { |
| 277 return this.getElement().getElementsByClassName( |
| 278 PreviewArea.Classes_.OPEN_SYSTEM_DIALOG_BUTTON)[0]; |
| 279 }, |
| 280 |
| 281 /** |
| 282 * Checks to see if a suitable plugin for rendering the preview exists. If |
| 283 * one does not exist, then an error message will be displayed. |
| 284 * @return {boolean} Whether Chromium has a suitable plugin for rendering |
| 285 * the preview. |
| 286 * @private |
| 287 */ |
| 288 checkPluginCompatibility_: function() { |
| 289 var compatObj = this.getElement().getElementsByClassName( |
| 290 PreviewArea.Classes_.COMPATIBILITY_OBJECT)[0]; |
| 291 var isCompatible = |
| 292 compatObj.onload && |
| 293 compatObj.goToPage && |
| 294 compatObj.removePrintButton && |
| 295 compatObj.loadPreviewPage && |
| 296 compatObj.printPreviewPageCount && |
| 297 compatObj.resetPrintPreviewUrl && |
| 298 compatObj.onPluginSizeChanged && |
| 299 compatObj.onScroll && |
| 300 compatObj.pageXOffset && |
| 301 compatObj.pageYOffset && |
| 302 compatObj.setZoomLevel && |
| 303 compatObj.setPageNumbers && |
| 304 compatObj.setPageXOffset && |
| 305 compatObj.setPageYOffset && |
| 306 compatObj.getHorizontalScrollbarThickness && |
| 307 compatObj.getVerticalScrollbarThickness && |
| 308 compatObj.getPageLocationNormalized && |
| 309 compatObj.getHeight && |
| 310 compatObj.getWidth; |
| 311 compatObj.parentElement.removeChild(compatObj); |
| 312 return isCompatible; |
| 313 }, |
| 314 |
| 315 /** |
| 316 * Hides the loading message. |
| 317 * @private |
| 318 */ |
| 319 hideLoadingMessage_: function() { |
| 320 var loadingMessage = this.getElement().getElementsByClassName( |
| 321 PreviewArea.Classes_.LOADING_MESSAGE)[0]; |
| 322 setIsVisible(loadingMessage, false); |
| 323 }, |
| 324 |
| 325 /** |
| 326 * Creates a preview plugin and adds it to the DOM. |
| 327 * @param {string} srcUrl Initial URL of the plugin. |
| 328 * @private |
| 329 */ |
| 330 createPlugin_: function(srcUrl) { |
| 331 if (this.plugin_) { |
| 332 throw Error('Pdf preview plugin already created'); |
| 333 } |
| 334 this.plugin_ = document.createElement('embed'); |
| 335 // NOTE: The plugin's 'id' field must be set to 'pdf-viewer' since |
| 336 // chrome/renderer/print_web_view_helper.cc actually references it. |
| 337 this.plugin_.setAttribute('id', 'pdf-viewer'); |
| 338 this.plugin_.setAttribute('class', 'preview-area-plugin'); |
| 339 this.plugin_.setAttribute( |
| 340 'type', 'application/x-google-chrome-print-preview-pdf'); |
| 341 this.plugin_.setAttribute('src', srcUrl); |
| 342 this.plugin_.setAttribute('aria-live', 'polite'); |
| 343 this.plugin_.setAttribute('aria-atomic', 'true'); |
| 344 this.getElement().appendChild(this.plugin_); |
| 345 |
| 346 global['onPreviewPluginLoad'] = this.onPluginLoad_.bind(this); |
| 347 this.plugin_.onload('onPreviewPluginLoad()'); |
| 348 |
| 349 global['onPreviewPluginVisualStateChange'] = |
| 350 this.onPreviewVisualStateChange_.bind(this); |
| 351 this.plugin_.onScroll('onPreviewPluginVisualStateChange()'); |
| 352 this.plugin_.onPluginSizeChanged('onPreviewPluginVisualStateChange()'); |
| 353 |
| 354 this.plugin_.removePrintButton(); |
| 355 this.plugin_.grayscale(!this.printTicketStore_.isColorEnabled()); |
| 356 }, |
| 357 |
| 358 /** |
| 359 * Dispatches a PREVIEW_GENERATION_DONE event if all conditions are met. |
| 360 * @private |
| 361 */ |
| 362 dispatchPreviewGenerationDoneIfReady_: function() { |
| 363 if (this.isDocumentReady_ && this.isPluginReloaded_) { |
| 364 cr.dispatchSimpleEvent( |
| 365 this, PreviewArea.EventType.PREVIEW_GENERATION_DONE); |
| 366 } |
| 367 }, |
| 368 |
| 369 /** |
| 370 * Called when the open-system-dialog button is clicked. Disables the |
| 371 * button, shows the throbber, and dispatches the OPEN_SYSTEM_DIALOG_CLICK |
| 372 * event. |
| 373 * @private |
| 374 */ |
| 375 onOpenSystemDialogButtonClick_: function() { |
| 376 this.openSystemDialogButton_.disabled = true; |
| 377 var openSystemDialogThrobber = this.getElement().getElementsByClassName( |
| 378 PreviewArea.Classes_.OPEN_SYSTEM_DIALOG_BUTTON_THROBBER)[0]; |
| 379 setIsVisible(openSystemDialogThrobber, true); |
| 380 cr.dispatchSimpleEvent( |
| 381 this, PreviewArea.EventType.OPEN_SYSTEM_DIALOG_CLICK); |
| 382 }, |
| 383 |
| 384 /** |
| 385 * Called when the print ticket changes. Updates the preview. |
| 386 * @private |
| 387 */ |
| 388 onTicketChange_: function() { |
| 389 if (this.previewGenerator_ && this.printTicketStore_.isTicketValid()) { |
| 390 this.previewGenerator_.requestPreview(); |
| 391 } |
| 392 }, |
| 393 |
| 394 /** |
| 395 * Called when the preview generator begins loading the preview. |
| 396 * @param {cr.Event} Contains the URL to initialize the plugin to. |
| 397 * @private |
| 398 */ |
| 399 onPreviewStart_: function(event) { |
| 400 this.isDocumentReady_ = false; |
| 401 this.isPluginReloaded_ = false; |
| 402 if (!this.plugin_) { |
| 403 this.createPlugin_(event.previewUrl); |
| 404 } |
| 405 this.plugin_.goToPage('0'); |
| 406 this.plugin_.resetPrintPreviewUrl(event.previewUrl); |
| 407 this.plugin_.reload(); |
| 408 this.plugin_.grayscale(!this.printTicketStore_.isColorEnabled()); |
| 409 cr.dispatchSimpleEvent( |
| 410 this, PreviewArea.EventType.PREVIEW_GENERATION_IN_PROGRESS); |
| 411 }, |
| 412 |
| 413 /** |
| 414 * Called when a page preview has been generated. Updates the plugin with |
| 415 * the new page. |
| 416 * @param {cr.Event} event Contains information about the page preview. |
| 417 * @private |
| 418 */ |
| 419 onPagePreviewReady_: function(event) { |
| 420 this.plugin_.loadPreviewPage(event.previewUrl, event.previewIndex); |
| 421 }, |
| 422 |
| 423 /** |
| 424 * Called when the preview generation is complete and the document is ready |
| 425 * to print. |
| 426 * @private |
| 427 */ |
| 428 onDocumentReady_: function(event) { |
| 429 this.isDocumentReady_ = true; |
| 430 this.dispatchPreviewGenerationDoneIfReady_(); |
| 431 }, |
| 432 |
| 433 /** |
| 434 * Called when the generation of a preview fails. Shows an error message. |
| 435 * @private |
| 436 */ |
| 437 onPreviewGenerationFail_: function() { |
| 438 this.overlayEl_.classList.remove(PreviewArea.Classes_.INVISIBLE); |
| 439 this.hideLoadingMessage_(); |
| 440 var previewFailedMessage = this.getElement().getElementsByClassName( |
| 441 PreviewArea.Classes_.PREVIEW_FAILED_MESSAGE)[0]; |
| 442 setIsVisible(previewFailedMessage, true); |
| 443 cr.dispatchSimpleEvent( |
| 444 this, PreviewArea.EventType.PREVIEW_GENERATION_FAIL); |
| 445 }, |
| 446 |
| 447 /** |
| 448 * Called when the plugin loads. This is a consequence of calling |
| 449 * plugin.reload(). Certain plugin state can only be set after the plugin |
| 450 * has loaded. |
| 451 * @private |
| 452 */ |
| 453 onPluginLoad_: function() { |
| 454 // Setting the plugin's page count can only be called after the plugin is |
| 455 // loaded and the document must be modifiable. |
| 456 if (this.printTicketStore_.isDocumentModifiable) { |
| 457 this.plugin_.printPreviewPageCount( |
| 458 this.printTicketStore_.getPageNumberSet().size); |
| 459 } |
| 460 this.plugin_.setPageNumbers(JSON.stringify( |
| 461 this.printTicketStore_.getPageNumberSet().asArray())); |
| 462 if (this.zoomLevel_ != null && this.pageOffset_ != null) { |
| 463 this.plugin_.setZoomLevel(this.zoomLevel_); |
| 464 this.plugin_.setPageXOffset(this.pageOffset_.x); |
| 465 this.plugin_.setPageYOffset(this.pageOffset_.y); |
| 466 } else { |
| 467 this.plugin_.fitToHeight(); |
| 468 } |
| 469 this.overlayEl_.classList.add(PreviewArea.Classes_.INVISIBLE); |
| 470 this.hideLoadingMessage_(); |
| 471 this.isPluginReloaded_ = true; |
| 472 this.dispatchPreviewGenerationDoneIfReady_(); |
| 473 }, |
| 474 |
| 475 /** |
| 476 * Called when the preview plugin's visual state has changed. This is a |
| 477 * consequence of scrolling or zooming the plugin. Updates the custom |
| 478 * margins component if shown. |
| 479 * @private |
| 480 */ |
| 481 onPreviewVisualStateChange_: function() { |
| 482 if (this.isPluginReloaded_) { |
| 483 this.zoomLevel_ = this.plugin_.getZoomLevel(); |
| 484 this.pageOffset_ = new print_preview.Coordinate2d( |
| 485 this.plugin_.pageXOffset(), this.plugin_.pageYOffset()); |
| 486 } |
| 487 var normalized = this.plugin_.getPageLocationNormalized().split(';'); |
| 488 var pluginWidth = this.plugin_.getWidth(); |
| 489 var pluginHeight = this.plugin_.getHeight(); |
| 490 var translationTransform = new print_preview.Coordinate2d( |
| 491 parseFloat(normalized[0]) * pluginWidth - 9.0, |
| 492 parseFloat(normalized[1]) * pluginHeight - 9.0); |
| 493 this.marginControlContainer_.updateTranslationTransform( |
| 494 translationTransform); |
| 495 var pageWidthInPixels = parseFloat(normalized[2]) * pluginWidth; |
| 496 this.marginControlContainer_.updateScaleTransform( |
| 497 pageWidthInPixels / this.printTicketStore_.pageSize.width); |
| 498 this.marginControlContainer_.updateClippingMask( |
| 499 new print_preview.Size( |
| 500 pluginWidth - this.plugin_.getVerticalScrollbarThickness(), |
| 501 pluginHeight - this.plugin_.getHorizontalScrollbarThickness())); |
| 502 } |
| 503 }; |
| 504 |
| 505 // Export |
| 506 return { |
| 507 PreviewArea: PreviewArea |
| 508 }; |
| 509 }); |
OLD | NEW |