Allow only one bookmark to be added for multiple fast starring
[chromium-blink-merge.git] / chrome / browser / resources / pdf / pdf.js
blob3fb61482a5e994149ec83f6ad9d9a90705b18a9a
1 // Copyright 2013 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 'use strict';
7 /**
8 * @return {number} Width of a scrollbar in pixels
9 */
10 function getScrollbarWidth() {
11 var div = document.createElement('div');
12 div.style.visibility = 'hidden';
13 div.style.overflow = 'scroll';
14 div.style.width = '50px';
15 div.style.height = '50px';
16 div.style.position = 'absolute';
17 document.body.appendChild(div);
18 var result = div.offsetWidth - div.clientWidth;
19 div.parentNode.removeChild(div);
20 return result;
23 /**
24 * Return the filename component of a URL.
25 * @param {string} url The URL to get the filename from.
26 * @return {string} The filename component.
28 function getFilenameFromURL(url) {
29 var components = url.split(/\/|\\/);
30 return components[components.length - 1];
33 /**
34 * Called when navigation happens in the current tab.
35 * @param {string} url The url to be opened in the current tab.
37 function onNavigateInCurrentTab(url) {
38 window.location.href = url;
41 /**
42 * Called when navigation happens in the new tab.
43 * @param {string} url The url to be opened in the new tab.
45 function onNavigateInNewTab(url) {
46 // Prefer the tabs API because it guarantees we can just open a new tab.
47 // window.open doesn't have this guarantee.
48 if (chrome.tabs)
49 chrome.tabs.create({ url: url});
50 else
51 window.open(url);
54 /**
55 * Whether keydown events should currently be ignored. Events are ignored when
56 * an editable element has focus, to allow for proper editing controls.
57 * @param {HTMLElement} activeElement The currently selected DOM node.
58 * @return {boolean} True if keydown events should be ignored.
60 function shouldIgnoreKeyEvents(activeElement) {
61 while (activeElement.shadowRoot != null &&
62 activeElement.shadowRoot.activeElement != null) {
63 activeElement = activeElement.shadowRoot.activeElement;
66 return (activeElement.isContentEditable ||
67 activeElement.tagName == 'INPUT' ||
68 activeElement.tagName == 'TEXTAREA');
71 /**
72 * The minimum number of pixels to offset the toolbar by from the bottom and
73 * right side of the screen.
75 PDFViewer.MIN_TOOLBAR_OFFSET = 15;
77 /**
78 * The height of the toolbar along the top of the page. The document will be
79 * shifted down by this much in the viewport.
81 PDFViewer.MATERIAL_TOOLBAR_HEIGHT = 64;
83 /**
84 * Creates a new PDFViewer. There should only be one of these objects per
85 * document.
86 * @constructor
87 * @param {!BrowserApi} browserApi An object providing an API to the browser.
89 function PDFViewer(browserApi) {
90 this.browserApi_ = browserApi;
91 this.loadState_ = LoadState.LOADING;
92 this.parentWindow_ = null;
94 this.delayedScriptingMessages_ = [];
96 this.isPrintPreview_ = this.browserApi_.getStreamInfo().originalUrl.indexOf(
97 'chrome://print') == 0;
98 this.isMaterial_ = location.pathname.substring(1) === 'index-material.html';
100 // The sizer element is placed behind the plugin element to cause scrollbars
101 // to be displayed in the window. It is sized according to the document size
102 // of the pdf and zoom level.
103 this.sizer_ = $('sizer');
104 this.toolbar_ = $('toolbar');
105 this.pageIndicator_ = $('page-indicator');
106 this.progressBar_ = $('progress-bar');
107 this.passwordScreen_ = $('password-screen');
108 this.passwordScreen_.addEventListener('password-submitted',
109 this.onPasswordSubmitted_.bind(this));
110 this.errorScreen_ = $('error-screen');
112 // Create the viewport.
113 var topToolbarHeight =
114 this.isMaterial_ ? PDFViewer.MATERIAL_TOOLBAR_HEIGHT : 0;
115 this.viewport_ = new Viewport(window,
116 this.sizer_,
117 this.viewportChanged_.bind(this),
118 this.beforeZoom_.bind(this),
119 this.afterZoom_.bind(this),
120 getScrollbarWidth(),
121 this.browserApi_.getDefaultZoom(),
122 topToolbarHeight);
124 // Create the plugin object dynamically so we can set its src. The plugin
125 // element is sized to fill the entire window and is set to be fixed
126 // positioning, acting as a viewport. The plugin renders into this viewport
127 // according to the scroll position of the window.
128 this.plugin_ = document.createElement('embed');
129 // NOTE: The plugin's 'id' field must be set to 'plugin' since
130 // chrome/renderer/printing/print_web_view_helper.cc actually references it.
131 this.plugin_.id = 'plugin';
132 this.plugin_.type = 'application/x-google-chrome-pdf';
133 this.plugin_.addEventListener('message', this.handlePluginMessage_.bind(this),
134 false);
136 // Handle scripting messages from outside the extension that wish to interact
137 // with it. We also send a message indicating that extension has loaded and
138 // is ready to receive messages.
139 window.addEventListener('message', this.handleScriptingMessage.bind(this),
140 false);
142 document.title = decodeURIComponent(
143 getFilenameFromURL(this.browserApi_.getStreamInfo().originalUrl));
144 this.plugin_.setAttribute('src',
145 this.browserApi_.getStreamInfo().originalUrl);
146 this.plugin_.setAttribute('stream-url',
147 this.browserApi_.getStreamInfo().streamUrl);
148 var headers = '';
149 for (var header in this.browserApi_.getStreamInfo().responseHeaders) {
150 headers += header + ': ' +
151 this.browserApi_.getStreamInfo().responseHeaders[header] + '\n';
153 this.plugin_.setAttribute('headers', headers);
155 if (this.isMaterial_) {
156 this.plugin_.setAttribute('is-material', '');
157 this.plugin_.setAttribute('top-toolbar-height',
158 PDFViewer.MATERIAL_TOOLBAR_HEIGHT);
161 if (!this.browserApi_.getStreamInfo().embedded)
162 this.plugin_.setAttribute('full-frame', '');
163 document.body.appendChild(this.plugin_);
165 // Setup the button event listeners.
166 if (!this.isMaterial_) {
167 $('fit-to-width-button').addEventListener('click',
168 this.viewport_.fitToWidth.bind(this.viewport_));
169 $('fit-to-page-button').addEventListener('click',
170 this.viewport_.fitToPage.bind(this.viewport_));
171 $('zoom-in-button').addEventListener('click',
172 this.viewport_.zoomIn.bind(this.viewport_));
173 $('zoom-out-button').addEventListener('click',
174 this.viewport_.zoomOut.bind(this.viewport_));
175 $('save-button').addEventListener('click', this.save_.bind(this));
176 $('print-button').addEventListener('click', this.print_.bind(this));
179 if (this.isMaterial_) {
180 this.zoomToolbar_ = $('zoom-toolbar');
181 this.zoomToolbar_.addEventListener('fit-to-width',
182 this.viewport_.fitToWidth.bind(this.viewport_));
183 this.zoomToolbar_.addEventListener('fit-to-page',
184 this.fitToPage_.bind(this));
185 this.zoomToolbar_.addEventListener('zoom-in',
186 this.viewport_.zoomIn.bind(this.viewport_));
187 this.zoomToolbar_.addEventListener('zoom-out',
188 this.viewport_.zoomOut.bind(this.viewport_));
190 this.materialToolbar_ = $('material-toolbar');
191 this.materialToolbar_.docTitle = document.title;
192 this.materialToolbar_.addEventListener('save', this.save_.bind(this));
193 this.materialToolbar_.addEventListener('print', this.print_.bind(this));
194 this.materialToolbar_.addEventListener('rotate-right',
195 this.rotateClockwise_.bind(this));
196 this.materialToolbar_.addEventListener('rotate-left',
197 this.rotateCounterClockwise_.bind(this));
199 document.body.addEventListener('change-page', function(e) {
200 this.viewport_.goToPage(e.detail.page);
201 }.bind(this));
203 this.toolbarManager_ =
204 new ToolbarManager(window, this.materialToolbar_, this.zoomToolbar_);
206 // Must attach to mouseup on the plugin element, since it eats mousedown and
207 // click events.
208 this.plugin_.addEventListener(
209 'mouseup',
210 this.materialToolbar_.hideDropdowns.bind(this.materialToolbar_));
213 // Set up the ZoomManager.
214 this.zoomManager_ = new ZoomManager(
215 this.viewport_, this.browserApi_.setZoom.bind(this.browserApi_),
216 this.browserApi_.getDefaultZoom());
217 this.browserApi_.addZoomEventListener(
218 this.zoomManager_.onBrowserZoomChange.bind(this.zoomManager_));
220 // Setup the keyboard event listener.
221 document.addEventListener('keydown', this.handleKeyEvent_.bind(this));
222 document.addEventListener('mousemove', this.handleMouseEvent_.bind(this));
224 // Parse open pdf parameters.
225 this.paramsParser_ =
226 new OpenPDFParamsParser(this.getNamedDestination_.bind(this));
227 this.navigator_ = new Navigator(this.browserApi_.getStreamInfo().originalUrl,
228 this.viewport_, this.paramsParser_,
229 onNavigateInCurrentTab, onNavigateInNewTab);
230 this.viewportScroller_ =
231 new ViewportScroller(this.viewport_, this.plugin_, window);
234 PDFViewer.prototype = {
236 * @private
237 * Handle key events. These may come from the user directly or via the
238 * scripting API.
239 * @param {KeyboardEvent} e the event to handle.
241 handleKeyEvent_: function(e) {
242 var position = this.viewport_.position;
243 // Certain scroll events may be sent from outside of the extension.
244 var fromScriptingAPI = e.fromScriptingAPI;
246 if (shouldIgnoreKeyEvents(document.activeElement) || e.defaultPrevented)
247 return;
249 if (this.isMaterial_)
250 this.toolbarManager_.hideToolbarsAfterTimeout(e);
252 var pageUpHandler = function() {
253 // Go to the previous page if we are fit-to-page.
254 if (this.viewport_.fittingType == Viewport.FittingType.FIT_TO_PAGE) {
255 this.viewport_.goToPage(this.viewport_.getMostVisiblePage() - 1);
256 // Since we do the movement of the page.
257 e.preventDefault();
258 } else if (fromScriptingAPI) {
259 position.y -= this.viewport.size.height;
260 this.viewport.position = position;
262 }.bind(this);
263 var pageDownHandler = function() {
264 // Go to the next page if we are fit-to-page.
265 if (this.viewport_.fittingType == Viewport.FittingType.FIT_TO_PAGE) {
266 this.viewport_.goToPage(this.viewport_.getMostVisiblePage() + 1);
267 // Since we do the movement of the page.
268 e.preventDefault();
269 } else if (fromScriptingAPI) {
270 position.y += this.viewport.size.height;
271 this.viewport.position = position;
273 }.bind(this);
275 switch (e.keyCode) {
276 case 27: // Escape key.
277 if (this.isMaterial_)
278 this.toolbarManager_.hideSingleToolbarLayer();
279 return;
280 case 32: // Space key.
281 if (e.shiftKey)
282 pageUpHandler();
283 else
284 pageDownHandler();
285 return;
286 case 33: // Page up key.
287 pageUpHandler();
288 return;
289 case 34: // Page down key.
290 pageDownHandler();
291 return;
292 case 37: // Left arrow key.
293 if (!(e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)) {
294 // Go to the previous page if there are no horizontal scrollbars.
295 if (!this.viewport_.documentHasScrollbars().horizontal) {
296 this.viewport_.goToPage(this.viewport_.getMostVisiblePage() - 1);
297 // Since we do the movement of the page.
298 e.preventDefault();
299 } else if (fromScriptingAPI) {
300 position.x -= Viewport.SCROLL_INCREMENT;
301 this.viewport.position = position;
304 return;
305 case 38: // Up arrow key.
306 if (fromScriptingAPI) {
307 position.y -= Viewport.SCROLL_INCREMENT;
308 this.viewport.position = position;
310 return;
311 case 39: // Right arrow key.
312 if (!(e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)) {
313 // Go to the next page if there are no horizontal scrollbars.
314 if (!this.viewport_.documentHasScrollbars().horizontal) {
315 this.viewport_.goToPage(this.viewport_.getMostVisiblePage() + 1);
316 // Since we do the movement of the page.
317 e.preventDefault();
318 } else if (fromScriptingAPI) {
319 position.x += Viewport.SCROLL_INCREMENT;
320 this.viewport.position = position;
323 return;
324 case 40: // Down arrow key.
325 if (fromScriptingAPI) {
326 position.y += Viewport.SCROLL_INCREMENT;
327 this.viewport.position = position;
329 return;
330 case 65: // a key.
331 if (e.ctrlKey || e.metaKey) {
332 this.plugin_.postMessage({
333 type: 'selectAll'
335 // Since we do selection ourselves.
336 e.preventDefault();
338 return;
339 case 71: // g key.
340 if (this.isMaterial_ && (e.ctrlKey || e.metaKey)) {
341 this.toolbarManager_.showToolbars();
342 this.materialToolbar_.selectPageNumber();
343 // To prevent the default "find text" behaviour in Chrome.
344 e.preventDefault();
346 return;
347 case 219: // left bracket.
348 if (e.ctrlKey)
349 this.rotateCounterClockwise_();
350 return;
351 case 221: // right bracket.
352 if (e.ctrlKey)
353 this.rotateClockwise_();
354 return;
357 // Give print preview a chance to handle the key event.
358 if (!fromScriptingAPI && this.isPrintPreview_) {
359 this.sendScriptingMessage_({
360 type: 'sendKeyEvent',
361 keyEvent: SerializeKeyEvent(e)
363 } else if (this.isMaterial_) {
364 // Show toolbars as a fallback.
365 if (!(e.shiftKey || e.ctrlKey || e.altKey))
366 this.toolbarManager_.showToolbars();
370 handleMouseEvent_: function(e) {
371 if (this.isMaterial_)
372 this.toolbarManager_.showToolbarsForMouseMove(e);
376 * @private
377 * Rotate the plugin clockwise.
379 rotateClockwise_: function() {
380 this.plugin_.postMessage({
381 type: 'rotateClockwise'
386 * @private
387 * Rotate the plugin counter-clockwise.
389 rotateCounterClockwise_: function() {
390 this.plugin_.postMessage({
391 type: 'rotateCounterclockwise'
395 fitToPage_: function() {
396 this.viewport_.fitToPage();
397 this.toolbarManager_.forceHideTopToolbar();
401 * @private
402 * Notify the plugin to print.
404 print_: function() {
405 this.plugin_.postMessage({
406 type: 'print'
411 * @private
412 * Notify the plugin to save.
414 save_: function() {
415 this.plugin_.postMessage({
416 type: 'save'
421 * Fetches the page number corresponding to the given named destination from
422 * the plugin.
423 * @param {string} name The namedDestination to fetch page number from plugin.
425 getNamedDestination_: function(name) {
426 this.plugin_.postMessage({
427 type: 'getNamedDestination',
428 namedDestination: name
433 * @private
434 * Sends a 'documentLoaded' message to the PDFScriptingAPI if the document has
435 * finished loading.
437 sendDocumentLoadedMessage_: function() {
438 if (this.loadState_ == LoadState.LOADING)
439 return;
440 this.sendScriptingMessage_({
441 type: 'documentLoaded',
442 load_state: this.loadState_
447 * @private
448 * Handle open pdf parameters. This function updates the viewport as per
449 * the parameters mentioned in the url while opening pdf. The order is
450 * important as later actions can override the effects of previous actions.
451 * @param {Object} viewportPosition The initial position of the viewport to be
452 * displayed.
454 handleURLParams_: function(viewportPosition) {
455 if (viewportPosition.page != undefined)
456 this.viewport_.goToPage(viewportPosition.page);
457 if (viewportPosition.position) {
458 // Make sure we don't cancel effect of page parameter.
459 this.viewport_.position = {
460 x: this.viewport_.position.x + viewportPosition.position.x,
461 y: this.viewport_.position.y + viewportPosition.position.y
464 if (viewportPosition.zoom)
465 this.viewport_.setZoom(viewportPosition.zoom);
469 * @private
470 * Update the loading progress of the document in response to a progress
471 * message being received from the plugin.
472 * @param {number} progress the progress as a percentage.
474 updateProgress_: function(progress) {
475 if (this.isMaterial_)
476 this.materialToolbar_.loadProgress = progress;
477 else
478 this.progressBar_.progress = progress;
480 if (progress == -1) {
481 // Document load failed.
482 this.errorScreen_.style.visibility = 'visible';
483 this.sizer_.style.display = 'none';
484 if (!this.isMaterial_)
485 this.toolbar_.style.visibility = 'hidden';
486 if (this.passwordScreen_.active) {
487 this.passwordScreen_.deny();
488 this.passwordScreen_.active = false;
490 this.loadState_ = LoadState.FAILED;
491 this.sendDocumentLoadedMessage_();
492 } else if (progress == 100) {
493 // Document load complete.
494 if (this.lastViewportPosition_)
495 this.viewport_.position = this.lastViewportPosition_;
496 this.paramsParser_.getViewportFromUrlParams(
497 this.browserApi_.getStreamInfo().originalUrl,
498 this.handleURLParams_.bind(this));
499 this.loadState_ = LoadState.SUCCESS;
500 this.sendDocumentLoadedMessage_();
501 while (this.delayedScriptingMessages_.length > 0)
502 this.handleScriptingMessage(this.delayedScriptingMessages_.shift());
504 if (this.isMaterial_)
505 this.toolbarManager_.hideToolbarsAfterTimeout();
510 * @private
511 * An event handler for handling password-submitted events. These are fired
512 * when an event is entered into the password screen.
513 * @param {Object} event a password-submitted event.
515 onPasswordSubmitted_: function(event) {
516 this.plugin_.postMessage({
517 type: 'getPasswordComplete',
518 password: event.detail.password
523 * @private
524 * An event handler for handling message events received from the plugin.
525 * @param {MessageObject} message a message event.
527 handlePluginMessage_: function(message) {
528 switch (message.data.type.toString()) {
529 case 'documentDimensions':
530 this.documentDimensions_ = message.data;
531 this.viewport_.setDocumentDimensions(this.documentDimensions_);
532 // If we received the document dimensions, the password was good so we
533 // can dismiss the password screen.
534 if (this.passwordScreen_.active)
535 this.passwordScreen_.accept();
537 if (this.isMaterial_) {
538 this.materialToolbar_.docLength =
539 this.documentDimensions_.pageDimensions.length;
540 } else {
541 this.pageIndicator_.initialFadeIn();
542 this.toolbar_.initialFadeIn();
544 break;
545 case 'email':
546 var href = 'mailto:' + message.data.to + '?cc=' + message.data.cc +
547 '&bcc=' + message.data.bcc + '&subject=' + message.data.subject +
548 '&body=' + message.data.body;
549 window.location.href = href;
550 break;
551 case 'getAccessibilityJSONReply':
552 this.sendScriptingMessage_(message.data);
553 break;
554 case 'getPassword':
555 // If the password screen isn't up, put it up. Otherwise we're
556 // responding to an incorrect password so deny it.
557 if (!this.passwordScreen_.active)
558 this.passwordScreen_.active = true;
559 else
560 this.passwordScreen_.deny();
561 break;
562 case 'getSelectedTextReply':
563 this.sendScriptingMessage_(message.data);
564 break;
565 case 'goToPage':
566 this.viewport_.goToPage(message.data.page);
567 break;
568 case 'loadProgress':
569 this.updateProgress_(message.data.progress);
570 break;
571 case 'navigate':
572 // If in print preview, always open a new tab.
573 if (this.isPrintPreview_)
574 this.navigator_.navigate(message.data.url, true);
575 else
576 this.navigator_.navigate(message.data.url, message.data.newTab);
577 break;
578 case 'setScrollPosition':
579 var position = this.viewport_.position;
580 if (message.data.x !== undefined)
581 position.x = message.data.x;
582 if (message.data.y !== undefined)
583 position.y = message.data.y;
584 this.viewport_.position = position;
585 break;
586 case 'setTranslatedStrings':
587 this.passwordScreen_.text = message.data.getPasswordString;
588 if (!this.isMaterial_) {
589 this.progressBar_.text = message.data.loadingString;
590 if (!this.isPrintPreview_)
591 this.progressBar_.style.visibility = 'visible';
593 this.errorScreen_.text = message.data.loadFailedString;
594 break;
595 case 'cancelStreamUrl':
596 chrome.mimeHandlerPrivate.abortStream();
597 break;
598 case 'bookmarks':
599 this.bookmarks_ = message.data.bookmarks;
600 if (this.isMaterial_ && this.bookmarks_.length !== 0)
601 this.materialToolbar_.bookmarks = this.bookmarks;
602 break;
603 case 'setIsSelecting':
604 this.viewportScroller_.setEnableScrolling(message.data.isSelecting);
605 break;
606 case 'getNamedDestinationReply':
607 this.paramsParser_.onNamedDestinationReceived(
608 message.data.pageNumber);
609 break;
614 * @private
615 * A callback that's called before the zoom changes. Notify the plugin to stop
616 * reacting to scroll events while zoom is taking place to avoid flickering.
618 beforeZoom_: function() {
619 this.plugin_.postMessage({
620 type: 'stopScrolling'
625 * @private
626 * A callback that's called after the zoom changes. Notify the plugin of the
627 * zoom change and to continue reacting to scroll events.
629 afterZoom_: function() {
630 var position = this.viewport_.position;
631 var zoom = this.viewport_.zoom;
632 if (this.isMaterial_)
633 this.zoomToolbar_.zoomValue = 100 * zoom;
634 this.plugin_.postMessage({
635 type: 'viewport',
636 zoom: zoom,
637 xOffset: position.x,
638 yOffset: position.y
640 this.zoomManager_.onPdfZoomChange();
644 * @private
645 * A callback that's called after the viewport changes.
647 viewportChanged_: function() {
648 if (!this.documentDimensions_)
649 return;
651 // Update the buttons selected.
652 if (!this.isMaterial_) {
653 $('fit-to-page-button').classList.remove('polymer-selected');
654 $('fit-to-width-button').classList.remove('polymer-selected');
655 if (this.viewport_.fittingType == Viewport.FittingType.FIT_TO_PAGE) {
656 $('fit-to-page-button').classList.add('polymer-selected');
657 } else if (this.viewport_.fittingType ==
658 Viewport.FittingType.FIT_TO_WIDTH) {
659 $('fit-to-width-button').classList.add('polymer-selected');
663 // Offset the toolbar position so that it doesn't move if scrollbars appear.
664 var hasScrollbars = this.viewport_.documentHasScrollbars();
665 var scrollbarWidth = this.viewport_.scrollbarWidth;
666 var verticalScrollbarWidth = hasScrollbars.vertical ? scrollbarWidth : 0;
667 var horizontalScrollbarWidth =
668 hasScrollbars.horizontal ? scrollbarWidth : 0;
669 var toolbarRight = Math.max(PDFViewer.MIN_TOOLBAR_OFFSET, scrollbarWidth);
670 var toolbarBottom = Math.max(PDFViewer.MIN_TOOLBAR_OFFSET, scrollbarWidth);
671 toolbarRight -= verticalScrollbarWidth;
672 toolbarBottom -= horizontalScrollbarWidth;
673 if (!this.isMaterial_) {
674 this.toolbar_.style.right = toolbarRight + 'px';
675 this.toolbar_.style.bottom = toolbarBottom + 'px';
676 // Hide the toolbar if it doesn't fit in the viewport.
677 if (this.toolbar_.offsetLeft < 0 || this.toolbar_.offsetTop < 0)
678 this.toolbar_.style.visibility = 'hidden';
679 else
680 this.toolbar_.style.visibility = 'visible';
683 // Update the page indicator.
684 var visiblePage = this.viewport_.getMostVisiblePage();
685 if (this.isMaterial_) {
686 this.materialToolbar_.pageNo = visiblePage + 1;
687 } else {
688 this.pageIndicator_.index = visiblePage;
689 if (this.documentDimensions_.pageDimensions.length > 1 &&
690 hasScrollbars.vertical) {
691 this.pageIndicator_.style.visibility = 'visible';
692 } else {
693 this.pageIndicator_.style.visibility = 'hidden';
697 var visiblePageDimensions = this.viewport_.getPageScreenRect(visiblePage);
698 var size = this.viewport_.size;
699 this.sendScriptingMessage_({
700 type: 'viewport',
701 pageX: visiblePageDimensions.x,
702 pageY: visiblePageDimensions.y,
703 pageWidth: visiblePageDimensions.width,
704 viewportWidth: size.width,
705 viewportHeight: size.height
710 * Handle a scripting message from outside the extension (typically sent by
711 * PDFScriptingAPI in a page containing the extension) to interact with the
712 * plugin.
713 * @param {MessageObject} message the message to handle.
715 handleScriptingMessage: function(message) {
716 if (this.parentWindow_ != message.source) {
717 this.parentWindow_ = message.source;
718 // Ensure that we notify the embedder if the document is loaded.
719 if (this.loadState_ != LoadState.LOADING)
720 this.sendDocumentLoadedMessage_();
723 if (this.handlePrintPreviewScriptingMessage_(message))
724 return;
726 // Delay scripting messages from users of the scripting API until the
727 // document is loaded. This simplifies use of the APIs.
728 if (this.loadState_ != LoadState.SUCCESS) {
729 this.delayedScriptingMessages_.push(message);
730 return;
733 switch (message.data.type.toString()) {
734 case 'getAccessibilityJSON':
735 case 'getSelectedText':
736 case 'print':
737 case 'selectAll':
738 this.plugin_.postMessage(message.data);
739 break;
744 * @private
745 * Handle scripting messages specific to print preview.
746 * @param {MessageObject} message the message to handle.
747 * @return {boolean} true if the message was handled, false otherwise.
749 handlePrintPreviewScriptingMessage_: function(message) {
750 if (!this.isPrintPreview_)
751 return false;
753 switch (message.data.type.toString()) {
754 case 'loadPreviewPage':
755 this.plugin_.postMessage(message.data);
756 return true;
757 case 'resetPrintPreviewMode':
758 this.loadState_ = LoadState.LOADING;
759 if (!this.inPrintPreviewMode_) {
760 this.inPrintPreviewMode_ = true;
761 this.viewport_.fitToPage();
764 // Stash the scroll location so that it can be restored when the new
765 // document is loaded.
766 this.lastViewportPosition_ = this.viewport_.position;
768 // TODO(raymes): Disable these properly in the plugin.
769 var printButton = $('print-button');
770 if (printButton)
771 printButton.parentNode.removeChild(printButton);
772 var saveButton = $('save-button');
773 if (saveButton)
774 saveButton.parentNode.removeChild(saveButton);
776 if (!this.isMaterial_)
777 this.pageIndicator_.pageLabels = message.data.pageNumbers;
779 this.plugin_.postMessage({
780 type: 'resetPrintPreviewMode',
781 url: message.data.url,
782 grayscale: message.data.grayscale,
783 // If the PDF isn't modifiable we send 0 as the page count so that no
784 // blank placeholder pages get appended to the PDF.
785 pageCount: (message.data.modifiable ?
786 message.data.pageNumbers.length : 0)
788 return true;
789 case 'sendKeyEvent':
790 this.handleKeyEvent_(DeserializeKeyEvent(message.data.keyEvent));
791 return true;
794 return false;
798 * @private
799 * Send a scripting message outside the extension (typically to
800 * PDFScriptingAPI in a page containing the extension).
801 * @param {Object} message the message to send.
803 sendScriptingMessage_: function(message) {
804 if (this.parentWindow_)
805 this.parentWindow_.postMessage(message, '*');
810 * @type {Viewport} the viewport of the PDF viewer.
812 get viewport() {
813 return this.viewport_;
817 * Each bookmark is an Object containing a:
818 * - title
819 * - page (optional)
820 * - array of children (themselves bookmarks)
821 * @type {Array} the top-level bookmarks of the PDF.
823 get bookmarks() {
824 return this.bookmarks_;