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 * Creates a new PDFViewer. There should only be one of these objects per
101 * @param {!BrowserApi} browserApi An object providing an API to the browser.
103 function PDFViewer(browserApi) {
104 this.browserApi_ = browserApi;
105 this.loadState_ = LoadState.LOADING;
106 this.parentWindow_ = null;
107 this.parentOrigin_ = null;
109 this.delayedScriptingMessages_ = [];
111 this.isPrintPreview_ = this.browserApi_.getStreamInfo().originalUrl.indexOf(
112 'chrome://print') == 0;
113 this.isMaterial_ = location.pathname.substring(1) === 'index-material.html';
115 // The sizer element is placed behind the plugin element to cause scrollbars
116 // to be displayed in the window. It is sized according to the document size
117 // of the pdf and zoom level.
118 this.sizer_ = $('sizer');
119 this.toolbar_ = $('toolbar');
120 this.pageIndicator_ = $('page-indicator');
121 this.progressBar_ = $('progress-bar');
122 this.passwordScreen_ = $('password-screen');
123 this.passwordScreen_.addEventListener('password-submitted',
124 this.onPasswordSubmitted_.bind(this));
125 this.errorScreen_ = $('error-screen');
127 this.errorScreen_.reloadFn = chrome.tabs.reload;
129 // Create the viewport.
130 var topToolbarHeight =
131 this.isMaterial_ ? PDFViewer.MATERIAL_TOOLBAR_HEIGHT : 0;
132 this.viewport_ = new Viewport(window,
134 this.viewportChanged_.bind(this),
135 this.beforeZoom_.bind(this),
136 this.afterZoom_.bind(this),
138 this.browserApi_.getDefaultZoom(),
141 // Create the plugin object dynamically so we can set its src. The plugin
142 // element is sized to fill the entire window and is set to be fixed
143 // positioning, acting as a viewport. The plugin renders into this viewport
144 // according to the scroll position of the window.
145 this.plugin_ = document.createElement('embed');
146 // NOTE: The plugin's 'id' field must be set to 'plugin' since
147 // chrome/renderer/printing/print_web_view_helper.cc actually references it.
148 this.plugin_.id = 'plugin';
149 this.plugin_.type = 'application/x-google-chrome-pdf';
150 this.plugin_.addEventListener('message', this.handlePluginMessage_.bind(this),
153 // Handle scripting messages from outside the extension that wish to interact
154 // with it. We also send a message indicating that extension has loaded and
155 // is ready to receive messages.
156 window.addEventListener('message', this.handleScriptingMessage.bind(this),
159 this.plugin_.setAttribute('src',
160 this.browserApi_.getStreamInfo().originalUrl);
161 this.plugin_.setAttribute('stream-url',
162 this.browserApi_.getStreamInfo().streamUrl);
164 for (var header in this.browserApi_.getStreamInfo().responseHeaders) {
165 headers += header + ': ' +
166 this.browserApi_.getStreamInfo().responseHeaders[header] + '\n';
168 this.plugin_.setAttribute('headers', headers);
170 if (this.isMaterial_) {
171 this.plugin_.setAttribute('is-material', '');
172 this.plugin_.setAttribute('top-toolbar-height',
173 PDFViewer.MATERIAL_TOOLBAR_HEIGHT);
176 if (!this.browserApi_.getStreamInfo().embedded)
177 this.plugin_.setAttribute('full-frame', '');
178 document.body.appendChild(this.plugin_);
180 // Setup the button event listeners.
181 if (!this.isMaterial_) {
182 $('fit-to-width-button').addEventListener('click',
183 this.viewport_.fitToWidth.bind(this.viewport_));
184 $('fit-to-page-button').addEventListener('click',
185 this.viewport_.fitToPage.bind(this.viewport_));
186 $('zoom-in-button').addEventListener('click',
187 this.viewport_.zoomIn.bind(this.viewport_));
188 $('zoom-out-button').addEventListener('click',
189 this.viewport_.zoomOut.bind(this.viewport_));
190 $('save-button').addEventListener('click', this.save_.bind(this));
191 $('print-button').addEventListener('click', this.print_.bind(this));
194 if (this.isMaterial_) {
195 this.zoomToolbar_ = $('zoom-toolbar');
196 this.zoomToolbar_.addEventListener('fit-to-width',
197 this.viewport_.fitToWidth.bind(this.viewport_));
198 this.zoomToolbar_.addEventListener('fit-to-page',
199 this.fitToPage_.bind(this));
200 this.zoomToolbar_.addEventListener('zoom-in',
201 this.viewport_.zoomIn.bind(this.viewport_));
202 this.zoomToolbar_.addEventListener('zoom-out',
203 this.viewport_.zoomOut.bind(this.viewport_));
205 this.materialToolbar_ = $('material-toolbar');
206 this.materialToolbar_.addEventListener('save', this.save_.bind(this));
207 this.materialToolbar_.addEventListener('print', this.print_.bind(this));
208 this.materialToolbar_.addEventListener('rotate-right',
209 this.rotateClockwise_.bind(this));
210 this.materialToolbar_.addEventListener('rotate-left',
211 this.rotateCounterClockwise_.bind(this));
213 document.body.addEventListener('change-page', function(e) {
214 this.viewport_.goToPage(e.detail.page);
217 this.toolbarManager_ =
218 new ToolbarManager(window, this.materialToolbar_, this.zoomToolbar_);
220 // Must attach to mouseup on the plugin element, since it eats mousedown and
222 this.plugin_.addEventListener(
224 this.materialToolbar_.hideDropdowns.bind(this.materialToolbar_));
227 // Set up the ZoomManager.
228 this.zoomManager_ = new ZoomManager(
229 this.viewport_, this.browserApi_.setZoom.bind(this.browserApi_),
230 this.browserApi_.getDefaultZoom());
231 this.browserApi_.addZoomEventListener(
232 this.zoomManager_.onBrowserZoomChange.bind(this.zoomManager_));
234 // Setup the keyboard event listener.
235 document.addEventListener('keydown', this.handleKeyEvent_.bind(this));
236 document.addEventListener('mousemove', this.handleMouseEvent_.bind(this));
238 // Parse open pdf parameters.
240 new OpenPDFParamsParser(this.getNamedDestination_.bind(this));
241 this.navigator_ = new Navigator(this.browserApi_.getStreamInfo().originalUrl,
242 this.viewport_, this.paramsParser_,
243 onNavigateInCurrentTab, onNavigateInNewTab);
244 this.viewportScroller_ =
245 new ViewportScroller(this.viewport_, this.plugin_, window);
248 PDFViewer.prototype = {
251 * Handle key events. These may come from the user directly or via the
253 * @param {KeyboardEvent} e the event to handle.
255 handleKeyEvent_: function(e) {
256 var position = this.viewport_.position;
257 // Certain scroll events may be sent from outside of the extension.
258 var fromScriptingAPI = e.fromScriptingAPI;
260 if (shouldIgnoreKeyEvents(document.activeElement) || e.defaultPrevented)
263 if (this.isMaterial_)
264 this.toolbarManager_.hideToolbarsAfterTimeout(e);
266 var pageUpHandler = function() {
267 // Go to the previous page if we are fit-to-page.
268 if (this.viewport_.fittingType == Viewport.FittingType.FIT_TO_PAGE) {
269 this.viewport_.goToPage(this.viewport_.getMostVisiblePage() - 1);
270 // Since we do the movement of the page.
272 } else if (fromScriptingAPI) {
273 position.y -= this.viewport.size.height;
274 this.viewport.position = position;
277 var pageDownHandler = function() {
278 // Go to the next page if we are fit-to-page.
279 if (this.viewport_.fittingType == Viewport.FittingType.FIT_TO_PAGE) {
280 this.viewport_.goToPage(this.viewport_.getMostVisiblePage() + 1);
281 // Since we do the movement of the page.
283 } else if (fromScriptingAPI) {
284 position.y += this.viewport.size.height;
285 this.viewport.position = position;
290 case 27: // Escape key.
291 if (this.isMaterial_ && !this.isPrintPreview) {
292 this.toolbarManager_.hideSingleToolbarLayer();
295 break; // Ensure escape falls through to the print-preview handler.
296 case 32: // Space key.
302 case 33: // Page up key.
305 case 34: // Page down key.
308 case 37: // Left arrow key.
309 if (!(e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)) {
310 // Go to the previous page if there are no horizontal scrollbars.
311 if (!this.viewport_.documentHasScrollbars().horizontal) {
312 this.viewport_.goToPage(this.viewport_.getMostVisiblePage() - 1);
313 // Since we do the movement of the page.
315 } else if (fromScriptingAPI) {
316 position.x -= Viewport.SCROLL_INCREMENT;
317 this.viewport.position = position;
321 case 38: // Up arrow key.
322 if (fromScriptingAPI) {
323 position.y -= Viewport.SCROLL_INCREMENT;
324 this.viewport.position = position;
327 case 39: // Right arrow key.
328 if (!(e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)) {
329 // Go to the next page if there are no horizontal scrollbars.
330 if (!this.viewport_.documentHasScrollbars().horizontal) {
331 this.viewport_.goToPage(this.viewport_.getMostVisiblePage() + 1);
332 // Since we do the movement of the page.
334 } else if (fromScriptingAPI) {
335 position.x += Viewport.SCROLL_INCREMENT;
336 this.viewport.position = position;
340 case 40: // Down arrow key.
341 if (fromScriptingAPI) {
342 position.y += Viewport.SCROLL_INCREMENT;
343 this.viewport.position = position;
347 if (e.ctrlKey || e.metaKey) {
348 this.plugin_.postMessage({
351 // Since we do selection ourselves.
356 if (this.isMaterial_ && (e.ctrlKey || e.metaKey)) {
357 this.toolbarManager_.showToolbars();
358 this.materialToolbar_.selectPageNumber();
359 // To prevent the default "find text" behaviour in Chrome.
363 case 219: // left bracket.
365 this.rotateCounterClockwise_();
367 case 221: // right bracket.
369 this.rotateClockwise_();
373 // Give print preview a chance to handle the key event.
374 if (!fromScriptingAPI && this.isPrintPreview_) {
375 this.sendScriptingMessage_({
376 type: 'sendKeyEvent',
377 keyEvent: SerializeKeyEvent(e)
379 } else if (this.isMaterial_) {
380 // Show toolbars as a fallback.
381 if (!(e.shiftKey || e.ctrlKey || e.altKey))
382 this.toolbarManager_.showToolbars();
386 handleMouseEvent_: function(e) {
387 if (this.isMaterial_)
388 this.toolbarManager_.showToolbarsForMouseMove(e);
393 * Rotate the plugin clockwise.
395 rotateClockwise_: function() {
396 this.plugin_.postMessage({
397 type: 'rotateClockwise'
403 * Rotate the plugin counter-clockwise.
405 rotateCounterClockwise_: function() {
406 this.plugin_.postMessage({
407 type: 'rotateCounterclockwise'
411 fitToPage_: function() {
412 this.viewport_.fitToPage();
413 this.toolbarManager_.forceHideTopToolbar();
418 * Notify the plugin to print.
421 this.plugin_.postMessage({
428 * Notify the plugin to save.
431 this.plugin_.postMessage({
437 * Fetches the page number corresponding to the given named destination from
439 * @param {string} name The namedDestination to fetch page number from plugin.
441 getNamedDestination_: function(name) {
442 this.plugin_.postMessage({
443 type: 'getNamedDestination',
444 namedDestination: name
450 * Sends a 'documentLoaded' message to the PDFScriptingAPI if the document has
453 sendDocumentLoadedMessage_: function() {
454 if (this.loadState_ == LoadState.LOADING)
456 this.sendScriptingMessage_({
457 type: 'documentLoaded',
458 load_state: this.loadState_
464 * Handle open pdf parameters. This function updates the viewport as per
465 * the parameters mentioned in the url while opening pdf. The order is
466 * important as later actions can override the effects of previous actions.
467 * @param {Object} viewportPosition The initial position of the viewport to be
470 handleURLParams_: function(viewportPosition) {
471 if (viewportPosition.page != undefined)
472 this.viewport_.goToPage(viewportPosition.page);
473 if (viewportPosition.position) {
474 // Make sure we don't cancel effect of page parameter.
475 this.viewport_.position = {
476 x: this.viewport_.position.x + viewportPosition.position.x,
477 y: this.viewport_.position.y + viewportPosition.position.y
480 if (viewportPosition.zoom)
481 this.viewport_.setZoom(viewportPosition.zoom);
486 * Update the loading progress of the document in response to a progress
487 * message being received from the plugin.
488 * @param {number} progress the progress as a percentage.
490 updateProgress_: function(progress) {
491 if (this.isMaterial_)
492 this.materialToolbar_.loadProgress = progress;
494 this.progressBar_.progress = progress;
496 if (progress == -1) {
497 // Document load failed.
498 this.errorScreen_.show();
499 this.sizer_.style.display = 'none';
500 if (!this.isMaterial_)
501 this.toolbar_.style.visibility = 'hidden';
502 if (this.passwordScreen_.active) {
503 this.passwordScreen_.deny();
504 this.passwordScreen_.active = false;
506 this.loadState_ = LoadState.FAILED;
507 this.sendDocumentLoadedMessage_();
508 } else if (progress == 100) {
509 // Document load complete.
510 if (this.lastViewportPosition_)
511 this.viewport_.position = this.lastViewportPosition_;
512 this.paramsParser_.getViewportFromUrlParams(
513 this.browserApi_.getStreamInfo().originalUrl,
514 this.handleURLParams_.bind(this));
515 this.loadState_ = LoadState.SUCCESS;
516 this.sendDocumentLoadedMessage_();
517 while (this.delayedScriptingMessages_.length > 0)
518 this.handleScriptingMessage(this.delayedScriptingMessages_.shift());
520 if (this.isMaterial_)
521 this.toolbarManager_.hideToolbarsAfterTimeout();
527 * An event handler for handling password-submitted events. These are fired
528 * when an event is entered into the password screen.
529 * @param {Object} event a password-submitted event.
531 onPasswordSubmitted_: function(event) {
532 this.plugin_.postMessage({
533 type: 'getPasswordComplete',
534 password: event.detail.password
540 * An event handler for handling message events received from the plugin.
541 * @param {MessageObject} message a message event.
543 handlePluginMessage_: function(message) {
544 switch (message.data.type.toString()) {
545 case 'documentDimensions':
546 this.documentDimensions_ = message.data;
547 this.viewport_.setDocumentDimensions(this.documentDimensions_);
548 // If we received the document dimensions, the password was good so we
549 // can dismiss the password screen.
550 if (this.passwordScreen_.active)
551 this.passwordScreen_.accept();
553 if (this.isMaterial_) {
554 this.materialToolbar_.docLength =
555 this.documentDimensions_.pageDimensions.length;
556 this.toolbarManager_.enableToolbars();
558 this.pageIndicator_.initialFadeIn();
559 this.toolbar_.initialFadeIn();
563 var href = 'mailto:' + message.data.to + '?cc=' + message.data.cc +
564 '&bcc=' + message.data.bcc + '&subject=' + message.data.subject +
565 '&body=' + message.data.body;
566 window.location.href = href;
568 case 'getAccessibilityJSONReply':
569 this.sendScriptingMessage_(message.data);
572 // If the password screen isn't up, put it up. Otherwise we're
573 // responding to an incorrect password so deny it.
574 if (!this.passwordScreen_.active)
575 this.passwordScreen_.active = true;
577 this.passwordScreen_.deny();
579 case 'getSelectedTextReply':
580 this.sendScriptingMessage_(message.data);
583 this.viewport_.goToPage(message.data.page);
586 this.updateProgress_(message.data.progress);
589 // If in print preview, always open a new tab.
590 if (this.isPrintPreview_)
591 this.navigator_.navigate(message.data.url, true);
593 this.navigator_.navigate(message.data.url, message.data.newTab);
595 case 'setScrollPosition':
596 var position = this.viewport_.position;
597 if (message.data.x !== undefined)
598 position.x = message.data.x;
599 if (message.data.y !== undefined)
600 position.y = message.data.y;
601 this.viewport_.position = position;
603 case 'setTranslatedStrings':
604 this.passwordScreen_.text = message.data.getPasswordString;
605 if (!this.isMaterial_) {
606 this.progressBar_.text = message.data.loadingString;
607 if (!this.isPrintPreview_)
608 this.progressBar_.style.visibility = 'visible';
610 this.errorScreen_.text = message.data.loadFailedString;
612 case 'cancelStreamUrl':
613 chrome.mimeHandlerPrivate.abortStream();
616 if (message.data.title) {
617 document.title = message.data.title;
620 getFilenameFromURL(this.browserApi_.getStreamInfo().originalUrl);
622 this.bookmarks_ = message.data.bookmarks;
623 if (this.isMaterial_) {
624 this.materialToolbar_.docTitle = document.title;
625 this.materialToolbar_.bookmarks = this.bookmarks;
628 case 'setIsSelecting':
629 this.viewportScroller_.setEnableScrolling(message.data.isSelecting);
631 case 'getNamedDestinationReply':
632 this.paramsParser_.onNamedDestinationReceived(
633 message.data.pageNumber);
640 * A callback that's called before the zoom changes. Notify the plugin to stop
641 * reacting to scroll events while zoom is taking place to avoid flickering.
643 beforeZoom_: function() {
644 this.plugin_.postMessage({
645 type: 'stopScrolling'
651 * A callback that's called after the zoom changes. Notify the plugin of the
652 * zoom change and to continue reacting to scroll events.
654 afterZoom_: function() {
655 var position = this.viewport_.position;
656 var zoom = this.viewport_.zoom;
657 this.plugin_.postMessage({
663 this.zoomManager_.onPdfZoomChange();
668 * A callback that's called after the viewport changes.
670 viewportChanged_: function() {
671 if (!this.documentDimensions_)
674 // Update the buttons selected.
675 if (!this.isMaterial_) {
676 $('fit-to-page-button').classList.remove('polymer-selected');
677 $('fit-to-width-button').classList.remove('polymer-selected');
678 if (this.viewport_.fittingType == Viewport.FittingType.FIT_TO_PAGE) {
679 $('fit-to-page-button').classList.add('polymer-selected');
680 } else if (this.viewport_.fittingType ==
681 Viewport.FittingType.FIT_TO_WIDTH) {
682 $('fit-to-width-button').classList.add('polymer-selected');
686 // Offset the toolbar position so that it doesn't move if scrollbars appear.
687 var hasScrollbars = this.viewport_.documentHasScrollbars();
688 var scrollbarWidth = this.viewport_.scrollbarWidth;
689 var verticalScrollbarWidth = hasScrollbars.vertical ? scrollbarWidth : 0;
690 var horizontalScrollbarWidth =
691 hasScrollbars.horizontal ? scrollbarWidth : 0;
692 var toolbarRight = Math.max(PDFViewer.MIN_TOOLBAR_OFFSET, scrollbarWidth);
693 var toolbarBottom = Math.max(PDFViewer.MIN_TOOLBAR_OFFSET, scrollbarWidth);
694 toolbarRight -= verticalScrollbarWidth;
695 toolbarBottom -= horizontalScrollbarWidth;
696 if (!this.isMaterial_) {
697 this.toolbar_.style.right = toolbarRight + 'px';
698 this.toolbar_.style.bottom = toolbarBottom + 'px';
699 // Hide the toolbar if it doesn't fit in the viewport.
700 if (this.toolbar_.offsetLeft < 0 || this.toolbar_.offsetTop < 0)
701 this.toolbar_.style.visibility = 'hidden';
703 this.toolbar_.style.visibility = 'visible';
706 // Update the page indicator.
707 var visiblePage = this.viewport_.getMostVisiblePage();
708 if (this.isMaterial_) {
709 this.materialToolbar_.pageNo = visiblePage + 1;
711 this.pageIndicator_.index = visiblePage;
712 if (this.documentDimensions_.pageDimensions.length > 1 &&
713 hasScrollbars.vertical) {
714 this.pageIndicator_.style.visibility = 'visible';
716 this.pageIndicator_.style.visibility = 'hidden';
720 var visiblePageDimensions = this.viewport_.getPageScreenRect(visiblePage);
721 var size = this.viewport_.size;
722 this.sendScriptingMessage_({
724 pageX: visiblePageDimensions.x,
725 pageY: visiblePageDimensions.y,
726 pageWidth: visiblePageDimensions.width,
727 viewportWidth: size.width,
728 viewportHeight: size.height
733 * Handle a scripting message from outside the extension (typically sent by
734 * PDFScriptingAPI in a page containing the extension) to interact with the
736 * @param {MessageObject} message the message to handle.
738 handleScriptingMessage: function(message) {
739 if (this.parentWindow_ != message.source) {
740 this.parentWindow_ = message.source;
741 this.parentOrigin_ = message.origin;
742 // Ensure that we notify the embedder if the document is loaded.
743 if (this.loadState_ != LoadState.LOADING)
744 this.sendDocumentLoadedMessage_();
747 if (this.handlePrintPreviewScriptingMessage_(message))
750 // Delay scripting messages from users of the scripting API until the
751 // document is loaded. This simplifies use of the APIs.
752 if (this.loadState_ != LoadState.SUCCESS) {
753 this.delayedScriptingMessages_.push(message);
757 switch (message.data.type.toString()) {
758 case 'getAccessibilityJSON':
759 case 'getSelectedText':
762 this.plugin_.postMessage(message.data);
769 * Handle scripting messages specific to print preview.
770 * @param {MessageObject} message the message to handle.
771 * @return {boolean} true if the message was handled, false otherwise.
773 handlePrintPreviewScriptingMessage_: function(message) {
774 if (!this.isPrintPreview_)
777 switch (message.data.type.toString()) {
778 case 'loadPreviewPage':
779 this.plugin_.postMessage(message.data);
781 case 'resetPrintPreviewMode':
782 this.loadState_ = LoadState.LOADING;
783 if (!this.inPrintPreviewMode_) {
784 this.inPrintPreviewMode_ = true;
785 this.viewport_.fitToPage();
788 // Stash the scroll location so that it can be restored when the new
789 // document is loaded.
790 this.lastViewportPosition_ = this.viewport_.position;
792 // TODO(raymes): Disable these properly in the plugin.
793 var printButton = $('print-button');
795 printButton.parentNode.removeChild(printButton);
796 var saveButton = $('save-button');
798 saveButton.parentNode.removeChild(saveButton);
800 if (!this.isMaterial_)
801 this.pageIndicator_.pageLabels = message.data.pageNumbers;
803 this.plugin_.postMessage({
804 type: 'resetPrintPreviewMode',
805 url: message.data.url,
806 grayscale: message.data.grayscale,
807 // If the PDF isn't modifiable we send 0 as the page count so that no
808 // blank placeholder pages get appended to the PDF.
809 pageCount: (message.data.modifiable ?
810 message.data.pageNumbers.length : 0)
814 this.handleKeyEvent_(DeserializeKeyEvent(message.data.keyEvent));
823 * Send a scripting message outside the extension (typically to
824 * PDFScriptingAPI in a page containing the extension).
825 * @param {Object} message the message to send.
827 sendScriptingMessage_: function(message) {
828 if (this.parentWindow_ && this.parentOrigin_) {
830 // Only send data back to the embedder if it is from the same origin,
831 // unless we're sending it to ourselves (which could happen in the case
832 // of tests). We also allow documentLoaded messages through as this won't
833 // leak important information.
834 if (this.parentOrigin_ == window.location.origin)
835 targetOrigin = this.parentOrigin_;
836 else if (message.type == 'documentLoaded')
839 targetOrigin = this.browserApi_.getStreamInfo().originalUrl;
840 this.parentWindow_.postMessage(message, targetOrigin);
845 * @type {Viewport} the viewport of the PDF viewer.
848 return this.viewport_;
852 * Each bookmark is an Object containing a:
855 * - array of children (themselves bookmarks)
856 * @type {Array} the top-level bookmarks of the PDF.
859 return this.bookmarks_;