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.
7 <include src
="../../../../ui/webui/resources/js/util.js">
8 <include src
="viewport.js">
9 <include src
="pdf_scripting_api.js">
12 * @return {number} Width of a scrollbar in pixels
14 function getScrollbarWidth() {
15 var div
= document
.createElement('div');
16 div
.style
.visibility
= 'hidden';
17 div
.style
.overflow
= 'scroll';
18 div
.style
.width
= '50px';
19 div
.style
.height
= '50px';
20 div
.style
.position
= 'absolute';
21 document
.body
.appendChild(div
);
22 var result
= div
.offsetWidth
- div
.clientWidth
;
23 div
.parentNode
.removeChild(div
);
28 * Creates a new PDFViewer. There should only be one of these objects per
31 function PDFViewer() {
32 // The sizer element is placed behind the plugin element to cause scrollbars
33 // to be displayed in the window. It is sized according to the document size
34 // of the pdf and zoom level.
35 this.sizer_
= $('sizer');
36 this.toolbar_
= $('toolbar');
37 this.pageIndicator_
= $('page-indicator');
38 this.progressBar_
= $('progress-bar');
39 this.passwordScreen_
= $('password-screen');
40 this.passwordScreen_
.addEventListener('password-submitted',
41 this.onPasswordSubmitted_
.bind(this));
42 this.errorScreen_
= $('error-screen');
44 // Create the viewport.
45 this.viewport_
= new Viewport(window
,
47 this.viewportChangedCallback_
.bind(this),
50 // Create the plugin object dynamically so we can set its src. The plugin
51 // element is sized to fill the entire window and is set to be fixed
52 // positioning, acting as a viewport. The plugin renders into this viewport
53 // according to the scroll position of the window.
54 this.plugin_
= document
.createElement('object');
55 this.plugin_
.id
= 'plugin';
56 this.plugin_
.type
= 'application/x-google-chrome-pdf';
57 this.plugin_
.addEventListener('message', this.handleMessage_
.bind(this),
60 // If the viewer is started from a MIME type request, there will be a
61 // background page and stream details object with the details of the request.
62 // Otherwise, we take the query string of the URL to indicate the URL of the
63 // PDF to load. This is used for print preview in particular.
65 if (chrome
.extension
.getBackgroundPage
&&
66 chrome
.extension
.getBackgroundPage()) {
67 streamDetails
= chrome
.extension
.getBackgroundPage().popStreamDetails();
71 // The URL of this page will be of the form
72 // "chrome-extension://<extension id>?<pdf url>". We pull out the <pdf url>
74 var url
= window
.location
.search
.substring(1);
81 this.plugin_
.setAttribute('src', streamDetails
.streamUrl
);
82 if (window
.top
== window
)
83 this.plugin_
.setAttribute('full-frame', '');
84 document
.body
.appendChild(this.plugin_
);
86 this.messagingHost_
= new PDFMessagingHost(window
, this);
88 this.setupEventListeners_(streamDetails
);
91 PDFViewer
.prototype = {
94 * Sets up event listeners for key shortcuts and also the UI buttons.
95 * @param {Object} streamDetails the details of the original HTTP request for
98 setupEventListeners_: function(streamDetails
) {
99 // Setup the button event listeners.
100 $('fit-to-width-button').addEventListener('click',
101 this.viewport_
.fitToWidth
.bind(this.viewport_
));
102 $('fit-to-page-button').addEventListener('click',
103 this.viewport_
.fitToPage
.bind(this.viewport_
));
104 $('zoom-in-button').addEventListener('click',
105 this.viewport_
.zoomIn
.bind(this.viewport_
));
106 $('zoom-out-button').addEventListener('click',
107 this.viewport_
.zoomOut
.bind(this.viewport_
));
108 $('save-button-link').href
= streamDetails
.originalUrl
;
109 $('print-button').addEventListener('click', this.print_
.bind(this));
111 // Setup keyboard event listeners.
112 document
.onkeydown = function(e
) {
114 case 37: // Left arrow key.
115 // Go to the previous page if there are no horizontal scrollbars.
116 if (!this.viewport_
.documentHasScrollbars().x
) {
117 this.viewport_
.goToPage(this.viewport_
.getMostVisiblePage() - 1);
118 // Since we do the movement of the page.
122 case 33: // Page up key.
123 // Go to the previous page if we are fit-to-page.
124 if (this.viewport_
.fittingType
== Viewport
.FittingType
.FIT_TO_PAGE
) {
125 this.viewport_
.goToPage(this.viewport_
.getMostVisiblePage() - 1);
126 // Since we do the movement of the page.
130 case 39: // Right arrow key.
131 // Go to the next page if there are no horizontal scrollbars.
132 if (!this.viewport_
.documentHasScrollbars().x
) {
133 this.viewport_
.goToPage(this.viewport_
.getMostVisiblePage() + 1);
134 // Since we do the movement of the page.
138 case 34: // Page down key.
139 // Go to the next page if we are fit-to-page.
140 if (this.viewport_
.fittingType
== Viewport
.FittingType
.FIT_TO_PAGE
) {
141 this.viewport_
.goToPage(this.viewport_
.getMostVisiblePage() + 1);
142 // Since we do the movement of the page.
146 case 187: // +/= key.
147 case 107: // Numpad + key.
148 if (e
.ctrlKey
|| e
.metaKey
) {
149 this.viewport_
.zoomIn();
150 // Since we do the zooming of the page.
154 case 189: // -/_ key.
155 case 109: // Numpad - key.
156 if (e
.ctrlKey
|| e
.metaKey
) {
157 this.viewport_
.zoomOut();
158 // Since we do the zooming of the page.
163 if (e
.ctrlKey
|| e
.metaKey
) {
164 // Simulate a click on the button so that the <a download ...>
165 // attribute is used.
166 $('save-button-link').click();
167 // Since we do the saving of the page.
172 if (e
.ctrlKey
|| e
.metaKey
) {
174 // Since we do the printing of the page.
184 * Notify the plugin to print.
187 this.plugin_
.postMessage({
194 * Update the loading progress of the document in response to a progress
195 * message being received from the plugin.
196 * @param {number} progress the progress as a percentage.
198 updateProgress_: function(progress
) {
199 this.progressBar_
.progress
= progress
;
200 if (progress
== -1) {
201 // Document load failed.
202 this.errorScreen_
.style
.visibility
= 'visible';
203 this.sizer_
.style
.display
= 'none';
204 this.toolbar_
.style
.visibility
= 'hidden';
205 if (this.passwordScreen_
.active
) {
206 this.passwordScreen_
.deny();
207 this.passwordScreen_
.active
= false;
209 } else if (progress
== 100) {
210 // Document load complete.
211 var loadEvent
= new Event('pdfload');
212 window
.dispatchEvent(loadEvent
);
213 // TODO(raymes): Replace this and other callbacks with events.
214 this.messagingHost_
.documentLoaded();
215 if (this.lastViewportPosition_
)
216 this.viewport_
.position
= this.lastViewportPosition_
;
222 * An event handler for handling password-submitted events. These are fired
223 * when an event is entered into the password screen.
224 * @param {Object} event a password-submitted event.
226 onPasswordSubmitted_: function(event
) {
227 this.plugin_
.postMessage({
228 type
: 'getPasswordComplete',
229 password
: event
.detail
.password
235 * An event handler for handling message events received from the plugin.
236 * @param {MessageObject} message a message event.
238 handleMessage_: function(message
) {
239 switch (message
.data
.type
.toString()) {
240 case 'documentDimensions':
241 this.documentDimensions_
= message
.data
;
242 this.viewport_
.setDocumentDimensions(this.documentDimensions_
);
243 this.toolbar_
.style
.visibility
= 'visible';
244 // If we received the document dimensions, the password was good so we
245 // can dismiss the password screen.
246 if (this.passwordScreen_
.active
)
247 this.passwordScreen_
.accept();
249 this.pageIndicator_
.initialFadeIn();
250 this.toolbar_
.initialFadeIn();
253 this.updateProgress_(message
.data
.progress
);
256 this.viewport_
.goToPage(message
.data
.page
);
258 case 'setScrollPosition':
259 var position
= this.viewport_
.position
;
260 if (message
.data
.x
!= undefined)
261 position
.x
= message
.data
.x
;
262 if (message
.data
.y
!= undefined)
263 position
.y
= message
.data
.y
;
264 this.viewport_
.position
= position
;
267 // If the password screen isn't up, put it up. Otherwise we're
268 // responding to an incorrect password so deny it.
269 if (!this.passwordScreen_
.active
)
270 this.passwordScreen_
.active
= true;
272 this.passwordScreen_
.deny();
274 case 'setTranslatedStrings':
275 this.passwordScreen_
.text
= message
.data
.getPasswordString
;
276 this.progressBar_
.text
= message
.data
.loadingString
;
277 this.errorScreen_
.text
= message
.data
.loadFailedString
;
284 * A callback that's called when the viewport changes.
286 viewportChangedCallback_: function() {
287 if (!this.documentDimensions_
)
290 // Update the buttons selected.
291 $('fit-to-page-button').classList
.remove('polymer-selected');
292 $('fit-to-width-button').classList
.remove('polymer-selected');
293 if (this.viewport_
.fittingType
== Viewport
.FittingType
.FIT_TO_PAGE
) {
294 $('fit-to-page-button').classList
.add('polymer-selected');
295 } else if (this.viewport_
.fittingType
==
296 Viewport
.FittingType
.FIT_TO_WIDTH
) {
297 $('fit-to-width-button').classList
.add('polymer-selected');
300 var hasScrollbars
= this.viewport_
.documentHasScrollbars();
301 var scrollbarWidth
= this.viewport_
.scrollbarWidth
;
302 // Offset the toolbar position so that it doesn't move if scrollbars appear.
303 var toolbarRight
= hasScrollbars
.vertical
? 0 : scrollbarWidth
;
304 var toolbarBottom
= hasScrollbars
.horizontal
? 0 : scrollbarWidth
;
305 this.toolbar_
.style
.right
= toolbarRight
+ 'px';
306 this.toolbar_
.style
.bottom
= toolbarBottom
+ 'px';
308 // Update the page indicator.
309 this.pageIndicator_
.index
= this.viewport_
.getMostVisiblePage();
310 if (this.documentDimensions_
.pageDimensions
.length
> 1 &&
311 hasScrollbars
.vertical
) {
312 this.pageIndicator_
.style
.visibility
= 'visible';
314 this.pageIndicator_
.style
.visibility
= 'hidden';
317 this.messagingHost_
.viewportChanged();
319 var position
= this.viewport_
.position
;
320 var zoom
= this.viewport_
.zoom
;
321 // Notify the plugin of the viewport change.
322 this.plugin_
.postMessage({
331 * Resets the viewer into print preview mode, which is used for Chrome print
333 * @param {string} url the url of the pdf to load.
334 * @param {boolean} grayscale true if the pdf should be displayed in
335 * grayscale, false otherwise.
336 * @param {Array.<number>} pageNumbers an array of the number to label each
337 * page in the document.
338 * @param {boolean} modifiable whether the PDF is modifiable or not.
340 resetPrintPreviewMode: function(url
,
344 if (!this.inPrintPreviewMode_
) {
345 this.inPrintPreviewMode_
= true;
346 this.viewport_
.fitToPage();
349 // Stash the scroll location so that it can be restored when the new
350 // document is loaded.
351 this.lastViewportPosition_
= this.viewport_
.position
;
353 // TODO(raymes): Disable these properly in the plugin.
354 var printButton
= $('print-button');
356 printButton
.parentNode
.removeChild(printButton
);
357 var saveButton
= $('save-button');
359 saveButton
.parentNode
.removeChild(saveButton
);
361 this.pageIndicator_
.pageLabels
= pageNumbers
;
363 this.plugin_
.postMessage({
364 type
: 'resetPrintPreviewMode',
366 grayscale
: grayscale
,
367 // If the PDF isn't modifiable we send 0 as the page count so that no
368 // blank placeholder pages get appended to the PDF.
369 pageCount
: (modifiable
? pageNumbers
.length
: 0)
374 * Load a page into the document while in print preview mode.
375 * @param {string} url the url of the pdf page to load.
376 * @param {number} index the index of the page to load.
378 loadPreviewPage: function(url
, index
) {
379 this.plugin_
.postMessage({
380 type
: 'loadPreviewPage',
387 * @type {Viewport} the viewport of the PDF viewer.
390 return this.viewport_
;
394 var viewer
= new PDFViewer();