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.
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];
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
;
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.
49 chrome
.tabs
.create({ url
: url
});
55 * The minimum number of pixels to offset the toolbar by from the bottom and
56 * right side of the screen.
58 PDFViewer
.MIN_TOOLBAR_OFFSET
= 15;
61 * Creates a new PDFViewer. There should only be one of these objects per
64 * @param {Object} streamDetails The stream object which points to the data
65 * contained in the PDF.
67 function PDFViewer(streamDetails
) {
68 this.streamDetails_
= streamDetails
;
70 this.parentWindow_
= null;
72 this.delayedScriptingMessages_
= [];
74 this.isPrintPreview_
=
75 this.streamDetails_
.originalUrl
.indexOf('chrome://print') == 0;
76 this.isMaterial_
= location
.pathname
.substring(1) === 'index-material.html';
78 // The sizer element is placed behind the plugin element to cause scrollbars
79 // to be displayed in the window. It is sized according to the document size
80 // of the pdf and zoom level.
81 this.sizer_
= $('sizer');
82 this.toolbar_
= $('toolbar');
83 this.pageIndicator_
= $('page-indicator');
84 this.progressBar_
= $('progress-bar');
85 this.passwordScreen_
= $('password-screen');
86 this.passwordScreen_
.addEventListener('password-submitted',
87 this.onPasswordSubmitted_
.bind(this));
88 this.errorScreen_
= $('error-screen');
90 // Create the viewport.
91 this.viewport_
= new Viewport(window
,
93 this.viewportChanged_
.bind(this),
94 this.beforeZoom_
.bind(this),
95 this.afterZoom_
.bind(this),
98 // Create the plugin object dynamically so we can set its src. The plugin
99 // element is sized to fill the entire window and is set to be fixed
100 // positioning, acting as a viewport. The plugin renders into this viewport
101 // according to the scroll position of the window.
102 this.plugin_
= document
.createElement('embed');
103 // NOTE: The plugin's 'id' field must be set to 'plugin' since
104 // chrome/renderer/printing/print_web_view_helper.cc actually references it.
105 this.plugin_
.id
= 'plugin';
106 this.plugin_
.type
= 'application/x-google-chrome-pdf';
107 this.plugin_
.addEventListener('message', this.handlePluginMessage_
.bind(this),
110 // Handle scripting messages from outside the extension that wish to interact
111 // with it. We also send a message indicating that extension has loaded and
112 // is ready to receive messages.
113 window
.addEventListener('message', this.handleScriptingMessage
.bind(this),
116 document
.title
= getFilenameFromURL(this.streamDetails_
.originalUrl
);
117 this.plugin_
.setAttribute('src', this.streamDetails_
.originalUrl
);
118 this.plugin_
.setAttribute('stream-url', this.streamDetails_
.streamUrl
);
120 for (var header
in this.streamDetails_
.responseHeaders
) {
121 headers
+= header
+ ': ' +
122 this.streamDetails_
.responseHeaders
[header
] + '\n';
124 this.plugin_
.setAttribute('headers', headers
);
126 if (this.isMaterial_
)
127 this.plugin_
.setAttribute('is-material', '');
129 if (!this.streamDetails_
.embedded
)
130 this.plugin_
.setAttribute('full-frame', '');
131 document
.body
.appendChild(this.plugin_
);
133 // Setup the button event listeners.
134 if (!this.isMaterial_
) {
135 $('fit-to-width-button').addEventListener('click',
136 this.viewport_
.fitToWidth
.bind(this.viewport_
));
137 $('fit-to-page-button').addEventListener('click',
138 this.viewport_
.fitToPage
.bind(this.viewport_
));
139 $('zoom-in-button').addEventListener('click',
140 this.viewport_
.zoomIn
.bind(this.viewport_
));
141 $('zoom-out-button').addEventListener('click',
142 this.viewport_
.zoomOut
.bind(this.viewport_
));
143 $('save-button').addEventListener('click', this.save_
.bind(this));
144 $('print-button').addEventListener('click', this.print_
.bind(this));
147 if (this.isMaterial_
) {
148 this.bookmarksPane_
= $('bookmarks-pane');
150 this.zoomSelector_
= $('zoom-selector');
151 this.zoomSelector_
.zoomMin
= Viewport
.ZOOM_FACTOR_RANGE
.min
* 100;
152 this.zoomSelector_
.zoomMax
= Viewport
.ZOOM_FACTOR_RANGE
.max
* 100;
153 this.zoomSelector_
.addEventListener('zoom', function(e
) {
154 this.viewport_
.setZoom(e
.detail
.zoom
);
156 this.zoomSelector_
.addEventListener('fit-to-width',
157 this.viewport_
.fitToWidth
.bind(this.viewport_
));
158 this.zoomSelector_
.addEventListener('fit-to-page',
159 this.viewport_
.fitToPage
.bind(this.viewport_
));
161 this.materialToolbar_
= $('material-toolbar');
162 this.materialToolbar_
.docTitle
= document
.title
;
163 this.materialToolbar_
.addEventListener('save', this.save_
.bind(this));
164 this.materialToolbar_
.addEventListener('print', this.print_
.bind(this));
165 this.materialToolbar_
.addEventListener('rotate-right',
166 this.rotateClockwise_
.bind(this));
167 this.materialToolbar_
.addEventListener('toggle-bookmarks', function() {
168 this.bookmarksPane_
.buttonToggle();
171 document
.body
.addEventListener('change-page', function(e
) {
172 this.viewport_
.goToPage(e
.detail
.page
);
175 this.uiManager_
= new UiManager(window
, this.materialToolbar_
,
176 [this.bookmarksPane_
]);
179 // Setup the keyboard event listener.
180 document
.onkeydown
= this.handleKeyEvent_
.bind(this);
182 // Set up the zoom API.
183 if (this.shouldManageZoom_()) {
184 chrome
.tabs
.setZoomSettings(this.streamDetails_
.tabId
,
185 {mode
: 'manual', scope
: 'per-tab'},
186 this.afterZoom_
.bind(this));
187 chrome
.tabs
.onZoomChange
.addListener(function(zoomChangeInfo
) {
188 if (zoomChangeInfo
.tabId
!= this.streamDetails_
.tabId
)
190 // If the zoom level is close enough to the current zoom level, don't
191 // change it. This avoids us getting into an infinite loop of zoom changes
192 // due to floating point error.
193 var MIN_ZOOM_DELTA
= 0.01;
194 var zoomDelta
= Math
.abs(this.viewport_
.zoom
-
195 zoomChangeInfo
.newZoomFactor
);
196 // We should not change zoom level when we are responsible for initiating
197 // the zoom. onZoomChange() is called before setZoomComplete() callback
198 // when we initiate the zoom.
199 if ((zoomDelta
> MIN_ZOOM_DELTA
) && !this.setZoomInProgress_
)
200 this.viewport_
.setZoom(zoomChangeInfo
.newZoomFactor
);
204 // Parse open pdf parameters.
206 new OpenPDFParamsParser(this.getNamedDestination_
.bind(this));
207 this.navigator_
= new Navigator(this.streamDetails_
.originalUrl
,
208 this.viewport_
, this.paramsParser_
,
209 onNavigateInCurrentTab
, onNavigateInNewTab
);
210 this.viewportScroller_
=
211 new ViewportScroller(this.viewport_
, this.plugin_
, window
);
214 PDFViewer
.prototype = {
217 * Handle key events. These may come from the user directly or via the
219 * @param {KeyboardEvent} e the event to handle.
221 handleKeyEvent_: function(e
) {
222 var position
= this.viewport_
.position
;
223 // Certain scroll events may be sent from outside of the extension.
224 var fromScriptingAPI
= e
.fromScriptingAPI
;
226 var pageUpHandler = function() {
227 // Go to the previous page if we are fit-to-page.
228 if (this.viewport_
.fittingType
== Viewport
.FittingType
.FIT_TO_PAGE
) {
229 this.viewport_
.goToPage(this.viewport_
.getMostVisiblePage() - 1);
230 // Since we do the movement of the page.
232 } else if (fromScriptingAPI
) {
233 position
.y
-= this.viewport
.size
.height
;
234 this.viewport
.position
= position
;
237 var pageDownHandler = function() {
238 // Go to the next page if we are fit-to-page.
239 if (this.viewport_
.fittingType
== Viewport
.FittingType
.FIT_TO_PAGE
) {
240 this.viewport_
.goToPage(this.viewport_
.getMostVisiblePage() + 1);
241 // Since we do the movement of the page.
243 } else if (fromScriptingAPI
) {
244 position
.y
+= this.viewport
.size
.height
;
245 this.viewport
.position
= position
;
250 case 32: // Space key.
256 case 33: // Page up key.
259 case 34: // Page down key.
262 case 37: // Left arrow key.
263 if (!(e
.altKey
|| e
.ctrlKey
|| e
.metaKey
|| e
.shiftKey
)) {
264 // Go to the previous page if there are no horizontal scrollbars.
265 if (!this.viewport_
.documentHasScrollbars().horizontal
) {
266 this.viewport_
.goToPage(this.viewport_
.getMostVisiblePage() - 1);
267 // Since we do the movement of the page.
269 } else if (fromScriptingAPI
) {
270 position
.x
-= Viewport
.SCROLL_INCREMENT
;
271 this.viewport
.position
= position
;
275 case 38: // Up arrow key.
276 if (fromScriptingAPI
) {
277 position
.y
-= Viewport
.SCROLL_INCREMENT
;
278 this.viewport
.position
= position
;
281 case 39: // Right arrow key.
282 if (!(e
.altKey
|| e
.ctrlKey
|| e
.metaKey
|| e
.shiftKey
)) {
283 // Go to the next page if there are no horizontal scrollbars.
284 if (!this.viewport_
.documentHasScrollbars().horizontal
) {
285 this.viewport_
.goToPage(this.viewport_
.getMostVisiblePage() + 1);
286 // Since we do the movement of the page.
288 } else if (fromScriptingAPI
) {
289 position
.x
+= Viewport
.SCROLL_INCREMENT
;
290 this.viewport
.position
= position
;
294 case 40: // Down arrow key.
295 if (fromScriptingAPI
) {
296 position
.y
+= Viewport
.SCROLL_INCREMENT
;
297 this.viewport
.position
= position
;
301 if (e
.ctrlKey
|| e
.metaKey
) {
302 this.plugin_
.postMessage({
305 // Since we do selection ourselves.
310 if (this.isMaterial_
&& (e
.ctrlKey
|| e
.metaKey
)) {
311 this.materialToolbar_
.selectPageNumber();
312 // To prevent the default "find text" behaviour in Chrome.
316 case 219: // left bracket.
318 this.rotateCounterClockwise_();
320 case 221: // right bracket.
322 this.rotateClockwise_();
326 // Give print preview a chance to handle the key event.
327 if (!fromScriptingAPI
&& this.isPrintPreview_
) {
328 this.sendScriptingMessage_({
329 type
: 'sendKeyEvent',
330 keyEvent
: SerializeKeyEvent(e
)
337 * Rotate the plugin clockwise.
339 rotateClockwise_: function() {
340 this.plugin_
.postMessage({
341 type
: 'rotateClockwise'
347 * Rotate the plugin counter-clockwise.
349 rotateCounterClockwise_: function() {
350 this.plugin_
.postMessage({
351 type
: 'rotateCounterclockwise'
357 * Notify the plugin to print.
360 this.plugin_
.postMessage({
367 * Notify the plugin to save.
370 this.plugin_
.postMessage({
376 * Fetches the page number corresponding to the given named destination from
378 * @param {string} name The namedDestination to fetch page number from plugin.
380 getNamedDestination_: function(name
) {
381 this.plugin_
.postMessage({
382 type
: 'getNamedDestination',
383 namedDestination
: name
389 * Handle open pdf parameters. This function updates the viewport as per
390 * the parameters mentioned in the url while opening pdf. The order is
391 * important as later actions can override the effects of previous actions.
392 * @param {Object} viewportPosition The initial position of the viewport to be
395 handleURLParams_: function(viewportPosition
) {
396 if (viewportPosition
.page
!= undefined)
397 this.viewport_
.goToPage(viewportPosition
.page
);
398 if (viewportPosition
.position
) {
399 // Make sure we don't cancel effect of page parameter.
400 this.viewport_
.position
= {
401 x
: this.viewport_
.position
.x
+ viewportPosition
.position
.x
,
402 y
: this.viewport_
.position
.y
+ viewportPosition
.position
.y
405 if (viewportPosition
.zoom
)
406 this.viewport_
.setZoom(viewportPosition
.zoom
);
411 * Update the loading progress of the document in response to a progress
412 * message being received from the plugin.
413 * @param {number} progress the progress as a percentage.
415 updateProgress_: function(progress
) {
416 if (this.isMaterial_
)
417 this.materialToolbar_
.loadProgress
= progress
;
419 this.progressBar_
.progress
= progress
;
421 if (progress
== -1) {
422 // Document load failed.
423 this.errorScreen_
.style
.visibility
= 'visible';
424 this.sizer_
.style
.display
= 'none';
425 if (!this.isMaterial_
)
426 this.toolbar_
.style
.visibility
= 'hidden';
427 if (this.passwordScreen_
.active
) {
428 this.passwordScreen_
.deny();
429 this.passwordScreen_
.active
= false;
431 } else if (progress
== 100) {
432 // Document load complete.
433 if (this.lastViewportPosition_
)
434 this.viewport_
.position
= this.lastViewportPosition_
;
435 this.paramsParser_
.getViewportFromUrlParams(
436 this.streamDetails_
.originalUrl
, this.handleURLParams_
.bind(this));
438 this.sendScriptingMessage_({
439 type
: 'documentLoaded'
441 while (this.delayedScriptingMessages_
.length
> 0)
442 this.handleScriptingMessage(this.delayedScriptingMessages_
.shift());
444 if (this.isMaterial_
)
445 this.uiManager_
.hideUiAfterTimeout();
451 * An event handler for handling password-submitted events. These are fired
452 * when an event is entered into the password screen.
453 * @param {Object} event a password-submitted event.
455 onPasswordSubmitted_: function(event
) {
456 this.plugin_
.postMessage({
457 type
: 'getPasswordComplete',
458 password
: event
.detail
.password
464 * An event handler for handling message events received from the plugin.
465 * @param {MessageObject} message a message event.
467 handlePluginMessage_: function(message
) {
468 switch (message
.data
.type
.toString()) {
469 case 'documentDimensions':
470 this.documentDimensions_
= message
.data
;
471 this.viewport_
.setDocumentDimensions(this.documentDimensions_
);
472 // If we received the document dimensions, the password was good so we
473 // can dismiss the password screen.
474 if (this.passwordScreen_
.active
)
475 this.passwordScreen_
.accept();
477 if (this.isMaterial_
) {
478 this.materialToolbar_
.docLength
=
479 this.documentDimensions_
.pageDimensions
.length
;
481 this.pageIndicator_
.initialFadeIn();
482 this.toolbar_
.initialFadeIn();
487 var href
= 'mailto:' + message
.data
.to
+ '?cc=' + message
.data
.cc
+
488 '&bcc=' + message
.data
.bcc
+ '&subject=' + message
.data
.subject
+
489 '&body=' + message
.data
.body
;
490 window
.location
.href
= href
;
492 case 'getAccessibilityJSONReply':
493 this.sendScriptingMessage_(message
.data
);
496 // If the password screen isn't up, put it up. Otherwise we're
497 // responding to an incorrect password so deny it.
498 if (!this.passwordScreen_
.active
)
499 this.passwordScreen_
.active
= true;
501 this.passwordScreen_
.deny();
503 case 'getSelectedTextReply':
504 this.sendScriptingMessage_(message
.data
);
507 this.viewport_
.goToPage(message
.data
.page
);
510 this.updateProgress_(message
.data
.progress
);
513 // If in print preview, always open a new tab.
514 if (this.isPrintPreview_
)
515 this.navigator_
.navigate(message
.data
.url
, true);
517 this.navigator_
.navigate(message
.data
.url
, message
.data
.newTab
);
519 case 'setScrollPosition':
520 var position
= this.viewport_
.position
;
521 if (message
.data
.x
!== undefined)
522 position
.x
= message
.data
.x
;
523 if (message
.data
.y
!== undefined)
524 position
.y
= message
.data
.y
;
525 this.viewport_
.position
= position
;
527 case 'setTranslatedStrings':
528 this.passwordScreen_
.text
= message
.data
.getPasswordString
;
529 if (!this.isMaterial_
) {
530 this.progressBar_
.text
= message
.data
.loadingString
;
531 if (!this.isPrintPreview_
)
532 this.progressBar_
.style
.visibility
= 'visible';
534 this.errorScreen_
.text
= message
.data
.loadFailedString
;
536 case 'cancelStreamUrl':
537 chrome
.mimeHandlerPrivate
.abortStream();
540 this.bookmarks_
= message
.data
.bookmarks
;
541 if (this.isMaterial_
&& this.bookmarks_
.length
!== 0) {
542 $('bookmarks-container').bookmarks
= this.bookmarks
;
543 this.materialToolbar_
.hasBookmarks
= true;
546 case 'setIsSelecting':
547 this.viewportScroller_
.setEnableScrolling(message
.data
.isSelecting
);
549 case 'getNamedDestinationReply':
550 this.paramsParser_
.onNamedDestinationReceived(
551 message
.data
.pageNumber
);
558 * A callback that's called before the zoom changes. Notify the plugin to stop
559 * reacting to scroll events while zoom is taking place to avoid flickering.
561 beforeZoom_: function() {
562 this.plugin_
.postMessage({
563 type
: 'stopScrolling'
569 * A callback that's called after the zoom changes. Notify the plugin of the
570 * zoom change and to continue reacting to scroll events.
572 afterZoom_: function() {
573 var position
= this.viewport_
.position
;
574 var zoom
= this.viewport_
.zoom
;
575 if (this.isMaterial_
)
576 this.zoomSelector_
.zoomValue
= 100 * zoom
;
577 if (this.shouldManageZoom_() && !this.setZoomInProgress_
) {
578 this.setZoomInProgress_
= true;
579 chrome
.tabs
.setZoom(this.streamDetails_
.tabId
, zoom
,
580 this.setZoomComplete_
.bind(this, zoom
));
582 this.plugin_
.postMessage({
592 * A callback that's called after chrome.tabs.setZoom is complete. This will
593 * call chrome.tabs.setZoom again if the zoom level has changed since it was
595 * @param {number} lastZoom the zoom level that chrome.tabs.setZoom was called
598 setZoomComplete_: function(lastZoom
) {
599 var zoom
= this.viewport_
.zoom
;
600 if (zoom
!== lastZoom
) {
601 chrome
.tabs
.setZoom(this.streamDetails_
.tabId
, zoom
,
602 this.setZoomComplete_
.bind(this, zoom
));
604 this.setZoomInProgress_
= false;
610 * A callback that's called after the viewport changes.
612 viewportChanged_: function() {
613 if (!this.documentDimensions_
)
616 // Update the buttons selected.
617 if (!this.isMaterial_
) {
618 $('fit-to-page-button').classList
.remove('polymer-selected');
619 $('fit-to-width-button').classList
.remove('polymer-selected');
620 if (this.viewport_
.fittingType
== Viewport
.FittingType
.FIT_TO_PAGE
) {
621 $('fit-to-page-button').classList
.add('polymer-selected');
622 } else if (this.viewport_
.fittingType
==
623 Viewport
.FittingType
.FIT_TO_WIDTH
) {
624 $('fit-to-width-button').classList
.add('polymer-selected');
628 // Offset the toolbar position so that it doesn't move if scrollbars appear.
629 var hasScrollbars
= this.viewport_
.documentHasScrollbars();
630 var scrollbarWidth
= this.viewport_
.scrollbarWidth
;
631 var verticalScrollbarWidth
= hasScrollbars
.vertical
? scrollbarWidth
: 0;
632 var horizontalScrollbarWidth
=
633 hasScrollbars
.horizontal
? scrollbarWidth
: 0;
634 var toolbarRight
= Math
.max(PDFViewer
.MIN_TOOLBAR_OFFSET
, scrollbarWidth
);
635 var toolbarBottom
= Math
.max(PDFViewer
.MIN_TOOLBAR_OFFSET
, scrollbarWidth
);
636 toolbarRight
-= verticalScrollbarWidth
;
637 toolbarBottom
-= horizontalScrollbarWidth
;
638 if (!this.isMaterial_
) {
639 this.toolbar_
.style
.right
= toolbarRight
+ 'px';
640 this.toolbar_
.style
.bottom
= toolbarBottom
+ 'px';
641 // Hide the toolbar if it doesn't fit in the viewport.
642 if (this.toolbar_
.offsetLeft
< 0 || this.toolbar_
.offsetTop
< 0)
643 this.toolbar_
.style
.visibility
= 'hidden';
645 this.toolbar_
.style
.visibility
= 'visible';
648 // Update the page indicator.
649 var visiblePage
= this.viewport_
.getMostVisiblePage();
650 if (this.isMaterial_
) {
651 this.materialToolbar_
.pageIndex
= visiblePage
;
653 this.pageIndicator_
.index
= visiblePage
;
654 if (this.documentDimensions_
.pageDimensions
.length
> 1 &&
655 hasScrollbars
.vertical
) {
656 this.pageIndicator_
.style
.visibility
= 'visible';
658 this.pageIndicator_
.style
.visibility
= 'hidden';
662 var visiblePageDimensions
= this.viewport_
.getPageScreenRect(visiblePage
);
663 var size
= this.viewport_
.size
;
664 this.sendScriptingMessage_({
666 pageX
: visiblePageDimensions
.x
,
667 pageY
: visiblePageDimensions
.y
,
668 pageWidth
: visiblePageDimensions
.width
,
669 viewportWidth
: size
.width
,
670 viewportHeight
: size
.height
675 * Handle a scripting message from outside the extension (typically sent by
676 * PDFScriptingAPI in a page containing the extension) to interact with the
678 * @param {MessageObject} message the message to handle.
680 handleScriptingMessage: function(message
) {
681 if (this.parentWindow_
!= message
.source
) {
682 this.parentWindow_
= message
.source
;
683 // Ensure that we notify the embedder if the document is loaded.
685 this.sendScriptingMessage_({
686 type
: 'documentLoaded'
691 if (this.handlePrintPreviewScriptingMessage_(message
))
694 // Delay scripting messages from users of the scripting API until the
695 // document is loaded. This simplifies use of the APIs.
697 this.delayedScriptingMessages_
.push(message
);
701 switch (message
.data
.type
.toString()) {
702 case 'getAccessibilityJSON':
703 case 'getSelectedText':
706 this.plugin_
.postMessage(message
.data
);
713 * Handle scripting messages specific to print preview.
714 * @param {MessageObject} message the message to handle.
715 * @return {boolean} true if the message was handled, false otherwise.
717 handlePrintPreviewScriptingMessage_: function(message
) {
718 if (!this.isPrintPreview_
)
721 switch (message
.data
.type
.toString()) {
722 case 'loadPreviewPage':
723 this.plugin_
.postMessage(message
.data
);
725 case 'resetPrintPreviewMode':
726 this.loaded_
= false;
727 if (!this.inPrintPreviewMode_
) {
728 this.inPrintPreviewMode_
= true;
729 this.viewport_
.fitToPage();
732 // Stash the scroll location so that it can be restored when the new
733 // document is loaded.
734 this.lastViewportPosition_
= this.viewport_
.position
;
736 // TODO(raymes): Disable these properly in the plugin.
737 var printButton
= $('print-button');
739 printButton
.parentNode
.removeChild(printButton
);
740 var saveButton
= $('save-button');
742 saveButton
.parentNode
.removeChild(saveButton
);
744 if (!this.isMaterial_
)
745 this.pageIndicator_
.pageLabels
= message
.data
.pageNumbers
;
747 this.plugin_
.postMessage({
748 type
: 'resetPrintPreviewMode',
749 url
: message
.data
.url
,
750 grayscale
: message
.data
.grayscale
,
751 // If the PDF isn't modifiable we send 0 as the page count so that no
752 // blank placeholder pages get appended to the PDF.
753 pageCount
: (message
.data
.modifiable
?
754 message
.data
.pageNumbers
.length
: 0)
758 this.handleKeyEvent_(DeserializeKeyEvent(message
.data
.keyEvent
));
767 * Send a scripting message outside the extension (typically to
768 * PDFScriptingAPI in a page containing the extension).
769 * @param {Object} message the message to send.
771 sendScriptingMessage_: function(message
) {
772 if (this.parentWindow_
)
773 this.parentWindow_
.postMessage(message
, '*');
778 * Return whether this PDFViewer should manage zoom for its containing page.
779 * @return {boolean} Whether this PDFViewer should manage zoom for its
782 shouldManageZoom_: function() {
783 return !!(chrome
.tabs
&& !this.streamDetails_
.embedded
&&
784 this.streamDetails_
.tabId
!= -1);
788 * @type {Viewport} the viewport of the PDF viewer.
791 return this.viewport_
;
795 * Each bookmark is an Object containing a:
798 * - array of children (themselves bookmarks)
799 * @type {Array} the top-level bookmarks of the PDF.
802 return this.bookmarks_
;