PDF Viewer: Add UI to download edited PDF
- Add action menu providing options to download the original or edited PDF - Menu only is shown if there are edits and the "SaveEditedPDFForm" feature is enabled. Bug: 1078543 Change-Id: I561175c7608b747c15acd03123aca66761c8157c Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2213240 Reviewed-by: Lei Zhang <thestig@chromium.org> Reviewed-by: dpapad <dpapad@chromium.org> Commit-Queue: Rebekah Potter <rbpotter@chromium.org> Cr-Commit-Position: refs/heads/master@{#777492}
This commit is contained in:
chrome
browser
pdf
resources
test
data
components
pdf
ui/webui/resources/cr_elements/cr_action_menu
@ -51,6 +51,8 @@ void AddStrings(base::Value* dict) {
|
||||
{"errorDialogTitle", IDS_PDF_ERROR_DIALOG_TITLE},
|
||||
{"pageReload", IDS_PDF_PAGE_RELOAD_BUTTON},
|
||||
{"bookmarks", IDS_PDF_BOOKMARKS},
|
||||
{"downloadEdited", IDS_PDF_DOWNLOAD_EDITED},
|
||||
{"downloadOriginal", IDS_PDF_DOWNLOAD_ORIGINAL},
|
||||
{"labelPageNumber", IDS_PDF_LABEL_PAGE_NUMBER},
|
||||
{"tooltipRotateCW", IDS_PDF_TOOLTIP_ROTATE_CW},
|
||||
{"tooltipDownload", IDS_PDF_TOOLTIP_DOWNLOAD},
|
||||
|
@ -86,6 +86,7 @@ js_library("toolbar_manager") {
|
||||
js_library("controller") {
|
||||
deps = [
|
||||
":annotation_tool",
|
||||
":constants",
|
||||
":viewport",
|
||||
"elements:viewer-pdf-toolbar",
|
||||
"//ui/webui/resources/js:assert.m",
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -8,6 +8,7 @@ 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 }} */
|
||||
@ -105,12 +106,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.
|
||||
@ -186,7 +188,7 @@ export class InkController extends ContentController {
|
||||
}
|
||||
|
||||
/** @override */
|
||||
save(requireResult) {
|
||||
save(requestType) {
|
||||
return this.inkHost_.saveDocument();
|
||||
}
|
||||
|
||||
@ -403,11 +405,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;
|
||||
}
|
||||
|
||||
@ -416,6 +422,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_();
|
||||
|
@ -79,6 +79,8 @@ 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",
|
||||
]
|
||||
|
@ -190,8 +190,16 @@
|
||||
title="$i18n{tooltipRotateCW}"></cr-icon-button>
|
||||
|
||||
<cr-icon-button id="download" iron-icon="cr:file-download"
|
||||
on-click="download" aria-label$="$i18n{tooltipDownload}"
|
||||
on-click="onDownloadClick_" aria-label$="$i18n{tooltipDownload}"
|
||||
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="$i18n{tooltipPrint}"
|
||||
|
@ -10,15 +10,17 @@ 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 {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',
|
||||
@ -118,6 +120,18 @@ Polymer({
|
||||
/** @type {?Object} */
|
||||
animation_: null,
|
||||
|
||||
/** @private {boolean} */
|
||||
hasEdits_: false,
|
||||
|
||||
/** @private {boolean} */
|
||||
hasAnnotations_: false,
|
||||
|
||||
/**
|
||||
* Whether the PDF Form save feature is enabled.
|
||||
* @private {boolean}
|
||||
*/
|
||||
pdfFormSaveEnabled_: false,
|
||||
|
||||
/**
|
||||
* @param {number} newProgress
|
||||
* @param {number} oldProgress
|
||||
@ -174,6 +188,10 @@ Polymer({
|
||||
}
|
||||
},
|
||||
|
||||
setIsEditing() {
|
||||
this.hasEdits_ = true;
|
||||
},
|
||||
|
||||
selectPageNumber() {
|
||||
this.$.pageselector.select();
|
||||
},
|
||||
@ -181,7 +199,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. */
|
||||
@ -213,8 +232,33 @@ Polymer({
|
||||
this.fire('rotate-right');
|
||||
},
|
||||
|
||||
download() {
|
||||
this.fire('save');
|
||||
/** @private */
|
||||
onDownloadClick_() {
|
||||
if (!this.hasAnnotations_ &&
|
||||
(!this.hasEdits_ || !this.pdfFormSaveEnabled_)) {
|
||||
this.fire('save', SaveRequestType.ORIGINAL);
|
||||
return;
|
||||
}
|
||||
this.$.downloadMenu.showAt(this.$.download, {
|
||||
anchorAlignmentX: AnchorAlignment.CENTER,
|
||||
anchorAlignmentY: AnchorAlignment.AFTER_END,
|
||||
noOffset: true,
|
||||
});
|
||||
},
|
||||
|
||||
/** @private */
|
||||
onDownloadOriginalClick_() {
|
||||
this.fire('save', SaveRequestType.ORIGINAL);
|
||||
this.$.downloadMenu.close();
|
||||
},
|
||||
|
||||
/** @private */
|
||||
onDownloadEditedClick_() {
|
||||
this.fire(
|
||||
'save',
|
||||
this.hasAnnotations_ ? SaveRequestType.ANNOTATION :
|
||||
SaveRequestType.EDITED);
|
||||
this.$.downloadMenu.close();
|
||||
},
|
||||
|
||||
print() {
|
||||
@ -232,6 +276,7 @@ Polymer({
|
||||
toggleAnnotation() {
|
||||
this.annotationMode = !this.annotationMode;
|
||||
if (this.annotationMode) {
|
||||
this.hasAnnotations_ = true;
|
||||
// Select pen tool when entering annotation mode.
|
||||
this.updateAnnotationTool_(/** @type {!HTMLElement} */ (this.$.pen));
|
||||
}
|
||||
@ -300,6 +345,7 @@ Polymer({
|
||||
|
||||
this.pdfAnnotationsEnabled_ =
|
||||
loadTimeData.getBoolean('pdfAnnotationsEnabled');
|
||||
this.pdfFormSaveEnabled_ = loadTimeData.getBoolean('pdfFormSaveEnabled');
|
||||
this.printingEnabled_ = loadTimeData.getBoolean('printingEnabled');
|
||||
},
|
||||
});
|
||||
|
@ -10,7 +10,7 @@ import {$, hasKeyModifiers, isRTL} from 'chrome://resources/js/util.m.js';
|
||||
|
||||
import {Bookmark} from './bookmark_type.js';
|
||||
import {BrowserApi} from './browser_api.js';
|
||||
import {FittingType, TwoUpViewAction} from './constants.js';
|
||||
import {FittingType, SaveRequestType, TwoUpViewAction} from './constants.js';
|
||||
import {ContentController, InkController, MessageData, PluginController, PrintPreviewParams} from './controller.js';
|
||||
import {FitToChangedEvent} from './elements/viewer-zoom-toolbar.js';
|
||||
import {PDFMetrics} from './metrics.js';
|
||||
@ -95,6 +95,12 @@ export class PDFViewer {
|
||||
/** @private {boolean} */
|
||||
this.hasEnteredAnnotationMode_ = false;
|
||||
|
||||
/** @private {boolean} */
|
||||
this.annotationMode_ = false;
|
||||
|
||||
/** @private {boolean} */
|
||||
this.hasEdits_ = false;
|
||||
|
||||
/** @private {boolean} */
|
||||
this.hadPassword_ = false;
|
||||
|
||||
@ -231,7 +237,7 @@ export class PDFViewer {
|
||||
if (toolbarEnabled) {
|
||||
this.toolbar_ = /** @type {!ViewerPdfToolbarElement} */ ($('toolbar'));
|
||||
this.toolbar_.hidden = false;
|
||||
this.toolbar_.addEventListener('save', () => this.save_());
|
||||
this.toolbar_.addEventListener('save', e => this.save_(e.detail));
|
||||
this.toolbar_.addEventListener('print', () => this.print_());
|
||||
this.toolbar_.addEventListener(
|
||||
'undo', () => this.currentController_.undo());
|
||||
@ -490,6 +496,7 @@ export class PDFViewer {
|
||||
*/
|
||||
async annotationModeToggled_(e) {
|
||||
const annotationMode = e.detail.value;
|
||||
this.annotationMode_ = annotationMode;
|
||||
this.zoomToolbar_.annotationMode = annotationMode;
|
||||
if (annotationMode) {
|
||||
// Enter annotation mode.
|
||||
@ -497,8 +504,9 @@ export class PDFViewer {
|
||||
// TODO(dstockwell): set plugin read-only, begin transition
|
||||
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'));
|
||||
@ -508,6 +516,7 @@ export class PDFViewer {
|
||||
// The user aborted entering annotation mode. Revert to the plugin.
|
||||
this.toolbar_.annotationMode = false;
|
||||
this.zoomToolbar_.annotationMode = false;
|
||||
this.annotationMode_ = false;
|
||||
this.updateProgress_(100);
|
||||
return;
|
||||
}
|
||||
@ -535,8 +544,9 @@ export class PDFViewer {
|
||||
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);
|
||||
// Ensure the plugin gets the initial viewport.
|
||||
@ -1061,6 +1071,11 @@ export class PDFViewer {
|
||||
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;
|
||||
this.toolbar_.setIsEditing();
|
||||
return;
|
||||
case 'setIsSelecting':
|
||||
this.setIsSelecting_(
|
||||
/** @type {{ isSelecting: boolean }} */ (data).isSelecting);
|
||||
@ -1234,24 +1249,46 @@ export class PDFViewer {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
@ -2,7 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import {FittingType} from 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/constants.js';
|
||||
import {FittingType, SaveRequestType} from 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/constants.js';
|
||||
import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
|
||||
import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
|
||||
|
||||
import {createBookmarksForTest} from './test_util.js';
|
||||
@ -304,7 +305,80 @@ const tests = [
|
||||
chrome.test.assertTrue(button.ironIcon.endsWith(fitPageIcon));
|
||||
|
||||
chrome.test.succeed();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Test that the toolbar shows an option to download the edited PDF if
|
||||
* available.
|
||||
*/
|
||||
function testEditedPdfOption() {
|
||||
const pdfToolbar = document.createElement('viewer-pdf-toolbar');
|
||||
document.body.appendChild(pdfToolbar);
|
||||
const downloadButton = pdfToolbar.$.download;
|
||||
const actionMenu = pdfToolbar.$.downloadMenu;
|
||||
chrome.test.assertFalse(actionMenu.open);
|
||||
loadTimeData.overrideValues({pdfFormSaveEnabled: false});
|
||||
pdfToolbar.strings = Object.assign({}, pdfToolbar.strings);
|
||||
|
||||
let lastRequestType;
|
||||
let numRequests = 0;
|
||||
pdfToolbar.addEventListener('save', e => {
|
||||
lastRequestType = e.detail;
|
||||
numRequests++;
|
||||
});
|
||||
|
||||
// No edits, and feature is off.
|
||||
downloadButton.click();
|
||||
chrome.test.assertFalse(actionMenu.open);
|
||||
chrome.test.assertEq(SaveRequestType.ORIGINAL, lastRequestType);
|
||||
chrome.test.assertEq(1, numRequests);
|
||||
|
||||
// Reset.
|
||||
lastRequestType = SaveRequestType.EDITED;
|
||||
|
||||
// Still does not show the menu if there are no edits.
|
||||
loadTimeData.overrideValues({pdfFormSaveEnabled: true});
|
||||
pdfToolbar.strings = Object.assign({}, pdfToolbar.strings);
|
||||
downloadButton.click();
|
||||
chrome.test.assertFalse(actionMenu.open);
|
||||
chrome.test.assertEq(SaveRequestType.ORIGINAL, lastRequestType);
|
||||
chrome.test.assertEq(2, numRequests);
|
||||
|
||||
// Set editing mode. Now, clicking download opens the menu.
|
||||
pdfToolbar.setIsEditing();
|
||||
downloadButton.click();
|
||||
chrome.test.assertTrue(actionMenu.open);
|
||||
chrome.test.assertEq(2, numRequests);
|
||||
|
||||
// Click on "Edited".
|
||||
const buttons = pdfToolbar.shadowRoot.querySelectorAll('button');
|
||||
buttons[0].click();
|
||||
chrome.test.assertEq(SaveRequestType.EDITED, lastRequestType);
|
||||
chrome.test.assertFalse(actionMenu.open);
|
||||
chrome.test.assertEq(3, numRequests);
|
||||
|
||||
// Click again.
|
||||
downloadButton.click();
|
||||
chrome.test.assertTrue(actionMenu.open);
|
||||
chrome.test.assertEq(3, numRequests);
|
||||
|
||||
// Click on "Original".
|
||||
buttons[1].click();
|
||||
chrome.test.assertEq(SaveRequestType.ORIGINAL, lastRequestType);
|
||||
chrome.test.assertFalse(actionMenu.open);
|
||||
chrome.test.assertEq(4, numRequests);
|
||||
|
||||
// Even if the document has been edited, always download the original if the
|
||||
// feature flag is off.
|
||||
loadTimeData.overrideValues({pdfFormSaveEnabled: false});
|
||||
pdfToolbar.strings = Object.assign({}, pdfToolbar.strings);
|
||||
downloadButton.click();
|
||||
chrome.test.assertFalse(actionMenu.open);
|
||||
chrome.test.assertEq(SaveRequestType.ORIGINAL, lastRequestType);
|
||||
chrome.test.assertEq(5, numRequests);
|
||||
|
||||
chrome.test.succeed();
|
||||
},
|
||||
];
|
||||
|
||||
chrome.test.runTests(tests);
|
||||
|
@ -1,6 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<grit-part>
|
||||
<if expr="enable_plugins">
|
||||
<message name="IDS_PDF_DOWNLOAD_ORIGINAL" desc="The label for the menu option to download the original, unedited PDF document.">
|
||||
Original document
|
||||
</message>
|
||||
<message name="IDS_PDF_DOWNLOAD_EDITED" desc="The label for the menu option to download the edited PDF document.">
|
||||
Edited document
|
||||
</message>
|
||||
<message name="IDS_PDF_NEED_PASSWORD" desc="A message asking the user for a password to open a PDF file.">
|
||||
This document is password protected. Please enter a password.
|
||||
</message>
|
||||
|
@ -0,0 +1 @@
|
||||
275fe039a311149fd0a2bafc5145669d2c58dd50
|
@ -0,0 +1 @@
|
||||
dfa4cded7d1e26aa6ab6e624c04df96572a69460
|
@ -109,7 +109,7 @@ constexpr char kJSPrintType[] = "print";
|
||||
// Save (Page -> Plugin)
|
||||
constexpr char kJSSaveType[] = "save";
|
||||
constexpr char kJSToken[] = "token";
|
||||
constexpr char kJSForce[] = "force";
|
||||
constexpr char kJSSaveRequestType[] = "saveRequestType";
|
||||
// Save Data (Plugin -> Page)
|
||||
constexpr char kJSSaveDataType[] = "saveData";
|
||||
constexpr char kJSFileName[] = "fileName";
|
||||
@ -182,6 +182,9 @@ constexpr char kJSNamedDestinationPageNumber[] = "pageNumber";
|
||||
constexpr char kJSSetIsSelectingType[] = "setIsSelecting";
|
||||
constexpr char kJSIsSelecting[] = "isSelecting";
|
||||
|
||||
// Editing forms in document (Plugin -> Page)
|
||||
constexpr char kJSSetIsEditingType[] = "setIsEditing";
|
||||
|
||||
// Notify when a form field is focused (Plugin -> Page)
|
||||
constexpr char kJSFieldFocusType[] = "formFocusChange";
|
||||
constexpr char kJSFieldFocus[] = "focused";
|
||||
@ -487,6 +490,7 @@ bool OutOfProcessInstance::Init(uint32_t argc,
|
||||
text_input_ = std::make_unique<pp::TextInput_Dev>(this);
|
||||
|
||||
bool enable_javascript = true;
|
||||
bool has_edits = false;
|
||||
const char* stream_url = nullptr;
|
||||
const char* original_url = nullptr;
|
||||
const char* top_level_url = nullptr;
|
||||
@ -509,6 +513,8 @@ bool OutOfProcessInstance::Init(uint32_t argc,
|
||||
} else if (strcmp(argn[i], "javascript") == 0) {
|
||||
if (base::FeatureList::IsEnabled(features::kPdfHonorJsContentSettings))
|
||||
enable_javascript = (strcmp(argv[i], "allow") == 0);
|
||||
} else if (strcmp(argn[i], "has-edits") == 0) {
|
||||
has_edits = true;
|
||||
}
|
||||
if (!success)
|
||||
return false;
|
||||
@ -531,6 +537,7 @@ bool OutOfProcessInstance::Init(uint32_t argc,
|
||||
|
||||
LoadUrl(stream_url, /*is_print_preview=*/false);
|
||||
url_ = original_url;
|
||||
edit_mode_ = has_edits;
|
||||
pp::PDF::SetCrashData(GetPluginInstance(), original_url, top_level_url);
|
||||
return engine_->New(original_url, headers);
|
||||
}
|
||||
@ -680,19 +687,27 @@ void OutOfProcessInstance::HandleMessage(const pp::Var& message) {
|
||||
Print();
|
||||
} else if (type == kJSSaveType) {
|
||||
if (!(dict.Get(pp::Var(kJSToken)).is_string() &&
|
||||
dict.Get(pp::Var(kJSForce)).is_bool())) {
|
||||
dict.Get(pp::Var(kJSSaveRequestType)).is_int())) {
|
||||
NOTREACHED();
|
||||
return;
|
||||
}
|
||||
const bool force = dict.Get(pp::Var(kJSForce)).AsBool();
|
||||
if (force) {
|
||||
// |force| being true means the user has entered annotation mode. In which
|
||||
// case, assume the user will make edits and prefer saving using the
|
||||
// plugin data.
|
||||
pp::PDF::SetPluginCanSave(this, true);
|
||||
SaveToBuffer(dict.Get(pp::Var(kJSToken)).AsString());
|
||||
} else {
|
||||
SaveToFile(dict.Get(pp::Var(kJSToken)).AsString());
|
||||
const SaveRequestType request_type = static_cast<SaveRequestType>(
|
||||
dict.Get(pp::Var(kJSSaveRequestType)).AsInt());
|
||||
switch (request_type) {
|
||||
case SaveRequestType::kAnnotation:
|
||||
// In annotation mode, assume the user will make edits and prefer saving
|
||||
// using the plugin data.
|
||||
pp::PDF::SetPluginCanSave(this, true);
|
||||
SaveToBuffer(dict.Get(pp::Var(kJSToken)).AsString());
|
||||
break;
|
||||
case SaveRequestType::kOriginal:
|
||||
pp::PDF::SetPluginCanSave(this, false);
|
||||
SaveToFile(dict.Get(pp::Var(kJSToken)).AsString());
|
||||
pp::PDF::SetPluginCanSave(this, CanSaveEdits());
|
||||
break;
|
||||
case SaveRequestType::kEdited:
|
||||
SaveToBuffer(dict.Get(pp::Var(kJSToken)).AsString());
|
||||
break;
|
||||
}
|
||||
} else if (type == kJSRotateClockwiseType) {
|
||||
RotateClockwise();
|
||||
@ -1470,7 +1485,7 @@ void OutOfProcessInstance::GetDocumentPassword(
|
||||
PostMessage(message);
|
||||
}
|
||||
|
||||
bool OutOfProcessInstance::ShouldSaveEdits() const {
|
||||
bool OutOfProcessInstance::CanSaveEdits() const {
|
||||
return edit_mode_ &&
|
||||
base::FeatureList::IsEnabled(features::kSaveEditedPDFForm);
|
||||
}
|
||||
@ -1488,7 +1503,7 @@ void OutOfProcessInstance::SaveToBuffer(const std::string& token) {
|
||||
edit_mode_ && !base::FeatureList::IsEnabled(features::kSaveEditedPDFForm);
|
||||
message.Set(kJSHasUnsavedChanges, pp::Var(has_unsaved_changes));
|
||||
|
||||
if (ShouldSaveEdits()) {
|
||||
if (CanSaveEdits()) {
|
||||
std::vector<uint8_t> data = engine_->GetSaveData();
|
||||
if (IsSaveDataSizeValid(data.size())) {
|
||||
pp::VarArrayBuffer buffer(data.size());
|
||||
@ -1514,14 +1529,9 @@ void OutOfProcessInstance::SaveToBuffer(const std::string& token) {
|
||||
}
|
||||
|
||||
void OutOfProcessInstance::SaveToFile(const std::string& token) {
|
||||
if (!ShouldSaveEdits()) {
|
||||
engine_->KillFormFocus();
|
||||
ConsumeSaveToken(token);
|
||||
pp::PDF::SaveAs(this);
|
||||
return;
|
||||
}
|
||||
|
||||
SaveToBuffer(token);
|
||||
engine_->KillFormFocus();
|
||||
ConsumeSaveToken(token);
|
||||
pp::PDF::SaveAs(this);
|
||||
}
|
||||
|
||||
void OutOfProcessInstance::ConsumeSaveToken(const std::string& token) {
|
||||
@ -1934,7 +1944,12 @@ void OutOfProcessInstance::IsSelectingChanged(bool is_selecting) {
|
||||
|
||||
void OutOfProcessInstance::IsEditModeChanged(bool is_edit_mode) {
|
||||
edit_mode_ = is_edit_mode;
|
||||
pp::PDF::SetPluginCanSave(this, ShouldSaveEdits());
|
||||
pp::PDF::SetPluginCanSave(this, CanSaveEdits());
|
||||
if (CanSaveEdits()) {
|
||||
pp::VarDictionary message;
|
||||
message.Set(kType, kJSSetIsEditingType);
|
||||
PostMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
float OutOfProcessInstance::GetToolbarHeightInScreenCoords() {
|
||||
|
@ -186,7 +186,7 @@ class OutOfProcessInstance : public pp::Instance,
|
||||
// frame's origin.
|
||||
pp::URLLoader CreateURLLoaderInternal();
|
||||
|
||||
bool ShouldSaveEdits() const;
|
||||
bool CanSaveEdits() const;
|
||||
void SaveToFile(const std::string& token);
|
||||
void SaveToBuffer(const std::string& token);
|
||||
void ConsumeSaveToken(const std::string& token);
|
||||
@ -212,6 +212,13 @@ class OutOfProcessInstance : public pp::Instance,
|
||||
LOAD_STATE_FAILED,
|
||||
};
|
||||
|
||||
// Must match SaveRequestType in chrome/browser/resources/pdf/constants.js.
|
||||
enum class SaveRequestType {
|
||||
kAnnotation = 0,
|
||||
kOriginal = 1,
|
||||
kEdited = 2,
|
||||
};
|
||||
|
||||
// Set new zoom scale.
|
||||
void SetZoom(double scale);
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
* minY: (number|undefined),
|
||||
* maxX: (number|undefined),
|
||||
* maxY: (number|undefined),
|
||||
* noOffset: (boolean|undefined),
|
||||
* }}
|
||||
*/
|
||||
let ShowAtConfig;
|
||||
@ -344,7 +345,7 @@ Polymer({
|
||||
const rect = this.anchorElement_.getBoundingClientRect();
|
||||
|
||||
let height = rect.height;
|
||||
if (opt_config &&
|
||||
if (opt_config && !opt_config.noOffset &&
|
||||
opt_config.anchorAlignmentY === AnchorAlignment.AFTER_END) {
|
||||
// When an action menu is positioned after the end of an element, the
|
||||
// action menu can appear too far away from the anchor element, typically
|
||||
|
Reference in New Issue
Block a user