| // 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. |
| |
| import './elements/viewer-error-dialog.js'; |
| import './elements/viewer-page-indicator.js'; |
| import './elements/shared-vars.js'; |
| import './pdf_viewer_shared_style.js'; |
| |
| import {assertNotReached} from 'chrome://resources/js/assert.m.js'; |
| import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; |
| import {isRTL} from 'chrome://resources/js/util.m.js'; |
| import {html} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; |
| |
| import {BrowserApi} from './browser_api.js'; |
| import {FittingType} from './constants.js'; |
| import {MessageData, PluginController, PrintPreviewParams} from './controller.js'; |
| import {ViewerZoomToolbarElement} from './elements/viewer-zoom-toolbar.js'; |
| import {DeserializeKeyEvent, LoadState, SerializeKeyEvent} from './pdf_scripting_api.js'; |
| import {PDFViewerBaseElement} from './pdf_viewer_base.js'; |
| import {DestinationMessageData, DocumentDimensionsMessageData, hasCtrlModifier, MessageObject, shouldIgnoreKeyEvents} from './pdf_viewer_utils.js'; |
| import {ToolbarManager} from './toolbar_manager.js'; |
| |
| class PDFViewerPPElement extends PDFViewerBaseElement { |
| static get is() { |
| return 'pdf-viewer-pp'; |
| } |
| |
| static get template() { |
| return html`{__html_template__}`; |
| } |
| |
| constructor() { |
| super(); |
| |
| /** @private {boolean} */ |
| this.isPrintPreviewLoadingFinished_ = false; |
| |
| /** @private {boolean} */ |
| this.inPrintPreviewMode_ = false; |
| |
| /** @private {boolean} */ |
| this.dark_ = false; |
| |
| /** @private {?ToolbarManager} */ |
| this.toolbarManager_ = null; |
| } |
| |
| /** @override */ |
| ready() { |
| super.ready(); |
| window.addEventListener('scroll', () => { |
| this.pluginController_.updateScroll(window.scrollX, window.scrollY); |
| }); |
| } |
| |
| /** @override */ |
| isNewUiEnabled() { |
| return false; |
| } |
| |
| /** @override */ |
| getBackgroundColor() { |
| return PRINT_PREVIEW_BACKGROUND_COLOR; |
| } |
| |
| /** |
| * @return {!ViewerZoomToolbarElement} |
| * @private |
| */ |
| getZoomToolbar_() { |
| return /** @type {!ViewerZoomToolbarElement} */ (this.$$('#zoom-toolbar')); |
| } |
| |
| /** @param {!BrowserApi} browserApi */ |
| init(browserApi) { |
| super.init( |
| browserApi, document.documentElement, |
| /** @type {!HTMLDivElement} */ (this.$$('#sizer')), |
| /** @type {!HTMLDivElement} */ (this.$$('#content'))); |
| |
| /** @private {?PluginController} */ |
| this.pluginController_ = PluginController.getInstance(); |
| |
| this.toolbarManager_ = new ToolbarManager(window, this.getZoomToolbar_()); |
| } |
| |
| /** @override */ |
| handleKeyEvent(e) { |
| if (shouldIgnoreKeyEvents() || e.defaultPrevented) { |
| return; |
| } |
| |
| this.toolbarManager_.hideToolbarAfterTimeout(); |
| // Let the viewport handle directional key events. |
| if (this.viewport.handleDirectionalKeyEvent(e, false)) { |
| return; |
| } |
| |
| switch (e.key) { |
| case 'Tab': |
| this.toolbarManager_.showToolbarForKeyboardNavigation(); |
| return; |
| case 'Escape': |
| break; // Ensure escape falls through to the print-preview handler. |
| case 'a': |
| if (hasCtrlModifier(e)) { |
| this.pluginController_.selectAll(); |
| // Since we do selection ourselves. |
| e.preventDefault(); |
| } |
| return; |
| case '\\': |
| if (e.ctrlKey) { |
| this.getZoomToolbar_().fitToggleFromHotKey(); |
| } |
| return; |
| } |
| |
| // Give print preview a chance to handle the key event. |
| if (!e.fromScriptingAPI) { |
| this.sendScriptingMessage( |
| {type: 'sendKeyEvent', keyEvent: SerializeKeyEvent(e)}); |
| } else { |
| // Show toolbar as a fallback. |
| if (!(e.shiftKey || e.ctrlKey || e.altKey)) { |
| this.getZoomToolbar_().show(); |
| } |
| } |
| } |
| |
| /** @private */ |
| setBackgroundColorForPrintPreview_() { |
| this.pluginController_.setBackgroundColor( |
| this.dark_ ? PRINT_PREVIEW_DARK_BACKGROUND_COLOR : |
| PRINT_PREVIEW_BACKGROUND_COLOR); |
| } |
| |
| /** @override */ |
| updateUIForViewportChange() { |
| // Offset the toolbar position so that it doesn't move if scrollbars appear. |
| const hasScrollbars = this.viewport.documentHasScrollbars(); |
| const scrollbarWidth = this.viewport.scrollbarWidth; |
| const verticalScrollbarWidth = hasScrollbars.vertical ? scrollbarWidth : 0; |
| const 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 LTR layout, the zoom toolbar is on the left |
| // left side, but the scrollbar is still on the right, so this is not |
| // necessary. |
| const zoomToolbar = this.getZoomToolbar_(); |
| if (isRTL()) { |
| 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. |
| zoomToolbar.style.bottom = -horizontalScrollbarWidth + 'px'; |
| |
| // Update the page indicator. |
| const visiblePage = this.viewport.getMostVisiblePage(); |
| const pageIndicator = this.$$('#page-indicator'); |
| const lastIndex = pageIndicator.index; |
| pageIndicator.index = visiblePage; |
| if (this.documentDimensions.pageDimensions.length > 1 && |
| hasScrollbars.vertical && lastIndex !== undefined) { |
| pageIndicator.style.visibility = 'visible'; |
| } else { |
| pageIndicator.style.visibility = 'hidden'; |
| } |
| |
| this.pluginController_.viewportChanged(); |
| } |
| |
| /** @override */ |
| handleScriptingMessage(message) { |
| if (super.handleScriptingMessage(message)) { |
| return true; |
| } |
| |
| if (this.handlePrintPreviewScriptingMessage_(message)) { |
| return true; |
| } |
| |
| if (this.delayScriptingMessage(message)) { |
| return true; |
| } |
| |
| switch (message.data.type.toString()) { |
| case 'getSelectedText': |
| this.pluginController_.getSelectedText().then( |
| this.sendScriptingMessage.bind(this)); |
| break; |
| case 'selectAll': |
| this.pluginController_.selectAll(); |
| break; |
| default: |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Handle scripting messages specific to print preview. |
| * @param {!MessageObject} message the message to handle. |
| * @return {boolean} true if the message was handled, false otherwise. |
| * @private |
| */ |
| handlePrintPreviewScriptingMessage_(message) { |
| let messageData = message.data; |
| switch (messageData.type.toString()) { |
| case 'loadPreviewPage': |
| 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; |
| this.isUserInitiatedEvent = false; |
| this.forceFit(FittingType.FIT_TO_PAGE); |
| this.updateViewportFit(FittingType.FIT_TO_PAGE); |
| this.isUserInitiatedEvent = true; |
| } |
| |
| // Stash the scroll location so that it can be restored when the new |
| // document is loaded. |
| this.lastViewportPosition = this.viewport.position; |
| this.$$('#page-indicator').pageLabels = messageData.pageNumbers; |
| |
| this.pluginController_.resetPrintPreviewMode(messageData); |
| return true; |
| case 'sendKeyEvent': |
| const keyEvent = DeserializeKeyEvent( |
| /** @type {{ keyEvent: Object }} */ (message.data).keyEvent); |
| keyEvent.fromScriptingAPI = true; |
| this.handleKeyEvent(keyEvent); |
| return true; |
| case 'hideToolbar': |
| this.toolbarManager_.resetKeyboardNavigationAndHideToolbar(); |
| return true; |
| case 'darkModeChanged': |
| this.dark_ = /** @type {{darkMode: boolean}} */ (message.data).darkMode; |
| this.setBackgroundColorForPrintPreview_(); |
| return true; |
| case 'scrollPosition': |
| const position = this.viewport.position; |
| messageData = /** @type {{ x: number, y: number }} */ (message.data); |
| position.y += messageData.y; |
| position.x += messageData.x; |
| this.viewport.setPosition(position); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** @override */ |
| setLoadState(loadState) { |
| super.setLoadState(loadState); |
| if (loadState === LoadState.FAILED) { |
| this.isPrintPreviewLoadingFinished_ = true; |
| } |
| } |
| |
| /** @override */ |
| handlePluginMessage(e) { |
| const data = e.detail; |
| switch (data.type.toString()) { |
| case 'documentDimensions': |
| this.setDocumentDimensions( |
| /** @type {!DocumentDimensionsMessageData} */ (data)); |
| return; |
| case 'loadProgress': |
| this.updateProgress( |
| /** @type {{ progress: number }} */ (data).progress); |
| return; |
| case 'navigateToDestination': |
| const destinationData = /** @type {!DestinationMessageData} */ (data); |
| this.viewport.handleNavigateToDestination( |
| destinationData.page, destinationData.x, destinationData.y, |
| destinationData.zoom); |
| return; |
| case 'printPreviewLoaded': |
| this.handlePrintPreviewLoaded_(); |
| return; |
| case 'setIsSelecting': |
| this.viewportScroller.setEnableScrolling( |
| /** @type {{ isSelecting: boolean }} */ (data).isSelecting); |
| return; |
| case 'touchSelectionOccurred': |
| this.sendScriptingMessage({ |
| type: 'touchSelectionOccurred', |
| }); |
| return; |
| case 'documentFocusChanged': |
| // TODO(crbug.com/1069370): Draw a focus rect around plugin. |
| return; |
| case 'sendKeyEvent': |
| const keyEvent = DeserializeKeyEvent( |
| /** @type {{ keyEvent: Object }} */ (data).keyEvent); |
| keyEvent.fromPlugin = true; |
| this.handleKeyEvent(keyEvent); |
| return; |
| case 'beep': |
| case 'formFocusChange': |
| case 'getPassword': |
| case 'metadata': |
| case 'navigate': |
| case 'setIsEditing': |
| // These messages are not relevant in Print Preview. |
| return; |
| } |
| assertNotReached('Unknown message type received: ' + data.type); |
| } |
| |
| /** |
| * Handles a notification that print preview has loaded from the |
| * current controller. |
| * @private |
| */ |
| handlePrintPreviewLoaded_() { |
| this.isPrintPreviewLoadingFinished_ = true; |
| this.sendDocumentLoadedMessage(); |
| } |
| |
| /** @override */ |
| readyToSendLoadMessage() { |
| return this.isPrintPreviewLoadingFinished_; |
| } |
| |
| /** @override */ |
| forceFit(view) { |
| this.getZoomToolbar_().forceFit(view); |
| } |
| |
| /** @override */ |
| handleStrings(strings) { |
| super.handleStrings(strings); |
| if (!strings) { |
| return; |
| } |
| this.setBackgroundColorForPrintPreview_(); |
| } |
| |
| /** @override */ |
| updateProgress(progress) { |
| super.updateProgress(progress); |
| if (progress === 100) { |
| this.toolbarManager_.hideToolbarAfterTimeout(); |
| } |
| } |
| } |
| |
| /** |
| * The background color used for print preview (--google-grey-refresh-300). Keep |
| * in sync with `ChromePdfStreamDelegate::MapToOriginalUrl()`. |
| * @type {number} |
| */ |
| const PRINT_PREVIEW_BACKGROUND_COLOR = 0xffdadce0; |
| |
| /** |
| * The background color used for print preview when dark mode is enabled |
| * (--google-grey-refresh-700). |
| * @type {number} |
| */ |
| const PRINT_PREVIEW_DARK_BACKGROUND_COLOR = 0xff5f6368; |
| |
| customElements.define(PDFViewerPPElement.is, PDFViewerPPElement); |