// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-(function() {
'use strict';
-<include src="../../../../ui/webui/resources/js/util.js"></include>
-<include src="viewport.js"></include>
-
-// 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.
-var plugin;
-
-// This 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.
-var sizer;
-
-// The toolbar element.
-var viewerToolbar;
-
-// The page indicator element.
-var viewerPageIndicator;
-
-// The progress bar element.
-var viewerProgressBar;
+/**
+ * @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;
+}
-// The element indicating there was an error loading the document.
-var viewerErrorScreen;
+/**
+ * The minimum number of pixels to offset the toolbar by from the bottom and
+ * right side of the screen.
+ */
+PDFViewer.MIN_TOOLBAR_OFFSET = 15;
-// The viewport object.
-var viewport;
+/**
+ * Creates a new PDFViewer. There should only be one of these objects per
+ * document.
+ * @param {Object} streamDetails The stream object which points to the data
+ * contained in the PDF.
+ */
+function PDFViewer(streamDetails) {
+ this.streamDetails = streamDetails;
+ this.loaded = false;
-// The element displaying the password screen.
-var viewerPasswordScreen;
+ // 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');
-// The document dimensions.
-var documentDimensions;
+ // Create the viewport.
+ this.viewport_ = new Viewport(window,
+ this.sizer_,
+ this.viewportChanged_.bind(this),
+ this.beforeZoom_.bind(this),
+ this.afterZoom_.bind(this),
+ getScrollbarWidth());
-// Notify the plugin to print.
-function print() {
- plugin.postMessage({
- type: 'print',
- });
-}
+ // 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);
-// Returns true if the fit-to-page button is enabled.
-function isFitToPageEnabled() {
- return $('fit-to-page-button').classList.contains('polymer-selected');
-}
+ // 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'});
-function updateProgress(progress) {
- viewerProgressBar.progress = progress;
- if (progress == -1) {
- // Document load failed.
- viewerErrorScreen.style.visibility = 'visible';
- sizer.style.display = 'none';
- viewerToolbar.style.visibility = 'hidden';
- if (viewerPasswordScreen.active) {
- viewerPasswordScreen.deny();
- viewerPasswordScreen.active = false;
- }
+ 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';
}
-}
-
-function onPasswordSubmitted(event) {
- plugin.postMessage({
- type: 'getPasswordComplete',
- password: event.detail.password
- });
-}
+ this.plugin_.setAttribute('headers', headers);
-// Called when a message is received from the plugin.
-function handleMessage(message) {
- switch (message.data.type.toString()) {
- case 'documentDimensions':
- documentDimensions = message.data;
- viewport.setDocumentDimensions(documentDimensions);
- viewerToolbar.style.visibility = 'visible';
- // If we received the document dimensions, the password was good so we can
- // dismiss the password screen.
- if (viewerPasswordScreen.active)
- viewerPasswordScreen.accept();
-
- viewerPageIndicator.initialFadeIn();
- viewerToolbar.initialFadeIn();
- break;
- case 'loadProgress':
- updateProgress(message.data.progress);
- break;
- case 'goToPage':
- viewport.goToPage(message.data.page);
- 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 (!viewerPasswordScreen.active)
- viewerPasswordScreen.active = true;
- else
- viewerPasswordScreen.deny();
- }
-}
+ if (window.top == window)
+ this.plugin_.setAttribute('full-frame', '');
+ document.body.appendChild(this.plugin_);
-// Callback that's called when the viewport changes.
-function viewportChangedCallback(zoom,
- x,
- y,
- scrollbarWidth,
- hasScrollbars,
- page) {
- // Offset the toolbar position so that it doesn't move if scrollbars appear.
- var toolbarRight = hasScrollbars.y ? 0 : scrollbarWidth;
- var toolbarBottom = hasScrollbars.x ? 0 : scrollbarWidth;
- viewerToolbar.style.right = toolbarRight + 'px';
- viewerToolbar.style.bottom = toolbarBottom + 'px';
-
- // Show or hide the page indicator.
- if (documentDimensions.pageDimensions.length > 1 && hasScrollbars.y)
- viewerPageIndicator.style.visibility = 'visible';
- else
- viewerPageIndicator.style.visibility = 'hidden';
-
- // Update the most visible page.
- viewerPageIndicator.text = page + 1;
-
- // Notify the plugin of the viewport change.
- plugin.postMessage({
+ // TODO(raymes): Remove this spurious message once crbug.com/388606 is fixed.
+ // This is a hack to initialize pepper sync scripting and avoid re-entrancy.
+ this.plugin_.postMessage({
type: 'viewport',
- zoom: zoom,
- xOffset: x,
- yOffset: y
+ zoom: 1,
+ xOffset: 0,
+ yOffset: 0
});
-}
-
-function load() {
- sizer = $('sizer');
- viewerToolbar = $('toolbar');
- viewerPageIndicator = $('page-indicator');
- viewerProgressBar = $('progress-bar');
- viewerPasswordScreen = $('password-screen');
- viewerPasswordScreen.addEventListener('password-submitted',
- onPasswordSubmitted);
- viewerErrorScreen = $('error-screen');
- viewerErrorScreen.text = 'Failed to load PDF document';
-
- // Create the viewport.
- viewport = new Viewport(window,
- sizer,
- isFitToPageEnabled,
- viewportChangedCallback);
-
- // Create the plugin object dynamically so we can set its src.
- plugin = document.createElement('object');
- plugin.id = 'plugin';
- plugin.type = 'application/x-google-chrome-pdf';
- plugin.addEventListener('message', handleMessage, false);
- // The pdf location is passed in stream details in the background page.
- var streamDetails = chrome.extension.getBackgroundPage().popStreamDetails();
- plugin.setAttribute('src', streamDetails.streamUrl);
- document.body.appendChild(plugin);
// Setup the button event listeners.
$('fit-to-width-button').addEventListener('click',
- viewport.fitToWidth.bind(viewport));
+ this.viewport_.fitToWidth.bind(this.viewport_));
$('fit-to-page-button').addEventListener('click',
- viewport.fitToPage.bind(viewport));
+ this.viewport_.fitToPage.bind(this.viewport_));
$('zoom-in-button').addEventListener('click',
- viewport.zoomIn.bind(viewport));
+ this.viewport_.zoomIn.bind(this.viewport_));
$('zoom-out-button').addEventListener('click',
- viewport.zoomOut.bind(viewport));
- $('save-button-link').href = streamDetails.originalUrl;
- $('print-button').addEventListener('click', print);
+ 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);
+
+ // Set up the zoom API.
+ if (chrome.tabs) {
+ chrome.tabs.setZoomSettings({mode: 'manual', scope: 'per-tab'},
+ this.afterZoom_.bind(this));
+ chrome.tabs.onZoomChange.addListener(function(zoomChangeInfo) {
+ // If the zoom level is close enough to the current zoom level, don't
+ // change it. This avoids us getting into an infinite loop of zoom changes
+ // due to floating point error.
+ var MIN_ZOOM_DELTA = 0.01;
+ var zoomDelta = Math.abs(this.viewport_.zoom -
+ zoomChangeInfo.newZoomFactor);
+ // We should not change zoom level when we are responsible for initiating
+ // the zoom. onZoomChange() is called before setZoomComplete() callback
+ // when we initiate the zoom.
+ if ((zoomDelta > MIN_ZOOM_DELTA) && !this.setZoomInProgress_)
+ this.viewport_.setZoom(zoomChangeInfo.newZoomFactor);
+ }.bind(this));
+ }
+
+ // Parse open pdf parameters.
+ var paramsParser = new OpenPDFParamsParser(this.streamDetails.originalUrl);
+ this.urlParams_ = paramsParser.urlParams;
+}
+
+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';
+ var pageUpHandler = function() {
+ // 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;
+ }
+ }.bind(this);
+ var pageDownHandler = function() {
+ // 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;
+ }
+ }.bind(this);
- // Setup keyboard event listeners.
- document.onkeydown = function(e) {
switch (e.keyCode) {
+ case 32: // Space key.
+ if (e.shiftKey)
+ pageUpHandler();
+ else
+ pageDownHandler();
+ return;
+ case 33: // Page up key.
+ pageUpHandler();
+ return;
+ case 34: // Page down key.
+ pageDownHandler();
+ return;
case 37: // Left arrow key.
// Go to the previous page if there are no horizontal scrollbars.
- if (!viewport.documentHasScrollbars().x) {
- viewport.goToPage(viewport.getMostVisiblePage() - 1);
+ 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 33: // Page up key.
- // Go to the previous page if we are fit-to-page.
- if (isFitToPageEnabled()) {
- viewport.goToPage(viewport.getMostVisiblePage() - 1);
- // Since we do the movement of the page.
- e.preventDefault();
+ 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 (!viewport.documentHasScrollbars().x) {
- viewport.goToPage(viewport.getMostVisiblePage() + 1);
- // Since we do the movement of the page.
- e.preventDefault();
- }
- return;
- case 34: // Page down key.
- // Go to the next page if we are fit-to-page.
- if (isFitToPageEnabled()) {
- viewport.goToPage(viewport.getMostVisiblePage() + 1);
+ 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 187: // +/= key.
- case 107: // Numpad + key.
- if (e.ctrlKey || e.metaKey) {
- 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) {
- viewport.zoomOut();
- // Since we do the zooming of the page.
- e.preventDefault();
+ case 40: // Down arrow key.
+ if (fromScriptingAPI) {
+ position.y += Viewport.SCROLL_INCREMENT;
+ this.viewport.position = position;
}
return;
case 83: // s key.
return;
case 80: // p key.
if (e.ctrlKey || e.metaKey) {
- print();
+ this.print_();
// Since we do the printing of the page.
e.preventDefault();
}
return;
+ case 219: // left bracket.
+ if (e.ctrlKey) {
+ this.plugin_.postMessage({
+ type: 'rotateCounterclockwise',
+ });
+ }
+ return;
+ case 221: // right bracket.
+ if (e.ctrlKey) {
+ this.plugin_.postMessage({
+ type: 'rotateClockwise',
+ });
+ }
+ return;
}
- };
-}
+ },
+
+ /**
+ * @private
+ * Notify the plugin to print.
+ */
+ print_: function() {
+ this.plugin_.postMessage({
+ type: 'print',
+ });
+ },
+
+ /**
+ * @private
+ * Handle open pdf parameters. This function updates the viewport as per
+ * the parameters mentioned in the url while opening pdf. The order is
+ * important as later actions can override the effects of previous actions.
+ */
+ handleURLParams_: function() {
+ if (this.urlParams_.page)
+ this.viewport_.goToPage(this.urlParams_.page);
+ if (this.urlParams_.position) {
+ // Make sure we don't cancel effect of page parameter.
+ this.viewport_.position = {
+ x: this.viewport_.position.x + this.urlParams_.position.x,
+ y: this.viewport_.position.y + this.urlParams_.position.y
+ };
+ }
+ if (this.urlParams_.zoom)
+ this.viewport_.setZoom(this.urlParams_.zoom);
+ },
+
+ /**
+ * @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.
+ if (this.lastViewportPosition_)
+ this.viewport_.position = this.lastViewportPosition_;
+ this.handleURLParams_();
+ this.loaded = true;
+ var loadEvent = new Event('pdfload');
+ window.dispatchEvent(loadEvent);
+ this.sendScriptingMessage_({
+ type: 'documentLoaded'
+ });
+ }
+ },
+
+ /**
+ * @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;
+ }
+ },
-load();
+ /**
+ * @private
+ * A callback that's called before the zoom changes. Notify the plugin to stop
+ * reacting to scroll events while zoom is taking place to avoid flickering.
+ */
+ beforeZoom_: function() {
+ this.plugin_.postMessage({
+ type: 'stopScrolling'
+ });
+ },
-})();
+ /**
+ * @private
+ * A callback that's called after the zoom changes. Notify the plugin of the
+ * zoom change and to continue reacting to scroll events.
+ */
+ afterZoom_: function() {
+ var position = this.viewport_.position;
+ var zoom = this.viewport_.zoom;
+ if (chrome.tabs && !this.setZoomInProgress_) {
+ this.setZoomInProgress_ = true;
+ chrome.tabs.setZoom(zoom, this.setZoomComplete_.bind(this, zoom));
+ }
+ this.plugin_.postMessage({
+ type: 'viewport',
+ zoom: zoom,
+ xOffset: position.x,
+ yOffset: position.y
+ });
+ },
+
+ /**
+ * @private
+ * A callback that's called after chrome.tabs.setZoom is complete. This will
+ * call chrome.tabs.setZoom again if the zoom level has changed since it was
+ * last called.
+ * @param {number} lastZoom the zoom level that chrome.tabs.setZoom was called
+ * with.
+ */
+ setZoomComplete_: function(lastZoom) {
+ var zoom = this.viewport_.zoom;
+ if (zoom != lastZoom)
+ chrome.tabs.setZoom(zoom, this.setZoomComplete_.bind(this, zoom));
+ else
+ this.setZoomInProgress_ = false;
+ },
+
+ /**
+ * @private
+ * A callback that's called after the viewport changes.
+ */
+ viewportChanged_: 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 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_;
+ }
+};