Merge pull request #12635 from calixteman/js_display_evts

JS -- Send events to the sandbox from annotation layer
This commit is contained in:
Brendan Dahl 2020-12-15 21:24:55 -08:00 committed by GitHub
commit 3c603fb28b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 647 additions and 113 deletions

View File

@ -365,7 +365,11 @@ class LinkAnnotationElement extends AnnotationElement {
parameters.data.url ||
parameters.data.dest ||
parameters.data.action ||
parameters.data.isTooltipOnly
parameters.data.isTooltipOnly ||
(parameters.data.actions &&
(parameters.data.actions.Action ||
parameters.data.actions.MouseUp ||
parameters.data.actions.MouseDown))
);
super(parameters, { isRenderable, createQuadrilaterals: true });
}
@ -387,6 +391,13 @@ class LinkAnnotationElement extends AnnotationElement {
this._bindNamedAction(link, data.action);
} else if (data.dest) {
this._bindLink(link, data.dest);
} else if (
data.actions &&
(data.actions.Action || data.actions.MouseUp || data.actions.MouseDown) &&
this.enableScripting &&
this.hasJSActions
) {
this._bindJSAction(link);
} else {
this._bindLink(link, "");
}
@ -443,6 +454,42 @@ class LinkAnnotationElement extends AnnotationElement {
};
link.className = "internalLink";
}
/**
* Bind JS actions to the link element.
*
* @private
* @param {Object} link
* @param {Object} data
* @memberof LinkAnnotationElement
*/
_bindJSAction(link) {
link.href = this.linkService.getAnchorUrl("#");
const { data } = this;
const map = new Map([
["Action", "onclick"],
["MouseUp", "onmouseup"],
["MouseDown", "onmousedown"],
]);
for (const name of Object.keys(data.actions)) {
const jsName = map.get(name);
if (!jsName) {
continue;
}
link[jsName] = () => {
window.dispatchEvent(
new CustomEvent("dispatchEventInSandbox", {
detail: {
id: data.id,
name,
},
})
);
return false;
};
}
link.className = "internalLink";
}
}
class TextAnnotationElement extends AnnotationElement {
@ -488,6 +535,53 @@ class WidgetAnnotationElement extends AnnotationElement {
return this.container;
}
_getKeyModifier(event) {
return (
(navigator.platform.includes("Win") && event.ctrlKey) ||
(navigator.platform.includes("Mac") && event.metaKey)
);
}
_setEventListener(element, baseName, eventName, valueGetter) {
if (this.data.actions && eventName.replace(" ", "") in this.data.actions) {
if (baseName.includes("mouse")) {
// Mouse events
element.addEventListener(baseName, event => {
window.dispatchEvent(
new CustomEvent("dispatchEventInSandbox", {
detail: {
id: this.data.id,
name: eventName,
value: valueGetter(event),
shift: event.shiftKey,
modifier: this._getKeyModifier(event),
},
})
);
});
} else {
// Non mouse event
element.addEventListener(baseName, event => {
window.dispatchEvent(
new CustomEvent("dispatchEventInSandbox", {
detail: {
id: this.data.id,
name: eventName,
value: event.target.checked,
},
})
);
});
}
}
}
_setEventListeners(element, names, getter) {
for (const [baseName, eventName] of names) {
this._setEventListener(element, baseName, eventName, getter);
}
}
}
class TextWidgetAnnotationElement extends WidgetAnnotationElement {
@ -496,6 +590,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
parameters.renderInteractiveForms ||
(!parameters.data.hasAppearance && !!parameters.data.fieldValue);
super(parameters, { isRenderable });
this.mouseState = parameters.mouseState;
}
render() {
@ -513,6 +608,12 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
const textContent = storage.getOrCreateValue(id, {
value: this.data.fieldValue,
}).value;
const elementData = {
userValue: null,
formattedValue: null,
beforeInputSelectionRange: null,
beforeInputValue: null,
};
if (this.data.multiLine) {
element = document.createElement("textarea");
@ -523,104 +624,196 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
element.setAttribute("value", textContent);
}
element.userValue = textContent;
elementData.userValue = textContent;
element.setAttribute("id", id);
element.addEventListener("input", function (event) {
storage.setValue(id, { value: event.target.value });
});
element.addEventListener("blur", function (event) {
let blurListener = event => {
if (elementData.formattedValue) {
event.target.value = elementData.formattedValue;
}
event.target.setSelectionRange(0, 0);
});
elementData.beforeInputSelectionRange = null;
};
if (this.enableScripting && this.hasJSActions) {
element.addEventListener("focus", event => {
if (event.target.userValue) {
event.target.value = event.target.userValue;
if (elementData.userValue) {
event.target.value = elementData.userValue;
}
});
if (this.data.actions) {
element.addEventListener("updateFromSandbox", function (event) {
const detail = event.detail;
const actions = {
value() {
const value = detail.value;
if (value === undefined || value === null) {
// remove data
event.target.userValue = "";
} else {
event.target.userValue = value;
}
},
valueAsString() {
const value = detail.valueAsString;
if (value === undefined || value === null) {
// remove data
event.target.value = "";
} else {
event.target.value = value;
}
storage.setValue(id, event.target.value);
},
focus() {
event.target.focus({ preventScroll: false });
},
userName() {
const tooltip = detail.userName;
event.target.title = tooltip;
},
hidden() {
event.target.style.display = detail.hidden ? "none" : "block";
},
editable() {
event.target.disabled = !detail.editable;
},
selRange() {
const [selStart, selEnd] = detail.selRange;
if (selStart >= 0 && selEnd < event.target.value.length) {
event.target.setSelectionRange(selStart, selEnd);
}
},
strokeColor() {
const color = detail.strokeColor;
event.target.style.color = ColorConverters[`${color[0]}_HTML`](
color.slice(1)
);
},
};
for (const name of Object.keys(detail)) {
if (name in actions) {
actions[name]();
element.addEventListener("updateFromSandbox", function (event) {
const { detail } = event;
const actions = {
value() {
elementData.userValue = detail.value || "";
storage.setValue(id, { value: elementData.userValue.toString() });
},
valueAsString() {
elementData.formattedValue = detail.valueAsString || "";
if (event.target !== document.activeElement) {
// Input hasn't the focus so display formatted string
event.target.value = elementData.formattedValue;
}
storage.setValue(id, {
formattedValue: elementData.formattedValue,
});
},
focus() {
setTimeout(() => event.target.focus({ preventScroll: false }), 0);
},
userName() {
// tooltip
event.target.title = detail.userName;
},
hidden() {
event.target.style.visibility = detail.hidden
? "hidden"
: "visible";
storage.setValue(id, { hidden: detail.hidden });
},
editable() {
event.target.disabled = !detail.editable;
},
selRange() {
const [selStart, selEnd] = detail.selRange;
if (selStart >= 0 && selEnd < event.target.value.length) {
event.target.setSelectionRange(selStart, selEnd);
}
},
strokeColor() {
const color = detail.strokeColor;
event.target.style.color = ColorConverters[`${color[0]}_HTML`](
color.slice(1)
);
},
};
Object.keys(detail)
.filter(name => name in actions)
.forEach(name => actions[name]());
});
if (this.data.actions) {
// Even if the field hasn't any actions
// leaving it can still trigger some actions with Calculate
element.addEventListener("keydown", event => {
elementData.beforeInputValue = event.target.value;
// if the key is one of Escape, Enter or Tab
// then the data are committed
let commitKey = -1;
if (event.key === "Escape") {
commitKey = 0;
} else if (event.key === "Enter") {
commitKey = 2;
} else if (event.key === "Tab") {
commitKey = 3;
}
if (commitKey === -1) {
return;
}
// Save the entered value
elementData.userValue = event.target.value;
window.dispatchEvent(
new CustomEvent("dispatchEventInSandbox", {
detail: {
id,
name: "Keystroke",
value: event.target.value,
willCommit: true,
commitKey,
selStart: event.target.selectionStart,
selEnd: event.target.selectionEnd,
},
})
);
});
const _blurListener = blurListener;
blurListener = null;
element.addEventListener("blur", event => {
if (this.mouseState.isDown) {
// Focus out using the mouse: data are committed
elementData.userValue = event.target.value;
window.dispatchEvent(
new CustomEvent("dispatchEventInSandbox", {
detail: {
id,
name: "Keystroke",
value: event.target.value,
willCommit: true,
commitKey: 1,
selStart: event.target.selectionStart,
selEnd: event.target.selectionEnd,
},
})
);
}
_blurListener(event);
});
element.addEventListener("mousedown", event => {
elementData.beforeInputValue = event.target.value;
elementData.beforeInputSelectionRange = null;
});
element.addEventListener("keyup", event => {
// keyup is triggered after input
if (event.target.selectionStart === event.target.selectionEnd) {
elementData.beforeInputSelectionRange = null;
}
});
element.addEventListener("select", event => {
elementData.beforeInputSelectionRange = [
event.target.selectionStart,
event.target.selectionEnd,
];
});
for (const eventType of Object.keys(this.data.actions)) {
switch (eventType) {
case "Format":
element.addEventListener("change", function (event) {
window.dispatchEvent(
new CustomEvent("dispatchEventInSandbox", {
detail: {
id,
name: "Keystroke",
value: event.target.value,
willCommit: true,
commitKey: 1,
selStart: event.target.selectionStart,
selEnd: event.target.selectionEnd,
},
})
);
});
break;
}
if ("Keystroke" in this.data.actions) {
// We should use beforeinput but this
// event isn't available in Firefox
element.addEventListener("input", event => {
let selStart = -1;
let selEnd = -1;
if (elementData.beforeInputSelectionRange) {
[selStart, selEnd] = elementData.beforeInputSelectionRange;
}
window.dispatchEvent(
new CustomEvent("dispatchEventInSandbox", {
detail: {
id,
name: "Keystroke",
value: elementData.beforeInputValue,
change: event.data,
willCommit: false,
selStart,
selEnd,
},
})
);
});
}
this._setEventListeners(
element,
[
["focus", "Focus"],
["blur", "Blur"],
["mousedown", "Mouse Down"],
["mouseenter", "Mouse Enter"],
["mouseleave", "Mouse Exit"],
["mouseup", "MouseUp"],
],
event => event.target.value
);
}
}
if (blurListener) {
element.addEventListener("blur", blurListener);
}
element.disabled = this.data.readOnly;
element.name = this.data.fieldName;
@ -715,6 +908,7 @@ class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement {
if (value) {
element.setAttribute("checked", true);
}
element.setAttribute("id", id);
element.addEventListener("change", function (event) {
const name = event.target.name;
@ -730,6 +924,48 @@ class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement {
storage.setValue(id, { value: event.target.checked });
});
if (this.enableScripting && this.hasJSActions) {
element.addEventListener("updateFromSandbox", event => {
const { detail } = event;
const actions = {
value() {
event.target.checked = detail.value !== "Off";
storage.setValue(id, { value: event.target.checked });
},
focus() {
setTimeout(() => event.target.focus({ preventScroll: false }), 0);
},
hidden() {
event.target.style.visibility = detail.hidden
? "hidden"
: "visible";
storage.setValue(id, { hidden: detail.hidden });
},
editable() {
event.target.disabled = !detail.editable;
},
};
Object.keys(detail)
.filter(name => name in actions)
.forEach(name => actions[name]());
});
this._setEventListeners(
element,
[
["change", "Validate"],
["change", "Action"],
["focus", "Focus"],
["blur", "Blur"],
["mousedown", "Mouse Down"],
["mouseenter", "Mouse Enter"],
["mouseleave", "Mouse Exit"],
["mouseup", "MouseUp"],
],
event => event.target.checked
);
}
this.container.appendChild(element);
return this.container;
}
@ -756,20 +992,69 @@ class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement {
if (value) {
element.setAttribute("checked", true);
}
element.setAttribute("pdfButtonValue", data.buttonValue);
element.setAttribute("id", id);
element.addEventListener("change", function (event) {
const name = event.target.name;
for (const radio of document.getElementsByName(name)) {
if (radio !== event.target) {
storage.setValue(
radio.parentNode.getAttribute("data-annotation-id"),
{ value: false }
);
const target = event.target;
for (const radio of document.getElementsByName(event.target.name)) {
if (radio !== target) {
storage.setValue(radio.getAttribute("id"), { value: false });
}
}
storage.setValue(id, { value: event.target.checked });
storage.setValue(id, { value: target.checked });
});
if (this.enableScripting && this.hasJSActions) {
element.addEventListener("updateFromSandbox", event => {
const { detail } = event;
const actions = {
value() {
const fieldValue = detail.value;
for (const radio of document.getElementsByName(event.target.name)) {
const radioId = radio.getAttribute("id");
if (fieldValue === radio.getAttribute("pdfButtonValue")) {
radio.setAttribute("checked", true);
storage.setValue(radioId, { value: true });
} else {
storage.setValue(radioId, { value: false });
}
}
},
focus() {
setTimeout(() => event.target.focus({ preventScroll: false }), 0);
},
hidden() {
event.target.style.visibility = detail.hidden
? "hidden"
: "visible";
storage.setValue(id, { hidden: detail.hidden });
},
editable() {
event.target.disabled = !detail.editable;
},
};
Object.keys(detail)
.filter(name => name in actions)
.forEach(name => actions[name]());
});
this._setEventListeners(
element,
[
["change", "Validate"],
["change", "Action"],
["focus", "Focus"],
["blur", "Blur"],
["mousedown", "Mouse Down"],
["mouseenter", "Mouse Enter"],
["mouseleave", "Mouse Exit"],
["mouseup", "MouseUp"],
],
event => event.target.checked
);
}
this.container.appendChild(element);
return this.container;
}
@ -816,6 +1101,7 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
const selectElement = document.createElement("select");
selectElement.disabled = this.data.readOnly;
selectElement.name = this.data.fieldName;
selectElement.setAttribute("id", id);
if (!this.data.combo) {
// List boxes have a size and (optionally) multiple selection.
@ -836,11 +1122,77 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
selectElement.appendChild(optionElement);
}
selectElement.addEventListener("input", function (event) {
function getValue(event) {
const options = event.target.options;
const value = options[options.selectedIndex].value;
storage.setValue(id, { value });
});
return options[options.selectedIndex].value;
}
if (this.enableScripting && this.hasJSActions) {
selectElement.addEventListener("updateFromSandbox", event => {
const { detail } = event;
const actions = {
value() {
const options = event.target.options;
const value = detail.value;
const i = options.indexOf(value);
if (i !== -1) {
options.selectedIndex = i;
storage.setValue(id, { value });
}
},
focus() {
setTimeout(() => event.target.focus({ preventScroll: false }), 0);
},
hidden() {
event.target.style.visibility = detail.hidden
? "hidden"
: "visible";
storage.setValue(id, { hidden: detail.hidden });
},
editable() {
event.target.disabled = !detail.editable;
},
};
Object.keys(detail)
.filter(name => name in actions)
.forEach(name => actions[name]());
});
selectElement.addEventListener("input", function (event) {
const value = getValue(event);
storage.setValue(id, { value });
window.dispatchEvent(
new CustomEvent("dispatchEventInSandbox", {
detail: {
id,
name: "Keystroke",
changeEx: value,
willCommit: true,
commitKey: 1,
keyDown: false,
},
})
);
});
this._setEventListeners(
selectElement,
[
["focus", "Focus"],
["blur", "Blur"],
["mousedown", "Mouse Down"],
["mouseenter", "Mouse Enter"],
["mouseleave", "Mouse Exit"],
["mouseup", "MouseUp"],
],
event => event.target.checked
);
} else {
selectElement.addEventListener("input", function (event) {
storage.setValue(id, { value: getValue(event) });
});
}
this.container.appendChild(selectElement);
return this.container;
@ -1599,6 +1951,7 @@ class AnnotationLayer {
parameters.annotationStorage || new AnnotationStorage(),
enableScripting: parameters.enableScripting,
hasJSActions: parameters.hasJSActions,
mouseState: parameters.mouseState,
});
if (element.isRenderable) {
const rendered = element.render();

View File

@ -913,6 +913,7 @@ class Doc extends PDFObject {
const field = this.getField(fieldName);
if (field) {
field.value = field.defaultValue;
field.valueAsString = field.value;
mustCalculate = true;
}
}
@ -920,6 +921,7 @@ class Doc extends PDFObject {
mustCalculate = this._fields.size !== 0;
for (const field of this._fields.values()) {
field.value = field.defaultValue;
field.valueAsString = field.value;
}
}
if (mustCalculate) {

View File

@ -66,7 +66,9 @@ class Field extends PDFObject {
this.type = data.type;
this.userName = data.userName;
this.value = data.value || "";
this.valueAsString = data.valueAsString;
// Need getter/setter
this._valueAsString = data.valueAsString;
// Private
this._document = data.doc;
@ -107,6 +109,28 @@ class Field extends PDFObject {
}
}
get valueAsString() {
return this._valueAsString;
}
set valueAsString(val) {
this._valueAsString = val ? val.toString() : "";
}
_getFunction(code, actionName) {
try {
// This eval is running in a sandbox so it's safe to use Function
// eslint-disable-next-line no-new-func
return Function("event", `with (this) {${code}}`).bind(this._document);
} catch (error) {
const value =
`"${error.toString()}" for action ` +
`"${actionName}" in object ${this._id}.`;
this._send({ command: "error", value });
}
return null;
}
setAction(cTrigger, cScript) {
if (typeof cTrigger !== "string" || typeof cScript !== "string") {
return;
@ -114,10 +138,10 @@ class Field extends PDFObject {
if (!(cTrigger in this._actions)) {
this._actions[cTrigger] = [];
}
this._actions[cTrigger].push(
// eslint-disable-next-line no-new-func
Function("event", `with (this) {${cScript}}`).bind(this._document)
);
const fun = this._getFunction(cScript, cTrigger);
if (fun) {
this._actions[cTrigger].push(fun);
}
}
setFocus() {
@ -127,16 +151,13 @@ class Field extends PDFObject {
_createActionsMap(actions) {
const actionsMap = new Map();
if (actions) {
const doc = this._document;
for (const [eventType, actionsForEvent] of Object.entries(actions)) {
// This stuff is running in a sandbox so it's safe to use Function
actionsMap.set(
eventType,
actionsForEvent.map(action =>
// eslint-disable-next-line no-new-func
Function("event", `with (this) {${action}}`).bind(doc)
)
);
const functions = actionsForEvent
.map(action => this._getFunction(action, eventType))
.filter(fun => !!fun);
if (functions.length > 0) {
actionsMap.set(eventType, functions);
}
}
}
return actionsMap;

View File

@ -54,7 +54,7 @@ class ProxyHandler {
obj[prop] = value;
if (obj._send && obj._id !== null && typeof old !== "function") {
const data = { id: obj._id };
data[prop] = value;
data[prop] = obj[prop];
// send the updated value to the other side
obj._send(data);

View File

@ -27,6 +27,44 @@ describe("Interaction", () => {
await closePages(pages);
});
it("must show a text field and then make in invisible when content is removed", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
let visibility = await page.$eval(
"#\\34 27R",
el => getComputedStyle(el).visibility
);
expect(visibility).withContext(`In ${browserName}`).toEqual("hidden");
await page.type("#\\34 16R", "3.14159", { delay: 200 });
await page.click("#\\34 19R");
visibility = await page.$eval(
"#\\34 27R",
el => getComputedStyle(el).visibility
);
expect(visibility)
.withContext(`In ${browserName}`)
.toEqual("visible");
// Clear the textfield
await page.click("#\\34 16R");
await page.keyboard.down("Control");
await page.keyboard.press("A");
await page.keyboard.up("Control");
await page.keyboard.press("Backspace");
// and leave it
await page.click("#\\34 19R");
visibility = await page.$eval(
"#\\34 27R",
el => getComputedStyle(el).visibility
);
expect(visibility).withContext(`In ${browserName}`).toEqual("hidden");
})
);
});
it("must format the field with 2 digits and leave field with a click", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
@ -34,6 +72,35 @@ describe("Interaction", () => {
await page.click("#\\34 19R");
const text = await page.$eval("#\\34 16R", el => el.value);
expect(text).withContext(`In ${browserName}`).toEqual("3,14");
const sum = await page.$eval("#\\34 27R", el => el.value);
expect(sum).withContext(`In ${browserName}`).toEqual("3,14");
})
);
});
it("must format the field with 2 digits, leave field with a click and again", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.type("#\\34 48R", "61803", { delay: 200 });
await page.click("#\\34 19R");
let text = await page.$eval("#\\34 48R", el => el.value);
expect(text).withContext(`In ${browserName}`).toEqual("61.803,00");
await page.click("#\\34 48R");
text = await page.$eval("#\\34 48R", el => el.value);
expect(text).withContext(`In ${browserName}`).toEqual("61803");
// Clear the textfield
await page.keyboard.down("Control");
await page.keyboard.press("A");
await page.keyboard.up("Control");
await page.keyboard.press("Backspace");
await page.type("#\\34 48R", "1.61803", { delay: 200 });
await page.click("#\\34 19R");
text = await page.$eval("#\\34 48R", el => el.value);
expect(text).withContext(`In ${browserName}`).toEqual("1,62");
})
);
});
@ -45,6 +112,67 @@ describe("Interaction", () => {
await page.keyboard.press("Tab");
const text = await page.$eval("#\\34 22R", el => el.value);
expect(text).withContext(`In ${browserName}`).toEqual("2,72");
const sum = await page.$eval("#\\34 27R", el => el.value);
expect(sum).withContext(`In ${browserName}`).toEqual("5,86");
})
);
});
it("must format the field with 2 digits and hit ESC", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
let sum = await page.$eval("#\\34 71R", el => el.value);
expect(sum).withContext(`In ${browserName}`).toEqual("4,24");
await page.type("#\\34 36R", "0.69314", { delay: 200 });
await page.keyboard.press("Escape");
const text = await page.$eval("#\\34 36R", el => el.value);
expect(text).withContext(`In ${browserName}`).toEqual("0.69314");
sum = await page.$eval("#\\34 71R", el => el.value);
expect(sum).withContext(`In ${browserName}`).toEqual("3,55");
})
);
});
it("must format the field with 2 digits on key ENTER", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.type("#\\34 19R", "0.577215", { delay: 200 });
await page.keyboard.press("Enter");
const text = await page.$eval("#\\34 19R", el => el.value);
expect(text).toEqual("0.577215");
const sum = await page.$eval("#\\34 27R", el => el.value);
expect(sum).toEqual("6,44");
})
);
});
it("must reset all", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
// this field has no actions but it must be cleared on reset
await page.type("#\\34 05R", "employee", { delay: 200 });
// click on reset button
await page.click("[data-annotation-id='402R']");
let text = await page.$eval("#\\34 16R", el => el.value);
expect(text).toEqual("");
text = await page.$eval("#\\34 22R", el => el.value);
expect(text).toEqual("");
text = await page.$eval("#\\34 19R", el => el.value);
expect(text).toEqual("");
text = await page.$eval("#\\34 05R", el => el.value);
expect(text).toEqual("");
const sum = await page.$eval("#\\34 27R", el => el.value);
expect(sum).toEqual("");
})
);
});

View File

@ -84,7 +84,7 @@ describe("Scripting", function () {
return s;
}
const number = 123;
const expected = ((number - 1) * number) / 2;
const expected = (((number - 1) * number) / 2).toString();
const refId = getId();
const data = {
@ -1094,7 +1094,7 @@ describe("Scripting", function () {
expect(send_queue.get(refIds[3])).toEqual({
id: refIds[3],
value: 1,
valueAsString: 1,
valueAsString: "1",
});
await sandbox.dispatchEventInSandbox({
@ -1107,7 +1107,7 @@ describe("Scripting", function () {
expect(send_queue.get(refIds[3])).toEqual({
id: refIds[3],
value: 3,
valueAsString: 3,
valueAsString: "3",
});
await sandbox.dispatchEventInSandbox({
@ -1120,7 +1120,7 @@ describe("Scripting", function () {
expect(send_queue.get(refIds[3])).toEqual({
id: refIds[3],
value: 6,
valueAsString: 6,
valueAsString: "6",
});
done();

View File

@ -47,6 +47,7 @@ class AnnotationLayerBuilder {
l10n = NullL10n,
enableScripting = false,
hasJSActionsPromise = null,
mouseState = null,
}) {
this.pageDiv = pageDiv;
this.pdfPage = pdfPage;
@ -58,6 +59,7 @@ class AnnotationLayerBuilder {
this.annotationStorage = annotationStorage;
this.enableScripting = enableScripting;
this._hasJSActionsPromise = hasJSActionsPromise;
this._mouseState = mouseState;
this.div = null;
this._cancelled = false;
@ -93,6 +95,7 @@ class AnnotationLayerBuilder {
annotationStorage: this.annotationStorage,
enableScripting: this.enableScripting,
hasJSActions,
mouseState: this._mouseState,
};
if (this.div) {
@ -139,6 +142,7 @@ class DefaultAnnotationLayerFactory {
* @param {IL10n} l10n
* @param {boolean} [enableScripting]
* @param {Promise<boolean>} [hasJSActionsPromise]
* @param {Object} [mouseState]
* @returns {AnnotationLayerBuilder}
*/
createAnnotationLayerBuilder(
@ -149,7 +153,8 @@ class DefaultAnnotationLayerFactory {
renderInteractiveForms = true,
l10n = NullL10n,
enableScripting = false,
hasJSActionsPromise = null
hasJSActionsPromise = null,
mouseState = null
) {
return new AnnotationLayerBuilder({
pageDiv,
@ -161,6 +166,7 @@ class DefaultAnnotationLayerFactory {
annotationStorage,
enableScripting,
hasJSActionsPromise,
mouseState,
});
}
}

View File

@ -258,6 +258,7 @@ const PDFViewerApplication = {
_wheelUnusedTicks: 0,
_idleCallbacks: new Set(),
_scriptingInstance: null,
_mouseState: Object.create(null),
// Called once when the document is loaded.
async initialize(appConfig) {
@ -501,6 +502,7 @@ const PDFViewerApplication = {
useOnlyCssZoom: AppOptions.get("useOnlyCssZoom"),
maxCanvasPixels: AppOptions.get("maxCanvasPixels"),
enableScripting: AppOptions.get("enableScripting"),
mouseState: this._mouseState,
});
pdfRenderingQueue.setViewer(this.pdfViewer);
pdfLinkService.setViewer(this.pdfViewer);
@ -1538,6 +1540,17 @@ const PDFViewerApplication = {
dispatchEventInSandbox
);
const mouseDown = event => {
this._mouseState.isDown = true;
};
const mouseUp = event => {
this._mouseState.isDown = false;
};
window.addEventListener("mousedown", mouseDown);
this._scriptingInstance.events.set("mousedown", mouseDown);
window.addEventListener("mouseup", mouseUp);
this._scriptingInstance.events.set("mouseup", mouseUp);
const dispatchEventName = generateRandomStringForSandbox(objects);
if (!this._contentLength) {

View File

@ -79,6 +79,7 @@ const DEFAULT_CACHE_SIZE = 10;
* @property {IL10n} l10n - Localization service.
* @property {boolean} [enableScripting] - Enable embedded script execution.
* The default value is `false`.
* @property {Object} [mouseState] - The mouse button state.
*/
function PDFPageViewBuffer(size) {
@ -194,6 +195,7 @@ class BaseViewer {
this.maxCanvasPixels = options.maxCanvasPixels;
this.l10n = options.l10n || NullL10n;
this.enableScripting = options.enableScripting || false;
this.mouseState = options.mouseState || null;
this.defaultRenderingQueue = !options.renderingQueue;
if (this.defaultRenderingQueue) {
@ -533,6 +535,7 @@ class BaseViewer {
maxCanvasPixels: this.maxCanvasPixels,
l10n: this.l10n,
enableScripting: this.enableScripting,
mouseState: this.mouseState,
});
this._pages.push(pageView);
}
@ -1265,6 +1268,7 @@ class BaseViewer {
* @param {IL10n} l10n
* @param {boolean} [enableScripting]
* @param {Promise<boolean>} [hasJSActionsPromise]
* @param {Object} [mouseState]
* @returns {AnnotationLayerBuilder}
*/
createAnnotationLayerBuilder(
@ -1275,7 +1279,8 @@ class BaseViewer {
renderInteractiveForms = false,
l10n = NullL10n,
enableScripting = false,
hasJSActionsPromise = null
hasJSActionsPromise = null,
mouseState = null
) {
return new AnnotationLayerBuilder({
pageDiv,
@ -1290,6 +1295,7 @@ class BaseViewer {
enableScripting,
hasJSActionsPromise:
hasJSActionsPromise || this.pdfDocument?.hasJSActions(),
mouseState,
});
}

View File

@ -188,6 +188,7 @@ class IPDFAnnotationLayerFactory {
* @param {IL10n} l10n
* @param {boolean} [enableScripting]
* @param {Promise<boolean>} [hasJSActionsPromise]
* @param {Object} [mouseState]
* @returns {AnnotationLayerBuilder}
*/
createAnnotationLayerBuilder(
@ -198,7 +199,8 @@ class IPDFAnnotationLayerFactory {
renderInteractiveForms = true,
l10n = undefined,
enableScripting = false,
hasJSActionsPromise = null
hasJSActionsPromise = null,
mouseState = null
) {}
}

View File

@ -63,6 +63,7 @@ import { viewerCompatibilityParams } from "./viewer_compatibility.js";
* @property {IL10n} l10n - Localization service.
* @property {boolean} [enableScripting] - Enable embedded script execution.
* The default value is `false`.
* @property {Object} [mouseState] - The mouse button state.
*/
const MAX_CANVAS_PIXELS = viewerCompatibilityParams.maxCanvasPixels || 16777216;
@ -109,6 +110,7 @@ class PDFPageView {
this.enableWebGL = options.enableWebGL || false;
this.l10n = options.l10n || NullL10n;
this.enableScripting = options.enableScripting || false;
this.mouseState = options.mouseState || null;
this.paintTask = null;
this.paintedViewportMap = new WeakMap();
@ -551,7 +553,8 @@ class PDFPageView {
this.renderInteractiveForms,
this.l10n,
this.enableScripting,
/* hasJSActionsPromise = */ null
/* hasJSActionsPromise = */ null,
this.mouseState
);
}
this._renderAnnotationLayer();