diff options
Diffstat (limited to 'chromium/chrome/browser/resources/pdf/pdf.js')
-rw-r--r-- | chromium/chrome/browser/resources/pdf/pdf.js | 512 |
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(); |