summaryrefslogtreecommitdiffstats
path: root/chromium/chrome/browser/resources/pdf/pdf.js
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/chrome/browser/resources/pdf/pdf.js')
-rw-r--r--chromium/chrome/browser/resources/pdf/pdf.js512
1 files changed, 474 insertions, 38 deletions
diff --git a/chromium/chrome/browser/resources/pdf/pdf.js b/chromium/chrome/browser/resources/pdf/pdf.js
index c2a47f476a4..60384e5db6e 100644
--- a/chromium/chrome/browser/resources/pdf/pdf.js
+++ b/chromium/chrome/browser/resources/pdf/pdf.js
@@ -2,50 +2,486 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-(function() {
+'use strict';
-var plugin;
-var sizer;
+<include src="../../../../ui/webui/resources/js/util.js">
+<include src="pdf_scripting_api.js">
+<include src="viewport.js">
-function onScroll() {
- var coordinates = [window.pageXOffset, window.pageYOffset];
- plugin.postMessage(coordinates);
+/**
+ * @return {number} Width of a scrollbar in pixels
+ */
+function getScrollbarWidth() {
+ var div = document.createElement('div');
+ div.style.visibility = 'hidden';
+ div.style.overflow = 'scroll';
+ div.style.width = '50px';
+ div.style.height = '50px';
+ div.style.position = 'absolute';
+ document.body.appendChild(div);
+ var result = div.offsetWidth - div.clientWidth;
+ div.parentNode.removeChild(div);
+ return result;
}
-function handleMessage(message) {
- if (message.data['type'] == 'document_dimensions') {
- if (sizer.style.height != message.data['document_height'] + 'px') {
- sizer.style.height = message.data['document_height'] + 'px';
- sizer.style.width = message.data['document_width'] + 'px';
- }
+/**
+ * The minimum number of pixels to offset the toolbar by from the bottom and
+ * right side of the screen.
+ */
+PDFViewer.MIN_TOOLBAR_OFFSET = 15;
+
+/**
+ * Creates a new PDFViewer. There should only be one of these objects per
+ * document.
+ */
+function PDFViewer() {
+ this.loaded = false;
+
+ // The sizer element is placed behind the plugin element to cause scrollbars
+ // to be displayed in the window. It is sized according to the document size
+ // of the pdf and zoom level.
+ this.sizer_ = $('sizer');
+ this.toolbar_ = $('toolbar');
+ this.pageIndicator_ = $('page-indicator');
+ this.progressBar_ = $('progress-bar');
+ this.passwordScreen_ = $('password-screen');
+ this.passwordScreen_.addEventListener('password-submitted',
+ this.onPasswordSubmitted_.bind(this));
+ this.errorScreen_ = $('error-screen');
+
+ // Create the viewport.
+ this.viewport_ = new Viewport(window,
+ this.sizer_,
+ this.viewportChangedCallback_.bind(this),
+ getScrollbarWidth());
+
+ // Create the plugin object dynamically so we can set its src. The plugin
+ // element is sized to fill the entire window and is set to be fixed
+ // positioning, acting as a viewport. The plugin renders into this viewport
+ // according to the scroll position of the window.
+ this.plugin_ = document.createElement('object');
+ // NOTE: The plugin's 'id' field must be set to 'plugin' since
+ // chrome/renderer/printing/print_web_view_helper.cc actually references it.
+ this.plugin_.id = 'plugin';
+ this.plugin_.type = 'application/x-google-chrome-pdf';
+ this.plugin_.addEventListener('message', this.handlePluginMessage_.bind(this),
+ false);
+
+ // Handle scripting messages from outside the extension that wish to interact
+ // with it. We also send a message indicating that extension has loaded and
+ // is ready to receive messages.
+ window.addEventListener('message', this.handleScriptingMessage_.bind(this),
+ false);
+ this.sendScriptingMessage_({type: 'readyToReceive'});
+
+ // If the viewer is started from a MIME type request, there will be a
+ // background page and stream details object with the details of the request.
+ // Otherwise, we take the query string of the URL to indicate the URL of the
+ // PDF to load. This is used for print preview in particular.
+ if (chrome.extension.getBackgroundPage &&
+ chrome.extension.getBackgroundPage()) {
+ this.streamDetails =
+ chrome.extension.getBackgroundPage().popStreamDetails();
}
-}
-function load() {
- window.addEventListener('scroll',
- function() { webkitRequestAnimationFrame(onScroll); });
-
- // The pdf location is passed in the document url in the format:
- // http://<.../pdf.html>?<pdf location>.
- var url = window.location.search.substring(1);
- plugin = document.createElement('object');
- plugin.setAttribute('width', '100%');
- plugin.setAttribute('height', '100%');
- plugin.setAttribute('type', 'application/x-google-chrome-pdf');
- plugin.setAttribute('src', url);
- plugin.style.zIndex = '1';
- plugin.style.position = 'fixed';
- plugin.addEventListener('message', handleMessage, false);
- document.body.appendChild(plugin);
-
- sizer = document.createElement('div');
- sizer.style.zIndex = '0';
- sizer.style.position = 'absolute';
- sizer.style.width = '100%';
- sizer.style.height = '100%';
- document.body.appendChild(sizer);
+ if (!this.streamDetails) {
+ // The URL of this page will be of the form
+ // "chrome-extension://<extension id>?<pdf url>". We pull out the <pdf url>
+ // part here.
+ var url = window.location.search.substring(1);
+ this.streamDetails = {
+ streamUrl: url,
+ originalUrl: url,
+ responseHeaders: ''
+ };
+ }
+
+ this.plugin_.setAttribute('src', this.streamDetails.originalUrl);
+ this.plugin_.setAttribute('stream-url', this.streamDetails.streamUrl);
+ var headers = '';
+ for (var header in this.streamDetails.responseHeaders) {
+ headers += header + ': ' +
+ this.streamDetails.responseHeaders[header] + '\n';
+ }
+ this.plugin_.setAttribute('headers', headers);
+
+ if (window.top == window)
+ this.plugin_.setAttribute('full-frame', '');
+ document.body.appendChild(this.plugin_);
+
+ // Setup the button event listeners.
+ $('fit-to-width-button').addEventListener('click',
+ this.viewport_.fitToWidth.bind(this.viewport_));
+ $('fit-to-page-button').addEventListener('click',
+ this.viewport_.fitToPage.bind(this.viewport_));
+ $('zoom-in-button').addEventListener('click',
+ this.viewport_.zoomIn.bind(this.viewport_));
+ $('zoom-out-button').addEventListener('click',
+ this.viewport_.zoomOut.bind(this.viewport_));
+ $('save-button-link').href = this.streamDetails.originalUrl;
+ $('print-button').addEventListener('click', this.print_.bind(this));
+
+ // Setup the keyboard event listener.
+ document.onkeydown = this.handleKeyEvent_.bind(this);
}
-load();
+PDFViewer.prototype = {
+ /**
+ * @private
+ * Handle key events. These may come from the user directly or via the
+ * scripting API.
+ * @param {KeyboardEvent} e the event to handle.
+ */
+ handleKeyEvent_: function(e) {
+ var position = this.viewport_.position;
+ // Certain scroll events may be sent from outside of the extension.
+ var fromScriptingAPI = e.type == 'scriptingKeypress';
+
+ switch (e.keyCode) {
+ case 33: // Page up key.
+ // Go to the previous page if we are fit-to-page.
+ if (this.viewport_.fittingType == Viewport.FittingType.FIT_TO_PAGE) {
+ this.viewport_.goToPage(this.viewport_.getMostVisiblePage() - 1);
+ // Since we do the movement of the page.
+ e.preventDefault();
+ } else if (fromScriptingAPI) {
+ position.y -= this.viewport.size.height;
+ this.viewport.position = position;
+ }
+ return;
+ case 34: // Page down key.
+ // Go to the next page if we are fit-to-page.
+ if (this.viewport_.fittingType == Viewport.FittingType.FIT_TO_PAGE) {
+ this.viewport_.goToPage(this.viewport_.getMostVisiblePage() + 1);
+ // Since we do the movement of the page.
+ e.preventDefault();
+ } else if (fromScriptingAPI) {
+ position.y += this.viewport.size.height;
+ this.viewport.position = position;
+ }
+ return;
+ case 37: // Left arrow key.
+ // Go to the previous page if there are no horizontal scrollbars.
+ if (!this.viewport_.documentHasScrollbars().x) {
+ this.viewport_.goToPage(this.viewport_.getMostVisiblePage() - 1);
+ // Since we do the movement of the page.
+ e.preventDefault();
+ } else if (fromScriptingAPI) {
+ position.x -= Viewport.SCROLL_INCREMENT;
+ this.viewport.position = position;
+ }
+ return;
+ case 38: // Up arrow key.
+ if (fromScriptingAPI) {
+ position.y -= Viewport.SCROLL_INCREMENT;
+ this.viewport.position = position;
+ }
+ return;
+ case 39: // Right arrow key.
+ // Go to the next page if there are no horizontal scrollbars.
+ if (!this.viewport_.documentHasScrollbars().x) {
+ this.viewport_.goToPage(this.viewport_.getMostVisiblePage() + 1);
+ // Since we do the movement of the page.
+ e.preventDefault();
+ } else if (fromScriptingAPI) {
+ position.x += Viewport.SCROLL_INCREMENT;
+ this.viewport.position = position;
+ }
+ return;
+ case 40: // Down arrow key.
+ if (fromScriptingAPI) {
+ position.y += Viewport.SCROLL_INCREMENT;
+ this.viewport.position = position;
+ }
+ return;
+ case 187: // +/= key.
+ case 107: // Numpad + key.
+ if (e.ctrlKey || e.metaKey) {
+ this.viewport_.zoomIn();
+ // Since we do the zooming of the page.
+ e.preventDefault();
+ }
+ return;
+ case 189: // -/_ key.
+ case 109: // Numpad - key.
+ if (e.ctrlKey || e.metaKey) {
+ this.viewport_.zoomOut();
+ // Since we do the zooming of the page.
+ e.preventDefault();
+ }
+ return;
+ case 83: // s key.
+ if (e.ctrlKey || e.metaKey) {
+ // Simulate a click on the button so that the <a download ...>
+ // attribute is used.
+ $('save-button-link').click();
+ // Since we do the saving of the page.
+ e.preventDefault();
+ }
+ return;
+ case 80: // p key.
+ if (e.ctrlKey || e.metaKey) {
+ this.print_();
+ // Since we do the printing of the page.
+ e.preventDefault();
+ }
+ return;
+ }
+ },
+
+ /**
+ * @private
+ * Notify the plugin to print.
+ */
+ print_: function() {
+ this.plugin_.postMessage({
+ type: 'print',
+ });
+ },
+
+ /**
+ * @private
+ * Update the loading progress of the document in response to a progress
+ * message being received from the plugin.
+ * @param {number} progress the progress as a percentage.
+ */
+ updateProgress_: function(progress) {
+ this.progressBar_.progress = progress;
+ if (progress == -1) {
+ // Document load failed.
+ this.errorScreen_.style.visibility = 'visible';
+ this.sizer_.style.display = 'none';
+ this.toolbar_.style.visibility = 'hidden';
+ if (this.passwordScreen_.active) {
+ this.passwordScreen_.deny();
+ this.passwordScreen_.active = false;
+ }
+ } else if (progress == 100) {
+ // Document load complete.
+ this.loaded = true;
+ var loadEvent = new Event('pdfload');
+ window.dispatchEvent(loadEvent);
+ this.sendScriptingMessage_({
+ type: 'documentLoaded'
+ });
+ if (this.lastViewportPosition_)
+ this.viewport_.position = this.lastViewportPosition_;
+ }
+ },
+
+ /**
+ * @private
+ * An event handler for handling password-submitted events. These are fired
+ * when an event is entered into the password screen.
+ * @param {Object} event a password-submitted event.
+ */
+ onPasswordSubmitted_: function(event) {
+ this.plugin_.postMessage({
+ type: 'getPasswordComplete',
+ password: event.detail.password
+ });
+ },
+
+ /**
+ * @private
+ * An event handler for handling message events received from the plugin.
+ * @param {MessageObject} message a message event.
+ */
+ handlePluginMessage_: function(message) {
+ switch (message.data.type.toString()) {
+ case 'documentDimensions':
+ this.documentDimensions_ = message.data;
+ this.viewport_.setDocumentDimensions(this.documentDimensions_);
+ this.toolbar_.style.visibility = 'visible';
+ // If we received the document dimensions, the password was good so we
+ // can dismiss the password screen.
+ if (this.passwordScreen_.active)
+ this.passwordScreen_.accept();
+
+ this.pageIndicator_.initialFadeIn();
+ this.toolbar_.initialFadeIn();
+ break;
+ case 'email':
+ var href = 'mailto:' + message.data.to + '?cc=' + message.data.cc +
+ '&bcc=' + message.data.bcc + '&subject=' + message.data.subject +
+ '&body=' + message.data.body;
+ var w = window.open(href, '_blank', 'width=1,height=1');
+ if (w)
+ w.close();
+ break;
+ case 'getAccessibilityJSONReply':
+ this.sendScriptingMessage_(message.data);
+ break;
+ case 'getPassword':
+ // If the password screen isn't up, put it up. Otherwise we're
+ // responding to an incorrect password so deny it.
+ if (!this.passwordScreen_.active)
+ this.passwordScreen_.active = true;
+ else
+ this.passwordScreen_.deny();
+ break;
+ case 'goToPage':
+ this.viewport_.goToPage(message.data.page);
+ break;
+ case 'loadProgress':
+ this.updateProgress_(message.data.progress);
+ break;
+ case 'navigate':
+ if (message.data.newTab)
+ window.open(message.data.url);
+ else
+ window.location.href = message.data.url;
+ break;
+ case 'setScrollPosition':
+ var position = this.viewport_.position;
+ if (message.data.x != undefined)
+ position.x = message.data.x;
+ if (message.data.y != undefined)
+ position.y = message.data.y;
+ this.viewport_.position = position;
+ break;
+ case 'setTranslatedStrings':
+ this.passwordScreen_.text = message.data.getPasswordString;
+ this.progressBar_.text = message.data.loadingString;
+ this.errorScreen_.text = message.data.loadFailedString;
+ break;
+ case 'cancelStreamUrl':
+ chrome.streamsPrivate.abort(this.streamDetails.streamUrl);
+ break;
+ }
+ },
+
+ /**
+ * @private
+ * A callback that's called when the viewport changes.
+ */
+ viewportChangedCallback_: function() {
+ if (!this.documentDimensions_)
+ return;
+
+ // Update the buttons selected.
+ $('fit-to-page-button').classList.remove('polymer-selected');
+ $('fit-to-width-button').classList.remove('polymer-selected');
+ if (this.viewport_.fittingType == Viewport.FittingType.FIT_TO_PAGE) {
+ $('fit-to-page-button').classList.add('polymer-selected');
+ } else if (this.viewport_.fittingType ==
+ Viewport.FittingType.FIT_TO_WIDTH) {
+ $('fit-to-width-button').classList.add('polymer-selected');
+ }
+
+ var hasScrollbars = this.viewport_.documentHasScrollbars();
+ var scrollbarWidth = this.viewport_.scrollbarWidth;
+ // Offset the toolbar position so that it doesn't move if scrollbars appear.
+ var toolbarRight = Math.max(PDFViewer.MIN_TOOLBAR_OFFSET, scrollbarWidth);
+ var toolbarBottom = Math.max(PDFViewer.MIN_TOOLBAR_OFFSET, scrollbarWidth);
+ if (hasScrollbars.vertical)
+ toolbarRight -= scrollbarWidth;
+ if (hasScrollbars.horizontal)
+ toolbarBottom -= scrollbarWidth;
+ this.toolbar_.style.right = toolbarRight + 'px';
+ this.toolbar_.style.bottom = toolbarBottom + 'px';
+
+ // Update the page indicator.
+ var visiblePage = this.viewport_.getMostVisiblePage();
+ this.pageIndicator_.index = visiblePage;
+ if (this.documentDimensions_.pageDimensions.length > 1 &&
+ hasScrollbars.vertical) {
+ this.pageIndicator_.style.visibility = 'visible';
+ } else {
+ this.pageIndicator_.style.visibility = 'hidden';
+ }
+
+ var position = this.viewport_.position;
+ var zoom = this.viewport_.zoom;
+ // Notify the plugin of the viewport change.
+ this.plugin_.postMessage({
+ type: 'viewport',
+ zoom: zoom,
+ xOffset: position.x,
+ yOffset: position.y
+ });
+
+ var visiblePageDimensions = this.viewport_.getPageScreenRect(visiblePage);
+ var size = this.viewport_.size;
+ this.sendScriptingMessage_({
+ type: 'viewport',
+ pageX: visiblePageDimensions.x,
+ pageY: visiblePageDimensions.y,
+ pageWidth: visiblePageDimensions.width,
+ viewportWidth: size.width,
+ viewportHeight: size.height,
+ });
+ },
+
+ /**
+ * @private
+ * Handle a scripting message from outside the extension (typically sent by
+ * PDFScriptingAPI in a page containing the extension) to interact with the
+ * plugin.
+ * @param {MessageObject} message the message to handle.
+ */
+ handleScriptingMessage_: function(message) {
+ switch (message.data.type.toString()) {
+ case 'getAccessibilityJSON':
+ case 'loadPreviewPage':
+ this.plugin_.postMessage(message.data);
+ break;
+ case 'resetPrintPreviewMode':
+ if (!this.inPrintPreviewMode_) {
+ this.inPrintPreviewMode_ = true;
+ this.viewport_.fitToPage();
+ }
+
+ // Stash the scroll location so that it can be restored when the new
+ // document is loaded.
+ this.lastViewportPosition_ = this.viewport_.position;
+
+ // TODO(raymes): Disable these properly in the plugin.
+ var printButton = $('print-button');
+ if (printButton)
+ printButton.parentNode.removeChild(printButton);
+ var saveButton = $('save-button');
+ if (saveButton)
+ saveButton.parentNode.removeChild(saveButton);
+
+ this.pageIndicator_.pageLabels = message.data.pageNumbers;
+
+ this.plugin_.postMessage({
+ type: 'resetPrintPreviewMode',
+ url: message.data.url,
+ grayscale: message.data.grayscale,
+ // If the PDF isn't modifiable we send 0 as the page count so that no
+ // blank placeholder pages get appended to the PDF.
+ pageCount: (message.data.modifiable ?
+ message.data.pageNumbers.length : 0)
+ });
+ break;
+ case 'sendKeyEvent':
+ var e = document.createEvent('Event');
+ e.initEvent('scriptingKeypress');
+ e.keyCode = message.data.keyCode;
+ this.handleKeyEvent_(e);
+ break;
+ }
+
+ },
+
+ /**
+ * @private
+ * Send a scripting message outside the extension (typically to
+ * PDFScriptingAPI in a page containing the extension).
+ * @param {Object} message the message to send.
+ */
+ sendScriptingMessage_: function(message) {
+ window.parent.postMessage(message, '*');
+ },
+
+ /**
+ * @type {Viewport} the viewport of the PDF viewer.
+ */
+ get viewport() {
+ return this.viewport_;
+ }
+};
-})();
+var viewer = new PDFViewer();