blob: 3c5bf9e72aeba85eb3fb0eb4af4e4a92d114e0d3 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
/**
* @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;
}
/**
* Return the filename component of a URL, percent decoded if possible.
* @param {string} url The URL to get the filename from.
* @return {string} The filename component.
*/
function getFilenameFromURL(url) {
// Ignore the query and fragment.
var mainUrl = url.split(/#|\?/)[0];
var components = mainUrl.split(/\/|\\/);
var filename = components[components.length - 1];
try {
return decodeURIComponent(filename);
} catch (e) {
if (e instanceof URIError)
return filename;
throw e;
}
}
/**
* Called when navigation happens in the current tab.
* @param {boolean} isInTab Indicates if the PDF viewer is displayed in a tab.
* @param {boolean} isSourceFileUrl Indicates if the navigation source is a
* file:// URL.
* @param {string} url The url to be opened in the current tab.
*/
function onNavigateInCurrentTab(isInTab, isSourceFileUrl, url) {
// When the PDFviewer is inside a browser tab, prefer the tabs API because
// it can navigate from one file:// URL to another.
if (chrome.tabs && isInTab && isSourceFileUrl)
chrome.tabs.update({url: url});
else
window.location.href = url;
}
/**
* Called when navigation happens in the new tab.
* @param {string} url The url to be opened in the new tab.
*/
function onNavigateInNewTab(url) {
// Prefer the tabs API because it guarantees we can just open a new tab.
// window.open doesn't have this guarantee.
if (chrome.tabs)
chrome.tabs.create({url: url});
else
window.open(url);
}
/**
* Whether keydown events should currently be ignored. Events are ignored when
* an editable element has focus, to allow for proper editing controls.
* @param {HTMLElement} activeElement The currently selected DOM node.
* @return {boolean} True if keydown events should be ignored.
*/
function shouldIgnoreKeyEvents(activeElement) {
while (activeElement.shadowRoot != null &&
activeElement.shadowRoot.activeElement != null) {
activeElement = activeElement.shadowRoot.activeElement;
}
return (activeElement.isContentEditable ||
activeElement.tagName == 'INPUT' ||
activeElement.tagName == 'TEXTAREA');
}
/**
* 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 height of the toolbar along the top of the page. The document will be
* shifted down by this much in the viewport.
*/
PDFViewer.MATERIAL_TOOLBAR_HEIGHT = 56;
/**
* Minimum height for the material toolbar to show (px). Should match the media
* query in index-material.css. If the window is smaller than this at load,
* leave no space for the toolbar.
*/
PDFViewer.TOOLBAR_WINDOW_MIN_HEIGHT = 250;
/**
* The light-gray background color used for print preview.
*/
PDFViewer.LIGHT_BACKGROUND_COLOR = '0xFFCCCCCC';
/**
* The dark-gray background color used for the regular viewer.
*/
PDFViewer.DARK_BACKGROUND_COLOR = '0xFF525659';
/**
* Creates a new PDFViewer. There should only be one of these objects per
* document.
* @constructor
* @param {!BrowserApi} browserApi An object providing an API to the browser.
*/
function PDFViewer(browserApi) {
this.browserApi_ = browserApi;
this.loadState_ = LoadState.LOADING;
this.parentWindow_ = null;
this.parentOrigin_ = null;
this.isFormFieldFocused_ = false;
this.delayedScriptingMessages_ = [];
this.isPrintPreview_ = this.browserApi_.getStreamInfo().originalUrl.indexOf(
'chrome://print') == 0;
// 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');
if (this.isPrintPreview_)
this.pageIndicator_ = $('page-indicator');
this.passwordScreen_ = $('password-screen');
this.passwordScreen_.addEventListener('password-submitted',
this.onPasswordSubmitted_.bind(this));
this.errorScreen_ = $('error-screen');
// Can only reload if we are in a normal tab.
if (chrome.tabs && this.browserApi_.getStreamInfo().tabId != -1) {
this.errorScreen_.reloadFn = function() {
chrome.tabs.reload(this.browserApi_.getStreamInfo().tabId);
}.bind(this);
}
// Create the viewport.
var shortWindow = window.innerHeight < PDFViewer.TOOLBAR_WINDOW_MIN_HEIGHT;
var topToolbarHeight = (!this.isPrintPreview_ && !shortWindow) ?
PDFViewer.MATERIAL_TOOLBAR_HEIGHT :
0;
this.viewport_ = new Viewport(window,
this.sizer_,
this.viewportChanged_.bind(this),
this.beforeZoom_.bind(this),
this.afterZoom_.bind(this),
getScrollbarWidth(),
this.browserApi_.getDefaultZoom(),
topToolbarHeight);
// 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('embed');
// 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.plugin_.setAttribute('src',
this.browserApi_.getStreamInfo().originalUrl);
this.plugin_.setAttribute('stream-url',
this.browserApi_.getStreamInfo().streamUrl);
var headers = '';
for (var header in this.browserApi_.getStreamInfo().responseHeaders) {
headers += header + ': ' +
this.browserApi_.getStreamInfo().responseHeaders[header] + '\n';
}
this.plugin_.setAttribute('headers', headers);
var backgroundColor = PDFViewer.DARK_BACKGROUND_COLOR;
this.plugin_.setAttribute('background-color', backgroundColor);
this.plugin_.setAttribute('top-toolbar-height', topToolbarHeight);
if (!this.browserApi_.getStreamInfo().embedded)
this.plugin_.setAttribute('full-frame', '');
document.body.appendChild(this.plugin_);
// Setup the button event listeners.
this.zoomToolbar_ = $('zoom-toolbar');
this.zoomToolbar_.addEventListener('fit-to-width',
this.viewport_.fitToWidth.bind(this.viewport_));
this.zoomToolbar_.addEventListener('fit-to-page',
this.fitToPage_.bind(this));
this.zoomToolbar_.addEventListener('zoom-in',
this.viewport_.zoomIn.bind(this.viewport_));
this.zoomToolbar_.addEventListener('zoom-out',
this.viewport_.zoomOut.bind(this.viewport_));
if (!this.isPrintPreview_) {
this.toolbar_ = $('toolbar');
this.toolbar_.hidden = false;
this.toolbar_.addEventListener('save', this.save_.bind(this));
this.toolbar_.addEventListener('print', this.print_.bind(this));
this.toolbar_.addEventListener('rotate-right',
this.rotateClockwise_.bind(this));
// Must attach to mouseup on the plugin element, since it eats mousedown
// and click events.
this.plugin_.addEventListener('mouseup',
this.toolbar_.hideDropdowns.bind(this.toolbar_));
this.toolbar_.docTitle =
getFilenameFromURL(this.browserApi_.getStreamInfo().originalUrl);
}
document.body.addEventListener('change-page', function(e) {
this.viewport_.goToPage(e.detail.page);
}.bind(this));
this.toolbarManager_ =
new ToolbarManager(window, this.toolbar_, this.zoomToolbar_);
// Set up the ZoomManager.
this.zoomManager_ = new ZoomManager(
this.viewport_, this.browserApi_.setZoom.bind(this.browserApi_),
this.browserApi_.getInitialZoom());
this.browserApi_.addZoomEventListener(
this.zoomManager_.onBrowserZoomChange.bind(this.zoomManager_));
// Setup the keyboard event listener.
document.addEventListener('keydown', this.handleKeyEvent_.bind(this));
document.addEventListener('mousemove', this.handleMouseEvent_.bind(this));
document.addEventListener('mouseout', this.handleMouseEvent_.bind(this));
// Parse open pdf parameters.
this.paramsParser_ =
new OpenPDFParamsParser(this.getNamedDestination_.bind(this));
var isInTab = this.browserApi_.getStreamInfo().tabId != -1;
var isSourceFileUrl =
this.browserApi_.getStreamInfo().originalUrl.indexOf('file://') == 0;
this.navigator_ = new Navigator(this.browserApi_.getStreamInfo().originalUrl,
this.viewport_, this.paramsParser_,
onNavigateInCurrentTab.bind(undefined,
isInTab,
isSourceFileUrl),
onNavigateInNewTab);
this.viewportScroller_ =
new ViewportScroller(this.viewport_, this.plugin_, window);
// Request translated strings.
chrome.resourcesPrivate.getStrings('pdf', this.handleStrings_.bind(this));
}
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.fromScriptingAPI;
if (shouldIgnoreKeyEvents(document.activeElement) || e.defaultPrevented)
return;
this.toolbarManager_.hideToolbarsAfterTimeout(e);
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);
switch (e.keyCode) {
case 9: // Tab key.
this.toolbarManager_.showToolbarsForKeyboardNavigation();
return;
case 27: // Escape key.
if (!this.isPrintPreview_) {
this.toolbarManager_.hideSingleToolbarLayer();
return;
}
break; // Ensure escape falls through to the print-preview handler.
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.
if (!(e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)) {
// Go to the previous page if there are no horizontal scrollbars and
// no form field is focused.
if (!(this.viewport_.documentHasScrollbars().horizontal ||
this.isFormFieldFocused_)) {
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.
if (!(e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)) {
// Go to the next page if there are no horizontal scrollbars and no
// form field is focused.
if (!(this.viewport_.documentHasScrollbars().horizontal ||
this.isFormFieldFocused_)) {
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 65: // a key.
if (e.ctrlKey || e.metaKey) {
this.plugin_.postMessage({
type: 'selectAll'
});
// Since we do selection ourselves.
e.preventDefault();
}
return;
case 71: // g key.
if (this.toolbar_ && (e.ctrlKey || e.metaKey) && e.altKey) {
this.toolbarManager_.showToolbars();
this.toolbar_.selectPageNumber();
}
return;
case 219: // left bracket.
if (e.ctrlKey)
this.rotateCounterClockwise_();
return;
case 221: // right bracket.
if (e.ctrlKey)
this.rotateClockwise_();
return;
}
// Give print preview a chance to handle the key event.
if (!fromScriptingAPI && this.isPrintPreview_) {
this.sendScriptingMessage_({
type: 'sendKeyEvent',
keyEvent: SerializeKeyEvent(e)
});
} else {
// Show toolbars as a fallback.
if (!(e.shiftKey || e.ctrlKey || e.altKey))
this.toolbarManager_.showToolbars();
}
},
handleMouseEvent_: function(e) {
if (e.type == 'mousemove')
this.toolbarManager_.handleMouseMove(e);
else if (e.type == 'mouseout')
this.toolbarManager_.hideToolbarsForMouseOut();
},
/**
* @private
* Rotate the plugin clockwise.
*/
rotateClockwise_: function() {
this.plugin_.postMessage({
type: 'rotateClockwise'
});
},
/**
* @private
* Rotate the plugin counter-clockwise.
*/
rotateCounterClockwise_: function() {
this.plugin_.postMessage({
type: 'rotateCounterclockwise'
});
},
fitToPage_: function() {
this.viewport_.fitToPage();
this.toolbarManager_.forceHideTopToolbar();
},
/**
* @private
* Notify the plugin to print.
*/
print_: function() {
this.plugin_.postMessage({
type: 'print'
});
},
/**
* @private
* Notify the plugin to save.
*/
save_: function() {
this.plugin_.postMessage({
type: 'save'
});
},
/**
* Fetches the page number corresponding to the given named destination from
* the plugin.
* @param {string} name The namedDestination to fetch page number from plugin.
*/
getNamedDestination_: function(name) {
this.plugin_.postMessage({
type: 'getNamedDestination',
namedDestination: name
});
},
/**
* @private
* Sends a 'documentLoaded' message to the PDFScriptingAPI if the document has
* finished loading.
*/
sendDocumentLoadedMessage_: function() {
if (this.loadState_ == LoadState.LOADING)
return;
this.sendScriptingMessage_({
type: 'documentLoaded',
load_state: this.loadState_
});
},
/**
* @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.
* @param {Object} viewportPosition The initial position of the viewport to be
* displayed.
*/
handleURLParams_: function(viewportPosition) {
if (viewportPosition.page != undefined)
this.viewport_.goToPage(viewportPosition.page);
if (viewportPosition.position) {
// Make sure we don't cancel effect of page parameter.
this.viewport_.position = {
x: this.viewport_.position.x + viewportPosition.position.x,
y: this.viewport_.position.y + viewportPosition.position.y
};
}
if (viewportPosition.zoom)
this.viewport_.setZoom(viewportPosition.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) {
if (this.toolbar_)
this.toolbar_.loadProgress = progress;
if (progress == -1) {
// Document load failed.
this.errorScreen_.show();
this.sizer_.style.display = 'none';
if (this.passwordScreen_.active) {
this.passwordScreen_.deny();
this.passwordScreen_.active = false;
}
this.loadState_ = LoadState.FAILED;
this.sendDocumentLoadedMessage_();
} else if (progress == 100) {
// Document load complete.
if (this.lastViewportPosition_)
this.viewport_.position = this.lastViewportPosition_;
this.paramsParser_.getViewportFromUrlParams(
this.browserApi_.getStreamInfo().originalUrl,
this.handleURLParams_.bind(this));
this.loadState_ = LoadState.SUCCESS;
this.sendDocumentLoadedMessage_();
while (this.delayedScriptingMessages_.length > 0)
this.handleScriptingMessage(this.delayedScriptingMessages_.shift());
this.toolbarManager_.hideToolbarsAfterTimeout();
}
},
/**
* @private
* Load a dictionary of translated strings into the UI. Used as a callback for
* chrome.resourcesPrivate.
* @param {Object} strings Dictionary of translated strings
*/
handleStrings_: function(strings) {
window.loadTimeData.data = strings;
i18nTemplate.process(document, loadTimeData);
this.zoomToolbar_.updateTooltips();
},
/**
* @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_);
// If we received the document dimensions, the password was good so we
// can dismiss the password screen.
if (this.passwordScreen_.active)
this.passwordScreen_.accept();
if (this.pageIndicator_)
this.pageIndicator_.initialFadeIn();
if (this.toolbar_) {
this.toolbar_.docLength =
this.documentDimensions_.pageDimensions.length;
}
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;
window.location.href = href;
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 'getSelectedTextReply':
this.sendScriptingMessage_(message.data);
break;
case 'goToPage':
this.viewport_.goToPage(message.data.page);
break;
case 'loadProgress':
this.updateProgress_(message.data.progress);
break;
case 'navigate':
// If in print preview, always open a new tab.
if (this.isPrintPreview_)
this.navigator_.navigate(message.data.url, true);
else
this.navigator_.navigate(message.data.url, message.data.newTab);
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 'cancelStreamUrl':
chrome.mimeHandlerPrivate.abortStream();
break;
case 'metadata':
if (message.data.title) {
document.title = message.data.title;
} else {
document.title =
getFilenameFromURL(this.browserApi_.getStreamInfo().originalUrl);
}
this.bookmarks_ = message.data.bookmarks;
if (this.toolbar_) {
this.toolbar_.docTitle = document.title;
this.toolbar_.bookmarks = this.bookmarks;
}
break;
case 'setIsSelecting':
this.viewportScroller_.setEnableScrolling(message.data.isSelecting);
break;
case 'getNamedDestinationReply':
this.paramsParser_.onNamedDestinationReceived(
message.data.pageNumber);
break;
case 'formFocusChange':
this.isFormFieldFocused_ = message.data.focused;
break;
}
},
/**
* @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;
this.plugin_.postMessage({
type: 'viewport',
zoom: zoom,
xOffset: position.x,
yOffset: position.y
});
this.zoomManager_.onPdfZoomChange();
},
/**
* @private
* A callback that's called after the viewport changes.
*/
viewportChanged_: function() {
if (!this.documentDimensions_)
return;
// Offset the toolbar position so that it doesn't move if scrollbars appear.
var hasScrollbars = this.viewport_.documentHasScrollbars();
var scrollbarWidth = this.viewport_.scrollbarWidth;
var verticalScrollbarWidth = hasScrollbars.vertical ? scrollbarWidth : 0;
var horizontalScrollbarWidth =
hasScrollbars.horizontal ? scrollbarWidth : 0;
// Shift the zoom toolbar to the left by half a scrollbar width. This
// gives a compromise: if there is no scrollbar visible then the toolbar
// will be half a scrollbar width further left than the spec but if there
// is a scrollbar visible it will be half a scrollbar width further right
// than the spec. In RTL layout, the zoom toolbar is on the left side, but
// the scrollbar is still on the right, so this is not necessary.
if (!isRTL()) {
this.zoomToolbar_.style.right = -verticalScrollbarWidth +
(scrollbarWidth / 2) + 'px';
}
// Having a horizontal scrollbar is much rarer so we don't offset the
// toolbar from the bottom any more than what the spec says. This means
// that when there is a scrollbar visible, it will be a full scrollbar
// width closer to the bottom of the screen than usual, but this is ok.
this.zoomToolbar_.style.bottom = -horizontalScrollbarWidth + 'px';
// Update the page indicator.
var visiblePage = this.viewport_.getMostVisiblePage();
if (this.toolbar_)
this.toolbar_.pageNo = visiblePage + 1;
// TODO(raymes): Give pageIndicator_ the same API as toolbar_.
if (this.pageIndicator_) {
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
});
},
/**
* 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) {
if (this.parentWindow_ != message.source) {
this.parentWindow_ = message.source;
this.parentOrigin_ = message.origin;
// Ensure that we notify the embedder if the document is loaded.
if (this.loadState_ != LoadState.LOADING)
this.sendDocumentLoadedMessage_();
}
if (this.handlePrintPreviewScriptingMessage_(message))
return;
// Delay scripting messages from users of the scripting API until the
// document is loaded. This simplifies use of the APIs.
if (this.loadState_ != LoadState.SUCCESS) {
this.delayedScriptingMessages_.push(message);
return;
}
switch (message.data.type.toString()) {
case 'getAccessibilityJSON':
case 'getSelectedText':
case 'print':
case 'selectAll':
this.plugin_.postMessage(message.data);
break;
}
},
/**
* @private
* Handle scripting messages specific to print preview.
* @param {MessageObject} message the message to handle.
* @return {boolean} true if the message was handled, false otherwise.
*/
handlePrintPreviewScriptingMessage_: function(message) {
if (!this.isPrintPreview_)
return false;
switch (message.data.type.toString()) {
case 'loadPreviewPage':
this.plugin_.postMessage(message.data);
return true;
case 'resetPrintPreviewMode':
this.loadState_ = LoadState.LOADING;
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)
});
return true;
case 'sendKeyEvent':
this.handleKeyEvent_(DeserializeKeyEvent(message.data.keyEvent));
return true;
}
return false;
},
/**
* @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) {
if (this.parentWindow_ && this.parentOrigin_) {
var targetOrigin;
// Only send data back to the embedder if it is from the same origin,
// unless we're sending it to ourselves (which could happen in the case
// of tests). We also allow documentLoaded messages through as this won't
// leak important information.
if (this.parentOrigin_ == window.location.origin)
targetOrigin = this.parentOrigin_;
else if (message.type == 'documentLoaded')
targetOrigin = '*';
else
targetOrigin = this.browserApi_.getStreamInfo().originalUrl;
this.parentWindow_.postMessage(message, targetOrigin);
}
},
/**
* @type {Viewport} the viewport of the PDF viewer.
*/
get viewport() {
return this.viewport_;
},
/**
* Each bookmark is an Object containing a:
* - title
* - page (optional)
* - array of children (themselves bookmarks)
* @type {Array} the top-level bookmarks of the PDF.
*/
get bookmarks() {
return this.bookmarks_;
}
};