[JS] Some functions (print, alert,...) must be called only after a user activation

- Some events, which require a user interaction, will allow those functions to be called.
But after few seconds, if there are no more user interaction, it won't be possible
anymore.
The idea is to give an opportunity to the user to leave the pdf.
- Disable print function when we're printing, the same with saving and disallow to save
on open events.
This commit is contained in:
Calixte Denizet 2022-10-24 18:56:52 +02:00
parent 27b251ac99
commit 0de804a256
4 changed files with 103 additions and 5 deletions

View File

@ -81,6 +81,13 @@ class SandboxSupportBase {
) {
return;
}
if (callbackId === 0) {
// This callbackId corresponds to the one used for userActivation.
// So here, we cancel the last userActivation.
this.win.clearTimeout(this.timeoutIds.get(callbackId));
}
const id = this.win.setTimeout(() => {
this.timeoutIds.delete(callbackId);
this.callSandboxFunction("timeoutCb", {

View File

@ -23,6 +23,7 @@ const VIEWER_TYPE = "PDF.js";
const VIEWER_VARIATION = "Full";
const VIEWER_VERSION = 21.00720099;
const FORMS_VERSION = 21.00720099;
const USERACTIVATION_CALLBACKID = 0;
class App extends PDFObject {
constructor(data) {
@ -45,7 +46,8 @@ class App extends PDFObject {
this._eventDispatcher = new EventDispatcher(
this._document,
data.calculationOrder,
this._objects
this._objects,
data.externalCall
);
this._timeoutIds = new WeakMap();
@ -63,7 +65,7 @@ class App extends PDFObject {
}
this._timeoutCallbackIds = new Map();
this._timeoutCallbackId = 0;
this._timeoutCallbackId = USERACTIVATION_CALLBACKID + 1;
this._globalEval = data.globalEval;
this._externalCall = data.externalCall;
}
@ -85,6 +87,11 @@ class App extends PDFObject {
}
_evalCallback({ callbackId, interval }) {
if (callbackId === USERACTIVATION_CALLBACKID) {
// Special callback id for userActivation stuff.
this._document.obj._userActivation = false;
return;
}
const expr = this._timeoutCallbackIds.get(callbackId);
if (!interval) {
this._unregisterTimeoutCallback(callbackId);
@ -429,6 +436,11 @@ class App extends PDFObject {
oDoc = null,
oCheckbox = null
) {
if (!this._document.obj._userActivation) {
return 0;
}
this._document.obj._userActivation = false;
if (cMsg && typeof cMsg === "object") {
nType = cMsg.nType;
cMsg = cMsg.cMsg;
@ -475,8 +487,18 @@ class App extends PDFObject {
}
execMenuItem(item) {
if (!this._document.obj._userActivation) {
return;
}
this._document.obj._userActivation = false;
switch (item) {
case "SaveAs":
if (this._document.obj._disableSaving) {
return;
}
this._send({ command: item });
break;
case "FirstPage":
case "LastPage":
case "NextPage":
@ -489,6 +511,9 @@ class App extends PDFObject {
this._send({ command: "zoom", value: "page-fit" });
break;
case "Print":
if (this._document.obj._disablePrinting) {
return;
}
this._send({ command: "print" });
break;
}
@ -629,4 +654,4 @@ class App extends PDFObject {
}
}
export { App };
export { App, USERACTIVATION_CALLBACKID };

View File

@ -96,6 +96,9 @@ class Doc extends PDFObject {
this._actions = createActionsMap(data.actions);
this._globalEval = data.globalEval;
this._pageActions = new Map();
this._userActivation = false;
this._disablePrinting = false;
this._disableSaving = false;
}
_dispatchDocEvent(name) {
@ -108,12 +111,27 @@ class Doc extends PDFObject {
"DidPrint",
"OpenAction",
]);
// When a pdf has just been opened it doesn't really make sense
// to save it: it's up to the user to decide if they want to do that.
// A pdf can contain an action /FooBar which will trigger a save
// even if there are no WillSave/DidSave (which are themselves triggered
// after a save).
this._disableSaving = true;
for (const actionName of this._actions.keys()) {
if (!dontRun.has(actionName)) {
this._runActions(actionName);
}
}
this._runActions("OpenAction");
this._disableSaving = false;
} else if (name === "WillPrint") {
this._disablePrinting = true;
this._runActions(name);
this._disablePrinting = false;
} else if (name === "WillSave") {
this._disableSaving = true;
this._runActions(name);
this._disableSaving = false;
} else {
this._runActions(name);
}
@ -361,6 +379,11 @@ class Doc extends PDFObject {
}
set layout(value) {
if (!this._userActivation) {
return;
}
this._userActivation = false;
if (typeof value !== "string") {
return;
}
@ -480,6 +503,11 @@ class Doc extends PDFObject {
}
set pageNum(value) {
if (!this._userActivation) {
return;
}
this._userActivation = false;
if (typeof value !== "number" || value < 0 || value >= this._numPages) {
return;
}
@ -628,6 +656,11 @@ class Doc extends PDFObject {
}
set zoomType(type) {
if (!this._userActivation) {
return;
}
this._userActivation = false;
if (typeof type !== "string") {
return;
}
@ -662,6 +695,11 @@ class Doc extends PDFObject {
}
set zoom(value) {
if (!this._userActivation) {
return;
}
this._userActivation = false;
if (typeof value !== "number" || value < 8.33 || value > 6400) {
return;
}
@ -1057,6 +1095,11 @@ class Doc extends PDFObject {
bAnnotations = true,
printParams = null
) {
if (this._disablePrinting || !this._userActivation) {
return;
}
this._userActivation = false;
if (bUI && typeof bUI === "object") {
nStart = bUI.nStart;
nEnd = bUI.nEnd;

View File

@ -13,6 +13,10 @@
* limitations under the License.
*/
import { USERACTIVATION_CALLBACKID } from "./doc.js";
const USERACTIVATION_MAXTIME_VALIDITY = 5000;
class Event {
constructor(data) {
this.change = data.change || "";
@ -39,10 +43,11 @@ class Event {
}
class EventDispatcher {
constructor(document, calculationOrder, objects) {
constructor(document, calculationOrder, objects, externalCall) {
this._document = document;
this._calculationOrder = calculationOrder;
this._objects = objects;
this._externalCall = externalCall;
this._document.obj._eventDispatcher = this;
this._isCalculating = false;
@ -66,6 +71,14 @@ class EventDispatcher {
return `${prefix}${event.change}${postfix}`;
}
userActivation() {
this._document.obj._userActivation = true;
this._externalCall("setTimeout", [
USERACTIVATION_CALLBACKID,
USERACTIVATION_MAXTIME_VALIDITY,
]);
}
dispatch(baseEvent) {
const id = baseEvent.id;
if (!(id in this._objects)) {
@ -76,19 +89,27 @@ class EventDispatcher {
event.name = baseEvent.name;
}
if (id === "doc") {
if (event.name === "Open") {
const eventName = event.name;
if (eventName === "Open") {
// Before running the Open event, we format all the fields
// (see bug 1766987).
this.formatAll();
}
if (
!["DidPrint", "DidSave", "WillPrint", "WillSave"].includes(eventName)
) {
this.userActivation();
}
this._document.obj._dispatchDocEvent(event.name);
} else if (id === "page") {
this.userActivation();
this._document.obj._dispatchPageEvent(
event.name,
baseEvent.actions,
baseEvent.pageNumber
);
} else if (id === "app" && baseEvent.name === "ResetForm") {
this.userActivation();
for (const fieldId of baseEvent.ids) {
const obj = this._objects[fieldId];
obj?.obj._reset();
@ -102,6 +123,8 @@ class EventDispatcher {
const event = (globalThis.event = new Event(baseEvent));
let savedChange;
this.userActivation();
if (source.obj._isButton()) {
source.obj._id = id;
event.value = source.obj._getExportValue(event.value);