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