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