зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1659753 - Support saving PDF form data when using "Save Page As". r=Gijs
Now when Ctrl/Cmd+S or "Save Page As" is used, Firefox will send PDF.js a message to trigger downloading. This allows PDF.js to generate a new PDF if there is modified form data that needs to be saved or send back the unmodified data. Once PDF.js has generated the blob, it will send messages to the PdfjsParent to open the "Save As" dialog. Adds two tests: 1) Saving a plain PDF without forms. 2) Saving a PDF with modified forms and verifies the new PDF has the form data. Differential Revision: https://phabricator.services.mozilla.com/D87675
This commit is contained in:
Родитель
99af4ed728
Коммит
686ff6882f
|
@ -306,6 +306,21 @@ class ChromeActions {
|
|||
filename = "document.pdf";
|
||||
}
|
||||
var blobUri = NetUtil.newURI(blobUrl);
|
||||
|
||||
// If the download was triggered from the ctrl/cmd+s or "Save Page As"
|
||||
// launch the "Save As" dialog.
|
||||
if (data.sourceEventType == "save") {
|
||||
let actor = getActor(this.domWindow);
|
||||
actor.sendAsyncMessage("PDFJS:Parent:saveURL", {
|
||||
blobUrl,
|
||||
filename,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// The download is from the fallback bar or the download button, so trigger
|
||||
// the open dialog to make it easier for users to save in the downloads
|
||||
// folder or launch a different PDF viewer.
|
||||
var extHelperAppSvc = Cc[
|
||||
"@mozilla.org/uriloader/external-helper-app-service;1"
|
||||
].getService(Ci.nsIExternalHelperAppService);
|
||||
|
|
|
@ -41,7 +41,8 @@ class PdfjsChild extends JSWindowActorChild {
|
|||
|
||||
case "PDFJS:ZoomIn":
|
||||
case "PDFJS:ZoomOut":
|
||||
case "PDFJS:ZoomReset": {
|
||||
case "PDFJS:ZoomReset":
|
||||
case "PDFJS:Save": {
|
||||
const type = msg.name.split("PDFJS:")[1].toLowerCase();
|
||||
this.dispatchEvent(type, null);
|
||||
break;
|
||||
|
|
|
@ -21,12 +21,20 @@ const { XPCOMUtils } = ChromeUtils.import(
|
|||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"SetClipboardSearchString",
|
||||
"resource://gre/modules/Finder.jsm"
|
||||
);
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"PrivateBrowsingUtils",
|
||||
"resource://gre/modules/PrivateBrowsingUtils.jsm"
|
||||
);
|
||||
|
||||
var Svc = {};
|
||||
XPCOMUtils.defineLazyServiceGetter(
|
||||
Svc,
|
||||
|
@ -74,6 +82,8 @@ class PdfjsParent extends JSWindowActorParent {
|
|||
return this._updateMatchesCount(aMsg);
|
||||
case "PDFJS:Parent:addEventListener":
|
||||
return this._addEventListener();
|
||||
case "PDFJS:Parent:saveURL":
|
||||
return this._saveURL(aMsg);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
@ -86,6 +96,23 @@ class PdfjsParent extends JSWindowActorParent {
|
|||
return this.browsingContext.top.embedderElement;
|
||||
}
|
||||
|
||||
_saveURL(aMsg) {
|
||||
const data = aMsg.data;
|
||||
this.browser.ownerGlobal.saveURL(
|
||||
data.blobUrl /* aURL */,
|
||||
data.filename /* aFileName */,
|
||||
null /* aFilePickerTitleKey */,
|
||||
true /* aShouldBypassCache */,
|
||||
false /* aSkipPrompt */,
|
||||
null /* aReferrerInfo */,
|
||||
null /* aSourceDocument */,
|
||||
PrivateBrowsingUtils.isBrowserPrivate(
|
||||
this.browser
|
||||
) /* aIsContentWindowPrivate */,
|
||||
Services.scriptSecurityManager.getSystemPrincipal() /* aPrincipal */
|
||||
);
|
||||
}
|
||||
|
||||
_updateControlState(aMsg) {
|
||||
let data = aMsg.data;
|
||||
let browser = this.browser;
|
||||
|
|
|
@ -15,6 +15,9 @@ support-files =
|
|||
file_pdfjs_object_stream.pdf^headers^
|
||||
[browser_pdfjs_savedialog.js]
|
||||
skip-if = verify
|
||||
[browser_pdfjs_saveas.js]
|
||||
support-files =
|
||||
!/toolkit/content/tests/browser/common/mockTransfer.js
|
||||
[browser_pdfjs_views.js]
|
||||
[browser_pdfjs_zoom.js]
|
||||
skip-if = (verify && debug && (os == 'win'))
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const RELATIVE_DIR = "toolkit/components/pdfjs/test/";
|
||||
const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR;
|
||||
|
||||
var MockFilePicker = SpecialPowers.MockFilePicker;
|
||||
MockFilePicker.init(window);
|
||||
|
||||
/* import-globals-from ../../../content/tests/browser/common/mockTransfer.js */
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
|
||||
this
|
||||
);
|
||||
|
||||
function createTemporarySaveDirectory() {
|
||||
var saveDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
|
||||
saveDir.append("testsavedir");
|
||||
if (!saveDir.exists()) {
|
||||
saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
|
||||
}
|
||||
return saveDir;
|
||||
}
|
||||
|
||||
function createPromiseForTransferComplete(expectedFileName, destFile) {
|
||||
return new Promise(resolve => {
|
||||
MockFilePicker.showCallback = fp => {
|
||||
info("Filepicker shown, checking filename");
|
||||
is(fp.defaultString, expectedFileName, "Filename should be correct.");
|
||||
let fileName = fp.defaultString;
|
||||
destFile.append(fileName);
|
||||
|
||||
MockFilePicker.setFiles([destFile]);
|
||||
MockFilePicker.filterIndex = 0; // kSaveAsType_Complete
|
||||
|
||||
MockFilePicker.showCallback = null;
|
||||
mockTransferCallback = function(downloadSuccess) {
|
||||
ok(downloadSuccess, "File should have been downloaded successfully");
|
||||
mockTransferCallback = () => {};
|
||||
resolve();
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
let tempDir = createTemporarySaveDirectory();
|
||||
|
||||
add_task(async function setup() {
|
||||
mockTransferRegisterer.register();
|
||||
registerCleanupFunction(function() {
|
||||
mockTransferRegisterer.unregister();
|
||||
MockFilePicker.cleanup();
|
||||
tempDir.remove(true);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Check triggering "Save Page As" on a non-forms PDF opens the "Save As" dialog
|
||||
* and successfully saves the file.
|
||||
*/
|
||||
add_task(async function test_pdf_saveas() {
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{ gBrowser, url: "about:blank" },
|
||||
async function(browser) {
|
||||
await waitForPdfJS(browser, TESTROOT + "file_pdfjs_test.pdf");
|
||||
let destFile = tempDir.clone();
|
||||
MockFilePicker.displayDirectory = tempDir;
|
||||
let fileSavedPromise = createPromiseForTransferComplete(
|
||||
"file_pdfjs_test.pdf",
|
||||
destFile
|
||||
);
|
||||
saveBrowser(browser);
|
||||
await fileSavedPromise;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Check triggering "Save Page As" on a PDF with forms that has been modified
|
||||
* does the following:
|
||||
* 1) opens the "Save As" dialog
|
||||
* 2) successfully saves the file
|
||||
* 3) the new file contains the new form data
|
||||
*/
|
||||
add_task(async function test_pdf_saveas_forms() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["pdfjs.renderInteractiveForms", true]],
|
||||
});
|
||||
let destFile = tempDir.clone();
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{ gBrowser, url: "about:blank" },
|
||||
async function(browser) {
|
||||
await waitForPdfJSAnnotationLayer(
|
||||
browser,
|
||||
TESTROOT + "file_pdfjs_form.pdf"
|
||||
);
|
||||
// Fill in the form input field.
|
||||
await SpecialPowers.spawn(browser, [], async function() {
|
||||
let formInput = content.document.querySelector(
|
||||
"#viewerContainer input"
|
||||
);
|
||||
ok(formInput, "PDF contains text field.");
|
||||
is(formInput.value, "", "Text field is empty to start.");
|
||||
formInput.value = "test";
|
||||
formInput.dispatchEvent(new content.window.Event("input"));
|
||||
});
|
||||
|
||||
MockFilePicker.displayDirectory = tempDir;
|
||||
let fileSavedPromise = createPromiseForTransferComplete(
|
||||
"file_pdfjs_form.pdf",
|
||||
destFile
|
||||
);
|
||||
saveBrowser(browser);
|
||||
await fileSavedPromise;
|
||||
}
|
||||
);
|
||||
|
||||
// Now that the file has been modified and saved, load it to verify the form
|
||||
// data persisted.
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{ gBrowser, url: "about:blank" },
|
||||
async function(browser) {
|
||||
await waitForPdfJSAnnotationLayer(browser, NetUtil.newURI(destFile).spec);
|
||||
await SpecialPowers.spawn(browser, [], async function() {
|
||||
let formInput = content.document.querySelector(
|
||||
"#viewerContainer input"
|
||||
);
|
||||
ok(formInput, "PDF contains text field.");
|
||||
is(formInput.value, "test", "Text field is filled in.");
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
|
@ -89,32 +89,10 @@ function saveBrowser(aBrowser, aSkipPrompt, aBrowsingContext = null) {
|
|||
throw new Error("Must have a browser when calling saveBrowser");
|
||||
}
|
||||
let persistable = aBrowser.frameLoader;
|
||||
// Because of how pdf.js deals with principals, saving the document the "normal"
|
||||
// way won't work. Work around this by saving the pdf's URL directly:
|
||||
if (
|
||||
aBrowser.contentPrincipal.spec == "resource://pdf.js/web/viewer.html" &&
|
||||
aBrowser.currentURI.schemeIs("file")
|
||||
) {
|
||||
let correctPrincipal = Services.scriptSecurityManager.createContentPrincipal(
|
||||
aBrowser.currentURI,
|
||||
aBrowser.contentPrincipal.originAttributes
|
||||
);
|
||||
internalSave(
|
||||
aBrowser.currentURI.spec,
|
||||
null /* no document */,
|
||||
null /* automatically determine filename */,
|
||||
null /* no content disposition */,
|
||||
"application/pdf",
|
||||
false /* don't bypass cache */,
|
||||
null /* no alternative title */,
|
||||
null /* no auto-chosen file info */,
|
||||
null /* null referrer will be OK for file: */,
|
||||
null /* no document */,
|
||||
aSkipPrompt /* caller decides about prompting */,
|
||||
null /* no cache key because the one for the document will be for pdfjs */,
|
||||
PrivateBrowsingUtils.isWindowPrivate(aBrowser.ownerGlobal),
|
||||
correctPrincipal
|
||||
);
|
||||
// PDF.js has its own way to handle saving PDFs since it may need to
|
||||
// generate a new PDF to save modified form data.
|
||||
if (aBrowser.contentPrincipal.spec == "resource://pdf.js/web/viewer.html") {
|
||||
aBrowser.sendMessageToActor("PDFJS:Save", {}, "Pdfjs");
|
||||
return;
|
||||
}
|
||||
let stack = Components.stack.caller;
|
||||
|
|
Загрузка…
Ссылка в новой задаче