summaryrefslogtreecommitdiff
path: root/chromium/chrome/browser/resources/pdf
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/chrome/browser/resources/pdf')
-rw-r--r--chromium/chrome/browser/resources/pdf/BUILD.gn95
-rw-r--r--chromium/chrome/browser/resources/pdf/annotation_tool.js2
-rw-r--r--chromium/chrome/browser/resources/pdf/browser_api.js20
-rw-r--r--chromium/chrome/browser/resources/pdf/constants.js11
-rw-r--r--chromium/chrome/browser/resources/pdf/controller.js145
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/BUILD.gn18
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-bookmark.html4
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-error-screen.html6
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-error-screen.js2
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-form-warning.html8
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-form-warning.js4
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-ink-host.js25
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-page-indicator.html2
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-page-selector.html19
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-page-selector.js2
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-password-screen.html9
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-password-screen.js8
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar-new.html10
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar-new.js17
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar.html80
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar.js161
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-pen-options.html6
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-pen-options.js39
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown.html10
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-zoom-button.html9
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar.html2
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar.js54
-rw-r--r--chromium/chrome/browser/resources/pdf/gesture_detector.js123
-rw-r--r--chromium/chrome/browser/resources/pdf/index.css50
-rw-r--r--chromium/chrome/browser/resources/pdf/index.html21
-rw-r--r--chromium/chrome/browser/resources/pdf/index_pp.html19
-rw-r--r--chromium/chrome/browser/resources/pdf/ink/externs.js74
-rw-r--r--chromium/chrome/browser/resources/pdf/ink/index.html2
-rw-r--r--chromium/chrome/browser/resources/pdf/ink/ink_api.js100
-rw-r--r--chromium/chrome/browser/resources/pdf/ink_controller.js128
-rw-r--r--chromium/chrome/browser/resources/pdf/main.js81
-rw-r--r--chromium/chrome/browser/resources/pdf/main_pp.js12
-rw-r--r--chromium/chrome/browser/resources/pdf/main_util.js83
-rw-r--r--chromium/chrome/browser/resources/pdf/metrics.js77
-rw-r--r--chromium/chrome/browser/resources/pdf/navigator.js8
-rw-r--r--chromium/chrome/browser/resources/pdf/open_pdf_params_parser.js85
-rw-r--r--chromium/chrome/browser/resources/pdf/pdf_scripting_api.js47
-rw-r--r--chromium/chrome/browser/resources/pdf/pdf_viewer.html61
-rw-r--r--chromium/chrome/browser/resources/pdf/pdf_viewer.js1446
-rw-r--r--chromium/chrome/browser/resources/pdf/pdf_viewer_base.js640
-rw-r--r--chromium/chrome/browser/resources/pdf/pdf_viewer_pp.html21
-rw-r--r--chromium/chrome/browser/resources/pdf/pdf_viewer_pp.js379
-rw-r--r--chromium/chrome/browser/resources/pdf/pdf_viewer_shared_style.html33
-rw-r--r--chromium/chrome/browser/resources/pdf/pdf_viewer_shared_style.js11
-rw-r--r--chromium/chrome/browser/resources/pdf/pdf_viewer_utils.js64
-rw-r--r--chromium/chrome/browser/resources/pdf/toolbar_manager.js48
-rw-r--r--chromium/chrome/browser/resources/pdf/viewport.js418
-rw-r--r--chromium/chrome/browser/resources/pdf/viewport_scroller.js15
-rw-r--r--chromium/chrome/browser/resources/pdf/zoom_manager.js36
54 files changed, 2808 insertions, 2042 deletions
diff --git a/chromium/chrome/browser/resources/pdf/BUILD.gn b/chromium/chrome/browser/resources/pdf/BUILD.gn
index c975fd597e2..02cef40417b 100644
--- a/chromium/chrome/browser/resources/pdf/BUILD.gn
+++ b/chromium/chrome/browser/resources/pdf/BUILD.gn
@@ -3,6 +3,22 @@
# found in the LICENSE file.
import("//third_party/closure_compiler/compile_js.gni")
+import("//tools/polymer/html_to_js.gni")
+
+group("web_components") {
+ public_deps = [
+ ":web_components_local",
+ "elements:web_components",
+ ]
+}
+
+html_to_js("web_components_local") {
+ js_files = [
+ "pdf_viewer.js",
+ "pdf_viewer_pp.js",
+ "pdf_viewer_shared_style.js",
+ ]
+}
group("closure_compile") {
deps = [
@@ -32,10 +48,12 @@ js_library("constants") {
}
js_library("gesture_detector") {
+ deps = [ "//ui/webui/resources/js/cr:event_target.m" ]
}
js_library("open_pdf_params_parser") {
deps = [ ":constants" ]
+ externs_list = [ "$externs_path/pending.js" ]
}
js_library("pdf_scripting_api") {
@@ -83,32 +101,61 @@ js_library("toolbar_manager") {
]
}
-js_library("controller") {
+js_library("ink_controller") {
deps = [
":annotation_tool",
+ ":controller",
+ ":viewport",
+ "//ui/webui/resources/js/cr:event_target.m",
+ ]
+}
+
+js_library("controller") {
+ deps = [
":viewport",
"elements:viewer-pdf-toolbar",
"//ui/webui/resources/js:assert.m",
"//ui/webui/resources/js:load_time_data.m",
"//ui/webui/resources/js:promise_resolver.m",
- "//ui/webui/resources/js:util.m",
"//ui/webui/resources/js/cr:event_target.m",
]
}
+js_library("pdf_viewer_base") {
+ deps = [
+ ":browser_api",
+ ":constants",
+ ":controller",
+ ":metrics",
+ ":pdf_scripting_api",
+ ":pdf_viewer_utils",
+ ":viewport",
+ ":viewport_scroller",
+ "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+ "//ui/webui/resources/js:assert.m",
+ "//ui/webui/resources/js:event_tracker.m",
+ "//ui/webui/resources/js:load_time_data.m",
+ "//ui/webui/resources/js:promise_resolver.m",
+ "//ui/webui/resources/js:util.m",
+ ]
+ externs_list = [ "$externs_path/resources_private.js" ]
+}
+
js_library("pdf_viewer") {
deps = [
":bookmark_type",
+ ":browser_api",
":constants",
":controller",
+ ":ink_controller",
":metrics",
":navigator",
":pdf_scripting_api",
+ ":pdf_viewer_base",
+ ":pdf_viewer_utils",
":toolbar_manager",
":viewport",
- ":viewport_scroller",
"elements:viewer-error-screen",
- "elements:viewer-page-indicator",
"elements:viewer-password-screen",
"elements:viewer-pdf-toolbar",
"elements:viewer-zoom-toolbar",
@@ -121,19 +168,39 @@ js_library("pdf_viewer") {
externs_list = [ "$externs_path/resources_private.js" ]
}
-js_library("main_util") {
+js_library("pdf_viewer_utils") {
deps = [
- ":browser_api",
- ":pdf_viewer",
+ ":controller",
+ ":viewport",
]
}
-js_library("main") {
- deps = [ ":main_util" ]
+js_library("pdf_viewer_pp") {
+ deps = [
+ ":constants",
+ ":controller",
+ ":pdf_scripting_api",
+ ":pdf_viewer_base",
+ ":pdf_viewer_utils",
+ ":toolbar_manager",
+ ":viewport",
+ "elements:viewer-error-screen",
+ "elements:viewer-page-indicator",
+ "elements:viewer-zoom-toolbar",
+ "//ui/webui/resources/js:assert.m",
+ "//ui/webui/resources/js:event_tracker.m",
+ "//ui/webui/resources/js:load_time_data.m",
+ "//ui/webui/resources/js:promise_resolver.m",
+ "//ui/webui/resources/js:util.m",
+ ]
+ externs_list = [ "$externs_path/resources_private.js" ]
}
-js_library("main_pp") {
- deps = [ ":main_util" ]
+js_library("main") {
+ deps = [
+ ":browser_api",
+ ":pdf_viewer",
+ ]
}
js_type_check("pdf_resources") {
@@ -144,14 +211,16 @@ js_type_check("pdf_resources") {
":constants",
":controller",
":gesture_detector",
+ ":ink_controller",
":main",
- ":main_pp",
- ":main_util",
":metrics",
":navigator",
":open_pdf_params_parser",
":pdf_scripting_api",
":pdf_viewer",
+ ":pdf_viewer_base",
+ ":pdf_viewer_pp",
+ ":pdf_viewer_utils",
":toolbar_manager",
":viewport",
":viewport_scroller",
diff --git a/chromium/chrome/browser/resources/pdf/annotation_tool.js b/chromium/chrome/browser/resources/pdf/annotation_tool.js
index 5e887b048b7..e8fa6e74d6c 100644
--- a/chromium/chrome/browser/resources/pdf/annotation_tool.js
+++ b/chromium/chrome/browser/resources/pdf/annotation_tool.js
@@ -9,7 +9,7 @@
* @typedef {{
* tool: string,
* size: number,
- * color: (string|null),
+ * color: (string|undefined),
* }}
*/
let AnnotationTool;
diff --git a/chromium/chrome/browser/resources/pdf/browser_api.js b/chromium/chrome/browser/resources/pdf/browser_api.js
index 53ceeef1da9..5e2fb44cdc3 100644
--- a/chromium/chrome/browser/resources/pdf/browser_api.js
+++ b/chromium/chrome/browser/resources/pdf/browser_api.js
@@ -28,7 +28,6 @@ function lookupDefaultZoom(streamInfo) {
* Returns a promise that will resolve to the initial zoom factor
* upon starting the plugin. This may differ from the default zoom
* if, for example, the page is zoomed before the plugin is run.
- *
* @param {!Object} streamInfo The stream object pointing to the data contained
* in the PDF.
* @return {Promise<number>} A promise that will resolve to the initial zoom
@@ -46,9 +45,7 @@ function lookupInitialZoom(streamInfo) {
});
}
-/**
- * A class providing an interface to the browser.
- */
+// A class providing an interface to the browser.
export class BrowserApi {
/**
* @param {!Object} streamInfo The stream object which points to the data
@@ -90,7 +87,6 @@ export class BrowserApi {
/**
* Sets the browser zoom.
- *
* @param {number} zoom The zoom factor to send to the browser.
* @return {Promise} A promise that will be resolved when the browser zoom
* has been updated.
@@ -104,23 +100,17 @@ export class BrowserApi {
});
}
- /**
- * @return {number} The default browser zoom factor.
- */
+ /** @return {number} The default browser zoom factor. */
getDefaultZoom() {
return this.defaultZoom_;
}
- /**
- * @return {number} The initial browser zoom factor.
- */
+ /** @return {number} The initial browser zoom factor. */
getInitialZoom() {
return this.initialZoom_;
}
- /**
- * @return {BrowserApi.ZoomBehavior} How to manage zoom.
- */
+ /** @return {BrowserApi.ZoomBehavior} How to manage zoom. */
getZoomBehavior() {
return this.zoomBehavior_;
}
@@ -160,7 +150,6 @@ BrowserApi.ZoomBehavior = {
/**
* Creates a BrowserApi for an extension running as a mime handler.
- *
* @return {!Promise<!BrowserApi>} A promise to a BrowserApi instance
* constructed using the mimeHandlerPrivate API.
*/
@@ -197,7 +186,6 @@ function createBrowserApiForMimeHandlerView() {
/**
* Creates a BrowserApi instance for an extension not running as a mime handler.
- *
* @return {!Promise<!BrowserApi>} A promise to a BrowserApi instance
* constructed from the URL.
*/
diff --git a/chromium/chrome/browser/resources/pdf/constants.js b/chromium/chrome/browser/resources/pdf/constants.js
index 70da3e827d7..475061b73ae 100644
--- a/chromium/chrome/browser/resources/pdf/constants.js
+++ b/chromium/chrome/browser/resources/pdf/constants.js
@@ -21,3 +21,14 @@ export const TwoUpViewAction = {
TWO_UP_VIEW_ENABLE: 'two-up-view-enable',
TWO_UP_VIEW_DISABLE: 'two-up-view-disable',
};
+
+/**
+ * Enumeration of save message request types. Must Match SaveRequestType in
+ * pdf/out_of_process_instance.h.
+ * @enum {number}
+ */
+export const SaveRequestType = {
+ ANNOTATION: 0,
+ ORIGINAL: 1,
+ EDITED: 2,
+};
diff --git a/chromium/chrome/browser/resources/pdf/controller.js b/chromium/chrome/browser/resources/pdf/controller.js
index 9eccd835645..95c02f1fa0d 100644
--- a/chromium/chrome/browser/resources/pdf/controller.js
+++ b/chromium/chrome/browser/resources/pdf/controller.js
@@ -6,8 +6,8 @@ import {assert} from 'chrome://resources/js/assert.m.js';
import {NativeEventTarget as EventTarget} from 'chrome://resources/js/cr/event_target.m.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
import {PromiseResolver} from 'chrome://resources/js/promise_resolver.m.js';
-import {$} from 'chrome://resources/js/util.m.js';
+import {SaveRequestType} from './constants.js';
import {PartialPoint, Point, Viewport} from './viewport.js';
/** @typedef {{ type: string }} */
@@ -45,25 +45,6 @@ let EmailMessageData;
*/
export let PrintPreviewParams;
-// Note: Redefining this type here, to work around the fact that ink externs
-// are only available on Chrome OS, so the targets that contain them cannot be
-// built on other platforms.
-// TODO (rbpotter): Break InkController into its own file that is only included
-// on Chrome OS.
-
-/**
- * @typedef {{
- * setAnnotationTool: function(AnnotationTool):void,
- * viewportChanged: function():void,
- * saveDocument: function():!Promise,
- * undo: function():void,
- * redo: function():void,
- * load: function(string, !ArrayBuffer):!Promise,
- * viewport: !Viewport,
- * }}
- */
-let ViewerInkHostElement;
-
/**
* Creates a cryptographically secure pseudorandom 128-bit token.
* @return {string} The generated token as a hex string.
@@ -105,12 +86,13 @@ export class ContentController {
/**
* 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.
+ * @param {!SaveRequestType} requestType The type of save request. If
+ * ANNOTATION, a response is required, otherwise the controller may save
+ * the document to disk internally.
* @return {Promise<{fileName: string, dataToSave: ArrayBuffer}>}
* @abstract
*/
- save(requireResult) {}
+ save(requestType) {}
/**
* Loads PDF document from `data` activates UI.
@@ -128,110 +110,9 @@ export class ContentController {
unload() {}
}
-/**
- * Controller for annotation mode, on Chrome OS only. Fires the following events
- * from its event target:
- * has-unsaved-changes: Fired to indicate there are ink annotations that have
- * not been saved.
- * set-annotation-undo-state: Contains information about whether undo or redo
- * options are available.
- */
-export class InkController extends ContentController {
- /** @param {!Viewport} viewport */
- constructor(viewport) {
- super();
-
- /** @private {!Viewport} */
- this.viewport_ = viewport;
-
- /** @private {?ViewerInkHostElement} */
- this.inkHost_ = null;
-
- /** @private {!EventTarget} */
- this.eventTarget_ = new EventTarget();
-
- /** @type {?AnnotationTool} */
- this.tool_ = null;
- }
-
- /** @return {!EventTarget} */
- getEventTarget() {
- return this.eventTarget_;
- }
-
- /** @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 */
- setTwoUpView(enableTwoUpView) {
- // TODO(dstockwell): Implement two up view.
- }
-
- /** @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_) {
- const inkHost = document.createElement('viewer-ink-host');
- $('content').appendChild(inkHost);
- this.inkHost_ = /** @type {!ViewerInkHostElement} */ (inkHost);
- this.inkHost_.viewport = this.viewport_;
- inkHost.addEventListener('stroke-added', e => {
- this.eventTarget_.dispatchEvent(new CustomEvent('has-unsaved-changes'));
- });
- inkHost.addEventListener('undo-state-changed', e => {
- this.eventTarget_.dispatchEvent(
- new CustomEvent('set-annotation-undo-state', {detail: e.detail}));
- });
- }
- return this.inkHost_.load(filename, data);
- }
-
- /** @override */
- unload() {
- this.inkHost_.remove();
- this.inkHost_ = null;
- }
-}
-
-/**
- * PDF plugin controller, responsible for communicating with the embedded plugin
- * element. Dispatches a 'plugin-message' event containing the message from the
- * plugin, if a message type not handled by this controller is received.
- */
+// PDF plugin controller, responsible for communicating with the embedded plugin
+// element. Dispatches a 'plugin-message' event containing the message from the
+// plugin, if a message type not handled by this controller is received.
export class PluginController extends ContentController {
/**
* @param {!HTMLEmbedElement} plugin
@@ -407,11 +288,15 @@ export class PluginController extends ContentController {
}
/** @override */
- save(requireResult) {
+ save(requestType) {
const resolver = new PromiseResolver();
const newToken = createToken();
this.pendingTokens_.set(newToken, resolver);
- this.postMessage_({type: 'save', token: newToken, force: requireResult});
+ this.postMessage_({
+ type: 'save',
+ token: newToken,
+ saveRequestType: requestType,
+ });
return resolver.promise;
}
@@ -420,6 +305,7 @@ export class PluginController extends ContentController {
const url = URL.createObjectURL(new Blob([data]));
this.plugin_.removeAttribute('headers');
this.plugin_.setAttribute('stream-url', url);
+ this.plugin_.setAttribute('has-edits', '');
this.plugin_.style.display = 'block';
try {
await this.getLoadedCallback_();
@@ -476,7 +362,6 @@ export class PluginController extends ContentController {
/**
* Handles the pdf file buffer received from the plugin.
- *
* @param {!SaveDataMessageData} messageData data of the message event.
* @private
*/
diff --git a/chromium/chrome/browser/resources/pdf/elements/BUILD.gn b/chromium/chrome/browser/resources/pdf/elements/BUILD.gn
index 1980c0a3421..0eaa9db89f2 100644
--- a/chromium/chrome/browser/resources/pdf/elements/BUILD.gn
+++ b/chromium/chrome/browser/resources/pdf/elements/BUILD.gn
@@ -14,6 +14,7 @@ js_type_check("closure_compile") {
":viewer-page-selector",
":viewer-password-screen",
":viewer-pdf-toolbar",
+ ":viewer-pdf-toolbar-new",
":viewer-toolbar-dropdown",
":viewer-zoom-button",
":viewer-zoom-toolbar",
@@ -67,7 +68,10 @@ js_library("viewer-page-selector") {
}
js_library("viewer-password-screen") {
- deps = [ "//ui/webui/resources/cr_elements/cr_input:cr_input.m" ]
+ deps = [
+ "//ui/webui/resources/cr_elements/cr_input:cr_input.m",
+ "//ui/webui/resources/js:load_time_data.m",
+ ]
}
js_library("viewer-pdf-toolbar") {
@@ -76,10 +80,21 @@ js_library("viewer-pdf-toolbar") {
":viewer-page-selector",
":viewer-toolbar-dropdown",
"..:annotation_tool",
+ "..:constants",
+ "//ui/webui/resources/cr_elements/cr_action_menu:cr_action_menu.m",
+ "//ui/webui/resources/js:assert.m",
+ "//ui/webui/resources/js:load_time_data.m",
+ "//ui/webui/resources/js:promise_resolver.m",
]
externs_list = [ "$externs_path/pending.js" ]
}
+js_library("viewer-pdf-toolbar-new") {
+ deps = [
+ "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+ ]
+}
+
js_library("viewer-pen-options") {
}
@@ -109,6 +124,7 @@ html_to_js("web_components") {
"viewer-page-selector.js",
"viewer-password-screen.js",
"viewer-pdf-toolbar.js",
+ "viewer-pdf-toolbar-new.js",
"viewer-toolbar-dropdown.js",
"viewer-zoom-button.js",
"viewer-zoom-toolbar.js",
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-bookmark.html b/chromium/chrome/browser/resources/pdf/elements/viewer-bookmark.html
index ec39a68550e..8cf60ea1aa7 100644
--- a/chromium/chrome/browser/resources/pdf/elements/viewer-bookmark.html
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-bookmark.html
@@ -47,13 +47,13 @@
<paper-ripple></paper-ripple>
<cr-icon-button id="expand" iron-icon="cr:chevron-right"
on-click="toggleChildren_"></cr-icon-button>
- <span id="title" tabindex="0">{{bookmark.title}}</span>
+ <span id="title" tabindex="0">[[bookmark.title]]</span>
</div>
<!-- dom-if will stamp the complex bookmark tree lazily as individual nodes
are opened. -->
<template is="dom-if" if="[[childrenShown_]]">
<template is="dom-repeat" items="[[bookmark.children]]">
- <viewer-bookmark bookmark="{{item}}" depth="[[childDepth_]]">
+ <viewer-bookmark bookmark="[[item]]" depth="[[childDepth_]]">
</viewer-bookmark>
</template>
</template>
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-error-screen.html b/chromium/chrome/browser/resources/pdf/elements/viewer-error-screen.html
index 03047141d77..e2c210fbd08 100644
--- a/chromium/chrome/browser/resources/pdf/elements/viewer-error-screen.html
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-error-screen.html
@@ -1,10 +1,10 @@
<style include="cr-hidden-style"></style>
<cr-dialog id="dialog" no-cancel>
- <div slot="title">[[strings.errorDialogTitle]]</div>
- <div slot="body">[[strings.pageLoadFailed]]</div>
+ <div slot="title">$i18n{errorDialogTitle}</div>
+ <div slot="body">$i18n{pageLoadFailed}</div>
<div slot="button-container" hidden$="[[!reloadFn]]">
<cr-button class="action-button" on-click="reload">
- [[strings.pageReload]]
+ $i18n{pageReload}
</cr-button>
</div>
</cr-dialog>
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-error-screen.js b/chromium/chrome/browser/resources/pdf/elements/viewer-error-screen.js
index d3383b06e25..d5702f659d4 100644
--- a/chromium/chrome/browser/resources/pdf/elements/viewer-error-screen.js
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-error-screen.js
@@ -15,8 +15,6 @@ Polymer({
properties: {
reloadFn: Function,
-
- strings: Object,
},
show() {
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-form-warning.html b/chromium/chrome/browser/resources/pdf/elements/viewer-form-warning.html
index 1906119a962..61293db59aa 100644
--- a/chromium/chrome/browser/resources/pdf/elements/viewer-form-warning.html
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-form-warning.html
@@ -1,13 +1,13 @@
<style include="cr-hidden-style"></style>
<cr-dialog id="dialog" no-cancel>
- <div slot="title">[[strings.annotationFormWarningTitle]]</div>
- <div slot="body">[[strings.annotationFormWarningDetail]]</div>
+ <div slot="title">$i18n{annotationFormWarningTitle}</div>
+ <div slot="body">$i18n{annotationFormWarningDetail}</div>
<div slot="button-container">
<cr-button class="cancel-button" on-click="onCancel">
- [[strings.annotationFormWarningKeepEditing]]
+ $i18n{annotationFormWarningKeepEditing}
</cr-button>
<cr-button class="action-button" on-click="onAction">
- [[strings.annotationFormWarningDiscard]]
+ $i18n{annotationFormWarningDiscard}
</cr-button>
</div>
</cr-dialog>
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-form-warning.js b/chromium/chrome/browser/resources/pdf/elements/viewer-form-warning.js
index d0cbd6d1de8..900c8d45b1e 100644
--- a/chromium/chrome/browser/resources/pdf/elements/viewer-form-warning.js
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-form-warning.js
@@ -14,10 +14,6 @@ Polymer({
_template: html`{__html_template__}`,
- properties: {
- strings: Object,
- },
-
/** @private {PromiseResolver} */
resolver_: null,
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-ink-host.js b/chromium/chrome/browser/resources/pdf/elements/viewer-ink-host.js
index fa208811e90..6c8b184407f 100644
--- a/chromium/chrome/browser/resources/pdf/elements/viewer-ink-host.js
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-ink-host.js
@@ -3,8 +3,9 @@
// found in the LICENSE file.
import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
import {PDFMetrics} from '../metrics.js';
-import {Viewport} from '../viewport.js';
+import {PAGE_SHADOW, Viewport} from '../viewport.js';
/** @enum {string} */
const State = {
@@ -62,7 +63,6 @@ Polymer({
/**
* Whether we should suppress pointer events due to a gesture,
* eg. pinch-zoom.
- *
* @private {boolean}
*/
pointerGesture_: false,
@@ -96,19 +96,10 @@ Polymer({
/**
* Dispatches a pointer event to Ink.
- *
* @param {PointerEvent} e
*/
dispatchPointerEvent_(e) {
- // TODO(dstockwell) come up with a solution to propagate e.timeStamp.
- this.ink_.dispatchPointerEvent(e.type, {
- pointerId: e.pointerId,
- pointerType: e.pointerType,
- clientX: e.clientX,
- clientY: e.clientY,
- pressure: e.pressure,
- buttons: e.buttons,
- });
+ this.ink_.dispatchPointerEvent(e);
},
/** @param {TouchEvent} e */
@@ -135,10 +126,10 @@ Polymer({
// A multi-touch gesture has started with the active pointer. Cancel
// the active pointer and suppress further events until it is released.
this.pointerGesture_ = true;
- this.ink_.dispatchPointerEvent('pointercancel', {
+ this.ink_.dispatchPointerEvent(new PointerEvent('pointercancel', {
pointerId: this.activePointer_.pointerId,
pointerType: this.activePointer_.pointerType,
- });
+ }));
}
return;
}
@@ -241,7 +232,7 @@ Polymer({
// color.
await new Promise(resolve => setTimeout(resolve));
this.ink_.setOutOfBoundsColor(BACKGROUND_COLOR);
- const spacing = Viewport.PAGE_SHADOW.top + Viewport.PAGE_SHADOW.bottom;
+ const spacing = PAGE_SHADOW.top + PAGE_SHADOW.bottom;
this.ink_.setPageSpacing(spacing);
this.style.visibility = 'visible';
},
@@ -256,8 +247,8 @@ Polymer({
const zoom = viewport.getZoom();
const documentWidth = viewport.getDocumentDimensions().width * zoom;
// Adjust for page shadows.
- const y = pos.y - Viewport.PAGE_SHADOW.top * zoom;
- let x = pos.x - Viewport.PAGE_SHADOW.left * zoom;
+ const y = pos.y - PAGE_SHADOW.top * zoom;
+ let x = pos.x - PAGE_SHADOW.left * zoom;
// Center the document if the width is smaller than the viewport.
if (documentWidth < size.width) {
x += (documentWidth - size.width) / 2;
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-page-indicator.html b/chromium/chrome/browser/resources/pdf/elements/viewer-page-indicator.html
index 966a10c3661..5504e761500 100644
--- a/chromium/chrome/browser/resources/pdf/elements/viewer-page-indicator.html
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-page-indicator.html
@@ -34,5 +34,5 @@
width: 0;
}
</style>
- <div id="text">{{label}}</div>
+ <div id="text">[[label]]</div>
<div id="triangle-end"></div>
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-page-selector.html b/chromium/chrome/browser/resources/pdf/elements/viewer-page-selector.html
index 1402b838971..002a23ad3fc 100644
--- a/chromium/chrome/browser/resources/pdf/elements/viewer-page-selector.html
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-page-selector.html
@@ -3,6 +3,8 @@
color: #fff;
display: flex;
font-size: 0.81rem;
+
+ --page-selector-spacing: 4px;
}
:host ::selection {
@@ -11,12 +13,10 @@
#pageselector::part(input),
#pagelength {
- box-sizing: content-box;
- padding: 0 3px;
/* --page-length-digits is set through JavaScript. 1px is added because
* the unit 'ch' does not provide exact whole number pixels, and
* therefore seems to have 1px-off bugginess. */
- width: calc(var(--page-length-digits) * 1ch + 1px);
+ width: calc(max(2, var(--page-length-digits)) * 1ch + 1px);
}
#pageselector {
@@ -29,13 +29,18 @@
#pageselector::part(input) {
background: rgba(0, 0, 0, 0.5);
+ box-sizing: content-box;
caret-color: var(--cr-input-color);
- text-align: end;
+ padding: 0 var(--page-selector-spacing);
+ }
+
+ #divider {
+ margin: 0 var(--page-selector-spacing);
}
</style>
<cr-input id="pageselector" value="[[pageNo]]" on-mouseup="select"
on-value-changed="onInputValueChange_" on-change="pageNoCommitted"
- aria-label$="{{strings.labelPageNumber}}">
+ aria-label$="$i18n{labelPageNumber}">
</cr-input>
- /
- <span id="pagelength">{{docLength}}</span>
+ <span id="divider">/</span>
+ <span id="pagelength">[[docLength]]</span>
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-page-selector.js b/chromium/chrome/browser/resources/pdf/elements/viewer-page-selector.js
index 5eda31ee514..6b8b55ad3dc 100644
--- a/chromium/chrome/browser/resources/pdf/elements/viewer-page-selector.js
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-page-selector.js
@@ -26,8 +26,6 @@ Polymer({
type: Number,
value: 1,
},
-
- strings: Object,
},
/** @return {!CrInputElement} */
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-password-screen.html b/chromium/chrome/browser/resources/pdf/elements/viewer-password-screen.html
index 43c0249bffa..b61cdb5434a 100644
--- a/chromium/chrome/browser/resources/pdf/elements/viewer-password-screen.html
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-password-screen.html
@@ -4,20 +4,19 @@
}
</style>
<cr-dialog id="dialog" no-cancel>
- <div slot="title">[[strings.passwordDialogTitle]]</div>
+ <div slot="title">$i18n{passwordDialogTitle}</div>
<div slot="body">
- <div id="message">[[strings.passwordPrompt]]</div>
+ <div id="message">$i18n{passwordPrompt}</div>
<cr-input id="password"
type="password"
- error-message="[[getErrorMessage_(strings.passwordInvalid,
- invalid)]]"
+ error-message="[[getErrorMessage_(invalid)]]"
invalid="[[invalid]]"
autofocus>
</cr-input>
</div>
<div slot="button-container">
<cr-button id="submit" class="action-button" on-click="submit">
- [[strings.passwordSubmit]]
+ $i18n{passwordSubmit}
</cr-button>
</div>
</cr-dialog>
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-password-screen.js b/chromium/chrome/browser/resources/pdf/elements/viewer-password-screen.js
index 79cd849c56b..7bc4507d700 100644
--- a/chromium/chrome/browser/resources/pdf/elements/viewer-password-screen.js
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-password-screen.js
@@ -8,6 +8,7 @@ import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
import 'chrome://resources/cr_elements/shared_style_css.m.js';
import 'chrome://resources/cr_elements/shared_vars_css.m.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
Polymer({
@@ -17,8 +18,6 @@ Polymer({
properties: {
invalid: Boolean,
-
- strings: Object,
},
get active() {
@@ -57,10 +56,9 @@ Polymer({
* Returns |message| if input is invalid, otherwise empty string.
* This avoids setting the error message (which announces to screen readers)
* when there is no error.
- * @param {string} message
* @return {string}
*/
- getErrorMessage_(message) {
- return this.invalid ? message : '';
+ getErrorMessage_() {
+ return this.invalid ? loadTimeData.getString('passwordInvalid') : '';
}
});
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar-new.html b/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar-new.html
new file mode 100644
index 00000000000..075635defbb
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar-new.html
@@ -0,0 +1,10 @@
+<style>
+ :host {
+ --pdf-toolbar-background-color: rgb(50, 54, 57);
+ background-color: var(--pdf-toolbar-background-color);
+ box-shadow: var(--cr-elevation-2);
+ color: white;
+ height: 48px;
+ }
+</style>
+New PDF Viewer toolbar will appear here.
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar-new.js b/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar-new.js
new file mode 100644
index 00000000000..06e1d118df0
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar-new.js
@@ -0,0 +1,17 @@
+// Copyright 2020 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 {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+class ViewerPdfToolbarNewElement extends PolymerElement {
+ static get is() {
+ return 'viewer-pdf-toolbar-new';
+ }
+
+ static get template() {
+ return html`{__html_template__}`;
+ }
+}
+customElements.define(
+ ViewerPdfToolbarNewElement.is, ViewerPdfToolbarNewElement);
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar.html b/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar.html
index 6a8daca8ea7..51c7bc66cb5 100644
--- a/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar.html
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar.html
@@ -1,4 +1,9 @@
<style include="cr-hidden-style">
+ :host {
+ --pdf-toolbar-background-color: rgb(50, 54, 57);
+ --pdf-toolbar-text-color: rgb(241, 241, 241);
+ }
+
:host ::selection {
background: rgba(255, 255, 255, 0.3);
}
@@ -36,7 +41,7 @@
}
cr-icon-button {
- --cr-icon-button-fill-color: rgb(241, 241, 241);
+ --cr-icon-button-fill-color: var(--pdf-toolbar-text-color);
margin: 6px;
}
@@ -54,8 +59,8 @@
}
#toolbar {
- @apply --shadow-elevation-2dp;
- background-color: rgb(50, 54, 57);
+ background-color: var(--pdf-toolbar-background-color);
+ box-shadow: var(--cr-elevation-2);
position: relative;
}
@@ -67,7 +72,7 @@
#toolbar,
#annotations-bar {
- color: rgb(241, 241, 241);
+ color: var(--pdf-toolbar-text-color);
display: flex;
height: 48px;
padding: 0 16px;
@@ -90,7 +95,7 @@
#pen,
#highlighter {
- --dropdown-open-background: rgb(50, 54, 57);
+ --dropdown-open-background: var(--pdf-viewer-background-color);
}
#eraser {
@@ -138,10 +143,6 @@
#rotate-left {
display: none;
}
-
- #pageselector-container {
- flex: 2;
- }
}
@media(max-width: 450px) {
@@ -156,40 +157,55 @@
display: none;
}
}
+
+ cr-action-menu::part(dialog) {
+ position: fixed;
+ top: 48px;
+ }
</style>
<div id="toolbar">
<div id="aligner">
- <span id="title" title="{{docTitle}}">
- <span>{{docTitle}}</span>
+ <span id="title" title="[[docTitle]]">
+ <span>[[docTitle]]</span>
</span>
<div id="pageselector-container">
<viewer-page-selector id="pageselector" class="invisible"
- doc-length="{{docLength}}" page-no="{{pageNo}}"
- strings="{{strings}}">
+ doc-length="[[docLength]]" page-no="[[pageNo]]">
</viewer-page-selector>
</div>
<div id="buttons" class="invisible">
- <template is="dom-if" if="[[pdfAnnotationsEnabled]]">
+<if expr="chromeos">
+ <template is="dom-if" if="[[pdfAnnotationsEnabled_]]">
<cr-icon-button id="annotate" iron-icon="pdf:create"
disabled="[[!annotationAvailable]]" on-click="toggleAnnotation"
- aria-label$="{{strings.tooltipAnnotate}}"
- title$="{{strings.tooltipAnnotate}}"></cr-icon-button>
+ aria-label$="$i18n{tooltipAnnotate}"
+ title="$i18n{tooltipAnnotate}"></cr-icon-button>
</template>
+</if>
<cr-icon-button id="rotate-right" iron-icon="pdf:rotate-right"
disabled="[[annotationMode]]" on-click="rotateRight"
- aria-label$="{{strings.tooltipRotateCW}}"
- title$="{{strings.tooltipRotateCW}}"></cr-icon-button>
+ aria-label$="$i18n{tooltipRotateCW}"
+ title="$i18n{tooltipRotateCW}"></cr-icon-button>
<cr-icon-button id="download" iron-icon="cr:file-download"
- on-click="download" aria-label$="{{strings.tooltipDownload}}"
- title$="{{strings.tooltipDownload}}"></cr-icon-button>
+ on-click="onDownloadClick_" aria-label$="$i18n{tooltipDownload}"
+ aria-haspopup$="[[downloadHasPopup_]]"
+ title="$i18n{tooltipDownload}"></cr-icon-button>
+ <cr-action-menu id="downloadMenu">
+ <button class="dropdown-item" on-click="onDownloadEditedClick_">
+ $i18n{downloadEdited}
+ </button>
+ <button class="dropdown-item" on-click="onDownloadOriginalClick_">
+ $i18n{downloadOriginal}
+ </button>
+ </cr-action-menu>
<cr-icon-button id="print" iron-icon="cr:print" on-click="print"
- hidden="[[!printingEnabled]]" title$="{{strings.tooltipPrint}}"
- aria-label$="{{strings.tooltipPrint}}"></cr-icon-button>
+ hidden="[[!printingEnabled_]]" title="$i18n{tooltipPrint}"
+ aria-label$="$i18n{tooltipPrint}"></cr-icon-button>
<viewer-toolbar-dropdown id="bookmarks"
selected
@@ -197,7 +213,7 @@
hidden$="[[!bookmarks.length]]"
open-icon="pdf:bookmark"
closed-icon="pdf:bookmark-border"
- header="{{strings.bookmarks}}">
+ header="$i18n{bookmarks}">
<template is="dom-repeat" items="[[bookmarks]]">
<viewer-bookmark bookmark="[[item]]" depth="0"></viewer-bookmark>
</template>
@@ -211,6 +227,7 @@
</div>
</div>
+<if expr="chromeos">
<div id="annotations-bar" hidden>
<viewer-toolbar-dropdown id="pen"
selected$="[[isAnnotationTool_('pen', annotationTool.tool)]]"
@@ -220,7 +237,7 @@
closed-icon="pdf:marker"
dropdown-centered
hide-header
- header$="{{strings.annotationPen}}"
+ header="$i18n{annotationPen}"
style="--pen-tip-fill: #000000">
<viewer-pen-options
selected-color="#000000"
@@ -239,7 +256,7 @@
closed-icon="pdf:highlighter"
dropdown-centered
hide-header
- header$="{{strings.annotationHighlighter}}"
+ header="$i18n{annotationHighlighter}"
style="--pen-tip-fill: #ffbc00">
<viewer-pen-options
selected-color="#ffbc00"
@@ -253,18 +270,19 @@
<cr-icon-button id="eraser"
selected$="[[isAnnotationTool_('eraser', annotationTool.tool)]]"
on-click="annotationToolClicked_" iron-icon="pdf:eraser"
- aria-label$="{{strings.annotationEraser}}"
- title$="{{strings.annotationEraser}}"></cr-icon-button>
+ aria-label$="$i18n{annotationEraser}"
+ title="$i18n{annotationEraser}"></cr-icon-button>
<div id="annotation-separator"></div>
<cr-icon-button id="undo" disabled="[[!canUndoAnnotation]]"
iron-icon="pdf:undo" on-click="undo"
- aria-label$="{{strings.annotationUndo}}"
- title$="{{strings.annotationUndo}}"></cr-icon-button>
+ aria-label$="$i18n{annotationUndo}"
+ title="$i18n{annotationUndo}"></cr-icon-button>
<cr-icon-button id="redo" disabled="[[!canRedoAnnotation]]"
iron-icon="pdf:redo" on-click="redo"
- aria-label$="{{strings.annotationRedo}}"
- title$="{{strings.annotationRedo}}"></cr-icon-button>
+ aria-label$="$i18n{annotationRedo}"
+ title="$i18n{annotationRedo}"></cr-icon-button>
</div>
+</if>
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar.js b/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar.js
index 9fb821a0ac4..defc20f57bb 100644
--- a/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar.js
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar.js
@@ -10,13 +10,19 @@ import './icons.js';
import './viewer-bookmark.js';
import './viewer-page-selector.js';
import './viewer-toolbar-dropdown.js';
-
// <if expr="chromeos">
import './viewer-pen-options.js';
+
// </if>
+import {AnchorAlignment} from 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.m.js';
+import {assert} from 'chrome://resources/js/assert.m.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
+import {PromiseResolver} from 'chrome://resources/js/promise_resolver.m.js';
import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
import {Bookmark} from '../bookmark_type.js';
+import {SaveRequestType} from '../constants.js';
Polymer({
is: 'viewer-pdf-toolbar',
@@ -29,10 +35,7 @@ Polymer({
* example the PDF is encrypted or password protected. Note, this is
* true regardless of whether the feature flag is enabled.
*/
- annotationAvailable: {
- type: Boolean,
- value: true,
- },
+ annotationAvailable: Boolean,
/** Whether the viewer is currently in annotation mode. */
annotationMode: {
@@ -45,7 +48,6 @@ Polymer({
/** @type {?AnnotationTool} */
annotationTool: {
type: Object,
- value: null,
notify: true,
},
@@ -74,6 +76,15 @@ Polymer({
/** The title of the PDF document. */
docTitle: String,
+ hasEdits: Boolean,
+
+ hasEnteredAnnotationMode: Boolean,
+
+ isFormFieldFocused: {
+ type: Boolean,
+ observer: 'onFormFieldFocusedChanged_',
+ },
+
/** The current loading progress of the PDF document (0 - 100). */
loadProgress: {
type: Number,
@@ -89,24 +100,52 @@ Polymer({
/** The number of the page being viewed (1-based). */
pageNo: Number,
- /** Whether the PDF Annotations feature is enabled. */
- pdfAnnotationsEnabled: {
+ /**
+ * Whether the PDF Annotations feature is enabled.
+ * @private
+ */
+ pdfAnnotationsEnabled_: {
type: Boolean,
value: false,
},
- /** Whether the Printing feature is enabled. */
- printingEnabled: {
+ /**
+ * Whether the Printing feature is enabled.
+ * @private
+ */
+ printingEnabled_: {
type: Boolean,
value: false,
},
- strings: Object,
+ /** @private */
+ downloadHasPopup_: {
+ type: String,
+ computed: 'computeDownloadHasPopup_(' +
+ 'pdfFormSaveEnabled_, hasEdits, hasEnteredAnnotationMode)',
+ },
+
+ strings: {
+ type: Object,
+ observer: 'onStringsSet_',
+ },
+
+ /**
+ * Whether the PDF Form save feature is enabled.
+ * @private
+ */
+ pdfFormSaveEnabled_: {
+ type: Boolean,
+ value: false,
+ },
},
/** @type {?Object} */
animation_: null,
+ /** @private {?PromiseResolver<boolean>} */
+ waitForFormFocusChange_: null,
+
/**
* @param {number} newProgress
* @param {number} oldProgress
@@ -119,7 +158,9 @@ Polymer({
this.$.pageselector.classList.toggle('invisible', !loaded);
this.$.buttons.classList.toggle('invisible', !loaded);
this.$.progress.style.opacity = loaded ? 0 : 1;
+ // <if expr="chromeos">
this.$['annotations-bar'].hidden = !loaded || !this.annotationMode;
+ // </if>
}
},
@@ -168,7 +209,8 @@ Polymer({
/** @return {boolean} Whether the toolbar should be kept open. */
shouldKeepOpen() {
return this.$.bookmarks.dropdownOpen || this.loadProgress < 100 ||
- this.$.pageselector.isActive() || this.annotationMode;
+ this.$.pageselector.isActive() || this.annotationMode ||
+ this.$.downloadMenu.open;
},
/** @return {boolean} Whether a dropdown was open and was hidden. */
@@ -178,6 +220,7 @@ Polymer({
this.$.bookmarks.toggleDropdown();
result = true;
}
+ // <if expr="chromeos">
if (this.$.pen.dropdownOpen) {
this.$.pen.toggleDropdown();
result = true;
@@ -186,6 +229,7 @@ Polymer({
this.$.highlighter.toggleDropdown();
result = true;
}
+ // </if>
return result;
},
@@ -198,8 +242,67 @@ Polymer({
this.fire('rotate-right');
},
- download() {
- this.fire('save');
+ /** @private */
+ showDownloadMenu_() {
+ this.$.downloadMenu.showAt(this.$.download, {
+ anchorAlignmentX: AnchorAlignment.CENTER,
+ anchorAlignmentY: AnchorAlignment.AFTER_END,
+ noOffset: true,
+ });
+ // For tests
+ this.fire('download-menu-shown-for-testing');
+ },
+
+ /** @private */
+ onDownloadClick_() {
+ this.waitForEdits_().then(hasEdits => {
+ if (hasEdits) {
+ this.showDownloadMenu_();
+ } else {
+ this.fire('save', SaveRequestType.ORIGINAL);
+ }
+ });
+ },
+
+ /**
+ * @return {!Promise<boolean>} Promise that resolves with true if the PDF has
+ * edits and/or annotations, and false otherwise.
+ * @private
+ */
+ waitForEdits_() {
+ if (this.hasEditsToSave_()) {
+ return Promise.resolve(true);
+ }
+ if (!this.isFormFieldFocused || !this.pdfFormSaveEnabled_) {
+ return Promise.resolve(false);
+ }
+ this.waitForFormFocusChange_ = new PromiseResolver();
+ return this.waitForFormFocusChange_.promise;
+ },
+
+ /** @private */
+ onFormFieldFocusedChanged_() {
+ if (!this.waitForFormFocusChange_) {
+ return;
+ }
+
+ this.waitForFormFocusChange_.resolve(this.hasEdits);
+ this.waitForFormFocusChange_ = null;
+ },
+
+ /** @private */
+ onDownloadOriginalClick_() {
+ this.fire('save', SaveRequestType.ORIGINAL);
+ this.$.downloadMenu.close();
+ },
+
+ /** @private */
+ onDownloadEditedClick_() {
+ this.fire(
+ 'save',
+ this.hasEnteredAnnotationMode ? SaveRequestType.ANNOTATION :
+ SaveRequestType.EDITED);
+ this.$.downloadMenu.close();
},
print() {
@@ -255,7 +358,7 @@ Polymer({
const tool = element.id;
const options = element.querySelector('viewer-pen-options') || {
selectedSize: 1,
- selectedColor: null,
+ selectedColor: undefined,
};
const attributeStyleMap = element.attributeStyleMap;
attributeStyleMap.set('--pen-tip-fill', options.selectedColor);
@@ -278,4 +381,32 @@ Polymer({
isAnnotationTool_(toolName) {
return !!this.annotationTool && this.annotationTool.tool === toolName;
},
+
+ /**
+ * @return {boolean}
+ * @private
+ */
+ hasEditsToSave_() {
+ return this.hasEnteredAnnotationMode ||
+ (this.pdfFormSaveEnabled_ && this.hasEdits);
+ },
+
+ /**
+ * @return {string} The value for the aria-haspopup attribute for the download
+ * button.
+ * @private
+ */
+ computeDownloadHasPopup_() {
+ return this.hasEditsToSave_() ? 'menu' : 'false';
+ },
+
+ /** @private */
+ onStringsSet_() {
+ assert(this.strings);
+
+ this.pdfAnnotationsEnabled_ =
+ loadTimeData.getBoolean('pdfAnnotationsEnabled');
+ this.pdfFormSaveEnabled_ = loadTimeData.getBoolean('pdfFormSaveEnabled');
+ this.printingEnabled_ = loadTimeData.getBoolean('printingEnabled');
+ },
});
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-pen-options.html b/chromium/chrome/browser/resources/pdf/elements/viewer-pen-options.html
index b44bf89f9c9..e98c1c20513 100644
--- a/chromium/chrome/browser/resources/pdf/elements/viewer-pen-options.html
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-pen-options.html
@@ -65,8 +65,8 @@
on-pointerdown="blurOnPointerDown">
</template>
<cr-icon-button id="expand" iron-icon="cr:expand-more" tabindex="3"
- on-click="toggleExpanded_" aria-label$="[[strings.annotationExpand]]"
- title$="[[strings.annotationExpand]]"></cr-icon-button>
+ on-click="toggleExpanded_" aria-label$="$i18n{annotationExpand}"
+ title$="$i18n{annotationExpand}"></cr-icon-button>
</div>
<div id="separator"></div>
<div id="sizes" on-change="sizeChanged_">
@@ -74,7 +74,7 @@
<input type="radio" name="size" value="[[item.size]]"
checked$="[[equal_(selectedSize, item.size)]]"
tabindex="2" style="--item-size: [[item.size]]"
- title$="{{lookup_(strings, item.name)}}"
+ title="[[lookup_(strings, item.name)]]"
aria-label$="[[lookup_(strings, item.name)]]"
on-pointerdown="blurOnPointerDown">
</template>
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-pen-options.js b/chromium/chrome/browser/resources/pdf/elements/viewer-pen-options.js
index 0cc12c94384..fd7e6e8661d 100644
--- a/chromium/chrome/browser/resources/pdf/elements/viewer-pen-options.js
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-pen-options.js
@@ -52,50 +52,62 @@ const sizes = [
{name: 'annotationSize20', size: 1},
];
-/**
- * Displays a set of radio buttons to select from
- * a predefined list of colors and sizes.
- */
+// Displays a set of radio buttons to select from a predefined list of colors
+// and sizes.
Polymer({
is: 'viewer-pen-options',
_template: html`{__html_template__}`,
properties: {
+ /** @private */
expanded_: {
type: Boolean,
value: false,
},
+
selectedSize: {
type: Number,
value: 0.250,
notify: true,
},
+
selectedColor: {
type: String,
value: '#000000',
notify: true,
},
+
+ /** @private */
sizes_: {
type: Array,
value: sizes,
},
+
+ /** @private */
colors_: {
type: Array,
value: colors,
},
+
strings: Object,
},
- /** @type {Array<!Animation>} */
+ /** @private {Array<!Animation>} */
expandAnimations_: null,
- /** @param {Event} e */
+ /**
+ * @param {!Event} e
+ * @private
+ */
sizeChanged_(e) {
this.selectedSize = Number(e.target.value);
},
- /** @param {Event} e */
+ /**
+ * @param {!Event} e
+ * @private
+ */
colorChanged_(e) {
this.selectedColor = e.target.value;
},
@@ -125,6 +137,7 @@ Polymer({
/**
* Updates the state of the UI to reflect the current value of `expanded`.
* Starts or reverses animations and enables/disable controls.
+ * @private
*/
updateExpandedState_() {
const colors = this.$.colors;
@@ -176,19 +189,16 @@ Polymer({
animation.play();
}
for (const input of colors.querySelectorAll('input:nth-child(n+8)')) {
- if (this.expanded_) {
- input.removeAttribute('disabled');
- } else {
- input.setAttribute('disabled', '');
- }
+ input.toggleAttribute('disabled', !this.expanded_);
}
},
/**
* Used to determine equality in computed bindings.
- *
* @param {*} a
* @param {*} b
+ * @return {boolean} Whether a === b
+ * @private
*/
equal_(a, b) {
return a === b;
@@ -196,10 +206,10 @@ Polymer({
/**
* Used to lookup a string in a computed binding.
- *
* @param {Object} strings
* @param {string} name
* @return {string}
+ * @private
*/
lookup_(strings, name) {
return strings ? strings[name] : '';
@@ -209,6 +219,7 @@ Polymer({
* Used to remove focus when clicking or tapping on a styled input
* element. This is a workaround until we can use the :focus-visible
* pseudo selector.
+ * @param {!Event} e
*/
blurOnPointerDown(e) {
const target = e.target;
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown.html b/chromium/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown.html
index 8c6e7640bde..ff49695f629 100644
--- a/chromium/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown.html
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown.html
@@ -27,23 +27,17 @@
#dropdown {
background-color: var(--cr-menu-background-color);
border-radius: 4px;
+ box-shadow: var(--cr-menu-shadow);
color: var(--cr-primary-text-color);
overflow-y: hidden;
padding-bottom: 2px;
width: var(--dropdown-width);
}
- @media (prefers-color-scheme: light) {
- #dropdown {
- @apply --shadow-elevation-2dp;
- }
- }
-
@media (prefers-color-scheme: dark) {
#dropdown {
background-image: linear-gradient(var(--cr-menu-background-sheen),
var(--cr-menu-background-sheen));
- box-shadow: var(--cr-menu-shadow);
}
}
@@ -89,7 +83,7 @@
<div id="container">
<div id="dropdown" style="display: none">
<template is="dom-if" if="[[!hideHeader]]">
- <h1>{{header}}</h1>
+ <h1>[[header]]</h1>
</template>
<div id="scroll-container">
<slot></slot>
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-button.html b/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-button.html
index f51075c5cab..ad97fce8a23 100644
--- a/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-button.html
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-button.html
@@ -35,11 +35,10 @@
--cr-icon-button-fill-color: var(--paper-grey-700);
--cr-icon-button-icon-size: 20px;
--cr-icon-button-size: 36px;
- --cr-icon-button-fill-color-focus: rgb(242, 242, 242);
background-color: rgb(242, 242, 242);
border-radius: 50%;
+ box-shadow: var(--cr-elevation-1);
overflow: visible;
- @apply --shadow-elevation-2dp;
}
cr-icon-button([disabled]) {
@@ -48,7 +47,6 @@
:host-context([is-print-preview]) cr-icon-button {
--cr-icon-button-fill-color: white;
- --cr-icon-button-fill-color-focus: var(--google-grey-600);
--cr-icon-button-size: 32px;
background-color: var(--google-grey-600);
}
@@ -62,17 +60,16 @@
@media (prefers-color-scheme: dark) {
:host-context([is-print-preview]) cr-icon-button {
--cr-icon-button-fill-color: var(--google-grey-200);
- --cr-icon-button-fill-color-focus: var(--google-grey-900);
background-color: var(--google-grey-900);
}
}
:host([keyboard-navigation-active]) cr-icon-button:focus {
- @apply --shadow-elevation-6dp;
+ box-shadow: var(--cr-elevation-4);
}
cr-icon-button:active {
- @apply --shadow-elevation-8dp;
+ box-shadow: var(--cr-elevation-5);
}
</style>
<div id="wrapper">
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar.html b/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar.html
index ab6a4d461ca..341f6bbf4c7 100644
--- a/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar.html
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar.html
@@ -67,7 +67,7 @@
<!-- TODO(crbug.com/51472): Change icons for two-up-view-button -->
<!-- once they are finalized. -->
<viewer-zoom-button id="two-up-view-button" delay="100"
- disabled="[[annotationMode]]" hidden$="[[!twoUpViewEnabled]]"
+ disabled="[[annotationMode]]" hidden$="[[!twoUpViewEnabled_]]"
on-fabclick="twoUpViewToggle_"
keyboard-navigation-active="[[keyboardNavigationActive_]]"
icons="pdf:create pdf:eraser"></viewer-zoom-button>
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar.js b/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar.js
index d58712f7562..f3bab8c69e1 100644
--- a/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar.js
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar.js
@@ -8,7 +8,9 @@ import './icons.js';
import './viewer-zoom-button.js';
import {assert} from 'chrome://resources/js/assert.m.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
import {FittingType, TwoUpViewAction} from '../constants.js';
/**
@@ -32,19 +34,25 @@ Polymer({
properties: {
/** Whether the viewer is currently in annotation mode. */
- annotationMode: {
+ annotationMode: Boolean,
+
+ isPrintPreview: {
type: Boolean,
value: false,
},
- isPrintPreview: Boolean,
+ strings: {
+ type: Object,
+ observer: 'onStringsSet_',
+ },
- twoUpViewEnabled: Boolean,
+ /** @private */
+ twoUpViewEnabled_: Boolean,
/** @private */
fitButtonDelay_: {
type: Number,
- computed: 'computeFitButtonDelay_(twoUpViewEnabled)',
+ computed: 'computeFitButtonDelay_(twoUpViewEnabled_)',
},
/** @private */
@@ -55,12 +63,11 @@ Polymer({
},
/**
- * @param {boolean} twoUpViewEnabled Whether two-up view button is enabled.
- * @return {number} Delay for :qthe fit button.
+ * @return {number} Delay for the fit button.
* @private
*/
- computeFitButtonDelay_(twoUpViewEnabled) {
- return twoUpViewEnabled ? 150 : 100;
+ computeFitButtonDelay_() {
+ return this.twoUpViewEnabled_ ? 150 : 100;
},
listeners: {
@@ -106,20 +113,27 @@ Polymer({
/**
* Change button tooltips to match any changes to localized strings.
- * @param {!{tooltipFitToPage: string,
- * tooltipFitToWidth: string,
- * tooltipTwoUpViewEnable: string,
- * tooltipTwoUpViewDisable: string,
- * tooltipZoomIn: string,
- * tooltipZoomOut: string}} strings
+ * @private
*/
- setStrings(strings) {
+ onStringsSet_() {
+ const strings =
+ /**
+ * @type {{tooltipFitToPage: string,
+ * tooltipFitToWidth: string,
+ * tooltipTwoUpViewEnable: string,
+ * tooltipTwoUpViewDisable: string,
+ * tooltipZoomIn: string,
+ * tooltipZoomOut: string}}
+ */
+ (this.strings);
this.$['fit-button'].tooltips =
[strings.tooltipFitToPage, strings.tooltipFitToWidth];
this.$['two-up-view-button'].tooltips =
[strings.tooltipTwoUpViewEnable, strings.tooltipTwoUpViewDisable];
this.$['zoom-in-button'].tooltips = [strings.tooltipZoomIn];
this.$['zoom-out-button'].tooltips = [strings.tooltipZoomOut];
+ this.twoUpViewEnabled_ =
+ loadTimeData.getBoolean('pdfTwoUpViewEnabled') && !this.isPrintPreview;
},
/** Handle clicks of the fit-button. */
@@ -175,7 +189,7 @@ Polymer({
* @private
*/
twoUpViewToggle_: function() {
- assert(this.twoUpViewEnabled);
+ assert(this.twoUpViewEnabled_);
const twoUpViewAction = this.$['two-up-view-button'].activeIndex ===
TWO_UP_VIEW_DISABLED_STATE ?
TwoUpViewAction.TWO_UP_VIEW_ENABLE :
@@ -184,16 +198,12 @@ Polymer({
this.fire('two-up-view-changed', twoUpViewAction);
},
- /**
- * Handle clicks of the zoom-in-button.
- */
+ /** Handle clicks of the zoom-in-button. */
zoomIn() {
this.fire('zoom-in');
},
- /**
- * Handle clicks of the zoom-out-button.
- */
+ /** Handle clicks of the zoom-out-button. */
zoomOut() {
this.fire('zoom-out');
},
diff --git a/chromium/chrome/browser/resources/pdf/gesture_detector.js b/chromium/chrome/browser/resources/pdf/gesture_detector.js
index e8a0db4e6a3..6755d76b9b0 100644
--- a/chromium/chrome/browser/resources/pdf/gesture_detector.js
+++ b/chromium/chrome/browser/resources/pdf/gesture_detector.js
@@ -2,34 +2,43 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import {NativeEventTarget as EventTarget} from 'chrome://resources/js/cr/event_target.m.js';
+
/**
- * A class that listens for touch events and produces events when these
- * touches form gestures (e.g. pinching).
+ * @typedef {{
+ * center: !{x: number, y: number},
+ * direction: (string|undefined),
+ * scaleRatio: (?number|undefined),
+ * startScaleRatio: (?number|undefined),
+ * }}
*/
+export let PinchEventDetail;
+
+// A class that listens for touch events and produces events when these
+// touches form gestures (e.g. pinching).
export class GestureDetector {
/**
- * @param {!Element} element The element to monitor for touch gestures.
+ * @param {!EventTarget|!Element} element The element to monitor for touch
+ * gestures.
*/
constructor(element) {
- /** @private {!Element} */
- this.element_ = element;
-
- this.element_.addEventListener(
+ element.addEventListener(
'touchstart',
/** @type {function(!Event)} */ (this.onTouchStart_.bind(this)),
{passive: true});
const boundOnTouch =
/** @type {function(!Event)} */ (this.onTouch_.bind(this));
- this.element_.addEventListener('touchmove', boundOnTouch, {passive: true});
- this.element_.addEventListener('touchend', boundOnTouch, {passive: true});
- this.element_.addEventListener(
- 'touchcancel', boundOnTouch, {passive: true});
+ element.addEventListener('touchmove', boundOnTouch, {passive: true});
+ element.addEventListener('touchend', boundOnTouch, {passive: true});
+ element.addEventListener('touchcancel', boundOnTouch, {passive: true});
- this.element_.addEventListener(
+ element.addEventListener(
'wheel',
/** @type {function(!Event)} */ (this.onWheel_.bind(this)),
{passive: false});
+ document.addEventListener(
+ 'contextmenu', e => this.handleContextMenuEvent_(e));
this.pinchStartEvent_ = null;
this.lastTouchTouchesCount_ = 0;
@@ -52,24 +61,17 @@ export class GestureDetector {
*/
this.wheelEndTimeout_ = null;
- /** @private {!Map<string, !Array<!Function>>} */
- this.listeners_ =
- new Map([['pinchstart', []], ['pinchupdate', []], ['pinchend', []]]);
+ /** @private {!EventTarget} */
+ this.eventTarget_ = new EventTarget();
}
- /**
- * Add a |listener| to be notified of |type| events.
- *
- * @param {string} type The event type to be notified for.
- * @param {!Function} listener The callback.
- */
- addEventListener(type, listener) {
- if (this.listeners_.has(type)) {
- this.listeners_.get(type).push(listener);
- }
+ /** @return {!EventTarget} */
+ getEventTarget() {
+ return this.eventTarget_;
}
/**
+ * Public for tests.
* @return {boolean} True if the last touch start was a two finger touch.
*/
wasTwoFingerTouch() {
@@ -77,22 +79,17 @@ export class GestureDetector {
}
/**
- * Call the relevant listeners with the given |pinchEvent|.
- *
- * @param {!Object} pinchEvent The event to notify the listeners of.
+ * Call the relevant listeners with the given |PinchEventDetail|.
+ * @param {string} type The type of pinch event.
+ * @param {!PinchEventDetail} detail The event to notify the listeners of.
* @private
*/
- notify_(pinchEvent) {
- const listeners = this.listeners_.get(pinchEvent.type);
-
- for (const l of listeners) {
- l(pinchEvent);
- }
+ notify_(type, detail) {
+ this.eventTarget_.dispatchEvent(new CustomEvent(type, {detail}));
}
/**
* The callback for touchstart events on the element.
- *
* @param {!TouchEvent} event Touch event on the element.
* @private
*/
@@ -104,12 +101,11 @@ export class GestureDetector {
this.pinchStartEvent_ = event;
this.lastEvent_ = event;
- this.notify_({type: 'pinchstart', center: GestureDetector.center_(event)});
+ this.notify_('pinchstart', {center: GestureDetector.center_(event)});
}
/**
* The callback for touch move, end, and cancel events on the element.
- *
* @param {!TouchEvent} event Touch event on the element.
* @private
*/
@@ -126,14 +122,12 @@ export class GestureDetector {
const startScaleRatio =
GestureDetector.pinchScaleRatio_(lastEvent, this.pinchStartEvent_);
const center = GestureDetector.center_(lastEvent);
- const endEvent = {
- type: 'pinchend',
- startScaleRatio: startScaleRatio,
- center: center
- };
this.pinchStartEvent_ = null;
this.lastEvent_ = null;
- this.notify_(endEvent);
+ this.notify_('pinchend', {
+ startScaleRatio: startScaleRatio,
+ center: center,
+ });
return;
}
@@ -141,12 +135,11 @@ export class GestureDetector {
const startScaleRatio =
GestureDetector.pinchScaleRatio_(event, this.pinchStartEvent_);
const center = GestureDetector.center_(event);
- this.notify_({
- type: 'pinchupdate',
+ this.notify_('pinchupdate', {
scaleRatio: scaleRatio,
direction: scaleRatio > 1.0 ? 'in' : 'out',
startScaleRatio: startScaleRatio,
- center: center
+ center: center,
});
this.lastEvent_ = event;
@@ -154,7 +147,6 @@ export class GestureDetector {
/**
* The callback for wheel events on the element.
- *
* @param {!WheelEvent} event Wheel event on the element.
* @private
*/
@@ -179,16 +171,15 @@ export class GestureDetector {
if (this.accumulatedWheelScale_ == null) {
this.accumulatedWheelScale_ = 1.0;
- this.notify_({type: 'pinchstart', center: position});
+ this.notify_('pinchstart', {center: position});
}
this.accumulatedWheelScale_ *= scale;
- this.notify_({
- type: 'pinchupdate',
+ this.notify_('pinchupdate', {
scaleRatio: scale,
direction: scale > 1.0 ? 'in' : 'out',
startScaleRatio: this.accumulatedWheelScale_,
- center: position
+ center: position,
});
// We don't get any phase information for the ctrl-wheels, so we don't know
@@ -200,21 +191,35 @@ export class GestureDetector {
}
const gestureEndDelayMs = 100;
const endEvent = {
- type: 'pinchend',
startScaleRatio: this.accumulatedWheelScale_,
- center: position
+ center: position,
};
- this.wheelEndTimeout_ = window.setTimeout(function(endEvent) {
- this.notify_(endEvent);
+ this.wheelEndTimeout_ = window.setTimeout(() => {
+ this.notify_('pinchend', endEvent);
this.wheelEndTimeout_ = null;
this.accumulatedWheelScale_ = null;
- }.bind(this), gestureEndDelayMs, endEvent);
+ }, gestureEndDelayMs);
+ }
+
+ /**
+ * @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.
+ const capabilities =
+ /** @type {{ sourceCapabilities: Object }} */ (e).sourceCapabilities;
+ if (capabilities.firesTouchEvents && !this.wasTwoFingerTouch()) {
+ e.preventDefault();
+ }
}
/**
* Computes the change in scale between this touch event
* and a previous one.
- *
* @param {!TouchEvent} event Latest touch event on the element.
* @param {!TouchEvent} prevEvent A previous touch event on the element.
* @return {?number} The ratio of the scale of this event and the
@@ -229,7 +234,6 @@ export class GestureDetector {
/**
* Computes the distance between fingers.
- *
* @param {!TouchEvent} event Touch event with at least 2 touch points.
* @return {number} Distance between touch[0] and touch[1].
* @private
@@ -244,9 +248,8 @@ export class GestureDetector {
/**
* Computes the midpoint between fingers.
- *
* @param {!TouchEvent} event Touch event with at least 2 touch points.
- * @return {!Object} Midpoint between touch[0] and touch[1].
+ * @return {!{x: number, y: number}} Midpoint between touch[0] and touch[1].
* @private
*/
static center_(event) {
diff --git a/chromium/chrome/browser/resources/pdf/index.css b/chromium/chrome/browser/resources/pdf/index.css
index 1f6605930ec..20fd3b62cda 100644
--- a/chromium/chrome/browser/resources/pdf/index.css
+++ b/chromium/chrome/browser/resources/pdf/index.css
@@ -13,53 +13,3 @@ body {
line-height: 154%;
margin: 0;
}
-
-viewer-page-indicator {
- opacity: 0;
- visibility: hidden;
- z-index: 2;
-}
-
-viewer-pdf-toolbar {
- position: fixed;
- width: 100%;
- z-index: 4;
-}
-
-#content {
- height: 100%;
- position: fixed;
- width: 100%;
- z-index: 1;
-}
-
-
-viewer-ink-host,
-#plugin {
- height: 100%;
- position: absolute;
- width: 100%;
-}
-
-#sizer {
- position: absolute;
- z-index: 0;
-}
-
-@media(max-height: 250px) {
- viewer-pdf-toolbar {
- display: none;
- }
-}
-
-@media(max-height: 200px) {
- viewer-zoom-toolbar {
- display: none;
- }
-}
-
-@media(max-width: 300px) {
- viewer-zoom-toolbar {
- display: none;
- }
-}
diff --git a/chromium/chrome/browser/resources/pdf/index.html b/chromium/chrome/browser/resources/pdf/index.html
index 6a78136126d..bce88410976 100644
--- a/chromium/chrome/browser/resources/pdf/index.html
+++ b/chromium/chrome/browser/resources/pdf/index.html
@@ -1,5 +1,6 @@
<!doctype html>
-<html $i18n{a11yenhanced}>
+<html dir="$i18n{textdirection}" lang="$i18n{language}"
+ $i18n{pdfViewerUpdateEnabledAttribute}>
<head>
<meta charset="utf-8">
@@ -7,22 +8,8 @@
<link rel="stylesheet" href="index.css">
</head>
<body>
-
-<viewer-pdf-toolbar id="toolbar" hidden></viewer-pdf-toolbar>
-
-<div id="sizer"></div>
-<viewer-password-screen id="password-screen"></viewer-password-screen>
-
-<viewer-zoom-toolbar id="zoom-toolbar" hidden></viewer-zoom-toolbar>
-
-<viewer-error-screen id="error-screen"></viewer-error-screen>
-
-<if expr="chromeos">
-<viewer-form-warning id="form-warning"></viewer-form-warning>
-</if>
-
-<div id="content"></div>
-
+<pdf-viewer id="viewer"></pdf-viewer>
</body>
<script type="module" src="main.js"></script>
+<script type="module" src="pdf_viewer.js"></script>
</html>
diff --git a/chromium/chrome/browser/resources/pdf/index_pp.html b/chromium/chrome/browser/resources/pdf/index_pp.html
index 5c44ada5d73..fd18cd50280 100644
--- a/chromium/chrome/browser/resources/pdf/index_pp.html
+++ b/chromium/chrome/browser/resources/pdf/index_pp.html
@@ -1,5 +1,5 @@
<!doctype html>
-<html $i18n{a11yenhanced}>
+<html is-print-preview dir="$i18n{textdirection}" lang="$i18n{language}">
<head>
<meta charset="utf-8">
@@ -7,16 +7,11 @@
<link rel="stylesheet" href="index.css">
</head>
<body>
-
-<div id="sizer"></div>
-<viewer-zoom-toolbar id="zoom-toolbar" hidden></viewer-zoom-toolbar>
-
-<viewer-page-indicator id="page-indicator"></viewer-page-indicator>
-
-<viewer-error-screen id="error-screen"></viewer-error-screen>
-
-<div id="content"></div>
-
+<pdf-viewer-pp id="viewer"></pdf-viewer-pp>
</body>
-<script type="module" src="main_pp.js"></script>
+<script type="module" src="main.js"></script>
+<!-- The line below refers to pdf_viewer_pp.js, which is served under the path
+ pdf_viewer.js so that the PDF viewer and Print Preview viewer can use the
+ same main.js source. main.js imports pdf_viewer.js. -->
+<script type="module" src="pdf_viewer.js"></script>
</html>
diff --git a/chromium/chrome/browser/resources/pdf/ink/externs.js b/chromium/chrome/browser/resources/pdf/ink/externs.js
deleted file mode 100644
index 8af66dfc91f..00000000000
--- a/chromium/chrome/browser/resources/pdf/ink/externs.js
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2019 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.
-
-/**
- * @fileoverview We don't rely on any of these types but they are missing
- * from the Ink extern file currently and need to be defined somewhere.
- * @externs
- */
-
-var ink = {}; // eslint-disable-line no-var
-ink.proto.scene_change = {};
-
-/** @constructor */
-ink.proto.Snapshot = function() {};
-
-/** @constructor */
-ink.proto.Command = function() {};
-
-/** @constructor */
-ink.proto.MutationPacket = function() {};
-
-/** @constructor */
-ink.proto.SetCallbackFlags = function() {};
-
-/** @constructor */
-ink.proto.ToolEvent = function() {};
-
-/** @constructor */
-ink.Box = function() {};
-
-/** @constructor */
-ink.Model = function() {};
-
-/** @constructor */
-ink.ElementListener = function() {};
-
-/** @constructor */
-ink.proto.scene_change.SceneChangeEvent = function() {};
-
-const goog = {};
-goog.events = {};
-goog.disposable = {};
-goog.math = {};
-goog.ui = {};
-goog.html = {};
-goog.proto2 = {};
-
-/** @constructor */
-goog.events.EventTarget = function() {};
-
-/** @interface */
-goog.disposable.IDisposable = function() {};
-
-/** @interface */
-goog.events.Listenable = function() {};
-
-/** @constructor */
-goog.math.Size = function() {};
-
-/** @constructor */
-goog.events.Event = function() {};
-
-/** @constructor */
-goog.proto2.Message = function() {};
-
-/**
- * @extends {goog.events.EventTarget}
- * @constructor
- */
-goog.ui.Component = function() {};
-
-/** @constructor */
-goog.html.SafeUrl = function() {};
diff --git a/chromium/chrome/browser/resources/pdf/ink/index.html b/chromium/chrome/browser/resources/pdf/ink/index.html
index 61d5e7e077b..07f64d813e6 100644
--- a/chromium/chrome/browser/resources/pdf/ink/index.html
+++ b/chromium/chrome/browser/resources/pdf/ink/index.html
@@ -1,5 +1,5 @@
<!DOCTYPE html>
-<script src="glcore_wasm_bootstrap_compiled.js"></script>
+<script src="ink_loader.js"></script>
<script src="ink_lib_binary.js"></script>
<script src="ink_api.js"></script>
<style>
diff --git a/chromium/chrome/browser/resources/pdf/ink/ink_api.js b/chromium/chrome/browser/resources/pdf/ink/ink_api.js
index c394b630077..5d4a4d72a83 100644
--- a/chromium/chrome/browser/resources/pdf/ink/ink_api.js
+++ b/chromium/chrome/browser/resources/pdf/ink/ink_api.js
@@ -3,36 +3,19 @@
// found in the LICENSE file.
/**
- * @typedef {{
- * canUndo: boolean,
- * canRedo: boolean,
- * }}
- */
-let UndoState;
-
-/**
* Wraps the Ink component with an API that can be called
* across an IFrame boundary.
*/
class InkAPI {
- /** @param {!ink.embed.EmbedComponent} embed */
- constructor(embed) {
- this.embed_ = embed;
- this.brush_ = ink.BrushModel.getInstance(embed);
+ /** @param {!drawings.Canvas} canvas */
+ constructor(canvas) {
+ this.canvas_ = canvas;
this.camera_ = null;
}
- /** @param {function(!UndoState)} listener */
+ /** @param {function(!drawings.UndoState)} listener */
addUndoStateListener(listener) {
- /** @param {!ink.UndoStateChangeEvent} e */
- function wrapper(e) {
- listener({
- canUndo: e.getCanUndo(),
- canRedo: e.getCanRedo(),
- });
- }
-
- this.embed_.addEventListener(ink.UndoStateChangeEvent.EVENT_TYPE, wrapper);
+ this.canvas_.addUndoRedoListener(listener);
}
/**
@@ -42,109 +25,70 @@ class InkAPI {
// We change the type from ArrayBuffer to Uint8Array due to the consequences
// of the buffer being passed across the iframe boundary. This realm has a
// different ArrayBuffer constructor than `buffer`.
- // TODO(dstockwell): Update Ink to allow Uint8Array here.
- this.embed_.setPDF(
- /** @type {!ArrayBuffer} */ (
- /** @type {!*} */ (new Uint8Array(buffer))));
+ this.canvas_.setPDF(new Uint8Array(buffer));
}
/**
- * @return {!Promise<Uint8Array>}
+ * @return {!Uint8Array}
*/
getPDF() {
- return this.embed_.getPDF();
+ return this.canvas_.getPDF();
}
/**
* @return {!Uint8Array}
*/
getPDFDestructive() {
- return this.embed_.getPDFDestructive();
+ return this.canvas_.getPDFDestructive();
}
async setCamera(camera) {
this.camera_ = camera;
- this.embed_.setCamera(camera);
+ this.canvas_.setCamera(camera);
// Wait for the next task to avoid a race where Ink drops the camera value
// when the canvas is rotated in low-latency mode.
- setTimeout(() => this.embed_.setCamera(this.camera_), 0);
+ setTimeout(() => this.canvas_.setCamera(this.camera_), 0);
}
/** @param {AnnotationTool} tool */
setAnnotationTool(tool) {
- const shape = {
- eraser: 'MAGIC_ERASE',
- pen: 'INKPEN',
- highlighter: 'SMART_HIGHLIGHTER_TOOL',
- }[tool.tool];
- this.brush_.setShape(shape);
- if (tool.tool !== 'eraser') {
- this.brush_.setColor(/** @type {string} */ (tool.color));
- }
- this.brush_.setStrokeWidth(tool.size);
+ this.canvas_.setTool(tool);
}
flush() {
- return new Promise(resolve => this.embed_.flush(resolve));
+ return this.canvas_.flush();
}
/** @param {string} hexColor */
setOutOfBoundsColor(hexColor) {
- this.embed_.setOutOfBoundsColor(ink.Color.fromString(hexColor));
+ this.canvas_.setOutOfBoundsColor(hexColor);
}
/** @param {string} url */
setBorderImage(url) {
- this.embed_.setBorderImage(url);
+ this.canvas_.setBorderImage(url);
}
/** @param {number} spacing in points */
setPageSpacing(spacing) {
- this.embed_.setVerticalPageLayout(spacing);
+ this.canvas_.setVerticalPageLayout(spacing);
}
- dispatchPointerEvent(type, init) {
- const engine = document.querySelector('#ink-engine');
- const match = engine.style.transform.match(/(\d+)deg/);
- const rotation = match ? Number(match[1]) : 0;
- let offsetX = init.clientX;
- let offsetY = init.clientY;
- // If Ink's canvas has been re-orientated away from 0, we must transform
- // the event's offsetX and offsetY to correspond with the rotation and
- // offset applied.
- if ([90, 180, 270].includes(rotation)) {
- const width = window.innerWidth;
- const height = window.innerHeight;
- const matrix = new DOMMatrix();
- matrix.translateSelf(width / 2, height / 2);
- matrix.rotateSelf(0, 0, -rotation);
- matrix.translateSelf(-width / 2, -height / 2);
- const result = matrix.transformPoint({x: offsetX, y: offsetY});
- offsetX = result.x - engine.offsetLeft;
- offsetY = result.y - engine.offsetTop;
- }
-
- const event = new PointerEvent(type, init);
- // Ink uses offsetX and offsetY, but we can only override them, not pass
- // as part of the init.
- Object.defineProperty(event, 'offsetX', {value: offsetX});
- Object.defineProperty(event, 'offsetY', {value: offsetY});
- engine.dispatchEvent(event);
+ dispatchPointerEvent(evt) {
+ this.canvas_.dispatchInput(evt);
}
undo() {
- this.embed_.undo();
+ this.canvas_.undo();
}
redo() {
- this.embed_.redo();
+ this.canvas_.redo();
}
}
/** @return {Promise<InkAPI>} */
window.initInk = async function() {
- const config = new ink.embed.Config();
- const embed = await ink.embed.EmbedComponent.execute(config);
- embed.assignFlag(ink.proto.Flag.ENABLE_HOST_CAMERA_CONTROL, true);
- return new InkAPI(embed);
+ const canvas = await drawings.Canvas.execute(document.body);
+ return new InkAPI(canvas);
};
diff --git a/chromium/chrome/browser/resources/pdf/ink_controller.js b/chromium/chrome/browser/resources/pdf/ink_controller.js
new file mode 100644
index 00000000000..276b6ab686b
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/ink_controller.js
@@ -0,0 +1,128 @@
+// Copyright 2020 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 {NativeEventTarget as EventTarget} from 'chrome://resources/js/cr/event_target.m.js';
+
+import {ContentController} from './controller.js';
+import {Viewport} from './viewport.js';
+
+// Note: Redefining this type here, to work around the fact that ink externs
+// are only available on Chrome OS, so the targets that contain them cannot be
+// built on other platforms.
+
+/**
+ * @typedef {{
+ * setAnnotationTool: function(AnnotationTool):void,
+ * viewportChanged: function():void,
+ * saveDocument: function():!Promise,
+ * undo: function():void,
+ * redo: function():void,
+ * load: function(string, !ArrayBuffer):!Promise,
+ * viewport: !Viewport,
+ * }}
+ */
+let ViewerInkHostElement;
+
+// Controller for annotation mode, on Chrome OS only. Fires the following events
+// from its event target:
+// has-unsaved-changes: Fired to indicate there are ink annotations that have
+// not been saved.
+// set-annotation-undo-state: Contains information about whether undo or redo
+// options are available.
+export class InkController extends ContentController {
+ /**
+ * @param {!Viewport} viewport
+ * @param {!HTMLDivElement} contentElement
+ */
+ constructor(viewport, contentElement) {
+ super();
+
+ /** @private {!Viewport} */
+ this.viewport_ = viewport;
+
+ /** @private {!HTMLDivElement} */
+ this.contentElement_ = contentElement;
+
+ /** @private {?ViewerInkHostElement} */
+ this.inkHost_ = null;
+
+ /** @private {!EventTarget} */
+ this.eventTarget_ = new EventTarget();
+
+ /** @type {?AnnotationTool} */
+ this.tool_ = null;
+ }
+
+ /** @return {!EventTarget} */
+ getEventTarget() {
+ return this.eventTarget_;
+ }
+
+ /** @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 */
+ setTwoUpView(enableTwoUpView) {
+ // TODO(dstockwell): Implement two up view.
+ }
+
+ /** @override */
+ viewportChanged() {
+ this.inkHost_.viewportChanged();
+ }
+
+ /** @override */
+ save(requestType) {
+ return this.inkHost_.saveDocument();
+ }
+
+ /** @override */
+ undo() {
+ this.inkHost_.undo();
+ }
+
+ /** @override */
+ redo() {
+ this.inkHost_.redo();
+ }
+
+ /** @override */
+ load(filename, data) {
+ if (!this.inkHost_) {
+ const inkHost = document.createElement('viewer-ink-host');
+ this.contentElement_.appendChild(inkHost);
+ this.inkHost_ = /** @type {!ViewerInkHostElement} */ (inkHost);
+ this.inkHost_.viewport = this.viewport_;
+ inkHost.addEventListener('stroke-added', e => {
+ this.eventTarget_.dispatchEvent(new CustomEvent('has-unsaved-changes'));
+ });
+ inkHost.addEventListener('undo-state-changed', e => {
+ this.eventTarget_.dispatchEvent(
+ new CustomEvent('set-annotation-undo-state', {detail: e.detail}));
+ });
+ }
+ return this.inkHost_.load(filename, data);
+ }
+
+ /** @override */
+ unload() {
+ this.inkHost_.remove();
+ this.inkHost_ = null;
+ }
+}
diff --git a/chromium/chrome/browser/resources/pdf/main.js b/chromium/chrome/browser/resources/pdf/main.js
index 28e1726b30b..4b65231c650 100644
--- a/chromium/chrome/browser/resources/pdf/main.js
+++ b/chromium/chrome/browser/resources/pdf/main.js
@@ -2,16 +2,75 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import './elements/viewer-error-screen.js';
-import './elements/viewer-password-screen.js';
-import './elements/viewer-pdf-toolbar.js';
-import './elements/viewer-zoom-toolbar.js';
-import './elements/shared-vars.js';
-// <if expr="chromeos">
-import './elements/viewer-ink-host.js';
-import './elements/viewer-form-warning.js';
-// </if>
-
-import {main} from './main_util.js';
+import './pdf_viewer.js';
+
+import {BrowserApi, createBrowserApi} from './browser_api.js';
+
+/**
+ * Stores any pending messages received which should be passed to the
+ * PDFViewer when it is created.
+ * @type Array
+ */
+const pendingMessages = [];
+
+/**
+ * Handles events that are received prior to the PDFViewer being created.
+ * @param {Object} message A message event received.
+ */
+function handleScriptingMessage(message) {
+ pendingMessages.push(message);
+}
+
+/**
+ * Initialize the global PDFViewer and pass any outstanding messages to it.
+ * @param {!BrowserApi} browserApi
+ */
+function initViewer(browserApi) {
+ // PDFViewer will handle any messages after it is created.
+ window.removeEventListener('message', handleScriptingMessage, false);
+ const viewer = document.querySelector('#viewer');
+ viewer.init(browserApi);
+ while (pendingMessages.length > 0) {
+ viewer.handleScriptingMessage(pendingMessages.shift());
+ }
+ window.viewer = viewer;
+}
+
+/**
+ * Determine if the content settings allow PDFs to execute javascript.
+ * @param {!BrowserApi} browserApi
+ * @return {!Promise<!BrowserApi>}
+ */
+function configureJavaScriptContentSetting(browserApi) {
+ return new Promise((resolve, reject) => {
+ chrome.contentSettings.javascript.get(
+ {
+ 'primaryUrl': browserApi.getStreamInfo().originalUrl,
+ 'secondaryUrl': window.location.origin
+ },
+ (result) => {
+ browserApi.getStreamInfo().javascript = result.setting;
+ resolve(browserApi);
+ });
+ });
+}
+
+/**
+ * Entrypoint for starting the PDF viewer. This function obtains the browser
+ * API for the PDF and initializes the PDF Viewer.
+ */
+function main() {
+ // Set up an event listener to catch scripting messages which are sent prior
+ // to the PDFViewer being created.
+ window.addEventListener('message', handleScriptingMessage, false);
+ let chain = createBrowserApi();
+
+ // Content settings may not be present in test environments.
+ if (chrome.contentSettings) {
+ chain = chain.then(configureJavaScriptContentSetting);
+ }
+
+ chain.then(initViewer);
+}
main();
diff --git a/chromium/chrome/browser/resources/pdf/main_pp.js b/chromium/chrome/browser/resources/pdf/main_pp.js
deleted file mode 100644
index b21153858b0..00000000000
--- a/chromium/chrome/browser/resources/pdf/main_pp.js
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright 2014 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-screen.js';
-import './elements/viewer-page-indicator.js';
-import './elements/viewer-zoom-toolbar.js';
-import './elements/shared-vars.js';
-
-import {main} from './main_util.js';
-
-main();
diff --git a/chromium/chrome/browser/resources/pdf/main_util.js b/chromium/chrome/browser/resources/pdf/main_util.js
deleted file mode 100644
index a0585f05ff4..00000000000
--- a/chromium/chrome/browser/resources/pdf/main_util.js
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2014 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 {BrowserApi, createBrowserApi} from './browser_api.js';
-import {PDFViewer} from './pdf_viewer.js';
-
-
-/**
- * Global PDFViewer object, accessible for testing.
- *
- * @type Object
- */
-window.viewer = null;
-
-
-/**
- * Stores any pending messages received which should be passed to the
- * PDFViewer when it is created.
- *
- * @type Array
- */
-const pendingMessages = [];
-
-/**
- * Handles events that are received prior to the PDFViewer being created.
- *
- * @param {Object} message A message event received.
- */
-function handleScriptingMessage(message) {
- pendingMessages.push(message);
-}
-/**
- * Initialize the global PDFViewer and pass any outstanding messages to it.
- *
- * @param {!BrowserApi} browserApi
- */
-function initViewer(browserApi) {
- // PDFViewer will handle any messages after it is created.
- window.removeEventListener('message', handleScriptingMessage, false);
- window.viewer = new PDFViewer(browserApi);
- while (pendingMessages.length > 0) {
- window.viewer.handleScriptingMessage(pendingMessages.shift());
- }
-}
-
-/**
- * Determine if the content settings allow PDFs to execute javascript.
- *
- * @param {!BrowserApi} browserApi
- * @return {!Promise<!BrowserApi>}
- */
-function configureJavaScriptContentSetting(browserApi) {
- return new Promise((resolve, reject) => {
- chrome.contentSettings.javascript.get(
- {
- 'primaryUrl': browserApi.getStreamInfo().originalUrl,
- 'secondaryUrl': window.location.origin
- },
- (result) => {
- browserApi.getStreamInfo().javascript = result.setting;
- resolve(browserApi);
- });
- });
-}
-
-/**
- * Entrypoint for starting the PDF viewer. This function obtains the browser
- * API for the PDF and constructs a PDFViewer object with it.
- */
-export function main() {
- // Set up an event listener to catch scripting messages which are sent prior
- // to the PDFViewer being created.
- window.addEventListener('message', handleScriptingMessage, false);
- let chain = createBrowserApi();
-
- // Content settings may not be present in test environments.
- if (chrome.contentSettings) {
- chain = chain.then(configureJavaScriptContentSetting);
- }
-
- chain.then(initViewer);
-}
diff --git a/chromium/chrome/browser/resources/pdf/metrics.js b/chromium/chrome/browser/resources/pdf/metrics.js
index d1ca0a8270b..42384c4b0db 100644
--- a/chromium/chrome/browser/resources/pdf/metrics.js
+++ b/chromium/chrome/browser/resources/pdf/metrics.js
@@ -4,13 +4,10 @@
import {FittingType, TwoUpViewAction} from './constants.js';
-/**
- * Handles events specific to the PDF viewer and logs the corresponding metrics.
- */
+// Handles events specific to the PDF viewer and logs the corresponding metrics.
export class PDFMetrics {
/**
* Records when the zoom mode is changed to fit a FittingType.
- *
* @param {FittingType} fittingType the new FittingType.
*/
static recordFitTo(fittingType) {
@@ -25,7 +22,6 @@ export class PDFMetrics {
/**
* Records when the two up view mode is enabled or disabled.
- *
* @param {TwoUpViewAction} twoUpViewAction the new TwoUpViewAction.
*/
static recordTwoUpView(twoUpViewAction) {
@@ -36,8 +32,18 @@ export class PDFMetrics {
}
/**
+ * Records zoom in and zoom out actions.
+ * @param {boolean} isZoomIn True when the action is zooming in, false when
+ * the action is zooming out.
+ */
+ static recordZoomAction(isZoomIn) {
+ PDFMetrics.record(
+ isZoomIn ? PDFMetrics.UserAction.ZOOM_IN :
+ PDFMetrics.UserAction.ZOOM_OUT);
+ }
+
+ /**
* Records the given action to chrome.metricsPrivate.
- *
* @param {PDFMetrics.UserAction} action
*/
static record(action) {
@@ -84,18 +90,15 @@ PDFMetrics.firstActionRecorded_ = new Set();
* The *_FIRST values are recorded automaticlly,
* eg. PDFMetrics.record(...ROTATE) will also record ROTATE_FIRST
* on the first instance.
- *
* @enum {number}
*/
PDFMetrics.UserAction = {
- /**
- * Recorded when the document is first loaded. This event serves as
- * denominator to determine percentages of documents in which an action was
- * taken as well as average number of each action per document.
- */
+ // Recorded when the document is first loaded. This event serves as
+ // denominator to determine percentages of documents in which an action was
+ // taken as well as average number of each action per document.
DOCUMENT_OPENED: 0,
- /** Recorded when the document is rotated clockwise or counter-clockwise. */
+ // Recorded when the document is rotated clockwise or counter-clockwise.
ROTATE_FIRST: 1,
ROTATE: 2,
@@ -105,26 +108,24 @@ PDFMetrics.UserAction = {
FIT_TO_PAGE_FIRST: 5,
FIT_TO_PAGE: 6,
- /** Recorded when the bookmarks panel is opened. */
+ // Recorded when the bookmarks panel is opened.
OPEN_BOOKMARKS_PANEL_FIRST: 7,
OPEN_BOOKMARKS_PANEL: 8,
- /** Recorded when a bookmark is followed. */
+ // Recorded when a bookmark is followed.
FOLLOW_BOOKMARK_FIRST: 9,
FOLLOW_BOOKMARK: 10,
- /** Recorded when the page selection is used to navigate to another page. */
+ // Recorded when the page selection is used to navigate to another page.
PAGE_SELECTOR_NAVIGATE_FIRST: 11,
PAGE_SELECTOR_NAVIGATE: 12,
- /** Recorded when the user triggers a save of the document. */
+ // Recorded when the user triggers a save of the document.
SAVE_FIRST: 13,
SAVE: 14,
- /**
- * Recorded when the user triggers a save of the document and the document
- * has been modified by annotations.
- */
+ // Recorded when the user triggers a save of the document and the document
+ // has been modified by annotations.
SAVE_WITH_ANNOTATION_FIRST: 15,
SAVE_WITH_ANNOTATION: 16,
@@ -137,39 +138,47 @@ PDFMetrics.UserAction = {
EXIT_ANNOTATION_MODE_FIRST: 21,
EXIT_ANNOTATION_MODE: 22,
- /** Recorded when a pen stroke is made. */
+ // Recorded when a pen stroke is made.
ANNOTATE_STROKE_TOOL_PEN_FIRST: 23,
ANNOTATE_STROKE_TOOL_PEN: 24,
- /** Recorded when an eraser stroke is made. */
+ // Recorded when an eraser stroke is made.
ANNOTATE_STROKE_TOOL_ERASER_FIRST: 25,
ANNOTATE_STROKE_TOOL_ERASER: 26,
- /** Recorded when a highlighter stroke is made. */
+ // Recorded when a highlighter stroke is made.
ANNOTATE_STROKE_TOOL_HIGHLIGHTER_FIRST: 27,
ANNOTATE_STROKE_TOOL_HIGHLIGHTER: 28,
- /** Recorded when a stroke is made using touch. */
+ // Recorded when a stroke is made using touch.
ANNOTATE_STROKE_DEVICE_TOUCH_FIRST: 29,
ANNOTATE_STROKE_DEVICE_TOUCH: 30,
- /** Recorded when a stroke is made using mouse. */
+ // Recorded when a stroke is made using mouse.
ANNOTATE_STROKE_DEVICE_MOUSE_FIRST: 31,
ANNOTATE_STROKE_DEVICE_MOUSE: 32,
- /** Recorded when a stroke is made using pen. */
+ // Recorded when a stroke is made using pen.
ANNOTATE_STROKE_DEVICE_PEN_FIRST: 33,
ANNOTATE_STROKE_DEVICE_PEN: 34,
- /** Recorded when two-up view mode is enabled. */
+ // Recorded when two-up view mode is enabled.
TWO_UP_VIEW_ENABLE_FIRST: 35,
TWO_UP_VIEW_ENABLE: 36,
- /** Recorded when two-up view mode is disabled. */
+ // Recorded when two-up view mode is disabled.
TWO_UP_VIEW_DISABLE_FIRST: 37,
TWO_UP_VIEW_DISABLE: 38,
- NUMBER_OF_ACTIONS: 39,
+ // Recorded when zoom in button is clicked.
+ ZOOM_IN_FIRST: 39,
+ ZOOM_IN: 40,
+
+ // Recorded when zoom out button is clicked.
+ ZOOM_OUT_FIRST: 41,
+ ZOOM_OUT: 42,
+
+ NUMBER_OF_ACTIONS: 43,
};
// Map from UserAction to the 'FIRST' action. These metrics are recorded
@@ -252,4 +261,12 @@ PDFMetrics.firstMap_ = new Map([
PDFMetrics.UserAction.TWO_UP_VIEW_DISABLE,
PDFMetrics.UserAction.TWO_UP_VIEW_DISABLE_FIRST,
],
+ [
+ PDFMetrics.UserAction.ZOOM_IN,
+ PDFMetrics.UserAction.ZOOM_IN_FIRST,
+ ],
+ [
+ PDFMetrics.UserAction.ZOOM_OUT,
+ PDFMetrics.UserAction.ZOOM_OUT_FIRST,
+ ],
]);
diff --git a/chromium/chrome/browser/resources/pdf/navigator.js b/chromium/chrome/browser/resources/pdf/navigator.js
index e24d3e8ff02..8aa2c1e3fd6 100644
--- a/chromium/chrome/browser/resources/pdf/navigator.js
+++ b/chromium/chrome/browser/resources/pdf/navigator.js
@@ -5,10 +5,8 @@
import {OpenPdfParamsParser} from './open_pdf_params_parser.js';
import {Viewport} from './viewport.js';
-/**
- * NavigatorDelegate for calling browser-specific functions to do the actual
- * navigating.
- */
+// NavigatorDelegate for calling browser-specific functions to do the actual
+// navigating.
export class NavigatorDelegate {
/**
* @param {number} tabId The tab ID of the PDF viewer or -1 if the viewer is
@@ -63,7 +61,7 @@ export class NavigatorDelegate {
}
}
-/** Navigator for navigating to links inside or outside the PDF. */
+// Navigator for navigating to links inside or outside the PDF.
export class PdfNavigator {
/**
* @param {string} originalUrl The original page URL.
diff --git a/chromium/chrome/browser/resources/pdf/open_pdf_params_parser.js b/chromium/chrome/browser/resources/pdf/open_pdf_params_parser.js
index 08ef73a94db..858a62b47fd 100644
--- a/chromium/chrome/browser/resources/pdf/open_pdf_params_parser.js
+++ b/chromium/chrome/browser/resources/pdf/open_pdf_params_parser.js
@@ -4,10 +4,8 @@
import {FittingType} from './constants.js';
-/**
- * Parses the open pdf parameters passed in the url to set initial viewport
- * settings for opening the pdf.
- */
+// Parses the open pdf parameters passed in the url to set initial viewport
+// settings for opening the pdf.
export class OpenPdfParamsParser {
/**
* @param {function(string):void} getNamedDestinationCallback
@@ -24,7 +22,6 @@ export class OpenPdfParamsParser {
/**
* Parse zoom parameter of open PDF parameters. The PDF should be opened at
* the specified zoom level.
- *
* @param {string} paramValue zoom value.
* @return {Object} Map with zoom parameters (zoom and position).
* @private
@@ -57,7 +54,6 @@ export class OpenPdfParamsParser {
/**
* Parse view parameter of open PDF parameters. The PDF should be opened at
* the specified fitting type mode and position.
- *
* @param {string} paramValue view value.
* @return {Object} Map with view parameters (view and viewPosition).
* @private
@@ -95,57 +91,35 @@ export class OpenPdfParamsParser {
}
/**
- * Parse the parameters encoded in the fragment of a URL into a dictionary.
- *
+ * Parse the parameters encoded in the fragment of a URL.
* @param {string} url to parse
- * @return {Object} Key-value pairs of URL parameters
+ * @return {!URLSearchParams}
* @private
*/
parseUrlParams_(url) {
- const params = {};
-
- const paramIndex = url.search('#');
- if (paramIndex === -1) {
- return params;
- }
-
- const paramTokens = url.substring(paramIndex + 1).split('&');
- if ((paramTokens.length === 1) && (paramTokens[0].search('=') === -1)) {
- // Handle the case of http://foo.com/bar#NAMEDDEST. This is not
- // explicitly mentioned except by example in the Adobe
- // "PDF Open Parameters" document.
- params['nameddest'] = paramTokens[0];
- return params;
- }
-
- for (const paramToken of paramTokens) {
- const keyValueSplit = paramToken.split('=');
- if (keyValueSplit.length !== 2) {
- continue;
+ const hash = new URL(url).hash;
+ const params = new URLSearchParams(hash.substring(1));
+
+ // Handle the case of http://foo.com/bar#NAMEDDEST. This is not
+ // explicitly mentioned except by example in the Adobe
+ // "PDF Open Parameters" document.
+ if (Array.from(params).length === 1) {
+ const key = Array.from(params.keys())[0];
+ if (params.get(key) === '') {
+ params.append('nameddest', key);
+ params.delete(key);
}
- params[keyValueSplit[0]] = keyValueSplit[1];
}
return params;
}
/**
- * Parse PDF url parameters used for controlling the state of UI. These need
- * to be available when the UI is being initialized, rather than when the PDF
- * is finished loading.
- *
* @param {string} url that needs to be parsed.
- * @return {Object} parsed url parameters.
+ * @return {boolean} Whether the toolbar UI element should be shown.
*/
- getUiUrlParams(url) {
- const params = this.parseUrlParams_(url);
- const uiParams = {toolbar: true};
-
- if ('toolbar' in params && params['toolbar'] === '0') {
- uiParams.toolbar = false;
- }
-
- return uiParams;
+ shouldShowToolbar(url) {
+ return this.parseUrlParams_(url).get('toolbar') !== '0';
}
/**
@@ -153,7 +127,6 @@ export class OpenPdfParamsParser {
* and specify actions to be performed when opening pdf files.
* See http://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/
* pdfs/pdf_open_parameters.pdf for details.
- *
* @param {string} url that needs to be parsed.
* @param {Function} callback function to be called with viewport info.
*/
@@ -163,25 +136,30 @@ export class OpenPdfParamsParser {
const urlParams = this.parseUrlParams_(url);
- if ('page' in urlParams) {
+ if (urlParams.has('page')) {
// |pageNumber| is 1-based, but goToPage() take a zero-based page number.
- const pageNumber = parseInt(urlParams['page'], 10);
+ const pageNumber = parseInt(urlParams.get('page'), 10);
if (!Number.isNaN(pageNumber) && pageNumber > 0) {
params['page'] = pageNumber - 1;
}
}
- if ('view' in urlParams) {
- Object.assign(params, this.parseViewParam_(urlParams['view']));
+ if (urlParams.has('view')) {
+ Object.assign(
+ params,
+ this.parseViewParam_(/** @type {string} */ (urlParams.get('view'))));
}
- if ('zoom' in urlParams) {
- Object.assign(params, this.parseZoomParam_(urlParams['zoom']));
+ if (urlParams.has('zoom')) {
+ Object.assign(
+ params,
+ this.parseZoomParam_(/** @type {string} */ (urlParams.get('zoom'))));
}
- if (params.page === undefined && 'nameddest' in urlParams) {
+ if (params.page === undefined && urlParams.has('nameddest')) {
this.outstandingRequests_.push({callback: callback, params: params});
- this.getNamedDestinationCallback_(urlParams['nameddest']);
+ this.getNamedDestinationCallback_(
+ /** @type {string} */ (urlParams.get('nameddest')));
} else {
callback(params);
}
@@ -190,7 +168,6 @@ export class OpenPdfParamsParser {
/**
* This is called when a named destination is received and the page number
* corresponding to the request for which a named destination is passed.
- *
* @param {number} pageNumber The page corresponding to the named destination
* requested.
*/
diff --git a/chromium/chrome/browser/resources/pdf/pdf_scripting_api.js b/chromium/chrome/browser/resources/pdf/pdf_scripting_api.js
index 8cae862e05d..6c2cad0de82 100644
--- a/chromium/chrome/browser/resources/pdf/pdf_scripting_api.js
+++ b/chromium/chrome/browser/resources/pdf/pdf_scripting_api.js
@@ -4,26 +4,27 @@
/**
* Turn a dictionary received from postMessage into a key event.
- *
* @param {Object} dict A dictionary representing the key event.
- * @return {!Event} A key event.
+ * @return {!KeyboardEvent} A key event.
*/
export function DeserializeKeyEvent(dict) {
- const e = document.createEvent('Event');
- e.initEvent('keydown', true, true);
- e.keyCode = dict.keyCode;
- e.code = dict.code;
- e.shiftKey = dict.shiftKey;
- e.ctrlKey = dict.ctrlKey;
- e.altKey = dict.altKey;
- e.metaKey = dict.metaKey;
+ const e = new KeyboardEvent('keydown', {
+ bubbles: true,
+ cancelable: true,
+ key: dict.key,
+ code: dict.code,
+ keyCode: dict.keyCode,
+ shiftKey: dict.shiftKey,
+ ctrlKey: dict.ctrlKey,
+ altKey: dict.altKey,
+ metaKey: dict.metaKey,
+ });
e.fromScriptingAPI = true;
return e;
}
/**
* Turn a key event into a dictionary which can be sent over postMessage.
- *
* @param {Event} event A key event.
* @return {Object} A dictionary representing the key event.
*/
@@ -31,6 +32,7 @@ export function SerializeKeyEvent(event) {
return {
keyCode: event.keyCode,
code: event.code,
+ key: event.key,
shiftKey: event.shiftKey,
ctrlKey: event.ctrlKey,
altKey: event.altKey,
@@ -49,11 +51,8 @@ export const LoadState = {
FAILED: 'failed'
};
-/**
- * Create a new PDFScriptingAPI. This provides a scripting interface to
- * the PDF viewer so that it can be customized by things like print preview.
- *
- */
+// Provides a scripting interface to the PDF viewer so that it can be customized
+// by things like print preview.
export class PDFScriptingAPI {
/**
* @param {Window} window the window of the page containing the pdf viewer.
@@ -134,7 +133,6 @@ export class PDFScriptingAPI {
* Send a message to the extension. If messages try to get sent before there
* is a plugin element set, then we queue them up and send them later (this
* can happen in print preview).
- *
* @param {Object} message The message to send.
* @private
*/
@@ -149,7 +147,6 @@ export class PDFScriptingAPI {
/**
* Sets the plugin element containing the PDF viewer. The element will usually
* be passed into the PDFScriptingAPI constructor but may also be set later.
- *
* @param {Object} plugin the plugin element containing the PDF viewer.
*/
setPlugin(plugin) {
@@ -168,7 +165,6 @@ export class PDFScriptingAPI {
/**
* Sets the callback which will be run when the PDF viewport changes.
- *
* @param {Function} callback the callback to be called.
*/
setViewportChangedCallback(callback) {
@@ -198,7 +194,6 @@ export class PDFScriptingAPI {
/**
* Resets the PDF viewer into print preview mode.
- *
* @param {string} url the url of the PDF to load.
* @param {boolean} grayscale whether or not to display the PDF in grayscale.
* @param {Array<number>} pageNumbers an array of the page numbers.
@@ -215,16 +210,13 @@ export class PDFScriptingAPI {
});
}
- /**
- * Hide the toolbars after a delay.
- */
+ /** Hide the toolbars after a delay. */
hideToolbars() {
this.sendMessage_({type: 'hideToolbars'});
}
/**
* Load a page into the document while in print preview mode.
- *
* @param {string} url the url of the pdf page to load.
* @param {number} index the index of the page to load.
*/
@@ -248,7 +240,6 @@ export class PDFScriptingAPI {
/**
* Get the selected text in the document. The callback will be called with the
* text that is selected. May only be called after document load.
- *
* @param {Function} callback a callback to be called with the selected text.
* @return {boolean} true if the function is successful, false if there is an
* outstanding request for selected text that has not been answered.
@@ -262,16 +253,13 @@ export class PDFScriptingAPI {
return true;
}
- /**
- * Print the document. May only be called after document load.
- */
+ /** Print the document. May only be called after document load. */
print() {
this.sendMessage_({type: 'print'});
}
/**
* Send a key event to the extension.
- *
* @param {Event} keyEvent the key event to send to the extension.
*/
sendKeyEvent(keyEvent) {
@@ -293,7 +281,6 @@ export class PDFScriptingAPI {
* iframe which is navigated to the PDF viewer extension and 2) a scripting
* interface which provides access to various features of the viewer for use
* by print preview and accessibility.
- *
* @param {string} src the source URL of the PDF to load initially.
* @param {string} baseUrl the base URL of the PDF viewer
* @return {!HTMLIFrameElement} the iframe element containing the PDF viewer.
diff --git a/chromium/chrome/browser/resources/pdf/pdf_viewer.html b/chromium/chrome/browser/resources/pdf/pdf_viewer.html
new file mode 100644
index 00000000000..a56670db71c
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/pdf_viewer.html
@@ -0,0 +1,61 @@
+<style include="pdf-viewer-shared-style">
+ viewer-pdf-toolbar,
+ viewer-pdf-toolbar-new {
+ position: fixed;
+ width: 100%;
+ z-index: 4;
+ }
+
+ viewer-ink-host {
+ height: 100%;
+ position: absolute;
+ width: 100%;
+ }
+
+ @media(max-height: 250px) {
+ viewer-pdf-toolbar {
+ display: none;
+ }
+ }
+</style>
+
+<template is="dom-if" if="[[!pdfViewerUpdateEnabled_]]">
+ <viewer-pdf-toolbar id="toolbar" strings="[[strings]]"
+ annotation-available="[[annotationAvailable_]]"
+ bookmarks="[[bookmarks_]]" doc-title="[[title_]]"
+ has-edits="[[hasEdits_]]"
+ has-entered-annotation-mode="[[hasEnteredAnnotationMode_]]"
+ is-form-field-focused="[[isFormFieldFocused_]]"
+ on-save="onToolbarSave_" on-print="onPrint_"
+ on-annotation-mode-toggled="onAnnotationModeToggled_"
+ on-annotation-tool-changed="onAnnotationToolChanged_"
+ on-rotate-right="rotateClockwise" on-undo="onUndo_" on-redo="onRedo_"
+ hidden>
+ </viewer-pdf-toolbar>
+</template>
+<template is="dom-if" if="[[pdfViewerUpdateEnabled_]]">
+ <viewer-pdf-toolbar-new id="toolbar" hidden>
+ </viewer-pdf-toolbar-new>
+</template>
+
+<div id="sizer"></div>
+<viewer-password-screen id="password-screen"
+ on-password-submitted="onPasswordSubmitted_">
+</viewer-password-screen>
+
+<viewer-zoom-toolbar id="zoom-toolbar" strings="[[strings]]"
+ annotation-mode="[[annotationMode_]]"
+ on-fit-to-changed="onFitToChanged"
+ on-two-up-view-changed="onTwoUpViewChanged_"
+ on-zoom-in="onZoomIn" on-zoom-out="onZoomOut"
+ hidden>
+</viewer-zoom-toolbar>
+
+<viewer-error-screen id="error-screen"></viewer-error-screen>
+
+<if expr="chromeos">
+<viewer-form-warning id="form-warning"></viewer-form-warning>
+</if>
+
+<div id="content"></div>
+
diff --git a/chromium/chrome/browser/resources/pdf/pdf_viewer.js b/chromium/chrome/browser/resources/pdf/pdf_viewer.js
index e6fc90845ab..e23b4b41e48 100644
--- a/chromium/chrome/browser/resources/pdf/pdf_viewer.js
+++ b/chromium/chrome/browser/resources/pdf/pdf_viewer.js
@@ -2,46 +2,38 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import './elements/viewer-error-screen.js';
+import './elements/viewer-password-screen.js';
+import './elements/viewer-pdf-toolbar.js';
+import './elements/viewer-pdf-toolbar-new.js';
+import './elements/shared-vars.js';
+// <if expr="chromeos">
+import './elements/viewer-ink-host.js';
+import './elements/viewer-form-warning.js';
+// </if>
+import './pdf_viewer_shared_style.js';
+
import {assert, assertNotReached} from 'chrome://resources/js/assert.m.js';
-import {EventTracker} from 'chrome://resources/js/event_tracker.m.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
-import {PromiseResolver} from 'chrome://resources/js/promise_resolver.m.js';
-import {$, hasKeyModifiers, isRTL} from 'chrome://resources/js/util.m.js';
+import {hasKeyModifiers, isRTL} from 'chrome://resources/js/util.m.js';
+import {html} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {Bookmark} from './bookmark_type.js';
import {BrowserApi} from './browser_api.js';
-import {FittingType, TwoUpViewAction} from './constants.js';
-import {ContentController, InkController, MessageData, PluginController, PrintPreviewParams} from './controller.js';
-import {FitToChangedEvent} from './elements/viewer-zoom-toolbar.js';
-import {GestureDetector} from './gesture_detector.js';
+import {FittingType, SaveRequestType, TwoUpViewAction} from './constants.js';
import {PDFMetrics} from './metrics.js';
import {NavigatorDelegate, PdfNavigator} from './navigator.js';
import {OpenPdfParamsParser} from './open_pdf_params_parser.js';
import {DeserializeKeyEvent, LoadState, SerializeKeyEvent} from './pdf_scripting_api.js';
+import {PDFViewerBaseElement} from './pdf_viewer_base.js';
+import {DestinationMessageData, DocumentDimensionsMessageData, shouldIgnoreKeyEvents} from './pdf_viewer_utils.js';
import {ToolbarManager} from './toolbar_manager.js';
-import {LayoutOptions, Point, Viewport} from './viewport.js';
-import {ViewportScroller} from './viewport_scroller.js';
-import {ZoomManager} from './zoom_manager.js';
+import {Point} from './viewport.js';
-/**
- * @typedef {{
- * source: Object,
- * origin: string,
- * data: !MessageData,
- * }}
- */
-let MessageObject;
+// <if expr="chromeos">
+import {InkController} from './ink_controller.js';
+// </if>
-/**
- * @typedef {{
- * type: string,
- * height: number,
- * width: number,
- * layoutOptions: (!LayoutOptions|undefined),
- * pageDimensions: Array
- * }}
- */
-let DocumentDimensionsMessageData;
/**
* @typedef {{
@@ -55,17 +47,6 @@ 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,
@@ -82,22 +63,9 @@ let MetadataMessageData;
*/
let RequiredSaveResult;
-/** @return {number} Width of a scrollbar in pixels */
-function getScrollbarWidth() {
- const 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);
- const result = div.offsetWidth - div.clientWidth;
- div.parentNode.removeChild(div);
- return result;
-}
-
/**
* Return the filename component of a URL, percent decoded if possible.
+ * Exported for tests.
* @param {string} url The URL to get the filename from.
* @return {string} The filename component.
*/
@@ -116,270 +84,187 @@ export 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 {Element} activeElement The currently selected DOM node.
- * @return {boolean} True if keydown events should be ignored.
- */
-export function shouldIgnoreKeyEvents(activeElement) {
- while (activeElement.shadowRoot != null &&
- activeElement.shadowRoot.activeElement != null) {
- activeElement = activeElement.shadowRoot.activeElement;
+class PDFViewerElement extends PDFViewerBaseElement {
+ static get is() {
+ return 'pdf-viewer';
}
- return (
- activeElement.isContentEditable ||
- (activeElement.tagName === 'INPUT' && activeElement.type !== 'radio') ||
- activeElement.tagName === 'TEXTAREA');
-}
+ static get template() {
+ return html`{__html_template__}`;
+ }
-/**
- * Creates a new PDFViewer. There should only be one of these objects per
- * document.
- */
-export class PDFViewer {
- /**
- * @param {!BrowserApi} browserApi An object providing an API to the browser.
- */
- constructor(browserApi) {
- /** @private {!BrowserApi} */
- this.browserApi_ = browserApi;
+ static get properties() {
+ return {
+ annotationAvailable_: {
+ type: Boolean,
+ computed: 'computeAnnotationAvailable_(' +
+ 'hadPassword_, rotated_, canSerializeDocument_)',
+ },
- /** @private {string} */
- this.originalUrl_ = this.browserApi_.getStreamInfo().originalUrl;
+ annotationMode_: {
+ type: Boolean,
+ value: false,
+ },
- /** @private {string} */
- this.javascript_ = this.browserApi_.getStreamInfo().javascript || 'block';
+ bookmarks_: Array,
- /** @private {!LoadState} */
- this.loadState_ = LoadState.LOADING;
+ hasEdits_: {
+ type: Boolean,
+ value: false,
+ },
- /** @private {?Object} */
- this.parentWindow_ = null;
+ hasEnteredAnnotationMode_: {
+ type: Boolean,
+ value: false,
+ },
- /** @private {?string} */
- this.parentOrigin_ = null;
+ rotated_: Boolean,
- /** @private {boolean} */
- this.isFormFieldFocused_ = false;
+ hadPassword_: Boolean,
- /** @private {number} */
- this.beepCount_ = 0;
+ canSerializeDocument_: Boolean,
- /** @private {!Array} */
- this.delayedScriptingMessages_ = [];
+ title_: String,
- /** @private {!PromiseResolver} */
- this.loaded_;
+ isFormFieldFocused_: Boolean,
- /** @private {boolean} */
- this.initialLoadComplete_ = false;
+ /** @private */
+ pdfViewerUpdateEnabled_: {
+ type: Boolean,
+ value: function() {
+ return document.documentElement.hasAttribute(
+ 'pdf-viewer-update-enabled');
+ },
+ },
+ };
+ }
+ constructor() {
+ super();
+
+ // Polymer properties
/** @private {boolean} */
- this.isPrintPreview_ = location.origin === 'chrome://print';
- document.documentElement.toggleAttribute(
- 'is-print-preview', this.isPrintPreview_);
+ this.annotationAvailable_;
/** @private {boolean} */
- this.isPrintPreviewLoadingFinished_ = false;
+ this.annotationMode_ = false;
+
+ /** @private {!Array<!Bookmark>} */
+ this.bookmarks_ = [];
/** @private {boolean} */
- this.isUserInitiatedEvent_ = true;
+ this.hasEdits_ = false;
/** @private {boolean} */
this.hasEnteredAnnotationMode_ = false;
/** @private {boolean} */
+ this.rotated_ = 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'));
- if (this.passwordScreen_) {
- this.passwordScreen_.addEventListener('password-submitted', e => {
- this.onPasswordSubmitted_(
- /** @type {!CustomEvent<{password: string}>} */ (e));
- });
- }
+ /** @private {string} */
+ this.title_ = '';
- /** @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);
- };
- }
+ /** @private {boolean} */
+ this.isFormFieldFocused_ = false;
- // 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(
- '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);
+ // Non-Polymer properties
- this.plugin_.setAttribute('background-color', PDFViewer.BACKGROUND_COLOR);
- this.plugin_.setAttribute('top-toolbar-height', topToolbarHeight);
- this.plugin_.setAttribute('javascript', this.javascript_);
+ /** @private {number} */
+ this.beepCount_ = 0;
- if (this.browserApi_.getStreamInfo().embedded) {
- this.plugin_.setAttribute(
- 'top-level-url', this.browserApi_.getStreamInfo().tabUrl);
- } else {
- this.plugin_.setAttribute('full-frame', '');
- }
+ /** @private {boolean} */
+ this.hadPassword_ = false;
+
+ /** @private {boolean} */
+ this.toolbarEnabled_ = false;
+
+ // <if expr="chromeos">
+ /** @private {?InkController} */
+ this.inkController_ = null;
+ // </if>
+
+ /** @private {?ToolbarManager} */
+ this.toolbarManager_ = null;
+
+ /** @private {?PdfNavigator} */
+ this.navigator_ = null;
+
+ /** @private {string} */
+ this.title_ = '';
+
+ /** @private {boolean} */
+ this.pdfViewerUpdateEnabled_;
+ }
- $('content').appendChild(this.plugin_);
+ /** @override */
+ getToolbarHeight() {
+ assert(this.paramsParser);
+ this.toolbarEnabled_ =
+ this.paramsParser.shouldShowToolbar(this.originalUrl);
+ return this.toolbarEnabled_ ? MATERIAL_TOOLBAR_HEIGHT : 0;
+ }
+
+ /** @override */
+ getContent() {
+ return /** @type {!HTMLDivElement} */ (this.$$('#content'));
+ }
- /** @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));
+ /** @override */
+ getSizer() {
+ return /** @type {!HTMLDivElement} */ (this.$$('#sizer'));
+ }
+
+ /** @override */
+ getZoomToolbar() {
+ return /** @type {!ViewerZoomToolbarElement} */ (this.$$('#zoom-toolbar'));
+ }
- /** @private {!InkController} */
- this.inkController_ = new InkController(this.viewport_);
- this.tracker_.add(
+ /** @override */
+ getErrorScreen() {
+ return /** @type {!ViewerErrorScreenElement} */ (this.$$('#error-screen'));
+ }
+
+ /**
+ * @return {!ViewerPdfToolbarElement}
+ * @private
+ */
+ getToolbar_() {
+ return /** @type {!ViewerPdfToolbarElement} */ (this.$$('#toolbar'));
+ }
+
+ /** @override */
+ getBackgroundColor() {
+ return BACKGROUND_COLOR;
+ }
+
+ /** @param {!BrowserApi} browserApi */
+ init(browserApi) {
+ super.init(browserApi);
+
+ // <if expr="chromeos">
+ this.inkController_ = new InkController(
+ this.viewport, /** @type {!HTMLDivElement} */ (this.getContent()));
+ this.tracker.add(
this.inkController_.getEventTarget(), 'stroke-added',
() => chrome.mimeHandlerPrivate.setShowBeforeUnloadDialog(true));
- this.tracker_.add(
+ this.tracker.add(
this.inkController_.getEventTarget(), 'set-annotation-undo-state',
e => this.setAnnotationUndoState_(e));
+ // </if>
- /** @private {!ContentController} */
- this.currentController_ = this.pluginController_;
-
- // Setup the button event listeners.
- /** @private {!ViewerZoomToolbarElement} */
- this.zoomToolbar_ =
- /** @type {!ViewerZoomToolbarElement} */ ($('zoom-toolbar'));
- this.zoomToolbar_.isPrintPreview = this.isPrintPreview_;
- this.zoomToolbar_.addEventListener(
- 'fit-to-changed',
- e => this.fitToChanged_(
- /** @type {!CustomEvent<FitToChangedEvent>} */ (e)));
- this.zoomToolbar_.addEventListener(
- 'two-up-view-changed',
- e => this.twoUpViewChanged_(
- /** @type {!CustomEvent<!TwoUpViewAction>} */ (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_);
+ this.title_ = getFilenameFromURL(this.originalUrl);
+ if (this.toolbarEnabled_) {
+ this.getToolbar_().hidden = false;
}
document.body.addEventListener('change-page', e => {
- this.viewport_.goToPage(e.detail.page);
+ this.viewport.goToPage(e.detail.page);
if (e.detail.origin === 'bookmark') {
PDFMetrics.record(PDFMetrics.UserAction.FOLLOW_BOOKMARK);
} else if (e.detail.origin === 'pageselector') {
@@ -387,12 +272,8 @@ export class PDFViewer {
}
});
- 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);
+ const point = this.viewport.convertPageToScreen(e.detail.page, e.detail);
this.goToPageAndXY_(e.detail.origin, e.detail.page, point);
});
@@ -409,59 +290,21 @@ export class PDFViewer {
}
});
- /** @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));
+ this.toolbarManager_ = new ToolbarManager(
+ window, this.pdfViewerUpdateEnabled_ ? null : this.getToolbar_(),
+ this.getZoomToolbar());
// 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} */
+ const tabId = this.browserApi.getStreamInfo().tabId;
this.navigator_ = new PdfNavigator(
- this.originalUrl_, this.viewport_, this.paramsParser_,
+ this.originalUrl, this.viewport,
+ /** @type {!OpenPdfParamsParser} */ (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.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));
@@ -475,168 +318,57 @@ export class PDFViewer {
* @private
*/
handleKeyEvent_(e) {
- const position = this.viewport_.position;
- // Certain scroll events may be sent from outside of the extension.
- const fromScriptingAPI = e.fromScriptingAPI;
-
if (shouldIgnoreKeyEvents(document.activeElement) || e.defaultPrevented) {
return;
}
this.toolbarManager_.hideToolbarsAfterTimeout();
- const pageUpHandler = () => {
- // Go to the previous page if we are fit-to-page or fit-to-height.
- if (this.viewport_.isPagedMode()) {
- this.viewport_.goToPreviousPage();
- // Since we do the movement of the page.
- e.preventDefault();
- } else if (fromScriptingAPI) {
- position.y -= this.viewport_.size.height;
- this.viewport_.position = position;
- }
- };
- const pageDownHandler = () => {
- // Go to the next page if we are fit-to-page or fit-to-height.
- if (this.viewport_.isPagedMode()) {
- this.viewport_.goToNextPage();
- // Since we do the movement of the page.
- e.preventDefault();
- } else if (fromScriptingAPI) {
- position.y += this.viewport_.size.height;
- this.viewport_.position = position;
- }
- };
+ // Let the viewport handle directional key events.
+ if (this.viewport.handleDirectionalKeyEvent(e, this.isFormFieldFocused_)) {
+ return;
+ }
- switch (e.keyCode) {
- case 9: // Tab key.
+ switch (e.key) {
+ case 'Tab':
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 (!hasKeyModifiers(e)) {
- // 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_.goToPreviousPage();
- // Since we do the movement of the page.
- e.preventDefault();
- } else if (fromScriptingAPI) {
- position.x -= Viewport.SCROLL_INCREMENT;
- this.viewport_.position = position;
- }
- }
+ case 'Escape':
+ this.toolbarManager_.hideSingleToolbarLayer();
return;
- case 38: // Up arrow key.
- if (fromScriptingAPI) {
- position.y -= Viewport.SCROLL_INCREMENT;
- this.viewport_.position = position;
- }
- return;
- case 39: // Right arrow key.
- if (!hasKeyModifiers(e)) {
- // 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_.goToNextPage();
- // 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.
+ case 'a':
if (e.ctrlKey || e.metaKey) {
- this.pluginController_.selectAll();
+ this.pluginController.selectAll();
// Since we do selection ourselves.
e.preventDefault();
}
return;
- case 71: // 'g' key.
- if (this.toolbar_ && (e.ctrlKey || e.metaKey) && e.altKey) {
+ case 'g':
+ if (this.toolbarEnabled_ && (e.ctrlKey || e.metaKey) && e.altKey) {
this.toolbarManager_.showToolbars();
- this.toolbar_.selectPageNumber();
+ this.getToolbar_().selectPageNumber();
}
return;
- case 219: // Left bracket key.
+ case '[':
if (e.ctrlKey) {
- this.rotateCounterclockwise_();
+ this.rotateCounterclockwise();
}
return;
- case 220: // Backslash key.
+ case '\\':
if (e.ctrlKey) {
- this.zoomToolbar_.fitToggleFromHotKey();
+ this.getZoomToolbar().fitToggleFromHotKey();
}
return;
- case 221: // Right bracket key.
+ case ']':
if (e.ctrlKey) {
- this.rotateClockwise_();
+ 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_(e) {
- if (e.type === 'mousemove') {
- this.toolbarManager_.handleMouseMove(e);
- } else if (e.type === 'mouseout') {
- this.toolbarManager_.hideToolbarsForMouseOut();
- }
- }
-
- /**
- * @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.
- const capabilities =
- /** @type {{ sourceCapabilities: Object }} */ (e).sourceCapabilities;
- if (capabilities.firesTouchEvents &&
- !this.gestureDetector_.wasTwoFingerTouch()) {
- e.preventDefault();
+ // Show toolbars as a fallback.
+ if (!(e.shiftKey || e.ctrlKey || e.altKey)) {
+ this.toolbarManager_.showToolbars();
}
}
@@ -645,59 +377,61 @@ export class PDFViewer {
* @param {!CustomEvent<{value: boolean}>} e
* @private
*/
- async annotationModeToggled_(e) {
+ async onAnnotationModeToggled_(e) {
const annotationMode = e.detail.value;
- this.zoomToolbar_.annotationMode = annotationMode;
+ this.annotationMode_ = annotationMode;
if (annotationMode) {
// Enter annotation mode.
- assert(this.currentController_ === this.pluginController_);
+ 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 saveResult = await this.pluginController_.save(true);
- // Data always exists when save is called with requireResult = true.
+ const saveResult =
+ await this.pluginController.save(SaveRequestType.ANNOTATION);
+ // Data always exists when save is called with requestType = ANNOTATION.
const result = /** @type {!RequiredSaveResult} */ (saveResult);
if (result.hasUnsavedChanges) {
assert(!loadTimeData.getBoolean('pdfFormSaveEnabled'));
try {
- await $('form-warning').show();
+ await this.$$('#form-warning').show();
} catch (e) {
// The user aborted entering annotation mode. Revert to the plugin.
- this.toolbar_.annotationMode = false;
- this.zoomToolbar_.annotationMode = false;
- this.updateProgress_(100);
+ this.getToolbar_().annotationMode = false;
+ this.annotationMode_ = false;
+ 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(
- assert(this.toolbar_.annotationTool));
- this.currentController_ = this.inkController_;
- this.pluginController_.unload();
- this.updateProgress_(100);
+ assert(this.getToolbar_().annotationTool));
+ this.currentController = this.inkController_;
+ this.pluginController.unload();
+ this.updateProgress(100);
} else {
// Exit annotation mode.
PDFMetrics.record(PDFMetrics.UserAction.EXIT_ANNOTATION_MODE);
- assert(this.currentController_ === this.inkController_);
+ 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(() => {
- this.currentController_ = this.pluginController_;
+ this.currentController = this.pluginController;
this.inkController_.unload();
});
// TODO(dstockwell): handle save failure
- const saveResult = await this.inkController_.save(true);
- // Data always exists when save is called with requireResult = true.
+ const saveResult =
+ await this.inkController_.save(SaveRequestType.ANNOTATION);
+ // Data always exists when save is called with requestType = ANNOTATION.
const result = /** @type {!RequiredSaveResult} */ (saveResult);
- await this.pluginController_.load(result.fileName, result.dataToSave);
+ await this.pluginController.load(result.fileName, result.dataToSave);
// Ensure the plugin gets the initial viewport.
- this.pluginController_.afterZoom();
+ this.pluginController.afterZoom();
}
}
@@ -706,32 +440,21 @@ export class PDFViewer {
* @return {Promise<void>}
*/
async exitAnnotationMode_() {
- if (!this.toolbar_.annotationMode) {
+ if (!this.getToolbar_().annotationMode) {
return;
}
- this.toolbar_.toggleAnnotation();
- this.zoomToolbar_.annotationMode = false;
+ this.getToolbar_().toggleAnnotation();
+ this.annotationMode_ = false;
await this.loaded;
}
- /**
- * Request to change the viewport fitting type.
- * @param {!CustomEvent<FitToChangedEvent>} e
- * @private
- */
- fitToChanged_(e) {
- if (e.detail.fittingType === FittingType.FIT_TO_PAGE) {
- this.viewport_.fitToPage();
- this.toolbarManager_.forceHideTopToolbar();
- } else if (e.detail.fittingType === FittingType.FIT_TO_WIDTH) {
- this.viewport_.fitToWidth();
- } else if (e.detail.fittingType === FittingType.FIT_TO_HEIGHT) {
- this.viewport_.fitToHeight();
- this.toolbarManager_.forceHideTopToolbar();
- }
+ /** @override */
+ onFitToChanged(e) {
+ super.onFitToChanged(e);
- if (e.detail.userInitiated) {
- PDFMetrics.recordFitTo(e.detail.fittingType);
+ if (e.detail.fittingType === FittingType.FIT_TO_PAGE ||
+ e.detail.fittingType === FittingType.FIT_TO_HEIGHT) {
+ this.toolbarManager_.forceHideTopToolbar();
}
}
@@ -741,70 +464,17 @@ export class PDFViewer {
* @param {!CustomEvent<!TwoUpViewAction>} e
* @private
*/
- twoUpViewChanged_(e) {
- this.currentController_.setTwoUpView(
+ onTwoUpViewChanged_(e) {
+ this.currentController.setTwoUpView(
e.detail === TwoUpViewAction.TWO_UP_VIEW_ENABLE);
this.toolbarManager_.forceHideTopToolbar();
- this.toolbar_.annotationAvailable =
+ this.getToolbar_().annotationAvailable =
(e.detail !== TwoUpViewAction.TWO_UP_VIEW_ENABLE);
PDFMetrics.recordTwoUpView(e.detail);
}
/**
- * Sends a 'documentLoaded' message to the PDFScriptingAPI if the document has
- * finished loading.
- * @private
- */
- sendDocumentLoadedMessage_() {
- if (this.loadState_ === LoadState.LOADING) {
- return;
- }
- if (this.isPrintPreview_ && !this.isPrintPreviewLoadingFinished_) {
- return;
- }
- 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_(params) {
- if (params.zoom) {
- this.viewport_.setZoom(params.zoom);
- }
-
- if (params.position) {
- this.viewport_.goToPageAndXY(
- params.page ? params.page : 0, params.position.x, params.position.y);
- } else if (params.page) {
- this.viewport_.goToPage(params.page);
- }
-
- if (params.view) {
- this.isUserInitiatedEvent_ = false;
- this.zoomToolbar_.forceFit(params.view);
- if (params.viewPosition) {
- const zoomedPositionShift =
- params.viewPosition * this.viewport_.getZoom();
- const currentViewportPosition = this.viewport_.position;
- if (params.view === FittingType.FIT_TO_WIDTH) {
- currentViewportPosition.y += zoomedPositionShift;
- } else if (params.view === FittingType.FIT_TO_HEIGHT) {
- currentViewportPosition.x += zoomedPositionShift;
- }
- this.viewport_.position = currentViewportPosition;
- }
- 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.
@@ -814,146 +484,38 @@ export class PDFViewer {
* @private
*/
goToPageAndXY_(origin, page, message) {
- this.viewport_.goToPageAndXY(page, message.x, message.y);
+ 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. Returns null if no promise has been created, which
- * is the case for initial load of the PDF.
- */
- get loaded() {
- 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`
- * promise if necessary.
- * @param {!LoadState} loadState
- * @private
- */
- setLoadState_(loadState) {
- 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) {
- this.loaded_.resolve();
- } else if (loadState === LoadState.FAILED) {
- this.loaded_.reject();
- } else {
- this.loaded_ = new PromiseResolver();
- }
- }
-
- /**
- * 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_(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_ && this.passwordScreen_.active) {
- this.passwordScreen_.deny();
- this.passwordScreen_.close();
- }
- this.setLoadState_(LoadState.FAILED);
- this.isPrintPreviewLoadingFinished_ = true;
- this.sendDocumentLoadedMessage_();
- } else if (progress === 100) {
- // Document load complete.
- if (this.lastViewportPosition_) {
- this.viewport_.position = this.lastViewportPosition_;
+ /** @override */
+ setLoadState(loadState) {
+ super.setLoadState(loadState);
+ if (loadState === LoadState.FAILED) {
+ const passwordScreen = this.$$('#password-screen');
+ if (passwordScreen && passwordScreen.active) {
+ passwordScreen.deny();
+ passwordScreen.close();
}
- this.paramsParser_.getViewportFromUrlParams(
- this.originalUrl_, params => this.handleURLParams_(params));
- this.setLoadState_(LoadState.SUCCESS);
- this.sendDocumentLoadedMessage_();
- while (this.delayedScriptingMessages_.length > 0) {
- this.handleScriptingMessage(this.delayedScriptingMessages_.shift());
- }
-
- this.toolbarManager_.hideToolbarsAfterTimeout();
- } else {
- this.setLoadState_(LoadState.LOADING);
}
}
- /** @private */
- 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_(strings) {
- const stringsDictionary =
- /** @type {{ textdirection: string, language: string }} */ (strings);
- document.documentElement.dir = stringsDictionary.textdirection;
- document.documentElement.lang = stringsDictionary.language;
-
- loadTimeData.data = strings;
-
- // Predefined zoom factors to be used when zooming in/out. These are in
- // ascending order.
- const presetZoomFactors = /** @type {!Array<number>} */ (
- JSON.parse(loadTimeData.getString('presetZoomFactors')));
- this.viewport_.setZoomFactorRange(presetZoomFactors);
-
- if (this.isPrintPreview_) {
- this.sendBackgroundColorForPrintPreview_();
- } else {
- $('toolbar').strings = strings;
- $('toolbar').pdfAnnotationsEnabled =
- loadTimeData.getBoolean('pdfAnnotationsEnabled');
- $('toolbar').printingEnabled = loadTimeData.getBoolean('printingEnabled');
- }
- $('zoom-toolbar').setStrings(strings);
- $('zoom-toolbar').twoUpViewEnabled =
- loadTimeData.getBoolean('pdfTwoUpViewEnabled') && !this.isPrintPreview_;
- // Display the zoom toolbar after the UI text direction is set, to ensure it
- // appears on the correct side of the PDF viewer.
- $('zoom-toolbar').hidden = false;
- if (this.passwordScreen_) {
- $('password-screen').strings = strings;
+ /** @override */
+ updateProgress(progress) {
+ if (this.toolbarEnabled_) {
+ this.getToolbar_().loadProgress = progress;
}
- $('error-screen').strings = strings;
- if ($('form-warning')) {
- $('form-warning').strings = strings;
+ super.updateProgress(progress);
+ if (progress === 100) {
+ this.toolbarManager_.hideToolbarsAfterTimeout();
}
}
@@ -964,73 +526,14 @@ export class PDFViewer {
* @private
*/
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_(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_(e) {
- // Throttle number of pinch events to one per frame.
- if (!this.sentPinchEvent_) {
- this.sentPinchEvent_ = true;
- window.requestAnimationFrame(() => {
- this.sentPinchEvent_ = false;
- 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_(e) {
- // Using rAF for pinch end prevents pinch updates scheduled by rAF getting
- // sent after the pinch end.
- window.requestAnimationFrame(() => {
- this.viewport_.pinchZoomEnd(e);
- });
+ this.pluginController.getPasswordComplete(event.detail.password);
}
- /**
- * A callback that's called when the start of a pinch zoom is detected.
- * @param {!Object} e the pinch event.
- * @private
- */
- 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_() {
- if (!this.documentDimensions_) {
- return;
- }
-
+ /** @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 hasScrollbars = this.viewport.documentHasScrollbars();
+ const scrollbarWidth = this.viewport.scrollbarWidth;
const verticalScrollbarWidth = hasScrollbars.vertical ? scrollbarWidth : 0;
const horizontalScrollbarWidth =
hasScrollbars.horizontal ? scrollbarWidth : 0;
@@ -1039,217 +542,70 @@ export class PDFViewer {
// 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 normally, and in LTR layout in Print Preview
- // when the NewPrintPreview flag is enabled, the zoom toolbar is on the left
+ // than the spec. In RTL layout normally, the zoom toolbar is on the left
// left side, but the scrollbar is still on the right, so this is not
// necessary.
- if (isRTL() === this.isPrintPreview_) {
- this.zoomToolbar_.style.right =
+ 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.
- this.zoomToolbar_.style.bottom = -horizontalScrollbarWidth + 'px';
+ zoomToolbar.style.bottom = -horizontalScrollbarWidth + 'px';
// Update the page indicator.
- const visiblePage = this.viewport_.getMostVisiblePage();
-
- if (this.toolbar_) {
- this.toolbar_.pageNo = visiblePage + 1;
- }
-
- // 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 && lastIndex !== undefined) {
- this.pageIndicator_.style.visibility = 'visible';
- } else {
- this.pageIndicator_.style.visibility = 'hidden';
- }
+ const visiblePage = this.viewport.getMostVisiblePage();
+ if (this.toolbarEnabled_) {
+ this.getToolbar_().pageNo = visiblePage + 1;
}
- this.currentController_.viewportChanged();
-
- const visiblePageDimensions = this.viewport_.getPageScreenRect(visiblePage);
- const size = this.viewport_.size;
- this.sendScriptingMessage_({
- type: 'viewport',
- pageX: visiblePageDimensions.x,
- pageY: visiblePageDimensions.y,
- pageWidth: visiblePageDimensions.width,
- viewportWidth: size.width,
- viewportHeight: size.height
- });
+ this.currentController.viewportChanged();
}
- /**
- * 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.
- */
+ /** @override */
handleScriptingMessage(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_();
- }
- }
+ super.handleScriptingMessage(message);
- 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);
+ if (this.delayScriptingMessage(message)) {
return;
}
switch (message.data.type.toString()) {
case 'getSelectedText':
- this.pluginController_.getSelectedText();
+ this.pluginController.getSelectedText();
break;
case 'print':
- this.pluginController_.print();
+ this.pluginController.print();
break;
case 'selectAll':
- this.pluginController_.selectAll();
+ this.pluginController.selectAll();
break;
}
}
- /**
- * 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) {
- if (!this.isPrintPreview_) {
- return false;
- }
-
- 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.zoomToolbar_.forceFit(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;
-
- // TODO(raymes): Disable these properly in the plugin.
- const printButton = $('print-button');
- if (printButton) {
- printButton.parentNode.removeChild(printButton);
- }
- const saveButton = $('save-button');
- if (saveButton) {
- saveButton.parentNode.removeChild(saveButton);
- }
-
- this.pageIndicator_.pageLabels = messageData.pageNumbers;
-
- this.pluginController_.resetPrintPreviewMode(messageData);
- return true;
- case 'sendKeyEvent':
- this.handleKeyEvent_(/** @type {!KeyboardEvent} */ (DeserializeKeyEvent(
- /** @type {{ keyEvent: Object }} */ (message.data).keyEvent)));
- return true;
- case 'hideToolbars':
- this.toolbarManager_.resetKeyboardNavigationAndHideToolbars();
- return true;
- case 'darkModeChanged':
- this.dark_ = /** @type {{darkMode: boolean}} */ (message.data).darkMode;
- if (this.isPrintPreview_) {
- this.sendBackgroundColorForPrintPreview_();
- }
- 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_.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_(message) {
- if (this.parentWindow_ && this.parentOrigin_) {
- let 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.originalUrl_;
- }
- try {
- this.parentWindow_.postMessage(message, targetOrigin);
- } catch (ok) {
- // TODO(crbug.com/1004425): targetOrigin probably was rejected, such as
- // a "data:" URL. This shouldn't cause this method to throw, though.
- }
- }
- }
-
- /**
- * @param {!CustomEvent<MessageData>} e
- * @private
- */
- handlePluginMessage_(e) {
+ /** @override */
+ handlePluginMessage(e) {
const data = e.detail;
switch (data.type.toString()) {
case 'beep':
this.handleBeep_();
return;
case 'documentDimensions':
- this.setDocumentDimensions_(
+ this.setDocumentDimensions(
/** @type {!DocumentDimensionsMessageData} */ (data));
return;
case 'getPassword':
this.handlePasswordRequest_();
return;
case 'getSelectedTextReply':
- this.handleSelectedTextReply_(
+ this.handleSelectedTextReply(
/** @type {{ selectedText: string }} */ (data).selectedText);
return;
case 'loadProgress':
- this.updateProgress_(
+ this.updateProgress(
/** @type {{ progress: number }} */ (data).progress);
return;
case 'navigate':
@@ -1258,52 +614,56 @@ export class PDFViewer {
return;
case 'navigateToDestination':
const destinationData = /** @type {!DestinationMessageData} */ (data);
- this.handleNavigateToDestination_(
+ this.viewport.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 'setIsEditing':
+ // Editing mode can only be entered once, and cannot be exited.
+ this.hasEdits_ = true;
+ return;
case 'setIsSelecting':
- this.setIsSelecting_(
+ this.viewportScroller.setEnableScrolling(
/** @type {{ isSelecting: boolean }} */ (data).isSelecting);
return;
case 'getNamedDestinationReply':
- this.paramsParser_.onNamedDestinationReceived(
+ this.paramsParser.onNamedDestinationReceived(
/** @type {{ pageNumber: number }} */ (data).pageNumber);
return;
case 'formFocusChange':
this.isFormFieldFocused_ =
/** @type {{ focused: boolean }} */ (data).focused;
return;
+ case 'touchSelectionOccurred':
+ this.sendScriptingMessage({
+ type: 'touchSelectionOccurred',
+ });
+ return;
+ case 'documentFocusChanged':
+ // TODO(crbug.com/1069370): Draw a focus rect around plugin.
+ return;
}
assertNotReached('Unknown message type received: ' + data.type);
}
- /**
- * Sets document dimensions from the current controller.
- * @param {!DocumentDimensionsMessageData} documentDimensions
- * @private
- */
- setDocumentDimensions_(documentDimensions) {
- this.documentDimensions_ = documentDimensions;
- this.isUserInitiatedEvent_ = false;
- this.viewport_.setDocumentDimensions(this.documentDimensions_);
- this.isUserInitiatedEvent_ = true;
+ /** @override */
+ setDocumentDimensions(documentDimensions) {
+ super.setDocumentDimensions(documentDimensions);
// If we received the document dimensions, the password was good so we
// can dismiss the password screen.
- if (this.passwordScreen_ && this.passwordScreen_.active) {
- this.passwordScreen_.close();
+ const passwordScreen = this.$$('#password-screen');
+ if (passwordScreen && passwordScreen.active) {
+ passwordScreen.close();
}
- if (this.toolbar_) {
- this.toolbar_.docLength = this.documentDimensions_.pageDimensions.length;
+ if (this.toolbarEnabled_) {
+ this.getToolbar_().docLength =
+ this.documentDimensions.pageDimensions.length;
}
}
@@ -1323,74 +683,24 @@ export class PDFViewer {
handlePasswordRequest_() {
// If the password screen isn't up, put it up. Otherwise we're
// responding to an incorrect password so deny it.
- assert(!!this.passwordScreen_);
- if (!this.passwordScreen_.active) {
+ const passwordScreen = this.$$('#password-screen');
+ assert(passwordScreen);
+ if (!passwordScreen.active) {
this.hadPassword_ = true;
- this.updateAnnotationAvailable_();
- this.passwordScreen_.show();
+ passwordScreen.show();
} else {
- this.passwordScreen_.deny();
+ passwordScreen.deny();
}
}
/**
- * Handles a selected text reply from the current controller.
- * @param {string} selectedText
- * @private
- */
- handleSelectedTextReply_(selectedText) {
- this.sendScriptingMessage_({
- type: 'getSelectedTextReply',
- selectedText: selectedText,
- });
- }
-
- /**
* Handles a navigation request from the current controller.
* @param {string} url
* @param {!PdfNavigator.WindowOpenDisposition} disposition
* @private
*/
handleNavigate_(url, disposition) {
- // If in print preview, always open a new tab.
- if (this.isPrintPreview_) {
- this.navigator_.navigate(
- url, PdfNavigator.WindowOpenDisposition.NEW_BACKGROUND_TAB);
- } 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_() {
- this.isPrintPreviewLoadingFinished_ = true;
- this.sendDocumentLoadedMessage_();
+ this.navigator_.navigate(url, disposition);
}
/**
@@ -1401,27 +711,10 @@ export class PDFViewer {
* @private
*/
setDocumentMetadata_(title, bookmarks, canSerializeDocument) {
- if (title) {
- document.title = title;
- } else {
- document.title = getFilenameFromURL(this.originalUrl_);
- }
+ this.title_ = title ? title : getFilenameFromURL(this.originalUrl);
+ document.title = this.title_;
this.bookmarks_ = bookmarks;
- if (this.toolbar_) {
- this.toolbar_.docTitle = document.title;
- this.toolbar_.bookmarks = this.bookmarks_;
- }
this.canSerializeDocument_ = canSerializeDocument;
- this.updateAnnotationAvailable_();
- }
-
- /**
- * Sets the is selecting flag from the current controller.
- * @param {boolean} isSelecting
- * @private
- */
- setIsSelecting_(isSelecting) {
- this.viewportScroller_.setEnableScrolling(isSelecting);
}
/**
@@ -1431,28 +724,58 @@ export class PDFViewer {
* @private
*/
async onSave_(streamUrl) {
- if (streamUrl !== this.browserApi_.getStreamInfo().streamUrl) {
+ if (streamUrl !== this.browserApi.getStreamInfo().streamUrl) {
return;
}
- this.save_();
+ let saveMode;
+ if (this.hasEnteredAnnotationMode_) {
+ saveMode = SaveRequestType.ANNOTATION;
+ } else if (
+ loadTimeData.getBoolean('pdfFormSaveEnabled') && this.hasEdits_) {
+ saveMode = SaveRequestType.EDITED;
+ } else {
+ saveMode = SaveRequestType.ORIGINAL;
+ }
+
+ this.save_(saveMode);
+ }
+
+ /**
+ * @param {!CustomEvent<!SaveRequestType>} e
+ * @private
+ */
+ onToolbarSave_(e) {
+ this.save_(e.detail);
}
/**
* Saves the current PDF document to disk.
+ * @param {SaveRequestType} requestType The type of save request.
* @private
*/
- async save_() {
+ async save_(requestType) {
PDFMetrics.record(PDFMetrics.UserAction.SAVE);
- if (this.hasEnteredAnnotationMode_) {
+ // If we have entered annotation mode we must require the local
+ // contents to ensure annotations are saved, unless the user specifically
+ // requested the original document. Otherwise we would save the cached
+ // remote copy without annotations.
+ if (requestType === SaveRequestType.ANNOTATION) {
PDFMetrics.record(PDFMetrics.UserAction.SAVE_WITH_ANNOTATION);
}
- // If we have entered annotation mode we must require the local
- // contents to ensure annotations are saved. Otherwise we would
- // save the cached or remote copy without annotatios.
- const requireResult = this.hasEnteredAnnotationMode_;
+ // Always send requests of type ORIGINAL to the plugin controller, not the
+ // ink controller. The ink controller always saves the edited document.
// TODO(dstockwell): Report an error to user if this fails.
- const result = await this.currentController_.save(requireResult);
+ let result;
+ if (requestType !== SaveRequestType.ORIGINAL || !this.annotationMode_) {
+ result = await this.currentController.save(requestType);
+ } else {
+ // Request type original in annotation mode --> need to exit annotation
+ // mode before saving. See https://crbug.com/919364.
+ await this.exitAnnotationMode_();
+ assert(!this.annotationMode_);
+ result = await this.currentController.save(SaveRequestType.ORIGINAL);
+ }
if (result == null) {
// The content controller handled the save internally.
return;
@@ -1465,7 +788,12 @@ export class PDFViewer {
}
chrome.fileSystem.chooseEntry(
- {type: 'saveFile', suggestedName: fileName}, entry => {
+ {
+ type: 'saveFile',
+ accepts: [{description: '*.pdf', extensions: ['pdf']}],
+ suggestedName: fileName
+ },
+ entry => {
if (chrome.runtime.lastError) {
if (chrome.runtime.lastError.message !== 'User cancelled') {
console.log(
@@ -1488,97 +816,83 @@ export class PDFViewer {
}
/** @private */
- async print_() {
+ async onPrint_() {
PDFMetrics.record(PDFMetrics.UserAction.PRINT);
await this.exitAnnotationMode_();
- this.currentController_.print();
+ this.currentController.print();
}
/**
* Updates the toolbar's annotation available flag depending on current
* conditions.
+ * @return {boolean} Whether annotations are available.
* @private
*/
- updateAnnotationAvailable_() {
- if (!this.toolbar_) {
- return;
- }
- let annotationAvailable = true;
- if (this.viewport_.getClockwiseRotations() !== 0) {
- annotationAvailable = false;
- }
- if (this.hadPassword_) {
- annotationAvailable = false;
- }
- if (!this.canSerializeDocument_) {
- annotationAvailable = false;
- }
- this.toolbar_.annotationAvailable = annotationAvailable;
+ computeAnnotationAvailable_() {
+ return this.canSerializeDocument_ && !this.rotated_ && !this.hadPassword_;
}
/** @private */
- rotateClockwise_() {
- PDFMetrics.record(PDFMetrics.UserAction.ROTATE);
- this.viewport_.rotateClockwise();
- this.currentController_.rotateClockwise();
- this.updateAnnotationAvailable_();
+ onUndo_() {
+ this.currentController.undo();
}
/** @private */
- rotateCounterclockwise_() {
- PDFMetrics.record(PDFMetrics.UserAction.ROTATE);
- this.viewport_.rotateCounterclockwise();
- this.currentController_.rotateCounterclockwise();
- this.updateAnnotationAvailable_();
+ onRedo_() {
+ this.currentController.redo();
+ }
+
+ /**
+ * @param {!CustomEvent<{value: AnnotationTool}>} e
+ * @private
+ */
+ onAnnotationToolChanged_(e) {
+ this.inkController_.setAnnotationTool(e.detail.value);
}
+ // <if expr="chromeos">
/**
* @param {!CustomEvent<{canUndo: boolean, canRedo: boolean}>} e
* @private
*/
setAnnotationUndoState_(e) {
- this.toolbar_.canUndoAnnotation = e.detail.canUndo;
- this.toolbar_.canRedoAnnotation = e.detail.canRedo;
+ this.getToolbar_().canUndoAnnotation = e.detail.canUndo;
+ this.getToolbar_().canRedoAnnotation = e.detail.canRedo;
}
+ // </if>
- /** @private */
- resetTrackers_() {
- this.viewport_.resetTracker();
- if (this.tracker_) {
- this.tracker_.removeAll();
- }
+ /** @override */
+ rotateClockwise() {
+ super.rotateClockwise();
+ this.rotated_ = this.viewport.getClockwiseRotations() !== 0;
}
-}
-// Export on |window| such that scripts injected from pdf_extension_test.cc can
-// access it.
-window.PDFViewer = PDFViewer;
+ /** @override */
+ rotateCounterclockwise() {
+ super.rotateCounterclockwise();
+ this.rotated_ = this.viewport.getClockwiseRotations() !== 0;
+ }
+}
/**
* The height of the toolbar along the top of the page. The document will be
* shifted down by this much in the viewport.
+ * @type {number}
*/
-PDFViewer.MATERIAL_TOOLBAR_HEIGHT = 56;
+const 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.
+ * @type {number}
*/
-PDFViewer.TOOLBAR_WINDOW_MIN_HEIGHT = 250;
-
-/**
- * The background color used for print preview (--google-grey-refresh-300).
- */
-PDFViewer.PRINT_PREVIEW_BACKGROUND_COLOR = '0xFFDADCE0';
-
-/**
- * The background color used for print preview when dark mode is enabled
- * (--google-grey-refresh-700).
- */
-PDFViewer.PRINT_PREVIEW_DARK_BACKGROUND_COLOR = '0xFF5F6368';
+const TOOLBAR_WINDOW_MIN_HEIGHT = 250;
/**
* The background color used for the regular viewer.
+ * @type {string}
*/
-PDFViewer.BACKGROUND_COLOR = '0xFF525659';
+const BACKGROUND_COLOR = '0xFF525659';
+
+customElements.define(PDFViewerElement.is, PDFViewerElement);
diff --git a/chromium/chrome/browser/resources/pdf/pdf_viewer_base.js b/chromium/chrome/browser/resources/pdf/pdf_viewer_base.js
new file mode 100644
index 00000000000..bb0c9a91d12
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/pdf_viewer_base.js
@@ -0,0 +1,640 @@
+// 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 {assert, assertNotReached} from 'chrome://resources/js/assert.m.js';
+import {EventTracker} from 'chrome://resources/js/event_tracker.m.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
+import {PromiseResolver} from 'chrome://resources/js/promise_resolver.m.js';
+import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {BrowserApi} from './browser_api.js';
+import {FittingType} from './constants.js';
+import {ContentController, MessageData, PluginController} from './controller.js';
+import {FitToChangedEvent} from './elements/viewer-zoom-toolbar.js';
+import {PDFMetrics} from './metrics.js';
+import {OpenPdfParamsParser} from './open_pdf_params_parser.js';
+import {LoadState} from './pdf_scripting_api.js';
+import {DocumentDimensionsMessageData, MessageObject} from './pdf_viewer_utils.js';
+import {Point, Viewport} from './viewport.js';
+import {ViewportScroller} from './viewport_scroller.js';
+import {ZoomManager} from './zoom_manager.js';
+
+/** @return {number} Width of a scrollbar in pixels */
+function getScrollbarWidth() {
+ const 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);
+ const result = div.offsetWidth - div.clientWidth;
+ div.parentNode.removeChild(div);
+ return result;
+}
+
+export class PDFViewerBaseElement extends PolymerElement {
+ static get is() {
+ return 'pdf-viewer-base';
+ }
+
+ static get template() {
+ return null;
+ }
+
+ static get properties() {
+ return {
+ strings: Object,
+ };
+ }
+
+ constructor() {
+ super();
+
+ /** @protected {Object|undefined} */
+ this.strings = undefined;
+
+ /** @protected {?BrowserApi} */
+ this.browserApi = null;
+
+ /** @protected {?ContentController} */
+ this.currentController = null;
+
+ /** @protected {string} */
+ this.originalUrl = '';
+
+ /** @protected {!EventTracker} */
+ this.tracker = new EventTracker();
+
+ /** @protected {boolean} */
+ this.isUserInitiatedEvent = true;
+
+ /** @protected {?Point} */
+ this.lastViewportPosition = null;
+
+ /** @protected {?OpenPdfParamsParser} */
+ this.paramsParser = null;
+
+ /** @protected {?ViewportScroller} */
+ this.viewportScroller = null;
+
+ /** @protected {?DocumentDimensionsMessageData} */
+ this.documentDimensions = null;
+
+ /** @private {boolean} */
+ this.overrideSendScriptingMessageForTest_ = false;
+
+ /** @private {!LoadState} */
+ this.loadState_ = LoadState.LOADING;
+
+ /** @private {?Object} */
+ this.parentWindow_ = null;
+
+ /** @private {?string} */
+ this.parentOrigin_ = null;
+
+ /** @private {!Array} */
+ this.delayedScriptingMessages_ = [];
+
+ /** @private {?PromiseResolver} */
+ this.loaded_ = null;
+
+ /** @private {boolean} */
+ this.initialLoadComplete_ = false;
+
+ /** @private {?Viewport} */
+ this.viewport_ = null;
+
+ /** @private {?PluginController} */
+ this.pluginController_ = null;
+
+ /** @private {?HTMLEmbedElement} */
+ this.plugin_ = null;
+
+ /** @private {?ZoomManager} */
+ this.zoomManager_ = null;
+ }
+
+ /** @return {number} The height of the top toolbar */
+ getToolbarHeight() {
+ return 0;
+ }
+
+ /**
+ * @return {!HTMLDivElement}
+ * @protected
+ */
+ getContent() {}
+
+ /**
+ * @return {!HTMLDivElement}
+ * @protected
+ */
+ getSizer() {}
+
+ /**
+ * @return {!ViewerZoomToolbarElement}
+ * @protected
+ */
+ getZoomToolbar() {}
+
+ /**
+ * @return {!ViewerErrorScreenElement}
+ * @protected
+ */
+ getErrorScreen() {}
+
+ /**
+ * @param {string} query
+ * @return {?Element}
+ * @protected
+ */
+ $$(query) {
+ return this.shadowRoot.querySelector(query);
+ }
+
+ /** @return {string} */
+ getBackgroundColor() {
+ return '';
+ }
+
+ /**
+ * @return {!HTMLEmbedElement} The plugin
+ * @private
+ */
+ createPlugin_() {
+ // 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.
+ const plugin =
+ /** @type {!HTMLEmbedElement} */ (document.createElement('embed'));
+
+ // NOTE: The plugin's 'id' field must be set to 'plugin' since
+ // ChromePrintRenderFrameHelperDeleage::GetPdfElement() in
+ // chrome/renderer/printing/chrome_print_render_frame_helper_delegate.cc
+ // actually references it.
+ plugin.id = 'plugin';
+ plugin.type = 'application/x-google-chrome-pdf';
+
+ plugin.setAttribute('src', this.originalUrl);
+ 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';
+ }
+ plugin.setAttribute('headers', headers);
+
+ plugin.setAttribute('background-color', this.getBackgroundColor());
+ plugin.setAttribute('top-toolbar-height', this.getToolbarHeight());
+
+ const javascript = this.browserApi.getStreamInfo().javascript || 'block';
+ plugin.setAttribute('javascript', javascript);
+
+ if (this.browserApi.getStreamInfo().embedded) {
+ plugin.setAttribute(
+ 'top-level-url', this.browserApi.getStreamInfo().tabUrl);
+ } else {
+ plugin.toggleAttribute('full-frame', true);
+ }
+
+ return plugin;
+ }
+
+ /** @param {!BrowserApi} browserApi */
+ init(browserApi) {
+ this.browserApi = browserApi;
+ this.originalUrl = this.browserApi.getStreamInfo().originalUrl;
+
+ PDFMetrics.record(PDFMetrics.UserAction.DOCUMENT_OPENED);
+
+ // Parse open pdf parameters.
+ this.paramsParser = new OpenPdfParamsParser(
+ destination => this.pluginController_.getNamedDestination(destination));
+
+ // Can only reload if we are in a normal tab.
+ if (chrome.tabs && this.browserApi.getStreamInfo().tabId !== -1) {
+ this.getErrorScreen().reloadFn = () => {
+ chrome.tabs.reload(this.browserApi.getStreamInfo().tabId);
+ };
+ }
+
+ // Create the viewport.
+ const defaultZoom =
+ this.browserApi.getZoomBehavior() === BrowserApi.ZoomBehavior.MANAGE ?
+ this.browserApi.getDefaultZoom() :
+ 1.0;
+ this.viewport_ = new Viewport(
+ window, this.getSizer(), this.getContent(), getScrollbarWidth(),
+ defaultZoom, this.getToolbarHeight());
+ 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_());
+
+ // 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);
+
+ // Create the plugin.
+ this.plugin_ = this.createPlugin_();
+ this.getContent().appendChild(this.plugin_);
+ this.pluginController_ = new PluginController(
+ this.plugin_, this.viewport_, () => this.isUserInitiatedEvent,
+ () => this.loaded);
+ this.currentController = this.pluginController_;
+ this.tracker.add(
+ this.pluginController_.getEventTarget(), 'plugin-message',
+ e => this.handlePluginMessage(e));
+
+ document.body.addEventListener('change-page-and-xy', e => {
+ const point = this.viewport_.convertPageToScreen(e.detail.page, e.detail);
+ this.viewport_.goToPageAndXY(e.detail.page, point.x, point.y);
+ });
+
+ // 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(assert(this.zoomManager_));
+ this.browserApi.addZoomEventListener(
+ zoom => this.zoomManager_.onBrowserZoomChange(zoom));
+
+ this.viewportScroller =
+ new ViewportScroller(this.viewport_, this.plugin_, window);
+
+ // Request translated strings.
+ chrome.resourcesPrivate.getStrings(
+ chrome.resourcesPrivate.Component.PDF,
+ strings => this.handleStrings(strings));
+ }
+
+ /**
+ * 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.
+ */
+ updateProgress(progress) {
+ if (progress === -1) {
+ // Document load failed.
+ this.getErrorScreen().show();
+ this.getSizer().style.display = 'none';
+ this.setLoadState(LoadState.FAILED);
+ this.sendDocumentLoadedMessage();
+ } else if (progress === 100) {
+ // Document load complete.
+ if (this.lastViewportPosition) {
+ this.viewport_.position = this.lastViewportPosition;
+ }
+ this.paramsParser.getViewportFromUrlParams(
+ this.originalUrl, params => this.handleURLParams_(params));
+ this.setLoadState(LoadState.SUCCESS);
+ this.sendDocumentLoadedMessage();
+ while (this.delayedScriptingMessages_.length > 0) {
+ this.handleScriptingMessage(this.delayedScriptingMessages_.shift());
+ }
+ } else {
+ this.setLoadState(LoadState.LOADING);
+ }
+ }
+
+ /** @return {boolean} Whether the documentLoaded message can be sent. */
+ readyToSendLoadMessage() {
+ return true;
+ }
+
+ /**
+ * Sends a 'documentLoaded' message to the PDFScriptingAPI if the document has
+ * finished loading.
+ */
+ sendDocumentLoadedMessage() {
+ if (this.loadState_ === LoadState.LOADING ||
+ !this.readyToSendLoadMessage()) {
+ return;
+ }
+ this.sendScriptingMessage(
+ {type: 'documentLoaded', load_state: this.loadState_});
+ }
+
+ /**
+ * Called to update the UI before sending the viewport scripting message.
+ * Should be overridden by subclasses.
+ * @protected
+ */
+ updateUIForViewportChange() {}
+
+ /**
+ * A callback that's called after the viewport changes.
+ * @private
+ */
+ viewportChanged_() {
+ if (!this.documentDimensions) {
+ return;
+ }
+
+ this.updateUIForViewportChange();
+
+ const visiblePage = this.viewport_.getMostVisiblePage();
+ const visiblePageDimensions = this.viewport_.getPageScreenRect(visiblePage);
+ const 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(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();
+ }
+ }
+ }
+
+ /**
+ * @param {!MessageObject} message The message to handle.
+ * @return {boolean} Whether the message was delayed and added to the queue.
+ */
+ delayScriptingMessage(message) {
+ // 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 true;
+ }
+ return false;
+ }
+
+ /**
+ * @param {!CustomEvent<MessageData>} e
+ * @protected
+ */
+ handlePluginMessage(e) {}
+
+ /**
+ * Sets document dimensions from the current controller.
+ * @param {!DocumentDimensionsMessageData} documentDimensions
+ * @protected
+ */
+ setDocumentDimensions(documentDimensions) {
+ this.documentDimensions = documentDimensions;
+ this.isUserInitiatedEvent = false;
+ this.viewport_.setDocumentDimensions(this.documentDimensions);
+ this.isUserInitiatedEvent = true;
+ }
+
+ /**
+ * @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_ ? this.loaded_.promise : null;
+ }
+
+ /** @return {!Viewport} */
+ get viewport() {
+ return assert(this.viewport_);
+ }
+
+ /**
+ * @return {!PluginController}
+ * @protected
+ */
+ get pluginController() {
+ return assert(this.pluginController_);
+ }
+
+ /**
+ * Updates the load state and triggers completion of the `loaded`
+ * promise if necessary.
+ * @param {!LoadState} loadState
+ * @protected
+ */
+ setLoadState(loadState) {
+ 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) {
+ this.loaded_.resolve();
+ } else if (loadState === LoadState.FAILED) {
+ this.loaded_.reject();
+ } else {
+ this.loaded_ = new PromiseResolver();
+ }
+ }
+
+ /**
+ * Load a dictionary of translated strings into the UI. Used as a callback for
+ * chrome.resourcesPrivate.
+ * @param {?Object} strings Dictionary of translated strings
+ * @protected
+ */
+ handleStrings(strings) {
+ if (!strings) {
+ return;
+ }
+ loadTimeData.data = strings;
+
+ // Predefined zoom factors to be used when zooming in/out. These are in
+ // ascending order.
+ const presetZoomFactors = /** @type {!Array<number>} */ (
+ JSON.parse(loadTimeData.getString('presetZoomFactors')));
+ this.viewport_.setZoomFactorRange(presetZoomFactors);
+
+ this.strings = strings;
+
+ // Display the zoom toolbar after the UI text direction is set, to ensure it
+ // appears on the correct side of the PDF viewer.
+ this.getZoomToolbar().hidden = false;
+ }
+
+ /**
+ * 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_(params) {
+ if (params.zoom) {
+ this.viewport_.setZoom(params.zoom);
+ }
+
+ if (params.position) {
+ this.viewport_.goToPageAndXY(
+ params.page ? params.page : 0, params.position.x, params.position.y);
+ } else if (params.page) {
+ this.viewport_.goToPage(params.page);
+ }
+
+ if (params.view) {
+ this.isUserInitiatedEvent = false;
+ this.getZoomToolbar().forceFit(params.view);
+ if (params.viewPosition) {
+ const zoomedPositionShift =
+ params.viewPosition * this.viewport_.getZoom();
+ const currentViewportPosition = this.viewport_.position;
+ if (params.view === FittingType.FIT_TO_WIDTH) {
+ currentViewportPosition.y += zoomedPositionShift;
+ } else if (params.view === FittingType.FIT_TO_HEIGHT) {
+ currentViewportPosition.x += zoomedPositionShift;
+ }
+ this.viewport_.position = currentViewportPosition;
+ }
+ this.isUserInitiatedEvent = true;
+ }
+ }
+
+ /**
+ * A callback that sets |isUserInitiatedEvent| to |userInitiated|.
+ * @param {boolean} userInitiated The value to set |isUserInitiatedEvent| to.
+ * @private
+ */
+ setUserInitiated_(userInitiated) {
+ assert(this.isUserInitiatedEvent !== userInitiated);
+ this.isUserInitiatedEvent = userInitiated;
+ }
+
+ overrideSendScriptingMessageForTest() {
+ this.overrideSendScriptingMessageForTest_ = true;
+ }
+
+ /**
+ * Send a scripting message outside the extension (typically to
+ * PDFScriptingAPI in a page containing the extension).
+ * @param {Object} message the message to send.
+ * @protected
+ */
+ sendScriptingMessage(message) {
+ if (this.parentWindow_ && this.parentOrigin_) {
+ let 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.originalUrl;
+ }
+ try {
+ this.parentWindow_.postMessage(message, targetOrigin);
+ } catch (ok) {
+ // TODO(crbug.com/1004425): targetOrigin probably was rejected, such as
+ // a "data:" URL. This shouldn't cause this method to throw, though.
+ }
+ }
+ }
+
+ /**
+ * Request to change the viewport fitting type.
+ * @param {!CustomEvent<FitToChangedEvent>} e
+ * @protected
+ */
+ onFitToChanged(e) {
+ if (e.detail.fittingType === FittingType.FIT_TO_PAGE) {
+ this.viewport_.fitToPage();
+ } else if (e.detail.fittingType === FittingType.FIT_TO_WIDTH) {
+ this.viewport_.fitToWidth();
+ } else if (e.detail.fittingType === FittingType.FIT_TO_HEIGHT) {
+ this.viewport_.fitToHeight();
+ }
+
+ if (e.detail.userInitiated) {
+ PDFMetrics.recordFitTo(e.detail.fittingType);
+ }
+ }
+
+ /** @protected */
+ onZoomIn() {
+ this.viewport_.zoomIn();
+ PDFMetrics.recordZoomAction(/*isZoomIn=*/ true);
+ }
+
+ /** @protected */
+ onZoomOut() {
+ this.viewport_.zoomOut();
+ PDFMetrics.recordZoomAction(/*isZoomIn=*/ false);
+ }
+
+ /**
+ * Handles a selected text reply from the current controller.
+ * @param {string} selectedText
+ * @protected
+ */
+ handleSelectedTextReply(selectedText) {
+ const message = {
+ type: 'getSelectedTextReply',
+ selectedText: selectedText,
+ };
+ if (this.overrideSendScriptingMessageForTest_) {
+ this.overrideSendScriptingMessageForTest_ = false;
+ try {
+ this.sendScriptingMessage(message);
+ } finally {
+ this.parentWindow_.postMessage('flush', '*');
+ }
+ return;
+ }
+ this.sendScriptingMessage(message);
+ }
+
+ /** @protected */
+ rotateClockwise() {
+ PDFMetrics.record(PDFMetrics.UserAction.ROTATE);
+ this.viewport_.rotateClockwise();
+ this.currentController.rotateClockwise();
+ }
+
+ /** @protected */
+ rotateCounterclockwise() {
+ PDFMetrics.record(PDFMetrics.UserAction.ROTATE);
+ this.viewport_.rotateCounterclockwise();
+ this.currentController.rotateCounterclockwise();
+ }
+
+ /** @private */
+ resetTrackers_() {
+ this.viewport_.resetTracker();
+ if (this.tracker) {
+ this.tracker.removeAll();
+ }
+ }
+}
+
+customElements.define(PDFViewerBaseElement.is, PDFViewerBaseElement);
diff --git a/chromium/chrome/browser/resources/pdf/pdf_viewer_pp.html b/chromium/chrome/browser/resources/pdf/pdf_viewer_pp.html
new file mode 100644
index 00000000000..dac20fda069
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/pdf_viewer_pp.html
@@ -0,0 +1,21 @@
+<style include="pdf-viewer-shared-style">
+ viewer-page-indicator {
+ opacity: 0;
+ visibility: hidden;
+ z-index: 2;
+ }
+</style>
+
+<div id="sizer"></div>
+
+<viewer-zoom-toolbar id="zoom-toolbar" strings="[[strings]]"
+ on-fit-to-changed="onFitToChanged" is-print-preview
+ on-zoom-in="onZoomIn" on-zoom-out="onZoomOut"
+ hidden>
+</viewer-zoom-toolbar>
+
+<viewer-error-screen id="error-screen"></viewer-error-screen>
+
+<viewer-page-indicator id="page-indicator"></viewer-page-indicator>
+
+<div id="content"></div>
diff --git a/chromium/chrome/browser/resources/pdf/pdf_viewer_pp.js b/chromium/chrome/browser/resources/pdf/pdf_viewer_pp.js
new file mode 100644
index 00000000000..6fa9a6f3e3c
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/pdf_viewer_pp.js
@@ -0,0 +1,379 @@
+// 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-screen.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, PrintPreviewParams} from './controller.js';
+import {DeserializeKeyEvent, LoadState, SerializeKeyEvent} from './pdf_scripting_api.js';
+import {PDFViewerBaseElement} from './pdf_viewer_base.js';
+import {DestinationMessageData, DocumentDimensionsMessageData, 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 */
+ getContent() {
+ return /** @type {!HTMLDivElement} */ (this.$$('#content'));
+ }
+
+ /** @override */
+ getSizer() {
+ return /** @type {!HTMLDivElement} */ (this.$$('#sizer'));
+ }
+
+ /** @override */
+ getZoomToolbar() {
+ return /** @type {!ViewerZoomToolbarElement} */ (this.$$('#zoom-toolbar'));
+ }
+
+ /** @override */
+ getErrorScreen() {
+ return /** @type {!ViewerErrorScreenElement} */ (this.$$('#error-screen'));
+ }
+
+ /** @override */
+ getBackgroundColor() {
+ return PRINT_PREVIEW_BACKGROUND_COLOR;
+ }
+
+ /** @param {!BrowserApi} browserApi */
+ init(browserApi) {
+ super.init(browserApi);
+
+ this.toolbarManager_ =
+ new ToolbarManager(window, null, this.getZoomToolbar());
+
+ // Setup the keyboard event listener.
+ document.addEventListener(
+ 'keydown',
+ e => this.handleKeyEvent_(/** @type {!KeyboardEvent} */ (e)));
+ }
+
+ /**
+ * Handle key events. These may come from the user directly or via the
+ * scripting API.
+ * @param {!KeyboardEvent} e the event to handle.
+ * @private
+ */
+ handleKeyEvent_(e) {
+ if (shouldIgnoreKeyEvents(document.activeElement) || e.defaultPrevented) {
+ return;
+ }
+
+ this.toolbarManager_.hideToolbarsAfterTimeout();
+ // Let the viewport handle directional key events.
+ if (this.viewport.handleDirectionalKeyEvent(e, false)) {
+ return;
+ }
+
+ switch (e.key) {
+ case 'Tab':
+ this.toolbarManager_.showToolbarsForKeyboardNavigation();
+ return;
+ case 'Escape':
+ break; // Ensure escape falls through to the print-preview handler.
+ case 'a':
+ if (e.ctrlKey || e.metaKey) {
+ this.pluginController.selectAll();
+ // Since we do selection ourselves.
+ e.preventDefault();
+ }
+ return;
+ case '[':
+ if (e.ctrlKey) {
+ this.rotateCounterclockwise();
+ }
+ return;
+ case '\\':
+ if (e.ctrlKey) {
+ this.getZoomToolbar().fitToggleFromHotKey();
+ }
+ return;
+ case ']':
+ if (e.ctrlKey) {
+ this.rotateClockwise();
+ }
+ return;
+ }
+
+ // Give print preview a chance to handle the key event.
+ if (!e.fromScriptingAPI) {
+ this.sendScriptingMessage(
+ {type: 'sendKeyEvent', keyEvent: SerializeKeyEvent(e)});
+ } else {
+ // Show toolbars as a fallback.
+ if (!(e.shiftKey || e.ctrlKey || e.altKey)) {
+ this.toolbarManager_.showToolbars();
+ }
+ }
+ }
+
+ /** @private */
+ sendBackgroundColorForPrintPreview_() {
+ this.pluginController.backgroundColorChanged(
+ 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) {
+ super.handleScriptingMessage(message);
+
+ if (this.handlePrintPreviewScriptingMessage_(message)) {
+ return;
+ }
+
+ if (this.delayScriptingMessage(message)) {
+ return;
+ }
+
+ switch (message.data.type.toString()) {
+ case 'getSelectedText':
+ this.pluginController.getSelectedText();
+ break;
+ case 'selectAll':
+ this.pluginController.selectAll();
+ break;
+ }
+ }
+
+ /**
+ * 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.getZoomToolbar().forceFit(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':
+ this.handleKeyEvent_(/** @type {!KeyboardEvent} */ (DeserializeKeyEvent(
+ /** @type {{ keyEvent: Object }} */ (message.data).keyEvent)));
+ return true;
+ case 'hideToolbars':
+ this.toolbarManager_.resetKeyboardNavigationAndHideToolbars();
+ return true;
+ case 'darkModeChanged':
+ this.dark_ = /** @type {{darkMode: boolean}} */ (message.data).darkMode;
+ this.sendBackgroundColorForPrintPreview_();
+ 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.position = 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 'getSelectedTextReply':
+ this.handleSelectedTextReply(
+ /** @type {{ selectedText: string }} */ (data).selectedText);
+ 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 'getNamedDestinationReply':
+ this.paramsParser.onNamedDestinationReceived(
+ /** @type {{ pageNumber: number }} */ (data).pageNumber);
+ return;
+ case 'touchSelectionOccurred':
+ this.sendScriptingMessage({
+ type: 'touchSelectionOccurred',
+ });
+ return;
+ case 'documentFocusChanged':
+ // TODO(crbug.com/1069370): Draw a focus rect around plugin.
+ 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 */
+ handleStrings(strings) {
+ super.handleStrings(strings);
+
+ if (!strings) {
+ return;
+ }
+ this.sendBackgroundColorForPrintPreview_();
+ }
+
+ /** @override */
+ updateProgress(progress) {
+ super.updateProgress(progress);
+ if (progress === 100) {
+ this.toolbarManager_.hideToolbarsAfterTimeout();
+ }
+ }
+}
+
+/**
+ * The background color used for print preview (--google-grey-refresh-300).
+ * @type {string}
+ */
+const PRINT_PREVIEW_BACKGROUND_COLOR = '0xFFDADCE0';
+
+/**
+ * The background color used for print preview when dark mode is enabled
+ * (--google-grey-refresh-700).
+ * @type {string}
+ */
+const PRINT_PREVIEW_DARK_BACKGROUND_COLOR = '0xFF5F6368';
+
+customElements.define(PDFViewerPPElement.is, PDFViewerPPElement);
diff --git a/chromium/chrome/browser/resources/pdf/pdf_viewer_shared_style.html b/chromium/chrome/browser/resources/pdf/pdf_viewer_shared_style.html
new file mode 100644
index 00000000000..ba2a5e4cf9b
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/pdf_viewer_shared_style.html
@@ -0,0 +1,33 @@
+<template>
+ <style>
+ #content {
+ height: 100%;
+ position: fixed;
+ width: 100%;
+ z-index: 1;
+ }
+
+ #plugin {
+ height: 100%;
+ position: absolute;
+ width: 100%;
+ }
+
+ #sizer {
+ position: absolute;
+ z-index: 0;
+ }
+
+ @media(max-height: 200px) {
+ viewer-zoom-toolbar {
+ display: none;
+ }
+ }
+
+ @media(max-width: 300px) {
+ viewer-zoom-toolbar {
+ display: none;
+ }
+ }
+ </style>
+</template>
diff --git a/chromium/chrome/browser/resources/pdf/pdf_viewer_shared_style.js b/chromium/chrome/browser/resources/pdf/pdf_viewer_shared_style.js
new file mode 100644
index 00000000000..d7411e11a3a
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/pdf_viewer_shared_style.js
@@ -0,0 +1,11 @@
+// Copyright 2020 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 {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+const template = document.createElement('template');
+template.innerHTML = `
+<dom-module id="pdf-viewer-shared-style">{__html_template__}</dom-module>
+`;
+document.body.appendChild(template.content.cloneNode(true));
diff --git a/chromium/chrome/browser/resources/pdf/pdf_viewer_utils.js b/chromium/chrome/browser/resources/pdf/pdf_viewer_utils.js
new file mode 100644
index 00000000000..2250aa80637
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/pdf_viewer_utils.js
@@ -0,0 +1,64 @@
+// Copyright 2020 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 {MessageData} from './controller.js';
+import {LayoutOptions} from './viewport.js';
+
+/**
+ * @typedef {{
+ * source: Object,
+ * origin: string,
+ * data: !MessageData,
+ * }}
+ */
+export let MessageObject;
+
+/**
+ * @typedef {{
+ * type: string,
+ * height: number,
+ * width: number,
+ * layoutOptions: (!LayoutOptions|undefined),
+ * pageDimensions: Array
+ * }}
+ */
+export let DocumentDimensionsMessageData;
+
+/**
+ * @typedef {{
+ * type: string,
+ * page: number,
+ * x: number,
+ * y: number,
+ * zoom: number
+ * }}
+ */
+export let DestinationMessageData;
+
+/**
+ * @typedef {{
+ * hasUnsavedChanges: (boolean|undefined),
+ * fileName: string,
+ * dataToSave: !ArrayBuffer
+ * }}
+ */
+export let RequiredSaveResult;
+
+/**
+ * Whether keydown events should currently be ignored. Events are ignored when
+ * an editable element has focus, to allow for proper editing controls.
+ * @param {Element} activeElement The currently selected DOM node.
+ * @return {boolean} True if keydown events should be ignored.
+ */
+export function shouldIgnoreKeyEvents(activeElement) {
+ while (activeElement.shadowRoot != null &&
+ activeElement.shadowRoot.activeElement != null) {
+ activeElement = activeElement.shadowRoot.activeElement;
+ }
+
+ return (
+ activeElement.isContentEditable ||
+ (activeElement.tagName === 'INPUT' && activeElement.type !== 'radio') ||
+ activeElement.tagName === 'TEXTAREA');
+}
diff --git a/chromium/chrome/browser/resources/pdf/toolbar_manager.js b/chromium/chrome/browser/resources/pdf/toolbar_manager.js
index 847b21172fc..f4b7aade0b4 100644
--- a/chromium/chrome/browser/resources/pdf/toolbar_manager.js
+++ b/chromium/chrome/browser/resources/pdf/toolbar_manager.js
@@ -4,20 +4,42 @@
import {isRTL} from 'chrome://resources/js/util.m.js';
-/** Idle time in ms before the UI is hidden. */
+/**
+ * Idle time in ms before the UI is hidden.
+ * @type {number}
+ */
const HIDE_TIMEOUT = 2000;
-/** Time in ms after force hide before toolbar is shown again. */
+
+/**
+ * Time in ms after force hide before toolbar is shown again.
+ * @type {number}
+ */
const FORCE_HIDE_TIMEOUT = 1000;
+
/**
* Velocity required in a mousemove to reveal the UI (pixels/ms). This is
* intended to be high enough that a fast flick of the mouse is required to
* reach it.
+ * @type {number}
*/
const SHOW_VELOCITY = 10;
-/** Distance from the top of the screen required to reveal the toolbars. */
+
+/**
+ * Distance from the top of the screen required to reveal the toolbars.
+ * @type {number}
+ */
const TOP_TOOLBAR_REVEAL_DISTANCE = 100;
-/** Distance from the bottom-right of the screen required to reveal toolbars. */
+
+/**
+ * Distance from right of the screen required to reveal toolbars.
+ * @type {number}
+ */
const SIDE_TOOLBAR_REVEAL_DISTANCE_RIGHT = 150;
+
+/**
+ * Distance from bottom of the screen required to reveal toolbars.
+ * @type {number}
+ */
const SIDE_TOOLBAR_REVEAL_DISTANCE_BOTTOM = 250;
/**
@@ -45,7 +67,7 @@ function isMouseNearSideToolbar(e, window, reverse) {
return atSide && atBottom;
}
-/** Responsible for co-ordinating between multiple toolbar elements. */
+// Responsible for co-ordinating between multiple toolbar elements.
export class ToolbarManager {
/**
* @param {!Window} window The window containing the UI.
@@ -89,6 +111,12 @@ export class ToolbarManager {
this.window_.addEventListener('resize', this.resizeDropdowns_.bind(this));
this.resizeDropdowns_();
+ document.addEventListener(
+ 'mousemove',
+ e => this.handleMouseMove_(/** @type {!MouseEvent} */ (e)));
+ document.addEventListener(
+ 'mouseout', () => this.hideToolbarsForMouseOut_());
+
if (this.isPrintPreview_) {
this.zoomToolbar_.addEventListener('keyboard-navigation-active', e => {
this.keyboardNavigationActive = e.detail;
@@ -96,8 +124,11 @@ export class ToolbarManager {
}
}
- /** @param {!MouseEvent} e */
- handleMouseMove(e) {
+ /**
+ * @param {!MouseEvent} e
+ * @private
+ */
+ handleMouseMove_(e) {
this.isMouseNearTopToolbar_ = !!this.toolbar_ && isMouseNearTopToolbar(e);
this.isMouseNearSideToolbar_ =
isMouseNearSideToolbar(e, this.window_, this.isPrintPreview_);
@@ -192,8 +223,9 @@ export class ToolbarManager {
/**
* Hide toolbars after a delay, regardless of the position of the mouse.
* Intended to be called when the mouse has moved out of the parent window.
+ * @private
*/
- hideToolbarsForMouseOut() {
+ hideToolbarsForMouseOut_() {
this.isMouseNearTopToolbar_ = false;
this.isMouseNearSideToolbar_ = false;
this.hideToolbarsAfterTimeout();
diff --git a/chromium/chrome/browser/resources/pdf/viewport.js b/chromium/chrome/browser/resources/pdf/viewport.js
index 37967176729..3e68d39f673 100644
--- a/chromium/chrome/browser/resources/pdf/viewport.js
+++ b/chromium/chrome/browser/resources/pdf/viewport.js
@@ -4,9 +4,10 @@
import {assert} from 'chrome://resources/js/assert.m.js';
import {EventTracker} from 'chrome://resources/js/event_tracker.m.js';
-import {$} from 'chrome://resources/js/util.m.js';
+import {$, hasKeyModifiers} from 'chrome://resources/js/util.m.js';
import {FittingType} from './constants.js';
+import {GestureDetector, PinchEventDetail} from './gesture_detector.js';
import {InactiveZoomManager, ZoomManager} from './zoom_manager.js';
/**
@@ -66,35 +67,29 @@ function vectorDelta(p1, p2) {
return {x: p2.x - p1.x, y: p2.y - p1.y};
}
-/**
- * @param {!Point} coordinateInFrame
- * @return {!Point} Coordinate converted to plugin coordinates.
- */
-function frameToPluginCoordinate(coordinateInFrame) {
- const container = $('plugin');
- return {
- x: coordinateInFrame.x - container.getBoundingClientRect().left,
- y: coordinateInFrame.y - container.getBoundingClientRect().top
- };
-}
-
export class Viewport {
/**
* @param {!Window} window
* @param {!HTMLDivElement} sizer The element which represents the size of the
* document in the viewport
+ * @param {!HTMLDivElement} content The element which is the parent of the
+ * plugin in the viewer.
* @param {number} scrollbarWidth The width of scrollbars on the page
* @param {number} defaultZoom The default zoom level.
* @param {number} topToolbarHeight The number of pixels that should initially
* be left blank above the document for the toolbar.
*/
- constructor(window, sizer, scrollbarWidth, defaultZoom, topToolbarHeight) {
+ constructor(
+ window, sizer, content, scrollbarWidth, defaultZoom, topToolbarHeight) {
/** @private {!Window} */
this.window_ = window;
/** @private {!HTMLDivElement} */
this.sizer_ = sizer;
+ /** @private {!HTMLDivElement} */
+ this.content_ = content;
+
/** @private {number} */
this.scrollbarWidth_ = scrollbarWidth;
@@ -168,11 +163,33 @@ export class Viewport {
/** @private {!EventTracker} */
this.tracker_ = new EventTracker();
+ /** @private {!GestureDetector} */
+ this.gestureDetector_ = new GestureDetector(this.content_);
+
+ /** @private {boolean} */
+ this.sentPinchEvent_ = false;
+
+ this.gestureDetector_.getEventTarget().addEventListener(
+ 'pinchstart',
+ e => this.onPinchStart_(
+ /** @type {!CustomEvent<!PinchEventDetail>} */ (e)));
+ this.gestureDetector_.getEventTarget().addEventListener(
+ 'pinchupdate',
+ e => this.onPinchUpdate_(
+ /** @type {!CustomEvent<!PinchEventDetail>} */ (e)));
+ this.gestureDetector_.getEventTarget().addEventListener(
+ 'pinchend',
+ e => this.onPinchEnd_(
+ /** @type {!CustomEvent<!PinchEventDetail>} */ (e)));
+
// Set to a default zoom manager - used in tests.
this.setZoomManager(new InactiveZoomManager(this.getZoom.bind(this), 1));
window.addEventListener('scroll', this.updateViewport_.bind(this));
window.addEventListener('resize', this.resizeWrapper_.bind(this));
+
+ document.body.addEventListener(
+ 'change-zoom', e => this.setZoom(e.detail.zoom));
}
/** @param {function():void} viewportChangedCallback */
@@ -288,8 +305,8 @@ export class Viewport {
y: point.y * pointsToPixels,
});
return {
- x: result.x + Viewport.PAGE_SHADOW.left,
- y: result.y + Viewport.PAGE_SHADOW.top,
+ x: result.x + PAGE_SHADOW.left,
+ y: result.y + PAGE_SHADOW.top,
};
}
@@ -348,9 +365,9 @@ export class Viewport {
* @param {number} zoom Zoom to compute scrollbars for
* @return {{horizontal: boolean, vertical: boolean}} Whether horizontal or
* vertical scrollbars are needed.
- * @private
+ * Public so tests can call it directly.
*/
- documentNeedsScrollbars_(zoom) {
+ documentNeedsScrollbars(zoom) {
const zoomedDimensions = this.getZoomedDocumentDimensions_(zoom);
if (!zoomedDimensions) {
return {horizontal: false, vertical: false};
@@ -376,7 +393,7 @@ export class Viewport {
* vertical scrollbars are needed.
*/
documentHasScrollbars() {
- return this.documentNeedsScrollbars_(this.getZoom());
+ return this.documentNeedsScrollbars(this.getZoom());
}
/**
@@ -394,6 +411,19 @@ export class Viewport {
}
/**
+ * @param {!Point} coordinateInFrame
+ * @return {!Point} Coordinate converted to plugin coordinates.
+ * @private
+ */
+ frameToPluginCoordinate_(coordinateInFrame) {
+ const container = this.content_.querySelector('#plugin');
+ return {
+ x: coordinateInFrame.x - container.getBoundingClientRect().left,
+ y: coordinateInFrame.y - container.getBoundingClientRect().top
+ };
+ }
+
+ /**
* Called when the viewport should be updated.
* @private
*/
@@ -447,7 +477,7 @@ export class Viewport {
/** @return {!Size} the size of the viewport excluding scrollbars. */
get size() {
- const needsScrollbars = this.documentNeedsScrollbars_(this.getZoom());
+ const needsScrollbars = this.documentNeedsScrollbars(this.getZoom());
const scrollbarWidth = needsScrollbars.vertical ? this.scrollbarWidth_ : 0;
const scrollbarHeight =
needsScrollbars.horizontal ? this.scrollbarWidth_ : 0;
@@ -589,9 +619,7 @@ export class Viewport {
};
}
- /**
- * @param {number} newZoom The zoom level to zoom to.
- */
+ /** @param {number} newZoom The zoom level to zoom to. */
setZoom(newZoom) {
this.fittingType_ = FittingType.NONE;
this.mightZoom_(() => {
@@ -775,7 +803,7 @@ export class Viewport {
pageDimensions.width, pageDimensions.height);
// Check if there needs to be any scrollbars.
- const needsScrollbars = this.documentNeedsScrollbars_(zoom);
+ const needsScrollbars = this.documentNeedsScrollbars(zoom);
// If the document fits, just return the zoom.
if (!needsScrollbars.horizontal && !needsScrollbars.vertical) {
@@ -992,84 +1020,152 @@ export class Viewport {
}
/**
- * Pinch zoom event handler.
- * @param {!Object} e The pinch event.
+ * @param {!KeyboardEvent} e
+ * @private
*/
- pinchZoom(e) {
- this.mightZoom_(() => {
- this.pinchPhase_ = e.direction === 'out' ?
- Viewport.PinchPhase.PINCH_UPDATE_ZOOM_OUT :
- Viewport.PinchPhase.PINCH_UPDATE_ZOOM_IN;
-
- const scaleDelta = e.startScaleRatio / this.prevScale_;
- if (this.firstPinchCenterInFrame_ != null) {
- this.pinchPanVector_ =
- vectorDelta(e.center, this.firstPinchCenterInFrame_);
- }
+ pageUpHandler_(e) {
+ // Go to the previous page if we are fit-to-page or fit-to-height.
+ if (this.isPagedMode_()) {
+ this.goToPreviousPage();
+ // Since we do the movement of the page.
+ e.preventDefault();
+ } else if (
+ /** @type {!{fromScriptingAPI: (boolean|undefined)}} */ (e)
+ .fromScriptingAPI) {
+ this.position.y -= this.size.height;
+ }
+ }
- const needsScrollbars =
- this.documentNeedsScrollbars_(this.zoomManager_.applyBrowserZoom(
- this.clampZoom_(this.internalZoom_ * scaleDelta)));
-
- this.pinchCenter_ = e.center;
-
- // If there's no horizontal scrolling, keep the content centered so the
- // user can't zoom in on the non-content area.
- // TODO(mcnee) Investigate other ways of scaling when we don't have
- // horizontal scrolling. We want to keep the document centered,
- // but this causes a potentially awkward transition when we start
- // using the gesture center.
- if (!needsScrollbars.horizontal) {
- this.pinchCenter_ = {
- x: this.window_.innerWidth / 2,
- y: this.window_.innerHeight / 2
- };
- } else if (this.keepContentCentered_) {
- this.oldCenterInContent_ =
- this.frameToContent_(frameToPluginCoordinate(e.center));
- this.keepContentCentered_ = false;
- }
+ /**
+ * @param {!KeyboardEvent} e
+ * @private
+ */
+ pageDownHandler_(e) {
+ // Go to the next page if we are fit-to-page or fit-to-height.
+ if (this.isPagedMode_()) {
+ this.goToNextPage();
+ // Since we do the movement of the page.
+ e.preventDefault();
+ } else if (
+ /** @type {!{fromScriptingAPI: (boolean|undefined)}} */ (e)
+ .fromScriptingAPI) {
+ this.position.y += this.size.height;
+ }
+ }
- this.setPinchZoomInternal_(scaleDelta, frameToPluginCoordinate(e.center));
- this.updateViewport_();
- this.prevScale_ = e.startScaleRatio;
- });
+ /**
+ * @param {!KeyboardEvent} e
+ * @param {boolean} formFieldFocused
+ * @private
+ */
+ arrowLeftHandler_(e, formFieldFocused) {
+ if (hasKeyModifiers(e)) {
+ return;
+ }
+
+ // Go to the previous page if there are no horizontal scrollbars and
+ // no form field is focused.
+ if (!(this.documentHasScrollbars().horizontal || formFieldFocused)) {
+ this.goToPreviousPage();
+ // Since we do the movement of the page.
+ e.preventDefault();
+ } else if (
+ /** @type {!{fromScriptingAPI: (boolean|undefined)}} */ (e)
+ .fromScriptingAPI) {
+ this.position.x -= SCROLL_INCREMENT;
+ }
}
- /** @param {!Object} e The pinch event. */
- pinchZoomStart(e) {
- this.pinchPhase_ = Viewport.PinchPhase.PINCH_START;
- this.prevScale_ = 1;
- this.oldCenterInContent_ =
- this.frameToContent_(frameToPluginCoordinate(e.center));
+ /**
+ * @param {!KeyboardEvent} e
+ * @param {boolean} formFieldFocused
+ * @private
+ */
+ arrowRightHandler_(e, formFieldFocused) {
+ if (hasKeyModifiers(e)) {
+ return;
+ }
- const needsScrollbars = this.documentNeedsScrollbars_(this.getZoom());
- this.keepContentCentered_ = !needsScrollbars.horizontal;
- // We keep track of begining of the pinch.
- // By doing so we will be able to compute the pan distance.
- this.firstPinchCenterInFrame_ = e.center;
+ // Go to the next page if there are no horizontal scrollbars and no
+ // form field is focused.
+ if (!(this.documentHasScrollbars().horizontal || formFieldFocused)) {
+ this.goToNextPage();
+ // Since we do the movement of the page.
+ e.preventDefault();
+ } else if (
+ /** @type {!{fromScriptingAPI: (boolean|undefined)}} */ (e)
+ .fromScriptingAPI) {
+ this.position.x += SCROLL_INCREMENT;
+ }
}
- /** @param {!Object} e The pinch event. */
- pinchZoomEnd(e) {
- this.mightZoom_(() => {
- this.pinchPhase_ = Viewport.PinchPhase.PINCH_END;
- const scaleDelta = e.startScaleRatio / this.prevScale_;
- this.pinchCenter_ = /** @type {!Point} */ (e.center);
+ /**
+ * @param {boolean} fromScriptingAPI
+ * @private
+ */
+ arrowDownHandler_(fromScriptingAPI) {
+ if (fromScriptingAPI) {
+ this.position.y += SCROLL_INCREMENT;
+ }
+ }
- this.setPinchZoomInternal_(scaleDelta, frameToPluginCoordinate(e.center));
- this.updateViewport_();
- });
+ /**
+ * @param {boolean} fromScriptingAPI
+ * @private
+ */
+ arrowUpHandler_(fromScriptingAPI) {
+ if (fromScriptingAPI) {
+ this.position.y -= SCROLL_INCREMENT;
+ }
+ }
- this.pinchPhase_ = Viewport.PinchPhase.PINCH_NONE;
- this.pinchPanVector_ = null;
- this.pinchCenter_ = null;
- this.firstPinchCenterInFrame_ = null;
+ /**
+ * Handle certain directional key events.
+ * @param {!KeyboardEvent} e the event to handle.
+ * @param {boolean} formFieldFocused Whether a form field is currently
+ * focused.
+ * @return {boolean} Whether the event was handled.
+ */
+ handleDirectionalKeyEvent(e, formFieldFocused) {
+ // Certain scroll events may be sent from outside of the extension.
+ const fromScriptingAPI =
+ /** @type {!{fromScriptingAPI: (boolean|undefined)}} */ (e)
+ .fromScriptingAPI;
+
+ switch (e.key) {
+ case '':
+ if (e.shiftKey) {
+ this.pageUpHandler_(e);
+ } else {
+ this.pageDownHandler_(e);
+ }
+ return true;
+ case 'PageUp':
+ this.pageUpHandler_(e);
+ return true;
+ case 'PageDown':
+ this.pageDownHandler_(e);
+ return true;
+ case 'ArrowLeft':
+ this.arrowLeftHandler_(e, formFieldFocused);
+ return true;
+ case 'ArrowUp':
+ this.arrowUpHandler_(!!fromScriptingAPI);
+ return true;
+ case 'ArrowRight':
+ this.arrowRightHandler_(e, formFieldFocused);
+ return true;
+ case 'ArrowDown':
+ this.arrowDownHandler_(!!fromScriptingAPI);
+ return true;
+ default:
+ return false;
+ }
}
/**
* Go to the next page. If the document is in two-up view, go to the left page
- * of the next row.
+ * of the next row. Public for tests.
*/
goToNextPage() {
const currentPage = this.getMostVisiblePage();
@@ -1080,7 +1176,7 @@ export class Viewport {
/**
* Go to the previous page. If the document is in two-up view, go to the left
- * page of the previous row.
+ * page of the previous row. Public for tests.
*/
goToPreviousPage() {
const currentPage = this.getMostVisiblePage();
@@ -1123,7 +1219,7 @@ export class Viewport {
// Unless we're in fit to page or fit to height mode, scroll above the
// page by |this.topToolbarHeight_| so that the toolbar isn't covering it
// initially.
- if (!this.isPagedMode()) {
+ if (!this.isPagedMode_()) {
toolbarOffset = this.topToolbarHeight_;
}
this.position = {
@@ -1160,7 +1256,7 @@ export class Viewport {
*/
getPageInsetDimensions(page) {
const pageDimensions = this.pageDimensions_[page];
- const shadow = Viewport.PAGE_SHADOW;
+ const shadow = PAGE_SHADOW;
return {
x: pageDimensions.x + shadow.left,
y: pageDimensions.y + shadow.top,
@@ -1192,7 +1288,7 @@ export class Viewport {
// TODO(raymes): This should really be set when the PDF plugin passes the
// page coordinates, but it isn't yet.
const x = (this.documentDimensions_.width - pageDimensions.width) / 2 +
- Viewport.PAGE_SHADOW.left;
+ PAGE_SHADOW.left;
// Compute the space on the left of the document if the document fits
// completely in the screen.
const zoom = this.getZoom();
@@ -1213,14 +1309,34 @@ export class Viewport {
* In a paged mode, page up and page down scroll to the top of the
* previous/next page and part of the page is under the toolbar.
* @return {boolean} Whether the current fitting type is a paged mode.
+ * @private
*/
- isPagedMode() {
+ isPagedMode_() {
return (
this.fittingType_ === FittingType.FIT_TO_PAGE ||
this.fittingType_ === FittingType.FIT_TO_HEIGHT);
}
/**
+ * Handles a navigation request to a destination from the current controller.
+ * @param {number} page
+ * @param {number} x
+ * @param {number} y
+ * @param {number} zoom
+ */
+ handleNavigateToDestination(page, x, y, zoom) {
+ if (zoom) {
+ this.setZoom(zoom);
+ }
+
+ if (x || y) {
+ this.goToPageAndXY(page, x ? x : 0, y ? y : 0);
+ } else {
+ this.goToPage(page);
+ }
+ }
+
+ /**
* @param {!PartialPoint} point The position to which to scroll the viewport.
*/
scrollTo(point) {
@@ -1254,6 +1370,112 @@ export class Viewport {
this.tracker_.removeAll();
}
}
+
+ /**
+ * A callback that's called when an update to a pinch zoom is detected.
+ * @param {!CustomEvent<!PinchEventDetail>} e the pinch event.
+ * @private
+ */
+ onPinchUpdate_(e) {
+ // Throttle number of pinch events to one per frame.
+ if (this.sentPinchEvent_) {
+ return;
+ }
+
+ this.sentPinchEvent_ = true;
+ this.window_.requestAnimationFrame(() => {
+ this.sentPinchEvent_ = false;
+ this.mightZoom_(() => {
+ const {direction, center, startScaleRatio} = e.detail;
+ this.pinchPhase_ = direction === 'out' ?
+ Viewport.PinchPhase.PINCH_UPDATE_ZOOM_OUT :
+ Viewport.PinchPhase.PINCH_UPDATE_ZOOM_IN;
+
+ const scaleDelta = startScaleRatio / this.prevScale_;
+ if (this.firstPinchCenterInFrame_ != null) {
+ this.pinchPanVector_ =
+ vectorDelta(center, this.firstPinchCenterInFrame_);
+ }
+
+ const needsScrollbars =
+ this.documentNeedsScrollbars(this.zoomManager_.applyBrowserZoom(
+ this.clampZoom_(this.internalZoom_ * scaleDelta)));
+
+ this.pinchCenter_ = center;
+
+ // If there's no horizontal scrolling, keep the content centered so
+ // the user can't zoom in on the non-content area.
+ // TODO(mcnee) Investigate other ways of scaling when we don't have
+ // horizontal scrolling. We want to keep the document centered,
+ // but this causes a potentially awkward transition when we start
+ // using the gesture center.
+ if (!needsScrollbars.horizontal) {
+ this.pinchCenter_ = {
+ x: this.window_.innerWidth / 2,
+ y: this.window_.innerHeight / 2
+ };
+ } else if (this.keepContentCentered_) {
+ this.oldCenterInContent_ =
+ this.frameToContent_(this.frameToPluginCoordinate_(center));
+ this.keepContentCentered_ = false;
+ }
+
+ this.setPinchZoomInternal_(
+ scaleDelta, this.frameToPluginCoordinate_(center));
+ this.updateViewport_();
+ this.prevScale_ = /** @type {number} */ (startScaleRatio);
+ });
+ });
+ }
+
+ /**
+ * A callback that's called when the end of a pinch zoom is detected.
+ * @param {!CustomEvent<!PinchEventDetail>} e the pinch event.
+ * @private
+ */
+ onPinchEnd_(e) {
+ // Using rAF for pinch end prevents pinch updates scheduled by rAF getting
+ // sent after the pinch end.
+ this.window_.requestAnimationFrame(() => {
+ this.mightZoom_(() => {
+ const {center, startScaleRatio} = e.detail;
+ this.pinchPhase_ = Viewport.PinchPhase.PINCH_END;
+ const scaleDelta = startScaleRatio / this.prevScale_;
+ this.pinchCenter_ = /** @type {!Point} */ (center);
+
+ this.setPinchZoomInternal_(
+ scaleDelta, this.frameToPluginCoordinate_(center));
+ this.updateViewport_();
+ });
+
+ this.pinchPhase_ = Viewport.PinchPhase.PINCH_NONE;
+ this.pinchPanVector_ = null;
+ this.pinchCenter_ = null;
+ this.firstPinchCenterInFrame_ = null;
+ });
+ }
+
+ /**
+ * A callback that's called when the start of a pinch zoom is detected.
+ * @param {!CustomEvent<!PinchEventDetail>} e the pinch event.
+ * @private
+ */
+ 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.
+ this.window_.requestAnimationFrame(() => {
+ this.pinchPhase_ = Viewport.PinchPhase.PINCH_START;
+ this.prevScale_ = 1;
+ this.oldCenterInContent_ =
+ this.frameToContent_(this.frameToPluginCoordinate_(e.detail.center));
+
+ const needsScrollbars = this.documentNeedsScrollbars(this.getZoom());
+ this.keepContentCentered_ = !needsScrollbars.horizontal;
+ // We keep track of beginning of the pinch.
+ // By doing so we will be able to compute the pan distance.
+ this.firstPinchCenterInFrame_ = e.detail.center;
+ });
+ }
}
/**
@@ -1274,11 +1496,15 @@ Viewport.PinchPhase = {
* keys are pressed. Usually we just let the browser handle scrolling on the
* window when these keys are pressed but in certain cases we need to simulate
* these events.
+ * @type {number}
*/
-Viewport.SCROLL_INCREMENT = 40;
+const SCROLL_INCREMENT = 40;
-/** The width of the page shadow around pages in pixels. */
-Viewport.PAGE_SHADOW = {
+/**
+ * The width of the page shadow around pages in pixels.
+ * @type {!{top: number, bottom: number, left: number, right: number}}
+ */
+export const PAGE_SHADOW = {
top: 3,
bottom: 7,
left: 5,
diff --git a/chromium/chrome/browser/resources/pdf/viewport_scroller.js b/chromium/chrome/browser/resources/pdf/viewport_scroller.js
index c0d8fe4f45f..8d0186ce3d9 100644
--- a/chromium/chrome/browser/resources/pdf/viewport_scroller.js
+++ b/chromium/chrome/browser/resources/pdf/viewport_scroller.js
@@ -2,12 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-/**
- * Creates a new ViewportScroller.
- * A ViewportScroller scrolls the page in response to drag selection with the
- * mouse.
- *
- */
+// Scrolls the page in response to drag selection with the mouse.
export class ViewportScroller {
/**
* @param {Object} viewport The viewport info of the page.
@@ -27,7 +22,6 @@ export class ViewportScroller {
/**
* Start scrolling the page by |scrollVelocity_| every
* |DRAG_TIMER_INTERVAL_MS_|.
- *
* @private
*/
startDragScrollTimer_() {
@@ -41,7 +35,6 @@ export class ViewportScroller {
/**
* Stops the drag scroll timer if it is active.
- *
* @private
*/
stopDragScrollTimer_() {
@@ -54,7 +47,6 @@ export class ViewportScroller {
/**
* Scrolls the viewport by the current scroll velocity.
- *
* @private
*/
dragScrollPage_() {
@@ -71,7 +63,6 @@ export class ViewportScroller {
/**
* Calculate the velocity to scroll while dragging using the distance of the
* cursor outside the viewport.
- *
* @param {Object} event The mousemove event.
* @return {Object} Object with x and y direction scroll velocity.
* @private
@@ -95,7 +86,6 @@ export class ViewportScroller {
/**
* Handles mousemove events. It updates the scroll velocity and starts and
* stops timer based on scroll velocity.
- *
* @param {Object} event The mousemove event.
* @private
*/
@@ -111,7 +101,6 @@ export class ViewportScroller {
/**
* Sets whether to scroll the viewport when the mouse is outside the
* viewport.
- *
* @param {boolean} isSelecting Represents selection status.
*/
setEnableScrolling(isSelecting) {
@@ -134,14 +123,12 @@ export class ViewportScroller {
/**
* The period of time in milliseconds to wait between updating the viewport
* position by the scroll velocity.
- *
* @private
*/
ViewportScroller.DRAG_TIMER_INTERVAL_MS_ = 100;
/**
* The maximum drag scroll distance per DRAG_TIMER_INTERVAL in pixels.
- *
* @private
*/
ViewportScroller.MAX_DRAG_SCROLL_DISTANCE_ = 100;
diff --git a/chromium/chrome/browser/resources/pdf/zoom_manager.js b/chromium/chrome/browser/resources/pdf/zoom_manager.js
index bb4e687e02c..bab9f2ec738 100644
--- a/chromium/chrome/browser/resources/pdf/zoom_manager.js
+++ b/chromium/chrome/browser/resources/pdf/zoom_manager.js
@@ -6,11 +6,8 @@ import {NativeEventTarget as EventTarget} from 'chrome://resources/js/cr/event_t
import {BrowserApi} from './browser_api.js';
-/**
- * Abstract parent of classes that manage updating the browser
- * with zoom changes and/or updating the viewer's zoom when
- * the browser zoom changes.
- */
+// Abstract parent of classes that manage updating the browser with zoom changes
+// and/or updating the viewer's zoom when the browser zoom changes.
export class ZoomManager {
/**
* @param {function():number} getViewportZoom Callback to get the viewport's
@@ -39,7 +36,6 @@ export class ZoomManager {
/**
* Creates the appropriate kind of zoom manager given the zoom behavior.
- *
* @param {BrowserApi.ZoomBehavior} zoomBehavior How to manage zoom.
* @param {function():number} getViewportZoom A function that gets the current
* viewport zoom.
@@ -62,20 +58,16 @@ export class ZoomManager {
/**
* Invoked when a browser-initiated zoom-level change occurs.
- *
* @param {number} newZoom the zoom level to zoom to.
*/
onBrowserZoomChange(newZoom) {}
- /**
- * Invoked when an extension-initiated zoom-level change occurs.
- */
+ /** Invoked when an extension-initiated zoom-level change occurs. */
onPdfZoomChange() {}
/**
* Combines the internal pdf zoom and the browser zoom to
* produce the total zoom level for the viewer.
- *
* @param {number} internalZoom the zoom level internal to the viewer.
* @return {number} the total zoom level.
*/
@@ -86,7 +78,6 @@ export class ZoomManager {
/**
* Given a zoom level, return the internal zoom level needed to
* produce that zoom level.
- *
* @param {number} totalZoom the total zoom level.
* @return {number} the zoom level internal to the viewer.
*/
@@ -96,7 +87,6 @@ export class ZoomManager {
/**
* Returns whether two numbers are approximately equal.
- *
* @param {number} a The first number.
* @param {number} b The second number.
*/
@@ -109,15 +99,11 @@ export class ZoomManager {
}
}
-/**
- * InactiveZoomManager has no control over the browser's zoom
- * and does not respond to browser zoom changes.
- */
+// Has no control over the browser's zoom and does not respond to browser zoom
+// changes.
export class InactiveZoomManager extends ZoomManager {}
-/**
- * ActiveZoomManager controls the browser's zoom.
- */
+// ActiveZoomManager controls the browser's zoom.
class ActiveZoomManager extends ZoomManager {
/**
* Constructs a ActiveZoomManager.
@@ -139,7 +125,6 @@ class ActiveZoomManager extends ZoomManager {
/**
* Invoked when a browser-initiated zoom-level change occurs.
- *
* @param {number} newZoom the zoom level to zoom to.
*/
onBrowserZoomChange(newZoom) {
@@ -193,7 +178,6 @@ class ActiveZoomManager extends ZoomManager {
/**
* Combines the internal pdf zoom and the browser zoom to
* produce the total zoom level for the viewer.
- *
* @param {number} internalZoom the zoom level internal to the viewer.
* @return {number} the total zoom level.
*/
@@ -206,7 +190,6 @@ class ActiveZoomManager extends ZoomManager {
/**
* Given a zoom level, return the internal zoom level needed to
* produce that zoom level.
- *
* @param {number} totalZoom the total zoom level.
* @return {number} the zoom level internal to the viewer.
*/
@@ -217,14 +200,11 @@ class ActiveZoomManager extends ZoomManager {
}
}
-/**
- * This EmbeddedZoomManager responds to changes in the browser zoom,
- * but does not control the browser zoom.
- */
+// Responds to changes in the browser zoom, but does not control the browser
+// zoom.
class EmbeddedZoomManager extends ZoomManager {
/**
* Invoked when a browser-initiated zoom-level change occurs.
- *
* @param {number} newZoom the new browser zoom level.
*/
onBrowserZoomChange(newZoom) {