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_
;