summaryrefslogtreecommitdiffstats
path: root/chromium/chrome/browser/resources/pdf/pdf_viewer.js
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/chrome/browser/resources/pdf/pdf_viewer.js')
-rw-r--r--chromium/chrome/browser/resources/pdf/pdf_viewer.js1482
1 files changed, 656 insertions, 826 deletions
diff --git a/chromium/chrome/browser/resources/pdf/pdf_viewer.js b/chromium/chrome/browser/resources/pdf/pdf_viewer.js
index 375663f029c..d85d80a2101 100644
--- a/chromium/chrome/browser/resources/pdf/pdf_viewer.js
+++ b/chromium/chrome/browser/resources/pdf/pdf_viewer.js
@@ -6,16 +6,64 @@
/**
* @typedef {{
- * dataToSave: Array,
- * token: string,
- * fileName: string
+ * source: Object,
+ * origin: string,
+ * data: !MessageData,
* }}
*/
-let SaveDataMessageData;
+let MessageObject;
/**
- * @return {number} Width of a scrollbar in pixels
+ * @typedef {{
+ * type: string,
+ * height: number,
+ * width: number,
+ * layoutOptions: (!LayoutOptions|undefined),
+ * pageDimensions: Array
+ * }}
+ */
+let DocumentDimensionsMessageData;
+
+/**
+ * @typedef {{
+ * type: string,
+ * url: string,
+ * disposition: !PdfNavigator.WindowOpenDisposition,
+ * }}
+ */
+let NavigateMessageData;
+
+/**
+ * @typedef {{
+ * type: string,
+ * page: number,
+ * x: number,
+ * y: number,
+ * zoom: number
+ * }}
+ */
+let DestinationMessageData;
+
+/**
+ * @typedef {{
+ * type: string,
+ * title: string,
+ * bookmarks: !Array<!Bookmark>,
+ * canSerializeDocument: boolean,
+ * }}
*/
+let MetadataMessageData;
+
+/**
+ * @typedef {{
+ * hasUnsavedChanges: (boolean|undefined),
+ * fileName: string,
+ * dataToSave: !ArrayBuffer
+ * }}
+ */
+let RequiredSaveResult;
+
+/** @return {number} Width of a scrollbar in pixels */
function getScrollbarWidth() {
const div = document.createElement('div');
div.style.visibility = 'hidden';
@@ -31,7 +79,6 @@ function getScrollbarWidth() {
/**
* 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.
*/
@@ -53,8 +100,7 @@ function getFilenameFromURL(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.
+ * @param {Element} activeElement The currently selected DOM node.
* @return {boolean} True if keydown events should be ignored.
*/
function shouldIgnoreKeyEvents(activeElement) {
@@ -70,285 +116,341 @@ function shouldIgnoreKeyEvents(activeElement) {
}
/**
- * Creates a cryptographically secure pseudorandom 128-bit token.
- *
- * @return {string} The generated token as a hex string.
+ * Creates a new PDFViewer. There should only be one of these objects per
+ * document.
*/
-function createToken() {
- const randomBytes = new Uint8Array(16);
- return window.crypto.getRandomValues(randomBytes)
- .map(b => b.toString(16).padStart(2, '0'))
- .join('');
-}
+class PDFViewer {
+ /**
+ * @param {!BrowserApi} browserApi An object providing an API to the browser.
+ */
+ constructor(browserApi) {
+ /** @private {!BrowserApi} */
+ this.browserApi_ = browserApi;
-/**
- * The minimum number of pixels to offset the toolbar by from the bottom and
- * right side of the screen.
- */
-PDFViewer.MIN_TOOLBAR_OFFSET = 15;
+ /** @private {string} */
+ this.originalUrl_ = this.browserApi_.getStreamInfo().originalUrl;
-/**
- * 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;
+ /** @private {string} */
+ this.javascript_ = this.browserApi_.getStreamInfo().javascript || 'block';
-/**
- * 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;
+ /** @private {!LoadState} */
+ this.loadState_ = LoadState.LOADING;
-/**
- * The background color used for print preview (--google-grey-refresh-300).
- */
-PDFViewer.PRINT_PREVIEW_BACKGROUND_COLOR = '0xFFDADCE0';
+ /** @private {?Object} */
+ this.parentWindow_ = null;
-/**
- * The background color used for print preview when dark mode is enabled
- * (--google-grey-refresh-700).
- */
-PDFViewer.PRINT_PREVIEW_DARK_BACKGROUND_COLOR = '0xFF5F6368';
+ /** @private {?string} */
+ this.parentOrigin_ = null;
-/**
- * The background color used for the regular viewer.
- */
-PDFViewer.BACKGROUND_COLOR = '0xFF525659';
+ /** @private {boolean} */
+ this.isFormFieldFocused_ = false;
-/**
- * Creates a new PDFViewer. There should only be one of these objects per
- * document.
- *
- * @param {!BrowserApi} browserApi An object providing an API to the browser.
- * @constructor
- */
-function PDFViewer(browserApi) {
- this.browserApi_ = browserApi;
- this.originalUrl_ = this.browserApi_.getStreamInfo().originalUrl;
- this.javascript_ = this.browserApi_.getStreamInfo().javascript || 'block';
- this.loadState_ = LoadState.LOADING;
- this.parentWindow_ = null;
- this.parentOrigin_ = null;
- this.isFormFieldFocused_ = false;
- this.beepCount_ = 0;
- this.delayedScriptingMessages_ = [];
- this.loaded_ = new PromiseResolver();
-
- this.isPrintPreview_ = location.origin === 'chrome://print';
- this.isPrintPreviewLoadingFinished_ = false;
- this.isUserInitiatedEvent_ = true;
-
- /** @private {boolean} */
- this.hasEnteredAnnotationMode_ = false;
-
- /** @private {boolean} */
- this.hadPassword_ = false;
-
- /** @private {boolean} */
- this.canSerializeDocument_ = false;
-
- PDFMetrics.record(PDFMetrics.UserAction.DOCUMENT_OPENED);
-
- // Parse open pdf parameters.
- this.paramsParser_ = new OpenPdfParamsParser(
- message => this.pluginController_.postMessage(message));
- const toolbarEnabled =
- this.paramsParser_.getUiUrlParams(this.originalUrl_).toolbar &&
- !this.isPrintPreview_;
-
- // 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', e => this.onPasswordSubmitted_(e));
- 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 = () => {
- chrome.tabs.reload(this.browserApi_.getStreamInfo().tabId);
- };
- }
+ /** @private {number} */
+ this.beepCount_ = 0;
- // Create the viewport.
- const shortWindow = window.innerHeight < PDFViewer.TOOLBAR_WINDOW_MIN_HEIGHT;
- const topToolbarHeight =
- (toolbarEnabled) ? PDFViewer.MATERIAL_TOOLBAR_HEIGHT : 0;
- const defaultZoom =
- this.browserApi_.getZoomBehavior() == BrowserApi.ZoomBehavior.MANAGE ?
- this.browserApi_.getDefaultZoom() :
- 1.0;
- this.viewport_ = new Viewport(
- window, this.sizer_, getScrollbarWidth(), defaultZoom, topToolbarHeight);
- this.viewport_.setViewportChangedCallback(() => this.viewportChanged_());
- this.viewport_.setBeforeZoomCallback(
- () => this.currentController_.beforeZoom());
- this.viewport_.setAfterZoomCallback(
- () => this.currentController_.afterZoom());
- this.viewport_.setUserInitiatedCallback(
- userInitiated => this.setUserInitiated_(userInitiated));
- window.addEventListener('beforeunload', () => this.viewport_.resetTracker());
-
- // 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_render_frame_helper.cc actually
- // references it.
- this.plugin_.id = 'plugin';
- this.plugin_.type = 'application/x-google-chrome-pdf';
-
- // 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', message => this.handleScriptingMessage(message), false);
-
- this.plugin_.setAttribute('src', this.originalUrl_);
- this.plugin_.setAttribute(
- 'stream-url', this.browserApi_.getStreamInfo().streamUrl);
- let headers = '';
- for (const header in this.browserApi_.getStreamInfo().responseHeaders) {
- headers += header + ': ' +
- this.browserApi_.getStreamInfo().responseHeaders[header] + '\n';
- }
- this.plugin_.setAttribute('headers', headers);
+ /** @private {!Array} */
+ this.delayedScriptingMessages_ = [];
+
+ /** @private {!PromiseResolver} */
+ this.loaded_;
+
+ /** @private {boolean} */
+ this.initialLoadComplete_ = false;
+
+ /** @private {boolean} */
+ this.isPrintPreview_ = location.origin === 'chrome://print';
- this.plugin_.setAttribute('background-color', PDFViewer.BACKGROUND_COLOR);
- this.plugin_.setAttribute('top-toolbar-height', topToolbarHeight);
- this.plugin_.setAttribute('javascript', this.javascript_);
+ /** @private {boolean} */
+ this.isPrintPreviewLoadingFinished_ = false;
- if (this.browserApi_.getStreamInfo().embedded) {
+ /** @private {boolean} */
+ this.isUserInitiatedEvent_ = true;
+
+ /** @private {boolean} */
+ this.hasEnteredAnnotationMode_ = false;
+
+ /** @private {boolean} */
+ this.hadPassword_ = false;
+
+ /** @private {boolean} */
+ this.canSerializeDocument_ = false;
+
+ /** @private {!EventTracker} */
+ this.tracker_ = new EventTracker();
+
+ PDFMetrics.record(PDFMetrics.UserAction.DOCUMENT_OPENED);
+
+ // Parse open pdf parameters.
+ /** @private {!OpenPdfParamsParser} */
+ this.paramsParser_ = new OpenPdfParamsParser(
+ destination => this.pluginController_.getNamedDestination(destination));
+ const toolbarEnabled =
+ this.paramsParser_.getUiUrlParams(this.originalUrl_).toolbar &&
+ !this.isPrintPreview_;
+
+ // 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_ = /** @type {!HTMLDivElement} */ ($('sizer'));
+
+ /** @private {?ViewerPageIndicatorElement} */
+ this.pageIndicator_ = this.isPrintPreview_ ?
+ /** @type {!ViewerPageIndicatorElement} */ ($('page-indicator')) :
+ null;
+
+ /** @private {?ViewerPasswordScreenElement} */
+ this.passwordScreen_ =
+ /** @type {!ViewerPasswordScreenElement} */ ($('password-screen'));
+ this.passwordScreen_.addEventListener('password-submitted', e => {
+ this.onPasswordSubmitted_(
+ /** @type {!CustomEvent<{password: string}>} */ (e));
+ });
+
+ /** @private {?ViewerErrorScreenElement} */
+ this.errorScreen_ =
+ /** @type {!ViewerErrorScreenElement} */ ($('error-screen'));
+ // Can only reload if we are in a normal tab.
+ if (chrome.tabs && this.browserApi_.getStreamInfo().tabId != -1) {
+ this.errorScreen_.reloadFn = () => {
+ chrome.tabs.reload(this.browserApi_.getStreamInfo().tabId);
+ };
+ }
+
+ // Create the viewport.
+ const shortWindow =
+ window.innerHeight < PDFViewer.TOOLBAR_WINDOW_MIN_HEIGHT;
+ const topToolbarHeight =
+ (toolbarEnabled) ? PDFViewer.MATERIAL_TOOLBAR_HEIGHT : 0;
+ const defaultZoom =
+ this.browserApi_.getZoomBehavior() == BrowserApi.ZoomBehavior.MANAGE ?
+ this.browserApi_.getDefaultZoom() :
+ 1.0;
+
+ /** @private {!Viewport} */
+ this.viewport_ = new Viewport(
+ window, this.sizer_, getScrollbarWidth(), defaultZoom,
+ topToolbarHeight);
+ this.viewport_.setViewportChangedCallback(() => this.viewportChanged_());
+ this.viewport_.setBeforeZoomCallback(
+ () => this.currentController_.beforeZoom());
+ this.viewport_.setAfterZoomCallback(
+ () => this.currentController_.afterZoom());
+ this.viewport_.setUserInitiatedCallback(
+ userInitiated => this.setUserInitiated_(userInitiated));
+ window.addEventListener('beforeunload', () => this.resetTrackers_());
+
+ // 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.
+ /** @private {!HTMLEmbedElement} */
+ this.plugin_ =
+ /** @type {!HTMLEmbedElement} */ (document.createElement('embed'));
+
+ // NOTE: The plugin's 'id' field must be set to 'plugin' since
+ // chrome/renderer/printing/print_render_frame_helper.cc actually
+ // references it.
+ this.plugin_.id = 'plugin';
+ this.plugin_.type = 'application/x-google-chrome-pdf';
+
+ // 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', message => {
+ this.handleScriptingMessage(/** @type {!MessageObject} */ (message));
+ }, false);
+
+ this.plugin_.setAttribute('src', this.originalUrl_);
this.plugin_.setAttribute(
- 'top-level-url', this.browserApi_.getStreamInfo().tabUrl);
- } else {
- this.plugin_.setAttribute('full-frame', '');
- }
+ 'stream-url', this.browserApi_.getStreamInfo().streamUrl);
+ let headers = '';
+ for (const header in this.browserApi_.getStreamInfo().responseHeaders) {
+ headers += header + ': ' +
+ this.browserApi_.getStreamInfo().responseHeaders[header] + '\n';
+ }
+ this.plugin_.setAttribute('headers', headers);
- $('content').appendChild(this.plugin_);
-
- this.pluginController_ =
- new PluginController(this.plugin_, this, this.viewport_);
- this.inkController_ = new InkController(this, this.viewport_);
- this.currentController_ = this.pluginController_;
-
- // Setup the button event listeners.
- this.zoomToolbar_ = $('zoom-toolbar');
- this.zoomToolbar_.setIsPrintPreview(this.isPrintPreview_);
- this.zoomToolbar_.addEventListener(
- 'fit-to-changed', e => this.fitToChanged_(e));
- this.zoomToolbar_.addEventListener('zoom-in', () => this.viewport_.zoomIn());
- this.zoomToolbar_.addEventListener(
- 'zoom-out', () => this.viewport_.zoomOut());
-
- this.gestureDetector_ = new GestureDetector($('content'));
- this.gestureDetector_.addEventListener(
- 'pinchstart', e => this.onPinchStart_(e));
- this.sentPinchEvent_ = false;
- this.gestureDetector_.addEventListener(
- 'pinchupdate', e => this.onPinchUpdate_(e));
- this.gestureDetector_.addEventListener('pinchend', e => this.onPinchEnd_(e));
-
- if (toolbarEnabled) {
- this.toolbar_ = $('toolbar');
- this.toolbar_.hidden = false;
- this.toolbar_.addEventListener('save', () => this.save());
- this.toolbar_.addEventListener('print', () => this.print());
- this.toolbar_.addEventListener(
- 'undo', () => this.currentController_.undo());
- this.toolbar_.addEventListener(
- 'redo', () => this.currentController_.redo());
- this.toolbar_.addEventListener(
- 'rotate-right', () => this.rotateClockwise());
- this.toolbar_.addEventListener(
- 'annotation-mode-toggled', e => this.annotationModeToggled_(e));
- this.toolbar_.addEventListener(
- 'annotation-tool-changed',
- e => this.inkController_.setAnnotationTool(e.detail.value));
-
- this.toolbar_.docTitle = getFilenameFromURL(this.originalUrl_);
- }
+ this.plugin_.setAttribute('background-color', PDFViewer.BACKGROUND_COLOR);
+ this.plugin_.setAttribute('top-toolbar-height', topToolbarHeight);
+ this.plugin_.setAttribute('javascript', this.javascript_);
- document.body.addEventListener('change-page', e => {
- this.viewport_.goToPage(e.detail.page);
- if (e.detail.origin == 'bookmark') {
- PDFMetrics.record(PDFMetrics.UserAction.FOLLOW_BOOKMARK);
- } else if (e.detail.origin == 'pageselector') {
- PDFMetrics.record(PDFMetrics.UserAction.PAGE_SELECTOR_NAVIGATE);
+ if (this.browserApi_.getStreamInfo().embedded) {
+ this.plugin_.setAttribute(
+ 'top-level-url', this.browserApi_.getStreamInfo().tabUrl);
+ } else {
+ this.plugin_.setAttribute('full-frame', '');
}
- });
-
- document.body.addEventListener('change-page-and-xy', e => {
- const point = this.viewport_.convertPageToScreen(e.detail.page, e.detail);
- this.goToPageAndXY_(e.detail.origin, e.detail.page, point);
- });
-
- document.body.addEventListener('navigate', e => {
- const disposition = e.detail.newtab ?
- PdfNavigator.WindowOpenDisposition.NEW_BACKGROUND_TAB :
- PdfNavigator.WindowOpenDisposition.CURRENT_TAB;
- this.navigator_.navigate(e.detail.uri, disposition);
- });
-
- document.body.addEventListener('dropdown-opened', e => {
- if (e.detail == 'bookmarks') {
- PDFMetrics.record(PDFMetrics.UserAction.OPEN_BOOKMARKS_PANEL);
+
+ $('content').appendChild(this.plugin_);
+
+ /** @private {!PluginController} */
+ this.pluginController_ = new PluginController(
+ this.plugin_, this.viewport_, () => this.isUserInitiatedEvent_,
+ () => this.loaded);
+ this.tracker_.add(
+ this.pluginController_.getEventTarget(), 'plugin-message',
+ e => this.handlePluginMessage_(e));
+
+ /** @private {!InkController} */
+ this.inkController_ = new InkController(this.viewport_);
+ this.tracker_.add(
+ this.inkController_.getEventTarget(), 'stroke-added',
+ () => chrome.mimeHandlerPrivate.setShowBeforeUnloadDialog(true));
+ this.tracker_.add(
+ this.inkController_.getEventTarget(), 'set-annotation-undo-state',
+ e => this.setAnnotationUndoState_(e));
+
+ /** @private {!ContentController} */
+ this.currentController_ = this.pluginController_;
+
+ // Setup the button event listeners.
+ /** @private {!ViewerZoomToolbarElement} */
+ this.zoomToolbar_ =
+ /** @type {!ViewerZoomToolbarElement} */ ($('zoom-toolbar'));
+ this.zoomToolbar_.setIsPrintPreview(this.isPrintPreview_);
+ this.zoomToolbar_.addEventListener(
+ 'fit-to-changed',
+ e => this.fitToChanged_(
+ /** @type {!CustomEvent<FitToChangedEvent>}} */ (e)));
+ this.zoomToolbar_.addEventListener(
+ 'zoom-in', () => this.viewport_.zoomIn());
+ this.zoomToolbar_.addEventListener(
+ 'zoom-out', () => this.viewport_.zoomOut());
+
+ /** @private {!GestureDetector} */
+ this.gestureDetector_ = new GestureDetector(assert($('content')));
+ this.gestureDetector_.addEventListener(
+ 'pinchstart', e => this.onPinchStart_(e));
+ this.sentPinchEvent_ = false;
+ this.gestureDetector_.addEventListener(
+ 'pinchupdate', e => this.onPinchUpdate_(e));
+ this.gestureDetector_.addEventListener(
+ 'pinchend', e => this.onPinchEnd_(e));
+
+ /** @private {?ViewerPdfToolbarElement} */
+ this.toolbar_ = null;
+ if (toolbarEnabled) {
+ this.toolbar_ = /** @type {!ViewerPdfToolbarElement} */ ($('toolbar'));
+ this.toolbar_.hidden = false;
+ this.toolbar_.addEventListener('save', () => this.save_());
+ this.toolbar_.addEventListener('print', () => this.print_());
+ this.toolbar_.addEventListener(
+ 'undo', () => this.currentController_.undo());
+ this.toolbar_.addEventListener(
+ 'redo', () => this.currentController_.redo());
+ this.toolbar_.addEventListener(
+ 'rotate-right', () => this.rotateClockwise_());
+ this.toolbar_.addEventListener('annotation-mode-toggled', e => {
+ this.annotationModeToggled_(
+ /** @type {!CustomEvent<{value: boolean}>} */ (e));
+ });
+ this.toolbar_.addEventListener(
+ 'annotation-tool-changed',
+ e => this.inkController_.setAnnotationTool(e.detail.value));
+
+ this.toolbar_.docTitle = getFilenameFromURL(this.originalUrl_);
+ }
+
+ document.body.addEventListener('change-page', e => {
+ this.viewport_.goToPage(e.detail.page);
+ if (e.detail.origin == 'bookmark') {
+ PDFMetrics.record(PDFMetrics.UserAction.FOLLOW_BOOKMARK);
+ } else if (e.detail.origin == 'pageselector') {
+ PDFMetrics.record(PDFMetrics.UserAction.PAGE_SELECTOR_NAVIGATE);
+ }
+ });
+
+ document.body.addEventListener('change-zoom', e => {
+ this.viewport_.setZoom(e.detail.zoom);
+ });
+
+ document.body.addEventListener('change-page-and-xy', e => {
+ const point = this.viewport_.convertPageToScreen(e.detail.page, e.detail);
+ this.goToPageAndXY_(e.detail.origin, e.detail.page, point);
+ });
+
+ document.body.addEventListener('navigate', e => {
+ const disposition = e.detail.newtab ?
+ PdfNavigator.WindowOpenDisposition.NEW_BACKGROUND_TAB :
+ PdfNavigator.WindowOpenDisposition.CURRENT_TAB;
+ this.navigator_.navigate(e.detail.uri, disposition);
+ });
+
+ document.body.addEventListener('dropdown-opened', e => {
+ if (e.detail == 'bookmarks') {
+ PDFMetrics.record(PDFMetrics.UserAction.OPEN_BOOKMARKS_PANEL);
+ }
+ });
+
+ /** @private {!ToolbarManager} */
+ this.toolbarManager_ =
+ new ToolbarManager(window, this.toolbar_, this.zoomToolbar_);
+
+ // Set up the ZoomManager.
+ /** @private {!ZoomManager} */
+ this.zoomManager_ = ZoomManager.create(
+ this.browserApi_.getZoomBehavior(), () => this.viewport_.getZoom(),
+ zoom => this.browserApi_.setZoom(zoom),
+ this.browserApi_.getInitialZoom());
+ this.viewport_.setZoomManager(this.zoomManager_);
+ this.browserApi_.addZoomEventListener(
+ zoom => this.zoomManager_.onBrowserZoomChange(zoom));
+
+ // Setup the keyboard event listener.
+ document.addEventListener(
+ 'keydown',
+ e => this.handleKeyEvent_(/** @type {!KeyboardEvent} */ (e)));
+ document.addEventListener('mousemove', e => this.handleMouseEvent_(e));
+ document.addEventListener('mouseout', e => this.handleMouseEvent_(e));
+ document.addEventListener(
+ 'contextmenu', e => this.handleContextMenuEvent_(e));
+
+ const tabId = this.browserApi_.getStreamInfo().tabId;
+ /** @private {!PdfNavigator} */
+ this.navigator_ = new PdfNavigator(
+ this.originalUrl_, this.viewport_, this.paramsParser_,
+ new NavigatorDelegate(tabId));
+
+ /** @private {!ViewportScroller} */
+ this.viewportScroller_ =
+ new ViewportScroller(this.viewport_, this.plugin_, window);
+
+ /** @private {!Array<!Bookmark>} */
+ this.bookmarks_;
+
+ /** @private {!Point} */
+ this.lastViewportPosition_;
+
+ /** @private {boolean} */
+ this.reverseZoomToolbar_;
+
+ /** @private {boolean} */
+ this.inPrintPreviewMode_;
+
+ /** @private {boolean} */
+ this.dark_;
+
+ /** @private {!DocumentDimensionsMessageData} */
+ this.documentDimensions_;
+
+ // Request translated strings.
+ chrome.resourcesPrivate.getStrings(
+ chrome.resourcesPrivate.Component.PDF,
+ strings => this.handleStrings_(strings));
+
+ // Listen for save commands from the browser.
+ if (chrome.mimeHandlerPrivate && chrome.mimeHandlerPrivate.onSave) {
+ chrome.mimeHandlerPrivate.onSave.addListener(url => this.onSave_(url));
}
- });
-
- this.toolbarManager_ =
- new ToolbarManager(window, this.toolbar_, this.zoomToolbar_);
-
- // Set up the ZoomManager.
- this.zoomManager_ = ZoomManager.create(
- this.browserApi_.getZoomBehavior(), () => this.viewport_.getZoom(),
- zoom => this.browserApi_.setZoom(zoom),
- this.browserApi_.getInitialZoom());
- this.viewport_.setZoomManager(this.zoomManager_);
- this.browserApi_.addZoomEventListener(
- zoom => this.zoomManager_.onBrowserZoomChange(zoom));
-
- // Setup the keyboard event listener.
- document.addEventListener('keydown', e => this.handleKeyEvent_(e));
- document.addEventListener('mousemove', e => this.handleMouseEvent_(e));
- document.addEventListener('mouseout', e => this.handleMouseEvent_(e));
- document.addEventListener(
- 'contextmenu', e => this.handleContextMenuEvent_(e));
-
- const tabId = this.browserApi_.getStreamInfo().tabId;
- this.navigator_ = new PdfNavigator(
- this.originalUrl_, this.viewport_, this.paramsParser_,
- new NavigatorDelegate(tabId));
- this.viewportScroller_ =
- new ViewportScroller(this.viewport_, this.plugin_, window);
-
- // Request translated strings.
- chrome.resourcesPrivate.getStrings(
- 'pdf', strings => this.handleStrings_(strings));
-
- // Listen for save commands from the browser.
- if (chrome.mimeHandlerPrivate && chrome.mimeHandlerPrivate.onSave) {
- chrome.mimeHandlerPrivate.onSave.addListener(url => this.onSave(url));
}
-}
-PDFViewer.prototype = {
/**
* Handle key events. These may come from the user directly or via the
* scripting API.
- *
- * @param {KeyboardEvent} e the event to handle.
+ * @param {!KeyboardEvent} e the event to handle.
* @private
*/
- handleKeyEvent_: function(e) {
+ handleKeyEvent_(e) {
const position = this.viewport_.position;
// Certain scroll events may be sent from outside of the extension.
const fromScriptingAPI = e.fromScriptingAPI;
@@ -357,7 +459,7 @@ PDFViewer.prototype = {
return;
}
- this.toolbarManager_.hideToolbarsAfterTimeout(e);
+ this.toolbarManager_.hideToolbarsAfterTimeout();
const pageUpHandler = () => {
// Go to the previous page if we are fit-to-page or fit-to-height.
@@ -366,8 +468,8 @@ PDFViewer.prototype = {
// Since we do the movement of the page.
e.preventDefault();
} else if (fromScriptingAPI) {
- position.y -= this.viewport.size.height;
- this.viewport.position = position;
+ position.y -= this.viewport_.size.height;
+ this.viewport_.position = position;
}
};
const pageDownHandler = () => {
@@ -377,8 +479,8 @@ PDFViewer.prototype = {
// Since we do the movement of the page.
e.preventDefault();
} else if (fromScriptingAPI) {
- position.y += this.viewport.size.height;
- this.viewport.position = position;
+ position.y += this.viewport_.size.height;
+ this.viewport_.position = position;
}
};
@@ -416,14 +518,14 @@ PDFViewer.prototype = {
e.preventDefault();
} else if (fromScriptingAPI) {
position.x -= Viewport.SCROLL_INCREMENT;
- this.viewport.position = position;
+ this.viewport_.position = position;
}
}
return;
case 38: // Up arrow key.
if (fromScriptingAPI) {
position.y -= Viewport.SCROLL_INCREMENT;
- this.viewport.position = position;
+ this.viewport_.position = position;
}
return;
case 39: // Right arrow key.
@@ -437,19 +539,19 @@ PDFViewer.prototype = {
e.preventDefault();
} else if (fromScriptingAPI) {
position.x += Viewport.SCROLL_INCREMENT;
- this.viewport.position = position;
+ this.viewport_.position = position;
}
}
return;
case 40: // Down arrow key.
if (fromScriptingAPI) {
position.y += Viewport.SCROLL_INCREMENT;
- this.viewport.position = position;
+ this.viewport_.position = position;
}
return;
case 65: // 'a' key.
if (e.ctrlKey || e.metaKey) {
- this.pluginController_.postMessage({type: 'selectAll'});
+ this.pluginController_.selectAll();
// Since we do selection ourselves.
e.preventDefault();
}
@@ -462,7 +564,7 @@ PDFViewer.prototype = {
return;
case 219: // Left bracket key.
if (e.ctrlKey) {
- this.rotateCounterclockwise();
+ this.rotateCounterclockwise_();
}
return;
case 220: // Backslash key.
@@ -472,7 +574,7 @@ PDFViewer.prototype = {
return;
case 221: // Right bracket key.
if (e.ctrlKey) {
- this.rotateClockwise();
+ this.rotateClockwise_();
}
return;
}
@@ -487,42 +589,49 @@ PDFViewer.prototype = {
this.toolbarManager_.showToolbars();
}
}
- },
+ }
- handleMouseEvent_: function(e) {
+ handleMouseEvent_(e) {
if (e.type == 'mousemove') {
this.toolbarManager_.handleMouseMove(e);
} else if (e.type == 'mouseout') {
this.toolbarManager_.hideToolbarsForMouseOut();
}
- },
+ }
- handleContextMenuEvent_: function(e) {
+ /**
+ * @param {!Event} e The context menu event
+ * @private
+ */
+ handleContextMenuEvent_(e) {
// Stop Chrome from popping up the context menu on long press. We need to
// make sure the start event did not have 2 touches because we don't want
// to block two finger tap opening the context menu. We check for
// firesTouchEvents in order to not block the context menu on right click.
- if (e.sourceCapabilities.firesTouchEvents &&
+ const capabilities =
+ /** @type {{ sourceCapabilities: Object }} */ (e).sourceCapabilities;
+ if (capabilities.firesTouchEvents &&
!this.gestureDetector_.wasTwoFingerTouch()) {
e.preventDefault();
}
- },
+ }
/**
* Handles the annotation mode being toggled on or off.
- *
* @param {!CustomEvent<{value: boolean}>} e
* @private
*/
- annotationModeToggled_: async function(e) {
+ async annotationModeToggled_(e) {
const annotationMode = e.detail.value;
if (annotationMode) {
// Enter annotation mode.
assert(this.currentController_ == this.pluginController_);
// TODO(dstockwell): set plugin read-only, begin transition
- this.updateProgress(0);
+ this.updateProgress_(0);
// TODO(dstockwell): handle save failure
- const result = await this.pluginController_.save(true);
+ const saveResult = await this.pluginController_.save(true);
+ // Data always exists when save is called with requireResult = true.
+ const result = /** @type {!RequiredSaveResult} */ (saveResult);
if (result.hasUnsavedChanges) {
assert(!loadTimeData.getBoolean('pdfFormSaveEnabled'));
try {
@@ -530,25 +639,26 @@ PDFViewer.prototype = {
} catch (e) {
// The user aborted entering annotation mode. Revert to the plugin.
this.toolbar_.annotationMode = false;
- this.updateProgress(100);
+ this.updateProgress_(100);
return;
}
}
PDFMetrics.record(PDFMetrics.UserAction.ENTER_ANNOTATION_MODE);
this.hasEnteredAnnotationMode_ = true;
// TODO(dstockwell): feed real progress data from the Ink component
- this.updateProgress(50);
+ this.updateProgress_(50);
await this.inkController_.load(result.fileName, result.dataToSave);
- this.inkController_.setAnnotationTool(this.toolbar_.annotationTool);
+ this.inkController_.setAnnotationTool(
+ assert(this.toolbar_.annotationTool));
this.currentController_ = this.inkController_;
this.pluginController_.unload();
- this.updateProgress(100);
+ this.updateProgress_(100);
} else {
// Exit annotation mode.
PDFMetrics.record(PDFMetrics.UserAction.EXIT_ANNOTATION_MODE);
assert(this.currentController_ == this.inkController_);
// TODO(dstockwell): set ink read-only, begin transition
- this.updateProgress(0);
+ this.updateProgress_(0);
// This runs separately to allow other consumers of `loaded` to queue
// up after this task.
this.loaded.then(() => {
@@ -556,36 +666,33 @@ PDFViewer.prototype = {
this.inkController_.unload();
});
// TODO(dstockwell): handle save failure
- const result = await this.inkController_.save(true);
+ const saveResult = await this.inkController_.save(true);
+ // Data always exists when save is called with requireResult = true.
+ const result = /** @type {!RequiredSaveResult} */ (saveResult);
await this.pluginController_.load(result.fileName, result.dataToSave);
// Ensure the plugin gets the initial viewport.
this.pluginController_.afterZoom();
}
- },
+ }
/**
* Exits annotation mode if active.
- *
* @return {Promise<void>}
*/
- exitAnnotationMode_: async function() {
+ async exitAnnotationMode_() {
if (!this.toolbar_.annotationMode) {
return;
}
this.toolbar_.toggleAnnotation();
await this.loaded;
- },
+ }
/**
* Request to change the viewport fitting type.
- *
- * @param {!CustomEvent<{
- * fittingType: FittingType,
- * userInitiated: boolean
- * }>} e
+ * @param {!CustomEvent<FitToChangedEvent>} e
* @private
*/
- fitToChanged_: function(e) {
+ fitToChanged_(e) {
if (e.detail.fittingType == FittingType.FIT_TO_PAGE) {
this.viewport_.fitToPage();
this.toolbarManager_.forceHideTopToolbar();
@@ -599,15 +706,14 @@ PDFViewer.prototype = {
if (e.detail.userInitiated) {
PDFMetrics.recordFitTo(e.detail.fittingType);
}
- },
+ }
/**
* Sends a 'documentLoaded' message to the PDFScriptingAPI if the document has
* finished loading.
- *
* @private
*/
- sendDocumentLoadedMessage_: function() {
+ sendDocumentLoadedMessage_() {
if (this.loadState_ == LoadState.LOADING) {
return;
}
@@ -616,17 +722,16 @@ PDFViewer.prototype = {
}
this.sendScriptingMessage_(
{type: 'documentLoaded', load_state: this.loadState_});
- },
+ }
/**
* 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} params The open params passed in the URL.
* @private
*/
- handleURLParams_: function(params) {
+ handleURLParams_(params) {
if (params.zoom) {
this.viewport_.setZoom(params.zoom);
}
@@ -654,32 +759,42 @@ PDFViewer.prototype = {
}
this.isUserInitiatedEvent_ = true;
}
- },
+ }
/**
* Moves the viewport to a point in a page. Called back after a
* 'transformPagePointReply' is returned from the plugin.
- *
* @param {string} origin Identifier for the caller for logging purposes.
* @param {number} page The index of the page to go to. zero-based.
- * @param {Object} message Message received from the plugin containing the
+ * @param {Point} message Message received from the plugin containing the
* x and y to navigate to in screen coordinates.
* @private
*/
- goToPageAndXY_: function(origin, page, message) {
+ goToPageAndXY_(origin, page, message) {
this.viewport_.goToPageAndXY(page, message.x, message.y);
if (origin == 'bookmark') {
PDFMetrics.record(PDFMetrics.UserAction.FOLLOW_BOOKMARK);
}
- },
+ }
/**
- * @return {Promise} Resolved when the load state reaches LOADED,
- * rejects on FAILED.
+ * @return {?Promise} Resolved when the load state reaches LOADED,
+ * rejects on FAILED. Returns null if no promise has been created, which
+ * is the case for initial load of the PDF.
*/
get loaded() {
- return this.loaded_.promise;
- },
+ return this.loaded_ ? this.loaded_.promise : null;
+ }
+
+ /** @return {!Viewport} The viewport. Used for testing. */
+ get viewport() {
+ return this.viewport_;
+ }
+
+ /** @return {!Array<!Bookmark>} The bookmarks. Used for testing. */
+ get bookmarks() {
+ return this.bookmarks_;
+ }
/**
* Updates the load state and triggers completion of the `loaded`
@@ -691,26 +806,29 @@ PDFViewer.prototype = {
if (this.loadState_ == loadState) {
return;
}
+ assert(
+ loadState == LoadState.LOADING || this.loadState_ == LoadState.LOADING);
+ this.loadState_ = loadState;
+ if (!this.initialLoadComplete_) {
+ this.initialLoadComplete_ = true;
+ return;
+ }
if (loadState == LoadState.SUCCESS) {
- assert(this.loadState_ == LoadState.LOADING);
this.loaded_.resolve();
} else if (loadState == LoadState.FAILED) {
- assert(this.loadState_ == LoadState.LOADING);
this.loaded_.reject();
} else {
- assert(loadState == LoadState.LOADING);
this.loaded_ = new PromiseResolver();
}
- this.loadState_ = loadState;
- },
+ }
/**
* Update the loading progress of the document in response to a progress
* message being received from the content controller.
- *
* @param {number} progress the progress as a percentage.
+ * @private
*/
- updateProgress: function(progress) {
+ updateProgress_(progress) {
if (this.toolbar_) {
this.toolbar_.loadProgress = progress;
}
@@ -743,28 +861,26 @@ PDFViewer.prototype = {
} else {
this.setLoadState_(LoadState.LOADING);
}
- },
+ }
/** @private */
- sendBackgroundColorForPrintPreview_: function() {
- this.pluginController_.postMessage({
- type: 'backgroundColorChanged',
- backgroundColor: this.dark_ ?
- PDFViewer.PRINT_PREVIEW_DARK_BACKGROUND_COLOR :
- PDFViewer.PRINT_PREVIEW_BACKGROUND_COLOR,
- });
- },
+ sendBackgroundColorForPrintPreview_() {
+ this.pluginController_.backgroundColorChanged(
+ this.dark_ ? PDFViewer.PRINT_PREVIEW_DARK_BACKGROUND_COLOR :
+ PDFViewer.PRINT_PREVIEW_BACKGROUND_COLOR);
+ }
/**
* Load a dictionary of translated strings into the UI. Used as a callback for
* chrome.resourcesPrivate.
- *
* @param {Object} strings Dictionary of translated strings
* @private
*/
- handleStrings_: function(strings) {
- document.documentElement.dir = strings.textdirection;
- document.documentElement.lang = strings.language;
+ handleStrings_(strings) {
+ const stringsDictionary =
+ /** @type {{ textdirection: string, language: string }} */ (strings);
+ document.documentElement.dir = stringsDictionary.textdirection;
+ document.documentElement.lang = stringsDictionary.language;
loadTimeData.data = strings;
const isNewPrintPreview = this.isPrintPreview_ &&
@@ -786,38 +902,34 @@ PDFViewer.prototype = {
if ($('form-warning')) {
$('form-warning').strings = strings;
}
- },
+ }
/**
* 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.
+ * @param {!CustomEvent<{password: string}>} event a password-submitted event.
* @private
*/
- onPasswordSubmitted_: function(event) {
- this.pluginController_.postMessage(
- {type: 'getPasswordComplete', password: event.detail.password});
- },
+ onPasswordSubmitted_(event) {
+ this.pluginController_.getPasswordComplete(event.detail.password);
+ }
/**
* A callback that sets |isUserInitiatedEvent_| to |userInitiated|.
- *
* @param {boolean} userInitiated The value to set |isUserInitiatedEvent_| to.
* @private
*/
- setUserInitiated_: function(userInitiated) {
+ setUserInitiated_(userInitiated) {
assert(this.isUserInitiatedEvent_ != userInitiated);
this.isUserInitiatedEvent_ = userInitiated;
- },
+ }
/**
* A callback that's called when an update to a pinch zoom is detected.
- *
* @param {!Object} e the pinch event.
* @private
*/
- onPinchUpdate_: function(e) {
+ onPinchUpdate_(e) {
// Throttle number of pinch events to one per frame.
if (!this.sentPinchEvent_) {
this.sentPinchEvent_ = true;
@@ -826,42 +938,39 @@ PDFViewer.prototype = {
this.viewport_.pinchZoom(e);
});
}
- },
+ }
/**
* A callback that's called when the end of a pinch zoom is detected.
- *
* @param {!Object} e the pinch event.
* @private
*/
- onPinchEnd_: function(e) {
+ onPinchEnd_(e) {
// Using rAF for pinch end prevents pinch updates scheduled by rAF getting
// sent after the pinch end.
window.requestAnimationFrame(() => {
this.viewport_.pinchZoomEnd(e);
});
- },
+ }
/**
* A callback that's called when the start of a pinch zoom is detected.
- *
* @param {!Object} e the pinch event.
* @private
*/
- onPinchStart_: function(e) {
+ onPinchStart_(e) {
// We also use rAF for pinch start, so that if there is a pinch end event
// scheduled by rAF, this pinch start will be sent after.
window.requestAnimationFrame(() => {
this.viewport_.pinchZoomStart(e);
});
- },
+ }
/**
* A callback that's called after the viewport changes.
- *
* @private
*/
- viewportChanged_: function() {
+ viewportChanged_() {
if (!this.documentDimensions_) {
return;
}
@@ -900,9 +1009,10 @@ PDFViewer.prototype = {
// TODO(raymes): Give pageIndicator_ the same API as toolbar_.
if (this.pageIndicator_) {
+ const lastIndex = this.pageIndicator_.index;
this.pageIndicator_.index = visiblePage;
if (this.documentDimensions_.pageDimensions.length > 1 &&
- hasScrollbars.vertical) {
+ hasScrollbars.vertical && lastIndex !== undefined) {
this.pageIndicator_.style.visibility = 'visible';
} else {
this.pageIndicator_.style.visibility = 'hidden';
@@ -921,16 +1031,15 @@ PDFViewer.prototype = {
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.
+ * @param {!MessageObject} message The message to handle.
*/
- handleScriptingMessage: function(message) {
+ handleScriptingMessage(message) {
if (this.parentWindow_ != message.source) {
this.parentWindow_ = message.source;
this.parentOrigin_ = message.origin;
@@ -953,30 +1062,38 @@ PDFViewer.prototype = {
switch (message.data.type.toString()) {
case 'getSelectedText':
+ this.pluginController_.getSelectedText();
+ break;
case 'print':
+ this.pluginController_.print();
+ break;
case 'selectAll':
- this.pluginController_.postMessage(message.data);
+ this.pluginController_.selectAll();
break;
}
- },
+ }
/**
* Handle scripting messages specific to print preview.
- *
- * @param {MessageObject} message the message to handle.
+ * @param {!MessageObject} message the message to handle.
* @return {boolean} true if the message was handled, false otherwise.
* @private
*/
- handlePrintPreviewScriptingMessage_: function(message) {
+ handlePrintPreviewScriptingMessage_(message) {
if (!this.isPrintPreview_) {
return false;
}
- switch (message.data.type.toString()) {
+ let messageData = message.data;
+ switch (messageData.type.toString()) {
case 'loadPreviewPage':
- this.pluginController_.postMessage(message.data);
+ messageData =
+ /** @type {{ url: string, index: number }} */ (messageData);
+ this.pluginController_.loadPreviewPage(
+ messageData.url, messageData.index);
return true;
case 'resetPrintPreviewMode':
+ messageData = /** @type {!PrintPreviewParams} */ (messageData);
this.setLoadState_(LoadState.LOADING);
if (!this.inPrintPreviewMode_) {
this.inPrintPreviewMode_ = true;
@@ -999,49 +1116,42 @@ PDFViewer.prototype = {
saveButton.parentNode.removeChild(saveButton);
}
- this.pageIndicator_.pageLabels = message.data.pageNumbers;
+ this.pageIndicator_.pageLabels = messageData.pageNumbers;
- this.pluginController_.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)
- });
+ this.pluginController_.resetPrintPreviewMode(messageData);
return true;
case 'sendKeyEvent':
- this.handleKeyEvent_(DeserializeKeyEvent(message.data.keyEvent));
+ this.handleKeyEvent_(/** @type {!KeyboardEvent} */ (DeserializeKeyEvent(
+ /** @type {{ keyEvent: Object }} */ (message.data).keyEvent)));
return true;
case 'hideToolbars':
this.toolbarManager_.resetKeyboardNavigationAndHideToolbars();
return true;
case 'darkModeChanged':
- this.dark_ = message.data.darkMode;
+ this.dark_ = /** @type {{darkMode: boolean}} */ (message.data).darkMode;
if (this.isPrintPreview_) {
this.sendBackgroundColorForPrintPreview_();
}
return true;
case 'scrollPosition':
const position = this.viewport_.position;
- position.y += message.data.y;
- position.x += message.data.x;
- this.viewport.position = position;
+ messageData = /** @type {{ x: number, y: number }} */ (message.data);
+ position.y += messageData.y;
+ position.x += messageData.x;
+ this.viewport_.position = position;
return true;
}
return false;
- },
+ }
/**
* Send a scripting message outside the extension (typically to
* PDFScriptingAPI in a page containing the extension).
- *
* @param {Object} message the message to send.
* @private
*/
- sendScriptingMessage_: function(message) {
+ sendScriptingMessage_(message) {
if (this.parentWindow_ && this.parentOrigin_) {
let targetOrigin;
// Only send data back to the embedder if it is from the same origin,
@@ -1057,34 +1167,73 @@ PDFViewer.prototype = {
}
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.
+ * @param {!CustomEvent<MessageData>} e
+ * @private
*/
- get bookmarks() {
- return this.bookmarks_;
- },
+ handlePluginMessage_(e) {
+ const data = e.detail;
+ switch (data.type.toString()) {
+ case 'beep':
+ this.handleBeep_();
+ return;
+ case 'documentDimensions':
+ this.setDocumentDimensions_(
+ /** @type {!DocumentDimensionsMessageData} */ (data));
+ return;
+ case 'getPassword':
+ this.handlePasswordRequest_();
+ return;
+ case 'getSelectedTextReply':
+ this.handleSelectedTextReply_(
+ /** @type {{ selectedText: string }} */ (data).selectedText);
+ return;
+ case 'loadProgress':
+ this.updateProgress_(
+ /** @type {{ progress: number }} */ (data).progress);
+ return;
+ case 'navigate':
+ const navigateData = /** @type {!NavigateMessageData} */ (data);
+ this.handleNavigate_(navigateData.url, navigateData.disposition);
+ return;
+ case 'navigateToDestination':
+ const destinationData = /** @type {!DestinationMessageData} */ (data);
+ this.handleNavigateToDestination_(
+ destinationData.page, destinationData.x, destinationData.y,
+ destinationData.zoom);
+ return;
+ case 'printPreviewLoaded':
+ this.handlePrintPreviewLoaded_();
+ return;
+ case 'metadata':
+ const metadata = /** @type {!MetadataMessageData} */ (data);
+ this.setDocumentMetadata_(
+ metadata.title, metadata.bookmarks, metadata.canSerializeDocument);
+ return;
+ case 'setIsSelecting':
+ this.setIsSelecting_(
+ /** @type {{ isSelecting: boolean }} */ (data).isSelecting);
+ return;
+ case 'getNamedDestinationReply':
+ this.paramsParser_.onNamedDestinationReceived(
+ /** @type {{ pageNumber: number }} */ (data).pageNumber);
+ return;
+ case 'formFocusChange':
+ this.isFormFieldFocused_ =
+ /** @type {{ focused: boolean }} */ (data).focused;
+ return;
+ }
+ assertNotReached('Unknown message type received: ' + data.type);
+ }
/**
* Sets document dimensions from the current controller.
- *
- * @param {{height: number, width: number, pageDimensions: Array}}
- * documentDimensions
+ * @param {!DocumentDimensionsMessageData} documentDimensions
+ * @private
*/
- setDocumentDimensions: function(documentDimensions) {
+ setDocumentDimensions_(documentDimensions) {
this.documentDimensions_ = documentDimensions;
this.isUserInitiatedEvent_ = false;
this.viewport_.setDocumentDimensions(this.documentDimensions_);
@@ -1098,20 +1247,22 @@ PDFViewer.prototype = {
if (this.toolbar_) {
this.toolbar_.docLength = this.documentDimensions_.pageDimensions.length;
}
- },
+ }
/**
* Handles a beep request from the current controller.
+ * @private
*/
- handleBeep: function() {
+ handleBeep_() {
// Beeps are annoying, so just track count for now.
this.beepCount_ += 1;
- },
+ }
/**
* Handles a password request from the current controller.
+ * @private
*/
- handlePasswordRequest: function() {
+ handlePasswordRequest_() {
// 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) {
@@ -1121,26 +1272,27 @@ PDFViewer.prototype = {
} else {
this.passwordScreen_.deny();
}
- },
+ }
/**
* Handles a selected text reply from the current controller.
* @param {string} selectedText
+ * @private
*/
- handleSelectedTextReply: function(selectedText) {
+ handleSelectedTextReply_(selectedText) {
this.sendScriptingMessage_({
type: 'getSelectedTextReply',
selectedText: selectedText,
});
- },
+ }
/**
* Handles a navigation request from the current controller.
- *
* @param {string} url
- * @param {string} disposition
+ * @param {!PdfNavigator.WindowOpenDisposition} disposition
+ * @private
*/
- handleNavigate: function(url, disposition) {
+ handleNavigate_(url, disposition) {
// If in print preview, always open a new tab.
if (this.isPrintPreview_) {
this.navigator_.navigate(
@@ -1148,24 +1300,48 @@ PDFViewer.prototype = {
} else {
this.navigator_.navigate(url, disposition);
}
- },
+ }
+
+ /**
+ * Handles an internal navigation request to a destination from the current
+ * controller.
+ *
+ * @param {number} page
+ * @param {number} x
+ * @param {number} y
+ * @param {number} zoom
+ * @private
+ */
+ handleNavigateToDestination_(page, x, y, zoom) {
+ if (zoom) {
+ this.viewport_.setZoom(zoom);
+ }
+
+ if (x || y) {
+ this.viewport_.goToPageAndXY(page, x ? x : 0, y ? y : 0);
+ } else {
+ this.viewport_.goToPage(page);
+ }
+ }
/**
* Handles a notification that print preview has loaded from the
* current controller.
+ * @private
*/
- handlePrintPreviewLoaded: function() {
+ handlePrintPreviewLoaded_() {
this.isPrintPreviewLoadingFinished_ = true;
this.sendDocumentLoadedMessage_();
- },
+ }
/**
* Sets document metadata from the current controller.
* @param {string} title
- * @param {Array} bookmarks
+ * @param {!Array<!Bookmark>} bookmarks
* @param {boolean} canSerializeDocument
+ * @private
*/
- setDocumentMetadata: function(title, bookmarks, canSerializeDocument) {
+ setDocumentMetadata_(title, bookmarks, canSerializeDocument) {
if (title) {
document.title = title;
} else {
@@ -1174,47 +1350,40 @@ PDFViewer.prototype = {
this.bookmarks_ = bookmarks;
if (this.toolbar_) {
this.toolbar_.docTitle = document.title;
- this.toolbar_.bookmarks = this.bookmarks;
+ this.toolbar_.bookmarks = this.bookmarks_;
}
this.canSerializeDocument_ = canSerializeDocument;
this.updateAnnotationAvailable_();
- },
+ }
/**
* Sets the is selecting flag from the current controller.
* @param {boolean} isSelecting
+ * @private
*/
- setIsSelecting: function(isSelecting) {
+ setIsSelecting_(isSelecting) {
this.viewportScroller_.setEnableScrolling(isSelecting);
- },
-
- /**
- * Sets the form field focused flag from the current controller.
- * @param {boolean} focused
- */
- setIsFormFieldFocused: function(focused) {
- this.isFormFieldFocused_ = focused;
- },
+ }
/**
* An event handler for when the browser tells the PDF Viewer to perform a
* save.
- *
* @param {string} streamUrl unique identifier for a PDF Viewer instance.
* @private
*/
- onSave: async function(streamUrl) {
+ async onSave_(streamUrl) {
if (streamUrl != this.browserApi_.getStreamInfo().streamUrl) {
return;
}
- this.save();
- },
+ this.save_();
+ }
/**
* Saves the current PDF document to disk.
+ * @private
*/
- save: async function() {
+ async save_() {
PDFMetrics.record(PDFMetrics.UserAction.SAVE);
if (this.hasEnteredAnnotationMode_) {
PDFMetrics.record(PDFMetrics.UserAction.SAVE_WITH_ANNOTATION);
@@ -1257,17 +1426,19 @@ PDFViewer.prototype = {
// Saving in Annotation mode is destructive: crbug.com/919364
this.exitAnnotationMode_();
- },
+ }
- print: async function() {
+ /** @private */
+ async print_() {
PDFMetrics.record(PDFMetrics.UserAction.PRINT);
await this.exitAnnotationMode_();
this.currentController_.print();
- },
+ }
/**
* Updates the toolbar's annotation available flag depending on current
* conditions.
+ * @private
*/
updateAnnotationAvailable_() {
if (!this.toolbar_) {
@@ -1284,408 +1455,67 @@ PDFViewer.prototype = {
annotationAvailable = false;
}
this.toolbar_.annotationAvailable = annotationAvailable;
- },
+ }
- rotateClockwise() {
+ /** @private */
+ rotateClockwise_() {
PDFMetrics.record(PDFMetrics.UserAction.ROTATE);
this.viewport_.rotateClockwise(1);
this.currentController_.rotateClockwise();
this.updateAnnotationAvailable_();
- },
+ }
- rotateCounterclockwise() {
+ /** @private */
+ rotateCounterclockwise_() {
PDFMetrics.record(PDFMetrics.UserAction.ROTATE);
this.viewport_.rotateClockwise(3);
this.currentController_.rotateCounterclockwise();
this.updateAnnotationAvailable_();
- },
-
- setHasUnsavedChanges: function() {
- // Warn the user if they attempt to close the window without saving.
- chrome.mimeHandlerPrivate.setShowBeforeUnloadDialog(true);
- },
-
- /** @param {UndoState} state */
- setAnnotationUndoState(state) {
- this.toolbar_.canUndoAnnotation = state.canUndo;
- this.toolbar_.canRedoAnnotation = state.canRedo;
- },
-};
-
-/** @abstract */
-class ContentController {
- constructor() {}
-
- /**
- * A callback that's called before the zoom changes.
- */
- beforeZoom() {}
-
- /**
- * A callback that's called after the zoom changes.
- */
- afterZoom() {}
-
- /**
- * Handles a change to the viewport.
- */
- viewportChanged() {}
-
- /**
- * Rotates the document 90 degrees in the clockwise direction.
- * @abstract
- */
- rotateClockwise() {}
-
- /**
- * Rotates the document 90 degrees in the counter clockwise direction.
- * @abstract
- */
- rotateCounterclockwise() {}
-
- /**
- * Triggers printing of the current document.
- */
- print() {}
-
- /**
- * Undo an edit action.
- */
- undo() {}
-
- /**
- * Redo an edit action.
- */
- redo() {}
-
- /**
- * Requests that the current document be saved.
- * @param {boolean} requireResult whether a response is required, otherwise
- * the controller may save the document to disk internally.
- * @return {Promise<{fileName: string, dataToSave: ArrayBuffer}}
- * @abstract
- */
- save(requireResult) {}
-
- /**
- * Loads PDF document from `data` activates UI.
- * @param {string} fileName
- * @param {ArrayBuffer} data
- * @return {Promise<void>}
- * @abstract
- */
- load(fileName, data) {}
-
- /**
- * Unloads the current document and removes the UI.
- * @abstract
- */
- unload() {}
-}
-
-class InkController extends ContentController {
- /**
- * @param {PDFViewer} viewer
- * @param {Viewport} viewport
- */
- constructor(viewer, viewport) {
- super();
- this.viewer_ = viewer;
- this.viewport_ = viewport;
-
- /** @type {ViewerInkHost} */
- this.inkHost_ = null;
}
- /** @param {AnnotationTool} tool */
- setAnnotationTool(tool) {
- this.tool_ = tool;
- if (this.inkHost_) {
- this.inkHost_.setAnnotationTool(tool);
- }
- }
-
- /** @override */
- rotateClockwise() {
- // TODO(dstockwell): implement rotation
- }
-
- /** @override */
- rotateCounterclockwise() {
- // TODO(dstockwell): implement rotation
- }
-
- /** @override */
- viewportChanged() {
- this.inkHost_.viewportChanged();
- }
-
- /** @override */
- save(requireResult) {
- return this.inkHost_.saveDocument();
- }
-
- /** @override */
- undo() {
- this.inkHost_.undo();
- }
-
- /** @override */
- redo() {
- this.inkHost_.redo();
- }
-
- /** @override */
- load(filename, data) {
- if (!this.inkHost_) {
- this.inkHost_ = document.createElement('viewer-ink-host');
- $('content').appendChild(this.inkHost_);
- this.inkHost_.viewport = this.viewport_;
- this.inkHost_.addEventListener('stroke-added', e => {
- this.viewer_.setHasUnsavedChanges();
- });
- this.inkHost_.addEventListener('undo-state-changed', e => {
- this.viewer_.setAnnotationUndoState(e.detail);
- });
- }
- return this.inkHost_.load(filename, data);
- }
-
- /** @override */
- unload() {
- this.inkHost_.remove();
- this.inkHost_ = null;
- }
-}
-
-class PluginController extends ContentController {
/**
- * @param {HTMLEmbedElement} plugin
- * @param {PDFViewer} viewer
- * @param {Viewport} viewport
- */
- constructor(plugin, viewer, viewport) {
- super();
- this.plugin_ = plugin;
- this.viewer_ = viewer;
- this.viewport_ = viewport;
-
- /** @private {!Map<string, PromiseResolver>} */
- this.pendingTokens_ = new Map();
- this.plugin_.addEventListener(
- 'message', e => this.handlePluginMessage_(e), false);
- }
-
- /**
- * Notify the plugin to stop reacting to scroll events while zoom is taking
- * place to avoid flickering.
- * @override
- */
- beforeZoom() {
- this.postMessage({type: 'stopScrolling'});
-
- if (this.viewport_.pinchPhase == Viewport.PinchPhase.PINCH_START) {
- const position = this.viewport_.position;
- const zoom = this.viewport_.getZoom();
- const pinchPhase = this.viewport_.pinchPhase;
- this.postMessage({
- type: 'viewport',
- userInitiated: true,
- zoom: zoom,
- xOffset: position.x,
- yOffset: position.y,
- pinchPhase: pinchPhase
- });
- }
- }
-
- /**
- * Notify the plugin of the zoom change and to continue reacting to scroll
- * events.
- * @override
- */
- afterZoom() {
- const position = this.viewport_.position;
- const zoom = this.viewport_.getZoom();
- const pinchVector = this.viewport_.pinchPanVector || {x: 0, y: 0};
- const pinchCenter = this.viewport_.pinchCenter || {x: 0, y: 0};
- const pinchPhase = this.viewport_.pinchPhase;
-
- this.postMessage({
- type: 'viewport',
- userInitiated: this.viewer_.isUserInitiatedEvent_,
- zoom: zoom,
- xOffset: position.x,
- yOffset: position.y,
- pinchPhase: pinchPhase,
- pinchX: pinchCenter.x,
- pinchY: pinchCenter.y,
- pinchVectorX: pinchVector.x,
- pinchVectorY: pinchVector.y
- });
- }
-
- // TODO(dstockwell): this method should be private, add controller APIs that
- // map to all of the existing usage. crbug.com/913279
- /**
- * Post a message to the PPAPI plugin. Some messages will cause an async reply
- * to be received through handlePluginMessage_().
- *
- * @param {Object} message Message to post.
+ * @param {!CustomEvent<{canUndo: boolean, canRedo: boolean}>} e
+ * @private
*/
- postMessage(message) {
- this.plugin_.postMessage(message);
- }
-
- /** @override */
- rotateClockwise() {
- this.postMessage({type: 'rotateClockwise'});
- }
-
- /** @override */
- rotateCounterclockwise() {
- this.postMessage({type: 'rotateCounterclockwise'});
- }
-
- /** @override */
- print() {
- this.postMessage({type: 'print'});
- }
-
- /** @override */
- save(requireResult) {
- const resolver = new PromiseResolver();
- const newToken = createToken();
- this.pendingTokens_.set(newToken, resolver);
- this.postMessage({type: 'save', token: newToken, force: requireResult});
- return resolver.promise;
+ setAnnotationUndoState_(e) {
+ this.toolbar_.canUndoAnnotation = e.detail.canUndo;
+ this.toolbar_.canRedoAnnotation = e.detail.canRedo;
}
- /** @override */
- async load(fileName, data) {
- const url = URL.createObjectURL(new Blob([data]));
- this.plugin_.removeAttribute('headers');
- this.plugin_.setAttribute('stream-url', url);
- this.plugin_.style.display = 'block';
- try {
- await this.viewer_.loaded;
- } finally {
- URL.revokeObjectURL(url);
- }
- }
-
- /** @override */
- unload() {
- this.plugin_.style.display = 'none';
- }
-
- /**
- * An event handler for handling message events received from the plugin.
- *
- * @param {MessageObject} message a message event.
- * @private
- */
- handlePluginMessage_(message) {
- switch (message.data.type.toString()) {
- case 'beep':
- this.viewer_.handleBeep();
- break;
- case 'documentDimensions':
- this.viewer_.setDocumentDimensions(message.data);
- break;
- case 'email':
- const 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 'getPassword':
- this.viewer_.handlePasswordRequest();
- break;
- case 'getSelectedTextReply':
- this.viewer_.handleSelectedTextReply(message.data.selectedText);
- break;
- case 'goToPage':
- this.viewport_.goToPage(message.data.page);
- break;
- case 'loadProgress':
- this.viewer_.updateProgress(message.data.progress);
- break;
- case 'navigate':
- this.viewer_.handleNavigate(message.data.url, message.data.disposition);
- break;
- case 'printPreviewLoaded':
- this.viewer_.handlePrintPreviewLoaded();
- break;
- case 'setScrollPosition':
- this.viewport_.scrollTo(/** @type {!PartialPoint} */ (message.data));
- break;
- case 'scrollBy':
- this.viewport_.scrollBy(/** @type {!Point} */ (message.data));
- break;
- case 'metadata':
- this.viewer_.setDocumentMetadata(
- message.data.title, message.data.bookmarks,
- message.data.canSerializeDocument);
- break;
- case 'setIsSelecting':
- this.viewer_.setIsSelecting(message.data.isSelecting);
- break;
- case 'getNamedDestinationReply':
- this.viewer_.paramsParser_.onNamedDestinationReceived(
- message.data.pageNumber);
- break;
- case 'formFocusChange':
- this.viewer_.setIsFormFieldFocused(message.data.focused);
- break;
- case 'saveData':
- this.saveData_(message.data);
- break;
- case 'consumeSaveToken':
- const resolver = this.pendingTokens_.get(message.data.token);
- assert(this.pendingTokens_.delete(message.data.token));
- resolver.resolve(null);
- break;
+ /** @private */
+ resetTrackers_() {
+ this.viewport_.resetTracker();
+ if (this.tracker_) {
+ this.tracker_.removeAll();
}
}
+}
- /**
- * Handles the pdf file buffer received from the plugin.
- *
- * @param {SaveDataMessageData} messageData data of the message event.
- * @private
- */
- saveData_(messageData) {
- assert(
- loadTimeData.getBoolean('pdfFormSaveEnabled') ||
- loadTimeData.getBoolean('pdfAnnotationsEnabled'));
-
- // Verify a token that was created by this instance is included to avoid
- // being spammed.
- const resolver = this.pendingTokens_.get(messageData.token);
- assert(this.pendingTokens_.delete(messageData.token));
+/**
+ * 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;
- if (!messageData.dataToSave) {
- resolver.reject();
- return;
- }
+/**
+ * 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;
- // Verify the file size and the first bytes to make sure it's a PDF. Cap at
- // 100 MB. This cap should be kept in sync with and is also enforced in
- // pdf/out_of_process_instance.cc.
- const MIN_FILE_SIZE = '%PDF1.0'.length;
- const MAX_FILE_SIZE = 100 * 1000 * 1000;
+/**
+ * The background color used for print preview (--google-grey-refresh-300).
+ */
+PDFViewer.PRINT_PREVIEW_BACKGROUND_COLOR = '0xFFDADCE0';
- const buffer = messageData.dataToSave;
- const bufView = new Uint8Array(buffer);
- assert(
- bufView.length <= MAX_FILE_SIZE,
- `File too large to be saved: ${bufView.length} bytes.`);
- assert(bufView.length >= MIN_FILE_SIZE);
- assert(
- String.fromCharCode(bufView[0], bufView[1], bufView[2], bufView[3]) ==
- '%PDF');
+/**
+ * The background color used for print preview when dark mode is enabled
+ * (--google-grey-refresh-700).
+ */
+PDFViewer.PRINT_PREVIEW_DARK_BACKGROUND_COLOR = '0xFF5F6368';
- resolver.resolve(messageData);
- }
-}
+/**
+ * The background color used for the regular viewer.
+ */
+PDFViewer.BACKGROUND_COLOR = '0xFF525659';