pdf.js revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
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. 4 5'use strict'; 6 7<include src="../../../../ui/webui/resources/js/util.js"> 8<include src="viewport.js"> 9<include src="pdf_scripting_api.js"> 10 11/** 12 * Creates a new PDFViewer. There should only be one of these objects per 13 * document. 14 */ 15function PDFViewer() { 16 // The sizer element is placed behind the plugin element to cause scrollbars 17 // to be displayed in the window. It is sized according to the document size 18 // of the pdf and zoom level. 19 this.sizer_ = $('sizer'); 20 this.toolbar_ = $('toolbar'); 21 this.pageIndicator_ = $('page-indicator'); 22 this.progressBar_ = $('progress-bar'); 23 this.passwordScreen_ = $('password-screen'); 24 this.passwordScreen_.addEventListener('password-submitted', 25 this.onPasswordSubmitted_.bind(this)); 26 this.errorScreen_ = $('error-screen'); 27 28 // Create the viewport. 29 this.viewport_ = new Viewport(window, 30 this.sizer_, 31 this.viewportChangedCallback_.bind(this)); 32 33 // Create the plugin object dynamically so we can set its src. The plugin 34 // element is sized to fill the entire window and is set to be fixed 35 // positioning, acting as a viewport. The plugin renders into this viewport 36 // according to the scroll position of the window. 37 this.plugin_ = document.createElement('object'); 38 this.plugin_.id = 'plugin'; 39 this.plugin_.type = 'application/x-google-chrome-pdf'; 40 this.plugin_.addEventListener('message', this.handleMessage_.bind(this), 41 false); 42 43 // If the viewer is started from a MIME type request, there will be a 44 // background page and stream details object with the details of the request. 45 // Otherwise, we take the query string of the URL to indicate the URL of the 46 // PDF to load. This is used for print preview in particular. 47 var streamDetails; 48 if (chrome.extension.getBackgroundPage && 49 chrome.extension.getBackgroundPage()) { 50 streamDetails = chrome.extension.getBackgroundPage().popStreamDetails(); 51 } 52 53 if (!streamDetails) { 54 // The URL of this page will be of the form 55 // "chrome-extension://<extension id>?<pdf url>". We pull out the <pdf url> 56 // part here. 57 var url = window.location.search.substring(1); 58 streamDetails = { 59 streamUrl: url, 60 originalUrl: url 61 }; 62 } 63 64 this.plugin_.setAttribute('src', streamDetails.streamUrl); 65 document.body.appendChild(this.plugin_); 66 67 this.messagingHost_ = new PDFMessagingHost(window, this); 68 69 this.setupEventListeners_(streamDetails); 70} 71 72PDFViewer.prototype = { 73 /** 74 * @private 75 * Sets up event listeners for key shortcuts and also the UI buttons. 76 * @param {Object} streamDetails the details of the original HTTP request for 77 * the PDF. 78 */ 79 setupEventListeners_: function(streamDetails) { 80 // Setup the button event listeners. 81 $('fit-to-width-button').addEventListener('click', 82 this.viewport_.fitToWidth.bind(this.viewport_)); 83 $('fit-to-page-button').addEventListener('click', 84 this.viewport_.fitToPage.bind(this.viewport_)); 85 $('zoom-in-button').addEventListener('click', 86 this.viewport_.zoomIn.bind(this.viewport_)); 87 $('zoom-out-button').addEventListener('click', 88 this.viewport_.zoomOut.bind(this.viewport_)); 89 $('save-button-link').href = streamDetails.originalUrl; 90 $('print-button').addEventListener('click', this.print_.bind(this)); 91 92 // Setup keyboard event listeners. 93 document.onkeydown = function(e) { 94 switch (e.keyCode) { 95 case 37: // Left arrow key. 96 // Go to the previous page if there are no horizontal scrollbars. 97 if (!this.viewport_.documentHasScrollbars().x) { 98 this.viewport_.goToPage(this.viewport_.getMostVisiblePage() - 1); 99 // Since we do the movement of the page. 100 e.preventDefault(); 101 } 102 return; 103 case 33: // Page up key. 104 // Go to the previous page if we are fit-to-page. 105 if (isFitToPageEnabled()) { 106 this.viewport_.goToPage(this.viewport_.getMostVisiblePage() - 1); 107 // Since we do the movement of the page. 108 e.preventDefault(); 109 } 110 return; 111 case 39: // Right arrow key. 112 // Go to the next page if there are no horizontal scrollbars. 113 if (!this.viewport_.documentHasScrollbars().x) { 114 this.viewport_.goToPage(this.viewport_.getMostVisiblePage() + 1); 115 // Since we do the movement of the page. 116 e.preventDefault(); 117 } 118 return; 119 case 34: // Page down key. 120 // Go to the next page if we are fit-to-page. 121 if (isFitToPageEnabled()) { 122 this.viewport_.goToPage(this.viewport_.getMostVisiblePage() + 1); 123 // Since we do the movement of the page. 124 e.preventDefault(); 125 } 126 return; 127 case 187: // +/= key. 128 case 107: // Numpad + key. 129 if (e.ctrlKey || e.metaKey) { 130 this.viewport_.zoomIn(); 131 // Since we do the zooming of the page. 132 e.preventDefault(); 133 } 134 return; 135 case 189: // -/_ key. 136 case 109: // Numpad - key. 137 if (e.ctrlKey || e.metaKey) { 138 this.viewport_.zoomOut(); 139 // Since we do the zooming of the page. 140 e.preventDefault(); 141 } 142 return; 143 case 83: // s key. 144 if (e.ctrlKey || e.metaKey) { 145 // Simulate a click on the button so that the <a download ...> 146 // attribute is used. 147 $('save-button-link').click(); 148 // Since we do the saving of the page. 149 e.preventDefault(); 150 } 151 return; 152 case 80: // p key. 153 if (e.ctrlKey || e.metaKey) { 154 this.print_(); 155 // Since we do the printing of the page. 156 e.preventDefault(); 157 } 158 return; 159 } 160 }.bind(this); 161 }, 162 163 /** 164 * @private 165 * Notify the plugin to print. 166 */ 167 print_: function() { 168 this.plugin_.postMessage({ 169 type: 'print', 170 }); 171 }, 172 173 /** 174 * @private 175 * Update the loading progress of the document in response to a progress 176 * message being received from the plugin. 177 * @param {number} progress the progress as a percentage. 178 */ 179 updateProgress_: function(progress) { 180 this.progressBar_.progress = progress; 181 if (progress == -1) { 182 // Document load failed. 183 this.errorScreen_.style.visibility = 'visible'; 184 this.sizer_.style.display = 'none'; 185 this.toolbar_.style.visibility = 'hidden'; 186 if (this.passwordScreen_.active) { 187 this.passwordScreen_.deny(); 188 this.passwordScreen_.active = false; 189 } 190 } else if (progress == 100) { 191 // Document load complete. 192 var loadEvent = new Event('pdfload'); 193 window.dispatchEvent(loadEvent); 194 // TODO(raymes): Replace this and other callbacks with events. 195 this.messagingHost_.documentLoaded(); 196 if (this.lastViewportPosition_) 197 this.viewport_.position = this.lastViewportPosition_; 198 } 199 }, 200 201 /** 202 * @private 203 * An event handler for handling password-submitted events. These are fired 204 * when an event is entered into the password screen. 205 * @param {Object} event a password-submitted event. 206 */ 207 onPasswordSubmitted_: function(event) { 208 this.plugin_.postMessage({ 209 type: 'getPasswordComplete', 210 password: event.detail.password 211 }); 212 }, 213 214 /** 215 * @private 216 * An event handler for handling message events received from the plugin. 217 * @param {MessageObject} message a message event. 218 */ 219 handleMessage_: function(message) { 220 switch (message.data.type.toString()) { 221 case 'documentDimensions': 222 this.documentDimensions_ = message.data; 223 this.viewport_.setDocumentDimensions(this.documentDimensions_); 224 this.toolbar_.style.visibility = 'visible'; 225 // If we received the document dimensions, the password was good so we 226 // can dismiss the password screen. 227 if (this.passwordScreen_.active) 228 this.passwordScreen_.accept(); 229 230 this.pageIndicator_.initialFadeIn(); 231 this.toolbar_.initialFadeIn(); 232 break; 233 case 'loadProgress': 234 this.updateProgress_(message.data.progress); 235 break; 236 case 'goToPage': 237 this.viewport_.goToPage(message.data.page); 238 break; 239 case 'setScrollPosition': 240 var position = this.viewport_.position; 241 if (message.data.x != undefined) 242 position.x = message.data.x; 243 if (message.data.y != undefined) 244 position.y = message.data.y; 245 this.viewport_.position = position; 246 break; 247 case 'getPassword': 248 // If the password screen isn't up, put it up. Otherwise we're 249 // responding to an incorrect password so deny it. 250 if (!this.passwordScreen_.active) 251 this.passwordScreen_.active = true; 252 else 253 this.passwordScreen_.deny(); 254 break; 255 case 'setTranslatedStrings': 256 this.passwordScreen_.text = message.data.getPasswordString; 257 this.progressBar_.text = message.data.loadingString; 258 this.errorScreen_.text = message.data.loadFailedString; 259 break; 260 } 261 }, 262 263 /** 264 * @private 265 * A callback that's called when the viewport changes. 266 */ 267 viewportChangedCallback_: function() { 268 if (!this.documentDimensions_) 269 return; 270 271 // Update the buttons selected. 272 $('fit-to-page-button').classList.remove('polymer-selected'); 273 $('fit-to-width-button').classList.remove('polymer-selected'); 274 if (this.viewport_.fittingType == Viewport.FittingType.FIT_TO_PAGE) { 275 $('fit-to-page-button').classList.add('polymer-selected'); 276 } else if (this.viewport_.fittingType == 277 Viewport.FittingType.FIT_TO_WIDTH) { 278 $('fit-to-width-button').classList.add('polymer-selected'); 279 } 280 281 var hasScrollbars = this.viewport_.documentHasScrollbars(); 282 var scrollbarWidth = this.viewport_.scrollbarWidth; 283 // Offset the toolbar position so that it doesn't move if scrollbars appear. 284 var toolbarRight = hasScrollbars.vertical ? 0 : scrollbarWidth; 285 var toolbarBottom = hasScrollbars.horizontal ? 0 : scrollbarWidth; 286 this.toolbar_.style.right = toolbarRight + 'px'; 287 this.toolbar_.style.bottom = toolbarBottom + 'px'; 288 289 // Update the page indicator. 290 this.pageIndicator_.index = this.viewport_.getMostVisiblePage(); 291 if (this.documentDimensions_.pageDimensions.length > 1 && 292 hasScrollbars.vertical) { 293 this.pageIndicator_.style.visibility = 'visible'; 294 } else { 295 this.pageIndicator_.style.visibility = 'hidden'; 296 } 297 298 this.messagingHost_.viewportChanged(); 299 300 var position = this.viewport_.position; 301 var zoom = this.viewport_.zoom; 302 // Notify the plugin of the viewport change. 303 this.plugin_.postMessage({ 304 type: 'viewport', 305 zoom: zoom, 306 xOffset: position.x, 307 yOffset: position.y 308 }); 309 }, 310 311 /** 312 * Resets the viewer into print preview mode, which is used for Chrome print 313 * preview. 314 * @param {string} url the url of the pdf to load. 315 * @param {boolean} grayscale true if the pdf should be displayed in 316 * grayscale, false otherwise. 317 * @param {Array.<number>} pageNumbers an array of the number to label each 318 * page in the document. 319 * @param {boolean} modifiable whether the PDF is modifiable or not. 320 */ 321 resetPrintPreviewMode: function(url, 322 grayscale, 323 pageNumbers, 324 modifiable) { 325 if (!this.inPrintPreviewMode_) { 326 this.inPrintPreviewMode_ = true; 327 this.viewport_.fitToPage(); 328 } 329 330 // Stash the scroll location so that it can be restored when the new 331 // document is loaded. 332 this.lastViewportPosition_ = this.viewport_.position; 333 334 // TODO(raymes): Disable these properly in the plugin. 335 var printButton = $('print-button'); 336 if (printButton) 337 printButton.parentNode.removeChild(printButton); 338 var saveButton = $('save-button'); 339 if (saveButton) 340 saveButton.parentNode.removeChild(saveButton); 341 342 this.pageIndicator_.pageLabels = pageNumbers; 343 344 this.plugin_.postMessage({ 345 type: 'resetPrintPreviewMode', 346 url: url, 347 grayscale: grayscale, 348 // If the PDF isn't modifiable we send 0 as the page count so that no 349 // blank placeholder pages get appended to the PDF. 350 pageCount: (modifiable ? pageNumbers.length : 0) 351 }); 352 }, 353 354 /** 355 * Load a page into the document while in print preview mode. 356 * @param {string} url the url of the pdf page to load. 357 * @param {number} index the index of the page to load. 358 */ 359 loadPreviewPage: function(url, index) { 360 this.plugin_.postMessage({ 361 type: 'loadPreviewPage', 362 url: url, 363 index: index 364 }); 365 }, 366 367 /** 368 * @type {Viewport} the viewport of the PDF viewer. 369 */ 370 get viewport() { 371 return this.viewport_; 372 } 373}; 374 375var viewer = new PDFViewer(); 376