0

Add event handler for downloading attachments.

This CL adds event handlers for downloading attachments, and replace
|attachmentToken| with |messageId| in |SaveAttachmentDataMessageData|
since now the controller uses |messageId| to locate the resolver.

Bug: 177188
Change-Id: I9f88c916d5eff997a2b03c91bf646c8dfa6a66e7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2337408
Reviewed-by: Lei Zhang <thestig@chromium.org>
Reviewed-by: Rebekah Potter <rbpotter@chromium.org>
Commit-Queue: Hui Yingst <nigi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#807665}
This commit is contained in:
Hui Yingst
2020-09-16 21:19:46 +00:00
committed by Commit Bot
parent ba008c16a3
commit 2a6201ec59
4 changed files with 89 additions and 6 deletions

@ -13,6 +13,15 @@ import {PartialPoint, Viewport} from './viewport.js';
/** @typedef {{type: string, messageId: (string|undefined)}} */
export let MessageData;
/**
* @typedef {{
* type: string,
* dataToSave: Array,
* messageId: string,
* }}
*/
let SaveAttachmentDataMessageData;
/**
* @typedef {{
* dataToSave: Array,
@ -103,6 +112,14 @@ export class ContentController {
*/
save(requestType) {}
/**
* Requests that the attachment at a certain index be saved.
* @param {number} index The index of the attachment to be saved.
* @return {Promise<{type: string, dataToSave: Array, messageId: string}>}
* @abstract
*/
saveAttachment(index) {}
/**
* Loads PDF document from `data` activates UI.
* @param {string} fileName
@ -358,6 +375,14 @@ export class PluginController extends ContentController {
return resolver.promise;
}
/** @override */
saveAttachment(index) {
return this.postMessageWithReply_({
type: 'saveAttachment',
attachmentIndex: index,
});
}
/** @override */
async load(fileName, data) {
const url = URL.createObjectURL(new Blob([data]));

@ -95,6 +95,9 @@ export class InkController extends ContentController {
return this.inkHost_.saveDocument();
}
/** @override */
saveAttachment(index) {}
/** @override */
undo() {
this.inkHost_.undo();

@ -874,6 +874,62 @@ export class PDFViewerElement extends PDFViewerBaseElement {
this.canSerializeDocument_ = metadata.canSerializeDocument;
}
/**
* An event handler for when the browser tells the PDF Viewer to perform a
* save on the attachment at a certain index. Callers of this function must
* be responsible for checking whether the attachment size is valid for
* downloading.
* @param {!CustomEvent<number>} e The event which contains the index of
* attachment to be downloaded.
* @private
*/
async onSaveAttachment_(e) {
const index = e.detail;
const size = this.attachments_[index].size;
assert(size !== -1);
let dataArray = [];
// If the attachment size is 0, skip requesting the backend to fetch the
// attachment data.
if (size !== 0) {
const result = await this.currentController.saveAttachment(index);
// Cap the PDF attachment size at 100 MB. This cap should be kept in sync
// with and is also enforced in pdf/out_of_process_instance.cc.
const MAX_FILE_SIZE = 100 * 1000 * 1000;
const bufView = new Uint8Array(result.dataToSave);
assert(
bufView.length <= MAX_FILE_SIZE,
`File too large to be saved: ${bufView.length} bytes.`);
assert(
bufView.length === size,
`Received attachment size does not match its expected value: ${
size} bytes.`);
dataArray = [result.dataToSave];
}
const blob = new Blob(dataArray);
const fileName = this.attachments_[index].name;
chrome.fileSystem.chooseEntry(
{type: 'saveFile', suggestedName: fileName}, entry => {
if (chrome.runtime.lastError) {
if (chrome.runtime.lastError.message !== 'User cancelled') {
console.error(
'chrome.fileSystem.chooseEntry failed: ' +
chrome.runtime.lastError.message);
}
return;
}
entry.createWriter(writer => {
writer.write(blob);
// Unblock closing the window now that the user has saved
// successfully.
chrome.mimeHandlerPrivate.setShowBeforeUnloadDialog(false);
});
});
}
/**
* An event handler for when the browser tells the PDF Viewer to perform a
* save.
@ -1009,7 +1065,7 @@ export class PDFViewerElement extends PDFViewerBaseElement {
entry => {
if (chrome.runtime.lastError) {
if (chrome.runtime.lastError.message !== 'User cancelled') {
console.log(
console.error(
'chrome.fileSystem.chooseEntry failed: ' +
chrome.runtime.lastError.message);
}

@ -132,10 +132,9 @@ constexpr char kJSPrintType[] = "print";
// Save attachment (Page -> Plugin)
constexpr char kJSSaveAttachmentType[] = "saveAttachment";
constexpr char kJSAttachmentIndex[] = "attachmentIndex";
constexpr char kJSAttachmentToken[] = "attachmentToken";
// Save attachment data (Plugin -> Page)
constexpr char kJSSaveAttachmentDataType[] = "saveAttachmentData";
constexpr char kJSAttachmentDataToSave[] = "attachmentDataToSave";
constexpr char kJSAttachmentDataToSave[] = "dataToSave";
// Save (Page -> Plugin)
constexpr char kJSSaveType[] = "save";
constexpr char kJSToken[] = "token";
@ -1728,7 +1727,7 @@ void OutOfProcessInstance::HandleResetPrintPreviewModeMessage(
void OutOfProcessInstance::HandleSaveAttachmentMessage(
const pp::VarDictionary& dict) {
if (!dict.Get(pp::Var(kJSAttachmentToken)).is_string() ||
if (!dict.Get(pp::Var(kJSMessageId)).is_string() ||
!dict.Get(pp::Var(kJSAttachmentIndex)).is_int() ||
dict.Get(pp::Var(kJSAttachmentIndex)).AsInt() < 0) {
NOTREACHED();
@ -1739,14 +1738,14 @@ void OutOfProcessInstance::HandleSaveAttachmentMessage(
const std::vector<DocumentAttachmentInfo>& list =
engine()->GetDocumentAttachmentInfoList();
if (static_cast<size_t>(index) >= list.size() || !list[index].is_readable ||
list[index].size_bytes == 0) {
!IsSaveDataSizeValid(list[index].size_bytes)) {
NOTREACHED();
return;
}
pp::VarDictionary message;
message.Set(kType, kJSSaveAttachmentDataType);
message.Set(kJSAttachmentToken, dict.Get(pp::Var(kJSAttachmentToken)));
message.Set(kJSMessageId, dict.Get(pp::Var(kJSMessageId)));
// This will be overwritten if the save is successful.
message.Set(kJSAttachmentDataToSave, pp::Var(pp::Var::Null()));