diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-05-16 09:59:13 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-05-20 10:28:53 +0000 |
commit | 6c11fb357ec39bf087b8b632e2b1e375aef1b38b (patch) | |
tree | c8315530db18a8ee566521c39ab8a6af4f72bc03 /chromium/chrome/browser/resources/pdf | |
parent | 3ffaed019d0772e59d6cdb2d0d32fe4834c31f72 (diff) |
BASELINE: Update Chromium to 74.0.3729.159
Change-Id: I8d2497da544c275415aedd94dd25328d555de811
Reviewed-by: Michael BrĂ¼ning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/chrome/browser/resources/pdf')
22 files changed, 1096 insertions, 607 deletions
diff --git a/chromium/chrome/browser/resources/pdf/BUILD.gn b/chromium/chrome/browser/resources/pdf/BUILD.gn index f79f34511ae..1483ff4a9a0 100644 --- a/chromium/chrome/browser/resources/pdf/BUILD.gn +++ b/chromium/chrome/browser/resources/pdf/BUILD.gn @@ -10,6 +10,7 @@ group("closure_compile") { ":pdf_resources", "elements/viewer-bookmark:closure_compile", "elements/viewer-error-screen:closure_compile", + "elements/viewer-form-warning:closure_compile", "elements/viewer-page-indicator:closure_compile", "elements/viewer-page-selector:closure_compile", "elements/viewer-password-screen:closure_compile", @@ -53,13 +54,44 @@ js_library("pdf_scripting_api") { js_library("viewport_scroller") { } +js_library("viewport_interface") { + deps = [ + ":pdf_fitting_type", + ] +} + +js_library("viewport") { + deps = [ + ":gesture_detector", + ":viewport_interface", + ":zoom_manager", + "//ui/webui/resources/js:util", + ] + externs_list = [ "$externs_path/pending.js" ] +} + +js_library("zoom_manager") { + deps = [ + ":browser_api", + ":viewport_interface", + ] +} + +js_library("metrics") { + externs_list = [ "$externs_path/metrics_private.js" ] +} + js_type_check("pdf_resources") { deps = [ ":browser_api", ":gesture_detector", + ":metrics", ":open_pdf_params_parser", ":pdf_fitting_type", ":pdf_scripting_api", + ":viewport", + ":viewport_interface", ":viewport_scroller", + ":zoom_manager", ] } diff --git a/chromium/chrome/browser/resources/pdf/elements/icons.html b/chromium/chrome/browser/resources/pdf/elements/icons.html index f71953e61f7..a5976b143ce 100644 --- a/chromium/chrome/browser/resources/pdf/elements/icons.html +++ b/chromium/chrome/browser/resources/pdf/elements/icons.html @@ -12,12 +12,14 @@ <g id="bookmark"><path d="M17 3H7c-1.1 0-1.99.9-1.99 2L5 21l7-3 7 3V5c0-1.1-.9-2-2-2z"></path></g> <g id="bookmark-border"><path d="M17 3H7c-1.1 0-1.99.9-1.99 2L5 21l7-3 7 3V5c0-1.1-.9-2-2-2zm0 15l-5-2.18L7 18V5h10v13z"></path></g> <g id="create"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"></path></g> - <g id="eraser"><path d="m15.543 4.9863c-0.32193 0.019835-0.65088 0.1626-0.91016 0.42188l-4.8867 4.8867 5.5332 5.5332h-2.1211l-4.4727-4.4727-0.65234 0.65234c-0.51854 0.51855-0.56773 1.319-0.10938 1.7773l4.166 4.166c0.45836 0.45836 1.2588 0.40917 1.7773-0.10938l6.5996-6.5996c0.51854-0.51855 0.56773-1.319 0.10938-1.7773l-4.166-4.166c-0.22918-0.22918-0.54525-0.33233-0.86719-0.3125zm-12.543 13.764v1.5h0.75 6 0.75v-1.5h-0.75-6-0.75z"></path></g> + <g id="eraser"><path d="M21.41,11.33 L13.04,20 L4.73,20 L2.58,17.86 C1.8,17.08 1.8,15.83 2.58,15.04 L13.62,3.58 C14.4,2.81 15.68,2.81 16.46,3.58 L21.41,8.51 C22.2,9.29 22.2,10.55 21.41,11.33 L21.41,11.33 Z"></path><polygon points="17.26 18 15.26 20 21.96 20 21.96 18"></polygon></g> <g id="fullscreen-exit"><path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"></path></g> - <g id="highlighter"><path d="M15.7498169,15.9885 L18.69405,19.7475 C18.75405,19.8165 18.79455,19.89975 18.8148,19.98825 C17.22405,20.30025 14.37105,20.25 12.0648,20.25 C9.72705,20.25 6.61248962,20.295 5.01423962,19.9755 C5.03523962,19.8915 5.07348962,19.8135 5.13123962,19.7475 L8.25498962,15.9885 L8.25498962,11.3135317 C8.25498962,11.2722817 8.26398962,11.2317817 8.28123962,11.1957817 L8.59698962,10.5297817 C8.59698962,10.5 15.4078169,10.49925 15.4078169,10.5 L15.7235669,11.1957817 C15.7400669,11.2317817 15.7498169,11.2722817 15.7498169,11.3135317 L15.7498169,15.9885"></path><path d="M13.846962,4.03912354 L14.04,4.09664481 C14.163,4.13093052 14.25,4.25693052 14.25,4.40178767 L14.25,5.43243216 L14.25,5.7872893 L14.25,8.82657296 C13.6995,8.91057296 12.79575,9 12,9 C11.20275,9 10.3005,8.91057296 9.75,8.82571582 L9.75,4.41586073 L9.75,3.85871787 L9.75,3.3152893 C9.75,3.11043216 9.91725,2.96043216 10.09125,3.0092893 L13.846962,4.03912354 Z" style="fill: var(--pen-tip-fill)"></g> - <g id="marker"><path d="M14.25,8.91853902 L14.25,6.53925711 C14.25,5.97869512 14.1161242,5.42520919 13.8582163,4.92039274 L12.7090497,2.67089612 C12.4225948,2.10981636 11.5774052,2.10964378 11.2909503,2.67072353 C10.911176,3.41422755 10.4575732,4.30184157 10.1413899,4.92108308 C9.88348208,5.42589953 9.75,5.97869512 9.75,6.53925711 L9.75,8.91940195 C10.4758827,8.97169576 11.2816971,9 11.9916328,9 C12.7058997,9 13.5195892,8.97152317 14.25,8.91853902" style="fill: var(--pen-tip-fill)"></path><path d="M15.7498169,15.9885 L15.7498169,11.3135317 C15.7498169,11.2722817 15.7400669,11.2317817 15.7235669,11.1957817 L15.4078169,10.5 C15.4078169,10.49925 8.59698962,10.5 8.59698962,10.5297817 L8.28123962,11.1957817 C8.26398962,11.2317817 8.25498962,11.2722817 8.25498962,11.3135317 L8.25498962,15.9885 L5.13123962,19.7475 C5.07348962,19.8135 5.03523962,19.8915 5.01423962,19.9755 C6.61248962,20.295 9.72705,20.25 12.0648,20.25 C14.37105,20.25 17.22405,20.30025 18.8148,19.98825 C18.79455,19.89975 18.75405,19.8165 18.69405,19.7475 L15.7498169,15.9885 Z"></path></g> + <g id="highlighter"><path d="M10.22,9.49 L4.31,15.49 C3.54,16.29 3.61,17.54 4.39,18.34 L0.77,22 L6.45,22 L7.19,21.25 C7.97,22.06 9.14,22.11 9.92,21.3 L15.88,15.25 L10.22,9.49 L10.22,9.49 Z"></path><path style="fill: var(--pen-tip-fill)" d="M22.68,5.49 L19.86,2.62 C19.08,1.82 17.79,1.78 17.02,2.58 L11.27,8.43 L16.93,14.18 L22.62,8.4 C23.39,7.59 23.45,6.29 22.68,5.49 L22.68,5.49 Z"></path><path style="fill: var(--pen-tip-border)" d="M18.4,3c0.3,0,0.5,0.1,0.7,0.3L22,6.2c0.4,0.4,0.4,1.1-0.1,1.5l-5,5.1l-4.3-4.3l5.1-5.2 C17.9,3.1,18.1,3,18.4,3 M18.4,2c-0.5,0-1,0.2-1.4,0.6l-5.8,5.9l5.7,5.8l5.7-5.8c0.8-0.8,0.8-2.1,0.1-2.9l-2.8-2.9 C19.5,2.2,18.9,2,18.4,2L18.4,2z"></path></g> + <g id="marker"><polygon points="3 17.25 3 21 6.74 21 14.28 13.47 10.53 9.72"></polygon><path style="fill: var(--pen-tip-fill)" d="M18.37,3.3 L20.71,5.63 C21.1,6.02 21.11,6.66 20.72,7.05 L15.35,12.41 L11.59,8.65 L14.12,6.12 L13.39,5.39 L7.73,11.05 L6.33,9.65 L12.7,3.29 C13.09,2.9 13.74,2.91 14.12,3.3 L15.54,4.71 L16.96,3.3 C17.34,2.91 17.98,2.91 18.37,3.3 L18.37,3.3 Z"></path><path style="fill: var(--pen-tip-border)" d="M17.7,4L20,6.3L15.4,11L13,8.6l1.8-1.8l0.7-0.7l-0.7-0.7l-0.2-0.2l0.2,0.2l0.7,0.7l0.7-0.7L17.7,4 M13.4,3 c-0.3,0-0.5,0.1-0.7,0.3L6.3,9.6l1.4,1.4l5.7-5.7l0.7,0.7l-2.5,2.5l3.8,3.8L20.7,7c0.4-0.4,0.4-1,0-1.4l-2.3-2.3 C18.2,3.1,17.9,3,17.7,3S17.2,3.1,17,3.3l-1.4,1.4l-1.4-1.4C13.9,3.1,13.7,3,13.4,3L13.4,3z"></path></g> + <g id="redo"><path d="M18.4 10.6C16.55 8.99 14.15 8 11.5 8c-4.65 0-8.58 3.03-9.96 7.22L3.9 16c1.05-3.19 4.05-5.5 7.6-5.5 1.95 0 3.73.72 5.12 1.88L13 16h9V7l-3.6 3.6z"></path></g> <g id="remove"><path d="M19 13H5v-2h14v2z"></path></g> <g id="rotate-right"><path d="M15.55 5.55L11 1v3.07C7.06 4.56 4 7.92 4 12s3.05 7.44 7 7.93v-2.02c-2.84-.48-5-2.94-5-5.91s2.16-5.43 5-5.91V10l4.55-4.45zM19.93 11c-.17-1.39-.72-2.73-1.62-3.89l-1.42 1.42c.54.75.88 1.6 1.02 2.47h2.02zM13 17.9v2.02c1.39-.17 2.74-.71 3.9-1.61l-1.44-1.44c-.75.54-1.59.89-2.46 1.03zm3.89-2.42l1.42 1.41c.9-1.16 1.45-2.5 1.62-3.89h-2.02c-.14.87-.48 1.72-1.02 2.48z"></path></g> + <g id="undo"><path d="M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z"></path></g> </defs> </svg> </iron-iconset-svg> diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-form-warning/BUILD.gn b/chromium/chrome/browser/resources/pdf/elements/viewer-form-warning/BUILD.gn new file mode 100644 index 00000000000..e27ead67627 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/elements/viewer-form-warning/BUILD.gn @@ -0,0 +1,18 @@ +# 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. + +import("//third_party/closure_compiler/compile_js.gni") + +js_type_check("closure_compile") { + deps = [ + ":viewer-form-warning", + ] +} + +js_library("viewer-form-warning") { + deps = [ + "//ui/webui/resources/cr_elements/cr_dialog:cr_dialog", + "//ui/webui/resources/js:promise_resolver", + ] +} diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-form-warning/viewer-form-warning.html b/chromium/chrome/browser/resources/pdf/elements/viewer-form-warning/viewer-form-warning.html new file mode 100644 index 00000000000..e86b7915e37 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/elements/viewer-form-warning/viewer-form-warning.html @@ -0,0 +1,25 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html"> +<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> +<link rel="import" href="chrome://resources/cr_elements/paper_button_style_css.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html"> + +<dom-module id="viewer-form-warning"> + <template> + <style include="paper-button-style cr-hidden-style"></style> + <cr-dialog id="dialog" no-cancel> + <div slot="title">[[strings.annotationFormWarningTitle]]</div> + <div slot="body">[[strings.annotationFormWarningDetail]]</div> + <div slot="button-container"> + <paper-button class="cancel-button" on-click="onCancel"> + [[strings.annotationFormWarningKeepEditing]] + </paper-button> + <paper-button class="action-button" on-click="onAction"> + [[strings.annotationFormWarningDiscard]] + </paper-button> + </div> + </cr-dialog> + </template> + <script src="viewer-form-warning.js"></script> +</dom-module> diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-form-warning/viewer-form-warning.js b/chromium/chrome/browser/resources/pdf/elements/viewer-form-warning/viewer-form-warning.js new file mode 100644 index 00000000000..539eddd6b67 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/elements/viewer-form-warning/viewer-form-warning.js @@ -0,0 +1,29 @@ +// 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. + +Polymer({ + is: 'viewer-form-warning', + properties: { + strings: Object, + }, + + /** @private {PromiseResolver} */ + resolver_: null, + + show: function() { + this.resolver_ = new PromiseResolver(); + /** @type {!CrDialogElement} */ (this.$.dialog).showModal(); + return this.resolver_.promise; + }, + + onCancel: function() { + this.resolver_.reject(); + this.$.dialog.cancel(); + }, + + onAction: function() { + this.resolver_.resolve(); + this.$.dialog.close(); + }, +}); diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-ink-host/BUILD.gn b/chromium/chrome/browser/resources/pdf/elements/viewer-ink-host/BUILD.gn index 420a227722d..32ca2b04311 100644 --- a/chromium/chrome/browser/resources/pdf/elements/viewer-ink-host/BUILD.gn +++ b/chromium/chrome/browser/resources/pdf/elements/viewer-ink-host/BUILD.gn @@ -12,11 +12,8 @@ js_type_check("closure_compile") { js_library("viewer-ink-host") { deps = [ + "//chrome/browser/resources/pdf:metrics", + "//chrome/browser/resources/pdf:viewport", "//chrome/browser/resources/pdf/ink:ink_api", ] - externs_list = [ - # TODO(dstockwell): Once viewport can be typechecked this can be replaced - # by a dep on viewport. - "externs.js", - ] } diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-ink-host/externs.js b/chromium/chrome/browser/resources/pdf/elements/viewer-ink-host/externs.js deleted file mode 100644 index f3769e25a2a..00000000000 --- a/chromium/chrome/browser/resources/pdf/elements/viewer-ink-host/externs.js +++ /dev/null @@ -1,51 +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. - -/** - * @typedef {{ - * x: number, - * y: number - * }} - */ -let Point; - -/** - * @typedef {{ - * width: number, - * height: number - * }} - */ -let Size; - -class Viewport { - /** - * @param {number} zoom - * @return {{width: number, height: number}} - */ - getDocumentDimensions(zoom) {} - - /** - * @param {!Point} point - * @return {boolean} - */ - isPointInsidePage(point) {} - - /** @return {!Point} */ - get position() {} - - /** @return {!Size} */ - get size() {} - - /** @return {number} */ - get zoom() {} -} - -/** @type {Object} */ -Viewport.PAGE_SHADOW; - -/** @type {number} */ -Viewport.PAGE_SHADOW.top; - -/** @type {number} */ -Viewport.PAGE_SHADOW.left; diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-ink-host/viewer-ink-host.html b/chromium/chrome/browser/resources/pdf/elements/viewer-ink-host/viewer-ink-host.html index 120d6ecc60e..1a7a2ff73ea 100644 --- a/chromium/chrome/browser/resources/pdf/elements/viewer-ink-host/viewer-ink-host.html +++ b/chromium/chrome/browser/resources/pdf/elements/viewer-ink-host/viewer-ink-host.html @@ -4,7 +4,6 @@ <template> <style> :host { - touch-action: none; visibility: hidden; } iframe { diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-ink-host/viewer-ink-host.js b/chromium/chrome/browser/resources/pdf/elements/viewer-ink-host/viewer-ink-host.js index 1895a2347d5..6bb2e4c68ed 100644 --- a/chromium/chrome/browser/resources/pdf/elements/viewer-ink-host/viewer-ink-host.js +++ b/chromium/chrome/browser/resources/pdf/elements/viewer-ink-host/viewer-ink-host.js @@ -50,6 +50,9 @@ Polymer({ /** @type {Viewport} */ viewport: null, + /** @type {?AnnotationTool} */ + tool_: null, + /** * Whether we should suppress pointer events due to a gesture, * eg. pinch-zoom. @@ -168,6 +171,26 @@ Polymer({ this.activePointer_ = null; if (!this.pointerGesture_) { this.dispatchPointerEvent_(e); + // If the stroke was not cancelled (type == pointercanel), + // notify about mutation and record metrics. + if (e.type == 'pointerup') { + this.dispatchEvent(new CustomEvent('stroke-added')); + if (e.pointerType == 'mouse') { + PDFMetrics.record(PDFMetrics.UserAction.ANNOTATE_STROKE_DEVICE_MOUSE); + } else if (e.pointerType == 'pen') { + PDFMetrics.record(PDFMetrics.UserAction.ANNOTATE_STROKE_DEVICE_PEN); + } else if (e.pointerType == 'touch') { + PDFMetrics.record(PDFMetrics.UserAction.ANNOTATE_STROKE_DEVICE_TOUCH); + } + if (this.tool_.tool == 'eraser') { + PDFMetrics.record(PDFMetrics.UserAction.ANNOTATE_STROKE_TOOL_ERASER); + } else if (this.tool_.tool == 'pen') { + PDFMetrics.record(PDFMetrics.UserAction.ANNOTATE_STROKE_TOOL_PEN); + } else if (this.tool_.tool == 'highlighter') { + PDFMetrics.record( + PDFMetrics.UserAction.ANNOTATE_STROKE_TOOL_HIGHLIGHTER); + } + } } this.pointerGesture_ = false; }, @@ -202,12 +225,15 @@ Polymer({ this.$.frame.src = 'ink/index.html'; await new Promise(resolve => this.$.frame.onload = resolve); this.ink_ = await this.$.frame.contentWindow.initInk(); + this.ink_.addUndoStateListener( + e => this.dispatchEvent( + new CustomEvent('undo-state-changed', {detail: e}))); this.ink_.setPDF(data); this.state_ = State.ACTIVE; this.viewportChanged(); - // TODO(dstockwell): we shouldn't need this extra flush. - await this.ink_.flush(); - await this.ink_.flush(); + // Wait for the next task to avoid a race where Ink drops the background + // color. + await new Promise(resolve => setTimeout(resolve)); this.ink_.setOutOfBoundsColor(BACKGROUND_COLOR); const spacing = Viewport.PAGE_SHADOW.top + Viewport.PAGE_SHADOW.bottom; this.ink_.setPageSpacing(spacing); @@ -222,7 +248,7 @@ Polymer({ const pos = viewport.position; const size = viewport.size; const zoom = viewport.zoom; - const documentWidth = viewport.getDocumentDimensions(zoom).width * zoom; + 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; @@ -248,6 +274,16 @@ Polymer({ this.ink_.setCamera(camera); }, + /** Undo the last edit action. */ + undo() { + this.ink_.undo(); + }, + + /** Redo the last undone edit action. */ + redo() { + this.ink_.redo(); + }, + /** * @return {!Promise<{fileName: string, dataToSave: ArrayBuffer}>} * The serialized PDF document including any annotations that were made. diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html b/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html index 713abee0bfe..921ce0f27a3 100644 --- a/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html +++ b/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html @@ -1,4 +1,6 @@ <link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-progress/paper-progress.html"> <link rel="import" href="chrome://resources/cr_elements/icons.html"> @@ -12,7 +14,7 @@ <dom-module id="viewer-pdf-toolbar"> <template> - <style> + <style include="cr-hidden-style"> :host ::selection { background: rgba(255, 255, 255, 0.3); } @@ -50,11 +52,20 @@ } paper-icon-button { - margin-inline-end: 12px; + height: 36px; + margin: 6px; + padding: 8px; + width: 36px; } - viewer-toolbar-dropdown { - margin-inline-end: 4px; + paper-icon-button:hover { + background: rgba(255, 255, 255, 0.08); + border-radius: 50%; + } + + paper-icon-button:focus { + --paper-icon-button-ink-color:white; + --paper-ripple-opacity: 0.24; } paper-progress { @@ -100,25 +111,45 @@ --dropdown-width: 346px; } - #eraser:not([selected]), - #pen:not([selected]), - #highlighter:not([selected]) { - filter: contrast(30%); - } - #pen, #highlighter { --dropdown-open-background: rgb(50, 54, 57); } - #eraser[selected] { - background-color: rgb(50, 54, 57); - border-radius: 4px; + #eraser { + opacity: 0.38; + } + + #eraser[selected], + #eraser:focus, + #eraser:hover { + opacity: 1; + } + + #annotation-separator { + background: white; + height: 30px; + margin-inline-end: 12px; + margin-inline-start: 12px; + opacity: 0.38; + width: 1px; } :host([annotation-mode]) #annotate { - background-color: rgb(25, 27, 29); - border-radius: 4px; + background-color: rgba(255, 255, 255, 0.24); + border-radius: 50%; + } + + #bookmarks { + margin-inline-start: 8px; + } + + #pen { + margin-inline-end: 10px; + } + + #highlighter { + margin-inline-end: 6px; } .invisible { @@ -165,6 +196,7 @@ <div id="buttons" class="invisible"> <template is="dom-if" if="[[pdfAnnotationsEnabled]]"> <paper-icon-button id="annotate" icon="pdf:create" + disabled="[[!annotationAvailable]]" on-click="toggleAnnotation" aria-label$="{{strings.tooltipAnnotate}}" title$="{{strings.tooltipAnnotate}}"> @@ -172,6 +204,7 @@ </template> <paper-icon-button id="rotate-right" icon="pdf:rotate-right" + disabled="[[annotationMode]]" on-click="rotateRight" aria-label$="{{strings.tooltipRotateCW}}" title$="{{strings.tooltipRotateCW}}"> @@ -190,6 +223,7 @@ </paper-icon-button> <viewer-toolbar-dropdown id="bookmarks" + selected metrics-id="bookmarks" hidden$="[[!bookmarks.length]]" open-icon="pdf:bookmark" @@ -201,19 +235,13 @@ </div> </div> <div id="progress-container"> - <paper-progress id="progress" value="[[loadProgress]]"></paper-progress> + <paper-progress id="progress" + value="[[loadProgress]]" + indeterminate="[[annotationMode]]"></paper-progress> </div> </div> - <div id="annotations-bar" class="invisible"> - <paper-icon-button id="eraser" - selected$="[[equal_('eraser', annotationTool.tool)]]" - on-click="annotationToolClicked_" - icon="pdf:eraser" - aria-label$="{{strings.annotationEraser}}" - title$="{{strings.annotationEraser}}"> - </paper-icon-button> - + <div id="annotations-bar" hidden> <viewer-toolbar-dropdown id="pen" selected$="[[equal_('pen', annotationTool.tool)]]" open-after-select @@ -251,6 +279,32 @@ on-selected-color-changed="annotationToolOptionChanged_"> </viewer-pen-options> </viewer-toolbar-dropdown> + + <paper-icon-button id="eraser" + selected$="[[equal_('eraser', annotationTool.tool)]]" + on-click="annotationToolClicked_" + icon="pdf:eraser" + aria-label$="{{strings.annotationEraser}}" + title$="{{strings.annotationEraser}}"> + </paper-icon-button> + + <div id="annotation-separator"></div> + + <paper-icon-button id="undo" + disabled="[[!canUndoAnnotation]]" + icon="pdf:undo" + on-click="undo" + aria-label$="{{strings.annotationUndo}}" + title$="{{strings.annotationUndo}}"> + </paper-icon-button> + + <paper-icon-button id="redo" + disabled="[[!canRedoAnnotation]]" + icon="pdf:redo" + on-click="redo" + aria-label$="{{strings.annotationRedo}}" + title$="{{strings.annotationRedo}}"> + </paper-icon-button> </div> </template> <script src="viewer-pdf-toolbar.js"></script> diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.js b/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.js index 5c61b3b8e98..8e18564aa75 100644 --- a/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.js +++ b/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.js @@ -53,9 +53,32 @@ Polymer({ }, /** + * Whether annotation mode can be entered. This would be false if for + * 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, + }, + + canUndoAnnotation: { + type: Boolean, + value: false, + }, + + canRedoAnnotation: { + type: Boolean, + value: false, + }, + + /** * Whether the PDF Annotations feature is enabled. */ - pdfAnnotationsEnabled: Boolean, + pdfAnnotationsEnabled: { + type: Boolean, + value: false, + }, strings: Object, }, @@ -72,8 +95,7 @@ Polymer({ this.$.pageselector.classList.toggle('invisible', !loaded); this.$.buttons.classList.toggle('invisible', !loaded); this.$.progress.style.opacity = loaded ? 0 : 1; - this.$['annotations-bar'].classList.toggle( - 'invisible', !(loaded && this.annotationMode)); + this.$['annotations-bar'].hidden = !loaded || !this.annotationMode; } }, @@ -163,12 +185,25 @@ Polymer({ this.fire('print'); }, + undo: function() { + this.fire('undo'); + }, + + redo: function() { + this.fire('redo'); + }, + toggleAnnotation: function() { this.annotationMode = !this.annotationMode; if (this.annotationMode) { // Select pen tool when entering annotation mode. this.updateAnnotationTool_(this.$.pen); } + this.dispatchEvent(new CustomEvent('annotation-mode-toggled', { + detail: { + value: this.annotationMode, + }, + })); }, /** @param {Event} e */ @@ -193,6 +228,10 @@ Polymer({ selectedColor: null, }; element.attributeStyleMap.set('--pen-tip-fill', options.selectedColor); + element.attributeStyleMap.set( + '--pen-tip-border', + options.selectedColor == '#000000' ? 'currentcolor' : + options.selectedColor); this.annotationTool = { tool: tool, size: options.selectedSize, diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-pen-options/viewer-pen-options.html b/chromium/chrome/browser/resources/pdf/elements/viewer-pen-options/viewer-pen-options.html index e582fdf4ba7..3151736c3e3 100644 --- a/chromium/chrome/browser/resources/pdf/elements/viewer-pen-options/viewer-pen-options.html +++ b/chromium/chrome/browser/resources/pdf/elements/viewer-pen-options/viewer-pen-options.html @@ -67,13 +67,14 @@ checked$="[[equal_(selectedColor, item.color)]]" tabindex="1" style="--item-color: [[item.color]]" title$="[[lookup_(strings, item.name)]]" - aria-label$="[[lookup_(strings, item.name)]]"> + aria-label$="[[lookup_(strings, item.name)]]" + on-pointerdown="blurOnPointerDown"> </template> <paper-icon-button id="expand" icon="cr:expand-more" tabindex="3" on-click="toggleExpanded_" - aria-label$="[[strings.tooltipExpand]]" - title$="[[strings.tooltipExpand]]"> + aria-label$="[[strings.annotationExpand]]" + title$="[[strings.annotationExpand]]"> </paper-icon-button> </div> <div id="separator"></div> @@ -83,10 +84,11 @@ checked$="[[equal_(selectedSize, item.size)]]" tabindex="2" style="--item-size: [[item.size]]" title$="{{lookup_(strings, item.name)}}" - aria-label$="[[lookup_(strings, item.name)]]"> + aria-label$="[[lookup_(strings, item.name)]]" + on-pointerdown="blurOnPointerDown"> </template> </div> </paper-icon-button> </template> <script src="viewer-pen-options.js"></script> -</dom-module>
\ No newline at end of file +</dom-module> diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-pen-options/viewer-pen-options.js b/chromium/chrome/browser/resources/pdf/elements/viewer-pen-options/viewer-pen-options.js index e01a388627a..e357009cb2b 100644 --- a/chromium/chrome/browser/resources/pdf/elements/viewer-pen-options/viewer-pen-options.js +++ b/chromium/chrome/browser/resources/pdf/elements/viewer-pen-options/viewer-pen-options.js @@ -94,18 +94,33 @@ Polymer({ this.selectedColor = e.target.value; }, + /** @private */ toggleExpanded_: function() { this.expanded_ = !this.expanded_; this.updateExpandedState_(); }, - attached() { + /** @private */ + updateExpandedStateAndFinishAnimations_: function() { this.updateExpandedState_(); for (const animation of this.expandAnimations_) { animation.finish(); } }, + /** @override */ + attached: function() { + // TODO (rbpotter): Remove this conditional when the migration to Polymer 2 + // is completed. + if (Polymer.DomIf) { + Polymer.RenderStatus.beforeNextRender(this, () => { + this.updateExpandedStateAndFinishAnimations_(); + }); + } else { + this.updateExpandedStateAndFinishAnimations_(); + } + }, + /** * Updates the state of the UI to reflect the current value of `expanded`. * Starts or reverses animations and enables/disable controls. @@ -133,14 +148,16 @@ Polymer({ }), ]; } - if (this.expanded_) { - for (const animation of this.expandAnimations_) { - animation.playbackRate = 1; - } - } else { - for (const animation of this.expandAnimations_) { - animation.playbackRate = -1; - } + for (const animation of this.expandAnimations_) { + // TODO(dstockwell): Ideally we would just set playbackRate, + // but there appears to be a web-animations bug that + // results in the animation getting stuck in the 'pending' + // state sometimes. See crbug.com/938857 + const currentTime = animation.currentTime; + animation.cancel(); + animation.playbackRate = this.expanded_ ? 1 : -1; + animation.currentTime = currentTime; + animation.play(); } for (const input of colors.querySelectorAll('input:nth-child(n+8)')) { if (this.expanded_) { @@ -157,7 +174,7 @@ Polymer({ * @param {*} a * @param {*} b */ - equal_: function(a,b) { + equal_: function(a, b) { return a == b; }, @@ -169,6 +186,16 @@ Polymer({ * @return {string} */ lookup_: function(strings, name) { - return strings[name]; - } -});
\ No newline at end of file + return strings ? strings[name] : ''; + }, + + /** + * 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. + */ + blurOnPointerDown(e) { + const target = e.target; + setTimeout(() => target.blur(), 0); + }, +}); diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.html b/chromium/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.html index 9401afc44ef..50dadbe03bd 100644 --- a/chromium/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.html +++ b/chromium/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.html @@ -1,6 +1,6 @@ <link rel="import" href="chrome://resources/html/polymer.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html"> <link rel="import" href="chrome://resources/cr_elements/icons.html"> <link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html"> @@ -8,11 +8,16 @@ <template> <style> :host { + display: inline-block; position: relative; text-align: start; --dropdown-width: 260px; } + :host-context([hidden]) { + display: none; + } + :host([dropdown-centered]) { --container-offset: calc(50% - var(--dropdown-width) / 2); } @@ -43,20 +48,37 @@ padding: 6px 0 4px 0; } - #icon { + #button { + border-radius: 4px; cursor: pointer; display: inline-block; + height: 32px; + margin: 0; + min-width: 48px; + opacity: 0.38; + padding-bottom: 6px; + padding-inline-end: 2px; + padding-inline-start: 6px; + padding-top: 6px; + width: 48px; } - :host([dropdown-open]) #icon, - :host([selected]) #icon { - background-color: var(--dropdown-open-background, rgb(25, 27, 29)); - border-radius: 4px; + #button:focus { + background-color: rgba(255, 255, 255, 0.24); + opacity: 1; } - #arrow { - margin-inline-start: -12px; - padding-inline-end: 4px; + #button:hover { + background-color: rgba(255, 255, 255, 0.08); + opacity: 1; + } + + :host([selected]) #button { + opacity: 1; + + } + :host([dropdown-open]) #button { + background-color: rgba(255, 255, 255, 0.24); } h1 { @@ -67,12 +89,11 @@ padding: 14px 28px; } </style> - <div on-click="toggleDropdown" id="icon"> - <paper-icon-button id="main-icon" icon="[[dropdownIcon]]" + <paper-button on-click="toggleDropdown" id="button" aria-label$="{{header}}" title$="{{header}}"> - </paper-icon-button> - <iron-icon icon="cr:arrow-drop-down" id="arrow"></iron-icon> - </div> + <iron-icon icon="[[dropdownIcon]]"></iron-icon> + <iron-icon icon="cr:arrow-drop-down"></iron-icon> + </paper-button> <div id="container"> <div id="dropdown" style="display: none"> diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.js b/chromium/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.js index 0ce8ebd6667..05f044afe47 100644 --- a/chromium/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.js +++ b/chromium/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.js @@ -101,6 +101,7 @@ Polymer({ } if (this.dropdownOpen) { this.toggleDropdown(); + this.blur(); } // Clean up the handler. The dropdown may already be closed. window.removeEventListener('pointerdown', listener); diff --git a/chromium/chrome/browser/resources/pdf/index.html b/chromium/chrome/browser/resources/pdf/index.html index 3450bd52e66..71b5d84126d 100644 --- a/chromium/chrome/browser/resources/pdf/index.html +++ b/chromium/chrome/browser/resources/pdf/index.html @@ -9,9 +9,11 @@ <link rel="import" href="elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html"> <link rel="import" href="elements/viewer-zoom-toolbar/viewer-zoom-toolbar.html"> <link rel="import" href="elements/shared-vars.html"> + <link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html"> <if expr="chromeos"> <link rel="import" href="elements/viewer-ink-host/viewer-ink-host.html"> + <link rel="import" href="elements/viewer-form-warning/viewer-form-warning.html"> </if> <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css"> @@ -30,11 +32,16 @@ <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> </body> <script src="pdf_fitting_type.js"></script> <script src="toolbar_manager.js"></script> +<script src="viewport_interface.js"></script> <script src="viewport.js"></script> <script src="open_pdf_params_parser.js"></script> <script src="navigator.js"></script> diff --git a/chromium/chrome/browser/resources/pdf/ink/BUILD.gn b/chromium/chrome/browser/resources/pdf/ink/BUILD.gn index ff59d37b78c..b98fd60781e 100644 --- a/chromium/chrome/browser/resources/pdf/ink/BUILD.gn +++ b/chromium/chrome/browser/resources/pdf/ink/BUILD.gn @@ -11,10 +11,7 @@ group("closure_compile") { } js_library("ink_api") { - externs_list = [ - "externs.js", - "../../../../../third_party/ink/build/ink_lib_externs.js", - ] + externs_list = [ "//third_party/ink/build/ink_lib_externs.js" ] } js_type_check("ink") { diff --git a/chromium/chrome/browser/resources/pdf/ink/ink_api.js b/chromium/chrome/browser/resources/pdf/ink/ink_api.js index bec99a72ec1..04e0d56dcf6 100644 --- a/chromium/chrome/browser/resources/pdf/ink/ink_api.js +++ b/chromium/chrome/browser/resources/pdf/ink/ink_api.js @@ -12,6 +12,14 @@ let AnnotationTool; /** + * @typedef {{ + * canUndo: boolean, + * canRedo: boolean, + * }} + */ +let UndoState; + +/** * Wraps the Ink component with an API that can be called * across an IFrame boundary. */ @@ -20,6 +28,20 @@ class InkAPI { constructor(embed) { this.embed_ = embed; this.brush_ = ink.BrushModel.getInstance(embed); + this.camera_ = null; + } + + /** @param {function(!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); } /** @@ -49,8 +71,12 @@ class InkAPI { return this.embed_.getPDFDestructive(); } - setCamera(camera) { + async setCamera(camera) { + this.camera_ = camera; this.embed_.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); } /** @param {AnnotationTool} tool */ @@ -58,7 +84,7 @@ class InkAPI { const shape = { eraser: 'MAGIC_ERASE', pen: 'INKPEN', - highlighter: 'HIGHLIGHTER', + highlighter: 'SMART_HIGHLIGHTER_TOOL', }[tool.tool]; this.brush_.setShape(shape); if (tool.tool != 'eraser') { @@ -87,8 +113,40 @@ class InkAPI { } 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); - document.querySelector('#ink-engine').dispatchEvent(event); + // 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); + } + + undo() { + this.embed_.undo(); + } + + redo() { + this.embed_.redo(); } } diff --git a/chromium/chrome/browser/resources/pdf/metrics.js b/chromium/chrome/browser/resources/pdf/metrics.js index d0cb77c8d97..d55c0d1f731 100644 --- a/chromium/chrome/browser/resources/pdf/metrics.js +++ b/chromium/chrome/browser/resources/pdf/metrics.js @@ -2,211 +2,225 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -(function() { - -'use strict'; - -// Keep in sync with enums.xml. -// Do not change the numeric values or reuse them since these numbers are -// persisted to logs. -const UserAction = { - DOCUMENT_OPENED: 0, // Baseline to use as denominator for all formulas. - ROTATE_FIRST: 1, - ROTATE: 2, - FIT_TO_WIDTH_FIRST: 3, - FIT_TO_WIDTH: 4, - FIT_TO_PAGE_FIRST: 5, - FIT_TO_PAGE: 6, - OPEN_BOOKMARKS_PANEL_FIRST: 7, - OPEN_BOOKMARKS_PANEL: 8, - FOLLOW_BOOKMARK_FIRST: 9, - FOLLOW_BOOKMARK: 10, - PAGE_SELECTOR_NAVIGATE_FIRST: 11, - PAGE_SELECTOR_NAVIGATE: 12, - NUMBER_OF_ACTIONS: 13 -}; /** * Handles events specific to the PDF viewer and logs the corresponding metrics. - * - * @interface */ -window.PDFMetrics = class { - constructor() {} - - /** - * Call 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. - */ - onDocumentOpened() {} - - /** - * Call when the document is rotated clockwise or counter-clockwise. - */ - onRotation() {} - +class PDFMetrics { /** - * Call when the zoom mode is changed to fit a FittingType. + * Records when the zoom mode is changed to fit a FittingType. * * @param {FittingType} fittingType the new FittingType. */ - onFitTo(fittingType) {} + static recordFitTo(fittingType) { + if (fittingType == FittingType.FIT_TO_PAGE) { + PDFMetrics.record(PDFMetrics.UserAction.FIT_TO_PAGE); + } else if (fittingType == FittingType.FIT_TO_WIDTH) { + PDFMetrics.record(PDFMetrics.UserAction.FIT_TO_WIDTH); + } + // There is no user action to do a fit-to-height, this only happens with + // the open param "view=FitV". + } /** - * Call when the bookmarks panel is opened. + * Records the given action to chrome.metricsPrivate. + * + * @param {PDFMetrics.UserAction} action */ - onOpenBookmarksPanel() {} + static record(action) { + if (!chrome.metricsPrivate) { + return; + } + if (!PDFMetrics.actionsMetric_) { + PDFMetrics.actionsMetric_ = { + 'metricName': 'PDF.Actions', + 'type': chrome.metricsPrivate.MetricTypeType.HISTOGRAM_LOG, + 'min': 1, + 'max': PDFMetrics.UserAction.NUMBER_OF_ACTIONS, + 'buckets': PDFMetrics.UserAction.NUMBER_OF_ACTIONS + 1 + }; + } + chrome.metricsPrivate.recordValue(PDFMetrics.actionsMetric_, action); + if (PDFMetrics.firstMap_.has(action)) { + const firstAction = PDFMetrics.firstMap_.get(action); + if (!PDFMetrics.firstActionRecorded_.has(firstAction)) { + chrome.metricsPrivate.recordValue( + PDFMetrics.actionsMetric_, firstAction); + PDFMetrics.firstActionRecorded_.add(firstAction); + } + } + } - /** - * Call when a bookmark is followed. - */ - onFollowBookmark() {} + static resetForTesting() { + PDFMetrics.firstActionRecorded_.clear(); + PDFMetrics.actionsMetric_ = null; + } +} - /** - * Call when the page selection is used to navigate to another page. - */ - onPageSelectorNavigation() {} -}; +/** @private {?chrome.metricsPrivate.MetricType} */ +PDFMetrics.actionsMetric_ = null; + +/** @private {Set} */ +PDFMetrics.firstActionRecorded_ = new Set(); +// Keep in sync with enums.xml. +// Do not change the numeric values or reuse them since these numbers are +// persisted to logs. /** - * Dummy implementation of PDFMetrics. - * This is used in print preview mode to avoid bundling the actions in the PDF - * viewer and the print preview in the same histogram. Also, metricsPrivate is - * not available in print preview. + * User Actions that can be recorded by calling PDFMetrics.record. + * The *_FIRST values are recorded automaticlly, + * eg. PDFMetrics.record(...ROTATE) will also record ROTATE_FIRST + * on the first instance. * - * @implements {PDFMetrics} + * @enum {number} */ -window.PDFMetricsDummy = class { - constructor() {} +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. + */ + DOCUMENT_OPENED: 0, - /** @override */ - onDocumentOpened() {} + /** Recorded when the document is rotated clockwise or counter-clockwise. */ + ROTATE_FIRST: 1, + ROTATE: 2, - /** @override */ - onRotation() {} + FIT_TO_WIDTH_FIRST: 3, + FIT_TO_WIDTH: 4, - /** @override */ - onFitTo(fittingType) {} + FIT_TO_PAGE_FIRST: 5, + FIT_TO_PAGE: 6, - /** @override */ - onOpenBookmarksPanel() {} + /** Recorded when the bookmarks panel is opened. */ + OPEN_BOOKMARKS_PANEL_FIRST: 7, + OPEN_BOOKMARKS_PANEL: 8, - /** @override */ - onFollowBookmark() {} + /** Recorded when a bookmark is followed. */ + FOLLOW_BOOKMARK_FIRST: 9, + FOLLOW_BOOKMARK: 10, - /** @override */ - onPageSelectorNavigation() {} -}; + /** Recorded when the page selection is used to navigate to another page. */ + PAGE_SELECTOR_NAVIGATE_FIRST: 11, + PAGE_SELECTOR_NAVIGATE: 12, -/** - * Implementation of PDFMetrics that logs the corresponding metrics to UMA - * through chrome.metricsPrivate. - * - * @implements {PDFMetrics} - */ -window.PDFMetricsImpl = class { - constructor() { - /** - * @private {Set} - */ - this.firstEventLogged_ = new Set(); - - /** - * @private {Object} - */ - this.actionsMetric_ = { - 'metricName': 'PDF.Actions', - 'type': chrome.metricsPrivate.MetricTypeType.HISTOGRAM_LOG, - 'min': 1, - 'max': UserAction.NUMBER_OF_ACTIONS, - 'buckets': UserAction.NUMBER_OF_ACTIONS + 1 - }; - } + /** Recorded when the user triggers a save of the document. */ + SAVE_FIRST: 13, + SAVE: 14, - /** @override */ - onDocumentOpened() { - this.logOnlyFirstTime_(UserAction.DOCUMENT_OPENED); - } + /** + * 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, - /** @override */ - onRotation() { - this.logFirstAndTotal_(UserAction.ROTATE_FIRST, UserAction.ROTATE); - } + PRINT_FIRST: 17, + PRINT: 18, - /** @override */ - onFitTo(fittingType) { - if (fittingType == FittingType.FIT_TO_PAGE) { - this.logFirstAndTotal_( - UserAction.FIT_TO_PAGE_FIRST, UserAction.FIT_TO_PAGE); - } else if (fittingType == FittingType.FIT_TO_WIDTH) { - this.logFirstAndTotal_( - UserAction.FIT_TO_WIDTH_FIRST, UserAction.FIT_TO_WIDTH); - } - // There is no user action to do a fit-to-height, this only happens with - // the open param "view=FitV". - } + ENTER_ANNOTATION_MODE_FIRST: 19, + ENTER_ANNOTATION_MODE: 20, - /** @override */ - onOpenBookmarksPanel() { - this.logFirstAndTotal_( - UserAction.OPEN_BOOKMARKS_PANEL_FIRST, UserAction.OPEN_BOOKMARKS_PANEL); - } + EXIT_ANNOTATION_MODE_FIRST: 21, + EXIT_ANNOTATION_MODE: 22, - /** @override */ - onFollowBookmark() { - this.logFirstAndTotal_( - UserAction.FOLLOW_BOOKMARK_FIRST, UserAction.FOLLOW_BOOKMARK); - } + /** Recorded when a pen stroke is made. */ + ANNOTATE_STROKE_TOOL_PEN_FIRST: 23, + ANNOTATE_STROKE_TOOL_PEN: 24, - /** @override */ - onPageSelectorNavigation() { - this.logFirstAndTotal_( - UserAction.PAGE_SELECTOR_NAVIGATE_FIRST, - UserAction.PAGE_SELECTOR_NAVIGATE); - } + /** Recorded when an eraser stroke is made. */ + ANNOTATE_STROKE_TOOL_ERASER_FIRST: 25, + ANNOTATE_STROKE_TOOL_ERASER: 26, - /** - * Logs the "first" event code if it hasn't been logged by this instance yet - * and also log the "total" event code. This distinction allows analyzing - * both: - * - in what percentage of documents each action was taken; - * - how many times, on average, each action is taken on a document; - * - * @param {number} firstEventCode event code for the "first" metric. - * @return {number} totalEventCode event code for the "total" metric. - * @private - */ - logFirstAndTotal_(firstEventCode, totalEventCode) { - this.log_(totalEventCode); - this.logOnlyFirstTime_(firstEventCode); - } + /** Recorded when a highlighter stroke is made. */ + ANNOTATE_STROKE_TOOL_HIGHLIGHTER_FIRST: 27, + ANNOTATE_STROKE_TOOL_HIGHLIGHTER: 28, - /** - * Logs the given event code to chrome.metricsPrivate. - * - * @param {number} eventCode event code to log. - * @private - */ - log_(eventCode) { - chrome.metricsPrivate.recordValue(this.actionsMetric_, eventCode); - } + /** Recorded when a stroke is made using touch. */ + ANNOTATE_STROKE_DEVICE_TOUCH_FIRST: 29, + ANNOTATE_STROKE_DEVICE_TOUCH: 30, - /** - * Logs the given event code. Subsequent calls of this method with the same - * event code have no effect on the this PDFMetrics instance. - * - * @param {number} eventCode event code to log. - * @private - */ - logOnlyFirstTime_(eventCode) { - if (!this.firstEventLogged_.has(eventCode)) { - this.log_(eventCode); - this.firstEventLogged_.add(eventCode); - } - } -}; + /** Recorded when a stroke is made using mouse. */ + ANNOTATE_STROKE_DEVICE_MOUSE_FIRST: 31, + ANNOTATE_STROKE_DEVICE_MOUSE: 32, -window.PDFMetrics.UserAction = UserAction; + /** Recorded when a stroke is made using pen. */ + ANNOTATE_STROKE_DEVICE_PEN_FIRST: 33, + ANNOTATE_STROKE_DEVICE_PEN: 34, + + NUMBER_OF_ACTIONS: 35, +}; -}()); +// Map from UserAction to the 'FIRST' action. These metrics are recorded +// by PDFMetrics.log the first time each corresponding action occurs. +/** @private Map<number, number> */ +PDFMetrics.firstMap_ = new Map([ + [ + PDFMetrics.UserAction.ROTATE, + PDFMetrics.UserAction.ROTATE_FIRST, + ], + [ + PDFMetrics.UserAction.FIT_TO_WIDTH, + PDFMetrics.UserAction.FIT_TO_WIDTH_FIRST, + ], + [ + PDFMetrics.UserAction.FIT_TO_PAGE, + PDFMetrics.UserAction.FIT_TO_PAGE_FIRST, + ], + [ + PDFMetrics.UserAction.OPEN_BOOKMARKS_PANEL, + PDFMetrics.UserAction.OPEN_BOOKMARKS_PANEL_FIRST, + ], + [ + PDFMetrics.UserAction.FOLLOW_BOOKMARK, + PDFMetrics.UserAction.FOLLOW_BOOKMARK_FIRST, + ], + [ + PDFMetrics.UserAction.PAGE_SELECTOR_NAVIGATE, + PDFMetrics.UserAction.PAGE_SELECTOR_NAVIGATE_FIRST, + ], + [ + PDFMetrics.UserAction.SAVE, + PDFMetrics.UserAction.SAVE_FIRST, + ], + [ + PDFMetrics.UserAction.SAVE_WITH_ANNOTATION, + PDFMetrics.UserAction.SAVE_WITH_ANNOTATION_FIRST, + ], + [ + PDFMetrics.UserAction.PRINT, + PDFMetrics.UserAction.PRINT_FIRST, + ], + [ + PDFMetrics.UserAction.ENTER_ANNOTATION_MODE, + PDFMetrics.UserAction.ENTER_ANNOTATION_MODE_FIRST, + ], + [ + PDFMetrics.UserAction.EXIT_ANNOTATION_MODE, + PDFMetrics.UserAction.EXIT_ANNOTATION_MODE_FIRST, + ], + [ + PDFMetrics.UserAction.ANNOTATE_STROKE_TOOL_PEN, + PDFMetrics.UserAction.ANNOTATE_STROKE_TOOL_PEN_FIRST, + ], + [ + PDFMetrics.UserAction.ANNOTATE_STROKE_TOOL_ERASER, + PDFMetrics.UserAction.ANNOTATE_STROKE_TOOL_ERASER_FIRST, + ], + [ + PDFMetrics.UserAction.ANNOTATE_STROKE_TOOL_HIGHLIGHTER, + PDFMetrics.UserAction.ANNOTATE_STROKE_TOOL_HIGHLIGHTER_FIRST, + ], + [ + PDFMetrics.UserAction.ANNOTATE_STROKE_DEVICE_TOUCH, + PDFMetrics.UserAction.ANNOTATE_STROKE_DEVICE_TOUCH_FIRST, + ], + [ + PDFMetrics.UserAction.ANNOTATE_STROKE_DEVICE_MOUSE, + PDFMetrics.UserAction.ANNOTATE_STROKE_DEVICE_MOUSE_FIRST, + ], + [ + PDFMetrics.UserAction.ANNOTATE_STROKE_DEVICE_PEN, + PDFMetrics.UserAction.ANNOTATE_STROKE_DEVICE_PEN_FIRST, + ], +]); diff --git a/chromium/chrome/browser/resources/pdf/pdf_viewer.js b/chromium/chrome/browser/resources/pdf/pdf_viewer.js index 03f0a28d55e..284afedf2d9 100644 --- a/chromium/chrome/browser/resources/pdf/pdf_viewer.js +++ b/chromium/chrome/browser/resources/pdf/pdf_viewer.js @@ -64,7 +64,8 @@ function shouldIgnoreKeyEvents(activeElement) { } return ( - activeElement.isContentEditable || activeElement.tagName == 'INPUT' || + activeElement.isContentEditable || + (activeElement.tagName == 'INPUT' && activeElement.type != 'radio') || activeElement.tagName == 'TEXTAREA'); } @@ -135,12 +136,13 @@ function PDFViewer(browserApi) { /** @private {boolean} */ this.hasEnteredAnnotationMode_ = false; - /** - * @type {!PDFMetrics} - */ - this.metrics = - (chrome.metricsPrivate ? new PDFMetricsImpl() : new PDFMetricsDummy()); - this.metrics.onDocumentOpened(); + /** @private {boolean} */ + this.hadPassword_ = false; + + /** @private {boolean} */ + this.canSerializeDocument_ = false; + + PDFMetrics.record(PDFMetrics.UserAction.DOCUMENT_OPENED); // Parse open pdf parameters. this.paramsParser_ = new OpenPDFParamsParser( @@ -175,7 +177,7 @@ function PDFViewer(browserApi) { this.browserApi_.getZoomBehavior() == BrowserApi.ZoomBehavior.MANAGE ? this.browserApi_.getDefaultZoom() : 1.0; - this.viewport_ = new Viewport( + this.viewport_ = new ViewportImpl( window, this.sizer_, this.viewportChanged_.bind(this), () => this.currentController_.beforeZoom(), () => { @@ -255,9 +257,13 @@ function PDFViewer(browserApi) { this.toolbar_.addEventListener('save', () => this.save()); this.toolbar_.addEventListener('print', () => this.print()); this.toolbar_.addEventListener( - 'rotate-right', () => this.currentController_.rotateClockwise()); + 'undo', () => this.currentController_.undo()); + this.toolbar_.addEventListener( + 'redo', () => this.currentController_.redo()); + this.toolbar_.addEventListener( + 'rotate-right', () => this.rotateClockwise()); this.toolbar_.addEventListener( - 'annotation-mode-changed', e => this.annotationModeChanged_(e)); + 'annotation-mode-toggled', e => this.annotationModeToggled_(e)); this.toolbar_.addEventListener( 'annotation-tool-changed', e => this.inkController_.setAnnotationTool(e.detail.value)); @@ -268,9 +274,9 @@ function PDFViewer(browserApi) { document.body.addEventListener('change-page', e => { this.viewport_.goToPage(e.detail.page); if (e.detail.origin == 'bookmark') { - this.metrics.onFollowBookmark(); + PDFMetrics.record(PDFMetrics.UserAction.FOLLOW_BOOKMARK); } else if (e.detail.origin == 'pageselector') { - this.metrics.onPageSelectorNavigation(); + PDFMetrics.record(PDFMetrics.UserAction.PAGE_SELECTOR_NAVIGATE); } }); @@ -288,7 +294,7 @@ function PDFViewer(browserApi) { document.body.addEventListener('dropdown-opened', e => { if (e.detail == 'bookmarks') { - this.metrics.onOpenBookmarksPanel(); + PDFMetrics.record(PDFMetrics.UserAction.OPEN_BOOKMARKS_PANEL); } }); @@ -320,6 +326,11 @@ function PDFViewer(browserApi) { // Request translated strings. chrome.resourcesPrivate.getStrings('pdf', this.handleStrings_.bind(this)); + + // Listen for save commands from the browser. + if (chrome.mimeHandlerPrivate && chrome.mimeHandlerPrivate.onSave) { + chrome.mimeHandlerPrivate.onSave.addListener(this.onSave.bind(this)); + } } PDFViewer.prototype = { @@ -444,7 +455,7 @@ PDFViewer.prototype = { return; case 219: // Left bracket key. if (e.ctrlKey) { - this.currentController_.rotateCounterClockwise(); + this.rotateCounterclockwise(); } return; case 220: // Backslash key. @@ -454,7 +465,7 @@ PDFViewer.prototype = { return; case 221: // Right bracket key. if (e.ctrlKey) { - this.currentController_.rotateClockwise(); + this.rotateClockwise(); } return; } @@ -493,19 +504,31 @@ PDFViewer.prototype = { /** * Handles the annotation mode being toggled on or off. * - * @param {CustomEvent} e + * @param {!CustomEvent<{value: boolean}>} e * @private */ - annotationModeChanged_: async function(e) { + annotationModeToggled_: async function(e) { const annotationMode = e.detail.value; if (annotationMode) { - this.hasEnteredAnnotationMode_ = true; - assert(this.currentController_ == this.pluginController_); // Enter annotation mode. + assert(this.currentController_ == this.pluginController_); // TODO(dstockwell): set plugin read-only, begin transition this.updateProgress(0); // TODO(dstockwell): handle save failure const result = await this.pluginController_.save(true); + if (result.hasUnsavedChanges) { + assert(!loadTimeData.getBoolean('pdfFormSaveEnabled')); + try { + await $('form-warning').show(); + } catch (e) { + // The user aborted entering annotation mode. Revert to the plugin. + this.toolbar_.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); await this.inkController_.load(result.fileName, result.dataToSave); @@ -515,6 +538,7 @@ PDFViewer.prototype = { this.updateProgress(100); } else { // Exit annotation mode. + PDFMetrics.record(PDFMetrics.UserAction.EXIT_ANNOTATION_MODE); assert(this.currentController_ == this.inkController_); // TODO(dstockwell): set ink read-only, begin transition this.updateProgress(0); @@ -548,7 +572,10 @@ PDFViewer.prototype = { /** * Request to change the viewport fitting type. * - * @param {CustomEvent} e Event received with the new FittingType as detail. + * @param {!CustomEvent<{ + * fittingType: FittingType, + * userInitiated: boolean + * }>} e * @private */ fitToChanged_: function(e) { @@ -563,7 +590,7 @@ PDFViewer.prototype = { } if (e.detail.userInitiated) { - this.metrics.onFitTo(e.detail.fittingType); + PDFMetrics.recordFitTo(e.detail.fittingType); } }, @@ -634,7 +661,7 @@ PDFViewer.prototype = { goToPageAndXY_: function(origin, page, message) { this.viewport_.goToPageAndXY(page, message.x, message.y); if (origin == 'bookmark') { - this.metrics.onFollowBookmark(); + PDFMetrics.record(PDFMetrics.UserAction.FOLLOW_BOOKMARK); } }, @@ -728,6 +755,9 @@ PDFViewer.prototype = { $('zoom-toolbar').strings = strings; $('password-screen').strings = strings; $('error-screen').strings = strings; + if ($('form-warning')) { + $('form-warning').strings = strings; + } }, /** @@ -1050,6 +1080,8 @@ PDFViewer.prototype = { // If the password screen isn't up, put it up. Otherwise we're // responding to an incorrect password so deny it. if (!this.passwordScreen_.active) { + this.hadPassword_ = true; + this.updateAnnotationAvailable_(); this.passwordScreen_.show(); } else { this.passwordScreen_.deny(); @@ -1095,8 +1127,9 @@ PDFViewer.prototype = { * Sets document metadata from the current controller. * @param {string} title * @param {Array} bookmarks + * @param {boolean} canSerializeDocument */ - setDocumentMetadata: function(title, bookmarks) { + setDocumentMetadata: function(title, bookmarks, canSerializeDocument) { if (title) { document.title = title; } else { @@ -1107,6 +1140,8 @@ PDFViewer.prototype = { this.toolbar_.docTitle = document.title; this.toolbar_.bookmarks = this.bookmarks; } + this.canSerializeDocument_ = canSerializeDocument; + this.updateAnnotationAvailable_(); }, /** @@ -1126,9 +1161,28 @@ PDFViewer.prototype = { }, /** + * An event handler for when the browser tells the PDF Viewer to perform a + * save. + * + * @param {string} streamUrl unique identifier for a PDF Viewer instance. + * @private + */ + onSave: async function(streamUrl) { + if (streamUrl != this.browserApi_.getStreamInfo().streamUrl) { + return; + } + + this.save(); + }, + + /** * Saves the current PDF document to disk. */ save: async function() { + PDFMetrics.record(PDFMetrics.UserAction.SAVE); + if (this.hasEnteredAnnotationMode_) { + 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. @@ -1148,9 +1202,20 @@ PDFViewer.prototype = { chrome.fileSystem.chooseEntry( {type: 'saveFile', suggestedName: fileName}, entry => { + if (chrome.runtime.lastError) { + if (chrome.runtime.lastError.message != 'User cancelled') { + console.log( + 'chrome.fileSystem.chooseEntry failed: ' + + chrome.runtime.lastError.message); + } + return; + } entry.createWriter(writer => { writer.write( new Blob([result.dataToSave], {type: 'application/pdf'})); + // Unblock closing the window now that the user has saved + // successfully. + chrome.mimeHandlerPrivate.setShowBeforeUnloadDialog(false); }); }); @@ -1159,8 +1224,52 @@ PDFViewer.prototype = { }, print: async function() { + PDFMetrics.record(PDFMetrics.UserAction.PRINT); await this.exitAnnotationMode_(); this.currentController_.print(); + }, + + /** + * Updates the toolbar's annotation available flag depending on current + * conditions. + */ + updateAnnotationAvailable_() { + let annotationAvailable = true; + if (this.viewport_.getClockwiseRotations() != 0) { + annotationAvailable = false; + } + if (this.hadPassword_) { + annotationAvailable = false; + } + if (!this.canSerializeDocument_) { + annotationAvailable = false; + } + this.toolbar_.annotationAvailable = annotationAvailable; + }, + + rotateClockwise() { + PDFMetrics.record(PDFMetrics.UserAction.ROTATE); + this.viewport_.rotateClockwise(1); + this.currentController_.rotateClockwise(); + this.updateAnnotationAvailable_(); + }, + + rotateCounterclockwise() { + PDFMetrics.record(PDFMetrics.UserAction.ROTATE); + this.viewport_.rotateClockwise(3); + this.currentController_.rotateCounterclockwise(); + this.updateAnnotationAvailable_(); + }, + + setHasUnsavedChanges: function() { + // Warn the user if they attempt to close the window without saving. + chrome.mimeHandlerPrivate.setShowBeforeUnloadDialog(true); + }, + + /** @param {UndoState} state */ + setAnnotationUndoState(state) { + this.toolbar_.canUndoAnnotation = state.canUndo; + this.toolbar_.canRedoAnnotation = state.canRedo; } }; @@ -1193,7 +1302,7 @@ class ContentController { * Rotates the document 90 degrees in the counter clockwise direction. * @abstract */ - rotateCounterClockwise() {} + rotateCounterclockwise() {} /** * Triggers printing of the current document. @@ -1201,6 +1310,16 @@ class ContentController { print() {} /** + * Undo an edit action. + */ + undo() {} + + /** + * Redo an edit action. + */ + redo() {} + + /** * Requests that the current document be saved. * @param {boolean} requireResult whether a response is required, otherwise * the controller may save the document to disk internally. @@ -1253,7 +1372,7 @@ class InkController extends ContentController { } /** @override */ - rotateCounterClockwise() { + rotateCounterclockwise() { // TODO(dstockwell): implement rotation } @@ -1268,11 +1387,27 @@ class InkController extends ContentController { } /** @override */ + undo() { + this.inkHost_.undo(); + } + + /** @override */ + redo() { + this.inkHost_.redo(); + } + + /** @override */ load(filename, data) { if (!this.inkHost_) { this.inkHost_ = document.createElement('viewer-ink-host'); $('content').appendChild(this.inkHost_); this.inkHost_.viewport = this.viewport_; + this.inkHost_.addEventListener('stroke-added', e => { + this.viewer_.setHasUnsavedChanges(); + }); + this.inkHost_.addEventListener('undo-state-changed', e => { + this.viewer_.setAnnotationUndoState(e.detail); + }); } return this.inkHost_.load(filename, data); } @@ -1365,15 +1500,11 @@ class PluginController extends ContentController { /** @override */ rotateClockwise() { - this.viewer_.metrics.onRotation(); - this.viewport_.rotateClockwise(1); this.postMessage({type: 'rotateClockwise'}); } /** @override */ - rotateCounterClockwise() { - this.viewer_.metrics.onRotation(); - this.viewport_.rotateClockwise(3); + rotateCounterclockwise() { this.postMessage({type: 'rotateCounterclockwise'}); } @@ -1421,7 +1552,7 @@ class PluginController extends ContentController { this.viewer_.handleBeep(); break; case 'documentDimensions': - viewer.setDocumentDimensions(message.data); + this.viewer_.setDocumentDimensions(message.data); break; case 'email': const href = 'mailto:' + message.data.to + '?cc=' + message.data.cc + @@ -1458,7 +1589,8 @@ class PluginController extends ContentController { break; case 'metadata': this.viewer_.setDocumentMetadata( - message.data.title, message.data.bookmarks); + message.data.title, message.data.bookmarks, + message.data.canSerializeDocument); break; case 'setIsSelecting': this.viewer_.setIsSelecting(message.data.isSelecting); diff --git a/chromium/chrome/browser/resources/pdf/viewport.js b/chromium/chrome/browser/resources/pdf/viewport.js index dce8d534ccd..02664b3fe33 100644 --- a/chromium/chrome/browser/resources/pdf/viewport.js +++ b/chromium/chrome/browser/resources/pdf/viewport.js @@ -2,37 +2,24 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -/** - * @typedef {{ - * x: number, - * y: number - * }} - */ -let Point; - -/** - * @typedef {{ - * x: (number|undefined), - * y: (number|undefined), - * }} - */ -let PartialPoint; /** - * @typedef {{ - * x: number, - * y: number, - * width: number, - * heigh: number, - * }} + * Clamps the zoom factor (or page scale factor) to be within the limits. + * + * @param {number} factor The zoom/scale factor. + * @return {number} The factor clamped within the limits. */ -let Rect; +function clampZoom(factor) { + return Math.max( + Viewport.ZOOM_FACTOR_RANGE.min, + Math.min(factor, Viewport.ZOOM_FACTOR_RANGE.max)); +} /** * Returns the height of the intersection of two rectangles. * - * @param {Rect} rect1 the first rect - * @param {Rect} rect2 the second rect + * @param {!ViewportRect} rect1 the first rect + * @param {!ViewportRect} rect2 the second rect * @return {number} the height of the intersection of the rects */ function getIntersectionHeight(rect1, rect2) { @@ -45,9 +32,9 @@ function getIntersectionHeight(rect1, rect2) { /** * Computes vector between two points. * - * @param {!Object} p1 The first point. - * @param {!Object} p2 The second point. - * @return {!Object} The vector. + * @param {!Point} p1 The first point. + * @param {!Point} p2 The second point. + * @return {!Point} The vector. */ function vectorDelta(p1, p2) { return {x: p2.x - p1.x, y: p2.y - p1.y}; @@ -61,133 +48,75 @@ function frameToPluginCoordinate(coordinateInFrame) { }; } -// TODO: convert Viewport to ES6 class syntax -/** - * Create a new viewport. - * - * @param {Window} window the window - * @param {Object} sizer is the element which represents the size of the - * document in the viewport - * @param {Function} viewportChangedCallback is run when the viewport changes - * @param {Function} beforeZoomCallback is run before a change in zoom - * @param {Function} afterZoomCallback is run after a change in zoom - * @param {Function} setUserInitiatedCallback is run to indicate whether a zoom - * event is user initiated. - * @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 - */ -function Viewport( - window, sizer, viewportChangedCallback, beforeZoomCallback, - afterZoomCallback, setUserInitiatedCallback, scrollbarWidth, defaultZoom, - topToolbarHeight) { - this.window_ = window; - this.sizer_ = sizer; - this.viewportChangedCallback_ = viewportChangedCallback; - this.beforeZoomCallback_ = beforeZoomCallback; - this.afterZoomCallback_ = afterZoomCallback; - this.setUserInitiatedCallback_ = setUserInitiatedCallback; - this.allowedToChangeZoom_ = false; - this.internalZoom_ = 1; - this.zoomManager_ = new InactiveZoomManager(this, 1); - this.documentDimensions_ = null; - this.pageDimensions_ = []; - this.scrollbarWidth_ = scrollbarWidth; - this.fittingType_ = FittingType.NONE; - this.defaultZoom_ = defaultZoom; - this.topToolbarHeight_ = topToolbarHeight; - this.prevScale_ = 1; - this.pinchPhase_ = Viewport.PinchPhase.PINCH_NONE; - this.pinchPanVector_ = null; - this.pinchCenter_ = null; - this.firstPinchCenterInFrame_ = null; - this.rotations_ = 0; - - window.addEventListener('scroll', this.updateViewport_.bind(this)); - window.addEventListener('resize', this.resizeWrapper_.bind(this)); -} - -/** - * Enumeration of pinch states. - * This should match PinchPhase enum in pdf/out_of_process_instance.h - * @enum {number} - */ -Viewport.PinchPhase = { - PINCH_NONE: 0, - PINCH_START: 1, - PINCH_UPDATE_ZOOM_OUT: 2, - PINCH_UPDATE_ZOOM_IN: 3, - PINCH_END: 4 -}; - -/** - * The increment to scroll a page by in pixels when up/down/left/right arrow - * 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. - */ -Viewport.SCROLL_INCREMENT = 40; - -/** - * Predefined zoom factors to be used when zooming in/out. These are in - * ascending order. This should match the lists in - * components/ui/zoom/page_zoom_constants.h and - * chrome/browser/resources/settings/appearance_page/appearance_page.js - */ -Viewport.ZOOM_FACTORS = [ - 0.25, 1 / 3, 0.5, 2 / 3, 0.75, 0.8, 0.9, 1, 1.1, 1.25, 1.5, 1.75, 2, 2.5, 3, - 4, 5 -]; - -/** - * The minimum and maximum range to be used to clip zoom factor. - */ -Viewport.ZOOM_FACTOR_RANGE = { - min: Viewport.ZOOM_FACTORS[0], - max: Viewport.ZOOM_FACTORS[Viewport.ZOOM_FACTORS.length - 1] -}; - -/** - * Clamps the zoom factor (or page scale factor) to be within the limits. - * - * @param {number} factor The zoom/scale factor. - * @return {number} The factor clamped within the limits. - */ -Viewport.clampZoom = function(factor) { - return Math.max( - Viewport.ZOOM_FACTOR_RANGE.min, - Math.min(factor, Viewport.ZOOM_FACTOR_RANGE.max)); -}; - -/** - * The width of the page shadow around pages in pixels. - */ -Viewport.PAGE_SHADOW = { - top: 3, - bottom: 7, - left: 5, - right: 5 -}; +/** @implements {Viewport} */ +class ViewportImpl { + /** + * Create a new viewport. + * + * @param {Window} window the window + * @param {Object} sizer is the element which represents the size of the + * document in the viewport + * @param {Function} viewportChangedCallback is run when the viewport changes + * @param {Function} beforeZoomCallback is run before a change in zoom + * @param {Function} afterZoomCallback is run after a change in zoom + * @param {Function} setUserInitiatedCallback is run to indicate whether a + * zoom event is user initiated. + * @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, viewportChangedCallback, beforeZoomCallback, + afterZoomCallback, setUserInitiatedCallback, scrollbarWidth, defaultZoom, + topToolbarHeight) { + this.window_ = window; + this.sizer_ = sizer; + this.viewportChangedCallback_ = viewportChangedCallback; + this.beforeZoomCallback_ = beforeZoomCallback; + this.afterZoomCallback_ = afterZoomCallback; + this.setUserInitiatedCallback_ = setUserInitiatedCallback; + this.allowedToChangeZoom_ = false; + this.internalZoom_ = 1; + this.zoomManager_ = new InactiveZoomManager(this, 1); + /** @private {?DocumentDimensions} */ + this.documentDimensions_ = null; + /** @private {Array<ViewportRect>} */ + this.pageDimensions_ = []; + this.scrollbarWidth_ = scrollbarWidth; + this.fittingType_ = FittingType.NONE; + this.defaultZoom_ = defaultZoom; + this.topToolbarHeight_ = topToolbarHeight; + this.prevScale_ = 1; + this.pinchPhase_ = Viewport.PinchPhase.PINCH_NONE; + this.pinchPanVector_ = null; + this.pinchCenter_ = null; + /** @private {?Point} */ + this.firstPinchCenterInFrame_ = null; + this.rotations_ = 0; + // TODO(dstockwell): why isn't this private? + this.oldCenterInContent = null; + this.keepContentCentered_ = null; -Viewport.prototype = { + window.addEventListener('scroll', this.updateViewport_.bind(this)); + window.addEventListener('resize', this.resizeWrapper_.bind(this)); + } /** * @param {number} n the number of clockwise 90-degree rotations to * increment by. */ - rotateClockwise: function(n) { + rotateClockwise(n) { this.rotations_ = (this.rotations_ + n) % 4; - }, + } /** * @return {number} the number of clockwise 90-degree rotations that have been * applied. */ - getClockwiseRotations: function() { + getClockwiseRotations() { return this.rotations_; - }, + } /** * Converts a page position (e.g. the location of a bookmark) to a screen @@ -197,7 +126,7 @@ Viewport.prototype = { * @param {Point} point The position on `page`. * @return The screen position. */ - convertPageToScreen: function(page, point) { + convertPageToScreen(page, point) { const dimensions = this.getPageInsetDimensions(page); // width & height are already rotated. @@ -231,7 +160,7 @@ Viewport.prototype = { x: result.x + Viewport.PAGE_SHADOW.left, y: result.y + Viewport.PAGE_SHADOW.top, }; - }, + } /** @@ -244,7 +173,7 @@ Viewport.prototype = { * @return {Object} A dictionary with scaled 'width'/'height' of the document. * @private */ - getZoomedDocumentDimensions_: function(zoom) { + getZoomedDocumentDimensions_(zoom) { if (!this.documentDimensions_) { return null; } @@ -252,30 +181,23 @@ Viewport.prototype = { width: Math.round(this.documentDimensions_.width * zoom), height: Math.round(this.documentDimensions_.height * zoom) }; - }, + } - /** - * Returns the document dimensions. - * - * @return {Point} A dictionary with the 'width'/'height' of the document. - */ - getDocumentDimensions: function() { + /** @override */ + getDocumentDimensions() { return { width: this.documentDimensions_.width, height: this.documentDimensions_.height }; - }, + } /** - * Returns true if the document needs scrollbars at the given zoom level. - * * @param {number} zoom compute whether scrollbars are needed at this zoom - * @return {Object} with 'horizontal' and 'vertical' keys which map to bool - * values indicating if the horizontal and vertical scrollbars are needed - * respectively. + * @return {{horizontal: boolean, vertical: boolean}} whether horizontal or + * vertical scrollbars are needed. * @private */ - documentNeedsScrollbars_: function(zoom) { + documentNeedsScrollbars_(zoom) { const zoomedDimensions = this.getZoomedDocumentDimensions_(zoom); if (!zoomedDimensions) { return {horizontal: false, vertical: false}; @@ -294,7 +216,7 @@ Viewport.prototype = { vertical: zoomedDimensions.height + this.topToolbarHeight_ > this.window_.innerHeight }; - }, + } /** * Returns true if the document needs scrollbars at the current zoom level. @@ -303,50 +225,50 @@ Viewport.prototype = { * indicating if the horizontal and vertical scrollbars are needed * respectively. */ - documentHasScrollbars: function() { + documentHasScrollbars() { return this.documentNeedsScrollbars_(this.zoom); - }, + } /** * Helper function called when the zoomed document size changes. * * @private */ - contentSizeChanged_: function() { + contentSizeChanged_() { const zoomedDimensions = this.getZoomedDocumentDimensions_(this.zoom); if (zoomedDimensions) { this.sizer_.style.width = zoomedDimensions.width + 'px'; this.sizer_.style.height = zoomedDimensions.height + this.topToolbarHeight_ + 'px'; } - }, + } /** * Called when the viewport should be updated. * * @private */ - updateViewport_: function() { + updateViewport_() { this.viewportChangedCallback_(); - }, + } /** * Called when the browser window size changes. * * @private */ - resizeWrapper_: function() { + resizeWrapper_() { this.setUserInitiatedCallback_(false); this.resize_(); this.setUserInitiatedCallback_(true); - }, + } /** * Called when the viewport size changes. * * @private */ - resize_: function() { + resize_() { if (this.fittingType_ == FittingType.FIT_TO_PAGE) { this.fitToPageInternal_(false); } else if (this.fittingType_ == FittingType.FIT_TO_WIDTH) { @@ -358,30 +280,26 @@ Viewport.prototype = { } else { this.updateViewport_(); } - }, + } - /** - * @type {Point} the scroll position of the viewport. - */ + /** @override */ get position() { return { x: this.window_.pageXOffset, y: this.window_.pageYOffset - this.topToolbarHeight_ }; - }, + } /** * Scroll the viewport to the specified position. * - * @type {Point} position The position to scroll to. + * @param {Point} position The position to scroll to. */ set position(position) { this.window_.scrollTo(position.x, position.y + this.topToolbarHeight_); - }, + } - /** - * @type {Object} the size of the viewport excluding scrollbars. - */ + /** @override */ get size() { const needsScrollbars = this.documentNeedsScrollbars_(this.zoom); const scrollbarWidth = needsScrollbars.vertical ? this.scrollbarWidth_ : 0; @@ -391,14 +309,12 @@ Viewport.prototype = { width: this.window_.innerWidth - scrollbarWidth, height: this.window_.innerHeight - scrollbarHeight }; - }, + } - /** - * @type {number} the zoom level of the viewport. - */ + /** @override */ get zoom() { return this.zoomManager_.applyBrowserZoom(this.internalZoom_); - }, + } /** * Set the zoom manager. @@ -407,30 +323,31 @@ Viewport.prototype = { */ set zoomManager(manager) { this.zoomManager_ = manager; - }, + } /** - * @type {Viewport.PinchPhase} The phase of the current pinch gesture for + * @return {Viewport.PinchPhase} The phase of the current pinch gesture for * the viewport. */ get pinchPhase() { return this.pinchPhase_; - }, + } /** - * @type {Object} The panning caused by the current pinch gesture (as + * @return {Object} The panning caused by the current pinch gesture (as * the deltas of the x and y coordinates). */ get pinchPanVector() { return this.pinchPanVector_; - }, + } /** - * @type {Object} The coordinates of the center of the current pinch gesture. + * @return {Object} The coordinates of the center of the current pinch + * gesture. */ get pinchCenter() { return this.pinchCenter_; - }, + } /** * Used to wrap a function that might perform zooming on the viewport. This is @@ -441,13 +358,13 @@ Viewport.prototype = { * @param {Function} f Function to wrap * @private */ - mightZoom_: function(f) { + mightZoom_(f) { this.beforeZoomCallback_(); this.allowedToChangeZoom_ = true; f(); this.allowedToChangeZoom_ = false; this.afterZoomCallback_(); - }, + } /** * Sets the zoom of the viewport. @@ -455,7 +372,7 @@ Viewport.prototype = { * @param {number} newZoom the zoom level to zoom to. * @private */ - setZoomInternal_: function(newZoom) { + setZoomInternal_(newZoom) { assert( this.allowedToChangeZoom_, 'Called Viewport.setZoomInternal_ without calling ' + @@ -473,7 +390,7 @@ Viewport.prototype = { x: currentScrollPos.x * this.zoom, y: currentScrollPos.y * this.zoom }; - }, + } /** * Sets the zoom of the viewport. @@ -483,12 +400,12 @@ Viewport.prototype = { * @param {!Object} center The pinch center in content coordinates. * @private */ - setPinchZoomInternal_: function(scaleDelta, center) { + setPinchZoomInternal_(scaleDelta, center) { assert( this.allowedToChangeZoom_, 'Called Viewport.setPinchZoomInternal_ without calling ' + 'Viewport.mightZoom_.'); - this.internalZoom_ = Viewport.clampZoom(this.internalZoom_ * scaleDelta); + this.internalZoom_ = clampZoom(this.internalZoom_ * scaleDelta); const newCenterInContent = this.frameToContent(center); const delta = { @@ -505,7 +422,7 @@ Viewport.prototype = { this.contentSizeChanged_(); // Scroll to the scaled scroll position. this.position = {x: currentScrollPos.x, y: currentScrollPos.y}; - }, + } /** * Converts a point from frame to content coordinates. @@ -514,35 +431,30 @@ Viewport.prototype = { * @return {!Object} The content coordinates. * @private */ - frameToContent: function(framePoint) { + frameToContent(framePoint) { // TODO(mcnee) Add a helper Point class to avoid duplicating operations // on plain {x,y} objects. return { x: (framePoint.x + this.position.x) / this.zoom, y: (framePoint.y + this.position.y) / this.zoom }; - }, + } /** * Sets the zoom to the given zoom level. * * @param {number} newZoom the zoom level to zoom to. */ - setZoom: function(newZoom) { + setZoom(newZoom) { this.fittingType_ = FittingType.NONE; this.mightZoom_(() => { - this.setZoomInternal_(Viewport.clampZoom(newZoom)); + this.setZoomInternal_(clampZoom(newZoom)); this.updateViewport_(); }); - }, + } - /** - * Gets notified of the browser zoom changing seperately from the - * internal zoom. - * - * @param {number} oldBrowserZoom the previous value of the browser zoom. - */ - updateZoomFromBrowserChange: function(oldBrowserZoom) { + /** @override */ + updateZoomFromBrowserChange(oldBrowserZoom) { this.mightZoom_(() => { // Record the scroll position (relative to the top-left of the window). const oldZoom = oldBrowserZoom * this.internalZoom_; @@ -558,21 +470,21 @@ Viewport.prototype = { }; this.updateViewport_(); }); - }, + } /** - * @type {number} the width of scrollbars in the viewport in pixels. + * @return {number} the width of scrollbars in the viewport in pixels. */ get scrollbarWidth() { return this.scrollbarWidth_; - }, + } /** - * @type {FittingType} the fitting type the viewport is currently in. + * @return {FittingType} the fitting type the viewport is currently in. */ get fittingType() { return this.fittingType_; - }, + } /** * Get the which page is at a given y position. @@ -581,7 +493,7 @@ Viewport.prototype = { * @return {number} the index of a page overlapping the given y-coordinate. * @private */ - getPageAtY_: function(y) { + getPageAtY_(y) { let min = 0; let max = this.pageDimensions_.length - 1; while (max >= min) { @@ -607,12 +519,9 @@ Viewport.prototype = { } } return 0; - }, + } - /** - * @param {Point} point - * @return {boolean} Whether |point| (in screen coordinates) is inside a page - */ + /** @override */ isPointInsidePage(point) { const zoom = this.zoom; const size = this.size; @@ -632,7 +541,7 @@ Viewport.prototype = { const minX = (outerWidth - pageWidth) / 2; const maxX = outerWidth - minX; return x >= minX && x <= maxX; - }, + } /** * Returns the page with the greatest proportion of its height in the current @@ -640,7 +549,7 @@ Viewport.prototype = { * * @return {number} the index of the most visible page. */ - getMostVisiblePage: function() { + getMostVisiblePage() { const firstVisiblePage = this.getPageAtY_(this.position.y / this.zoom); if (firstVisiblePage == this.pageDimensions_.length - 1) { return firstVisiblePage; @@ -664,7 +573,7 @@ Viewport.prototype = { return firstVisiblePage + 1; } return firstVisiblePage; - }, + } /** * Compute the zoom level for fit-to-page, fit-to-width or fit-to-height. @@ -679,7 +588,7 @@ Viewport.prototype = { * @return {number} the internal zoom to set * @private */ - computeFittingZoom_: function(pageDimensions, fitWidth, fitHeight) { + computeFittingZoom_(pageDimensions, fitWidth, fitHeight) { assert( fitWidth || fitHeight, 'Invalid parameters. At least one of fitWidth and fitHeight must be ' + @@ -730,7 +639,7 @@ Viewport.prototype = { pageDimensions.height); return this.zoomManager_.internalZoomComponent(zoom); - }, + } /** * Compute a zoom level given the dimensions to fit and the actual numbers @@ -747,7 +656,7 @@ Viewport.prototype = { * @return {number} the internal zoom to set * @private */ - computeFittingZoomGivenDimensions_: function( + computeFittingZoomGivenDimensions_( fitWidth, fitHeight, windowWidth, windowHeight, pageWidth, pageHeight) { // Assumes at least one of {fitWidth, fitHeight} is set. let zoomWidth; @@ -772,12 +681,12 @@ Viewport.prototype = { } return Math.max(zoom, 0); - }, + } /** * Zoom the viewport so that the page width consumes the entire viewport. */ - fitToWidth: function() { + fitToWidth() { this.mightZoom_(() => { this.fittingType_ = FittingType.FIT_TO_WIDTH; if (!this.documentDimensions_) { @@ -789,7 +698,7 @@ Viewport.prototype = { this.computeFittingZoom_(this.documentDimensions_, true, false)); this.updateViewport_(); }); - }, + } /** * Zoom the viewport so that the page height consumes the entire viewport. @@ -799,7 +708,7 @@ Viewport.prototype = { * should remain at the current scroll position. * @private */ - fitToHeightInternal_: function(scrollToTopOfPage) { + fitToHeightInternal_(scrollToTopOfPage) { this.mightZoom_(() => { this.fittingType_ = FittingType.FIT_TO_HEIGHT; if (!this.documentDimensions_) { @@ -818,14 +727,14 @@ Viewport.prototype = { } this.updateViewport_(); }); - }, + } /** * Zoom the viewport so that the page height consumes the entire viewport. */ - fitToHeight: function() { + fitToHeight() { this.fitToHeightInternal_(true); - }, + } /** * Zoom the viewport so that a page consumes as much as possible of the it. @@ -835,7 +744,7 @@ Viewport.prototype = { * should remain at the current scroll position. * @private */ - fitToPageInternal_: function(scrollToTopOfPage) { + fitToPageInternal_(scrollToTopOfPage) { this.mightZoom_(() => { this.fittingType_ = FittingType.FIT_TO_PAGE; if (!this.documentDimensions_) { @@ -853,20 +762,20 @@ Viewport.prototype = { } this.updateViewport_(); }); - }, + } /** * Zoom the viewport so that a page consumes the entire viewport. Also scrolls * the viewport to the top of the current page. */ - fitToPage: function() { + fitToPage() { this.fitToPageInternal_(true); - }, + } /** * Zoom the viewport to the default zoom policy. */ - fitToNone: function() { + fitToNone() { this.mightZoom_(() => { this.fittingType_ = FittingType.NONE; if (!this.documentDimensions_) { @@ -877,12 +786,12 @@ Viewport.prototype = { this.computeFittingZoom_(this.documentDimensions_, true, false))); this.updateViewport_(); }); - }, + } /** * Zoom out to the next predefined zoom level. */ - zoomOut: function() { + zoomOut() { this.mightZoom_(() => { this.fittingType_ = FittingType.NONE; let nextZoom = Viewport.ZOOM_FACTORS[0]; @@ -894,12 +803,12 @@ Viewport.prototype = { this.setZoomInternal_(nextZoom); this.updateViewport_(); }); - }, + } /** * Zoom in to the next predefined zoom level. */ - zoomIn: function() { + zoomIn() { this.mightZoom_(() => { this.fittingType_ = FittingType.NONE; let nextZoom = Viewport.ZOOM_FACTORS[Viewport.ZOOM_FACTORS.length - 1]; @@ -911,26 +820,28 @@ Viewport.prototype = { this.setZoomInternal_(nextZoom); this.updateViewport_(); }); - }, + } /** * Pinch zoom event handler. * * @param {!Object} e The pinch event. */ - pinchZoom: function(e) { + 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_; - this.pinchPanVector_ = - vectorDelta(e.center, this.firstPinchCenterInFrame_); + if (this.firstPinchCenterInFrame_ != null) { + this.pinchPanVector_ = + vectorDelta(e.center, this.firstPinchCenterInFrame_); + } const needsScrollbars = this.documentNeedsScrollbars_(this.zoomManager_.applyBrowserZoom( - Viewport.clampZoom(this.internalZoom_ * scaleDelta))); + clampZoom(this.internalZoom_ * scaleDelta))); this.pinchCenter_ = e.center; @@ -955,9 +866,10 @@ Viewport.prototype = { this.updateViewport_(); this.prevScale_ = e.startScaleRatio; }); - }, + } - pinchZoomStart: function(e) { + /** @param {!Object} e The pinch event. */ + pinchZoomStart(e) { this.pinchPhase_ = Viewport.PinchPhase.PINCH_START; this.prevScale_ = 1; this.oldCenterInContent = @@ -968,9 +880,10 @@ Viewport.prototype = { // We keep track of begining of the pinch. // By doing so we will be able to compute the pan distance. this.firstPinchCenterInFrame_ = e.center; - }, + } - pinchZoomEnd: function(e) { + /** @param {!Object} e The pinch event. */ + pinchZoomEnd(e) { this.mightZoom_(() => { this.pinchPhase_ = Viewport.PinchPhase.PINCH_END; const scaleDelta = e.startScaleRatio / this.prevScale_; @@ -984,16 +897,16 @@ Viewport.prototype = { this.pinchPanVector_ = null; this.pinchCenter_ = null; this.firstPinchCenterInFrame_ = null; - }, + } /** * Go to the given page index. * * @param {number} page the index of the page to go to. zero-based. */ - goToPage: function(page) { + goToPage(page) { this.goToPageAndXY(page, 0, 0); - }, + } /** * Go to the given y position in the given page index. @@ -1002,7 +915,7 @@ Viewport.prototype = { * @param {number} x the x position in the page to go to. * @param {number} y the y position in the page to go to. */ - goToPageAndXY: function(page, x, y) { + goToPageAndXY(page, x, y) { this.mightZoom_(() => { if (this.pageDimensions_.length === 0) { return; @@ -1027,14 +940,15 @@ Viewport.prototype = { }; this.updateViewport_(); }); - }, + } /** * Set the dimensions of the document. * - * @param {Object} documentDimensions the dimensions of the document + * @param {DocumentDimensions} documentDimensions the dimensions of the + * document */ - setDocumentDimensions: function(documentDimensions) { + setDocumentDimensions(documentDimensions) { this.mightZoom_(() => { const initialDimensions = !this.documentDimensions_; this.documentDimensions_ = documentDimensions; @@ -1048,13 +962,13 @@ Viewport.prototype = { this.contentSizeChanged_(); this.resize_(); }); - }, + } /** * @param {number} page - * @return {Rect} The bounds for page `page` minus the shadows. + * @return {ViewportRect} The bounds for page `page` minus the shadows. */ - getPageInsetDimensions: function(page) { + getPageInsetDimensions(page) { const pageDimensions = this.pageDimensions_[page]; const shadow = Viewport.PAGE_SHADOW; return { @@ -1063,7 +977,7 @@ Viewport.prototype = { width: pageDimensions.width - shadow.left - shadow.right, height: pageDimensions.height - shadow.top - shadow.bottom, }; - }, + } /** * Get the coordinates of the page contents (excluding the page shadow) @@ -1072,7 +986,7 @@ Viewport.prototype = { * @param {number} page the index of the page to get the rect for. * @return {Object} a rect representing the page in screen coordinates. */ - getPageScreenRect: function(page) { + getPageScreenRect(page) { if (!this.documentDimensions_) { return {x: 0, y: 0, width: 0, height: 0}; } @@ -1102,7 +1016,7 @@ Viewport.prototype = { width: insetDimensions.width * this.zoom, height: insetDimensions.height * this.zoom }; - }, + } /** * Check if the current fitting type is a paged mode. @@ -1112,18 +1026,18 @@ Viewport.prototype = { * * @return {boolean} Whether the current fitting type is a paged mode. */ - isPagedMode: function(page) { + isPagedMode() { return ( this.fittingType_ == FittingType.FIT_TO_PAGE || this.fittingType_ == FittingType.FIT_TO_HEIGHT); - }, + } /** * Scroll the viewport to the specified position. * - * @param {!PartialPoint} point The position to which to move the viewport. + * @param {!Point} point The position to which to move the viewport. */ - scrollTo: function(point) { + scrollTo(point) { let changed = false; const newPosition = this.position; if (point.x !== undefined && point.x != newPosition.x) { @@ -1138,17 +1052,17 @@ Viewport.prototype = { if (changed) { this.position = newPosition; } - }, + } /** * Scroll the viewport by the specified delta. * * @param {!Point} delta The delta by which to move the viewport. */ - scrollBy: function(delta) { + scrollBy(delta) { const newPosition = this.position; newPosition.x += delta.x; newPosition.y += delta.y; this.scrollTo(newPosition); } -}; +} diff --git a/chromium/chrome/browser/resources/pdf/viewport_interface.js b/chromium/chrome/browser/resources/pdf/viewport_interface.js new file mode 100644 index 00000000000..c9f99aceb2f --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/viewport_interface.js @@ -0,0 +1,136 @@ +// 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. + +/** + * @typedef {{ + * width: number, + * height: number, + * pageDimensions: Array<ViewportRect>, + * }} + */ +let DocumentDimensions; + +/** + * @typedef {{ + * x: number, + * y: number + * }} + */ +let Point; + +/** + * @typedef {{ + * width: number, + * height: number, + * }} + */ +let Size; + +/** + * @typedef {{ + * x: number, + * y: number, + * width: number, + * height: number, + * }} + */ +let ViewportRect; + +/** + * @interface + */ +class Viewport { + /** + * Returns the document dimensions. + * + * @return {!Size} A dictionary with the 'width'/'height' of the document. + */ + getDocumentDimensions() {} + + /** + * @return {!Point} the scroll position of the viewport. + */ + get position() {} + + /** + * @return {!Size} the size of the viewport excluding scrollbars. + */ + get size() {} + + /** + * @return {number} the zoom level of the viewport. + */ + get zoom() {} + + /** + * Sets the zoom to the given zoom level. + * + * @param {number} newZoom the zoom level to zoom to. + */ + setZoom(newZoom) {} + + /** + * Gets notified of the browser zoom changing separately from the + * internal zoom. + * + * @param {number} oldBrowserZoom the previous value of the browser zoom. + */ + updateZoomFromBrowserChange(oldBrowserZoom) {} + + /** + * @param {!Point} point + * @return {boolean} Whether |point| (in screen coordinates) is inside a page + */ + isPointInsidePage(point) {} +} + +/** + * Enumeration of pinch states. + * This should match PinchPhase enum in pdf/out_of_process_instance.h + * @enum {number} + */ +Viewport.PinchPhase = { + PINCH_NONE: 0, + PINCH_START: 1, + PINCH_UPDATE_ZOOM_OUT: 2, + PINCH_UPDATE_ZOOM_IN: 3, + PINCH_END: 4 +}; + +/** + * The increment to scroll a page by in pixels when up/down/left/right arrow + * 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. + */ +Viewport.SCROLL_INCREMENT = 40; + +/** + * Predefined zoom factors to be used when zooming in/out. These are in + * ascending order. This should match the lists in + * components/ui/zoom/page_zoom_constants.h and + * chrome/browser/resources/settings/appearance_page/appearance_page.js + */ +Viewport.ZOOM_FACTORS = [ + 0.25, 1 / 3, 0.5, 2 / 3, 0.75, 0.8, 0.9, 1, 1.1, 1.25, 1.5, 1.75, 2, 2.5, 3, + 4, 5 +]; + +/** + * The minimum and maximum range to be used to clip zoom factor. + */ +Viewport.ZOOM_FACTOR_RANGE = { + min: Viewport.ZOOM_FACTORS[0], + max: Viewport.ZOOM_FACTORS[Viewport.ZOOM_FACTORS.length - 1] +}; + +/** + * The width of the page shadow around pages in pixels. + */ +Viewport.PAGE_SHADOW = { + top: 3, + bottom: 7, + left: 5, + right: 5 +}; |