blob: fb88b84b28201b8454bab7edebe80acd64d1e5db [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.
import './elements/viewer-error-dialog.js';
import './elements/viewer-page-indicator.js';
import './elements/shared-vars.js';
import './elements/viewer-zoom-toolbar.js';
import './pdf_viewer_shared_style.js';
import {assertNotReached} from 'chrome://resources/js/assert_ts.js';
import {isRTL} from 'chrome://resources/js/util.m.js';
import {BrowserApi} from './browser_api.js';
import {ExtendedKeyEvent, FittingType} from './constants.js';
import {MessageData, PluginController, PrintPreviewParams} from './controller.js';
import {ViewerPageIndicatorElement} from './elements/viewer-page-indicator.js';
import {ViewerZoomToolbarElement} from './elements/viewer-zoom-toolbar.js';
import {deserializeKeyEvent, LoadState, serializeKeyEvent} from './pdf_scripting_api.js';
import {KeyEventData, PDFViewerBaseElement} from './pdf_viewer_base.js';
import {getTemplate} from './pdf_viewer_pp.html.js';
import {DestinationMessageData, DocumentDimensionsMessageData, hasCtrlModifier, shouldIgnoreKeyEvents} from './pdf_viewer_utils.js';
import {ToolbarManager} from './toolbar_manager.js';
export interface PDFViewerPPElement {
$: {
content: HTMLElement,
pageIndicator: ViewerPageIndicatorElement,
sizer: HTMLElement,
zoomToolbar: ViewerZoomToolbarElement,
};
}
export class PDFViewerPPElement extends PDFViewerBaseElement {
static get is() {
return 'pdf-viewer-pp';
}
static get template() {
return getTemplate();
}
private isPrintPreviewLoadingFinished_: boolean = false;
private inPrintPreviewMode_: boolean = false;
private dark_: boolean = false;
private pluginController_: PluginController|undefined = undefined;
private toolbarManager_: ToolbarManager|null = null;
override isNewUiEnabled() {
return false;
}
getBackgroundColor() {
return PRINT_PREVIEW_BACKGROUND_COLOR;
}
init(browserApi: BrowserApi) {
this.initInternal(
browserApi, document.documentElement, this.$.sizer, this.$.content);
this.pluginController_ = PluginController.getInstance();
this.$.pageIndicator.setViewport(this.viewport);
this.toolbarManager_ = new ToolbarManager(window, this.$.zoomToolbar);
}
handleKeyEvent(e: ExtendedKeyEvent) {
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.$.zoomToolbar.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.$.zoomToolbar.show();
}
}
}
private setBackgroundColorForPrintPreview_() {
this.pluginController_!.setBackgroundColor(
this.dark_ ? PRINT_PREVIEW_DARK_BACKGROUND_COLOR :
PRINT_PREVIEW_BACKGROUND_COLOR);
}
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.$.zoomToolbar;
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.$.pageIndicator;
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: MessageEvent) {
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 message the message to handle.
* @return true if the message was handled, false otherwise.
*/
private handlePrintPreviewScriptingMessage_(message: MessageEvent): boolean {
const messageData = message.data;
switch (messageData.type.toString()) {
case 'loadPreviewPage':
const loadData =
messageData as MessageData & {url: string, index: number};
this.pluginController_!.loadPreviewPage(loadData.url, loadData.index);
return true;
case 'resetPrintPreviewMode':
const printPreviewData =
messageData as (MessageData & PrintPreviewParams);
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.$.pageIndicator.pageLabels = printPreviewData.pageNumbers;
this.pluginController_!.resetPrintPreviewMode(printPreviewData);
return true;
case 'sendKeyEvent':
const keyEvent =
deserializeKeyEvent((message.data as KeyEventData).keyEvent);
const extendedKeyEvent = keyEvent as ExtendedKeyEvent;
extendedKeyEvent.fromScriptingAPI = true;
this.handleKeyEvent(extendedKeyEvent);
return true;
case 'hideToolbar':
this.toolbarManager_!.resetKeyboardNavigationAndHideToolbar();
return true;
case 'darkModeChanged':
this.dark_ =
(message.data as (MessageData & {darkMode: boolean})).darkMode;
this.setBackgroundColorForPrintPreview_();
return true;
case 'scrollPosition':
const position = this.viewport.position;
const positionData =
message.data as (MessageData & {x: number, y: number});
position.y += positionData.y;
position.x += positionData.x;
this.viewport.setPosition(position);
return true;
}
return false;
}
override setLoadState(loadState: LoadState) {
super.setLoadState(loadState);
if (loadState === LoadState.FAILED) {
this.isPrintPreviewLoadingFinished_ = true;
}
}
override handlePluginMessage(e: CustomEvent) {
const data = e.detail;
switch (data.type.toString()) {
case 'documentDimensions':
this.setDocumentDimensions(data as DocumentDimensionsMessageData);
return;
case 'loadProgress':
this.updateProgress((data as {progress: number}).progress);
return;
case 'navigateToDestination':
const destinationData = data as DestinationMessageData;
this.viewport.handleNavigateToDestination(
destinationData.page, destinationData.x, destinationData.y,
destinationData.zoom);
return;
case 'printPreviewLoaded':
this.handlePrintPreviewLoaded_();
return;
case 'setIsSelecting':
this.viewportScroller!.setEnableScrolling(
(data as (MessageData & {isSelecting: boolean})).isSelecting);
return;
case 'setSmoothScrolling':
this.viewport.setSmoothScrolling((data as (MessageData & {
smoothScrolling: boolean,
})).smoothScrolling);
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((data as KeyEventData).keyEvent) as
ExtendedKeyEvent;
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_;
}
forceFit(view: FittingType) {
this.$.zoomToolbar.forceFit(view);
}
protected afterZoom(_viewportZoom: number) {}
override handleStrings(strings: {[key: string]: string}) {
super.handleStrings(strings);
if (!strings) {
return;
}
this.setBackgroundColorForPrintPreview_();
}
override updateProgress(progress: number) {
super.updateProgress(progress);
if (progress === 100) {
this.toolbarManager_!.hideToolbarAfterTimeout();
}
}
}
/**
* The background color used for print preview (--google-grey-300). Keep
* in sync with `ChromePdfStreamDelegate::MapToOriginalUrl()`.
*/
const PRINT_PREVIEW_BACKGROUND_COLOR: number = 0xffdadce0;
/**
* The background color used for print preview when dark mode is enabled
* (--google-grey-700).
*/
const PRINT_PREVIEW_DARK_BACKGROUND_COLOR: number = 0xff5f6368;
declare global {
interface HTMLElementTagNameMap {
'pdf-viewer-pp': PDFViewerPPElement;
}
}
customElements.define(PDFViewerPPElement.is, PDFViewerPPElement);