Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(292)

Side by Side Diff: chrome/browser/resources/print_preview/previewarea/preview_area.js

Issue 10108001: Refactor print preview web ui (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Address reviewer comments Created 8 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 * Timeout object used to display a loading message if the preview is taking
100 * a long time to generate.
101 * @type {Object}
102 * @private
103 */
104 this.loadingTimeout_ = null;
105
106 /**
107 * Overlay element.
108 * @type {HTMLElement}
109 * @private
110 */
111 this.overlayEl_ = null;
112
113 /**
114 * The "Open system dialog" button.
115 * @type {HTMLButtonElement}
116 * @private
117 */
118 this.openSystemDialogButton_ = null;
119 };
120
121 /**
122 * Event types dispatched by the preview area.
123 * @enum {string}
124 */
125 PreviewArea.EventType = {
126 // Dispatched when the "Open system dialog" button is clicked.
127 OPEN_SYSTEM_DIALOG_CLICK:
128 'print_preview.PreviewArea.OPEN_SYSTEM_DIALOG_CLICK',
129
130 // Dispatched when the document preview is complete.
131 PREVIEW_GENERATION_DONE:
132 'print_preview.PreviewArea.PREVIEW_GENERATION_DONE',
133
134 // Dispatched when the document preview failed to be generated.
135 PREVIEW_GENERATION_FAIL:
136 'print_preview.PreviewArea.PREVIEW_GENERATION_FAIL',
137
138 // Dispatched when a new document preview is being generated.
139 PREVIEW_GENERATION_IN_PROGRESS:
140 'print_preview.PreviewArea.PREVIEW_GENERATION_IN_PROGRESS'
141 };
142
143 /**
144 * CSS classes used by the preview area.
145 * @enum {string}
146 * @private
147 */
148 PreviewArea.Classes_ = {
149 COMPATIBILITY_OBJECT: 'preview-area-compatibility-object',
150 CUSTOM_MESSAGE_TEXT: 'preview-area-custom-message-text',
151 MESSAGE: 'preview-area-message',
152 INVISIBLE: 'invisible',
153 OPEN_SYSTEM_DIALOG_BUTTON: 'preview-area-open-system-dialog-button',
154 OPEN_SYSTEM_DIALOG_BUTTON_THROBBER:
155 'preview-area-open-system-dialog-button-throbber',
156 OVERLAY: 'preview-area-overlay-layer',
157 PDF_PLUGIN: 'preview-area-pdf-plugin'
158 };
159
160 /**
161 * Enumeration of IDs shown in the preview area.
162 * @enum {string}
163 * @private
164 */
165 PreviewArea.MessageId_ = {
166 CUSTOM: 'custom',
167 LOADING: 'loading',
168 PREVIEW_FAILED: 'preview-failed'
169 };
170
171 /**
172 * Maps message IDs to the CSS class that contains them.
173 * @type {object.<PreviewArea.MessageId_, string>}
174 * @private
175 */
176 PreviewArea.MessageIdClassMap_ = {};
177 PreviewArea.MessageIdClassMap_[PreviewArea.MessageId_.CUSTOM] =
178 'preview-area-custom-message';
179 PreviewArea.MessageIdClassMap_[PreviewArea.MessageId_.LOADING] =
180 'preview-area-loading-message';
181 PreviewArea.MessageIdClassMap_[PreviewArea.MessageId_.PREVIEW_FAILED] =
182 'preview-area-preview-failed-message';
183
184 /**
185 * Amount of time in milliseconds to wait after issueing a new preview before
186 * the loading message is shown.
187 * @type {number}
188 * @const
189 * @private
190 */
191 PreviewArea.LOADING_TIMEOUT_ = 200;
192
193 PreviewArea.prototype = {
194 __proto__: print_preview.Component.prototype,
195
196 /**
197 * Should only be called after calling this.render().
198 * @return {boolean} Whether the preview area has a compatible plugin to
199 * display the print preview in.
200 */
201 get hasCompatiblePlugin() {
202 return this.previewGenerator_ != null;
203 },
204
205 /**
206 * Processes a keyboard event that could possibly be used to change state of
207 * the preview plugin.
208 * @param {MouseEvent} e Mouse event to process.
209 */
210 handleDirectionalKeyEvent: function(e) {
211 // Make sure the PDF plugin is there.
212 // We only care about: PageUp, PageDown, Left, Up, Right, Down.
213 // If the user is holding a modifier key, ignore.
214 if (!this.plugin_ ||
215 !arrayContains([33, 34, 37, 38, 39, 40], e.keyCode) ||
216 e.metaKey || e.altKey || e.shiftKey || e.ctrlKey) {
217 return;
218 }
219
220 // Don't handle the key event for these elements.
221 var tagName = document.activeElement.tagName;
222 if (arrayContains(['INPUT', 'SELECT', 'EMBED'], tagName)) {
223 return;
224 }
225
226 // For the most part, if any div of header was the last clicked element,
227 // then the active element is the body. Starting with the last clicked
228 // element, and work up the DOM tree to see if any element has a
229 // scrollbar. If there exists a scrollbar, do not handle the key event
230 // here.
231 var element = e.target;
232 while (element) {
233 if (element.scrollHeight > element.clientHeight ||
234 element.scrollWidth > element.clientWidth) {
235 return;
236 }
237 element = element.parentElement;
238 }
239
240 // No scroll bar anywhere, or the active element is something else, like a
241 // button. Note: buttons have a bigger scrollHeight than clientHeight.
242 this.plugin_.sendKeyEvent(e.keyCode);
243 e.preventDefault();
244 },
245
246 /**
247 * Shows a custom message on the preview area's overlay.
248 * @param {string} message Custom message to show.
249 */
250 showCustomMessage: function(message) {
251 this.showMessage_(PreviewArea.MessageId_.CUSTOM, message);
252 },
253
254 /** @override */
255 enterDocument: function() {
256 print_preview.Component.prototype.enterDocument.call(this);
257 this.tracker.add(
258 this.openSystemDialogButton_,
259 'click',
260 this.onOpenSystemDialogButtonClick_.bind(this));
261
262 this.tracker.add(
263 this.printTicketStore_,
264 print_preview.PrintTicketStore.EventType.INITIALIZE,
265 this.onTicketChange_.bind(this));
266 this.tracker.add(
267 this.printTicketStore_,
268 print_preview.PrintTicketStore.EventType.TICKET_CHANGE,
269 this.onTicketChange_.bind(this));
270 this.tracker.add(
271 this.printTicketStore_,
272 print_preview.PrintTicketStore.EventType.CAPABILITIES_CHANGE,
273 this.onTicketChange_.bind(this));
274
275 if (this.checkPluginCompatibility_()) {
276 this.previewGenerator_ = new print_preview.PreviewGenerator(
277 this.destinationStore_, this.printTicketStore_, this.nativeLayer_);
278 this.tracker.add(
279 this.previewGenerator_,
280 print_preview.PreviewGenerator.EventType.PREVIEW_START,
281 this.onPreviewStart_.bind(this));
282 this.tracker.add(
283 this.previewGenerator_,
284 print_preview.PreviewGenerator.EventType.PAGE_READY,
285 this.onPagePreviewReady_.bind(this));
286 this.tracker.add(
287 this.previewGenerator_,
288 print_preview.PreviewGenerator.EventType.FAIL,
289 this.onPreviewGenerationFail_.bind(this));
290 this.tracker.add(
291 this.previewGenerator_,
292 print_preview.PreviewGenerator.EventType.DOCUMENT_READY,
293 this.onDocumentReady_.bind(this));
294 } else {
295 // Hide loading message and show no plugin message.
296 this.setIsLoadingMessageVisible_(false);
297 this.showCustomMessage(localStrings.getString('noPlugin'));
298 }
299 },
300
301 /** @override */
302 exitDocument: function() {
303 print_preview.Component.prototype.exitDocument.call(this);
304 if (this.previewGenerator_) {
305 this.previewGenerator_.removeEventListeners();
306 }
307 this.overlayEl_ = null;
308 this.openSystemDialogButton_ = null;
309 },
310
311 /** @override */
312 decorateInternal: function() {
313 this.marginControlContainer_.decorate(this.getElement());
314 this.overlayEl_ = this.getElement().getElementsByClassName(
315 PreviewArea.Classes_.OVERLAY)[0];
316 this.openSystemDialogButton_ = this.getElement().getElementsByClassName(
317 PreviewArea.Classes_.OPEN_SYSTEM_DIALOG_BUTTON)[0];
318 },
319
320 /**
321 * Checks to see if a suitable plugin for rendering the preview exists. If
322 * one does not exist, then an error message will be displayed.
323 * @return {boolean} Whether Chromium has a suitable plugin for rendering
324 * the preview.
325 * @private
326 */
327 checkPluginCompatibility_: function() {
328 var compatObj = this.getElement().getElementsByClassName(
329 PreviewArea.Classes_.COMPATIBILITY_OBJECT)[0];
330 var isCompatible =
331 compatObj.onload &&
332 compatObj.goToPage &&
333 compatObj.removePrintButton &&
334 compatObj.loadPreviewPage &&
335 compatObj.printPreviewPageCount &&
336 compatObj.resetPrintPreviewUrl &&
337 compatObj.onPluginSizeChanged &&
338 compatObj.onScroll &&
339 compatObj.pageXOffset &&
340 compatObj.pageYOffset &&
341 compatObj.setZoomLevel &&
342 compatObj.setPageNumbers &&
343 compatObj.setPageXOffset &&
344 compatObj.setPageYOffset &&
345 compatObj.getHorizontalScrollbarThickness &&
346 compatObj.getVerticalScrollbarThickness &&
347 compatObj.getPageLocationNormalized &&
348 compatObj.getHeight &&
349 compatObj.getWidth;
350 compatObj.parentElement.removeChild(compatObj);
351 return isCompatible;
352 },
353
354 /**
355 * Shows a given message on the overlay.
356 * @param {print_preview.PreviewArea.MessageId_} messageId ID of the message
357 * to show.
358 * @param {string=} opt_message Optional message to show that can be used
359 * by some message IDs.
360 * @private
361 */
362 showMessage_: function(messageId, opt_message) {
363 // Hide all messages.
364 var messageEls = this.getElement().getElementsByClassName(
365 PreviewArea.Classes_.MESSAGE);
366 for (var i = 0, messageEl; messageEl = messageEls[i]; i++) {
367 setIsVisible(messageEl, false);
368 }
369
370 // Show specific message.
371 if (messageId == PreviewArea.MessageId_.CUSTOM) {
372 var customMessageTextEl = this.getElement().getElementsByClassName(
373 PreviewArea.Classes_.CUSTOM_MESSAGE_TEXT)[0];
374 customMessageTextEl.textContent = opt_message;
375 }
376 var messageEl = this.getElement().getElementsByClassName(
377 PreviewArea.MessageIdClassMap_[messageId])[0];
378 setIsVisible(messageEl, true);
379
380 // Show overlay.
381 this.overlayEl_.classList.remove(PreviewArea.Classes_.INVISIBLE);
382 },
383
384 /**
385 * Hides the message overlay.
386 * @private
387 */
388 hideOverlay_: function() {
389 this.overlayEl_.classList.add(PreviewArea.Classes_.INVISIBLE);
390 },
391
392 /**
393 * Creates a preview plugin and adds it to the DOM.
394 * @param {string} srcUrl Initial URL of the plugin.
395 * @private
396 */
397 createPlugin_: function(srcUrl) {
398 if (this.plugin_) {
399 throw Error('Pdf preview plugin already created');
400 }
401 this.plugin_ = document.createElement('embed');
402 // NOTE: The plugin's 'id' field must be set to 'pdf-viewer' since
403 // chrome/renderer/print_web_view_helper.cc actually references it.
404 this.plugin_.setAttribute('id', 'pdf-viewer');
405 this.plugin_.setAttribute('class', 'preview-area-plugin');
406 this.plugin_.setAttribute(
407 'type', 'application/x-google-chrome-print-preview-pdf');
408 this.plugin_.setAttribute('src', srcUrl);
409 this.plugin_.setAttribute('aria-live', 'polite');
410 this.plugin_.setAttribute('aria-atomic', 'true');
411 this.getElement().appendChild(this.plugin_);
412
413 global['onPreviewPluginLoad'] = this.onPluginLoad_.bind(this);
414 this.plugin_.onload('onPreviewPluginLoad()');
415
416 global['onPreviewPluginVisualStateChange'] =
417 this.onPreviewVisualStateChange_.bind(this);
418 this.plugin_.onScroll('onPreviewPluginVisualStateChange()');
419 this.plugin_.onPluginSizeChanged('onPreviewPluginVisualStateChange()');
420
421 this.plugin_.removePrintButton();
422 this.plugin_.grayscale(!this.printTicketStore_.isColorEnabled());
423 },
424
425 /**
426 * Dispatches a PREVIEW_GENERATION_DONE event if all conditions are met.
427 * @private
428 */
429 dispatchPreviewGenerationDoneIfReady_: function() {
430 if (this.isDocumentReady_ && this.isPluginReloaded_) {
431 cr.dispatchSimpleEvent(
432 this, PreviewArea.EventType.PREVIEW_GENERATION_DONE);
433 }
434 },
435
436 /**
437 * Called when the open-system-dialog button is clicked. Disables the
438 * button, shows the throbber, and dispatches the OPEN_SYSTEM_DIALOG_CLICK
439 * event.
440 * @private
441 */
442 onOpenSystemDialogButtonClick_: function() {
443 this.openSystemDialogButton_.disabled = true;
444 var openSystemDialogThrobber = this.getElement().getElementsByClassName(
445 PreviewArea.Classes_.OPEN_SYSTEM_DIALOG_BUTTON_THROBBER)[0];
446 setIsVisible(openSystemDialogThrobber, true);
447 cr.dispatchSimpleEvent(
448 this, PreviewArea.EventType.OPEN_SYSTEM_DIALOG_CLICK);
449 },
450
451 /**
452 * Called when the print ticket changes. Updates the preview.
453 * @private
454 */
455 onTicketChange_: function() {
456 if (this.previewGenerator_ && this.printTicketStore_.isTicketValid()) {
457 if (this.previewGenerator_.requestPreview()) {
458 this.loadingTimeout_ = setTimeout(
459 this.showMessage_.bind(this, PreviewArea.MessageId_.LOADING),
460 PreviewArea.LOADING_TIMEOUT_);
461 }
462 }
463 },
464
465 /**
466 * Called when the preview generator begins loading the preview.
467 * @param {cr.Event} Contains the URL to initialize the plugin to.
468 * @private
469 */
470 onPreviewStart_: function(event) {
471 this.isDocumentReady_ = false;
472 this.isPluginReloaded_ = false;
473 if (!this.plugin_) {
474 this.createPlugin_(event.previewUrl);
475 }
476 this.plugin_.goToPage('0');
477 this.plugin_.resetPrintPreviewUrl(event.previewUrl);
478 this.plugin_.reload();
479 this.plugin_.grayscale(!this.printTicketStore_.isColorEnabled());
480 cr.dispatchSimpleEvent(
481 this, PreviewArea.EventType.PREVIEW_GENERATION_IN_PROGRESS);
482 },
483
484 /**
485 * Called when a page preview has been generated. Updates the plugin with
486 * the new page.
487 * @param {cr.Event} event Contains information about the page preview.
488 * @private
489 */
490 onPagePreviewReady_: function(event) {
491 this.plugin_.loadPreviewPage(event.previewUrl, event.previewIndex);
492 },
493
494 /**
495 * Called when the preview generation is complete and the document is ready
496 * to print.
497 * @private
498 */
499 onDocumentReady_: function(event) {
500 this.isDocumentReady_ = true;
501 this.dispatchPreviewGenerationDoneIfReady_();
502 },
503
504 /**
505 * Called when the generation of a preview fails. Shows an error message.
506 * @private
507 */
508 onPreviewGenerationFail_: function() {
509 this.showMessage_(PreviewArea.MessageId_.PREVIEW_FAILED);
510 cr.dispatchSimpleEvent(
511 this, PreviewArea.EventType.PREVIEW_GENERATION_FAIL);
512 },
513
514 /**
515 * Called when the plugin loads. This is a consequence of calling
516 * plugin.reload(). Certain plugin state can only be set after the plugin
517 * has loaded.
518 * @private
519 */
520 onPluginLoad_: function() {
521 if (this.loadingTimeout_) {
522 clearTimeout(this.loadingTimeout_);
523 this.loadingTimeout_ = null;
524 }
525 // Setting the plugin's page count can only be called after the plugin is
526 // loaded and the document must be modifiable.
527 if (this.printTicketStore_.isDocumentModifiable) {
528 this.plugin_.printPreviewPageCount(
529 this.printTicketStore_.getPageNumberSet().size);
530 }
531 this.plugin_.setPageNumbers(JSON.stringify(
532 this.printTicketStore_.getPageNumberSet().asArray()));
533 if (this.zoomLevel_ != null && this.pageOffset_ != null) {
534 this.plugin_.setZoomLevel(this.zoomLevel_);
535 this.plugin_.setPageXOffset(this.pageOffset_.x);
536 this.plugin_.setPageYOffset(this.pageOffset_.y);
537 } else {
538 this.plugin_.fitToHeight();
539 }
540 this.hideOverlay_();
541 this.isPluginReloaded_ = true;
542 this.dispatchPreviewGenerationDoneIfReady_();
543 },
544
545 /**
546 * Called when the preview plugin's visual state has changed. This is a
547 * consequence of scrolling or zooming the plugin. Updates the custom
548 * margins component if shown.
549 * @private
550 */
551 onPreviewVisualStateChange_: function() {
552 if (this.isPluginReloaded_) {
553 this.zoomLevel_ = this.plugin_.getZoomLevel();
554 this.pageOffset_ = new print_preview.Coordinate2d(
555 this.plugin_.pageXOffset(), this.plugin_.pageYOffset());
556 }
557 var normalized = this.plugin_.getPageLocationNormalized().split(';');
558 var pluginWidth = this.plugin_.getWidth();
559 var pluginHeight = this.plugin_.getHeight();
560 var translationTransform = new print_preview.Coordinate2d(
561 parseFloat(normalized[0]) * pluginWidth,
562 parseFloat(normalized[1]) * pluginHeight);
563 this.marginControlContainer_.updateTranslationTransform(
564 translationTransform);
565 var pageWidthInPixels = parseFloat(normalized[2]) * pluginWidth;
566 this.marginControlContainer_.updateScaleTransform(
567 pageWidthInPixels / this.printTicketStore_.pageSize.width);
568 this.marginControlContainer_.updateClippingMask(
569 new print_preview.Size(
570 pluginWidth - this.plugin_.getVerticalScrollbarThickness(),
571 pluginHeight - this.plugin_.getHorizontalScrollbarThickness()));
572 }
573 };
574
575 // Export
576 return {
577 PreviewArea: PreviewArea
578 };
579 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698