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.
8 * @return {number} Width of a scrollbar in pixels
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);
24 * Return the filename component of a URL, percent decoded if possible.
25 * @param {string} url The URL to get the filename from.
26 * @return {string} The filename component.
28 function getFilenameFromURL(url) {
29 // Ignore the query and fragment.
30 var mainUrl = url.split(/#|\?/)[0];
31 var components = mainUrl.split(/\/|\\/);
32 var filename = components[components.length - 1];
34 return decodeURIComponent(filename);
36 if (e instanceof URIError)
43 * Called when navigation happens in the current tab.
44 * @param {string} url The url to be opened in the current tab.
46 function onNavigateInCurrentTab(url) {
47 // Prefer the tabs API because it can navigate from one file:// URL to
50 chrome.tabs.update({url: url});
52 window.location.href = url;
56 * Called when navigation happens in the new tab.
57 * @param {string} url The url to be opened in the new tab.
59 function onNavigateInNewTab(url) {
60 // Prefer the tabs API because it guarantees we can just open a new tab.
61 // window.open doesn't have this guarantee.
63 chrome.tabs.create({url: url});
69 * Whether keydown events should currently be ignored. Events are ignored when
70 * an editable element has focus, to allow for proper editing controls.
71 * @param {HTMLElement} activeElement The currently selected DOM node.
72 * @return {boolean} True if keydown events should be ignored.
74 function shouldIgnoreKeyEvents(activeElement) {
75 while (activeElement.shadowRoot != null &&
76 activeElement.shadowRoot.activeElement != null) {
77 activeElement = activeElement.shadowRoot.activeElement;
80 return (activeElement.isContentEditable ||
81 activeElement.tagName == 'INPUT' ||
82 activeElement.tagName == 'TEXTAREA');
86 * The minimum number of pixels to offset the toolbar by from the bottom and
87 * right side of the screen.
89 PDFViewer.MIN_TOOLBAR_OFFSET = 15;
92 * The height of the toolbar along the top of the page. The document will be
93 * shifted down by this much in the viewport.
95 PDFViewer.MATERIAL_TOOLBAR_HEIGHT = 56;
98 * The light-gray background color used for print preview.
100 PDFViewer.LIGHT_BACKGROUND_COLOR = '0xFFCCCCCC';
103 * The dark-gray background color used for the regular viewer.
105 PDFViewer.DARK_BACKGROUND_COLOR = '0xFF525659';
108 * Creates a new PDFViewer. There should only be one of these objects per
111 * @param {!BrowserApi} browserApi An object providing an API to the browser.
113 function PDFViewer(browserApi) {
114 this.browserApi_ = browserApi;
115 this.loadState_ = LoadState.LOADING;
116 this.parentWindow_ = null;
117 this.parentOrigin_ = null;
119 this.delayedScriptingMessages_ = [];
121 this.isPrintPreview_ = this.browserApi_.getStreamInfo().originalUrl.indexOf(
122 'chrome://print') == 0;
123 this.isMaterial_ = location.pathname.substring(1) === 'index-material.html';
125 // The sizer element is placed behind the plugin element to cause scrollbars
126 // to be displayed in the window. It is sized according to the document size
127 // of the pdf and zoom level.
128 this.sizer_ = $('sizer');
129 this.toolbar_ = $('toolbar');
130 if (!this.isMaterial_ || this.isPrintPreview_)
131 this.pageIndicator_ = $('page-indicator');
132 this.progressBar_ = $('progress-bar');
133 this.passwordScreen_ = $('password-screen');
134 this.passwordScreen_.addEventListener('password-submitted',
135 this.onPasswordSubmitted_.bind(this));
136 this.errorScreen_ = $('error-screen');
138 this.errorScreen_.reloadFn = chrome.tabs.reload;
140 // Create the viewport.
141 var topToolbarHeight =
142 (this.isMaterial_ && !this.isPrintPreview_) ?
143 PDFViewer.MATERIAL_TOOLBAR_HEIGHT : 0;
144 this.viewport_ = new Viewport(window,
146 this.viewportChanged_.bind(this),
147 this.beforeZoom_.bind(this),
148 this.afterZoom_.bind(this),
150 this.browserApi_.getDefaultZoom(),
153 // Create the plugin object dynamically so we can set its src. The plugin
154 // element is sized to fill the entire window and is set to be fixed
155 // positioning, acting as a viewport. The plugin renders into this viewport
156 // according to the scroll position of the window.
157 this.plugin_ = document.createElement('embed');
158 // NOTE: The plugin's 'id' field must be set to 'plugin' since
159 // chrome/renderer/printing/print_web_view_helper.cc actually references it.
160 this.plugin_.id = 'plugin';
161 this.plugin_.type = 'application/x-google-chrome-pdf';
162 this.plugin_.addEventListener('message', this.handlePluginMessage_.bind(this),
165 // Handle scripting messages from outside the extension that wish to interact
166 // with it. We also send a message indicating that extension has loaded and
167 // is ready to receive messages.
168 window.addEventListener('message', this.handleScriptingMessage.bind(this),
171 this.plugin_.setAttribute('src',
172 this.browserApi_.getStreamInfo().originalUrl);
173 this.plugin_.setAttribute('stream-url',
174 this.browserApi_.getStreamInfo().streamUrl);
176 for (var header in this.browserApi_.getStreamInfo().responseHeaders) {
177 headers += header + ': ' +
178 this.browserApi_.getStreamInfo().responseHeaders[header] + '\n';
180 this.plugin_.setAttribute('headers', headers);
182 var backgroundColor = PDFViewer.DARK_BACKGROUND_COLOR;
183 if (!this.isMaterial_)
184 backgroundColor = PDFViewer.LIGHT_BACKGROUND_COLOR;
185 this.plugin_.setAttribute('background-color', backgroundColor);
186 this.plugin_.setAttribute('top-toolbar-height', topToolbarHeight);
188 if (!this.browserApi_.getStreamInfo().embedded)
189 this.plugin_.setAttribute('full-frame', '');
190 document.body.appendChild(this.plugin_);
192 // Setup the button event listeners.
193 if (!this.isMaterial_) {
194 $('fit-to-width-button').addEventListener('click',
195 this.viewport_.fitToWidth.bind(this.viewport_));
196 $('fit-to-page-button').addEventListener('click',
197 this.viewport_.fitToPage.bind(this.viewport_));
198 $('zoom-in-button').addEventListener('click',
199 this.viewport_.zoomIn.bind(this.viewport_));
200 $('zoom-out-button').addEventListener('click',
201 this.viewport_.zoomOut.bind(this.viewport_));
202 $('save-button').addEventListener('click', this.save_.bind(this));
203 $('print-button').addEventListener('click', this.print_.bind(this));
206 if (this.isMaterial_) {
207 this.zoomToolbar_ = $('zoom-toolbar');
208 this.zoomToolbar_.addEventListener('fit-to-width',
209 this.viewport_.fitToWidth.bind(this.viewport_));
210 this.zoomToolbar_.addEventListener('fit-to-page',
211 this.fitToPage_.bind(this));
212 this.zoomToolbar_.addEventListener('zoom-in',
213 this.viewport_.zoomIn.bind(this.viewport_));
214 this.zoomToolbar_.addEventListener('zoom-out',
215 this.viewport_.zoomOut.bind(this.viewport_));
217 if (!this.isPrintPreview_) {
218 this.materialToolbar_ = $('material-toolbar');
219 this.materialToolbar_.hidden = false;
220 this.materialToolbar_.addEventListener('save', this.save_.bind(this));
221 this.materialToolbar_.addEventListener('print', this.print_.bind(this));
222 this.materialToolbar_.addEventListener('rotate-right',
223 this.rotateClockwise_.bind(this));
224 this.materialToolbar_.addEventListener('rotate-left',
225 this.rotateCounterClockwise_.bind(this));
226 // Must attach to mouseup on the plugin element, since it eats mousedown
228 this.plugin_.addEventListener('mouseup',
229 this.materialToolbar_.hideDropdowns.bind(this.materialToolbar_));
232 document.body.addEventListener('change-page', function(e) {
233 this.viewport_.goToPage(e.detail.page);
236 this.toolbarManager_ =
237 new ToolbarManager(window, this.materialToolbar_, this.zoomToolbar_);
240 // Set up the ZoomManager.
241 this.zoomManager_ = new ZoomManager(
242 this.viewport_, this.browserApi_.setZoom.bind(this.browserApi_),
243 this.browserApi_.getInitialZoom());
244 this.browserApi_.addZoomEventListener(
245 this.zoomManager_.onBrowserZoomChange.bind(this.zoomManager_));
247 // Setup the keyboard event listener.
248 document.addEventListener('keydown', this.handleKeyEvent_.bind(this));
249 document.addEventListener('mousemove', this.handleMouseEvent_.bind(this));
250 document.addEventListener('mouseout', this.handleMouseEvent_.bind(this));
252 // Parse open pdf parameters.
254 new OpenPDFParamsParser(this.getNamedDestination_.bind(this));
255 this.navigator_ = new Navigator(this.browserApi_.getStreamInfo().originalUrl,
256 this.viewport_, this.paramsParser_,
257 onNavigateInCurrentTab, onNavigateInNewTab);
258 this.viewportScroller_ =
259 new ViewportScroller(this.viewport_, this.plugin_, window);
261 // Request translated strings.
262 if (!this.isPrintPreview_)
263 chrome.resourcesPrivate.getStrings('pdf', this.handleStrings_.bind(this));
266 PDFViewer.prototype = {
269 * Handle key events. These may come from the user directly or via the
271 * @param {KeyboardEvent} e the event to handle.
273 handleKeyEvent_: function(e) {
274 var position = this.viewport_.position;
275 // Certain scroll events may be sent from outside of the extension.
276 var fromScriptingAPI = e.fromScriptingAPI;
278 if (shouldIgnoreKeyEvents(document.activeElement) || e.defaultPrevented)
281 if (this.isMaterial_)
282 this.toolbarManager_.hideToolbarsAfterTimeout(e);
284 var pageUpHandler = function() {
285 // Go to the previous page if we are fit-to-page.
286 if (this.viewport_.fittingType == Viewport.FittingType.FIT_TO_PAGE) {
287 this.viewport_.goToPage(this.viewport_.getMostVisiblePage() - 1);
288 // Since we do the movement of the page.
290 } else if (fromScriptingAPI) {
291 position.y -= this.viewport.size.height;
292 this.viewport.position = position;
295 var pageDownHandler = function() {
296 // Go to the next page if we are fit-to-page.
297 if (this.viewport_.fittingType == Viewport.FittingType.FIT_TO_PAGE) {
298 this.viewport_.goToPage(this.viewport_.getMostVisiblePage() + 1);
299 // Since we do the movement of the page.
301 } else if (fromScriptingAPI) {
302 position.y += this.viewport.size.height;
303 this.viewport.position = position;
308 case 27: // Escape key.
309 if (this.isMaterial_ && !this.isPrintPreview) {
310 this.toolbarManager_.hideSingleToolbarLayer();
313 break; // Ensure escape falls through to the print-preview handler.
314 case 32: // Space key.
320 case 33: // Page up key.
323 case 34: // Page down key.
326 case 37: // Left arrow key.
327 if (!(e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)) {
328 // Go to the previous page if there are no horizontal scrollbars.
329 if (!this.viewport_.documentHasScrollbars().horizontal) {
330 this.viewport_.goToPage(this.viewport_.getMostVisiblePage() - 1);
331 // Since we do the movement of the page.
333 } else if (fromScriptingAPI) {
334 position.x -= Viewport.SCROLL_INCREMENT;
335 this.viewport.position = position;
339 case 38: // Up arrow key.
340 if (fromScriptingAPI) {
341 position.y -= Viewport.SCROLL_INCREMENT;
342 this.viewport.position = position;
345 case 39: // Right arrow key.
346 if (!(e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)) {
347 // Go to the next page if there are no horizontal scrollbars.
348 if (!this.viewport_.documentHasScrollbars().horizontal) {
349 this.viewport_.goToPage(this.viewport_.getMostVisiblePage() + 1);
350 // Since we do the movement of the page.
352 } else if (fromScriptingAPI) {
353 position.x += Viewport.SCROLL_INCREMENT;
354 this.viewport.position = position;
358 case 40: // Down arrow key.
359 if (fromScriptingAPI) {
360 position.y += Viewport.SCROLL_INCREMENT;
361 this.viewport.position = position;
365 if (e.ctrlKey || e.metaKey) {
366 this.plugin_.postMessage({
369 // Since we do selection ourselves.
374 if (this.isMaterial_ && this.materialToolbar_ &&
375 (e.ctrlKey || e.metaKey)) {
376 this.toolbarManager_.showToolbars();
377 this.materialToolbar_.selectPageNumber();
378 // To prevent the default "find text" behaviour in Chrome.
382 case 219: // left bracket.
384 this.rotateCounterClockwise_();
386 case 221: // right bracket.
388 this.rotateClockwise_();
392 // Give print preview a chance to handle the key event.
393 if (!fromScriptingAPI && this.isPrintPreview_) {
394 this.sendScriptingMessage_({
395 type: 'sendKeyEvent',
396 keyEvent: SerializeKeyEvent(e)
398 } else if (this.isMaterial_) {
399 // Show toolbars as a fallback.
400 if (!(e.shiftKey || e.ctrlKey || e.altKey))
401 this.toolbarManager_.showToolbars();
405 handleMouseEvent_: function(e) {
406 if (this.isMaterial_) {
407 if (e.type == 'mousemove')
408 this.toolbarManager_.showToolbarsForMouseMove(e);
409 else if (e.type == 'mouseout')
410 this.toolbarManager_.hideToolbarsForMouseOut();
416 * Rotate the plugin clockwise.
418 rotateClockwise_: function() {
419 this.plugin_.postMessage({
420 type: 'rotateClockwise'
426 * Rotate the plugin counter-clockwise.
428 rotateCounterClockwise_: function() {
429 this.plugin_.postMessage({
430 type: 'rotateCounterclockwise'
434 fitToPage_: function() {
435 this.viewport_.fitToPage();
436 this.toolbarManager_.forceHideTopToolbar();
441 * Notify the plugin to print.
444 this.plugin_.postMessage({
451 * Notify the plugin to save.
454 this.plugin_.postMessage({
460 * Fetches the page number corresponding to the given named destination from
462 * @param {string} name The namedDestination to fetch page number from plugin.
464 getNamedDestination_: function(name) {
465 this.plugin_.postMessage({
466 type: 'getNamedDestination',
467 namedDestination: name
473 * Sends a 'documentLoaded' message to the PDFScriptingAPI if the document has
476 sendDocumentLoadedMessage_: function() {
477 if (this.loadState_ == LoadState.LOADING)
479 this.sendScriptingMessage_({
480 type: 'documentLoaded',
481 load_state: this.loadState_
487 * Handle open pdf parameters. This function updates the viewport as per
488 * the parameters mentioned in the url while opening pdf. The order is
489 * important as later actions can override the effects of previous actions.
490 * @param {Object} viewportPosition The initial position of the viewport to be
493 handleURLParams_: function(viewportPosition) {
494 if (viewportPosition.page != undefined)
495 this.viewport_.goToPage(viewportPosition.page);
496 if (viewportPosition.position) {
497 // Make sure we don't cancel effect of page parameter.
498 this.viewport_.position = {
499 x: this.viewport_.position.x + viewportPosition.position.x,
500 y: this.viewport_.position.y + viewportPosition.position.y
503 if (viewportPosition.zoom)
504 this.viewport_.setZoom(viewportPosition.zoom);
509 * Update the loading progress of the document in response to a progress
510 * message being received from the plugin.
511 * @param {number} progress the progress as a percentage.
513 updateProgress_: function(progress) {
514 if (this.isMaterial_) {
515 if (this.materialToolbar_)
516 this.materialToolbar_.loadProgress = progress;
518 this.progressBar_.progress = progress;
521 if (progress == -1) {
522 // Document load failed.
523 this.errorScreen_.show();
524 this.sizer_.style.display = 'none';
525 if (!this.isMaterial_)
526 this.toolbar_.style.visibility = 'hidden';
527 if (this.passwordScreen_.active) {
528 this.passwordScreen_.deny();
529 this.passwordScreen_.active = false;
531 this.loadState_ = LoadState.FAILED;
532 this.sendDocumentLoadedMessage_();
533 } else if (progress == 100) {
534 // Document load complete.
535 if (this.lastViewportPosition_)
536 this.viewport_.position = this.lastViewportPosition_;
537 this.paramsParser_.getViewportFromUrlParams(
538 this.browserApi_.getStreamInfo().originalUrl,
539 this.handleURLParams_.bind(this));
540 this.loadState_ = LoadState.SUCCESS;
541 this.sendDocumentLoadedMessage_();
542 while (this.delayedScriptingMessages_.length > 0)
543 this.handleScriptingMessage(this.delayedScriptingMessages_.shift());
545 if (this.isMaterial_)
546 this.toolbarManager_.hideToolbarsAfterTimeout();
552 * Load a dictionary of translated strings into the UI. Used as a callback for
553 * chrome.resourcesPrivate.
554 * @param {Object} strings Dictionary of translated strings
556 handleStrings_: function(strings) {
557 this.passwordScreen_.text = strings.passwordPrompt;
558 if (!this.isMaterial_) {
559 this.progressBar_.text = strings.pageLoading;
560 if (!this.isPrintPreview_)
561 this.progressBar_.style.visibility = 'visible';
563 this.errorScreen_.text = strings.pageLoadFailed;
568 * An event handler for handling password-submitted events. These are fired
569 * when an event is entered into the password screen.
570 * @param {Object} event a password-submitted event.
572 onPasswordSubmitted_: function(event) {
573 this.plugin_.postMessage({
574 type: 'getPasswordComplete',
575 password: event.detail.password
581 * An event handler for handling message events received from the plugin.
582 * @param {MessageObject} message a message event.
584 handlePluginMessage_: function(message) {
585 switch (message.data.type.toString()) {
586 case 'documentDimensions':
587 this.documentDimensions_ = message.data;
588 this.viewport_.setDocumentDimensions(this.documentDimensions_);
589 // If we received the document dimensions, the password was good so we
590 // can dismiss the password screen.
591 if (this.passwordScreen_.active)
592 this.passwordScreen_.accept();
594 if (this.pageIndicator_)
595 this.pageIndicator_.initialFadeIn();
597 if (this.isMaterial_) {
598 if (this.materialToolbar_) {
599 this.materialToolbar_.docLength =
600 this.documentDimensions_.pageDimensions.length;
603 this.toolbar_.initialFadeIn();
607 var href = 'mailto:' + message.data.to + '?cc=' + message.data.cc +
608 '&bcc=' + message.data.bcc + '&subject=' + message.data.subject +
609 '&body=' + message.data.body;
610 window.location.href = href;
612 case 'getAccessibilityJSONReply':
613 this.sendScriptingMessage_(message.data);
616 // If the password screen isn't up, put it up. Otherwise we're
617 // responding to an incorrect password so deny it.
618 if (!this.passwordScreen_.active)
619 this.passwordScreen_.active = true;
621 this.passwordScreen_.deny();
623 case 'getSelectedTextReply':
624 this.sendScriptingMessage_(message.data);
627 this.viewport_.goToPage(message.data.page);
630 this.updateProgress_(message.data.progress);
633 // If in print preview, always open a new tab.
634 if (this.isPrintPreview_)
635 this.navigator_.navigate(message.data.url, true);
637 this.navigator_.navigate(message.data.url, message.data.newTab);
639 case 'setScrollPosition':
640 var position = this.viewport_.position;
641 if (message.data.x !== undefined)
642 position.x = message.data.x;
643 if (message.data.y !== undefined)
644 position.y = message.data.y;
645 this.viewport_.position = position;
647 case 'cancelStreamUrl':
648 chrome.mimeHandlerPrivate.abortStream();
651 if (message.data.title) {
652 document.title = message.data.title;
655 getFilenameFromURL(this.browserApi_.getStreamInfo().originalUrl);
657 this.bookmarks_ = message.data.bookmarks;
658 if (this.isMaterial_ && this.materialToolbar_) {
659 this.materialToolbar_.docTitle = document.title;
660 this.materialToolbar_.bookmarks = this.bookmarks;
663 case 'setIsSelecting':
664 this.viewportScroller_.setEnableScrolling(message.data.isSelecting);
666 case 'getNamedDestinationReply':
667 this.paramsParser_.onNamedDestinationReceived(
668 message.data.pageNumber);
675 * A callback that's called before the zoom changes. Notify the plugin to stop
676 * reacting to scroll events while zoom is taking place to avoid flickering.
678 beforeZoom_: function() {
679 this.plugin_.postMessage({
680 type: 'stopScrolling'
686 * A callback that's called after the zoom changes. Notify the plugin of the
687 * zoom change and to continue reacting to scroll events.
689 afterZoom_: function() {
690 var position = this.viewport_.position;
691 var zoom = this.viewport_.zoom;
692 this.plugin_.postMessage({
698 this.zoomManager_.onPdfZoomChange();
703 * A callback that's called after the viewport changes.
705 viewportChanged_: function() {
706 if (!this.documentDimensions_)
709 // Update the buttons selected.
710 if (!this.isMaterial_) {
711 $('fit-to-page-button').classList.remove('polymer-selected');
712 $('fit-to-width-button').classList.remove('polymer-selected');
713 if (this.viewport_.fittingType == Viewport.FittingType.FIT_TO_PAGE) {
714 $('fit-to-page-button').classList.add('polymer-selected');
715 } else if (this.viewport_.fittingType ==
716 Viewport.FittingType.FIT_TO_WIDTH) {
717 $('fit-to-width-button').classList.add('polymer-selected');
721 // Offset the toolbar position so that it doesn't move if scrollbars appear.
722 var hasScrollbars = this.viewport_.documentHasScrollbars();
723 var scrollbarWidth = this.viewport_.scrollbarWidth;
724 var verticalScrollbarWidth = hasScrollbars.vertical ? scrollbarWidth : 0;
725 var horizontalScrollbarWidth =
726 hasScrollbars.horizontal ? scrollbarWidth : 0;
727 if (this.isMaterial_) {
728 // Shift the zoom toolbar to the left by half a scrollbar width. This
729 // gives a compromise: if there is no scrollbar visible then the toolbar
730 // will be half a scrollbar width further left than the spec but if there
731 // is a scrollbar visible it will be half a scrollbar width further right
733 this.zoomToolbar_.style.right = -verticalScrollbarWidth +
734 (scrollbarWidth / 2) + 'px';
735 // Having a horizontal scrollbar is much rarer so we don't offset the
736 // toolbar from the bottom any more than what the spec says. This means
737 // that when there is a scrollbar visible, it will be a full scrollbar
738 // width closer to the bottom of the screen than usual, but this is ok.
739 this.zoomToolbar_.style.bottom = -horizontalScrollbarWidth + 'px';
741 var toolbarRight = Math.max(PDFViewer.MIN_TOOLBAR_OFFSET, scrollbarWidth);
743 Math.max(PDFViewer.MIN_TOOLBAR_OFFSET, scrollbarWidth);
744 toolbarRight -= verticalScrollbarWidth;
745 toolbarBottom -= horizontalScrollbarWidth;
746 this.toolbar_.style.right = toolbarRight + 'px';
747 this.toolbar_.style.bottom = toolbarBottom + 'px';
748 // Hide the toolbar if it doesn't fit in the viewport.
749 if (this.toolbar_.offsetLeft < 0 || this.toolbar_.offsetTop < 0)
750 this.toolbar_.style.visibility = 'hidden';
752 this.toolbar_.style.visibility = 'visible';
755 // Update the page indicator.
756 var visiblePage = this.viewport_.getMostVisiblePage();
758 if (this.materialToolbar_)
759 this.materialToolbar_.pageNo = visiblePage + 1;
761 // TODO(raymes): Give pageIndicator_ the same API as materialToolbar_.
762 if (this.pageIndicator_) {
763 this.pageIndicator_.index = visiblePage;
764 if (this.documentDimensions_.pageDimensions.length > 1 &&
765 hasScrollbars.vertical) {
766 this.pageIndicator_.style.visibility = 'visible';
768 this.pageIndicator_.style.visibility = 'hidden';
772 var visiblePageDimensions = this.viewport_.getPageScreenRect(visiblePage);
773 var size = this.viewport_.size;
774 this.sendScriptingMessage_({
776 pageX: visiblePageDimensions.x,
777 pageY: visiblePageDimensions.y,
778 pageWidth: visiblePageDimensions.width,
779 viewportWidth: size.width,
780 viewportHeight: size.height
785 * Handle a scripting message from outside the extension (typically sent by
786 * PDFScriptingAPI in a page containing the extension) to interact with the
788 * @param {MessageObject} message the message to handle.
790 handleScriptingMessage: function(message) {
791 if (this.parentWindow_ != message.source) {
792 this.parentWindow_ = message.source;
793 this.parentOrigin_ = message.origin;
794 // Ensure that we notify the embedder if the document is loaded.
795 if (this.loadState_ != LoadState.LOADING)
796 this.sendDocumentLoadedMessage_();
799 if (this.handlePrintPreviewScriptingMessage_(message))
802 // Delay scripting messages from users of the scripting API until the
803 // document is loaded. This simplifies use of the APIs.
804 if (this.loadState_ != LoadState.SUCCESS) {
805 this.delayedScriptingMessages_.push(message);
809 switch (message.data.type.toString()) {
810 case 'getAccessibilityJSON':
811 case 'getSelectedText':
814 this.plugin_.postMessage(message.data);
821 * Handle scripting messages specific to print preview.
822 * @param {MessageObject} message the message to handle.
823 * @return {boolean} true if the message was handled, false otherwise.
825 handlePrintPreviewScriptingMessage_: function(message) {
826 if (!this.isPrintPreview_)
829 switch (message.data.type.toString()) {
830 case 'loadPreviewPage':
831 this.plugin_.postMessage(message.data);
833 case 'resetPrintPreviewMode':
834 this.loadState_ = LoadState.LOADING;
835 if (!this.inPrintPreviewMode_) {
836 this.inPrintPreviewMode_ = true;
837 this.viewport_.fitToPage();
840 // Stash the scroll location so that it can be restored when the new
841 // document is loaded.
842 this.lastViewportPosition_ = this.viewport_.position;
844 // TODO(raymes): Disable these properly in the plugin.
845 var printButton = $('print-button');
847 printButton.parentNode.removeChild(printButton);
848 var saveButton = $('save-button');
850 saveButton.parentNode.removeChild(saveButton);
852 this.pageIndicator_.pageLabels = message.data.pageNumbers;
854 this.plugin_.postMessage({
855 type: 'resetPrintPreviewMode',
856 url: message.data.url,
857 grayscale: message.data.grayscale,
858 // If the PDF isn't modifiable we send 0 as the page count so that no
859 // blank placeholder pages get appended to the PDF.
860 pageCount: (message.data.modifiable ?
861 message.data.pageNumbers.length : 0)
865 this.handleKeyEvent_(DeserializeKeyEvent(message.data.keyEvent));
874 * Send a scripting message outside the extension (typically to
875 * PDFScriptingAPI in a page containing the extension).
876 * @param {Object} message the message to send.
878 sendScriptingMessage_: function(message) {
879 if (this.parentWindow_ && this.parentOrigin_) {
881 // Only send data back to the embedder if it is from the same origin,
882 // unless we're sending it to ourselves (which could happen in the case
883 // of tests). We also allow documentLoaded messages through as this won't
884 // leak important information.
885 if (this.parentOrigin_ == window.location.origin)
886 targetOrigin = this.parentOrigin_;
887 else if (message.type == 'documentLoaded')
890 targetOrigin = this.browserApi_.getStreamInfo().originalUrl;
891 this.parentWindow_.postMessage(message, targetOrigin);
896 * @type {Viewport} the viewport of the PDF viewer.
899 return this.viewport_;
903 * Each bookmark is an Object containing a:
906 * - array of children (themselves bookmarks)
907 * @type {Array} the top-level bookmarks of the PDF.
910 return this.bookmarks_;